diff --git a/.editorconfig b/.editorconfig index f84e6de2a0..2e3045fb17 100644 --- a/.editorconfig +++ b/.editorconfig @@ -253,6 +253,9 @@ csharp_space_between_square_brackets = false # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#wrap-options csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true +# Namespace options +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#namespace-options +csharp_style_namespace_declarations = file_scoped:warning ########################################## # .NET Naming Rules @@ -452,4 +455,4 @@ dotnet_naming_rule.parameters_rule.severity = warning # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -########################################## \ No newline at end of file +########################################## diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..e6e79e0408 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,4 @@ +# file-scoped namespaces and global usings +5528a2923ccc63d776c91994b0b17a2c3ad5be94 +d14c82023fc01a6fca99c42212cb3c97991fae9e +0e9a066195a100ae56b4ca49cee9927fb15e1482 \ No newline at end of file diff --git a/README.md b/README.md index 34466eccd7..cfa6fc7d67 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ To clone ImageSharp locally, click the "Clone in [YOUR_OS]" button above or run git clone https://github.com/SixLabors/ImageSharp ``` +Then set the following config to ensure blame commands ignore mass reformatting commits. + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + If working with Windows please ensure that you have enabled long file paths in git (run as Administrator). ```bash diff --git a/SixLabors.ImageSharp.props b/SixLabors.ImageSharp.props new file mode 100644 index 0000000000..6ec4d8c594 --- /dev/null +++ b/SixLabors.ImageSharp.props @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/shared-infrastructure b/shared-infrastructure index 5eb77e2d9e..f79b8d073a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 5eb77e2d9eb4f0ece012c996941ab78db1af2a41 +Subproject commit f79b8d073aeb2deb4cb095a9d4b94aa2be26c848 diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 2fbb429957..10267c8ef7 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -1,185 +1,179 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Extension methods over Image{TPixel} +/// +public static class AdvancedImageExtensions { /// - /// Extension methods over Image{TPixel} + /// For a given file path find the best encoder to use via its extension. /// - public static class AdvancedImageExtensions + /// The source image. + /// The target file path to save the image to. + /// The file path is null. + /// No encoder available for provided path. + /// The matching . + public static IImageEncoder DetectEncoder(this Image source, string filePath) { - /// - /// For a given file path find the best encoder to use via its extension. - /// - /// The source image. - /// The target file path to save the image to. - /// The file path is null. - /// No encoder available for provided path. - /// The matching . - public static IImageEncoder DetectEncoder(this Image source, string filePath) - { - Guard.NotNull(filePath, nameof(filePath)); + Guard.NotNull(filePath, nameof(filePath)); - string ext = Path.GetExtension(filePath); - IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); - if (format is null) + string ext = Path.GetExtension(filePath); + IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); + if (format is null) + { + StringBuilder sb = new(); + sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); + foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) { - StringBuilder sb = new(); - sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}'. Registered encoders include:"); - foreach (IImageFormat fmt in source.GetConfiguration().ImageFormats) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); + sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", fmt.Name, string.Join(", ", fmt.FileExtensions), Environment.NewLine); } - IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + throw new NotSupportedException(sb.ToString()); + } + + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); - if (encoder is null) + if (encoder is null) + { + StringBuilder sb = new(); + sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); + foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { - StringBuilder sb = new(); - sb.AppendLine(CultureInfo.InvariantCulture, $"No encoder was found for extension '{ext}' using image format '{format.Name}'. Registered encoders include:"); - foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); + sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", enc.Key, enc.Value.GetType().Name, Environment.NewLine); } - return encoder; + throw new NotSupportedException(sb.ToString()); } - /// - /// Accepts a to implement a double-dispatch pattern in order to - /// apply pixel-specific operations on non-generic instances - /// - /// The source image. - /// The image visitor. - public static void AcceptVisitor(this Image source, IImageVisitor visitor) - => source.Accept(visitor); - - /// - /// Accepts a to implement a double-dispatch pattern in order to - /// apply pixel-specific operations on non-generic instances - /// - /// The source image. - /// The image visitor. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) - => source.AcceptAsync(visitor, cancellationToken); - - /// - /// Gets the configuration for the image. - /// - /// The source image. - /// Returns the configuration. - public static Configuration GetConfiguration(this Image source) - => GetConfiguration((IConfigurationProvider)source); - - /// - /// Gets the configuration for the image frame. - /// - /// The source image. - /// Returns the configuration. - public static Configuration GetConfiguration(this ImageFrame source) - => GetConfiguration((IConfigurationProvider)source); - - /// - /// Gets the configuration. - /// - /// The source image - /// Returns the bounds of the image - private static Configuration GetConfiguration(IConfigurationProvider source) - => source?.Configuration ?? Configuration.Default; - - /// - /// 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 . - /// - /// Thrown when the in . - 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 . - /// - /// Thrown when the in . - 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 of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Memory DangerousGetPixelRowMemory(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 encoder; + } - return source.PixelBuffer.GetSafeRowMemory(rowIndex); - } + /// + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances + /// + /// The source image. + /// The image visitor. + public static void AcceptVisitor(this Image source, IImageVisitor visitor) + => source.Accept(visitor); - /// - /// Gets the representation of the pixels as of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The type of the pixel. - /// The source. - /// The row. - /// The - public static Memory DangerousGetPixelRowMemory(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)); + /// + /// Accepts a to implement a double-dispatch pattern in order to + /// apply pixel-specific operations on non-generic instances + /// + /// The source image. + /// The image visitor. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) + => source.AcceptAsync(visitor, cancellationToken); - return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); - } + /// + /// Gets the configuration for the image. + /// + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this Image source) + => GetConfiguration((IConfigurationProvider)source); + + /// + /// Gets the configuration for the image frame. + /// + /// The source image. + /// Returns the configuration. + public static Configuration GetConfiguration(this ImageFrame source) + => GetConfiguration((IConfigurationProvider)source); + + /// + /// Gets the configuration. + /// + /// The source image + /// Returns the bounds of the image + private static Configuration GetConfiguration(IConfigurationProvider source) + => source?.Configuration ?? Configuration.Default; + + /// + /// 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 . + /// + /// Thrown when the in . + public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) + where TPixel : unmanaged, IPixel + => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); - /// - /// Gets the assigned to 'source'. - /// - /// The source image. - /// Returns the configuration. - internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source) - => GetConfiguration(source).MemoryAllocator; + /// + /// 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 . + /// + /// Thrown when the in . + 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 of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + public static Memory DangerousGetPixelRowMemory(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 contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The type of the pixel. + /// The source. + /// The row. + /// The + public static Memory DangerousGetPixelRowMemory(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'. + /// + /// The source image. + /// Returns the configuration. + internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source) + => GetConfiguration(source).MemoryAllocator; } diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 1732a6bdbf..9c285e21de 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; @@ -30,534 +29,533 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Unlike traditional Mono/.NET, code on the iPhone is statically compiled ahead of time instead of being +/// compiled on demand by a JIT compiler. This means there are a few limitations with respect to generics, +/// these are caused because not every possible generic instantiation can be determined up front at compile time. +/// The Aot Compiler is designed to overcome the limitations of this compiler. +/// None of the methods in this class should ever be called, the code only has to exist at compile-time to be picked up by the AoT compiler. +/// (Very similar to the LinkerIncludes.cs technique used in Xamarin.Android projects.) +/// +[ExcludeFromCodeCoverage] +internal static class AotCompilerTools { /// - /// Unlike traditional Mono/.NET, code on the iPhone is statically compiled ahead of time instead of being - /// compiled on demand by a JIT compiler. This means there are a few limitations with respect to generics, - /// these are caused because not every possible generic instantiation can be determined up front at compile time. - /// The Aot Compiler is designed to overcome the limitations of this compiler. - /// None of the methods in this class should ever be called, the code only has to exist at compile-time to be picked up by the AoT compiler. - /// (Very similar to the LinkerIncludes.cs technique used in Xamarin.Android projects.) + /// This is the method that seeds the AoT compiler. + /// None of these seed methods needs to actually be called to seed the compiler. + /// The calls just need to be present when the code is compiled, and each implementation will be built. /// - [ExcludeFromCodeCoverage] - internal static class AotCompilerTools + /// + /// This method doesn't actually do anything but serves an important purpose... + /// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception: + /// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode." + /// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT + /// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on + /// iOS so it bombs out. + /// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the + /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! + /// + [Preserve] + private static void SeedPixelFormats() { - /// - /// This is the method that seeds the AoT compiler. - /// None of these seed methods needs to actually be called to seed the compiler. - /// The calls just need to be present when the code is compiled, and each implementation will be built. - /// - /// - /// This method doesn't actually do anything but serves an important purpose... - /// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception: - /// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode." - /// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT - /// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on - /// iOS so it bombs out. - /// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the - /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! - /// - [Preserve] - private static void SeedPixelFormats() + try { - try - { - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - Unsafe.SizeOf(); - - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - Seed(); - } - catch - { - // nop - } - - throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime."); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + Unsafe.SizeOf(); + + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); + Seed(); } - - /// - /// Seeds the compiler using the given pixel format. - /// - /// The pixel format. - [Preserve] - private static void Seed() - where TPixel : unmanaged, IPixel + catch { - // This is we actually call all the individual methods you need to seed. - AotCompileImage(); - AotCompileImageProcessingContextFactory(); - AotCompileImageEncoderInternals(); - AotCompileImageDecoderInternals(); - AotCompileImageEncoders(); - AotCompileImageDecoders(); - AotCompileImageProcessors(); - AotCompileGenericImageProcessors(); - AotCompileResamplers(); - AotCompileQuantizers(); - AotCompilePixelSamplingStrategys(); - AotCompileDithers(); - AotCompileMemoryManagers(); - - Unsafe.SizeOf(); - - // TODO: Do the discovery work to figure out what works and what doesn't. + // nop } - /// - /// This method pre-seeds the for a given pixel format in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static unsafe void AotCompileImage() - where TPixel : unmanaged, IPixel - { - Image img = default; - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - img.CloneAs(default); - - ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); - ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); - } + throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime."); + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageProcessingContextFactory() - where TPixel : unmanaged, IPixel - => default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext(default, default, default); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageEncoderInternals() - where TPixel : unmanaged, IPixel - { - default(WebpEncoderCore).Encode(default, default, default); - default(BmpEncoderCore).Encode(default, default, default); - default(GifEncoderCore).Encode(default, default, default); - default(JpegEncoderCore).Encode(default, default, default); - default(PbmEncoderCore).Encode(default, default, default); - default(PngEncoderCore).Encode(default, default, default); - default(TgaEncoderCore).Encode(default, default, default); - default(TiffEncoderCore).Encode(default, default, default); - } + /// + /// Seeds the compiler using the given pixel format. + /// + /// The pixel format. + [Preserve] + private static void Seed() + where TPixel : unmanaged, IPixel + { + // This is we actually call all the individual methods you need to seed. + AotCompileImage(); + AotCompileImageProcessingContextFactory(); + AotCompileImageEncoderInternals(); + AotCompileImageDecoderInternals(); + AotCompileImageEncoders(); + AotCompileImageDecoders(); + AotCompileImageProcessors(); + AotCompileGenericImageProcessors(); + AotCompileResamplers(); + AotCompileQuantizers(); + AotCompilePixelSamplingStrategys(); + AotCompileDithers(); + AotCompileMemoryManagers(); + + Unsafe.SizeOf(); + + // TODO: Do the discovery work to figure out what works and what doesn't. + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageDecoderInternals() - where TPixel : unmanaged, IPixel - { - default(WebpDecoderCore).Decode(default, default); - default(BmpDecoderCore).Decode(default, default); - default(GifDecoderCore).Decode(default, default); - default(JpegDecoderCore).Decode(default, default); - default(PbmDecoderCore).Decode(default, default); - default(PngDecoderCore).Decode(default, default); - default(TgaDecoderCore).Decode(default, default); - default(TiffDecoderCore).Decode(default, default); - } + /// + /// This method pre-seeds the for a given pixel format in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static unsafe void AotCompileImage() + where TPixel : unmanaged, IPixel + { + Image img = default; + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + img.CloneAs(default); + + ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); + ImageFrame.LoadPixelData(default, default(ReadOnlySpan), default, default); + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageEncoders() - where TPixel : unmanaged, IPixel - { - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - AotCompileImageEncoder(); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageProcessingContextFactory() + where TPixel : unmanaged, IPixel + => default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext(default, default, default); - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageDecoders() - where TPixel : unmanaged, IPixel - { - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - AotCompileImageDecoder(); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageEncoderInternals() + where TPixel : unmanaged, IPixel + { + default(WebpEncoderCore).Encode(default, default, default); + default(BmpEncoderCore).Encode(default, default, default); + default(GifEncoderCore).Encode(default, default, default); + default(JpegEncoderCore).Encode(default, default, default); + default(PbmEncoderCore).Encode(default, default, default); + default(PngEncoderCore).Encode(default, default, default); + default(TgaEncoderCore).Encode(default, default, default); + default(TiffEncoderCore).Encode(default, default, default); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The encoder. - [Preserve] - private static void AotCompileImageEncoder() - where TPixel : unmanaged, IPixel - where TEncoder : class, IImageEncoder - { - default(TEncoder).Encode(default, default); - default(TEncoder).EncodeAsync(default, default, default); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageDecoderInternals() + where TPixel : unmanaged, IPixel + { + default(WebpDecoderCore).Decode(default, default); + default(BmpDecoderCore).Decode(default, default); + default(GifDecoderCore).Decode(default, default); + default(JpegDecoderCore).Decode(default, default); + default(PbmDecoderCore).Decode(default, default); + default(PngDecoderCore).Decode(default, default); + default(TgaDecoderCore).Decode(default, default); + default(TiffDecoderCore).Decode(default, default); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The decoder. - [Preserve] - private static void AotCompileImageDecoder() - where TPixel : unmanaged, IPixel - where TDecoder : class, IImageDecoder - => default(TDecoder).Decode(default, default, default); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// - /// There is no structure that implements ISwizzler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileImageProcessors() - where TPixel : unmanaged, IPixel - { - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - AotCompileImageProcessor(); - - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - AotCompilerCloningImageProcessor(); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageEncoders() + where TPixel : unmanaged, IPixel + { + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + AotCompileImageEncoder(); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, IImageProcessor - => default(TProc).CreatePixelSpecificProcessor(default, default, default); - - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompilerCloningImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, ICloningImageProcessor - => default(TProc).CreatePixelSpecificCloningProcessor(default, default, default); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// - /// There is no structure that implements ISwizzler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileGenericImageProcessors() - where TPixel : unmanaged, IPixel - { - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - AotCompileGenericCloningImageProcessor>(); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageDecoders() + where TPixel : unmanaged, IPixel + { + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + AotCompileImageDecoder(); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileGenericCloningImageProcessor() - where TPixel : unmanaged, IPixel - where TProc : class, ICloningImageProcessor - => default(TProc).CloneAndExecute(); - - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileResamplers() - where TPixel : unmanaged, IPixel - { - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - AotCompileResampler(); - } + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The encoder. + [Preserve] + private static void AotCompileImageEncoder() + where TPixel : unmanaged, IPixel + where TEncoder : class, IImageEncoder + { + default(TEncoder).Encode(default, default); + default(TEncoder).EncodeAsync(default, default, default); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The processor type - [Preserve] - private static void AotCompileResampler() - where TPixel : unmanaged, IPixel - where TResampler : struct, IResampler - { - default(TResampler).ApplyTransform(default); + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The decoder. + [Preserve] + private static void AotCompileImageDecoder() + where TPixel : unmanaged, IPixel + where TDecoder : class, IImageDecoder + => default(TDecoder).Decode(default, default, default); - default(AffineTransformProcessor).ApplyTransform(default); - default(ProjectiveTransformProcessor).ApplyTransform(default); - default(ResizeProcessor).ApplyTransform(default); - default(RotateProcessor).ApplyTransform(default); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// + /// There is no structure that implements ISwizzler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileImageProcessors() + where TPixel : unmanaged, IPixel + { + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + AotCompileImageProcessor(); + + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + AotCompilerCloningImageProcessor(); + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileQuantizers() - where TPixel : unmanaged, IPixel - { - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - AotCompileQuantizer(); - } + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The processor type + [Preserve] + private static void AotCompileImageProcessor() + where TPixel : unmanaged, IPixel + where TProc : class, IImageProcessor + => default(TProc).CreatePixelSpecificProcessor(default, default, default); - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The quantizer type - [Preserve] - private static void AotCompileQuantizer() - where TPixel : unmanaged, IPixel + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The processor type + [Preserve] + private static void AotCompilerCloningImageProcessor() + where TPixel : unmanaged, IPixel + where TProc : class, ICloningImageProcessor + => default(TProc).CreatePixelSpecificCloningProcessor(default, default, default); - where TQuantizer : class, IQuantizer - { - default(TQuantizer).CreatePixelSpecificQuantizer(default); - default(TQuantizer).CreatePixelSpecificQuantizer(default, default); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// + /// There is no structure that implements ISwizzler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileGenericImageProcessors() + where TPixel : unmanaged, IPixel + { + AotCompileGenericCloningImageProcessor>(); + AotCompileGenericCloningImageProcessor>(); + AotCompileGenericCloningImageProcessor>(); + AotCompileGenericCloningImageProcessor>(); + AotCompileGenericCloningImageProcessor>(); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompilePixelSamplingStrategys() - where TPixel : unmanaged, IPixel - { - default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default); - default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default); - } + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The processor type + [Preserve] + private static void AotCompileGenericCloningImageProcessor() + where TPixel : unmanaged, IPixel + where TProc : class, ICloningImageProcessor + => default(TProc).CloneAndExecute(); - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileDithers() - where TPixel : unmanaged, IPixel - { - AotCompileDither(); - AotCompileDither(); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileResamplers() + where TPixel : unmanaged, IPixel + { + AotCompileResampler(); + AotCompileResampler(); + AotCompileResampler(); + AotCompileResampler(); + AotCompileResampler(); + AotCompileResampler(); + AotCompileResampler(); + } - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The dither. - [Preserve] - private static void AotCompileDither() + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The processor type + [Preserve] + private static void AotCompileResampler() where TPixel : unmanaged, IPixel - where TDither : struct, IDither - { - var octree = default(OctreeQuantizer); - default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default); + where TResampler : struct, IResampler + { + default(TResampler).ApplyTransform(default); - var palette = default(PaletteQuantizer); - default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default); + default(AffineTransformProcessor).ApplyTransform(default); + default(ProjectiveTransformProcessor).ApplyTransform(default); + default(ResizeProcessor).ApplyTransform(default); + default(RotateProcessor).ApplyTransform(default); + } - var wu = default(WuQuantizer); - default(TDither).ApplyQuantizationDither, TPixel>(ref wu, default, default, default); - default(TDither).ApplyPaletteDither.DitherProcessor, TPixel>(default, default, default); - } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileQuantizers() + where TPixel : unmanaged, IPixel + { + AotCompileQuantizer(); + AotCompileQuantizer(); + AotCompileQuantizer(); + AotCompileQuantizer(); + AotCompileQuantizer(); + } - /// - /// This method pre-seeds the all in the AoT compiler. - /// - /// The pixel format. - [Preserve] - private static void AotCompileMemoryManagers() - where TPixel : unmanaged, IPixel - { - AotCompileMemoryManager(); - AotCompileMemoryManager(); - } + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The quantizer type + [Preserve] + private static void AotCompileQuantizer() + where TPixel : unmanaged, IPixel - /// - /// This method pre-seeds the in the AoT compiler. - /// - /// The pixel format. - /// The buffer. - [Preserve] - private static void AotCompileMemoryManager() - where TPixel : unmanaged, IPixel - where TBuffer : MemoryAllocator - { - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - default(TBuffer).Allocate(default, default); - } + where TQuantizer : class, IQuantizer + { + default(TQuantizer).CreatePixelSpecificQuantizer(default); + default(TQuantizer).CreatePixelSpecificQuantizer(default, default); + } + + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompilePixelSamplingStrategys() + where TPixel : unmanaged, IPixel + { + default(DefaultPixelSamplingStrategy).EnumeratePixelRegions(default); + default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default); + } + + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileDithers() + where TPixel : unmanaged, IPixel + { + AotCompileDither(); + AotCompileDither(); + } + + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The dither. + [Preserve] + private static void AotCompileDither() + where TPixel : unmanaged, IPixel + where TDither : struct, IDither + { + var octree = default(OctreeQuantizer); + default(TDither).ApplyQuantizationDither, TPixel>(ref octree, default, default, default); + + var palette = default(PaletteQuantizer); + default(TDither).ApplyQuantizationDither, TPixel>(ref palette, default, default, default); + + var wu = default(WuQuantizer); + default(TDither).ApplyQuantizationDither, TPixel>(ref wu, default, default, default); + default(TDither).ApplyPaletteDither.DitherProcessor, TPixel>(default, default, default); + } + + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompileMemoryManagers() + where TPixel : unmanaged, IPixel + { + AotCompileMemoryManager(); + AotCompileMemoryManager(); + } + + /// + /// This method pre-seeds the in the AoT compiler. + /// + /// The pixel format. + /// The buffer. + [Preserve] + private static void AotCompileMemoryManager() + where TPixel : unmanaged, IPixel + where TBuffer : MemoryAllocator + { + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); + default(TBuffer).Allocate(default, default); } } diff --git a/src/ImageSharp/Advanced/IConfigurationProvider.cs b/src/ImageSharp/Advanced/IConfigurationProvider.cs index da1f6c1fd2..086461f448 100644 --- a/src/ImageSharp/Advanced/IConfigurationProvider.cs +++ b/src/ImageSharp/Advanced/IConfigurationProvider.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Defines the contract for objects that can provide access to configuration. +/// +internal interface IConfigurationProvider { /// - /// Defines the contract for objects that can provide access to configuration. + /// Gets the configuration which allows altering default behaviour or extending the library. /// - internal interface IConfigurationProvider - { - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - Configuration Configuration { get; } - } + Configuration Configuration { get; } } diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index 2f2aa7a318..5e8a4e4512 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations +/// on non-generic instances. +/// +public interface IImageVisitor { /// - /// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations - /// on non-generic instances. + /// Provides a pixel-specific implementation for a given operation. /// - public interface IImageVisitor - { - /// - /// Provides a pixel-specific implementation for a given operation. - /// - /// The image. - /// The pixel type. - void Visit(Image image) - where TPixel : unmanaged, IPixel; - } + /// The image. + /// The pixel type. + void Visit(Image image) + where TPixel : unmanaged, IPixel; +} +/// +/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations +/// on non-generic instances. +/// +public interface IImageVisitorAsync +{ /// - /// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations - /// on non-generic instances. + /// Provides a pixel-specific implementation for a given operation. /// - public interface IImageVisitorAsync - { - /// - /// Provides a pixel-specific implementation for a given operation. - /// - /// The image. - /// The token to monitor for cancellation requests. - /// The pixel type. - /// A representing the asynchronous operation. - Task VisitAsync(Image image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - } + /// The image. + /// The token to monitor for cancellation requests. + /// The pixel type. + /// A representing the asynchronous operation. + Task VisitAsync(Image image, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs index d6d5415363..a46f7d4080 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -4,29 +4,28 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Encapsulates the basic properties and methods required to manipulate images. +/// +internal interface IPixelSource { /// - /// Encapsulates the basic properties and methods required to manipulate images. + /// Gets the pixel buffer. /// - internal interface IPixelSource - { - /// - /// Gets the pixel buffer. - /// - Buffer2D PixelBuffer { get; } - } + Buffer2D PixelBuffer { get; } +} +/// +/// Encapsulates the basic properties and methods required to manipulate images. +/// +/// The type of the pixel. +internal interface IPixelSource + where TPixel : unmanaged, IPixel +{ /// - /// Encapsulates the basic properties and methods required to manipulate images. + /// Gets the pixel buffer. /// - /// The type of the pixel. - internal interface IPixelSource - where TPixel : unmanaged, IPixel - { - /// - /// Gets the pixel buffer. - /// - Buffer2D PixelBuffer { get; } - } + Buffer2D PixelBuffer { get; } } diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs index 33a9bc40c2..cc24255641 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -3,17 +3,16 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Defines the contract for an action that operates on a row interval. +/// +public interface IRowIntervalOperation { /// - /// Defines the contract for an action that operates on a row interval. + /// Invokes the method passing the row interval. /// - public interface IRowIntervalOperation - { - /// - /// Invokes the method passing the row interval. - /// - /// The row interval. - void Invoke(in RowInterval rows); - } + /// The row interval. + void Invoke(in RowInterval rows); } diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index b1e6dae314..3d61eb7333 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -1,23 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced +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 { /// - /// Defines the contract for an action that operates on a row interval with a temporary buffer. + /// Invokes the method passing the row interval and a 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); - } + /// 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 index 8a7d62e0f5..7c2943e97d 100644 --- a/src/ImageSharp/Advanced/IRowOperation.cs +++ b/src/ImageSharp/Advanced/IRowOperation.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Defines the contract for an action that operates on a row. +/// +public interface IRowOperation { /// - /// Defines the contract for an action that operates on a row. + /// Invokes the method passing the row y coordinate. /// - public interface IRowOperation - { - /// - /// Invokes the method passing the row y coordinate. - /// - /// The row y coordinate. - void Invoke(int y); - } + /// The row y coordinate. + void Invoke(int y); } diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs index cf491c82f4..3b6a3eb0c5 100644 --- a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -1,22 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Advanced; -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 { /// - /// Defines the contract for an action that operates on a row with a temporary buffer. + /// Invokes the method passing the row and a 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); - } + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); } diff --git a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs index 01b1a1538a..fd9692f9ae 100644 --- a/src/ImageSharp/Advanced/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -1,100 +1,97 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced +namespace SixLabors.ImageSharp.Advanced; + +/// +/// Defines execution settings for methods in . +/// +public readonly struct ParallelExecutionSettings { /// - /// Defines execution settings for methods in . + /// Default value for . /// - public readonly struct ParallelExecutionSettings - { - /// - /// Default value for . - /// - public const int DefaultMinimumPixelsProcessedPerTask = 4096; + public const int DefaultMinimumPixelsProcessedPerTask = 4096; - /// - /// Initializes a new instance of the struct. - /// - /// The value used for initializing when using TPL. - /// The value for . - /// The . - public ParallelExecutionSettings( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - MemoryAllocator memoryAllocator) + /// + /// Initializes a new instance of the struct. + /// + /// The value used for initializing when using TPL. + /// The value for . + /// The . + public ParallelExecutionSettings( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + MemoryAllocator memoryAllocator) + { + // Shall be compatible with ParallelOptions.MaxDegreeOfParallelism: + // https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism + if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1) { - // Shall be compatible with ParallelOptions.MaxDegreeOfParallelism: - // https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions.maxdegreeofparallelism - if (maxDegreeOfParallelism == 0 || maxDegreeOfParallelism < -1) - { - throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); - } + throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); + } - Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask)); - Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); + Guard.MustBeGreaterThan(minimumPixelsProcessedPerTask, 0, nameof(minimumPixelsProcessedPerTask)); + Guard.NotNull(memoryAllocator, nameof(memoryAllocator)); - this.MaxDegreeOfParallelism = maxDegreeOfParallelism; - this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; - this.MemoryAllocator = memoryAllocator; - } + this.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; + this.MemoryAllocator = memoryAllocator; + } - /// - /// Initializes a new instance of the struct. - /// - /// The value used for initializing when using TPL. - /// The . - public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) - : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The value used for initializing when using TPL. + /// The . + public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) + : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) + { + } - /// - /// Gets the . - /// - public MemoryAllocator MemoryAllocator { get; } + /// + /// Gets the . + /// + public MemoryAllocator MemoryAllocator { get; } - /// - /// Gets the value used for initializing when using TPL. - /// - public int MaxDegreeOfParallelism { get; } + /// + /// Gets the value used for initializing when using TPL. + /// + public int MaxDegreeOfParallelism { get; } - /// - /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. - /// Launching tasks for pixel regions below this limit is not worth the overhead. - /// Initialized with by default, - /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) - /// - public int MinimumPixelsProcessedPerTask { get; } + /// + /// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL. + /// Launching tasks for pixel regions below this limit is not worth the overhead. + /// Initialized with by default, + /// the optimum value is operation specific. (The cheaper the operation, the larger the value is.) + /// + public int MinimumPixelsProcessedPerTask { get; } - /// - /// Creates a new instance of - /// having multiplied by - /// - /// The value to multiply with. - /// The modified . - public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) - { - Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier)); + /// + /// Creates a new instance of + /// having multiplied by + /// + /// The value to multiply with. + /// The modified . + public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) + { + Guard.MustBeGreaterThan(multiplier, 0, nameof(multiplier)); - return new ParallelExecutionSettings( - this.MaxDegreeOfParallelism, - this.MinimumPixelsProcessedPerTask * multiplier, - this.MemoryAllocator); - } + return new ParallelExecutionSettings( + this.MaxDegreeOfParallelism, + this.MinimumPixelsProcessedPerTask * multiplier, + this.MemoryAllocator); + } - /// - /// Get the default for a - /// - /// The . - /// The . - public static ParallelExecutionSettings FromConfiguration(Configuration configuration) - { - return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); - } + /// + /// Get the default for a + /// + /// The . + /// The . + public static ParallelExecutionSettings FromConfiguration(Configuration configuration) + { + return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); } } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index d2d01ebdfa..9e5099b893 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -1,198 +1,195 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced +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 { - /// - /// 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 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) { - 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; - } + 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); + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.maxY) - { - return; - } + if (yMin >= this.maxY) + { + return; + } - int yMax = Math.Min(yMin + this.stepY, this.maxY); + 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); - } + 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 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) { - 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; - } + 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); + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); - if (yMin >= this.maxY) - { - return; - } + if (yMin >= this.maxY) + { + return; + } - int yMax = Math.Min(yMin + this.stepY, this.maxY); + int yMax = Math.Min(yMin + this.stepY, this.maxY); - using IMemoryOwner buffer = this.allocator.Allocate(this.width); + using IMemoryOwner buffer = this.allocator.Allocate(this.width); - Span span = buffer.Memory.Span; + Span span = buffer.Memory.Span; - for (int y = yMin; y < yMax; y++) - { - Unsafe.AsRef(this.action).Invoke(y, 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 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) { - 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; - } + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.operation = operation; + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) { - int yMin = this.minY + (i * this.stepY); + return; + } - if (yMin >= this.maxY) - { - return; - } + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); - 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); + } + } - // 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; } - private readonly struct RowIntervalOperationWrapper - where T : struct, IRowIntervalOperation - where TBuffer : unmanaged + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) { - 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; - } + int yMin = this.minY + (i * this.stepY); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int i) + if (yMin >= this.maxY) { - int yMin = this.minY + (i * this.stepY); - - if (yMin >= this.maxY) - { - return; - } + return; + } - int yMax = Math.Min(yMin + this.stepY, this.maxY); - var rows = new RowInterval(yMin, yMax); + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); - using IMemoryOwner buffer = this.allocator.Allocate(this.width); + using IMemoryOwner buffer = this.allocator.Allocate(this.width); - Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); - } + 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 index d37a76951e..21736b71f0 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -1,288 +1,285 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced +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 { /// - /// 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. + /// Iterate through the rows of a rectangle in optimized batches. /// - public static partial class ParallelRowIterator + /// 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 { - /// - /// 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); - } + 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); + /// + /// 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 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); + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) { - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(operation).Invoke(y); - } - - return; + Unsafe.AsRef(operation).Invoke(y); } - 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); + return; } - /// - /// 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 verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation); - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; + /// + /// 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); + } - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - using IMemoryOwner buffer = allocator.Allocate(width); - Span span = buffer.Memory.Span; + /// + /// 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); - for (int y = top; y < bottom; y++) - { - Unsafe.AsRef(operation).Invoke(y, span); - } + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; - return; - } + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; - int verticalStep = DivideCeil(height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using IMemoryOwner buffer = allocator.Allocate(width); + Span span = buffer.Memory.Span; - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y, span); + } - /// - /// 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); + return; } - /// - /// 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 verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + /// + /// 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); + } - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - Unsafe.AsRef(in operation).Invoke(in rows); - return; - } + /// + /// 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 verticalStep = DivideCeil(rectangle.Height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); - } + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - /// - /// 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 + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) { - var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - IterateRowIntervals(rectangle, in parallelSettings, in operation); + var rows = new RowInterval(top, bottom); + Unsafe.AsRef(in operation).Invoke(in rows); + return; } - /// - /// 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 verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation); - int top = rectangle.Top; - int bottom = rectangle.Bottom; - int width = rectangle.Width; - int height = rectangle.Height; + 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); + } - int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - MemoryAllocator allocator = parallelSettings.MemoryAllocator; + /// + /// 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); - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(top, bottom); - using IMemoryOwner buffer = allocator.Allocate(width); + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; - Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; - return; - } + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using IMemoryOwner buffer = allocator.Allocate(width); - int verticalStep = DivideCeil(height, numOfSteps); - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); - Parallel.For( - 0, - numOfSteps, - parallelOptions, - wrappingOperation.Invoke); + return; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); - 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)}"); - } + 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/PreserveAttribute.cs b/src/ImageSharp/Advanced/PreserveAttribute.cs index 5e3a712707..4b1a240346 100644 --- a/src/ImageSharp/Advanced/PreserveAttribute.cs +++ b/src/ImageSharp/Advanced/PreserveAttribute.cs @@ -1,17 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Advanced; -namespace SixLabors.ImageSharp.Advanced +/// +/// This is necessary to avoid being excluded from compilation in environments that do AOT builds, such as Unity's IL2CPP and Xamarin. +/// The only thing that matters is the class name. +/// There is no need to use or inherit from the PreserveAttribute class in each environment. +/// +[AttributeUsage(AttributeTargets.Method)] +internal sealed class PreserveAttribute : Attribute { - /// - /// This is necessary to avoid being excluded from compilation in environments that do AOT builds, such as Unity's IL2CPP and Xamarin. - /// The only thing that matters is the class name. - /// There is no need to use or inherit from the PreserveAttribute class in each environment. - /// - [AttributeUsage(AttributeTargets.Method)] - internal sealed class PreserveAttribute : Attribute - { - } } diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index 2e8f4100e4..bbb848867d 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -5,237 +5,236 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Contains constructors and implicit conversion methods. +/// +public readonly partial struct Color { - /// - /// Contains constructors and implicit conversion methods. - /// - public readonly partial struct Color - { - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgba64 pixel) - { - this.data = pixel; - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgba64 pixel) + { + this.data = pixel; + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgb48 pixel) - { - this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgb48 pixel) + { + this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(La32 pixel) - { - this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(La32 pixel) + { + this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(L16 pixel) - { - this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(L16 pixel) + { + this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgba32 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgba32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Argb32 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Argb32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Bgra32 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Bgra32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Abgr32 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Abgr32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Rgb24 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Rgb24 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Bgr24 pixel) - { - this.data = new Rgba64(pixel); - this.boxedHighPrecisionPixel = null; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Bgr24 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } - /// - /// Initializes a new instance of the struct. - /// - /// The containing the color information. - [MethodImpl(InliningOptions.ShortMethod)] - public Color(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W); - this.data = default; - } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Vector4 vector) + { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W); + this.data = default; + } - /// - /// Converts a to . - /// - /// The . - /// The . - public static explicit operator Vector4(Color color) => color.ToVector4(); - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static explicit operator Color(Vector4 source) => new(source); - - [MethodImpl(InliningOptions.ShortMethod)] - internal Rgba32 ToRgba32() + /// + /// Converts a to . + /// + /// The . + /// The . + public static explicit operator Vector4(Color color) => color.ToVector4(); + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static explicit operator Color(Vector4 source) => new(source); + + [MethodImpl(InliningOptions.ShortMethod)] + internal Rgba32 ToRgba32() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToRgba32(); - } - - Rgba32 value = default; - this.boxedHighPrecisionPixel.ToRgba32(ref value); - return value; + return this.data.ToRgba32(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Bgra32 ToBgra32() + Rgba32 value = default; + this.boxedHighPrecisionPixel.ToRgba32(ref value); + return value; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal Bgra32 ToBgra32() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToBgra32(); - } - - Bgra32 value = default; - value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - return value; + return this.data.ToBgra32(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Argb32 ToArgb32() + Bgra32 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal Argb32 ToArgb32() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToArgb32(); - } - - Argb32 value = default; - value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - return value; + return this.data.ToArgb32(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Abgr32 ToAbgr32() + Argb32 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal Abgr32 ToAbgr32() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToAbgr32(); - } - - Abgr32 value = default; - value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - return value; + return this.data.ToAbgr32(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Rgb24 ToRgb24() + Abgr32 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal Rgb24 ToRgb24() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToRgb24(); - } - - Rgb24 value = default; - value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - return value; + return this.data.ToRgb24(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Bgr24 ToBgr24() + Rgb24 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal Bgr24 ToBgr24() + { + if (this.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToBgr24(); - } - - Bgr24 value = default; - value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); - return value; + return this.data.ToBgr24(); } - [MethodImpl(InliningOptions.ShortMethod)] - internal Vector4 ToVector4() - { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.ToScaledVector4(); - } + Bgr24 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } - return this.boxedHighPrecisionPixel.ToScaledVector4(); + [MethodImpl(InliningOptions.ShortMethod)] + internal Vector4 ToVector4() + { + if (this.boxedHighPrecisionPixel is null) + { + return this.data.ToScaledVector4(); } + + return this.boxedHighPrecisionPixel.ToScaledVector4(); } } diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs index 8a01b9e6ec..f8b4c90fd6 100644 --- a/src/ImageSharp/Color/Color.NamedColors.cs +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -1,918 +1,914 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Contains static named color values. +/// +/// +public readonly partial struct Color { - /// - /// 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. + /// + public static readonly Color AliceBlue = FromRgba(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Color AntiqueWhite = FromRgba(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Aqua = FromRgba(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Color Aquamarine = FromRgba(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Color Azure = FromRgba(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Color Beige = FromRgba(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Color Bisque = FromRgba(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Color Black = FromRgba(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly Color BlanchedAlmond = FromRgba(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Color Blue = FromRgba(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly Color BlueViolet = FromRgba(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly Color Brown = FromRgba(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly Color BurlyWood = FromRgba(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly Color CadetBlue = FromRgba(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly Color Chartreuse = FromRgba(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly Color Chocolate = FromRgba(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly Color Coral = FromRgba(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly Color CornflowerBlue = FromRgba(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly Color Cornsilk = FromRgba(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly Color Crimson = FromRgba(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Cyan = Aqua; + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly Color DarkBlue = FromRgba(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly Color DarkCyan = FromRgba(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly Color DarkGoldenrod = FromRgba(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGray = FromRgba(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + 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. + /// + public static readonly Color DarkKhaki = FromRgba(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly Color DarkMagenta = FromRgba(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly Color DarkOliveGreen = FromRgba(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly Color DarkOrange = FromRgba(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly Color DarkOrchid = FromRgba(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly Color DarkRed = FromRgba(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8F. + /// + public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly Color DarkSlateBlue = FromRgba(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + 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. + /// + public static readonly Color DarkTurquoise = FromRgba(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly Color DarkViolet = FromRgba(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly Color DeepPink = FromRgba(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly Color DeepSkyBlue = FromRgba(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + 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. + /// + public static readonly Color DodgerBlue = FromRgba(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly Color Firebrick = FromRgba(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly Color FloralWhite = FromRgba(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly Color ForestGreen = FromRgba(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Fuchsia = FromRgba(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly Color Gainsboro = FromRgba(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly Color GhostWhite = FromRgba(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly Color Gold = FromRgba(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly Color Goldenrod = FromRgba(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Gray = FromRgba(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Color Green = FromRgba(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + 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. + /// + public static readonly Color Honeydew = FromRgba(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly Color HotPink = FromRgba(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly Color IndianRed = FromRgba(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly Color Indigo = FromRgba(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly Color Ivory = FromRgba(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly Color Khaki = FromRgba(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly Color Lavender = FromRgba(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly Color LavenderBlush = FromRgba(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly Color LawnGreen = FromRgba(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly Color LemonChiffon = FromRgba(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly Color LightBlue = FromRgba(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly Color LightCoral = FromRgba(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly Color LightCyan = FromRgba(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly Color LightGoldenrodYellow = FromRgba(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGray = FromRgba(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + 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. + /// + public static readonly Color LightPink = FromRgba(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly Color LightSalmon = FromRgba(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly Color LightSeaGreen = FromRgba(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly Color LightSkyBlue = FromRgba(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + 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. + /// + public static readonly Color LightSteelBlue = FromRgba(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly Color LightYellow = FromRgba(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Color Lime = FromRgba(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly Color LimeGreen = FromRgba(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly Color Linen = FromRgba(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Magenta = Fuchsia; + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly Color Maroon = FromRgba(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly Color MediumAquamarine = FromRgba(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly Color MediumBlue = FromRgba(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly Color MediumOrchid = FromRgba(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly Color MediumPurple = FromRgba(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly Color MediumSeaGreen = FromRgba(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly Color MediumSlateBlue = FromRgba(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly Color MediumSpringGreen = FromRgba(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly Color MediumTurquoise = FromRgba(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly Color MediumVioletRed = FromRgba(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly Color MidnightBlue = FromRgba(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly Color MintCream = FromRgba(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly Color MistyRose = FromRgba(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly Color Moccasin = FromRgba(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly Color NavajoWhite = FromRgba(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Color Navy = FromRgba(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly Color OldLace = FromRgba(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Color Olive = FromRgba(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly Color OliveDrab = FromRgba(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly Color Orange = FromRgba(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly Color OrangeRed = FromRgba(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly Color Orchid = FromRgba(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly Color PaleGoldenrod = FromRgba(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly Color PaleGreen = FromRgba(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly Color PaleTurquoise = FromRgba(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly Color PaleVioletRed = FromRgba(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly Color PapayaWhip = FromRgba(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly Color PeachPuff = FromRgba(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly Color Peru = FromRgba(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly Color Pink = FromRgba(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly Color Plum = FromRgba(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly Color PowderBlue = FromRgba(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Color Purple = FromRgba(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #663399. + /// + public static readonly Color RebeccaPurple = FromRgba(102, 51, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Color Red = FromRgba(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly Color RosyBrown = FromRgba(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly Color RoyalBlue = FromRgba(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly Color SaddleBrown = FromRgba(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly Color Salmon = FromRgba(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly Color SandyBrown = FromRgba(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly Color SeaGreen = FromRgba(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly Color SeaShell = FromRgba(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly Color Sienna = FromRgba(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Color Silver = FromRgba(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly Color SkyBlue = FromRgba(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly Color SlateBlue = FromRgba(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + 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. + /// + public static readonly Color Snow = FromRgba(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly Color SpringGreen = FromRgba(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly Color SteelBlue = FromRgba(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly Color Tan = FromRgba(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Color Teal = FromRgba(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly Color Thistle = FromRgba(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly Color Tomato = FromRgba(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00000000. + /// + public static readonly Color Transparent = FromRgba(0, 0, 0, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly Color Turquoise = FromRgba(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly Color Violet = FromRgba(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly Color Wheat = FromRgba(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color White = FromRgba(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly Color WhiteSmoke = FromRgba(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Color Yellow = FromRgba(255, 255, 0, 255); + + /// + /// 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() { - private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0F8FF. - /// - public static readonly Color AliceBlue = FromRgba(240, 248, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAEBD7. - /// - public static readonly Color AntiqueWhite = FromRgba(250, 235, 215, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Color Aqua = FromRgba(0, 255, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFFD4. - /// - public static readonly Color Aquamarine = FromRgba(127, 255, 212, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFFF. - /// - public static readonly Color Azure = FromRgba(240, 255, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5DC. - /// - public static readonly Color Beige = FromRgba(245, 245, 220, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4C4. - /// - public static readonly Color Bisque = FromRgba(255, 228, 196, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #000000. - /// - public static readonly Color Black = FromRgba(0, 0, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEBCD. - /// - public static readonly Color BlanchedAlmond = FromRgba(255, 235, 205, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #0000FF. - /// - public static readonly Color Blue = FromRgba(0, 0, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #8A2BE2. - /// - public static readonly Color BlueViolet = FromRgba(138, 43, 226, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #A52A2A. - /// - public static readonly Color Brown = FromRgba(165, 42, 42, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DEB887. - /// - public static readonly Color BurlyWood = FromRgba(222, 184, 135, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #5F9EA0. - /// - public static readonly Color CadetBlue = FromRgba(95, 158, 160, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFF00. - /// - public static readonly Color Chartreuse = FromRgba(127, 255, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #D2691E. - /// - public static readonly Color Chocolate = FromRgba(210, 105, 30, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF7F50. - /// - public static readonly Color Coral = FromRgba(255, 127, 80, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #6495ED. - /// - public static readonly Color CornflowerBlue = FromRgba(100, 149, 237, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF8DC. - /// - public static readonly Color Cornsilk = FromRgba(255, 248, 220, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DC143C. - /// - public static readonly Color Crimson = FromRgba(220, 20, 60, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Color Cyan = Aqua; - - /// - /// Represents a matching the W3C definition that has an hex value of #00008B. - /// - public static readonly Color DarkBlue = FromRgba(0, 0, 139, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #008B8B. - /// - public static readonly Color DarkCyan = FromRgba(0, 139, 139, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #B8860B. - /// - public static readonly Color DarkGoldenrod = FromRgba(184, 134, 11, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Color DarkGray = FromRgba(169, 169, 169, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #006400. - /// - 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. - /// - public static readonly Color DarkKhaki = FromRgba(189, 183, 107, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B008B. - /// - public static readonly Color DarkMagenta = FromRgba(139, 0, 139, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #556B2F. - /// - public static readonly Color DarkOliveGreen = FromRgba(85, 107, 47, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF8C00. - /// - public static readonly Color DarkOrange = FromRgba(255, 140, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #9932CC. - /// - public static readonly Color DarkOrchid = FromRgba(153, 50, 204, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B0000. - /// - public static readonly Color DarkRed = FromRgba(139, 0, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #E9967A. - /// - public static readonly Color DarkSalmon = FromRgba(233, 150, 122, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8F. - /// - public static readonly Color DarkSeaGreen = FromRgba(143, 188, 143, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #483D8B. - /// - public static readonly Color DarkSlateBlue = FromRgba(72, 61, 139, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - 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. - /// - public static readonly Color DarkTurquoise = FromRgba(0, 206, 209, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #9400D3. - /// - public static readonly Color DarkViolet = FromRgba(148, 0, 211, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF1493. - /// - public static readonly Color DeepPink = FromRgba(255, 20, 147, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00BFFF. - /// - public static readonly Color DeepSkyBlue = FromRgba(0, 191, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - 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. - /// - public static readonly Color DodgerBlue = FromRgba(30, 144, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #B22222. - /// - public static readonly Color Firebrick = FromRgba(178, 34, 34, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAF0. - /// - public static readonly Color FloralWhite = FromRgba(255, 250, 240, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #228B22. - /// - public static readonly Color ForestGreen = FromRgba(34, 139, 34, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Color Fuchsia = FromRgba(255, 0, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DCDCDC. - /// - public static readonly Color Gainsboro = FromRgba(220, 220, 220, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F8F8FF. - /// - public static readonly Color GhostWhite = FromRgba(248, 248, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFD700. - /// - public static readonly Color Gold = FromRgba(255, 215, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DAA520. - /// - public static readonly Color Goldenrod = FromRgba(218, 165, 32, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Color Gray = FromRgba(128, 128, 128, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #008000. - /// - public static readonly Color Green = FromRgba(0, 128, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #ADFF2F. - /// - 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. - /// - public static readonly Color Honeydew = FromRgba(240, 255, 240, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF69B4. - /// - public static readonly Color HotPink = FromRgba(255, 105, 180, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #CD5C5C. - /// - public static readonly Color IndianRed = FromRgba(205, 92, 92, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #4B0082. - /// - public static readonly Color Indigo = FromRgba(75, 0, 130, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFF0. - /// - public static readonly Color Ivory = FromRgba(255, 255, 240, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F0E68C. - /// - public static readonly Color Khaki = FromRgba(240, 230, 140, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #E6E6FA. - /// - public static readonly Color Lavender = FromRgba(230, 230, 250, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF0F5. - /// - public static readonly Color LavenderBlush = FromRgba(255, 240, 245, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #7CFC00. - /// - public static readonly Color LawnGreen = FromRgba(124, 252, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFACD. - /// - public static readonly Color LemonChiffon = FromRgba(255, 250, 205, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #ADD8E6. - /// - public static readonly Color LightBlue = FromRgba(173, 216, 230, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F08080. - /// - public static readonly Color LightCoral = FromRgba(240, 128, 128, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #E0FFFF. - /// - public static readonly Color LightCyan = FromRgba(224, 255, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAFAD2. - /// - public static readonly Color LightGoldenrodYellow = FromRgba(250, 250, 210, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Color LightGray = FromRgba(211, 211, 211, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #90EE90. - /// - 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. - /// - public static readonly Color LightPink = FromRgba(255, 182, 193, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA07A. - /// - public static readonly Color LightSalmon = FromRgba(255, 160, 122, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #20B2AA. - /// - public static readonly Color LightSeaGreen = FromRgba(32, 178, 170, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEFA. - /// - public static readonly Color LightSkyBlue = FromRgba(135, 206, 250, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - 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. - /// - public static readonly Color LightSteelBlue = FromRgba(176, 196, 222, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFE0. - /// - public static readonly Color LightYellow = FromRgba(255, 255, 224, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF00. - /// - public static readonly Color Lime = FromRgba(0, 255, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #32CD32. - /// - public static readonly Color LimeGreen = FromRgba(50, 205, 50, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FAF0E6. - /// - public static readonly Color Linen = FromRgba(250, 240, 230, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Color Magenta = Fuchsia; - - /// - /// Represents a matching the W3C definition that has an hex value of #800000. - /// - public static readonly Color Maroon = FromRgba(128, 0, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #66CDAA. - /// - public static readonly Color MediumAquamarine = FromRgba(102, 205, 170, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #0000CD. - /// - public static readonly Color MediumBlue = FromRgba(0, 0, 205, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #BA55D3. - /// - public static readonly Color MediumOrchid = FromRgba(186, 85, 211, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #9370DB. - /// - public static readonly Color MediumPurple = FromRgba(147, 112, 219, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #3CB371. - /// - public static readonly Color MediumSeaGreen = FromRgba(60, 179, 113, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #7B68EE. - /// - public static readonly Color MediumSlateBlue = FromRgba(123, 104, 238, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FA9A. - /// - public static readonly Color MediumSpringGreen = FromRgba(0, 250, 154, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #48D1CC. - /// - public static readonly Color MediumTurquoise = FromRgba(72, 209, 204, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #C71585. - /// - public static readonly Color MediumVioletRed = FromRgba(199, 21, 133, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #191970. - /// - public static readonly Color MidnightBlue = FromRgba(25, 25, 112, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5FFFA. - /// - public static readonly Color MintCream = FromRgba(245, 255, 250, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4E1. - /// - public static readonly Color MistyRose = FromRgba(255, 228, 225, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4B5. - /// - public static readonly Color Moccasin = FromRgba(255, 228, 181, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDEAD. - /// - public static readonly Color NavajoWhite = FromRgba(255, 222, 173, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #000080. - /// - public static readonly Color Navy = FromRgba(0, 0, 128, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FDF5E6. - /// - public static readonly Color OldLace = FromRgba(253, 245, 230, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #808000. - /// - public static readonly Color Olive = FromRgba(128, 128, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #6B8E23. - /// - public static readonly Color OliveDrab = FromRgba(107, 142, 35, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA500. - /// - public static readonly Color Orange = FromRgba(255, 165, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF4500. - /// - public static readonly Color OrangeRed = FromRgba(255, 69, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DA70D6. - /// - public static readonly Color Orchid = FromRgba(218, 112, 214, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #EEE8AA. - /// - public static readonly Color PaleGoldenrod = FromRgba(238, 232, 170, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #98FB98. - /// - public static readonly Color PaleGreen = FromRgba(152, 251, 152, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #AFEEEE. - /// - public static readonly Color PaleTurquoise = FromRgba(175, 238, 238, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DB7093. - /// - public static readonly Color PaleVioletRed = FromRgba(219, 112, 147, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEFD5. - /// - public static readonly Color PapayaWhip = FromRgba(255, 239, 213, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDAB9. - /// - public static readonly Color PeachPuff = FromRgba(255, 218, 185, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #CD853F. - /// - public static readonly Color Peru = FromRgba(205, 133, 63, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFC0CB. - /// - public static readonly Color Pink = FromRgba(255, 192, 203, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #DDA0DD. - /// - public static readonly Color Plum = FromRgba(221, 160, 221, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #B0E0E6. - /// - public static readonly Color PowderBlue = FromRgba(176, 224, 230, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #800080. - /// - public static readonly Color Purple = FromRgba(128, 0, 128, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #663399. - /// - public static readonly Color RebeccaPurple = FromRgba(102, 51, 153, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF0000. - /// - public static readonly Color Red = FromRgba(255, 0, 0, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #BC8F8F. - /// - public static readonly Color RosyBrown = FromRgba(188, 143, 143, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #4169E1. - /// - public static readonly Color RoyalBlue = FromRgba(65, 105, 225, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #8B4513. - /// - public static readonly Color SaddleBrown = FromRgba(139, 69, 19, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FA8072. - /// - public static readonly Color Salmon = FromRgba(250, 128, 114, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F4A460. - /// - public static readonly Color SandyBrown = FromRgba(244, 164, 96, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #2E8B57. - /// - public static readonly Color SeaGreen = FromRgba(46, 139, 87, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF5EE. - /// - public static readonly Color SeaShell = FromRgba(255, 245, 238, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #A0522D. - /// - public static readonly Color Sienna = FromRgba(160, 82, 45, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #C0C0C0. - /// - public static readonly Color Silver = FromRgba(192, 192, 192, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEEB. - /// - public static readonly Color SkyBlue = FromRgba(135, 206, 235, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #6A5ACD. - /// - public static readonly Color SlateBlue = FromRgba(106, 90, 205, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - 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. - /// - public static readonly Color Snow = FromRgba(255, 250, 250, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF7F. - /// - public static readonly Color SpringGreen = FromRgba(0, 255, 127, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #4682B4. - /// - public static readonly Color SteelBlue = FromRgba(70, 130, 180, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #D2B48C. - /// - public static readonly Color Tan = FromRgba(210, 180, 140, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #008080. - /// - public static readonly Color Teal = FromRgba(0, 128, 128, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #D8BFD8. - /// - public static readonly Color Thistle = FromRgba(216, 191, 216, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FF6347. - /// - public static readonly Color Tomato = FromRgba(255, 99, 71, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #00000000. - /// - public static readonly Color Transparent = FromRgba(0, 0, 0, 0); - - /// - /// Represents a matching the W3C definition that has an hex value of #40E0D0. - /// - public static readonly Color Turquoise = FromRgba(64, 224, 208, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #EE82EE. - /// - public static readonly Color Violet = FromRgba(238, 130, 238, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5DEB3. - /// - public static readonly Color Wheat = FromRgba(245, 222, 179, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Color White = FromRgba(255, 255, 255, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5F5. - /// - public static readonly Color WhiteSmoke = FromRgba(245, 245, 245, 255); - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFF00. - /// - public static readonly Color Yellow = FromRgba(255, 255, 0, 255); - - /// - /// 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) { - 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 } - }; - } + { 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.WebSafePalette.cs b/src/ImageSharp/Color/Color.WebSafePalette.cs index 40275bad47..feb4a8659d 100644 --- a/src/ImageSharp/Color/Color.WebSafePalette.cs +++ b/src/ImageSharp/Color/Color.WebSafePalette.cs @@ -1,166 +1,163 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Contains the definition of . +/// +public partial struct Color { - /// - /// Contains the definition of . - /// - public partial struct Color - { - private static readonly Lazy WebSafePaletteLazy = new Lazy(CreateWebSafePalette, true); + private static readonly Lazy WebSafePaletteLazy = new Lazy(CreateWebSafePalette, true); - /// - /// Gets a collection of named, web safe colors as defined in the CSS Color Module Level 4. - /// - public static ReadOnlyMemory WebSafePalette => WebSafePaletteLazy.Value; + /// + /// Gets a collection of named, web safe colors as defined in the CSS Color Module Level 4. + /// + public static ReadOnlyMemory WebSafePalette => WebSafePaletteLazy.Value; - private static Color[] CreateWebSafePalette() => new[] - { - AliceBlue, - AntiqueWhite, - Aqua, - Aquamarine, - Azure, - Beige, - Bisque, - Black, - BlanchedAlmond, - Blue, - BlueViolet, - Brown, - BurlyWood, - CadetBlue, - Chartreuse, - Chocolate, - Coral, - CornflowerBlue, - Cornsilk, - Crimson, - Cyan, - DarkBlue, - DarkCyan, - DarkGoldenrod, - DarkGray, - DarkGreen, - DarkKhaki, - DarkMagenta, - DarkOliveGreen, - DarkOrange, - DarkOrchid, - DarkRed, - DarkSalmon, - DarkSeaGreen, - DarkSlateBlue, - DarkSlateGray, - DarkTurquoise, - DarkViolet, - DeepPink, - DeepSkyBlue, - DimGray, - DodgerBlue, - Firebrick, - FloralWhite, - ForestGreen, - Fuchsia, - Gainsboro, - GhostWhite, - Gold, - Goldenrod, - Gray, - Green, - GreenYellow, - Honeydew, - HotPink, - IndianRed, - Indigo, - Ivory, - Khaki, - Lavender, - LavenderBlush, - LawnGreen, - LemonChiffon, - LightBlue, - LightCoral, - LightCyan, - LightGoldenrodYellow, - LightGray, - LightGreen, - LightPink, - LightSalmon, - LightSeaGreen, - LightSkyBlue, - LightSlateGray, - LightSteelBlue, - LightYellow, - Lime, - LimeGreen, - Linen, - Magenta, - Maroon, - MediumAquamarine, - MediumBlue, - MediumOrchid, - MediumPurple, - MediumSeaGreen, - MediumSlateBlue, - MediumSpringGreen, - MediumTurquoise, - MediumVioletRed, - MidnightBlue, - MintCream, - MistyRose, - Moccasin, - NavajoWhite, - Navy, - OldLace, - Olive, - OliveDrab, - Orange, - OrangeRed, - Orchid, - PaleGoldenrod, - PaleGreen, - PaleTurquoise, - PaleVioletRed, - PapayaWhip, - PeachPuff, - Peru, - Pink, - Plum, - PowderBlue, - Purple, - RebeccaPurple, - Red, - RosyBrown, - RoyalBlue, - SaddleBrown, - Salmon, - SandyBrown, - SeaGreen, - SeaShell, - Sienna, - Silver, - SkyBlue, - SlateBlue, - SlateGray, - Snow, - SpringGreen, - SteelBlue, - Tan, - Teal, - Thistle, - Tomato, - Transparent, - Turquoise, - Violet, - Wheat, - White, - WhiteSmoke, - Yellow, - YellowGreen - }; - } + private static Color[] CreateWebSafePalette() => new[] + { + AliceBlue, + AntiqueWhite, + Aqua, + Aquamarine, + Azure, + Beige, + Bisque, + Black, + BlanchedAlmond, + Blue, + BlueViolet, + Brown, + BurlyWood, + CadetBlue, + Chartreuse, + Chocolate, + Coral, + CornflowerBlue, + Cornsilk, + Crimson, + Cyan, + DarkBlue, + DarkCyan, + DarkGoldenrod, + DarkGray, + DarkGreen, + DarkKhaki, + DarkMagenta, + DarkOliveGreen, + DarkOrange, + DarkOrchid, + DarkRed, + DarkSalmon, + DarkSeaGreen, + DarkSlateBlue, + DarkSlateGray, + DarkTurquoise, + DarkViolet, + DeepPink, + DeepSkyBlue, + DimGray, + DodgerBlue, + Firebrick, + FloralWhite, + ForestGreen, + Fuchsia, + Gainsboro, + GhostWhite, + Gold, + Goldenrod, + Gray, + Green, + GreenYellow, + Honeydew, + HotPink, + IndianRed, + Indigo, + Ivory, + Khaki, + Lavender, + LavenderBlush, + LawnGreen, + LemonChiffon, + LightBlue, + LightCoral, + LightCyan, + LightGoldenrodYellow, + LightGray, + LightGreen, + LightPink, + LightSalmon, + LightSeaGreen, + LightSkyBlue, + LightSlateGray, + LightSteelBlue, + LightYellow, + Lime, + LimeGreen, + Linen, + Magenta, + Maroon, + MediumAquamarine, + MediumBlue, + MediumOrchid, + MediumPurple, + MediumSeaGreen, + MediumSlateBlue, + MediumSpringGreen, + MediumTurquoise, + MediumVioletRed, + MidnightBlue, + MintCream, + MistyRose, + Moccasin, + NavajoWhite, + Navy, + OldLace, + Olive, + OliveDrab, + Orange, + OrangeRed, + Orchid, + PaleGoldenrod, + PaleGreen, + PaleTurquoise, + PaleVioletRed, + PapayaWhip, + PeachPuff, + Peru, + Pink, + Plum, + PowderBlue, + Purple, + RebeccaPurple, + Red, + RosyBrown, + RoyalBlue, + SaddleBrown, + Salmon, + SandyBrown, + SeaGreen, + SeaShell, + Sienna, + Silver, + SkyBlue, + SlateBlue, + SlateGray, + Snow, + SpringGreen, + SteelBlue, + Tan, + Teal, + Thistle, + Tomato, + Transparent, + Turquoise, + Violet, + Wheat, + White, + WhiteSmoke, + Yellow, + YellowGreen + }; } diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs index 6083112dba..1058da6547 100644 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -1,135 +1,132 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Contains the definition of . +/// +public partial struct Color { - /// - /// Contains the definition of . - /// - public partial struct Color - { - private static readonly Lazy WernerPaletteLazy = new Lazy(CreateWernerPalette, true); + private static readonly Lazy WernerPaletteLazy = new Lazy(CreateWernerPalette, true); - /// - /// 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 ReadOnlyMemory WernerPalette => WernerPaletteLazy.Value; + /// + /// 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 ReadOnlyMemory WernerPalette => WernerPaletteLazy.Value; - private static Color[] CreateWernerPalette() => new[] - { - 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") - }; - } + private static Color[] CreateWernerPalette() => new[] + { + 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") + }; } diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 9cff680e46..28dc1cf924 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -1,334 +1,332 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a color value that is convertible to any type. +/// +/// +/// The internal representation and layout of this structure is hidden by intention. +/// It's not serializable, and it should not be considered as part of a contract. +/// Unlike System.Drawing.Color, has to be converted to a specific pixel value +/// to query the color components. +/// +public readonly partial struct Color : IEquatable { + private readonly Rgba64 data; + private readonly IPixel boxedHighPrecisionPixel; + + [MethodImpl(InliningOptions.ShortMethod)] + private Color(byte r, byte g, byte b, byte a) + { + this.data = new Rgba64( + ColorNumerics.UpscaleFrom8BitTo16Bit(r), + ColorNumerics.UpscaleFrom8BitTo16Bit(g), + ColorNumerics.UpscaleFrom8BitTo16Bit(b), + ColorNumerics.UpscaleFrom8BitTo16Bit(a)); + + this.boxedHighPrecisionPixel = null; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private Color(byte r, byte g, byte b) + { + this.data = new Rgba64( + ColorNumerics.UpscaleFrom8BitTo16Bit(r), + ColorNumerics.UpscaleFrom8BitTo16Bit(g), + ColorNumerics.UpscaleFrom8BitTo16Bit(b), + ushort.MaxValue); + + this.boxedHighPrecisionPixel = null; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private Color(IPixel pixel) + { + this.boxedHighPrecisionPixel = pixel; + this.data = default; + } + /// - /// Represents a color value that is convertible to any type. + /// Checks whether two structures are equal. /// - /// - /// The internal representation and layout of this structure is hidden by intention. - /// It's not serializable, and it should not be considered as part of a contract. - /// Unlike System.Drawing.Color, has to be converted to a specific pixel value - /// to query the color components. - /// - public readonly partial struct Color : IEquatable - { - private readonly Rgba64 data; - private readonly IPixel boxedHighPrecisionPixel; + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Color left, Color right) => left.Equals(right); - [MethodImpl(InliningOptions.ShortMethod)] - private Color(byte r, byte g, byte b, byte a) - { - this.data = new Rgba64( - ColorNumerics.UpscaleFrom8BitTo16Bit(r), - ColorNumerics.UpscaleFrom8BitTo16Bit(g), - ColorNumerics.UpscaleFrom8BitTo16Bit(b), - ColorNumerics.UpscaleFrom8BitTo16Bit(a)); + /// + /// Checks whether two structures are not equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Color left, Color right) => !left.Equals(right); - this.boxedHighPrecisionPixel = null; - } + /// + /// Creates a from RGBA bytes. + /// + /// The red component (0-255). + /// The green component (0-255). + /// The blue component (0-255). + /// The alpha component (0-255). + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a); - [MethodImpl(InliningOptions.ShortMethod)] - private Color(byte r, byte g, byte b) - { - this.data = new Rgba64( - ColorNumerics.UpscaleFrom8BitTo16Bit(r), - ColorNumerics.UpscaleFrom8BitTo16Bit(g), - ColorNumerics.UpscaleFrom8BitTo16Bit(b), - ushort.MaxValue); + /// + /// Creates a from RGB bytes. + /// + /// The red component (0-255). + /// The green component (0-255). + /// The blue component (0-255). + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b); - this.boxedHighPrecisionPixel = null; + /// + /// Creates a from the given . + /// + /// The pixel to convert from. + /// The pixel format. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Color FromPixel(TPixel pixel) + where TPixel : unmanaged, IPixel + { + // Avoid boxing in case we can convert to Rgba64 safely and efficently + if (typeof(TPixel) == typeof(Rgba64)) + { + return new((Rgba64)(object)pixel); } - - [MethodImpl(InliningOptions.ShortMethod)] - private Color(IPixel pixel) + else if (typeof(TPixel) == typeof(Rgb48)) { - this.boxedHighPrecisionPixel = pixel; - this.data = default; + return new((Rgb48)(object)pixel); } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Color left, Color right) => left.Equals(right); - - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Color left, Color right) => !left.Equals(right); - - /// - /// Creates a from RGBA bytes. - /// - /// The red component (0-255). - /// The green component (0-255). - /// The blue component (0-255). - /// The alpha component (0-255). - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a); - - /// - /// Creates a from RGB bytes. - /// - /// The red component (0-255). - /// The green component (0-255). - /// The blue component (0-255). - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b); - - /// - /// Creates a from the given . - /// - /// The pixel to convert from. - /// The pixel format. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromPixel(TPixel pixel) - where TPixel : unmanaged, IPixel + else if (typeof(TPixel) == typeof(La32)) { - // Avoid boxing in case we can convert to Rgba64 safely and efficently - if (typeof(TPixel) == typeof(Rgba64)) - { - return new((Rgba64)(object)pixel); - } - else if (typeof(TPixel) == typeof(Rgb48)) - { - return new((Rgb48)(object)pixel); - } - else if (typeof(TPixel) == typeof(La32)) - { - return new((La32)(object)pixel); - } - else if (typeof(TPixel) == typeof(L16)) - { - return new((L16)(object)pixel); - } - else if (Unsafe.SizeOf() <= Unsafe.SizeOf()) - { - Rgba32 p = default; - pixel.ToRgba32(ref p); - return new(p); - } - else - { - return new(pixel); - } + return new((La32)(object)pixel); } - - /// - /// 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. - /// - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static Color ParseHex(string hex) + else if (typeof(TPixel) == typeof(L16)) { - Rgba32 rgba = Rgba32.ParseHex(hex); - - return new Color(rgba); + return new((L16)(object)pixel); } - - /// - /// 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) + else if (Unsafe.SizeOf() <= Unsafe.SizeOf()) + { + Rgba32 p = default; + pixel.ToRgba32(ref p); + return new(p); + } + else { - result = default; + return new(pixel); + } + } - if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) - { - result = new Color(rgba); - return true; - } + /// + /// 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. + /// + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Color ParseHex(string hex) + { + Rgba32 rgba = Rgba32.ParseHex(hex); - return false; - } + 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; - /// - /// 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 . - /// - /// Input string is not in the correct format. - public static Color Parse(string input) + if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) { - Guard.NotNull(input, nameof(input)); + result = new Color(rgba); + return true; + } - if (!TryParse(input, out Color color)) - { - throw new ArgumentException("Input string is not in the correct format.", nameof(input)); - } + return false; + } - return color; - } + /// + /// 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 . + /// + /// Input string is not in the correct format. + public static Color Parse(string input) + { + Guard.NotNull(input, nameof(input)); - /// - /// 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) + if (!TryParse(input, out Color color)) { - result = default; + throw new ArgumentException("Input string is not in the correct format.", nameof(input)); + } - if (string.IsNullOrWhiteSpace(input)) - { - return false; - } + return color; + } - if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) - { - return true; - } + /// + /// 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; - return TryParseHex(input, out result); + if (string.IsNullOrWhiteSpace(input)) + { + return false; } - /// - /// Alters the alpha channel of the color, returning a new instance. - /// - /// The new value of alpha [0..1]. - /// The color having it's alpha channel altered. - public Color WithAlpha(float alpha) + if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) { - Vector4 v = (Vector4)this; - v.W = alpha; - return new Color(v); + return true; } - /// - /// Gets the hexadecimal representation of the color instance in rrggbbaa form. - /// - /// A hexadecimal string representation of the value. - [MethodImpl(InliningOptions.ShortMethod)] - public string ToHex() => this.data.ToRgba32().ToHex(); - - /// - public override string ToString() => this.ToHex(); - - /// - /// Converts the color instance to a specified type. - /// - /// The pixel type to convert to. - /// The pixel value. - [MethodImpl(InliningOptions.ShortMethod)] - public TPixel ToPixel() - where TPixel : unmanaged, IPixel + return TryParseHex(input, out result); + } + + /// + /// Alters the alpha channel of the color, returning a new instance. + /// + /// The new value of alpha [0..1]. + /// The color having it's alpha channel altered. + public Color WithAlpha(float alpha) + { + Vector4 v = (Vector4)this; + v.W = alpha; + return new Color(v); + } + + /// + /// Gets the hexadecimal representation of the color instance in rrggbbaa form. + /// + /// A hexadecimal string representation of the value. + [MethodImpl(InliningOptions.ShortMethod)] + public string ToHex() => this.data.ToRgba32().ToHex(); + + /// + public override string ToString() => this.ToHex(); + + /// + /// Converts the color instance to a specified type. + /// + /// The pixel type to convert to. + /// The pixel value. + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel ToPixel() + where TPixel : unmanaged, IPixel + { + if (this.boxedHighPrecisionPixel is TPixel pixel) { - if (this.boxedHighPrecisionPixel is TPixel pixel) - { - return pixel; - } - - if (this.boxedHighPrecisionPixel is null) - { - pixel = default; - pixel.FromRgba64(this.data); - return pixel; - } + return pixel; + } + if (this.boxedHighPrecisionPixel is null) + { pixel = default; - pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + pixel.FromRgba64(this.data); return pixel; } - /// - /// Bulk converts a span of to a span of a specified type. - /// - /// The pixel type to convert to. - /// The configuration. - /// The source color span. - /// The destination pixel span. - [MethodImpl(InliningOptions.ShortMethod)] + pixel = default; + pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return pixel; + } + + /// + /// Bulk converts a span of to a span of a specified type. + /// + /// The pixel type to convert to. + /// The configuration. + /// The source color span. + /// The destination pixel span. + [MethodImpl(InliningOptions.ShortMethod)] #pragma warning disable RCS1163 // Unused parameter. - public static void ToPixel(Configuration configuration, ReadOnlySpan source, Span destination) + public static void ToPixel(Configuration configuration, ReadOnlySpan source, Span destination) #pragma warning restore RCS1163 // Unused parameter. - where TPixel : unmanaged, IPixel + where TPixel : unmanaged, IPixel + { + // TODO: Investigate bulk operations utilizing configuration parameter here. + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + for (int i = 0; i < source.Length; i++) { - // TODO: Investigate bulk operations utilizing configuration parameter here. - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - for (int i = 0; i < source.Length; i++) - { - destination[i] = source[i].ToPixel(); - } + destination[i] = source[i].ToPixel(); } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Color other) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Color other) + { + if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) { - if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null) - { - return this.data.PackedValue == other.data.PackedValue; - } - - return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; + return this.data.PackedValue == other.data.PackedValue; } - /// - public override bool Equals(object obj) => obj is Color other && this.Equals(other); + return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - { - if (this.boxedHighPrecisionPixel is null) - { - return this.data.PackedValue.GetHashCode(); - } + /// + public override bool Equals(object obj) => obj is Color other && this.Equals(other); - return this.boxedHighPrecisionPixel.GetHashCode(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + { + if (this.boxedHighPrecisionPixel is null) + { + return this.data.PackedValue.GetHashCode(); } + + return this.boxedHighPrecisionPixel.GetHashCode(); } } diff --git a/src/ImageSharp/ColorSpaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs index bc84342dec..35b664e8a3 100644 --- a/src/ImageSharp/ColorSpaces/CieLab.cs +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -1,137 +1,135 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents a CIE L*a*b* 1976 color. +/// +/// +public readonly struct CieLab : IEquatable { /// - /// Represents a CIE L*a*b* 1976 color. - /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(float l, float a, float b) + : this(l, a, b, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. /// - public readonly struct CieLab : IEquatable + /// The vector representing the l, a, b components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab(Vector3 vector, CieXyz whitePoint) + : this() { - /// - /// D50 standard illuminant. - /// Used when reference white is not specified explicitly. - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLab(float l, float a, float b) - : this(l, a, b, DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLab(float l, float a, float b, CieXyz whitePoint) - : this(new Vector3(l, a, b), whitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, a, b components. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLab(Vector3 vector) - : this(vector, DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, a, b components. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLab(Vector3 vector, CieXyz whitePoint) - : this() - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.A = vector.Y; - this.B = vector.Z; - this.WhitePoint = whitePoint; - } - - /// - /// Gets the lightness dimension. - /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public readonly float L { get; } - - /// - /// Gets the a color component. - /// A value usually ranging from -100 to 100. Negative is green, positive magenta. - /// - public readonly float A { get; } - - /// - /// Gets the b color component. - /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow - /// - public readonly float B { get; } - - /// - /// Gets the reference white point of this color - /// - public readonly CieXyz WhitePoint { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); - - /// - public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object obj) => obj is CieLab other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieLab other) => - this.L.Equals(other.L) - && this.A.Equals(other.A) - && this.B.Equals(other.B) - && this.WhitePoint.Equals(other.WhitePoint); + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.A = vector.Y; + this.B = vector.Z; + this.WhitePoint = whitePoint; } + + /// + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L { get; } + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A { get; } + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B { get; } + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLab left, CieLab right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLab other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLab other) => + this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.WhitePoint.Equals(other.WhitePoint); } diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs index 0c46e9b7cc..e6f53cbb42 100644 --- a/src/ImageSharp/ColorSpaces/CieLch.cs +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -1,161 +1,159 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. +/// +/// +public readonly struct CieLch : IEquatable { /// - /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. - /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. /// - public readonly struct CieLch : IEquatable + public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; + + private static readonly Vector3 Min = new(0, -200, 0); + private static readonly Vector3 Max = new(100, 200, 360); + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(float l, float c, float h) + : this(l, c, h, DefaultWhitePoint) { - /// - /// D50 standard illuminant. - /// Used when reference white is not specified explicitly. - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; - - private static readonly Vector3 Min = new(0, -200, 0); - private static readonly Vector3 Max = new(100, 200, 360); - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLch(float l, float c, float h) - : this(l, c, h, DefaultWhitePoint) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLch(float l, float c, float h, CieXyz whitePoint) - : this(new Vector3(l, c, h), whitePoint) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLch(Vector3 vector) - : this(vector, DefaultWhitePoint) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLch(Vector3 vector, CieXyz whitePoint) - { - vector = Vector3.Clamp(vector, Min, Max); - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - this.WhitePoint = whitePoint; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLch(Vector3 vector, CieXyz whitePoint) + { + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + this.WhitePoint = whitePoint; + } - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public readonly float L { get; } - - /// - /// Gets the a chroma component. - /// A value ranging from 0 to 200. - /// - public readonly float C { get; } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public readonly float H { get; } - - /// - /// Gets the reference white point of this color - /// - public readonly CieXyz WhitePoint { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieLch left, CieLch right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); - - /// - public override int GetHashCode() - => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); - - /// - public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override bool Equals(object obj) => obj is CieLch other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieLch other) - => this.L.Equals(other.L) - && this.C.Equals(other.C) - && this.H.Equals(other.H) - && this.WhitePoint.Equals(other.WhitePoint); - - /// - /// Computes the saturation of the color (chroma normalized by lightness) - /// - /// - /// A value ranging from 0 to 100. - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public float Saturation() - { - float result = 100 * (this.C / this.L); + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L { get; } + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C { get; } + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public readonly float H { get; } + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLch left, CieLch right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); + + /// + public override int GetHashCode() + => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override bool Equals(object obj) => obj is CieLch other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLch other) + => this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) + && this.WhitePoint.Equals(other.WhitePoint); - if (float.IsNaN(result)) - { - return 0; - } + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public float Saturation() + { + float result = 100 * (this.C / this.L); - return result; + if (float.IsNaN(result)) + { + return 0; } + + return result; } } diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs index 2227b60225..de669e7ed9 100644 --- a/src/ImageSharp/ColorSpaces/CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -1,158 +1,156 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. +/// +/// +public readonly struct CieLchuv : IEquatable { + private static readonly Vector3 Min = new(0, -200, 0); + private static readonly Vector3 Max = new(100, 200, 360); + /// - /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. - /// + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. /// - public readonly struct CieLchuv : IEquatable + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(float l, float c, float h) + : this(l, c, h, DefaultWhitePoint) { - private static readonly Vector3 Min = new(0, -200, 0); - private static readonly Vector3 Max = new(100, 200, 360); - - /// - /// D50 standard illuminant. - /// Used when reference white is not specified explicitly. - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLchuv(float l, float c, float h) - : this(l, c, h, DefaultWhitePoint) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The chroma, relative saturation. - /// The hue in degrees. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLchuv(float l, float c, float h, CieXyz whitePoint) - : this(new Vector3(l, c, h), whitePoint) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The chroma, relative saturation. + /// The hue in degrees. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(float l, float c, float h, CieXyz whitePoint) + : this(new Vector3(l, c, h), whitePoint) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLchuv(Vector3 vector) - : this(vector, DefaultWhitePoint) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, c, h components. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLchuv(Vector3 vector, CieXyz whitePoint) - : this() - { - vector = Vector3.Clamp(vector, Min, Max); - this.L = vector.X; - this.C = vector.Y; - this.H = vector.Z; - this.WhitePoint = whitePoint; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, c, h components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLchuv(Vector3 vector, CieXyz whitePoint) + : this() + { + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; + this.WhitePoint = whitePoint; + } - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public readonly float L { get; } - - /// - /// Gets the a chroma component. - /// A value ranging from 0 to 200. - /// - public readonly float C { get; } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public readonly float H { get; } - - /// - /// Gets the reference white point of this color - /// - public readonly CieXyz WhitePoint { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); - - /// - public override string ToString() => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); - - /// - public override bool Equals(object obj) => obj is CieLchuv other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieLchuv other) - => this.L.Equals(other.L) - && this.C.Equals(other.C) - && this.H.Equals(other.H) - && this.WhitePoint.Equals(other.WhitePoint); - - /// - /// Computes the saturation of the color (chroma normalized by lightness) - /// - /// - /// A value ranging from 0 to 100. - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public float Saturation() - { - float result = 100 * (this.C / this.L); + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L { get; } + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C { get; } + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. + /// + public readonly float H { get; } + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieLchuv left, CieLchuv right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.C, this.H, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLchuv other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLchuv other) + => this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) + && this.WhitePoint.Equals(other.WhitePoint); - if (float.IsNaN(result)) - { - return 0; - } + /// + /// Computes the saturation of the color (chroma normalized by lightness) + /// + /// + /// A value ranging from 0 to 100. + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public float Saturation() + { + float result = 100 * (this.C / this.L); - return result; + if (float.IsNaN(result)) + { + return 0; } + + return result; } } diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs index 32481bcb52..f4de5198e0 100644 --- a/src/ImageSharp/ColorSpaces/CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -1,138 +1,136 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International +/// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which +/// attempted perceptual uniformity +/// +/// +public readonly struct CieLuv : IEquatable { /// - /// The CIE 1976 (L*, u*, v*) color space, commonly known by its abbreviation CIELUV, is a color space adopted by the International - /// Commission on Illumination (CIE) in 1976, as a simple-to-compute transformation of the 1931 CIE XYZ color space, but which - /// attempted perceptual uniformity - /// + /// D65 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(float l, float u, float v) + : this(l, u, v, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The blue-yellow chromaticity coordinate of the given whitepoint. + /// The red-green chromaticity coordinate of the given whitepoint. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(float l, float u, float v, CieXyz whitePoint) + : this(new Vector3(l, u, v), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, u, v components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. /// - public readonly struct CieLuv : IEquatable + /// The vector representing the l, u, v components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public CieLuv(Vector3 vector, CieXyz whitePoint) { - /// - /// D65 standard illuminant. - /// Used when reference white is not specified explicitly. - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The blue-yellow chromaticity coordinate of the given whitepoint. - /// The red-green chromaticity coordinate of the given whitepoint. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLuv(float l, float u, float v) - : this(l, u, v, DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The blue-yellow chromaticity coordinate of the given whitepoint. - /// The red-green chromaticity coordinate of the given whitepoint. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLuv(float l, float u, float v, CieXyz whitePoint) - : this(new Vector3(l, u, v), whitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, u, v components. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLuv(Vector3 vector) - : this(vector, DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, u, v components. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public CieLuv(Vector3 vector, CieXyz whitePoint) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.U = vector.Y; - this.V = vector.Z; - this.WhitePoint = whitePoint; - } - - /// - /// Gets the lightness dimension - /// A value usually ranging between 0 and 100. - /// - public readonly float L { get; } - - /// - /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. - /// A value usually ranging between -100 and 100. - /// - public readonly float U { get; } - - /// - /// Gets the red-green chromaticity coordinate of the given whitepoint. - /// A value usually ranging between -100 and 100. - /// - public readonly float V { get; } - - /// - /// Gets the reference white point of this color - /// - public readonly CieXyz WhitePoint { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); - - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V, this.WhitePoint); - - /// - public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); - - /// - public override bool Equals(object obj) => obj is CieLuv other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieLuv other) - => this.L.Equals(other.L) - && this.U.Equals(other.U) - && this.V.Equals(other.V) - && this.WhitePoint.Equals(other.WhitePoint); + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.U = vector.Y; + this.V = vector.Z; + this.WhitePoint = whitePoint; } + + /// + /// Gets the lightness dimension + /// A value usually ranging between 0 and 100. + /// + public readonly float L { get; } + + /// + /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float U { get; } + + /// + /// Gets the red-green chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float V { get; } + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieLuv left, CieLuv right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); + + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.U, this.V, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); + + /// + public override bool Equals(object obj) => obj is CieLuv other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieLuv other) + => this.L.Equals(other.L) + && this.U.Equals(other.U) + && this.V.Equals(other.V) + && this.WhitePoint.Equals(other.WhitePoint); } diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs index 6bbcad075c..2e725a5011 100644 --- a/src/ImageSharp/ColorSpaces/CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an CIE xyY 1931 color +/// +/// +public readonly struct CieXyy : IEquatable { /// - /// Represents an CIE xyY 1931 color - /// + /// Initializes a new instance of the struct. /// - public readonly struct CieXyy : IEquatable + /// The x chroma component. + /// The y chroma component. + /// The y luminance component. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyy(float x, float y, float yl) { - /// - /// Initializes a new instance of the struct. - /// - /// The x chroma component. - /// The y chroma component. - /// The y luminance component. - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyy(float x, float y, float yl) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = x; - this.Y = y; - this.Yl = yl; - } + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = x; + this.Y = y; + this.Yl = yl; + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the x, y, Y components. - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyy(Vector3 vector) - : this() - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = vector.X; - this.Y = vector.Y; - this.Yl = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, Y components. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyy(Vector3 vector) + : this() + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = vector.X; + this.Y = vector.Y; + this.Yl = vector.Z; + } - /// - /// Gets the X chrominance component. - /// A value usually ranging between 0 and 1. - /// - public readonly float X { get; } + /// + /// Gets the X chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float X { get; } - /// - /// Gets the Y chrominance component. - /// A value usually ranging between 0 and 1. - /// - public readonly float Y { get; } + /// + /// Gets the Y chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Y { get; } - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public readonly float Yl { get; } + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Yl { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyy left, CieXyy right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Yl); + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Yl); - /// - public override string ToString() => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"CieXyy({this.X:#0.##}, {this.Y:#0.##}, {this.Yl:#0.##})"); - /// - public override bool Equals(object obj) => obj is CieXyy other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is CieXyy other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieXyy other) - => this.X.Equals(other.X) - && this.Y.Equals(other.Y) - && this.Yl.Equals(other.Yl); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyy other) + => this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Yl.Equals(other.Yl); } diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs index b767128438..53d93c32b8 100644 --- a/src/ImageSharp/ColorSpaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -1,104 +1,102 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an CIE XYZ 1931 color +/// +/// +public readonly struct CieXyz : IEquatable { /// - /// Represents an CIE XYZ 1931 color - /// + /// Initializes a new instance of the struct. /// - public readonly struct CieXyz : IEquatable + /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative + /// The y luminance component. + /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz(float x, float y, float z) + : this(new Vector3(x, y, z)) { - /// - /// Initializes a new instance of the struct. - /// - /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative - /// The y luminance component. - /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyz(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the x, y, z components. - public CieXyz(Vector3 vector) - : this() - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.X = vector.X; - this.Y = vector.Y; - this.Z = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the x, y, z components. + public CieXyz(Vector3 vector) + : this() + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.X = vector.X; + this.Y = vector.Y; + this.Z = vector.Z; + } - /// - /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative. - /// A value usually ranging between 0 and 1. - /// - public readonly float X { get; } + /// + /// Gets the X component. A mix (a linear combination) of cone response curves chosen to be nonnegative. + /// A value usually ranging between 0 and 1. + /// + public readonly float X { get; } - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public readonly float Y { get; } + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Y { get; } - /// - /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response. - /// A value usually ranging between 0 and 1. - /// - public readonly float Z { get; } + /// + /// Gets the Z component. Quasi-equal to blue stimulation, or the S cone response. + /// A value usually ranging between 0 and 1. + /// + public readonly float Z { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyz left, CieXyz right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyz left, CieXyz right) => !left.Equals(right); - /// - /// Returns a new representing this instance. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() => new(this.X, this.Y, this.Z); + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new(this.X, this.Y, this.Z); - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z); - /// - public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"CieXyz({this.X:#0.##}, {this.Y:#0.##}, {this.Z:#0.##})"); - /// - public override bool Equals(object obj) => obj is CieXyz other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is CieXyz other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieXyz other) - => this.X.Equals(other.X) - && this.Y.Equals(other.Y) - && this.Z.Equals(other.Z); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyz other) + => this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Z.Equals(other.Z); } diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index fad331101c..615b6d34dc 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -1,109 +1,107 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an CMYK (cyan, magenta, yellow, keyline) color. +/// +public readonly struct Cmyk : IEquatable { + private static readonly Vector4 Min = Vector4.Zero; + private static readonly Vector4 Max = Vector4.One; + /// - /// Represents an CMYK (cyan, magenta, yellow, keyline) color. + /// Initializes a new instance of the struct. /// - public readonly struct Cmyk : IEquatable + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk(float c, float m, float y, float k) + : this(new Vector4(c, m, y, k)) { - private static readonly Vector4 Min = Vector4.Zero; - private static readonly Vector4 Max = Vector4.One; - - /// - /// Initializes a new instance of the struct. - /// - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The keyline black component. - [MethodImpl(InliningOptions.ShortMethod)] - public Cmyk(float c, float m, float y, float k) - : this(new Vector4(c, m, y, k)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the c, m, y, k components. - [MethodImpl(InliningOptions.ShortMethod)] - public Cmyk(Vector4 vector) - { - vector = Numerics.Clamp(vector, Min, Max); - this.C = vector.X; - this.M = vector.Y; - this.Y = vector.Z; - this.K = vector.W; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the c, m, y, k components. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk(Vector4 vector) + { + vector = Numerics.Clamp(vector, Min, Max); + this.C = vector.X; + this.M = vector.Y; + this.Y = vector.Z; + this.K = vector.W; + } - /// - /// Gets the cyan color component. - /// A value ranging between 0 and 1. - /// - public readonly float C { get; } + /// + /// Gets the cyan color component. + /// A value ranging between 0 and 1. + /// + public readonly float C { get; } - /// - /// Gets the magenta color component. - /// A value ranging between 0 and 1. - /// - public readonly float M { get; } + /// + /// Gets the magenta color component. + /// A value ranging between 0 and 1. + /// + public readonly float M { get; } - /// - /// Gets the yellow color component. - /// A value ranging between 0 and 1. - /// - public readonly float Y { get; } + /// + /// Gets the yellow color component. + /// A value ranging between 0 and 1. + /// + public readonly float Y { get; } - /// - /// Gets the keyline black color component. - /// A value ranging between 0 and 1. - /// - public readonly float K { get; } + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public readonly float K { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Cmyk left, Cmyk right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.C, this.M, this.Y, this.K); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.C, this.M, this.Y, this.K); - /// - public override string ToString() => FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"Cmyk({this.C:#0.##}, {this.M:#0.##}, {this.Y:#0.##}, {this.K:#0.##})"); - /// - public override bool Equals(object obj) => obj is Cmyk other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Cmyk other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Cmyk other) - => this.C.Equals(other.C) - && this.M.Equals(other.M) - && this.Y.Equals(other.Y) - && this.K.Equals(other.K); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Cmyk other) + => this.C.Equals(other.C) + && this.M.Equals(other.M) + && this.Y.Equals(other.Y) + && this.K.Equals(other.K); } diff --git a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs index db2c21448c..e5d98430fd 100644 --- a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -1,36 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Companding +namespace SixLabors.ImageSharp.ColorSpaces.Companding; + +/// +/// Implements gamma companding. +/// +/// +/// +/// +/// +public static class GammaCompanding { /// - /// Implements gamma companding. + /// Expands a companded channel to its linear equivalent with respect to the energy. /// - /// - /// - /// - /// - public static class GammaCompanding - { - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value. - /// The gamma value. - /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel, float gamma) => MathF.Pow(channel, gamma); + /// The channel value. + /// The gamma value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel, float gamma) => MathF.Pow(channel, gamma); - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value. - /// The gamma value. - /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel, float gamma) => MathF.Pow(channel, 1 / gamma); - } + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The gamma value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel, float gamma) => MathF.Pow(channel, 1 / gamma); } diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs index 211005ac8f..db44fd069f 100644 --- a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -1,38 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces.Companding +namespace SixLabors.ImageSharp.ColorSpaces.Companding; + +/// +/// Implements L* companding. +/// +/// +/// For more info see: +/// +/// +/// +public static class LCompanding { /// - /// Implements L* companding. + /// Expands a companded channel to its linear equivalent with respect to the energy. /// - /// - /// For more info see: - /// - /// - /// - public static class LCompanding - { - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value. - /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) - => channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : Numerics.Pow3((channel + 0.16F) / 1.16F); + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel <= 0.08F ? (100F * channel) / CieConstants.Kappa : Numerics.Pow3((channel + 0.16F) / 1.16F); - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value - /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) - => channel <= CieConstants.Epsilon ? (channel * CieConstants.Kappa) / 100F : (1.16F * MathF.Pow(channel, 0.3333333F)) - 0.16F; - } + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel <= CieConstants.Epsilon ? (channel * CieConstants.Kappa) / 100F : (1.16F * MathF.Pow(channel, 0.3333333F)) - 0.16F; } diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs index 372dc7ac2c..4500976189 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -1,41 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Companding +namespace SixLabors.ImageSharp.ColorSpaces.Companding; + +/// +/// Implements Rec. 2020 companding function. +/// +/// +/// +/// +public static class Rec2020Companding { + private const float Alpha = 1.09929682680944F; + private const float AlphaMinusOne = Alpha - 1F; + private const float Beta = 0.018053968510807F; + private const float InverseBeta = Beta * 4.5F; + private const float Epsilon = 1 / 0.45F; + /// - /// Implements Rec. 2020 companding function. + /// Expands a companded channel to its linear equivalent with respect to the energy. /// - /// - /// - /// - public static class Rec2020Companding - { - private const float Alpha = 1.09929682680944F; - private const float AlphaMinusOne = Alpha - 1F; - private const float Beta = 0.018053968510807F; - private const float InverseBeta = Beta * 4.5F; - private const float Epsilon = 1 / 0.45F; - - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value. - /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) - => channel < InverseBeta ? channel / 4.5F : MathF.Pow((channel + AlphaMinusOne) / Alpha, Epsilon); + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel < InverseBeta ? channel / 4.5F : MathF.Pow((channel + AlphaMinusOne) / Alpha, Epsilon); - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value. - /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) - => channel < Beta ? 4.5F * channel : (Alpha * MathF.Pow(channel, 0.45F)) - AlphaMinusOne; - } + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel < Beta ? 4.5F * channel : (Alpha * MathF.Pow(channel, 0.45F)) - AlphaMinusOne; } diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs index f753d16dc7..6e4767bcd9 100644 --- a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -1,37 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Companding +namespace SixLabors.ImageSharp.ColorSpaces.Companding; + +/// +/// Implements the Rec. 709 companding function. +/// +/// +/// http://en.wikipedia.org/wiki/Rec._709 +/// +public static class Rec709Companding { + private const float Epsilon = 1 / 0.45F; + /// - /// Implements the Rec. 709 companding function. + /// Expands a companded channel to its linear equivalent with respect to the energy. /// - /// - /// http://en.wikipedia.org/wiki/Rec._709 - /// - public static class Rec709Companding - { - private const float Epsilon = 1 / 0.45F; - - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value. - /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) - => channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, Epsilon); + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Expand(float channel) + => channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, Epsilon); - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value. - /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) - => channel < 0.018F ? 4.5F * channel : (1.099F * MathF.Pow(channel, 0.45F)) - 0.099F; - } + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(InliningOptions.ShortMethod)] + public static float Compress(float channel) + => channel < 0.018F ? 4.5F * channel : (1.099F * MathF.Pow(channel, 0.45F)) - 0.099F; } diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 82e9a123be..e776a0dc20 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -1,231 +1,229 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.ColorSpaces.Companding +namespace SixLabors.ImageSharp.ColorSpaces.Companding; + +/// +/// Implements sRGB companding. +/// +/// +/// For more info see: +/// +/// +/// +public static class SRgbCompanding { - /// - /// Implements sRGB companding. - /// - /// - /// For more info see: - /// - /// - /// - public static class SRgbCompanding - { - private const int Length = Scale + 2; // 256kb @ 16bit precision. - private const int Scale = (1 << 16) - 1; + private const int Length = Scale + 2; // 256kb @ 16bit precision. + private const int Scale = (1 << 16) - 1; - private static readonly Lazy LazyCompressTable = new( - () => - { - float[] result = new float[Length]; + private static readonly Lazy LazyCompressTable = new( + () => + { + float[] result = new float[Length]; - for (int i = 0; i < result.Length; i++) + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= (0.04045 / 12.92)) + { + d *= 12.92; + } + else { - double d = (double)i / Scale; - if (d <= (0.04045 / 12.92)) - { - d *= 12.92; - } - else - { - d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; - } - - result[i] = (float)d; + d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; } - return result; - }, - true); + result[i] = (float)d; + } - private static readonly Lazy LazyExpandTable = new( - () => - { - float[] result = new float[Length]; + return result; + }, + true); + + private static readonly Lazy LazyExpandTable = new( + () => + { + float[] result = new float[Length]; - for (int i = 0; i < result.Length; i++) + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= 0.04045) + { + d /= 12.92; + } + else { - double d = (double)i / Scale; - if (d <= 0.04045) - { - d /= 12.92; - } - else - { - d = Math.Pow((d + 0.055) / 1.055, 2.4); - } - - result[i] = (float)d; + d = Math.Pow((d + 0.055) / 1.055, 2.4); } - return result; - }, - true); + result[i] = (float)d; + } + + return result; + }, + true); - private static float[] ExpandTable => LazyExpandTable.Value; + private static float[] ExpandTable => LazyExpandTable.Value; - private static float[] CompressTable => LazyCompressTable.Value; + private static float[] CompressTable => LazyCompressTable.Value; - /// - /// Expands the companded vectors to their linear equivalents with respect to the energy. - /// - /// The span of vectors. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Expand(Span vectors) + /// + /// Expands the companded vectors to their linear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Expand(Span vectors) + { + if (Avx2.IsSupported && vectors.Length >= 2) { - if (Avx2.IsSupported && vectors.Length >= 2) - { - CompandAvx2(vectors, ExpandTable); + CompandAvx2(vectors, ExpandTable); - if (Numerics.Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - Expand(ref MemoryMarshal.GetReference(vectors[^1..])); - } - } - else + if (Numerics.Modulo2(vectors.Length) != 0) { - CompandScalar(vectors, ExpandTable); + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Expand(ref MemoryMarshal.GetReference(vectors[^1..])); } } + else + { + CompandScalar(vectors, ExpandTable); + } + } - /// - /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. - /// - /// The span of vectors. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Compress(Span vectors) + /// + /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Compress(Span vectors) + { + if (Avx2.IsSupported && vectors.Length >= 2) { - if (Avx2.IsSupported && vectors.Length >= 2) - { - CompandAvx2(vectors, CompressTable); + CompandAvx2(vectors, CompressTable); - if (Numerics.Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - Compress(ref MemoryMarshal.GetReference(vectors[^1..])); - } - } - else + if (Numerics.Modulo2(vectors.Length) != 0) { - CompandScalar(vectors, CompressTable); + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Compress(ref MemoryMarshal.GetReference(vectors[^1..])); } } - - /// - /// Expands a companded vector to its linear equivalent with respect to the energy. - /// - /// The vector. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Expand(ref Vector4 vector) + else { - // Alpha is already a linear representation of opacity so we do not want to convert it. - vector.X = Expand(vector.X); - vector.Y = Expand(vector.Y); - vector.Z = Expand(vector.Z); + CompandScalar(vectors, CompressTable); } + } - /// - /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. - /// - /// The vector. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Compress(ref Vector4 vector) - { - // Alpha is already a linear representation of opacity so we do not want to convert it. - vector.X = Compress(vector.X); - vector.Y = Compress(vector.Y); - vector.Z = Compress(vector.Z); - } + /// + /// Expands a companded vector to its linear equivalent with respect to the energy. + /// + /// The vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Expand(ref Vector4 vector) + { + // Alpha is already a linear representation of opacity so we do not want to convert it. + vector.X = Expand(vector.X); + vector.Y = Expand(vector.Y); + vector.Z = Expand(vector.Z); + } + + /// + /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. + /// + /// The vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Compress(ref Vector4 vector) + { + // Alpha is already a linear representation of opacity so we do not want to convert it. + vector.X = Compress(vector.X); + vector.Y = Compress(vector.Y); + vector.Z = Compress(vector.Z); + } - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// The channel value. - /// The representing the linear channel value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Expand(float channel) - => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); - - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. - /// - /// The channel value. - /// The representing the nonlinear channel value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Compress(float channel) - => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void CompandAvx2(Span vectors, float[] table) + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// The channel value. + /// The representing the linear channel value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Expand(float channel) + => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. + /// + /// The channel value. + /// The representing the nonlinear channel value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Compress(float channel) + => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandAvx2(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) { - fixed (float* tablePointer = &table[0]) - { - var scale = Vector256.Create((float)Scale); - Vector256 zero = Vector256.Zero; - var offset = Vector256.Create(1); + var scale = Vector256.Create((float)Scale); + Vector256 zero = Vector256.Zero; + var offset = Vector256.Create(1); - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 multiplied = Avx.Multiply(scale, vectorsBase); - multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector256 multiplied = Avx.Multiply(scale, vectorsBase); + multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); - Vector256 truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); - Vector256 truncatedF = Avx.ConvertToVector256Single(truncated); + Vector256 truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); + Vector256 truncatedF = Avx.ConvertToVector256Single(truncated); - Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); - Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); + Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); + Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); - // Alpha is already a linear representation of opacity so we do not want to convert it. - Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); - vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } + // Alpha is already a linear representation of opacity so we do not want to convert it. + Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void CompandScalar(Span vectors, float[] table) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandScalar(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) { - fixed (float* tablePointer = &table[0]) - { - Vector4 zero = Vector4.Zero; - var scale = new Vector4(Scale); - ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length); + Vector4 zero = Vector4.Zero; + var scale = new Vector4(Scale); + ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); - float f0 = multiplied.X; - float f1 = multiplied.Y; - float f2 = multiplied.Z; + float f0 = multiplied.X; + float f1 = multiplied.Y; + float f2 = multiplied.Z; - uint i0 = (uint)f0; - uint i1 = (uint)f1; - uint i2 = (uint)f2; + uint i0 = (uint)f0; + uint i1 = (uint)f1; + uint i2 = (uint)f2; - // Alpha is already a linear representation of opacity so we do not want to convert it. - vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); - vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); - vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); + // Alpha is already a linear representation of opacity so we do not want to convert it. + vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); + vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); + vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs index 62fa445c5e..7c87944043 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/CieConstants.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Constants use for Cie conversion calculations +/// +/// +internal static class CieConstants { /// - /// Constants use for Cie conversion calculations - /// + /// 216F / 24389F /// - internal static class CieConstants - { - /// - /// 216F / 24389F - /// - public const float Epsilon = 0.008856452F; + public const float Epsilon = 0.008856452F; - /// - /// 24389F / 27F - /// - public const float Kappa = 903.2963F; - } + /// + /// 24389F / 27F + /// + public const float Kappa = 903.2963F; } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs index d200aa83ec..52a9ef27ee 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -1,158 +1,157 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Performs chromatic adaptation on the various color spaces. +/// +public partial class ColorSpaceConverter { - /// - /// Performs chromatic adaptation on the various color spaces. - /// - public partial class ColorSpaceConverter + /// + /// Performs chromatic adaptation of given color. + /// Target white point is . + /// + /// The color to adapt + /// The source white point. + /// The adapted color + public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint) => this.Adapt(color, sourceWhitePoint, this.whitePoint); + + /// + /// Performs chromatic adaptation of given color. + /// Target white point is . + /// + /// The color to adapt + /// The source white point. + /// The target white point. + /// The adapted color + public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint, in CieXyz targetWhitePoint) { - /// - /// Performs chromatic adaptation of given color. - /// Target white point is . - /// - /// The color to adapt - /// The source white point. - /// The adapted color - public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint) => this.Adapt(color, sourceWhitePoint, this.whitePoint); - - /// - /// Performs chromatic adaptation of given color. - /// Target white point is . - /// - /// The color to adapt - /// The source white point. - /// The target white point. - /// The adapted color - public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint, in CieXyz targetWhitePoint) + if (!this.performChromaticAdaptation || sourceWhitePoint.Equals(targetWhitePoint)) { - if (!this.performChromaticAdaptation || sourceWhitePoint.Equals(targetWhitePoint)) - { - return color; - } - - return this.chromaticAdaptation.Transform(color, sourceWhitePoint, targetWhitePoint); + return color; } - /// - /// Adapts color from the source white point to white point set in . - /// - /// The color to adapt - /// The adapted color - public CieLab Adapt(in CieLab color) - { - if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) - { - return color; - } + return this.chromaticAdaptation.Transform(color, sourceWhitePoint, targetWhitePoint); + } - var xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLab Adapt(in CieLab color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; } - /// - /// Adapts color from the source white point to white point set in . - /// - /// The color to adapt - /// The adapted color - public CieLch Adapt(in CieLch color) - { - if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) - { - return color; - } + var xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } - var labColor = this.ToCieLab(color); - return this.ToCieLch(labColor); + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLch Adapt(in CieLch color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; } - /// - /// Adapts color from the source white point to white point set in . - /// - /// The color to adapt - /// The adapted color - public CieLchuv Adapt(in CieLchuv color) - { - if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) - { - return color; - } + var labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); + } - var luvColor = this.ToCieLuv(color); - return this.ToCieLchuv(luvColor); + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLchuv Adapt(in CieLchuv color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) + { + return color; } - /// - /// Adapts color from the source white point to white point set in . - /// - /// The color to adapt - /// The adapted color - public CieLuv Adapt(in CieLuv color) - { - if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLuvWhitePoint)) - { - return color; - } + var luvColor = this.ToCieLuv(color); + return this.ToCieLchuv(luvColor); + } - var xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public CieLuv Adapt(in CieLuv color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLuvWhitePoint)) + { + return color; } - /// - /// Adapts color from the source white point to white point set in . - /// - /// The color to adapt - /// The adapted color - public HunterLab Adapt(in HunterLab color) - { - if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetHunterLabWhitePoint)) - { - return color; - } + var xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + /// + /// Adapts color from the source white point to white point set in . + /// + /// The color to adapt + /// The adapted color + public HunterLab Adapt(in HunterLab color) + { + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetHunterLabWhitePoint)) + { + return color; } - /// - /// Adapts a color from the source working space to working space set in . - /// - /// The color to adapt - /// The adapted color - public LinearRgb Adapt(in LinearRgb color) + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Adapts a color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public LinearRgb Adapt(in LinearRgb color) + { + if (!this.performChromaticAdaptation || color.WorkingSpace.Equals(this.targetRgbWorkingSpace)) { - if (!this.performChromaticAdaptation || color.WorkingSpace.Equals(this.targetRgbWorkingSpace)) - { - return color; - } + return color; + } - // Conversion to XYZ - LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); - CieXyz unadapted = converterToXYZ.Convert(color); + // Conversion to XYZ + LinearRgbToCieXyzConverter converterToXYZ = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); + CieXyz unadapted = converterToXYZ.Convert(color); - // Adaptation - CieXyz adapted = this.chromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.targetRgbWorkingSpace.WhitePoint); + // Adaptation + CieXyz adapted = this.chromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.targetRgbWorkingSpace.WhitePoint); - // Conversion back to RGB - return this.cieXyzToLinearRgbConverter.Convert(adapted); - } + // Conversion back to RGB + return this.cieXyzToLinearRgbConverter.Convert(adapted); + } - /// - /// Adapts an color from the source working space to working space set in . - /// - /// The color to adapt - /// The adapted color - public Rgb Adapt(in Rgb color) + /// + /// Adapts an color from the source working space to working space set in . + /// + /// The color to adapt + /// The adapted color + public Rgb Adapt(in Rgb color) + { + if (!this.performChromaticAdaptation) { - if (!this.performChromaticAdaptation) - { - return color; - } - - var linearInput = ToLinearRgb(color); - LinearRgb linearOutput = this.Adapt(linearInput); - return ToRgb(linearOutput); + return color; } + + var linearInput = ToLinearRgb(color); + LinearRgb linearOutput = this.Adapt(linearInput); + return ToRgb(linearOutput); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs index c3dd3b473f..721df36678 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -1,443 +1,441 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLch color) { - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in CieLch color) - { - // Conversion (preserving white point) - CieLab unadapted = CieLchToCieLabConverter.Convert(color); + // Conversion (preserving white point) + CieLab unadapted = CieLchToCieLabConverter.Convert(color); - return this.Adapt(unadapted); - } + return this.Adapt(unadapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in CieXyz color) - { - CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLabWhitePoint); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in CieXyz color) + { + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLabWhitePoint); - return this.cieXyzToCieLabConverter.Convert(adapted); - } + return this.cieXyzToCieLabConverter.Convert(adapted); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in Cmyk color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in Hsl color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Cmyk color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } - return this.ToCieLab(xyzColor); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in Hsv color) + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Hsl color) + { + CieXyz xyzColor = this.ToCieXyz(color); + + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Hsv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in LinearRgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in LinearRgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in Rgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in Rgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLab ToCieLab(in YCbCr color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLab ToCieLab(in YCbCr color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLab(xyzColor); - } + return this.ToCieLab(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLab(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLab(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs index ec7d48b783..da8e68b480 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -1,443 +1,441 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLab color) { - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in CieLab color) - { - CieLab adapted = this.Adapt(color); + CieLab adapted = this.Adapt(color); - return CieLabToCieLchConverter.Convert(adapted); - } + return CieLabToCieLchConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in CieLchuv color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in CieLuv color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in CieXyy color) - { - var xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieXyy color) + { + var xyzColor = ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in CieXyz color) - { - var labColor = this.ToCieLab(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in CieXyz color) + { + var labColor = this.ToCieLab(color); - return this.ToCieLch(labColor); - } + return this.ToCieLch(labColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in Cmyk color) + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in Hsl color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in Hsv color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in HunterLab color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in LinearRgb color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in Lms color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Lms color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in Rgb color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLch ToCieLch(in YCbCr color) - { - var xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLch ToCieLch(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); - return this.ToCieLch(xyzColor); - } + return this.ToCieLch(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLch destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLch dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLch(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLch dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLch(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs index 52c092a2e8..75e955e41f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -1,443 +1,441 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLab color) { - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in CieLuv color) - { - CieLuv adapted = this.Adapt(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieLuv color) + { + CieLuv adapted = this.Adapt(color); - return CieLuvToCieLchuvConverter.Convert(adapted); - } + return CieLuvToCieLchuvConverter.Convert(adapted); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in CieXyz color) - { - CieLuv luvColor = this.ToCieLuv(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in CieXyz color) + { + CieLuv luvColor = this.ToCieLuv(color); - return this.ToCieLchuv(luvColor); - } + return this.ToCieLchuv(luvColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in Cmyk color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Cmyk color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in Hsl color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Hsl color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in Hsv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Hsv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in LinearRgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in LinearRgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in Rgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in Rgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); - } + return this.ToCieLchuv(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLchuv ToCieLchuv(in YCbCr color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLchuv(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLchuv ToCieLchuv(in YCbCr color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLchuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLchuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLchuv(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLchuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLchuv(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs index 572d8875db..b04acc9907 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -1,437 +1,435 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLab color) { - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); - } + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in CieLchuv color) - { - // Conversion (preserving white point) - CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - // Adaptation - return this.Adapt(unadapted); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in CieXyy color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieLchuv color) + { + // Conversion (preserving white point) + CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in CieXyz color) - { - // Adaptation - CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLuvWhitePoint); + // Adaptation + return this.Adapt(unadapted); + } - // Conversion - return this.cieXyzToCieLuvConverter.Convert(adapted); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in Cmyk color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in Hsl color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in Hsv color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } - } + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in CieXyz color) + { + // Adaptation + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLuvWhitePoint); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); - } + // Conversion + return this.cieXyzToCieLuvConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Cmyk color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in Lms color) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Hsl color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Hsv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in LinearRgb color) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in Rgb color) + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in LinearRgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieLuv ToCieLuv(in YCbCr color) + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in Rgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCieLuv(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieLuv ToCieLuv(in YCbCr color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToCieLuv(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieLuv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieLuv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieLuv(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieLuv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieLuv(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs index b5d655e680..a3851de9f0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -1,439 +1,437 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLab color) { - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static CieXyy ToCieXyy(in CieXyz color) => CieXyzAndCieXyyConverter.Convert(color); - - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static CieXyy ToCieXyy(in CieXyz color) => CieXyzAndCieXyyConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = ToCieXyy(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in Cmyk color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Cmyk color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(Hsl color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(Hsl color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in Hsv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Hsv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in LinearRgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in LinearRgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in Rgb color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in Rgb color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyy ToCieXyy(in YCbCr color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyy ToCieXyy(in YCbCr color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return ToCieXyy(xyzColor); - } + return ToCieXyy(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyy destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyy dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyy(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyy dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyy(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs index d1f0ca489d..ac1fa8ed0a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -1,468 +1,466 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + private static readonly HunterLabToCieXyzConverter HunterLabToCieXyzConverter = new(); + + private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter; + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLab color) { - private static readonly HunterLabToCieXyzConverter HunterLabToCieXyzConverter = new(); + // Conversion + CieXyz unadapted = CieLabToCieXyzConverter.Convert(color); - private LinearRgbToCieXyzConverter linearRgbToCieXyzConverter; + // Adaptation + return this.Adapt(unadapted, color.WhitePoint); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in CieLab color) - { - // Conversion - CieXyz unadapted = CieLabToCieXyzConverter.Convert(color); + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - // Adaptation - return this.Adapt(unadapted, color.WhitePoint); - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in CieLch color) - { - // Conversion to Lab - CieLab labColor = CieLchToCieLabConverter.Convert(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLch color) + { + // Conversion to Lab + CieLab labColor = CieLchToCieLabConverter.Convert(color); - // Conversion to XYZ (incl. adaptation) - return this.ToCieXyz(labColor); - } + // Conversion to XYZ (incl. adaptation) + return this.ToCieXyz(labColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in CieLchuv color) - { - // Conversion to Luv - CieLuv luvColor = CieLchuvToCieLuvConverter.Convert(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLchuv color) + { + // Conversion to Luv + CieLuv luvColor = CieLchuvToCieLuvConverter.Convert(color); - // Conversion to XYZ (incl. adaptation) - return this.ToCieXyz(luvColor); - } + // Conversion to XYZ (incl. adaptation) + return this.ToCieXyz(luvColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in CieLuv color) - { - // Conversion - CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in CieLuv color) + { + // Conversion + CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color); - // Adaptation - return this.Adapt(unadapted, color.WhitePoint); - } + // Adaptation + return this.Adapt(unadapted, color.WhitePoint); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static CieXyz ToCieXyz(in CieXyy color) - - // Conversion - => CieXyzAndCieXyyConverter.Convert(color); - - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = ToCieXyz(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in Cmyk color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static CieXyz ToCieXyz(in CieXyy color) + + // Conversion + => CieXyzAndCieXyyConverter.Convert(color); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - return this.ToCieXyz(rgb); - } + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in Hsl color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Cmyk color) + { + Rgb rgb = ToRgb(color); - return this.ToCieXyz(rgb); - } + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in Hsv color) - { - // Conversion - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Hsl color) + { + Rgb rgb = ToRgb(color); - return this.ToCieXyz(rgb); - } + return this.ToCieXyz(rgb); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in HunterLab color) - { - CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Hsv color) + { + // Conversion + Rgb rgb = ToRgb(color); - return this.Adapt(unadapted, color.WhitePoint); - } + return this.ToCieXyz(rgb); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in LinearRgb color) - { - // Conversion - LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); - CieXyz unadapted = converter.Convert(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in HunterLab color) + { + CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color); - return this.Adapt(unadapted, color.WorkingSpace.WhitePoint); - } + return this.Adapt(unadapted, color.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in LinearRgb color) + { + // Conversion + LinearRgbToCieXyzConverter converter = this.GetLinearRgbToCieXyzConverter(color.WorkingSpace); + CieXyz unadapted = converter.Convert(color); + + return this.Adapt(unadapted, color.WorkingSpace.WhitePoint); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in Lms color) - => this.cieXyzAndLmsConverter.Convert(color); - - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Lms color) + => this.cieXyzAndLmsConverter.Convert(color); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in Rgb color) + for (int i = 0; i < count; i++) { - // Conversion - LinearRgb linear = RgbToLinearRgbConverter.Convert(color); - return this.ToCieXyz(linear); + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in Rgb color) + { + // Conversion + LinearRgb linear = RgbToLinearRgbConverter.Convert(color); + return this.ToCieXyz(linear); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public CieXyz ToCieXyz(in YCbCr color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public CieXyz ToCieXyz(in YCbCr color) + { + Rgb rgb = this.ToRgb(color); - return this.ToCieXyz(rgb); - } + return this.ToCieXyz(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCieXyz(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCieXyz(sp); } + } - /// - /// Gets the correct converter for the given rgb working space. - /// - /// The source working space - /// The - private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) + /// + /// Gets the correct converter for the given rgb working space. + /// + /// The source working space + /// The + private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) + { + if (this.linearRgbToCieXyzConverter?.SourceWorkingSpace.Equals(workingSpace) == true) { - if (this.linearRgbToCieXyzConverter?.SourceWorkingSpace.Equals(workingSpace) == true) - { - return this.linearRgbToCieXyzConverter; - } - - return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace); + return this.linearRgbToCieXyzConverter; } + + return this.linearRgbToCieXyzConverter = new LinearRgbToCieXyzConverter(workingSpace); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs index 152a7f7fc6..cadcc9e03f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -1,439 +1,437 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLab color) { - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in CieXyz color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in CieXyz color) + { + Rgb rgb = this.ToRgb(color); - return CmykAndRgbConverter.Convert(rgb); - } + return CmykAndRgbConverter.Convert(rgb); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static Cmyk ToCmyk(in Hsl color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static Cmyk ToCmyk(in Hsl color) + { + Rgb rgb = ToRgb(color); - return CmykAndRgbConverter.Convert(rgb); - } + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = ToCmyk(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static Cmyk ToCmyk(in Hsv color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static Cmyk ToCmyk(in Hsv color) + { + Rgb rgb = ToRgb(color); - return CmykAndRgbConverter.Convert(rgb); - } + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = ToCmyk(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static Cmyk ToCmyk(in LinearRgb color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static Cmyk ToCmyk(in LinearRgb color) + { + Rgb rgb = ToRgb(color); - return CmykAndRgbConverter.Convert(rgb); - } + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = ToCmyk(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToCmyk(xyzColor); - } + return this.ToCmyk(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors, + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors, - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static Cmyk ToCmyk(in Rgb color) => CmykAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static Cmyk ToCmyk(in Rgb color) => CmykAndRgbConverter.Convert(color); - - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = ToCmyk(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = ToCmyk(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Cmyk ToCmyk(in YCbCr color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Cmyk ToCmyk(in YCbCr color) + { + Rgb rgb = this.ToRgb(color); - return CmykAndRgbConverter.Convert(rgb); - } + return CmykAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref Cmyk destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref Cmyk dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToCmyk(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Cmyk dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToCmyk(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs index 19e1bef0d0..b763a3ebe7 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -1,439 +1,437 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLab color) { - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsl ToHsl(in CieXyz color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsl ToHsl(in CieXyz color) + { + Rgb rgb = this.ToRgb(color); - return HslAndRgbConverter.Convert(rgb); - } + return HslAndRgbConverter.Convert(rgb); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsl ToHsl(in Cmyk color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsl ToHsl(in Cmyk color) + { + Rgb rgb = ToRgb(color); - return HslAndRgbConverter.Convert(rgb); - } + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsl(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsl(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsl ToHsl(in Hsv color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsl ToHsl(in Hsv color) + { + Rgb rgb = ToRgb(color); - return HslAndRgbConverter.Convert(rgb); - } + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsl(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static Hsl ToHsl(in LinearRgb color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static Hsl ToHsl(in LinearRgb color) + { + Rgb rgb = ToRgb(color); - return HslAndRgbConverter.Convert(rgb); - } + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsl(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsl(xyzColor); - } + return this.ToHsl(xyzColor); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsl ToHsl(in Rgb color) => HslAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsl ToHsl(in Rgb color) => HslAndRgbConverter.Convert(color); - - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsl(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsl(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public Hsl ToHsl(in YCbCr color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public Hsl ToHsl(in YCbCr color) + { + Rgb rgb = this.ToRgb(color); - return HslAndRgbConverter.Convert(rgb); - } + return HslAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsl destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsl dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsl(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsl dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsl(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs index c6a3a68d78..4b4b9d0077 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -1,439 +1,437 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLab color) { - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieLch color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in CieXyz color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in CieXyz color) + { + Rgb rgb = this.ToRgb(color); - return HsvAndRgbConverter.Convert(rgb); - } + return HsvAndRgbConverter.Convert(rgb); + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsv ToHsv(in Cmyk color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsv ToHsv(in Cmyk color) + { + Rgb rgb = ToRgb(color); - return HsvAndRgbConverter.Convert(rgb); - } + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsv(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsv ToHsv(in Hsl color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsv ToHsv(in Hsl color) + { + Rgb rgb = ToRgb(color); - return HsvAndRgbConverter.Convert(rgb); - } + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors. + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors. - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsv(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsv ToHsv(in LinearRgb color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsv ToHsv(in LinearRgb color) + { + Rgb rgb = ToRgb(color); - return HsvAndRgbConverter.Convert(rgb); - } + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsv(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToHsv(xyzColor); - } + return this.ToHsv(xyzColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Hsv ToHsv(in Rgb color) => HsvAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Hsv ToHsv(in Rgb color) => HsvAndRgbConverter.Convert(color); - - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = ToHsv(sp); - } + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = ToHsv(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Hsv ToHsv(in YCbCr color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Hsv ToHsv(in YCbCr color) + { + Rgb rgb = this.ToRgb(color); - return HsvAndRgbConverter.Convert(rgb); - } + return HsvAndRgbConverter.Convert(rgb); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref Hsv destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref Hsv dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHsv(sp); - } + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Hsv dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHsv(sp); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs index 0e880ed591..01c040231a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -1,432 +1,430 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) { - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref HunterLab dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToHunterLab(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieLab color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); - } + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieLch color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieLchuv color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieLuv color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); - } + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieXyy color) + for (int i = 0; i < count; i++) { - var xyzColor = ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in CieXyz color) - { - CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetHunterLabWhitePoint); + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - return this.cieXyzToHunterLabConverter.Convert(adapted); - } + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in Cmyk color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in Hsl color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in Hsv color) + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in LinearRgb color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in Lms color) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in Rgb color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public HunterLab ToHunterLab(in YCbCr color) + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref HunterLab destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToHunterLab(xyzColor); + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref HunterLab dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToHunterLab(sp); } } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieXyy color) + { + var xyzColor = ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in CieXyz color) + { + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetHunterLabWhitePoint); + + return this.cieXyzToHunterLabConverter.Convert(adapted); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Lms color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public HunterLab ToHunterLab(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToHunterLab(xyzColor); + } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index c5801b0b40..416274e003 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -1,431 +1,429 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) { - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public static void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToLinearRgb(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public static void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToLinearRgb(sp); - } - } + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToLinearRgb(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToLinearRgb(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLinearRgb(sp); - } - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); - } + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieLch color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieLchuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); - } + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieLuv color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieXyy color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = ToCieXyz(color); - return this.ToLinearRgb(xyzColor); + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToLinearRgb(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in CieXyz color) - { - // Adaptation - CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetRgbWorkingSpace.WhitePoint); + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - // Conversion - return this.cieXyzToLinearRgbConverter.Convert(adapted); - } + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static LinearRgb ToLinearRgb(in Cmyk color) + for (int i = 0; i < count; i++) { - Rgb rgb = ToRgb(color); - return ToLinearRgb(rgb); + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static LinearRgb ToLinearRgb(in Hsl color) + for (int i = 0; i < count; i++) { - Rgb rgb = ToRgb(color); - return ToLinearRgb(rgb); + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static LinearRgb ToLinearRgb(in Hsv color) + for (int i = 0; i < count; i++) { - Rgb rgb = ToRgb(color); - return ToLinearRgb(rgb); + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in HunterLab color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in Lms color) + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToLinearRgb(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToLinearRgb(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static LinearRgb ToLinearRgb(in Rgb color) - => RgbToLinearRgbConverter.Convert(color); - - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public LinearRgb ToLinearRgb(in YCbCr color) + for (int i = 0; i < count; i++) { - Rgb rgb = this.ToRgb(color); - return ToLinearRgb(rgb); + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in CieXyz color) + { + // Adaptation + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetRgbWorkingSpace.WhitePoint); + + // Conversion + return this.cieXyzToLinearRgbConverter.Convert(adapted); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static LinearRgb ToLinearRgb(in Cmyk color) + { + Rgb rgb = ToRgb(color); + return ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static LinearRgb ToLinearRgb(in Hsl color) + { + Rgb rgb = ToRgb(color); + return ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static LinearRgb ToLinearRgb(in Hsv color) + { + Rgb rgb = ToRgb(color); + return ToLinearRgb(rgb); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToLinearRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static LinearRgb ToLinearRgb(in Rgb color) + => RgbToLinearRgbConverter.Convert(color); + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public LinearRgb ToLinearRgb(in YCbCr color) + { + Rgb rgb = this.ToRgb(color); + return ToLinearRgb(rgb); + } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs index dbaaa4dd0e..e2870a6eb4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs @@ -1,427 +1,425 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) { - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref Lms destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref Lms dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToLms(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieLab color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); - } + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieLch color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieLchuv color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieLuv color) - { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); - } + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieXyy color) + for (int i = 0; i < count; i++) { - var xyzColor = ToCieXyz(color); - return this.ToLms(xyzColor); + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in CieXyz color) => this.cieXyzAndLmsConverter.Convert(color); - - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in Cmyk color) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in Hsl color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in Hsv color) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in HunterLab color) + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in LinearRgb color) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in Rgb color) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Lms destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Lms ToLms(in YCbCr color) + for (int i = 0; i < count; i++) { - var xyzColor = this.ToCieXyz(color); - return this.ToLms(xyzColor); + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Lms dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLms(sp); } } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLch color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLchuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieLuv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieXyy color) + { + var xyzColor = ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in CieXyz color) => this.cieXyzAndLmsConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Cmyk color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Hsl color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Hsv color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in HunterLab color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in LinearRgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in Rgb color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Lms ToLms(in YCbCr color) + { + var xyzColor = this.ToCieXyz(color); + return this.ToLms(xyzColor); + } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index 461ce8c21c..7346a28f33 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -1,421 +1,419 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) { - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } - } + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } - } + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } - } + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToRgb(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToRgb(sp); - } - } + ref CieLchuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToRgb(sp); - } + ref CieLchuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } - } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = ToRgb(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); - ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToRgb(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieLab color) + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToRgb(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieLch color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToRgb(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieLchuv color) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToRgb(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieLuv color) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieXyy color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = ToCieXyz(color); - return this.ToRgb(xyzColor); + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = ToRgb(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in CieXyz color) - { - // Conversion - LinearRgb linear = this.ToLinearRgb(color); + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - // Compand - return ToRgb(linear); - } + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Rgb ToRgb(in Cmyk color) => CmykAndRgbConverter.Convert(color); - - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Rgb ToRgb(in Hsv color) => HsvAndRgbConverter.Convert(color); - - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Rgb ToRgb(in Hsl color) => HslAndRgbConverter.Convert(color); - - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in HunterLab color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public static Rgb ToRgb(in LinearRgb color) => LinearRgbToRgbConverter.Convert(color); - - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in Lms color) + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - return this.ToRgb(xyzColor); + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToRgb(sp); } + } - /// - /// Converts a into a - /// - /// The color to convert. - /// The - public Rgb ToRgb(in YCbCr color) - { - // Conversion - Rgb rgb = YCbCrAndRgbConverter.Convert(color); + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } - // Adaptation - return this.Adapt(rgb); - } + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLchuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in CieXyz color) + { + // Conversion + LinearRgb linear = this.ToLinearRgb(color); + + // Compand + return ToRgb(linear); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Rgb ToRgb(in Cmyk color) => CmykAndRgbConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Rgb ToRgb(in Hsv color) => HsvAndRgbConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Rgb ToRgb(in Hsl color) => HslAndRgbConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public static Rgb ToRgb(in LinearRgb color) => LinearRgbToRgbConverter.Convert(color); + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); + return this.ToRgb(xyzColor); + } + + /// + /// Converts a into a + /// + /// The color to convert. + /// The + public Rgb ToRgb(in YCbCr color) + { + // Conversion + Rgb rgb = YCbCrAndRgbConverter.Convert(color); + + // Adaptation + return this.Adapt(rgb); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs index 48bb8ebd61..f267a0d89d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -1,406 +1,404 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Allows conversion to . +/// +public partial class ColorSpaceConverter { - /// - /// Allows conversion to . - /// - public partial class ColorSpaceConverter + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) { - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } - } + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors. - /// The span to the destination colors. - public void Convert(ReadOnlySpan source, Span destination) + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref CieLab sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors. + /// The span to the destination colors. + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLch sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref CieLch sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieLuv sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref CieLuv sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref CieXyy sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref CieXyy sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = ToYCbCr(sp); - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Cmyk sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = ToYCbCr(sp); - } + ref Cmyk sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + ref Hsl sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = ToYCbCr(sp); - } + ref Hsl sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Hsv sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref Hsv sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref HunterLab sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = ToYCbCr(sp); - } + ref HunterLab sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } + + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public void Convert(ReadOnlySpan source, Span destination) + ref LinearRgb sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Lms sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Lms sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = this.ToYCbCr(sp); - } + ref LinearRgb sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = ToYCbCr(sp); } + } - /// - /// Performs the bulk conversion from into . - /// - /// The span to the source colors - /// The span to the destination colors - public static void Convert(ReadOnlySpan source, Span destination) + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + ref Lms sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); - ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); - ref YCbCr dp = ref Unsafe.Add(ref destRef, i); - dp = ToYCbCr(sp); - } + ref Lms sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToYCbCr(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Performs the bulk conversion from into . + /// + /// The span to the source colors + /// The span to the destination colors + public static void Convert(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; - return this.ToYCbCr(xyzColor); - } + ref Rgb sourceRef = ref MemoryMarshal.GetReference(source); + ref YCbCr destRef = ref MemoryMarshal.GetReference(destination); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieLch color) + for (int i = 0; i < count; i++) { - CieXyz xyzColor = this.ToCieXyz(color); - - return this.ToYCbCr(xyzColor); + ref Rgb sp = ref Unsafe.Add(ref sourceRef, i); + ref YCbCr dp = ref Unsafe.Add(ref destRef, i); + dp = ToYCbCr(sp); } + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieLuv color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToYCbCr(xyzColor); - } + return this.ToYCbCr(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieXyy color) - { - CieXyz xyzColor = ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLch color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToYCbCr(xyzColor); - } + return this.ToYCbCr(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieXyz color) - { - Rgb rgb = this.ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieLuv color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return YCbCrAndRgbConverter.Convert(rgb); - } + return this.ToYCbCr(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static YCbCr ToYCbCr(in Cmyk color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieXyy color) + { + CieXyz xyzColor = ToCieXyz(color); - return YCbCrAndRgbConverter.Convert(rgb); - } + return this.ToYCbCr(xyzColor); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static YCbCr ToYCbCr(in Hsl color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in CieXyz color) + { + Rgb rgb = this.ToRgb(color); - return YCbCrAndRgbConverter.Convert(rgb); - } + return YCbCrAndRgbConverter.Convert(rgb); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static YCbCr ToYCbCr(in Hsv color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static YCbCr ToYCbCr(in Cmyk color) + { + Rgb rgb = ToRgb(color); - return YCbCrAndRgbConverter.Convert(rgb); - } + return YCbCrAndRgbConverter.Convert(rgb); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in HunterLab color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static YCbCr ToYCbCr(in Hsl color) + { + Rgb rgb = ToRgb(color); - return this.ToYCbCr(xyzColor); - } + return YCbCrAndRgbConverter.Convert(rgb); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static YCbCr ToYCbCr(in LinearRgb color) - { - Rgb rgb = ToRgb(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static YCbCr ToYCbCr(in Hsv color) + { + Rgb rgb = ToRgb(color); - return YCbCrAndRgbConverter.Convert(rgb); - } + return YCbCrAndRgbConverter.Convert(rgb); + } - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in Lms color) - { - CieXyz xyzColor = this.ToCieXyz(color); + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in HunterLab color) + { + CieXyz xyzColor = this.ToCieXyz(color); - return this.ToYCbCr(xyzColor); - } + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static YCbCr ToYCbCr(in LinearRgb color) + { + Rgb rgb = ToRgb(color); - /// - /// Converts a into a . - /// - /// The color to convert. - /// The - public static YCbCr ToYCbCr(in Rgb color) => YCbCrAndRgbConverter.Convert(color); + return YCbCrAndRgbConverter.Convert(rgb); } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public YCbCr ToYCbCr(in Lms color) + { + CieXyz xyzColor = this.ToCieXyz(color); + + return this.ToYCbCr(xyzColor); + } + + /// + /// Converts a into a . + /// + /// The color to convert. + /// The + public static YCbCr ToYCbCr(in Rgb color) => YCbCrAndRgbConverter.Convert(color); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs index 001719046f..fbe441a8c7 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -3,58 +3,57 @@ using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Provides methods to allow the conversion of color values between different color spaces. +/// +public partial class ColorSpaceConverter { + // Options. + private static readonly ColorSpaceConverterOptions DefaultOptions = new ColorSpaceConverterOptions(); + private readonly Matrix4x4 lmsAdaptationMatrix; + private readonly CieXyz whitePoint; + private readonly CieXyz targetLuvWhitePoint; + private readonly CieXyz targetLabWhitePoint; + private readonly CieXyz targetHunterLabWhitePoint; + private readonly RgbWorkingSpace targetRgbWorkingSpace; + private readonly IChromaticAdaptation chromaticAdaptation; + private readonly bool performChromaticAdaptation; + private readonly CieXyzAndLmsConverter cieXyzAndLmsConverter; + private readonly CieXyzToCieLabConverter cieXyzToCieLabConverter; + private readonly CieXyzToCieLuvConverter cieXyzToCieLuvConverter; + private readonly CieXyzToHunterLabConverter cieXyzToHunterLabConverter; + private readonly CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; + /// - /// Provides methods to allow the conversion of color values between different color spaces. + /// Initializes a new instance of the class. /// - public partial class ColorSpaceConverter + public ColorSpaceConverter() + : this(DefaultOptions) { - // Options. - private static readonly ColorSpaceConverterOptions DefaultOptions = new ColorSpaceConverterOptions(); - private readonly Matrix4x4 lmsAdaptationMatrix; - private readonly CieXyz whitePoint; - private readonly CieXyz targetLuvWhitePoint; - private readonly CieXyz targetLabWhitePoint; - private readonly CieXyz targetHunterLabWhitePoint; - private readonly RgbWorkingSpace targetRgbWorkingSpace; - private readonly IChromaticAdaptation chromaticAdaptation; - private readonly bool performChromaticAdaptation; - private readonly CieXyzAndLmsConverter cieXyzAndLmsConverter; - private readonly CieXyzToCieLabConverter cieXyzToCieLabConverter; - private readonly CieXyzToCieLuvConverter cieXyzToCieLuvConverter; - private readonly CieXyzToHunterLabConverter cieXyzToHunterLabConverter; - private readonly CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; - - /// - /// Initializes a new instance of the class. - /// - public ColorSpaceConverter() - : this(DefaultOptions) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The configuration options. - public ColorSpaceConverter(ColorSpaceConverterOptions options) - { - Guard.NotNull(options, nameof(options)); - this.whitePoint = options.WhitePoint; - this.targetLuvWhitePoint = options.TargetLuvWhitePoint; - this.targetLabWhitePoint = options.TargetLabWhitePoint; - this.targetHunterLabWhitePoint = options.TargetHunterLabWhitePoint; - this.targetRgbWorkingSpace = options.TargetRgbWorkingSpace; - this.chromaticAdaptation = options.ChromaticAdaptation; - this.performChromaticAdaptation = this.chromaticAdaptation != null; - this.lmsAdaptationMatrix = options.LmsAdaptationMatrix; + /// + /// Initializes a new instance of the class. + /// + /// The configuration options. + public ColorSpaceConverter(ColorSpaceConverterOptions options) + { + Guard.NotNull(options, nameof(options)); + this.whitePoint = options.WhitePoint; + this.targetLuvWhitePoint = options.TargetLuvWhitePoint; + this.targetLabWhitePoint = options.TargetLabWhitePoint; + this.targetHunterLabWhitePoint = options.TargetHunterLabWhitePoint; + this.targetRgbWorkingSpace = options.TargetRgbWorkingSpace; + this.chromaticAdaptation = options.ChromaticAdaptation; + this.performChromaticAdaptation = this.chromaticAdaptation != null; + this.lmsAdaptationMatrix = options.LmsAdaptationMatrix; - this.cieXyzAndLmsConverter = new CieXyzAndLmsConverter(this.lmsAdaptationMatrix); - this.cieXyzToCieLabConverter = new CieXyzToCieLabConverter(this.targetLabWhitePoint); - this.cieXyzToCieLuvConverter = new CieXyzToCieLuvConverter(this.targetLuvWhitePoint); - this.cieXyzToHunterLabConverter = new CieXyzToHunterLabConverter(this.targetHunterLabWhitePoint); - this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(this.targetRgbWorkingSpace); - } + this.cieXyzAndLmsConverter = new CieXyzAndLmsConverter(this.lmsAdaptationMatrix); + this.cieXyzToCieLabConverter = new CieXyzToCieLabConverter(this.targetLabWhitePoint); + this.cieXyzToCieLuvConverter = new CieXyzToCieLuvConverter(this.targetLuvWhitePoint); + this.cieXyzToHunterLabConverter = new CieXyzToHunterLabConverter(this.targetHunterLabWhitePoint); + this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(this.targetRgbWorkingSpace); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs index ceb7f24ad3..4fb5210670 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -3,52 +3,51 @@ using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Configuration options for the class. +/// +public class ColorSpaceConverterOptions { /// - /// Configuration options for the class. + /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space. + /// When default, no adaptation will be performed. + /// Defaults to: . + /// + public CieXyz WhitePoint { get; set; } = CieLuv.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetLuvWhitePoint { get; set; } = CieLuv.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetLabWhitePoint { get; set; } = CieLab.DefaultWhitePoint; + + /// + /// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information) + /// Defaults to: . + /// + public CieXyz TargetHunterLabWhitePoint { get; set; } = HunterLab.DefaultWhitePoint; + + /// + /// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information) + /// Defaults to: . + /// + public RgbWorkingSpace TargetRgbWorkingSpace { get; set; } = Rgb.DefaultWorkingSpace; + + /// + /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. + /// + public IChromaticAdaptation ChromaticAdaptation { get; set; } = new VonKriesChromaticAdaptation(); + + /// + /// Gets or sets transformation matrix used in conversion to and from . /// - public class ColorSpaceConverterOptions - { - /// - /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space. - /// When default, no adaptation will be performed. - /// Defaults to: . - /// - public CieXyz WhitePoint { get; set; } = CieLuv.DefaultWhitePoint; - - /// - /// Gets or sets the white point used *when creating* Luv/LChuv colors. (Luv/LChuv colors on the input already contain the white point information) - /// Defaults to: . - /// - public CieXyz TargetLuvWhitePoint { get; set; } = CieLuv.DefaultWhitePoint; - - /// - /// Gets or sets the white point used *when creating* Lab/LChab colors. (Lab/LChab colors on the input already contain the white point information) - /// Defaults to: . - /// - public CieXyz TargetLabWhitePoint { get; set; } = CieLab.DefaultWhitePoint; - - /// - /// Gets or sets the white point used *when creating* HunterLab colors. (HunterLab colors on the input already contain the white point information) - /// Defaults to: . - /// - public CieXyz TargetHunterLabWhitePoint { get; set; } = HunterLab.DefaultWhitePoint; - - /// - /// Gets or sets the target working space used *when creating* RGB colors. (RGB colors on the input already contain the working space information) - /// Defaults to: . - /// - public RgbWorkingSpace TargetRgbWorkingSpace { get; set; } = Rgb.DefaultWorkingSpace; - - /// - /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. - /// - public IChromaticAdaptation ChromaticAdaptation { get; set; } = new VonKriesChromaticAdaptation(); - - /// - /// Gets or sets transformation matrix used in conversion to and from . - /// - public Matrix4x4 LmsAdaptationMatrix { get; set; } = CieXyzAndLmsConverter.DefaultTransformationMatrix; - } + public Matrix4x4 LmsAdaptationMatrix { get; set; } = CieXyzAndLmsConverter.DefaultTransformationMatrix; } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs index bb021f6100..abcff202f5 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyChromaticityCoordinates.cs @@ -1,85 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; // ReSharper disable CompareOfFloatsByEqualityOperator -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Represents the coordinates of CIEXY chromaticity space. +/// +public readonly struct CieXyChromaticityCoordinates : IEquatable { /// - /// Represents the coordinates of CIEXY chromaticity space. + /// Initializes a new instance of the struct. /// - public readonly struct CieXyChromaticityCoordinates : IEquatable + /// Chromaticity coordinate x (usually from 0 to 1) + /// Chromaticity coordinate y (usually from 0 to 1) + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyChromaticityCoordinates(float x, float y) { - /// - /// Initializes a new instance of the struct. - /// - /// Chromaticity coordinate x (usually from 0 to 1) - /// Chromaticity coordinate y (usually from 0 to 1) - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyChromaticityCoordinates(float x, float y) - { - this.X = x; - this.Y = y; - } + this.X = x; + this.Y = y; + } - /// - /// Gets the chromaticity X-coordinate. - /// - /// - /// Ranges usually from 0 to 1. - /// - public readonly float X { get; } + /// + /// Gets the chromaticity X-coordinate. + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float X { get; } - /// - /// Gets the chromaticity Y-coordinate - /// - /// - /// Ranges usually from 0 to 1. - /// - public readonly float Y { get; } + /// + /// Gets the chromaticity Y-coordinate + /// + /// + /// Ranges usually from 0 to 1. + /// + public readonly float Y { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) + => left.Equals(right); - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - => !left.Equals(right); + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) + => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - => HashCode.Combine(this.X, this.Y); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + => HashCode.Combine(this.X, this.Y); - /// - public override string ToString() - => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); + /// + public override string ToString() + => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); - /// - public override bool Equals(object obj) - => obj is CieXyChromaticityCoordinates other && this.Equals(other); + /// + public override bool Equals(object obj) + => obj is CieXyChromaticityCoordinates other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(CieXyChromaticityCoordinates other) - => this.X.Equals(other.X) && this.Y.Equals(other.Y); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyChromaticityCoordinates other) + => this.X.Equals(other.X) && this.Y.Equals(other.Y); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs index 9da7826c40..f16ce7b0fa 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs @@ -1,33 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLchToCieLabConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLchToCieLabConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieLab Convert(in CieLch input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieLab Convert(in CieLch input) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC - float l = input.L, c = input.C, hDegrees = input.H; - float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, c = input.C, hDegrees = input.H; + float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); - float a = c * MathF.Cos(hRadians); - float b = c * MathF.Sin(hRadians); + float a = c * MathF.Cos(hRadians); + float b = c * MathF.Sin(hRadians); - return new CieLab(l, a, b, input.WhitePoint); - } + return new CieLab(l, a, b, input.WhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs index da7d3f509e..4eeb7695e1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs @@ -1,41 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLabToCieLchConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLabToCieLchConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieLch Convert(in CieLab input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieLch Convert(in CieLab input) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC - float l = input.L, a = input.A, b = input.B; - float c = MathF.Sqrt((a * a) + (b * b)); - float hRadians = MathF.Atan2(b, a); - float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - - // Wrap the angle round at 360. - hDegrees %= 360; + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC + float l = input.L, a = input.A, b = input.B; + float c = MathF.Sqrt((a * a) + (b * b)); + float hRadians = MathF.Atan2(b, a); + float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - // Make sure it's not negative. - while (hDegrees < 0) - { - hDegrees += 360; - } + // Wrap the angle round at 360. + hDegrees %= 360; - return new CieLch(l, c, hDegrees, input.WhitePoint); + // Make sure it's not negative. + while (hDegrees < 0) + { + hDegrees += 360; } + + return new CieLch(l, c, hDegrees, input.WhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs index e852e61bea..b02ad0000b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs @@ -4,41 +4,40 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLabToCieXyzConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLabToCieXyzConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieXyz Convert(in CieLab input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieXyz Convert(in CieLab input) - { - // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html - float l = input.L, a = input.A, b = input.B; - float fy = (l + 16) / 116F; - float fx = (a / 500F) + fy; - float fz = fy - (b / 200F); - - float fx3 = Numerics.Pow3(fx); - float fz3 = Numerics.Pow3(fz); - - float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; - float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; - float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; - - Vector3 wxyz = new(input.WhitePoint.X, input.WhitePoint.Y, input.WhitePoint.Z); - - // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white) - Vector3 xyzr = Vector3.Clamp(new Vector3(xr, yr, zr), Vector3.Zero, Vector3.One); - - Vector3 xyz = xyzr * wxyz; - return new CieXyz(xyz); - } + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html + float l = input.L, a = input.A, b = input.B; + float fy = (l + 16) / 116F; + float fx = (a / 500F) + fy; + float fz = fy - (b / 200F); + + float fx3 = Numerics.Pow3(fx); + float fz3 = Numerics.Pow3(fz); + + float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; + float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? Numerics.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; + float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; + + Vector3 wxyz = new(input.WhitePoint.X, input.WhitePoint.Y, input.WhitePoint.Z); + + // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white) + Vector3 xyzr = Vector3.Clamp(new Vector3(xr, yr, zr), Vector3.Zero, Vector3.One); + + Vector3 xyz = xyzr * wxyz; + return new CieXyz(xyz); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs index c2f2e5d294..9a1e79da48 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs @@ -1,33 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLchuvToCieLuvConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLchuvToCieLuvConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieLuv Convert(in CieLchuv input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieLuv Convert(in CieLchuv input) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 - float l = input.L, c = input.C, hDegrees = input.H; - float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 + float l = input.L, c = input.C, hDegrees = input.H; + float hRadians = GeometryUtilities.DegreeToRadian(hDegrees); - float u = c * MathF.Cos(hRadians); - float v = c * MathF.Sin(hRadians); + float u = c * MathF.Cos(hRadians); + float v = c * MathF.Sin(hRadians); - return new CieLuv(l, u, v, input.WhitePoint); - } + return new CieLuv(l, u, v, input.WhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs index f894d748ba..4756bab825 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs @@ -1,41 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLuvToCieLchuvConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLuvToCieLchuvConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieLchuv Convert(in CieLuv input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieLchuv Convert(in CieLuv input) - { - // Conversion algorithm described here: - // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 - float l = input.L, a = input.U, b = input.V; - float c = MathF.Sqrt((a * a) + (b * b)); - float hRadians = MathF.Atan2(b, a); - float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - - // Wrap the angle round at 360. - hDegrees %= 360; + // Conversion algorithm described here: + // https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29 + float l = input.L, a = input.U, b = input.V; + float c = MathF.Sqrt((a * a) + (b * b)); + float hRadians = MathF.Atan2(b, a); + float hDegrees = GeometryUtilities.RadianToDegree(hRadians); - // Make sure it's not negative. - while (hDegrees < 0) - { - hDegrees += 360; - } + // Wrap the angle round at 360. + hDegrees %= 360; - return new CieLchuv(l, c, hDegrees, input.WhitePoint); + // Make sure it's not negative. + while (hDegrees < 0) + { + hDegrees += 360; } + + return new CieLchuv(l, c, hDegrees, input.WhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs index aa12e511e5..404c4e824d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs @@ -3,72 +3,71 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal static class CieLuvToCieXyzConverter { /// - /// Converts from to . + /// Performs the conversion from the input to an instance of type. /// - internal static class CieLuvToCieXyzConverter + /// The input color instance. + /// The converted result + public static CieXyz Convert(in CieLuv input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - public static CieXyz Convert(in CieLuv input) - { - // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html - float l = input.L, u = input.U, v = input.V; - - float u0 = ComputeU0(input.WhitePoint); - float v0 = ComputeV0(input.WhitePoint); - - float y = l > CieConstants.Kappa * CieConstants.Epsilon - ? Numerics.Pow3((l + 16) / 116) - : l / CieConstants.Kappa; + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html + float l = input.L, u = input.U, v = input.V; - float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; - float b = -5 * y; - const float c = -0.3333333F; - float d = y * ((39 * l / (v + (13 * l * v0))) - 5); + float u0 = ComputeU0(input.WhitePoint); + float v0 = ComputeV0(input.WhitePoint); - float x = (d - b) / (a - c); - float z = (x * a) + b; + float y = l > CieConstants.Kappa * CieConstants.Epsilon + ? Numerics.Pow3((l + 16) / 116) + : l / CieConstants.Kappa; - if (float.IsNaN(x) || x < 0) - { - x = 0; - } + float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; + float b = -5 * y; + const float c = -0.3333333F; + float d = y * ((39 * l / (v + (13 * l * v0))) - 5); - if (float.IsNaN(y) || y < 0) - { - y = 0; - } + float x = (d - b) / (a - c); + float z = (x * a) + b; - if (float.IsNaN(z) || z < 0) - { - z = 0; - } + if (float.IsNaN(x) || x < 0) + { + x = 0; + } - return new CieXyz(x, y, z); + if (float.IsNaN(y) || y < 0) + { + y = 0; } - /// - /// Calculates the blue-yellow chromacity based on the given whitepoint. - /// - /// The whitepoint - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private static float ComputeU0(in CieXyz input) - => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + if (float.IsNaN(z) || z < 0) + { + z = 0; + } - /// - /// Calculates the red-green chromacity based on the given whitepoint. - /// - /// The whitepoint - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private static float ComputeV0(in CieXyz input) - => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + return new CieXyz(x, y, z); } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeU0(in CieXyz input) + => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeV0(in CieXyz input) + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs index 619d04d0cc..4cc443cf7b 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -1,54 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between CIE XYZ and CIE xyY. +/// for formulas. +/// +internal static class CieXyzAndCieXyyConverter { /// - /// Color converter between CIE XYZ and CIE xyY. - /// for formulas. + /// Performs the conversion from the input to an instance of type. /// - internal static class CieXyzAndCieXyyConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieXyy Convert(in CieXyz input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieXyy Convert(in CieXyz input) - { - float x = input.X / (input.X + input.Y + input.Z); - float y = input.Y / (input.X + input.Y + input.Z); - - if (float.IsNaN(x) || float.IsNaN(y)) - { - return new CieXyy(0, 0, input.Y); - } + float x = input.X / (input.X + input.Y + input.Z); + float y = input.Y / (input.X + input.Y + input.Z); - return new CieXyy(x, y, input.Y); + if (float.IsNaN(x) || float.IsNaN(y)) + { + return new CieXyy(0, 0, input.Y); } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieXyz Convert(in CieXyy input) + return new CieXyy(x, y, input.Y); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieXyz Convert(in CieXyy input) + { + if (MathF.Abs(input.Y) < Constants.Epsilon) { - if (MathF.Abs(input.Y) < Constants.Epsilon) - { - return new CieXyz(0, 0, input.Yl); - } + return new CieXyz(0, 0, input.Yl); + } - float x = (input.X * input.Yl) / input.Y; - float y = input.Yl; - float z = ((1 - input.X - input.Y) * y) / input.Y; + float x = (input.X * input.Yl) / input.Y; + float y = input.Yl; + float z = ((1 - input.X - input.Y) * y) / input.Y; - return new CieXyz(x, y, z); - } + return new CieXyz(x, y, z); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs index 5bb394dfa7..ba221108fb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs @@ -3,43 +3,42 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// The base class for converting between and color spaces. +/// +internal abstract class CieXyzAndHunterLabConverterBase { /// - /// The base class for converting between and color spaces. + /// Returns the Ka coefficient that depends upon the whitepoint illuminant. /// - internal abstract class CieXyzAndHunterLabConverterBase + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static float ComputeKa(CieXyz whitePoint) { - /// - /// Returns the Ka coefficient that depends upon the whitepoint illuminant. - /// - /// The whitepoint - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static float ComputeKa(CieXyz whitePoint) + if (whitePoint.Equals(Illuminants.C)) { - if (whitePoint.Equals(Illuminants.C)) - { - return 175F; - } - - return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y); + return 175F; } - /// - /// Returns the Kb coefficient that depends upon the whitepoint illuminant. - /// - /// The whitepoint - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static float ComputeKb(CieXyz whitePoint) - { - if (whitePoint == Illuminants.C) - { - return 70F; - } + return 100F * (175F / 198.04F) * (whitePoint.X + whitePoint.Y); + } - return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z); + /// + /// Returns the Kb coefficient that depends upon the whitepoint illuminant. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static float ComputeKb(CieXyz whitePoint) + { + if (whitePoint == Illuminants.C) + { + return 70F; } + + return 100F * (70F / 218.11F) * (whitePoint.Y + whitePoint.Z); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs index ac9f1f6363..b5f8a70b6f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs @@ -4,67 +4,66 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// +internal sealed class CieXyzAndLmsConverter { /// - /// Color converter between and + /// Default transformation matrix used, when no other is set. (Bradford) + /// /// - internal sealed class CieXyzAndLmsConverter - { - /// - /// Default transformation matrix used, when no other is set. (Bradford) - /// - /// - public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford; + public static readonly Matrix4x4 DefaultTransformationMatrix = LmsAdaptationMatrix.Bradford; - private Matrix4x4 inverseTransformationMatrix; - private Matrix4x4 transformationMatrix; + private Matrix4x4 inverseTransformationMatrix; + private Matrix4x4 transformationMatrix; - /// - /// Initializes a new instance of the class. - /// - public CieXyzAndLmsConverter() - : this(DefaultTransformationMatrix) - { - } + /// + /// Initializes a new instance of the class. + /// + public CieXyzAndLmsConverter() + : this(DefaultTransformationMatrix) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// Definition of the cone response domain (see ), - /// if not set will be used. - /// - public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix) - { - this.transformationMatrix = transformationMatrix; - Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); - } + /// + /// Initializes a new instance of the class. + /// + /// + /// Definition of the cone response domain (see ), + /// if not set will be used. + /// + public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix) + { + this.transformationMatrix = transformationMatrix; + Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); + } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public Lms Convert(in CieXyz input) - { - Vector3 vector = Vector3.Transform(input.ToVector3(), this.transformationMatrix); + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Lms Convert(in CieXyz input) + { + Vector3 vector = Vector3.Transform(input.ToVector3(), this.transformationMatrix); - return new Lms(vector); - } + return new Lms(vector); + } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyz Convert(in Lms input) - { - Vector3 vector = Vector3.Transform(input.ToVector3(), this.inverseTransformationMatrix); + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in Lms input) + { + Vector3 vector = Vector3.Transform(input.ToVector3(), this.inverseTransformationMatrix); - return new CieXyz(vector); - } + return new CieXyz(vector); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs index 641ee8077b..df7686c316 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal sealed class CieXyzToCieLabConverter { /// - /// Converts from to . + /// Initializes a new instance of the class. + /// + public CieXyzToCieLabConverter() + : this(CieLab.DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target reference lab white point + public CieXyzToCieLabConverter(CieXyz labWhitePoint) => this.LabWhitePoint = labWhitePoint; + + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LabWhitePoint { get; } + + /// + /// Performs the conversion from the input to an instance of type. /// - internal sealed class CieXyzToCieLabConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieLab Convert(in CieXyz input) { - /// - /// Initializes a new instance of the class. - /// - public CieXyzToCieLabConverter() - : this(CieLab.DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The target reference lab white point - public CieXyzToCieLabConverter(CieXyz labWhitePoint) => this.LabWhitePoint = labWhitePoint; - - /// - /// Gets the target reference whitepoint. When not set, is used. - /// - public CieXyz LabWhitePoint { get; } - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public CieLab Convert(in CieXyz input) - { - // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html - float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z; - - float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz; - - float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) / 116F; - float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) / 116F; - float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) / 116F; - - float l = (116F * fy) - 16F; - float a = 500F * (fx - fy); - float b = 200F * (fy - fz); - - return new CieLab(l, a, b, this.LabWhitePoint); - } + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html + float wx = this.LabWhitePoint.X, wy = this.LabWhitePoint.Y, wz = this.LabWhitePoint.Z; + + float xr = input.X / wx, yr = input.Y / wy, zr = input.Z / wz; + + float fx = xr > CieConstants.Epsilon ? MathF.Pow(xr, 0.3333333F) : ((CieConstants.Kappa * xr) + 16F) / 116F; + float fy = yr > CieConstants.Epsilon ? MathF.Pow(yr, 0.3333333F) : ((CieConstants.Kappa * yr) + 16F) / 116F; + float fz = zr > CieConstants.Epsilon ? MathF.Pow(zr, 0.3333333F) : ((CieConstants.Kappa * zr) + 16F) / 116F; + + float l = (116F * fy) - 16F; + float a = 500F * (fx - fy); + float b = 200F * (fy - fz); + + return new CieLab(l, a, b, this.LabWhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs index 48843dd81f..1e17ae54f0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs @@ -1,88 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Converts from to . +/// +internal sealed class CieXyzToCieLuvConverter { /// - /// Converts from to . + /// Initializes a new instance of the class. /// - internal sealed class CieXyzToCieLuvConverter + public CieXyzToCieLuvConverter() + : this(CieLuv.DefaultWhitePoint) { - /// - /// Initializes a new instance of the class. - /// - public CieXyzToCieLuvConverter() - : this(CieLuv.DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The target reference luv white point - public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) => this.LuvWhitePoint = luvWhitePoint; - - /// - /// Gets the target reference whitepoint. When not set, is used. - /// - public CieXyz LuvWhitePoint { get; } + } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - public CieLuv Convert(in CieXyz input) - { - // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html - float yr = input.Y / this.LuvWhitePoint.Y; - float up = ComputeUp(input); - float vp = ComputeVp(input); - float upr = ComputeUp(this.LuvWhitePoint); - float vpr = ComputeVp(this.LuvWhitePoint); + /// + /// Initializes a new instance of the class. + /// + /// The target reference luv white point + public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) => this.LuvWhitePoint = luvWhitePoint; - float l = yr > CieConstants.Epsilon ? ((116 * MathF.Pow(yr, 0.3333333F)) - 16F) : (CieConstants.Kappa * yr); + /// + /// Gets the target reference whitepoint. When not set, is used. + /// + public CieXyz LuvWhitePoint { get; } - if (float.IsNaN(l) || l < 0) - { - l = 0; - } + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + public CieLuv Convert(in CieXyz input) + { + // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Luv.html + float yr = input.Y / this.LuvWhitePoint.Y; + float up = ComputeUp(input); + float vp = ComputeVp(input); + float upr = ComputeUp(this.LuvWhitePoint); + float vpr = ComputeVp(this.LuvWhitePoint); - float u = 13 * l * (up - upr); - float v = 13 * l * (vp - vpr); + float l = yr > CieConstants.Epsilon ? ((116 * MathF.Pow(yr, 0.3333333F)) - 16F) : (CieConstants.Kappa * yr); - if (float.IsNaN(u)) - { - u = 0; - } + if (float.IsNaN(l) || l < 0) + { + l = 0; + } - if (float.IsNaN(v)) - { - v = 0; - } + float u = 13 * l * (up - upr); + float v = 13 * l * (vp - vpr); - return new CieLuv(l, u, v, this.LuvWhitePoint); + if (float.IsNaN(u)) + { + u = 0; } - /// - /// Calculates the blue-yellow chromacity based on the given whitepoint. - /// - /// The whitepoint - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ComputeUp(in CieXyz input) - => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + if (float.IsNaN(v)) + { + v = 0; + } - /// - /// Calculates the red-green chromacity based on the given whitepoint. - /// - /// The whitepoint - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private static float ComputeVp(in CieXyz input) - => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); + return new CieLuv(l, u, v, this.LuvWhitePoint); } + + /// + /// Calculates the blue-yellow chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ComputeUp(in CieXyz input) + => (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); + + /// + /// Calculates the red-green chromacity based on the given whitepoint. + /// + /// The whitepoint + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static float ComputeVp(in CieXyz input) + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs index 494c7b3c89..dab953a749 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// +internal sealed class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase { /// - /// Color converter between and + /// Initializes a new instance of the class. /// - internal sealed class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase + public CieXyzToHunterLabConverter() + : this(HunterLab.DefaultWhitePoint) { - /// - /// Initializes a new instance of the class. - /// - public CieXyzToHunterLabConverter() - : this(HunterLab.DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The hunter Lab white point. - public CieXyzToHunterLabConverter(CieXyz labWhitePoint) => this.HunterLabWhitePoint = labWhitePoint; + } - /// - /// Gets the target reference white. When not set, is used. - /// - public CieXyz HunterLabWhitePoint { get; } + /// + /// Initializes a new instance of the class. + /// + /// The hunter Lab white point. + public CieXyzToHunterLabConverter(CieXyz labWhitePoint) => this.HunterLabWhitePoint = labWhitePoint; - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public HunterLab Convert(in CieXyz input) - { - // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab - float x = input.X, y = input.Y, z = input.Z; - float xn = this.HunterLabWhitePoint.X, yn = this.HunterLabWhitePoint.Y, zn = this.HunterLabWhitePoint.Z; + /// + /// Gets the target reference white. When not set, is used. + /// + public CieXyz HunterLabWhitePoint { get; } - float ka = ComputeKa(this.HunterLabWhitePoint); - float kb = ComputeKb(this.HunterLabWhitePoint); + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab Convert(in CieXyz input) + { + // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab + float x = input.X, y = input.Y, z = input.Z; + float xn = this.HunterLabWhitePoint.X, yn = this.HunterLabWhitePoint.Y, zn = this.HunterLabWhitePoint.Z; - float yByYn = y / yn; - float sqrtYbyYn = MathF.Sqrt(yByYn); - float l = 100 * sqrtYbyYn; - float a = ka * (((x / xn) - yByYn) / sqrtYbyYn); - float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn); + float ka = ComputeKa(this.HunterLabWhitePoint); + float kb = ComputeKb(this.HunterLabWhitePoint); - if (float.IsNaN(a)) - { - a = 0; - } + float yByYn = y / yn; + float sqrtYbyYn = MathF.Sqrt(yByYn); + float l = 100 * sqrtYbyYn; + float a = ka * (((x / xn) - yByYn) / sqrtYbyYn); + float b = kb * ((yByYn - (z / zn)) / sqrtYbyYn); - if (float.IsNaN(b)) - { - b = 0; - } + if (float.IsNaN(a)) + { + a = 0; + } - return new HunterLab(l, a, b, this.HunterLabWhitePoint); + if (float.IsNaN(b)) + { + b = 0; } + + return new HunterLab(l, a, b, this.HunterLabWhitePoint); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs index c9ae11ffcc..4d15cb5d56 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -4,53 +4,52 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// +internal sealed class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public CieXyzToLinearRgbConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public CieXyzToLinearRgbConverter(RgbWorkingSpace workingSpace) + { + this.TargetWorkingSpace = workingSpace; + + // Gets the inverted Rgb -> Xyz matrix + Matrix4x4.Invert(GetRgbToCieXyzMatrix(workingSpace), out Matrix4x4 inverted); + + this.conversionMatrix = inverted; + } + /// - /// Color converter between and + /// Gets the target working space. /// - internal sealed class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase + public RgbWorkingSpace TargetWorkingSpace { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb Convert(in CieXyz input) { - private readonly Matrix4x4 conversionMatrix; - - /// - /// Initializes a new instance of the class. - /// - public CieXyzToLinearRgbConverter() - : this(Rgb.DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The target working space. - public CieXyzToLinearRgbConverter(RgbWorkingSpace workingSpace) - { - this.TargetWorkingSpace = workingSpace; - - // Gets the inverted Rgb -> Xyz matrix - Matrix4x4.Invert(GetRgbToCieXyzMatrix(workingSpace), out Matrix4x4 inverted); - - this.conversionMatrix = inverted; - } - - /// - /// Gets the target working space. - /// - public RgbWorkingSpace TargetWorkingSpace { get; } - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb Convert(in CieXyz input) - { - var vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); - - return new LinearRgb(vector, this.TargetWorkingSpace); - } + var vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); + + return new LinearRgb(vector, this.TargetWorkingSpace); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs index 31b11575b6..07ca1c7e46 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and . +/// +internal static class CmykAndRgbConverter { /// - /// Color converter between and . + /// Performs the conversion from the input to an instance of type. /// - internal static class CmykAndRgbConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgb Convert(in Cmyk input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static Rgb Convert(in Cmyk input) - { - Vector3 rgb = (Vector3.One - new Vector3(input.C, input.M, input.Y)) * (Vector3.One - new Vector3(input.K)); - return new Rgb(rgb); - } + Vector3 rgb = (Vector3.One - new Vector3(input.C, input.M, input.Y)) * (Vector3.One - new Vector3(input.K)); + return new Rgb(rgb); + } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public static Cmyk Convert(in Rgb input) - { - // To CMY - Vector3 cmy = Vector3.One - input.ToVector3(); + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public static Cmyk Convert(in Rgb input) + { + // To CMY + Vector3 cmy = Vector3.One - input.ToVector3(); - // To CMYK - Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); + // To CMYK + Vector3 k = new(MathF.Min(cmy.X, MathF.Min(cmy.Y, cmy.Z))); - if (MathF.Abs(k.X - 1F) < Constants.Epsilon) - { - return new Cmyk(0, 0, 0, 1F); - } + if (MathF.Abs(k.X - 1F) < Constants.Epsilon) + { + return new Cmyk(0, 0, 0, 1F); + } - cmy = (cmy - k) / (Vector3.One - k); + cmy = (cmy - k) / (Vector3.One - k); - return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); - } + return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs index 70cc064a8b..24aecc3c45 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs @@ -1,160 +1,158 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between HSL and Rgb +/// See for formulas. +/// +internal static class HslAndRgbConverter { /// - /// Color converter between HSL and Rgb - /// See for formulas. + /// Performs the conversion from the input to an instance of type. /// - internal static class HslAndRgbConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgb Convert(in Hsl input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static Rgb Convert(in Hsl input) + float rangedH = input.H / 360F; + float r = 0; + float g = 0; + float b = 0; + float s = input.S; + float l = input.L; + + if (MathF.Abs(l) > Constants.Epsilon) { - float rangedH = input.H / 360F; - float r = 0; - float g = 0; - float b = 0; - float s = input.S; - float l = input.L; - - if (MathF.Abs(l) > Constants.Epsilon) + if (MathF.Abs(s) < Constants.Epsilon) { - if (MathF.Abs(s) < Constants.Epsilon) - { - r = g = b = l; - } - else - { - float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s); - float temp1 = (2F * l) - temp2; - - r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); - g = GetColorComponent(temp1, temp2, rangedH); - b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); - } + r = g = b = l; } - - return new Rgb(r, g, b); - } - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static Hsl Convert(in Rgb input) - { - float r = input.R; - float g = input.G; - float b = input.B; - - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; - float h = 0F; - float s = 0F; - float l = (max + min) / 2F; - - if (MathF.Abs(chroma) < Constants.Epsilon) + else { - return new Hsl(0F, s, l); - } + float temp2 = (l < .5F) ? l * (1F + s) : l + s - (l * s); + float temp1 = (2F * l) - temp2; - if (MathF.Abs(r - max) < Constants.Epsilon) - { - h = (g - b) / chroma; - } - else if (MathF.Abs(g - max) < Constants.Epsilon) - { - h = 2F + ((b - r) / chroma); - } - else if (MathF.Abs(b - max) < Constants.Epsilon) - { - h = 4F + ((r - g) / chroma); + r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); + g = GetColorComponent(temp1, temp2, rangedH); + b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); } + } - h *= 60F; - if (h < 0F) - { - h += 360F; - } + return new Rgb(r, g, b); + } - if (l <= .5F) - { - s = chroma / (max + min); - } - else - { - s = chroma / (2F - max - min); - } + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static Hsl Convert(in Rgb input) + { + float r = input.R; + float g = input.G; + float b = input.B; + + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); + float chroma = max - min; + float h = 0F; + float s = 0F; + float l = (max + min) / 2F; + + if (MathF.Abs(chroma) < Constants.Epsilon) + { + return new Hsl(0F, s, l); + } - return new Hsl(h, s, l); + if (MathF.Abs(r - max) < Constants.Epsilon) + { + h = (g - b) / chroma; + } + else if (MathF.Abs(g - max) < Constants.Epsilon) + { + h = 2F + ((b - r) / chroma); + } + else if (MathF.Abs(b - max) < Constants.Epsilon) + { + h = 4F + ((r - g) / chroma); } - /// - /// Gets the color component from the given values. - /// - /// The first value. - /// The second value. - /// The third value. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static float GetColorComponent(float first, float second, float third) + h *= 60F; + if (h < 0F) { - third = MoveIntoRange(third); - if (third < 0.1666667F) - { - return first + ((second - first) * 6F * third); - } + h += 360F; + } - if (third < .5F) - { - return second; - } + if (l <= .5F) + { + s = chroma / (max + min); + } + else + { + s = chroma / (2F - max - min); + } - if (third < 0.6666667F) - { - return first + ((second - first) * (0.6666667F - third) * 6F); - } + return new Hsl(h, s, l); + } - return first; + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static float GetColorComponent(float first, float second, float third) + { + third = MoveIntoRange(third); + if (third < 0.1666667F) + { + return first + ((second - first) * 6F * third); } - /// - /// Moves the specific value within the acceptable range for - /// conversion. - /// Used for converting colors to this type. - /// - /// The value to shift. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static float MoveIntoRange(float value) + if (third < .5F) { - if (value < 0F) - { - value++; - } - else if (value > 1F) - { - value--; - } + return second; + } + + if (third < 0.6666667F) + { + return first + ((second - first) * (0.6666667F - third) * 6F); + } + + return first; + } - return value; + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static float MoveIntoRange(float value) + { + if (value < 0F) + { + value++; + } + else if (value > 1F) + { + value--; } + + return value; } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs index 9b01c80887..5273576175 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs @@ -1,130 +1,128 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between HSV and Rgb +/// See for formulas. +/// +internal static class HsvAndRgbConverter { /// - /// Color converter between HSV and Rgb - /// See for formulas. + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgb Convert(in Hsv input) + { + float s = input.S; + float v = input.V; + + if (MathF.Abs(s) < Constants.Epsilon) + { + return new Rgb(v, v, v); + } + + float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60; + int i = (int)Math.Truncate(h); + float f = h - i; + + float p = v * (1F - s); + float q = v * (1F - (s * f)); + float t = v * (1F - (s * (1F - f))); + + float r, g, b; + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return new Rgb(r, g, b); + } + + /// + /// Performs the conversion from the input to an instance of type. /// - internal static class HsvAndRgbConverter + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static Hsv Convert(in Rgb input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static Rgb Convert(in Hsv input) + float r = input.R; + float g = input.G; + float b = input.B; + + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); + float chroma = max - min; + float h = 0; + float s = 0; + float v = max; + + if (MathF.Abs(chroma) < Constants.Epsilon) + { + return new Hsv(0, s, v); + } + + if (MathF.Abs(r - max) < Constants.Epsilon) { - float s = input.S; - float v = input.V; - - if (MathF.Abs(s) < Constants.Epsilon) - { - return new Rgb(v, v, v); - } - - float h = (MathF.Abs(input.H - 360) < Constants.Epsilon) ? 0 : input.H / 60; - int i = (int)Math.Truncate(h); - float f = h - i; - - float p = v * (1F - s); - float q = v * (1F - (s * f)); - float t = v * (1F - (s * (1F - f))); - - float r, g, b; - switch (i) - { - case 0: - r = v; - g = t; - b = p; - break; - - case 1: - r = q; - g = v; - b = p; - break; - - case 2: - r = p; - g = v; - b = t; - break; - - case 3: - r = p; - g = q; - b = v; - break; - - case 4: - r = t; - g = p; - b = v; - break; - - default: - r = v; - g = p; - b = q; - break; - } - - return new Rgb(r, g, b); + h = (g - b) / chroma; + } + else if (MathF.Abs(g - max) < Constants.Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (MathF.Abs(b - max) < Constants.Epsilon) + { + h = 4 + ((r - g) / chroma); } - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static Hsv Convert(in Rgb input) + h *= 60; + if (h < 0.0) { - float r = input.R; - float g = input.G; - float b = input.B; - - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; - float h = 0; - float s = 0; - float v = max; - - if (MathF.Abs(chroma) < Constants.Epsilon) - { - return new Hsv(0, s, v); - } - - if (MathF.Abs(r - max) < Constants.Epsilon) - { - h = (g - b) / chroma; - } - else if (MathF.Abs(g - max) < Constants.Epsilon) - { - h = 2 + ((b - r) / chroma); - } - else if (MathF.Abs(b - max) < Constants.Epsilon) - { - h = 4 + ((r - g) / chroma); - } - - h *= 60; - if (h < 0.0) - { - h += 360; - } - - s = chroma / v; - - return new Hsv(h, s, v); + h += 360; } + + s = chroma / v; + + return new Hsv(h, s, v); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs index 424b43b129..3930e8dc2d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// +internal sealed class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase { /// - /// Color converter between and + /// Performs the conversion from the input to an instance of type. /// - internal sealed class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public static CieXyz Convert(in HunterLab input) { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public static CieXyz Convert(in HunterLab input) - { - // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab - float l = input.L, a = input.A, b = input.B; - float xn = input.WhitePoint.X, yn = input.WhitePoint.Y, zn = input.WhitePoint.Z; + // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab + float l = input.L, a = input.A, b = input.B; + float xn = input.WhitePoint.X, yn = input.WhitePoint.Y, zn = input.WhitePoint.Z; - float ka = ComputeKa(input.WhitePoint); - float kb = ComputeKb(input.WhitePoint); + float ka = ComputeKa(input.WhitePoint); + float kb = ComputeKb(input.WhitePoint); - float pow = Numerics.Pow2(l / 100F); - float sqrtPow = MathF.Sqrt(pow); - float y = pow * yn; + float pow = Numerics.Pow2(l / 100F); + float sqrtPow = MathF.Sqrt(pow); + float y = pow * yn; - float x = (((a / ka) * sqrtPow) + pow) * xn; - float z = (((b / kb) * sqrtPow) - pow) * (-zn); + float x = (((a / ka) * sqrtPow) + pow) * xn; + float z = (((b / kb) * sqrtPow) - pow) * (-zn); - return new CieXyz(x, y, z); - } + return new CieXyz(x, y, z); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs index ac0ab73119..27391fc802 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -3,72 +3,71 @@ using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Provides base methods for converting between and color spaces. +/// +internal abstract class LinearRgbAndCieXyzConverterBase { /// - /// Provides base methods for converting between and color spaces. + /// Returns the correct matrix to convert between the Rgb and CieXyz color space. /// - internal abstract class LinearRgbAndCieXyzConverterBase + /// The Rgb working space. + /// The based on the chromaticity and working space. + public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) { - /// - /// Returns the correct matrix to convert between the Rgb and CieXyz color space. - /// - /// The Rgb working space. - /// The based on the chromaticity and working space. - public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) - { - DebugGuard.NotNull(workingSpace, nameof(workingSpace)); - RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; + DebugGuard.NotNull(workingSpace, nameof(workingSpace)); + RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; - float xr = chromaticity.R.X; - float xg = chromaticity.G.X; - float xb = chromaticity.B.X; - float yr = chromaticity.R.Y; - float yg = chromaticity.G.Y; - float yb = chromaticity.B.Y; + float xr = chromaticity.R.X; + float xg = chromaticity.G.X; + float xb = chromaticity.B.X; + float yr = chromaticity.R.Y; + float yg = chromaticity.G.Y; + float yb = chromaticity.B.Y; - float mXr = xr / yr; - float mZr = (1 - xr - yr) / yr; + float mXr = xr / yr; + float mZr = (1 - xr - yr) / yr; - float mXg = xg / yg; - float mZg = (1 - xg - yg) / yg; + float mXg = xg / yg; + float mZg = (1 - xg - yg) / yg; - float mXb = xb / yb; - float mZb = (1 - xb - yb) / yb; + float mXb = xb / yb; + float mZb = (1 - xb - yb) / yb; - Matrix4x4 xyzMatrix = new() - { - M11 = mXr, - M21 = mXg, - M31 = mXb, - M12 = 1F, - M22 = 1F, - M32 = 1F, - M13 = mZr, - M23 = mZg, - M33 = mZb, - M44 = 1F - }; + Matrix4x4 xyzMatrix = new() + { + M11 = mXr, + M21 = mXg, + M31 = mXb, + M12 = 1F, + M22 = 1F, + M32 = 1F, + M13 = mZr, + M23 = mZg, + M33 = mZb, + M44 = 1F + }; - Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); + Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); - Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.ToVector3(), inverseXyzMatrix); + Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.ToVector3(), inverseXyzMatrix); - // Use transposed Rows/Columns - // TODO: Is there a built in method for this multiplication? - return new Matrix4x4 - { - M11 = vector.X * mXr, - M21 = vector.Y * mXg, - M31 = vector.Z * mXb, - M12 = vector.X * 1, - M22 = vector.Y * 1, - M32 = vector.Z * 1, - M13 = vector.X * mZr, - M23 = vector.Y * mZg, - M33 = vector.Z * mZb, - M44 = 1F - }; - } + // Use transposed Rows/Columns + // TODO: Is there a built in method for this multiplication? + return new Matrix4x4 + { + M11 = vector.X * mXr, + M21 = vector.Y * mXg, + M31 = vector.Z * mXb, + M12 = vector.X * 1, + M22 = vector.Y * 1, + M32 = vector.Z * 1, + M13 = vector.X * mZr, + M23 = vector.Y * mZg, + M33 = vector.Z * mZb, + M44 = 1F + }; } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs index 5f0755969b..091cab9931 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -4,50 +4,49 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// +internal sealed class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase { + private readonly Matrix4x4 conversionMatrix; + + /// + /// Initializes a new instance of the class. + /// + public LinearRgbToCieXyzConverter() + : this(Rgb.DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The target working space. + public LinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) + { + this.SourceWorkingSpace = workingSpace; + this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); + } + /// - /// Color converter between and + /// Gets the source working space /// - internal sealed class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase + public RgbWorkingSpace SourceWorkingSpace { get; } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public CieXyz Convert(in LinearRgb input) { - private readonly Matrix4x4 conversionMatrix; - - /// - /// Initializes a new instance of the class. - /// - public LinearRgbToCieXyzConverter() - : this(Rgb.DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The target working space. - public LinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) - { - this.SourceWorkingSpace = workingSpace; - this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); - } - - /// - /// Gets the source working space - /// - public RgbWorkingSpace SourceWorkingSpace { get; } - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result - [MethodImpl(InliningOptions.ShortMethod)] - public CieXyz Convert(in LinearRgb input) - { - DebugGuard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); - - Vector3 vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); - return new CieXyz(vector); - } + DebugGuard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); + + Vector3 vector = Vector3.Transform(input.ToVector3(), this.conversionMatrix); + return new CieXyz(vector); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs index d8cfa7c567..d41e7f74da 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -3,24 +3,23 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and . +/// +internal static class LinearRgbToRgbConverter { /// - /// Color converter between and . + /// Performs the conversion from the input to an instance of type. /// - internal static class LinearRgbToRgbConverter - { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public static Rgb Convert(in LinearRgb input) => - new( - r: input.WorkingSpace.Compress(input.R), - g: input.WorkingSpace.Compress(input.G), - b: input.WorkingSpace.Compress(input.B), - workingSpace: input.WorkingSpace); - } + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgb Convert(in LinearRgb input) => + new( + r: input.WorkingSpace.Compress(input.R), + g: input.WorkingSpace.Compress(input.G), + b: input.WorkingSpace.Compress(input.B), + workingSpace: input.WorkingSpace); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs index 08a684baab..c63bbae3da 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -3,24 +3,23 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between Rgb and LinearRgb. +/// +internal static class RgbToLinearRgbConverter { /// - /// Color converter between Rgb and LinearRgb. + /// Performs the conversion from the input to an instance of type. /// - internal static class RgbToLinearRgbConverter - { - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public static LinearRgb Convert(in Rgb input) - => new( - r: input.WorkingSpace.Expand(input.R), - g: input.WorkingSpace.Expand(input.G), - b: input.WorkingSpace.Expand(input.B), - workingSpace: input.WorkingSpace); - } + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public static LinearRgb Convert(in Rgb input) + => new( + r: input.WorkingSpace.Expand(input.R), + g: input.WorkingSpace.Expand(input.G), + b: input.WorkingSpace.Expand(input.B), + workingSpace: input.WorkingSpace); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs index ac4d857385..eda55ec4c5 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Color converter between and +/// See for formulas. +/// +internal static class YCbCrAndRgbConverter { + private static readonly Vector3 MaxBytes = new(255F); + /// - /// Color converter between and - /// See for formulas. + /// Performs the conversion from the input to an instance of type. /// - internal static class YCbCrAndRgbConverter + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgb Convert(in YCbCr input) { - private static readonly Vector3 MaxBytes = new(255F); - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public static Rgb Convert(in YCbCr input) - { - float y = input.Y; - float cb = input.Cb - 128F; - float cr = input.Cr - 128F; - - float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - - return new Rgb(new Vector3(r, g, b) / MaxBytes); - } - - /// - /// Performs the conversion from the input to an instance of type. - /// - /// The input color instance. - /// The converted result. - [MethodImpl(InliningOptions.ShortMethod)] - public static YCbCr Convert(in Rgb input) - { - Vector3 rgb = input.ToVector3() * MaxBytes; - float r = rgb.X; - float g = rgb.Y; - float b = rgb.Z; - - float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - - return new YCbCr(y, cb, cr); - } + float y = input.Y; + float cb = input.Cb - 128F; + float cr = input.Cr - 128F; + + float r = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); + float g = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); + float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + + return new Rgb(new Vector3(r, g, b) / MaxBytes); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result. + [MethodImpl(InliningOptions.ShortMethod)] + public static YCbCr Convert(in Rgb input) + { + Vector3 rgb = input.ToVector3() * MaxBytes; + float r = rgb.X; + float g = rgb.Y; + float b = rgb.Z; + + float y = (0.299F * r) + (0.587F * g) + (0.114F * b); + float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + + return new YCbCr(y, cb, cr); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs index 703b3a7487..3b6abb041e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/IChromaticAdaptation.cs @@ -1,39 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +/// +/// Chromatic adaptation. +/// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M] +/// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD). +/// +public interface IChromaticAdaptation { /// - /// Chromatic adaptation. - /// A linear transformation of a source color (XS, YS, ZS) into a destination color (XD, YD, ZD) by a linear transformation [M] - /// which is dependent on the source reference white (XWS, YWS, ZWS) and the destination reference white (XWD, YWD, ZWD). + /// Performs a linear transformation of a source color in to the destination color. /// - public interface IChromaticAdaptation - { - /// - /// Performs a linear transformation of a source color in to the destination color. - /// - /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). - /// The source color. - /// The source white point. - /// The destination white point. - /// The - CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint); + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The source color. + /// The source white point. + /// The destination white point. + /// The + CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint); - /// - /// Performs a bulk linear transformation of a source color in to the destination color. - /// - /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). - /// The span to the source colors. - /// The span to the destination colors. - /// The source white point. - /// The destination white point. - void Transform( - ReadOnlySpan source, - Span destination, - CieXyz sourceWhitePoint, - in CieXyz destinationWhitePoint); - } + /// + /// Performs a bulk linear transformation of a source color in to the destination color. + /// + /// Doesn't crop the resulting color space coordinates (e. g. allows negative values for XYZ coordinates). + /// The span to the source colors. + /// The span to the destination colors. + /// The source white point. + /// The destination white point. + void Transform( + ReadOnlySpan source, + Span destination, + CieXyz sourceWhitePoint, + in CieXyz destinationWhitePoint); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs index ea0dfbb4c9..80bd160e8a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs @@ -3,132 +3,131 @@ using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Matrices used for transformation from to , defining the cone response domain. +/// Used in +/// +/// +/// Matrix data obtained from: +/// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization +/// S. Bianco, R. Schettini +/// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy +/// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.pdf +/// +public static class LmsAdaptationMatrix { /// - /// Matrices used for transformation from to , defining the cone response domain. - /// Used in + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) /// - /// - /// Matrix data obtained from: - /// Two New von Kries Based Chromatic Adaptation Transforms Found by Numerical Optimization - /// S. Bianco, R. Schettini - /// DISCo, Department of Informatics, Systems and Communication, University of Milan-Bicocca, viale Sarca 336, 20126 Milan, Italy - /// https://web.stanford.edu/~sujason/ColorBalancing/Papers/Two%20New%20von%20Kries%20Based%20Chromatic%20Adaptation.pdf - /// - public static class LmsAdaptationMatrix - { - /// - /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) - /// - public static readonly Matrix4x4 VonKriesHPEAdjusted - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.40024F, - M12 = 0.7076F, - M13 = -0.08081F, - M21 = -0.2263F, - M22 = 1.16532F, - M23 = 0.0457F, - M31 = 0, - M32 = 0, - M33 = 0.91822F, - M44 = 1F // Important for inverse transforms. - }); + public static readonly Matrix4x4 VonKriesHPEAdjusted + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.40024F, + M12 = 0.7076F, + M13 = -0.08081F, + M21 = -0.2263F, + M22 = 1.16532F, + M23 = 0.0457F, + M31 = 0, + M32 = 0, + M33 = 0.91822F, + M44 = 1F // Important for inverse transforms. + }); - /// - /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy) - /// - public static readonly Matrix4x4 VonKriesHPE - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.3897F, - M12 = 0.6890F, - M13 = -0.0787F, - M21 = -0.2298F, - M22 = 1.1834F, - M23 = 0.0464F, - M31 = 0, - M32 = 0, - M33 = 1F, - M44 = 1F - }); + /// + /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez for equal energy) + /// + public static readonly Matrix4x4 VonKriesHPE + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.3897F, + M12 = 0.6890F, + M13 = -0.0787F, + M21 = -0.2298F, + M22 = 1.1834F, + M23 = 0.0464F, + M31 = 0, + M32 = 0, + M33 = 1F, + M44 = 1F + }); - /// - /// XYZ scaling chromatic adaptation transform matrix - /// - public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); + /// + /// XYZ scaling chromatic adaptation transform matrix + /// + public static readonly Matrix4x4 XyzScaling = Matrix4x4.Transpose(Matrix4x4.Identity); - /// - /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) - /// - public static readonly Matrix4x4 Bradford - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.8951F, - M12 = 0.2664F, - M13 = -0.1614F, - M21 = -0.7502F, - M22 = 1.7135F, - M23 = 0.0367F, - M31 = 0.0389F, - M32 = -0.0685F, - M33 = 1.0296F, - M44 = 1F - }); + /// + /// Bradford chromatic adaptation transform matrix (used in CMCCAT97) + /// + public static readonly Matrix4x4 Bradford + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.8951F, + M12 = 0.2664F, + M13 = -0.1614F, + M21 = -0.7502F, + M22 = 1.7135F, + M23 = 0.0367F, + M31 = 0.0389F, + M32 = -0.0685F, + M33 = 1.0296F, + M44 = 1F + }); - /// - /// Spectral sharpening and the Bradford transform - /// - public static readonly Matrix4x4 BradfordSharp - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 1.2694F, - M12 = -0.0988F, - M13 = -0.1706F, - M21 = -0.8364F, - M22 = 1.8006F, - M23 = 0.0357F, - M31 = 0.0297F, - M32 = -0.0315F, - M33 = 1.0018F, - M44 = 1F - }); + /// + /// Spectral sharpening and the Bradford transform + /// + public static readonly Matrix4x4 BradfordSharp + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 1.2694F, + M12 = -0.0988F, + M13 = -0.1706F, + M21 = -0.8364F, + M22 = 1.8006F, + M23 = 0.0357F, + M31 = 0.0297F, + M32 = -0.0315F, + M33 = 1.0018F, + M44 = 1F + }); - /// - /// CMCCAT2000 (fitted from all available color data sets) - /// - public static readonly Matrix4x4 CMCCAT2000 - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.7982F, - M12 = 0.3389F, - M13 = -0.1371F, - M21 = -0.5918F, - M22 = 1.5512F, - M23 = 0.0406F, - M31 = 0.0008F, - M32 = 0.239F, - M33 = 0.9753F, - M44 = 1F - }); + /// + /// CMCCAT2000 (fitted from all available color data sets) + /// + public static readonly Matrix4x4 CMCCAT2000 + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.7982F, + M12 = 0.3389F, + M13 = -0.1371F, + M21 = -0.5918F, + M22 = 1.5512F, + M23 = 0.0406F, + M31 = 0.0008F, + M32 = 0.239F, + M33 = 0.9753F, + M44 = 1F + }); - /// - /// CAT02 (optimized for minimizing CIELAB differences) - /// - public static readonly Matrix4x4 CAT02 - = Matrix4x4.Transpose(new Matrix4x4 - { - M11 = 0.7328F, - M12 = 0.4296F, - M13 = -0.1624F, - M21 = -0.7036F, - M22 = 1.6975F, - M23 = 0.0061F, - M31 = 0.0030F, - M32 = 0.0136F, - M33 = 0.9834F, - M44 = 1F - }); - } + /// + /// CAT02 (optimized for minimizing CIELAB differences) + /// + public static readonly Matrix4x4 CAT02 + = Matrix4x4.Transpose(new Matrix4x4 + { + M11 = 0.7328F, + M12 = 0.4296F, + M13 = -0.1624F, + M21 = -0.7036F, + M22 = 1.6975F, + M23 = 0.0061F, + M31 = 0.0030F, + M32 = 0.0136F, + M33 = 0.9834F, + M44 = 1F + }); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs index a38626020d..9dace7c41d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -1,91 +1,88 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +/// +/// Represents the chromaticity coordinates of RGB primaries. +/// One of the specifiers of . +/// +public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable { /// - /// Represents the chromaticity coordinates of RGB primaries. - /// One of the specifiers of . + /// Initializes a new instance of the struct. /// - public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable + /// The chromaticity coordinates of the red channel. + /// The chromaticity coordinates of the green channel. + /// The chromaticity coordinates of the blue channel. + public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) { - /// - /// Initializes a new instance of the struct. - /// - /// The chromaticity coordinates of the red channel. - /// The chromaticity coordinates of the green channel. - /// The chromaticity coordinates of the blue channel. - public RgbPrimariesChromaticityCoordinates(CieXyChromaticityCoordinates r, CieXyChromaticityCoordinates g, CieXyChromaticityCoordinates b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Gets the chromaticity coordinates of the red channel. - /// - public CieXyChromaticityCoordinates R { get; } + this.R = r; + this.G = g; + this.B = b; + } - /// - /// Gets the chromaticity coordinates of the green channel. - /// - public CieXyChromaticityCoordinates G { get; } + /// + /// Gets the chromaticity coordinates of the red channel. + /// + public CieXyChromaticityCoordinates R { get; } - /// - /// Gets the chromaticity coordinates of the blue channel. - /// - public CieXyChromaticityCoordinates B { get; } + /// + /// Gets the chromaticity coordinates of the green channel. + /// + public CieXyChromaticityCoordinates G { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) - { - return left.Equals(right); - } + /// + /// Gets the chromaticity coordinates of the blue channel. + /// + public CieXyChromaticityCoordinates B { get; } - /// - /// Compares two objects for inequality - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return left.Equals(right); + } - /// - public override bool Equals(object obj) - { - return obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other); - } + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(RgbPrimariesChromaticityCoordinates left, RgbPrimariesChromaticityCoordinates right) + { + return !left.Equals(right); + } - /// - public bool Equals(RgbPrimariesChromaticityCoordinates other) - { - return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - } + /// + public override bool Equals(object obj) + { + return obj is RgbPrimariesChromaticityCoordinates other && this.Equals(other); + } - /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + /// + public bool Equals(RgbPrimariesChromaticityCoordinates other) + { + return this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); } + + /// + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs index 5723d26914..7b9915c23f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/VonKriesChromaticAdaptation.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Implementation of the von Kries chromatic adaptation model. +/// +/// +/// Transformation described here: +/// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html +/// +public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation { + private readonly CieXyzAndLmsConverter converter; + + /// + /// Initializes a new instance of the class. + /// + public VonKriesChromaticAdaptation() + : this(new CieXyzAndLmsConverter()) + { + } + /// - /// Implementation of the von Kries chromatic adaptation model. + /// Initializes a new instance of the class. /// - /// - /// Transformation described here: - /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - /// - public sealed class VonKriesChromaticAdaptation : IChromaticAdaptation + /// + /// The transformation matrix used for the conversion (definition of the cone response domain). + /// + /// + public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) + : this(new CieXyzAndLmsConverter(transformationMatrix)) { - private readonly CieXyzAndLmsConverter converter; + } + + /// + /// Initializes a new instance of the class. + /// + /// The color converter + internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; - /// - /// Initializes a new instance of the class. - /// - public VonKriesChromaticAdaptation() - : this(new CieXyzAndLmsConverter()) + /// + public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) + { + if (sourceWhitePoint.Equals(destinationWhitePoint)) { + return source; } - /// - /// Initializes a new instance of the class. - /// - /// - /// The transformation matrix used for the conversion (definition of the cone response domain). - /// - /// - public VonKriesChromaticAdaptation(Matrix4x4 transformationMatrix) - : this(new CieXyzAndLmsConverter(transformationMatrix)) + Lms sourceColorLms = this.converter.Convert(source); + Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); + + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); + + return this.converter.Convert(targetColorLms); + } + + /// + public void Transform( + ReadOnlySpan source, + Span destination, + CieXyz sourceWhitePoint, + in CieXyz destinationWhitePoint) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + int count = source.Length; + + if (sourceWhitePoint.Equals(destinationWhitePoint)) { + source.CopyTo(destination[..count]); + return; } - /// - /// Initializes a new instance of the class. - /// - /// The color converter - internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; + ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - /// - public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) + for (int i = 0; i < count; i++) { - if (sourceWhitePoint.Equals(destinationWhitePoint)) - { - return source; - } + ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); + ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - Lms sourceColorLms = this.converter.Convert(source); + Lms sourceColorLms = this.converter.Convert(sp); Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); - return this.converter.Convert(targetColorLms); - } - - /// - public void Transform( - ReadOnlySpan source, - Span destination, - CieXyz sourceWhitePoint, - in CieXyz destinationWhitePoint) - { - Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); - int count = source.Length; - - if (sourceWhitePoint.Equals(destinationWhitePoint)) - { - source.CopyTo(destination[..count]); - return; - } - - ref CieXyz sourceRef = ref MemoryMarshal.GetReference(source); - ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - ref CieXyz sp = ref Unsafe.Add(ref sourceRef, i); - ref CieXyz dp = ref Unsafe.Add(ref destRef, i); - - Lms sourceColorLms = this.converter.Convert(sp); - Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); - Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); - - Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); - var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); - - dp = this.converter.Convert(targetColorLms); - } + dp = this.converter.Convert(targetColorLms); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs index 9c2bc6f026..87b0ce8065 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -1,66 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// The gamma working space. +/// +public sealed class GammaWorkingSpace : RgbWorkingSpace { /// - /// The gamma working space. + /// Initializes a new instance of the class. /// - public sealed class GammaWorkingSpace : RgbWorkingSpace - { - /// - /// Initializes a new instance of the class. - /// - /// The gamma value. - /// The reference white point. - /// The chromaticity of the rgb primaries. - public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma; + /// The gamma value. + /// The reference white point. + /// The chromaticity of the rgb primaries. + public GammaWorkingSpace(float gamma, CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) => this.Gamma = gamma; - /// - /// Gets the gamma value. - /// - public float Gamma { get; } + /// + /// Gets the gamma value. + /// + public float Gamma { get; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Compress(float channel) => GammaCompanding.Compress(channel, this.Gamma); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => GammaCompanding.Compress(channel, this.Gamma); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Expand(float channel) => GammaCompanding.Expand(channel, this.Gamma); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => GammaCompanding.Expand(channel, this.Gamma); - /// - public override bool Equals(object obj) + /// + public override bool Equals(object obj) + { + if (obj is null) { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } + return false; + } - if (obj is GammaWorkingSpace other) - { - return this.Gamma.Equals(other.Gamma) - && this.WhitePoint.Equals(other.WhitePoint) - && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); - } + if (ReferenceEquals(this, obj)) + { + return true; + } - return false; + if (obj is GammaWorkingSpace other) + { + return this.Gamma.Equals(other.Gamma) + && this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); } - /// - public override int GetHashCode() => HashCode.Combine( - this.WhitePoint, - this.ChromaticityCoordinates, - this.Gamma); + return false; } + + /// + public override int GetHashCode() => HashCode.Combine( + this.WhitePoint, + this.ChromaticityCoordinates, + this.Gamma); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs index f608baac48..199f6c8d85 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -4,29 +4,28 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// L* working space. +/// +public sealed class LWorkingSpace : RgbWorkingSpace { /// - /// L* working space. + /// Initializes a new instance of the class. /// - public sealed class LWorkingSpace : RgbWorkingSpace + /// The reference white point. + /// The chromaticity of the rgb primaries. + public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public LWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Compress(float channel) => LCompanding.Compress(channel); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => LCompanding.Compress(channel); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Expand(float channel) => LCompanding.Expand(channel); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => LCompanding.Expand(channel); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs index b675fe53d5..52cc0f95af 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -4,29 +4,28 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. +/// +public sealed class Rec2020WorkingSpace : RgbWorkingSpace { /// - /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. + /// Initializes a new instance of the class. /// - public sealed class Rec2020WorkingSpace : RgbWorkingSpace + /// The reference white point. + /// The chromaticity of the rgb primaries. + public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public Rec2020WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Compress(float channel) => Rec2020Companding.Compress(channel); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => Rec2020Companding.Compress(channel); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Expand(float channel) => Rec2020Companding.Expand(channel); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => Rec2020Companding.Expand(channel); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs index 6df9a17b9a..c030e91025 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -4,29 +4,28 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// Rec. 709 (ITU-R Recommendation BT.709) working space. +/// +public sealed class Rec709WorkingSpace : RgbWorkingSpace { /// - /// Rec. 709 (ITU-R Recommendation BT.709) working space. + /// Initializes a new instance of the class. /// - public sealed class Rec709WorkingSpace : RgbWorkingSpace + /// The reference white point. + /// The chromaticity of the rgb primaries. + public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public Rec709WorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Compress(float channel) => Rec709Companding.Compress(channel); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => Rec709Companding.Compress(channel); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Expand(float channel) => Rec709Companding.Expand(channel); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => Rec709Companding.Expand(channel); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs index ad869a75b0..44893e19da 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpace.cs @@ -1,84 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +/// +/// Base class for all implementations of . +/// +public abstract class RgbWorkingSpace { /// - /// Base class for all implementations of . + /// Initializes a new instance of the class. /// - public abstract class RgbWorkingSpace + /// The reference white point. + /// The chromaticity of the rgb primaries. + protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - protected RgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - { - this.WhitePoint = referenceWhite; - this.ChromaticityCoordinates = chromaticityCoordinates; - } + this.WhitePoint = referenceWhite; + this.ChromaticityCoordinates = chromaticityCoordinates; + } - /// - /// Gets the reference white point - /// - public CieXyz WhitePoint { get; } + /// + /// Gets the reference white point + /// + public CieXyz WhitePoint { get; } - /// - /// Gets the chromaticity of the rgb primaries. - /// - public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } + /// + /// Gets the chromaticity of the rgb primaries. + /// + public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { get; } - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// - /// For more info see: - /// - /// - /// The channel value. - /// The representing the linear channel value. - public abstract float Expand(float channel); + /// + /// Expands a companded channel to its linear equivalent with respect to the energy. + /// + /// + /// For more info see: + /// + /// + /// The channel value. + /// The representing the linear channel value. + public abstract float Expand(float channel); - /// - /// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system). - /// - /// - /// For more info see: - /// - /// - /// The channel value. - /// The representing the nonlinear channel value. - public abstract float Compress(float channel); + /// + /// Compresses an uncompanded channel (linear) to its nonlinear equivalent (depends on the RGB color system). + /// + /// + /// For more info see: + /// + /// + /// The channel value. + /// The representing the nonlinear channel value. + public abstract float Compress(float channel); - /// - public override bool Equals(object obj) + /// + public override bool Equals(object obj) + { + if (obj is null) { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj is RgbWorkingSpace other) - { - return this.WhitePoint.Equals(other.WhitePoint) - && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); - } - return false; } - /// - public override int GetHashCode() + if (ReferenceEquals(this, obj)) { - return HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); + return true; } + + if (obj is RgbWorkingSpace other) + { + return this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); + } + + return false; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs index 236da1fd02..767157f4cb 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -4,29 +4,28 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion +namespace SixLabors.ImageSharp.ColorSpaces.Conversion; + +/// +/// The sRgb working space. +/// +public sealed class SRgbWorkingSpace : RgbWorkingSpace { /// - /// The sRgb working space. + /// Initializes a new instance of the class. /// - public sealed class SRgbWorkingSpace : RgbWorkingSpace + /// The reference white point. + /// The chromaticity of the rgb primaries. + public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + : base(referenceWhite, chromaticityCoordinates) { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The chromaticity of the rgb primaries. - public SRgbWorkingSpace(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - : base(referenceWhite, chromaticityCoordinates) - { - } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Compress(float channel) => SRgbCompanding.Compress(channel); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Compress(float channel) => SRgbCompanding.Compress(channel); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override float Expand(float channel) => SRgbCompanding.Expand(channel); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override float Expand(float channel) => SRgbCompanding.Expand(channel); } diff --git a/src/ImageSharp/ColorSpaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs index 01ea512a6d..e34a7945a9 100644 --- a/src/ImageSharp/ColorSpaces/Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -1,102 +1,100 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents a Hsl (hue, saturation, lightness) color. +/// +public readonly struct Hsl : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new(360, 1, 1); + /// - /// Represents a Hsl (hue, saturation, lightness) color. + /// Initializes a new instance of the struct. /// - public readonly struct Hsl : IEquatable + /// The h hue component. + /// The s saturation component. + /// The l value (lightness) component. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsl(float h, float s, float l) + : this(new Vector3(h, s, l)) { - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(360, 1, 1); - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The l value (lightness) component. - [MethodImpl(InliningOptions.ShortMethod)] - public Hsl(float h, float s, float l) - : this(new Vector3(h, s, l)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the h, s, l components. - [MethodImpl(InliningOptions.ShortMethod)] - public Hsl(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.H = vector.X; - this.S = vector.Y; - this.L = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, l components. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsl(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.L = vector.Z; + } - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public readonly float H { get; } + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public readonly float H { get; } - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public readonly float S { get; } + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public readonly float S { get; } - /// - /// Gets the lightness component. - /// A value ranging between 0 and 1. - /// - public readonly float L { get; } + /// + /// Gets the lightness component. + /// A value ranging between 0 and 1. + /// + public readonly float L { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Hsl left, Hsl right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Hsl left, Hsl right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.L); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.L); - /// - public override string ToString() => FormattableString.Invariant($"Hsl({this.H:#0.##}, {this.S:#0.##}, {this.L:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"Hsl({this.H:#0.##}, {this.S:#0.##}, {this.L:#0.##})"); - /// - public override bool Equals(object obj) => obj is Hsl other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Hsl other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Hsl other) - => this.H.Equals(other.H) - && this.S.Equals(other.S) - && this.L.Equals(other.L); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Hsl other) + => this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.L.Equals(other.L); } diff --git a/src/ImageSharp/ColorSpaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs index 284ef4e535..501d421e5e 100644 --- a/src/ImageSharp/ColorSpaces/Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -1,100 +1,98 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). +/// +public readonly struct Hsv : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new(360, 1, 1); + /// - /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). + /// Initializes a new instance of the struct. /// - public readonly struct Hsv : IEquatable + /// The h hue component. + /// The s saturation component. + /// The v value (brightness) component. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsv(float h, float s, float v) + : this(new Vector3(h, s, v)) { - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(360, 1, 1); - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The v value (brightness) component. - [MethodImpl(InliningOptions.ShortMethod)] - public Hsv(float h, float s, float v) - : this(new Vector3(h, s, v)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the h, s, v components. - [MethodImpl(InliningOptions.ShortMethod)] - public Hsv(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.H = vector.X; - this.S = vector.Y; - this.V = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the h, s, v components. + [MethodImpl(InliningOptions.ShortMethod)] + public Hsv(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.V = vector.Z; + } - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public readonly float H { get; } + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public readonly float H { get; } - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public readonly float S { get; } + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public readonly float S { get; } - /// - /// Gets the value (brightness) component. - /// A value ranging between 0 and 1. - /// - public readonly float V { get; } + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public readonly float V { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Hsv left, Hsv right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Hsv left, Hsv right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.H, this.S, this.V); - /// - public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"Hsv({this.H:#0.##}, {this.S:#0.##}, {this.V:#0.##})"); - /// - public override bool Equals(object obj) => obj is Hsv other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Hsv other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Hsv other) - => this.H.Equals(other.H) - && this.S.Equals(other.S) - && this.V.Equals(other.V); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Hsv other) + => this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.V.Equals(other.V); } diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs index 4b5ea842d4..a1039b2359 100644 --- a/src/ImageSharp/ColorSpaces/HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -1,136 +1,134 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an Hunter LAB color. +/// . +/// +public readonly struct HunterLab : IEquatable { /// - /// Represents an Hunter LAB color. - /// . + /// D50 standard illuminant. + /// Used when reference white is not specified explicitly. + /// + public static readonly CieXyz DefaultWhitePoint = Illuminants.C; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(float l, float a, float b) + : this(new Vector3(l, a, b), DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(float l, float a, float b, CieXyz whitePoint) + : this(new Vector3(l, a, b), whitePoint) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, a, b components. + /// Uses as white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(Vector3 vector) + : this(vector, DefaultWhitePoint) + { + } + + /// + /// Initializes a new instance of the struct. /// - public readonly struct HunterLab : IEquatable + /// The vector representing the l a b components. + /// The reference white point. + [MethodImpl(InliningOptions.ShortMethod)] + public HunterLab(Vector3 vector, CieXyz whitePoint) { - /// - /// D50 standard illuminant. - /// Used when reference white is not specified explicitly. - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.C; - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public HunterLab(float l, float a, float b) - : this(new Vector3(l, a, b), DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public HunterLab(float l, float a, float b, CieXyz whitePoint) - : this(new Vector3(l, a, b), whitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, a, b components. - /// Uses as white point. - [MethodImpl(InliningOptions.ShortMethod)] - public HunterLab(Vector3 vector) - : this(vector, DefaultWhitePoint) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l a b components. - /// The reference white point. - [MethodImpl(InliningOptions.ShortMethod)] - public HunterLab(Vector3 vector, CieXyz whitePoint) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.A = vector.Y; - this.B = vector.Z; - this.WhitePoint = whitePoint; - } - - /// - /// Gets the lightness dimension. - /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public readonly float L { get; } - - /// - /// Gets the a color component. - /// A value usually ranging from -100 to 100. Negative is green, positive magenta. - /// - public readonly float A { get; } - - /// - /// Gets the b color component. - /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow - /// - public readonly float B { get; } - - /// - /// Gets the reference white point of this color. - /// - public readonly CieXyz WhitePoint { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right); - - /// - /// Compares two objects for inequality - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); - - /// - public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object obj) => obj is HunterLab other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HunterLab other) - => this.L.Equals(other.L) - && this.A.Equals(other.A) - && this.B.Equals(other.B) - && this.WhitePoint.Equals(other.WhitePoint); + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.A = vector.Y; + this.B = vector.Z; + this.WhitePoint = whitePoint; } + + /// + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L { get; } + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A { get; } + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B { get; } + + /// + /// Gets the reference white point of this color. + /// + public readonly CieXyz WhitePoint { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(HunterLab left, HunterLab right) => left.Equals(right); + + /// + /// Compares two objects for inequality + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.L, this.A, this.B, this.WhitePoint); + + /// + public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is HunterLab other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(HunterLab other) + => this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.WhitePoint.Equals(other.WhitePoint); } diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs index 4f14982faa..7c25305c2c 100644 --- a/src/ImageSharp/ColorSpaces/Illuminants.cs +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -1,72 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// The well known standard illuminants. +/// Standard illuminants provide a basis for comparing images or colors recorded under different lighting +/// +/// +/// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html +///
+/// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant +///
+public static class Illuminants { /// - /// The well known standard illuminants. - /// Standard illuminants provide a basis for comparing images or colors recorded under different lighting + /// Incandescent / Tungsten /// - /// - /// Coefficients taken from: http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - ///
- /// Descriptions taken from: http://en.wikipedia.org/wiki/Standard_illuminant - ///
- public static class Illuminants - { - /// - /// Incandescent / Tungsten - /// - public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F); + public static readonly CieXyz A = new CieXyz(1.09850F, 1F, 0.35585F); - /// - /// Direct sunlight at noon (obsoleteF) - /// - public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F); + /// + /// Direct sunlight at noon (obsoleteF) + /// + public static readonly CieXyz B = new CieXyz(0.99072F, 1F, 0.85223F); - /// - /// Average / North sky Daylight (obsoleteF) - /// - public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F); + /// + /// Average / North sky Daylight (obsoleteF) + /// + public static readonly CieXyz C = new CieXyz(0.98074F, 1F, 1.18232F); - /// - /// Horizon Light. ICC profile PCS - /// - public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F); + /// + /// Horizon Light. ICC profile PCS + /// + public static readonly CieXyz D50 = new CieXyz(0.96422F, 1F, 0.82521F); - /// - /// Mid-morning / Mid-afternoon Daylight - /// - public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F); + /// + /// Mid-morning / Mid-afternoon Daylight + /// + public static readonly CieXyz D55 = new CieXyz(0.95682F, 1F, 0.92149F); - /// - /// Noon Daylight: TelevisionF, sRGB color space - /// - public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F); + /// + /// Noon Daylight: TelevisionF, sRGB color space + /// + public static readonly CieXyz D65 = new CieXyz(0.95047F, 1F, 1.08883F); - /// - /// North sky Daylight - /// - public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F); + /// + /// North sky Daylight + /// + public static readonly CieXyz D75 = new CieXyz(0.94972F, 1F, 1.22638F); - /// - /// Equal energy - /// - public static readonly CieXyz E = new CieXyz(1F, 1F, 1F); + /// + /// Equal energy + /// + public static readonly CieXyz E = new CieXyz(1F, 1F, 1F); - /// - /// Cool White Fluorescent - /// - public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F); + /// + /// Cool White Fluorescent + /// + public static readonly CieXyz F2 = new CieXyz(0.99186F, 1F, 0.67393F); - /// - /// D65 simulatorF, Daylight simulator - /// - public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F); + /// + /// D65 simulatorF, Daylight simulator + /// + public static readonly CieXyz F7 = new CieXyz(0.95041F, 1F, 1.08747F); - /// - /// Philips TL84F, Ultralume 40 - /// - public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F); - } + /// + /// Philips TL84F, Ultralume 40 + /// + public static readonly CieXyz F11 = new CieXyz(1.00962F, 1F, 0.64350F); } diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs index abf2c6e81a..52daf5d521 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -1,144 +1,142 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an linear Rgb color with specified working space +/// +public readonly struct LinearRgb : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; + + /// + /// The default LinearRgb working space. + /// + public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + /// - /// Represents an linear Rgb color with specified working space + /// Initializes a new instance of the struct. /// - public readonly struct LinearRgb : IEquatable + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(float r, float g, float b) + : this(r, g, b, DefaultWorkingSpace) { - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = Vector3.One; - - /// - /// The default LinearRgb working space. - /// - public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component ranging between 0 and 1. - /// The green component ranging between 0 and 1. - /// The blue component ranging between 0 and 1. - [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb(float r, float g, float b) - : this(r, g, b, DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component ranging between 0 and 1. - /// The green component ranging between 0 and 1. - /// The blue component ranging between 0 and 1. - /// The rgb working space. - [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb(float r, float g, float b, RgbWorkingSpace workingSpace) - : this(new Vector3(r, g, b), workingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the r, g, b components. - [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb(Vector3 vector) - : this(vector, DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the r, g, b components. - /// The LinearRgb working space. - [MethodImpl(InliningOptions.ShortMethod)] - public LinearRgb(Vector3 vector, RgbWorkingSpace workingSpace) - { - // Clamp to 0-1 range. - vector = Vector3.Clamp(vector, Min, Max); - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - this.WorkingSpace = workingSpace; - } - - /// - /// Gets the red component. - /// A value usually ranging between 0 and 1. - /// - public readonly float R { get; } - - /// - /// Gets the green component. - /// A value usually ranging between 0 and 1. - /// - public readonly float G { get; } - - /// - /// Gets the blue component. - /// A value usually ranging between 0 and 1. - /// - public readonly float B { get; } - - /// - /// Gets the LinearRgb color space - /// - public readonly RgbWorkingSpace WorkingSpace { get; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(LinearRgb left, LinearRgb right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(LinearRgb left, LinearRgb right) => !left.Equals(right); - - /// - /// Returns a new representing this instance. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() => new(this.R, this.G, this.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - - /// - public override string ToString() => FormattableString.Invariant($"LinearRgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object obj) => obj is LinearRgb other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(LinearRgb other) - => this.R.Equals(other.R) - && this.G.Equals(other.G) - && this.B.Equals(other.B); } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(float r, float g, float b, RgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + /// The LinearRgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(Vector3 vector, RgbWorkingSpace workingSpace) + { + // Clamp to 0-1 range. + vector = Vector3.Clamp(vector, Min, Max); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.WorkingSpace = workingSpace; + } + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R { get; } + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G { get; } + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B { get; } + + /// + /// Gets the LinearRgb color space + /// + public readonly RgbWorkingSpace WorkingSpace { get; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(LinearRgb left, LinearRgb right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(LinearRgb left, LinearRgb right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new(this.R, this.G, this.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + public override string ToString() => FormattableString.Invariant($"LinearRgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is LinearRgb other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(LinearRgb other) + => this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); } diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs index ee77118909..5a1727addb 100644 --- a/src/ImageSharp/ColorSpaces/Lms.cs +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -1,105 +1,103 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// LMS is a color space represented by the response of the three types of cones of the human eye, +/// named after their responsivity (sensitivity) at long, medium and short wavelengths. +/// +/// +public readonly struct Lms : IEquatable { /// - /// LMS is a color space represented by the response of the three types of cones of the human eye, - /// named after their responsivity (sensitivity) at long, medium and short wavelengths. - /// + /// Initializes a new instance of the struct. /// - public readonly struct Lms : IEquatable + /// L represents the responsivity at long wavelengths. + /// M represents the responsivity at medium wavelengths. + /// S represents the responsivity at short wavelengths. + [MethodImpl(InliningOptions.ShortMethod)] + public Lms(float l, float m, float s) + : this(new Vector3(l, m, s)) { - /// - /// Initializes a new instance of the struct. - /// - /// L represents the responsivity at long wavelengths. - /// M represents the responsivity at medium wavelengths. - /// S represents the responsivity at short wavelengths. - [MethodImpl(InliningOptions.ShortMethod)] - public Lms(float l, float m, float s) - : this(new Vector3(l, m, s)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the l, m, s components. - [MethodImpl(InliningOptions.ShortMethod)] - public Lms(Vector3 vector) - { - // Not clamping as documentation about this space only indicates "usual" ranges - this.L = vector.X; - this.M = vector.Y; - this.S = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the l, m, s components. + [MethodImpl(InliningOptions.ShortMethod)] + public Lms(Vector3 vector) + { + // Not clamping as documentation about this space only indicates "usual" ranges + this.L = vector.X; + this.M = vector.Y; + this.S = vector.Z; + } - /// - /// Gets the L long component. - /// A value usually ranging between -1 and 1. - /// - public readonly float L { get; } + /// + /// Gets the L long component. + /// A value usually ranging between -1 and 1. + /// + public readonly float L { get; } - /// - /// Gets the M medium component. - /// A value usually ranging between -1 and 1. - /// - public readonly float M { get; } + /// + /// Gets the M medium component. + /// A value usually ranging between -1 and 1. + /// + public readonly float M { get; } - /// - /// Gets the S short component. - /// A value usually ranging between -1 and 1. - /// - public readonly float S { get; } + /// + /// Gets the S short component. + /// A value usually ranging between -1 and 1. + /// + public readonly float S { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Lms left, Lms right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Lms left, Lms right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Lms left, Lms right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Lms left, Lms right) => !left.Equals(right); - /// - /// Returns a new representing this instance. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() => new(this.L, this.M, this.S); + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new(this.L, this.M, this.S); - /// - public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); + /// + public override int GetHashCode() => HashCode.Combine(this.L, this.M, this.S); - /// - public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); + /// + public override string ToString() => FormattableString.Invariant($"Lms({this.L:#0.##}, {this.M:#0.##}, {this.S:#0.##})"); - /// - public override bool Equals(object obj) => obj is Lms other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Lms other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Lms other) - => this.L.Equals(other.L) - && this.M.Equals(other.M) - && this.S.Equals(other.S); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Lms other) + => this.L.Equals(other.L) + && this.M.Equals(other.M) + && this.S.Equals(other.S); } diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index 76d55d04d1..b70d96b647 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -1,165 +1,163 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an RGB color with specified working space. +/// +public readonly struct Rgb : IEquatable { /// - /// Represents an RGB color with specified working space. + /// The default rgb working space. + /// + public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(float r, float g, float b) + : this(r, g, b, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component ranging between 0 and 1. + /// The green component ranging between 0 and 1. + /// The blue component ranging between 0 and 1. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(float r, float g, float b, RgbWorkingSpace workingSpace) + : this(new Vector3(r, g, b), workingSpace) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the r, g, b components. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(Vector3 vector) + : this(vector, DefaultWorkingSpace) + { + } + + /// + /// Initializes a new instance of the struct. /// - public readonly struct Rgb : IEquatable + /// The vector representing the r, g, b components. + /// The rgb working space. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(Vector3 vector, RgbWorkingSpace workingSpace) { - /// - /// The default rgb working space. - /// - public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; - - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = Vector3.One; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component ranging between 0 and 1. - /// The green component ranging between 0 and 1. - /// The blue component ranging between 0 and 1. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgb(float r, float g, float b) - : this(r, g, b, DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component ranging between 0 and 1. - /// The green component ranging between 0 and 1. - /// The blue component ranging between 0 and 1. - /// The rgb working space. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgb(float r, float g, float b, RgbWorkingSpace workingSpace) - : this(new Vector3(r, g, b), workingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the r, g, b components. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgb(Vector3 vector) - : this(vector, DefaultWorkingSpace) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the r, g, b components. - /// The rgb working space. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgb(Vector3 vector, RgbWorkingSpace workingSpace) - { - vector = Vector3.Clamp(vector, Min, Max); - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - this.WorkingSpace = workingSpace; - } - - /// - /// Gets the red component. - /// A value usually ranging between 0 and 1. - /// - public readonly float R { get; } - - /// - /// Gets the green component. - /// A value usually ranging between 0 and 1. - /// - public readonly float G { get; } - - /// - /// Gets the blue component. - /// A value usually ranging between 0 and 1. - /// - public readonly float B { get; } - - /// - /// Gets the Rgb color space - /// - public readonly RgbWorkingSpace WorkingSpace { get; } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgb(Rgb24 color) => new(color.R / 255F, color.G / 255F, color.B / 255F); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgb(Rgba32 color) => new(color.R / 255F, color.G / 255F, color.B / 255F); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgb left, Rgb right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right); - - /// - /// Returns a new representing this instance. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() => new(this.R, this.G, this.B); - - /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); - - /// - public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); - - /// - public override bool Equals(object obj) => obj is Rgb other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgb other) - => this.R.Equals(other.R) - && this.G.Equals(other.G) - && this.B.Equals(other.B); + vector = Vector3.Clamp(vector, Min, Max); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.WorkingSpace = workingSpace; } + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R { get; } + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G { get; } + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B { get; } + + /// + /// Gets the Rgb color space + /// + public readonly RgbWorkingSpace WorkingSpace { get; } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgb24 color) => new(color.R / 255F, color.G / 255F, color.B / 255F); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgba32 color) => new(color.R / 255F, color.G / 255F, color.B / 255F); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb left, Rgb right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb left, Rgb right) => !left.Equals(right); + + /// + /// Returns a new representing this instance. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Vector3 ToVector3() => new(this.R, this.G, this.B); + + /// + public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); + + /// + public override bool Equals(object obj) => obj is Rgb other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(Rgb other) + => this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); } diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs index 00cca02dab..53c8c2cf08 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -5,111 +5,110 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Chromaticity coordinates based on: +/// +public static class RgbWorkingSpaces { /// - /// Chromaticity coordinates based on: - /// - public static class RgbWorkingSpaces - { - /// - /// sRgb working space. - /// - /// - /// Uses proper companding function, according to: - /// - /// - public static readonly RgbWorkingSpace SRgb = new SRgbWorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Simplified sRgb working space (uses gamma companding instead of ). - /// See also . - /// - public static readonly RgbWorkingSpace SRgbSimplified = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Rec. 709 (ITU-R Recommendation BT.709) working space. - /// - public static readonly RgbWorkingSpace Rec709 = new Rec709WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); - - /// - /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. - /// - public static readonly RgbWorkingSpace Rec2020 = new Rec2020WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); - - /// - /// ECI Rgb v2 working space. - /// - public static readonly RgbWorkingSpace ECIRgbv2 = new LWorkingSpace(Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); - - /// - /// Adobe Rgb (1998) working space. - /// - public static readonly RgbWorkingSpace AdobeRgb1998 = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// Apple sRgb working space. - /// - public static readonly RgbWorkingSpace ApplesRgb = new GammaWorkingSpace(1.8F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); - - /// - /// Best Rgb working space. - /// - public static readonly RgbWorkingSpace BestRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); - - /// - /// Beta Rgb working space. - /// - public static readonly RgbWorkingSpace BetaRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); - - /// - /// Bruce Rgb working space. - /// - public static readonly RgbWorkingSpace BruceRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// CIE Rgb working space. - /// - public static readonly RgbWorkingSpace CIERgb = new GammaWorkingSpace(2.2F, Illuminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); - - /// - /// ColorMatch Rgb working space. - /// - public static readonly RgbWorkingSpace ColorMatchRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); - - /// - /// Don Rgb 4 working space. - /// - public static readonly RgbWorkingSpace DonRgb4 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); - - /// - /// Ekta Space PS5 working space. - /// - public static readonly RgbWorkingSpace EktaSpacePS5 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); - - /// - /// NTSC Rgb working space. - /// - public static readonly RgbWorkingSpace NTSCRgb = new GammaWorkingSpace(2.2F, Illuminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); - - /// - /// PAL/SECAM Rgb working space. - /// - public static readonly RgbWorkingSpace PALSECAMRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); - - /// - /// ProPhoto Rgb working space. - /// - public static readonly RgbWorkingSpace ProPhotoRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); - - /// - /// SMPTE-C Rgb working space. - /// - public static readonly RgbWorkingSpace SMPTECRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); - - /// - /// Wide Gamut Rgb working space. - /// - public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); - } + /// sRgb working space. + /// + /// + /// Uses proper companding function, according to: + /// + /// + public static readonly RgbWorkingSpace SRgb = new SRgbWorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Simplified sRgb working space (uses gamma companding instead of ). + /// See also . + /// + public static readonly RgbWorkingSpace SRgbSimplified = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Rec. 709 (ITU-R Recommendation BT.709) working space. + /// + public static readonly RgbWorkingSpace Rec709 = new Rec709WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); + + /// + /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. + /// + public static readonly RgbWorkingSpace Rec2020 = new Rec2020WorkingSpace(Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); + + /// + /// ECI Rgb v2 working space. + /// + public static readonly RgbWorkingSpace ECIRgbv2 = new LWorkingSpace(Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + + /// + /// Adobe Rgb (1998) working space. + /// + public static readonly RgbWorkingSpace AdobeRgb1998 = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// Apple sRgb working space. + /// + public static readonly RgbWorkingSpace ApplesRgb = new GammaWorkingSpace(1.8F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + + /// + /// Best Rgb working space. + /// + public static readonly RgbWorkingSpace BestRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + + /// + /// Beta Rgb working space. + /// + public static readonly RgbWorkingSpace BetaRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); + + /// + /// Bruce Rgb working space. + /// + public static readonly RgbWorkingSpace BruceRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// CIE Rgb working space. + /// + public static readonly RgbWorkingSpace CIERgb = new GammaWorkingSpace(2.2F, Illuminants.E, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); + + /// + /// ColorMatch Rgb working space. + /// + public static readonly RgbWorkingSpace ColorMatchRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); + + /// + /// Don Rgb 4 working space. + /// + public static readonly RgbWorkingSpace DonRgb4 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + + /// + /// Ekta Space PS5 working space. + /// + public static readonly RgbWorkingSpace EktaSpacePS5 = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); + + /// + /// NTSC Rgb working space. + /// + public static readonly RgbWorkingSpace NTSCRgb = new GammaWorkingSpace(2.2F, Illuminants.C, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + + /// + /// PAL/SECAM Rgb working space. + /// + public static readonly RgbWorkingSpace PALSECAMRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + + /// + /// ProPhoto Rgb working space. + /// + public static readonly RgbWorkingSpace ProPhotoRgb = new GammaWorkingSpace(1.8F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); + + /// + /// SMPTE-C Rgb working space. + /// + public static readonly RgbWorkingSpace SMPTECRgb = new GammaWorkingSpace(2.2F, Illuminants.D65, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + + /// + /// Wide Gamut Rgb working space. + /// + public static readonly RgbWorkingSpace WideGamutRgb = new GammaWorkingSpace(2.2F, Illuminants.D50, new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); } diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs index 2670a27008..459063fffa 100644 --- a/src/ImageSharp/ColorSpaces/YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces +namespace SixLabors.ImageSharp.ColorSpaces; + +/// +/// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg. +/// +/// +/// +public readonly struct YCbCr : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new(255); + /// - /// Represents an YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification for the JFIF use with Jpeg. - /// - /// + /// Initializes a new instance of the struct. /// - public readonly struct YCbCr : IEquatable + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + [MethodImpl(InliningOptions.ShortMethod)] + public YCbCr(float y, float cb, float cr) + : this(new Vector3(y, cb, cr)) { - private static readonly Vector3 Min = Vector3.Zero; - private static readonly Vector3 Max = new(255); - - /// - /// Initializes a new instance of the struct. - /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - [MethodImpl(InliningOptions.ShortMethod)] - public YCbCr(float y, float cb, float cr) - : this(new Vector3(y, cb, cr)) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The vector representing the y, cb, cr components. - [MethodImpl(InliningOptions.ShortMethod)] - public YCbCr(Vector3 vector) - { - vector = Vector3.Clamp(vector, Min, Max); - this.Y = vector.X; - this.Cb = vector.Y; - this.Cr = vector.Z; - } + /// + /// Initializes a new instance of the struct. + /// + /// The vector representing the y, cb, cr components. + [MethodImpl(InliningOptions.ShortMethod)] + public YCbCr(Vector3 vector) + { + vector = Vector3.Clamp(vector, Min, Max); + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; + } - /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 255. - /// - public readonly float Y { get; } + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 255. + /// + public readonly float Y { get; } - /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. - /// - public readonly float Cb { get; } + /// + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. + /// + public readonly float Cb { get; } - /// - /// Gets the Cr chroma component. - /// A value ranging between 0 and 255. - /// - public readonly float Cr { get; } + /// + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. + /// + public readonly float Cr { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(YCbCr left, YCbCr right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(YCbCr left, YCbCr right) => left.Equals(right); - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.Y, this.Cb, this.Cr); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.Y, this.Cb, this.Cr); - /// - public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); + /// + public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); - /// - public override bool Equals(object obj) => obj is YCbCr other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is YCbCr other && this.Equals(other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(YCbCr other) - => this.Y.Equals(other.Y) - && this.Cb.Equals(other.Cb) - && this.Cr.Equals(other.Cr); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(YCbCr other) + => this.Y.Equals(other.Y) + && this.Cb.Equals(other.Cb) + && this.Cr.Equals(other.Cr); } diff --git a/src/ImageSharp/Common/ByteOrder.cs b/src/ImageSharp/Common/ByteOrder.cs index 603384b3a7..edbeee9dc8 100644 --- a/src/ImageSharp/Common/ByteOrder.cs +++ b/src/ImageSharp/Common/ByteOrder.cs @@ -1,23 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// The byte order of the data stream. +/// +public enum ByteOrder { /// - /// The byte order of the data stream. + /// The big-endian byte order (Motorola). + /// Most-significant byte comes first, and ends with the least-significant byte. /// - public enum ByteOrder - { - /// - /// The big-endian byte order (Motorola). - /// Most-significant byte comes first, and ends with the least-significant byte. - /// - BigEndian, + BigEndian, - /// - /// The little-endian byte order (Intel). - /// Least-significant byte comes first and ends with the most-significant byte. - /// - LittleEndian - } + /// + /// The little-endian byte order (Intel). + /// Least-significant byte comes first and ends with the most-significant byte. + /// + LittleEndian } diff --git a/src/ImageSharp/Common/Constants.cs b/src/ImageSharp/Common/Constants.cs index 0a3378939e..fa2f72c74a 100644 --- a/src/ImageSharp/Common/Constants.cs +++ b/src/ImageSharp/Common/Constants.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Common constants used throughout the project +/// +internal static class Constants { /// - /// Common constants used throughout the project + /// The epsilon value for comparing floating point numbers. /// - internal static class Constants - { - /// - /// The epsilon value for comparing floating point numbers. - /// - public static readonly float Epsilon = 0.001F; + public static readonly float Epsilon = 0.001F; - /// - /// The epsilon squared value for comparing floating point numbers. - /// - public static readonly float EpsilonSquared = Epsilon * Epsilon; - } + /// + /// The epsilon squared value for comparing floating point numbers. + /// + public static readonly float EpsilonSquared = Epsilon * Epsilon; } diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 0c50410a54..5fb81c16b0 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -1,36 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// The exception that is thrown when the library tries to load +/// an image, which has format or content that is invalid or unsupported by ImageSharp. +/// +public class ImageFormatException : Exception { /// - /// The exception that is thrown when the library tries to load - /// an image, which has format or content that is invalid or unsupported by ImageSharp. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public class ImageFormatException : Exception + /// The error message that explains the reason for this exception. + internal ImageFormatException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - internal ImageFormatException(string errorMessage) - : base(errorMessage) - { - } + } - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - internal ImageFormatException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + internal ImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { } } diff --git a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs index 51c238af85..9b4687eb20 100644 --- a/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageProcessingException.cs @@ -1,42 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// The exception that is thrown when an error occurs when applying a process to an image. +/// +public sealed class ImageProcessingException : Exception { /// - /// The exception that is thrown when an error occurs when applying a process to an image. + /// Initializes a new instance of the class. /// - public sealed class ImageProcessingException : Exception + public ImageProcessingException() { - /// - /// Initializes a new instance of the class. - /// - public ImageProcessingException() - { - } + } - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public ImageProcessingException(string errorMessage) - : base(errorMessage) - { - } + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageProcessingException(string errorMessage) + : base(errorMessage) + { + } - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public ImageProcessingException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { } } diff --git a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs index 7b48c9fe94..6f7ada0bd1 100644 --- a/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs +++ b/src/ImageSharp/Common/Exceptions/InvalidImageContentException.cs @@ -1,42 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// The exception that is thrown when the library tries to load +/// an image which contains invalid content. +/// +public sealed class InvalidImageContentException : ImageFormatException { /// - /// The exception that is thrown when the library tries to load - /// an image which contains invalid content. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public sealed class InvalidImageContentException : ImageFormatException + /// The error message that explains the reason for this exception. + public InvalidImageContentException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public InvalidImageContentException(string errorMessage) - : base(errorMessage) - { - } + } - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public InvalidImageContentException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public InvalidImageContentException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } - internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException) - : this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException) - { - } + internal InvalidImageContentException(Size size, InvalidMemoryOperationException memoryException) + : this($"Cannot decode image. Failed to allocate buffers for possibly degenerate dimensions: {size.Width}x{size.Height}.", memoryException) + { } } diff --git a/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs index beab6586be..7adbeb6afe 100644 --- a/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/UnknownImageFormatException.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// The exception that is thrown when the library tries to load +/// an image which has an unknown format. +/// +public sealed class UnknownImageFormatException : ImageFormatException { /// - /// The exception that is thrown when the library tries to load - /// an image which has an unknown format. + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. /// - public sealed class UnknownImageFormatException : ImageFormatException + /// The error message that explains the reason for this exception. + public UnknownImageFormatException(string errorMessage) + : base(errorMessage) { - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public UnknownImageFormatException(string errorMessage) - : base(errorMessage) - { - } } } diff --git a/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs b/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs index 883468abd5..6ed83a0d8a 100644 --- a/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ConfigurationExtensions.cs @@ -1,22 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Threading.Tasks; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Contains extension methods for +/// +internal static class ConfigurationExtensions { /// - /// Contains extension methods for + /// Creates a object based on , + /// having set to /// - internal static class ConfigurationExtensions + public static ParallelOptions GetParallelOptions(this Configuration configuration) { - /// - /// Creates a object based on , - /// having set to - /// - public static ParallelOptions GetParallelOptions(this Configuration configuration) - { - return new ParallelOptions { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; - } + return new ParallelOptions { MaxDegreeOfParallelism = configuration.MaxDegreeOfParallelism }; } } diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index 92fba789ac..a7e613e196 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,55 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Encapsulates a series of time saving extension methods to the interface. +/// +internal static class EnumerableExtensions { /// - /// Encapsulates a series of time saving extension methods to the interface. + /// Generates a sequence of integral numbers within a specified range. /// - internal static class EnumerableExtensions + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) { - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// - /// The start index, inclusive. - /// - /// - /// A method that has one parameter and returns a calculating the end index. - /// - /// - /// The incremental step. - /// - /// - /// The that contains a range of sequential integral numbers. - /// - public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) - { - return RangeIterator(fromInclusive, toDelegate, step); - } + return RangeIterator(fromInclusive, toDelegate, step); + } - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// The start index, inclusive. - /// - /// A method that has one parameter and returns a calculating the end index. - /// - /// The incremental step. - /// - /// The that contains a range of sequential integral numbers. - /// - private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// The start index, inclusive. + /// + /// A method that has one parameter and returns a calculating the end index. + /// + /// The incremental step. + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) { - int i = fromInclusive; - while (toDelegate(i)) - { - yield return i; - i += step; - } + yield return i; + i += step; } } } diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 3018b2d0eb..7ed3348240 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -1,74 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +internal static class StreamExtensions { /// - /// Extension methods for the type. + /// Writes data from a stream from the provided buffer. /// - internal static class StreamExtensions - { - /// - /// Writes data from a stream from the provided buffer. - /// - /// The stream. - /// The buffer. - /// The offset within the buffer to begin writing. - /// The number of bytes to write to the stream. - public static void Write(this Stream stream, Span buffer, int offset, int count) - => stream.Write(buffer.Slice(offset, count)); + /// The stream. + /// The buffer. + /// The offset within the buffer to begin writing. + /// The number of bytes to write to the stream. + public static void Write(this Stream stream, Span buffer, int offset, int count) + => stream.Write(buffer.Slice(offset, count)); - /// - /// Reads data from a stream into the provided buffer. - /// - /// The stream. - /// The buffer. - /// The offset within the buffer where the bytes are read into. - /// The number of bytes, if available, to read. - /// The actual number of bytes read. - public static int Read(this Stream stream, Span buffer, int offset, int count) - => stream.Read(buffer.Slice(offset, count)); + /// + /// Reads data from a stream into the provided buffer. + /// + /// The stream. + /// The buffer. + /// The offset within the buffer where the bytes are read into. + /// The number of bytes, if available, to read. + /// The actual number of bytes read. + public static int Read(this Stream stream, Span buffer, int offset, int count) + => stream.Read(buffer.Slice(offset, count)); - /// - /// Skips the number of bytes in the given stream. - /// - /// The stream. - /// A byte offset relative to the origin parameter. - public static void Skip(this Stream stream, int count) + /// + /// Skips the number of bytes in the given stream. + /// + /// The stream. + /// A byte offset relative to the origin parameter. + public static void Skip(this Stream stream, int count) + { + if (count < 1) { - if (count < 1) - { - return; - } + return; + } - if (stream.CanSeek) - { - stream.Seek(count, SeekOrigin.Current); - return; - } + if (stream.CanSeek) + { + stream.Seek(count, SeekOrigin.Current); + return; + } - byte[] buffer = ArrayPool.Shared.Rent(count); - try + byte[] buffer = ArrayPool.Shared.Rent(count); + try + { + while (count > 0) { - while (count > 0) + int bytesRead = stream.Read(buffer, 0, count); + if (bytesRead == 0) { - int bytesRead = stream.Read(buffer, 0, count); - if (bytesRead == 0) - { - break; - } - - count -= bytesRead; + break; } + + count -= bytesRead; } - finally - { - ArrayPool.Shared.Return(buffer); - } + } + finally + { + ArrayPool.Shared.Return(buffer); } } } diff --git a/src/ImageSharp/Common/Helpers/ColorNumerics.cs b/src/ImageSharp/Common/Helpers/ColorNumerics.cs index 8ae344a4eb..47c4b296bd 100644 --- a/src/ImageSharp/Common/Helpers/ColorNumerics.cs +++ b/src/ImageSharp/Common/Helpers/ColorNumerics.cs @@ -1,177 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Provides optimized static methods for common mathematical functions specific +/// to color processing. +/// +internal static class ColorNumerics { /// - /// Provides optimized static methods for common mathematical functions specific - /// to color processing. + /// Vector for converting pixel to gray value as specified by + /// ITU-R Recommendation BT.709. /// - internal static class ColorNumerics - { - /// - /// Vector for converting pixel to gray value as specified by - /// ITU-R Recommendation BT.709. - /// - private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f); + private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f); - /// - /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709. - /// - /// The vector to get the luminance from. - /// - /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images). - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels) - => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1)); + /// + /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709. + /// + /// The vector to get the luminance from. + /// + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels) + => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1)); - /// - /// Gets the luminance from the rgb components using the formula - /// as specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte Get8BitBT709Luminance(byte r, byte g, byte b) - => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); + /// + /// Gets the luminance from the rgb components using the formula + /// as specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte Get8BitBT709Luminance(byte r, byte g, byte b) + => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - /// - /// Gets the luminance from the rgb components using the formula as - /// specified by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); + /// + /// Gets the luminance from the rgb components using the formula as + /// specified by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) + => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - /// - /// Gets the luminance from the rgb components using the formula as specified - /// by ITU-R Recommendation BT.709. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort Get16BitBT709Luminance(float r, float g, float b) - => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); + /// + /// Gets the luminance from the rgb components using the formula as specified + /// by ITU-R Recommendation BT.709. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort Get16BitBT709Luminance(float r, float g, float b) + => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5F); - /// - /// Scales a value from a 16 bit to an - /// 8 bit equivalent. - /// - /// The 8 bit component value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte DownScaleFrom16BitTo8Bit(ushort component) - { - // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: - // - // (V * 255) / 65535 - // - // This reduces to round(V / 257), or floor((V + 128.5)/257) - // - // Represent V as the two byte value vhi.vlo. Make a guess that the - // result is the top byte of V, vhi, then the correction to this value - // is: - // - // error = floor(((V-vhi.vhi) + 128.5) / 257) - // = floor(((vlo-vhi) + 128.5) / 257) - // - // This can be approximated using integer arithmetic (and a signed - // shift): - // - // error = (vlo-vhi+128) >> 8; - // - // The approximate differs from the exact answer only when (vlo-vhi) is - // 128; it then gives a correction of +1 when the exact correction is - // 0. This gives 128 errors. The exact answer (correct for all 16-bit - // input values) is: - // - // error = (vlo-vhi+128)*65535 >> 24; - // - // An alternative arithmetic calculation which also gives no errors is: - // - // (V * 255 + 32895) >> 16 - return (byte)(((component * 255) + 32895) >> 16); - } + /// + /// Scales a value from a 16 bit to an + /// 8 bit equivalent. + /// + /// The 8 bit component value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte DownScaleFrom16BitTo8Bit(ushort component) + { + // To scale to 8 bits From a 16-bit value V the required value (from the PNG specification) is: + // + // (V * 255) / 65535 + // + // This reduces to round(V / 257), or floor((V + 128.5)/257) + // + // Represent V as the two byte value vhi.vlo. Make a guess that the + // result is the top byte of V, vhi, then the correction to this value + // is: + // + // error = floor(((V-vhi.vhi) + 128.5) / 257) + // = floor(((vlo-vhi) + 128.5) / 257) + // + // This can be approximated using integer arithmetic (and a signed + // shift): + // + // error = (vlo-vhi+128) >> 8; + // + // The approximate differs from the exact answer only when (vlo-vhi) is + // 128; it then gives a correction of +1 when the exact correction is + // 0. This gives 128 errors. The exact answer (correct for all 16-bit + // input values) is: + // + // error = (vlo-vhi+128)*65535 >> 24; + // + // An alternative arithmetic calculation which also gives no errors is: + // + // (V * 255 + 32895) >> 16 + return (byte)(((component * 255) + 32895) >> 16); + } - /// - /// Scales a value from an 8 bit to - /// an 16 bit equivalent. - /// - /// The 8 bit component value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort UpscaleFrom8BitTo16Bit(byte component) - => (ushort)(component * 257); + /// + /// Scales a value from an 8 bit to + /// an 16 bit equivalent. + /// + /// The 8 bit component value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort UpscaleFrom8BitTo16Bit(byte component) + => (ushort)(component * 257); - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBitsNeededForColorDepth(int colors) - => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBitsNeededForColorDepth(int colors) + => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); - /// - /// Returns how many colors will be created by the specified number of bits. - /// - /// The bit depth. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetColorCountForBitDepth(int bitDepth) - => 1 << bitDepth; + /// + /// Returns how many colors will be created by the specified number of bits. + /// + /// The bit depth. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetColorCountForBitDepth(int bitDepth) + => 1 << bitDepth; - /// - /// Transforms a vector by the given color matrix. - /// - /// The source vector. - /// The transformation color matrix. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) - { - float x = vector.X; - float y = vector.Y; - float z = vector.Z; - float w = vector.W; + /// + /// Transforms a vector by the given color matrix. + /// + /// The source vector. + /// The transformation color matrix. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Transform(ref Vector4 vector, ref ColorMatrix matrix) + { + float x = vector.X; + float y = vector.Y; + float z = vector.Z; + float w = vector.W; - vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51; - vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52; - vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53; - vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54; - } + vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51; + vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52; + vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53; + vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54; + } - /// - /// Bulk variant of . - /// - /// The span of vectors - /// The transformation color matrix. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Transform(Span vectors, ref ColorMatrix matrix) - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + /// + /// Bulk variant of . + /// + /// The span of vectors + /// The transformation color matrix. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Transform(Span vectors, ref ColorMatrix matrix) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - for (int i = 0; i < vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Transform(ref v, ref matrix); - } + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Transform(ref v, ref matrix); } } } diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 6e1ed21817..be2daa139e 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; // TODO: These should just call the guard equivalents -namespace SixLabors +namespace SixLabors; + +/// +/// Provides methods to protect against invalid parameters for a DEBUG build. +/// +internal static partial class DebugGuard { /// - /// Provides methods to protect against invalid parameters for a DEBUG build. + /// Verifies whether a specific condition is met, throwing an exception if it's false. /// - internal static partial class DebugGuard + /// The condition + /// The error message + [Conditional("DEBUG")] + public static void IsTrue(bool target, string message) { - /// - /// Verifies whether a specific condition is met, throwing an exception if it's false. - /// - /// The condition - /// The error message - [Conditional("DEBUG")] - public static void IsTrue(bool target, string message) + if (!target) { - if (!target) - { - throw new InvalidOperationException(message); - } + throw new InvalidOperationException(message); } + } - /// - /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true. - /// - /// Whether the object is disposed. - /// The name of the object. - [Conditional("DEBUG")] - public static void NotDisposed(bool isDisposed, string objectName) + /// + /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true. + /// + /// Whether the object is disposed. + /// The name of the object. + [Conditional("DEBUG")] + public static void NotDisposed(bool isDisposed, string objectName) + { + if (isDisposed) { - if (isDisposed) - { - throw new ObjectDisposedException(objectName); - } + throw new ObjectDisposedException(objectName); } + } - /// - /// Verifies, that the target span is of same size than the 'other' span. - /// - /// The element type of the spans - /// The target span. - /// The 'other' span to compare 'target' to. - /// The name of the parameter that is to be checked. - /// - /// has a different size than - /// - [Conditional("DEBUG")] - public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) - where T : struct + /// + /// Verifies, that the target span is of same size than the 'other' span. + /// + /// The element type of the spans + /// The target span. + /// The 'other' span to compare 'target' to. + /// The name of the parameter that is to be checked. + /// + /// has a different size than + /// + [Conditional("DEBUG")] + public static void MustBeSameSized(ReadOnlySpan target, ReadOnlySpan other, string parameterName) + where T : struct + { + if (target.Length != other.Length) { - if (target.Length != other.Length) - { - throw new ArgumentException("Span-s must be the same size!", parameterName); - } + throw new ArgumentException("Span-s must be the same size!", parameterName); } + } - /// - /// Verifies, that the `target` span has the length of 'minSpan', or longer. - /// - /// The element type of the spans - /// The target span. - /// The 'minSpan' span to compare 'target' to. - /// The name of the parameter that is to be checked. - /// - /// has less items than - /// - [Conditional("DEBUG")] - public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) - where T : struct + /// + /// Verifies, that the `target` span has the length of 'minSpan', or longer. + /// + /// The element type of the spans + /// The target span. + /// The 'minSpan' span to compare 'target' to. + /// The name of the parameter that is to be checked. + /// + /// has less items than + /// + [Conditional("DEBUG")] + public static void MustBeSizedAtLeast(ReadOnlySpan target, ReadOnlySpan minSpan, string parameterName) + where T : struct + { + if (target.Length < minSpan.Length) { - if (target.Length < minSpan.Length) - { - throw new ArgumentException($"Span-s must be at least of length {minSpan.Length}!", parameterName); - } + throw new ArgumentException($"Span-s must be at least of length {minSpan.Length}!", parameterName); } } } diff --git a/src/ImageSharp/Common/Helpers/EnumUtils.cs b/src/ImageSharp/Common/Helpers/EnumUtils.cs index a598411629..7fbeeac0b4 100644 --- a/src/ImageSharp/Common/Helpers/EnumUtils.cs +++ b/src/ImageSharp/Common/Helpers/EnumUtils.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Common utility methods for working with enums. +/// +internal static class EnumUtils { /// - /// Common utility methods for working with enums. + /// Converts the numeric representation of the enumerated constants to an equivalent enumerated object. /// - internal static class EnumUtils + /// The type of enum + /// The value to parse + /// The default value to return. + /// The . + public static TEnum Parse(int value, TEnum defaultValue) + where TEnum : struct, Enum { - /// - /// Converts the numeric representation of the enumerated constants to an equivalent enumerated object. - /// - /// The type of enum - /// The value to parse - /// The default value to return. - /// The . - public static TEnum Parse(int value, TEnum defaultValue) - where TEnum : struct, Enum - { - DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); - - TEnum valueEnum = Unsafe.As(ref value); - if (Enum.IsDefined(valueEnum)) - { - return valueEnum; - } + DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); - return defaultValue; + TEnum valueEnum = Unsafe.As(ref value); + if (Enum.IsDefined(valueEnum)) + { + return valueEnum; } - /// - /// Returns a value indicating whether the given enum has a flag of the given value. - /// - /// The type of enum. - /// The value. - /// The flag. - /// The . - public static bool HasFlag(TEnum value, TEnum flag) - where TEnum : struct, Enum - { - DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); + return defaultValue; + } - uint flagValue = Unsafe.As(ref flag); - return (Unsafe.As(ref value) & flagValue) == flagValue; - } + /// + /// Returns a value indicating whether the given enum has a flag of the given value. + /// + /// The type of enum. + /// The value. + /// The flag. + /// The . + public static bool HasFlag(TEnum value, TEnum flag) + where TEnum : struct, Enum + { + DebugGuard.IsTrue(Unsafe.SizeOf() == sizeof(int), "Only int-sized enums are supported."); + + uint flagValue = Unsafe.As(ref flag); + return (Unsafe.As(ref value) & flagValue) == flagValue; } } diff --git a/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs b/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs index 704f75d4f7..dc10d1af3e 100644 --- a/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs +++ b/src/ImageSharp/Common/Helpers/ExifResolutionValues.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Common.Helpers +namespace SixLabors.ImageSharp.Common.Helpers; + +internal readonly struct ExifResolutionValues { - internal readonly struct ExifResolutionValues + public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution) { - public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution) - { - this.ResolutionUnit = resolutionUnit; - this.HorizontalResolution = horizontalResolution; - this.VerticalResolution = verticalResolution; - } + this.ResolutionUnit = resolutionUnit; + this.HorizontalResolution = horizontalResolution; + this.VerticalResolution = verticalResolution; + } - public ushort ResolutionUnit { get; } + public ushort ResolutionUnit { get; } - public double? HorizontalResolution { get; } + public double? HorizontalResolution { get; } - public double? VerticalResolution { get; } - } + public double? VerticalResolution { get; } } diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index fb18392f38..cc10c6e00a 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -1,30 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp; -namespace SixLabors +namespace SixLabors; + +internal static partial class Guard { - 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) { - /// - /// 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().IsValueType) { - if (value.GetType().IsValueType) - { - return; - } - - ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); + return; } + + ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); } } diff --git a/src/ImageSharp/Common/Helpers/HexConverter.cs b/src/ImageSharp/Common/Helpers/HexConverter.cs index 40f80a86f8..7ec0ca625c 100644 --- a/src/ImageSharp/Common/Helpers/HexConverter.cs +++ b/src/ImageSharp/Common/Helpers/HexConverter.cs @@ -1,98 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Common.Helpers +namespace SixLabors.ImageSharp.Common.Helpers; + +internal static class HexConverter { - internal static class HexConverter + /// + /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. + /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. + /// + /// The hexadecimal string to parse. + /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. + /// The number of bytes written to . + public static int HexStringToBytes(ReadOnlySpan chars, Span bytes) { - /// - /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. - /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. - /// - /// The hexadecimal string to parse. - /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. - /// The number of bytes written to . - public static int HexStringToBytes(ReadOnlySpan chars, Span bytes) + if ((chars.Length % 2) != 0) { - if ((chars.Length % 2) != 0) - { - throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); - } - - if ((bytes.Length * 2) < chars.Length) - { - throw new ArgumentException("Output span must be at least half the length of the input string"); - } - else - { - // Slightly better performance in the loop below, allows us to skip a bounds check - // while still supporting output buffers that are larger than necessary - bytes = bytes[..(chars.Length / 2)]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int FromChar(int c) - { - // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - // This doesn't actually allocate. - ReadOnlySpan charToHexLookup = new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255 - }; + throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); + } - return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; - } + if ((bytes.Length * 2) < chars.Length) + { + throw new ArgumentException("Output span must be at least half the length of the input string"); + } + else + { + // Slightly better performance in the loop below, allows us to skip a bounds check + // while still supporting output buffers that are larger than necessary + bytes = bytes[..(chars.Length / 2)]; + } - // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; - while (j < bytes.Length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int FromChar(int c) + { + // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + // This doesn't actually allocate. + ReadOnlySpan charToHexLookup = new byte[] { - byteLo = FromChar(chars[i + 1]); - byteHi = FromChar(chars[i]); + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255 + }; - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - { - break; - } - - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; - } + return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; + } - if (byteLo == 0xFF) - { - i++; - } + // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. if ((byteLo | byteHi) == 0xFF) { - throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); + break; } - return j; + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; } + + if (byteLo == 0xFF) + { + i++; + } + + if ((byteLo | byteHi) == 0xFF) + { + throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); + } + + return j; } } diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs index 520c19201b..e84d21ed55 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -5,26 +5,25 @@ // #define PROFILING using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Global inlining options. Helps temporarily disable inlining for better profiler output. +/// +internal static class InliningOptions { /// - /// Global inlining options. Helps temporarily disable inlining for better profiler output. + /// regardless of the build conditions. /// - internal static class InliningOptions - { - /// - /// regardless of the build conditions. - /// - public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining; #if PROFILING - public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; - - public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; + public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; + + public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else - public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; + public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization; - public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; + public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif - public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; - } + public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining; } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 6b9bd72cfa..fc6cfd585a 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -1,859 +1,857 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 +namespace SixLabors.ImageSharp; + +/// +/// Provides optimized static methods for trigonometric, logarithmic, +/// and other common mathematical functions. +/// +internal static class Numerics { + public const int BlendAlphaControl = 0b_10_00_10_00; + private const int ShuffleAlphaControl = 0b_11_11_11_11; + /// - /// Provides optimized static methods for trigonometric, logarithmic, - /// and other common mathematical functions. + /// Determine the Greatest CommonDivisor (GCD) of two numbers. /// - internal static class Numerics + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GreatestCommonDivisor(int a, int b) { - public const int BlendAlphaControl = 0b_10_00_10_00; - private const int ShuffleAlphaControl = 0b_11_11_11_11; - - /// - /// Determine the Greatest CommonDivisor (GCD) of two numbers. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GreatestCommonDivisor(int a, int b) + while (b != 0) { - while (b != 0) - { - int temp = b; - b = a % b; - a = temp; - } - - return a; + int temp = b; + b = a % b; + a = temp; } - /// - /// Determine the Least Common Multiple (LCM) of two numbers. - /// See https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LeastCommonMultiple(int a, int b) - => a / GreatestCommonDivisor(a, b) * b; - - /// - /// Calculates % 2 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo2(int x) => x & 1; - - /// - /// Calculates % 4 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo4(int x) => x & 3; - - /// - /// Calculates % 8 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Modulo8(int x) => x & 7; - - /// - /// Calculates % 8 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static nint Modulo8(nint x) => x & 7; - - /// - /// Fast (x mod m) calculator, with the restriction that - /// should be power of 2. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ModuloP2(int x, int m) => x & (m - 1); - - /// - /// Returns the absolute value of a 32-bit signed integer. - /// Uses bit shifting to speed up the operation compared to . - /// - /// - /// A number that is greater than , but less than - /// or equal to - /// - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Abs(int x) - { - int y = x >> 31; - return (x ^ y) - y; - } + return a; + } - /// - /// Returns a specified number raised to the power of 2 - /// - /// A single-precision floating-point number - /// The number raised to the power of 2. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Pow2(float x) => x * x; - - /// - /// Returns a specified number raised to the power of 3 - /// - /// A single-precision floating-point number - /// The number raised to the power of 3. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Pow3(float x) => x * x * x; - - /// - /// Implementation of 1D Gaussian G(x) function - /// - /// The x provided to G(x). - /// The spread of the blur. - /// The Gaussian G(x) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Gaussian(float x, float sigma) - { - const float numerator = 1.0f; - float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; + /// + /// Determine the Least Common Multiple (LCM) of two numbers. + /// See https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeastCommonMultiple(int a, int b) + => a / GreatestCommonDivisor(a, b) * b; - float exponentNumerator = -x * x; - float exponentDenominator = 2 * Pow2(sigma); + /// + /// Calculates % 2 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo2(int x) => x & 1; - float left = numerator / denominator; - float right = MathF.Exp(exponentNumerator / exponentDenominator); + /// + /// Calculates % 4 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo4(int x) => x & 3; - return left * right; - } + /// + /// Calculates % 8 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Modulo8(int x) => x & 7; - /// - /// Returns the result of a normalized sine cardinal function for the given value. - /// SinC(x) = sin(pi*x)/(pi*x). - /// - /// A single-precision floating-point number to calculate the result for. - /// - /// The sine cardinal of . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float SinC(float f) + /// + /// Calculates % 8 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint Modulo8(nint x) => x & 7; + + /// + /// Fast (x mod m) calculator, with the restriction that + /// should be power of 2. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ModuloP2(int x, int m) => x & (m - 1); + + /// + /// Returns the absolute value of a 32-bit signed integer. + /// Uses bit shifting to speed up the operation compared to . + /// + /// + /// A number that is greater than , but less than + /// or equal to + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Abs(int x) + { + int y = x >> 31; + return (x ^ y) - y; + } + + /// + /// Returns a specified number raised to the power of 2 + /// + /// A single-precision floating-point number + /// The number raised to the power of 2. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow2(float x) => x * x; + + /// + /// Returns a specified number raised to the power of 3 + /// + /// A single-precision floating-point number + /// The number raised to the power of 3. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow3(float x) => x * x * x; + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Gaussian(float x, float sigma) + { + const float numerator = 1.0f; + float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; + + float exponentNumerator = -x * x; + float exponentDenominator = 2 * Pow2(sigma); + + float left = numerator / denominator; + float right = MathF.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + /// + /// Returns the result of a normalized sine cardinal function for the given value. + /// SinC(x) = sin(pi*x)/(pi*x). + /// + /// A single-precision floating-point number to calculate the result for. + /// + /// The sine cardinal of . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float SinC(float f) + { + if (MathF.Abs(f) > Constants.Epsilon) { - if (MathF.Abs(f) > Constants.Epsilon) - { - f *= MathF.PI; - float result = MathF.Sin(f) / f; - return MathF.Abs(result) < Constants.Epsilon ? 0F : result; - } + f *= MathF.PI; + float result = MathF.Sin(f) / f; + return MathF.Abs(result) < Constants.Epsilon ? 0F : result; + } - return 1F; + return 1F; + } + + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte Clamp(byte value, byte min, byte max) + { + // Order is important here as someone might set min to higher than max. + if (value > max) + { + return max; } - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte Clamp(byte value, byte min, byte max) + if (value < min) { - // Order is important here as someone might set min to higher than max. - if (value > max) - { - return max; - } + return min; + } - if (value < min) - { - return min; - } + return value; + } - return value; + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Clamp(uint value, uint min, uint max) + { + if (value > max) + { + return max; } - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Clamp(uint value, uint min, uint max) + if (value < min) { - if (value > max) - { - return max; - } + return min; + } - if (value < min) - { - return min; - } + return value; + } - return value; + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Clamp(int value, int min, int max) + { + if (value > max) + { + return max; } - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Clamp(int value, int min, int max) + if (value < min) { - if (value > max) - { - return max; - } + return min; + } - if (value < min) - { - return min; - } + return value; + } - return value; + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Clamp(float value, float min, float max) + { + if (value > max) + { + return max; } - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Clamp(float value, float min, float max) + if (value < min) { - if (value > max) - { - return max; - } + return min; + } - if (value < min) - { - return min; - } + return value; + } - return value; + /// + /// Returns the value clamped to the inclusive range of min and max. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double Clamp(double value, double min, double max) + { + if (value > max) + { + return max; } - /// - /// Returns the value clamped to the inclusive range of min and max. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Clamp(double value, double min, double max) + if (value < min) { - if (value > max) - { - return max; - } + return min; + } - if (value < min) - { - return min; - } + return value; + } - return value; - } + /// + /// Returns the value clamped to the inclusive range of min and max. + /// 5x Faster than + /// on platforms < NET 5. + /// + /// The value to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + /// The clamped . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max) + => Vector4.Min(Vector4.Max(value, min), max); - /// - /// Returns the value clamped to the inclusive range of min and max. - /// 5x Faster than - /// on platforms < NET 5. - /// - /// The value to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - /// The clamped . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Clamp(Vector4 value, Vector4 min, Vector4 max) - => Vector4.Min(Vector4.Max(value, min), max); - - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, byte min, byte max) + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span span, byte min, byte max) + { + Span remainder = span[ClampReduce(span, min, max)..]; + + if (remainder.Length > 0) { - Span remainder = span[ClampReduce(span, min, max)..]; + ref byte remainderStart = ref MemoryMarshal.GetReference(remainder); + ref byte remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - if (remainder.Length > 0) + while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) { - ref byte remainderStart = ref MemoryMarshal.GetReference(remainder); - ref byte remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); + remainderStart = Clamp(remainderStart, min, max); - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } + remainderStart = ref Unsafe.Add(ref remainderStart, 1); } } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span span, uint min, uint max) + { + Span remainder = span[ClampReduce(span, min, max)..]; - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, uint min, uint max) + if (remainder.Length > 0) { - Span remainder = span[ClampReduce(span, min, max)..]; + ref uint remainderStart = ref MemoryMarshal.GetReference(remainder); + ref uint remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - if (remainder.Length > 0) + while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) { - ref uint remainderStart = ref MemoryMarshal.GetReference(remainder); - ref uint remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); + remainderStart = Clamp(remainderStart, min, max); - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } + remainderStart = ref Unsafe.Add(ref remainderStart, 1); } } + } - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, int min, int max) + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span span, int min, int max) + { + Span remainder = span[ClampReduce(span, min, max)..]; + + if (remainder.Length > 0) { - Span remainder = span[ClampReduce(span, min, max)..]; + ref int remainderStart = ref MemoryMarshal.GetReference(remainder); + ref int remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - if (remainder.Length > 0) + while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) { - ref int remainderStart = ref MemoryMarshal.GetReference(remainder); - ref int remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); + remainderStart = Clamp(remainderStart, min, max); - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); - - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } + remainderStart = ref Unsafe.Add(ref remainderStart, 1); } } + } - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, float min, float max) + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span span, float min, float max) + { + Span remainder = span[ClampReduce(span, min, max)..]; + + if (remainder.Length > 0) { - Span remainder = span[ClampReduce(span, min, max)..]; + ref float remainderStart = ref MemoryMarshal.GetReference(remainder); + ref float remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - if (remainder.Length > 0) + while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) { - ref float remainderStart = ref MemoryMarshal.GetReference(remainder); - ref float remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); + remainderStart = Clamp(remainderStart, min, max); - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } + remainderStart = ref Unsafe.Add(ref remainderStart, 1); } } + } + + /// + /// Clamps the span values to the inclusive range of min and max. + /// + /// The span containing the values to clamp. + /// The minimum inclusive value. + /// The maximum inclusive value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clamp(Span span, double min, double max) + { + Span remainder = span[ClampReduce(span, min, max)..]; - /// - /// Clamps the span values to the inclusive range of min and max. - /// - /// The span containing the values to clamp. - /// The minimum inclusive value. - /// The maximum inclusive value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clamp(Span span, double min, double max) + if (remainder.Length > 0) { - Span remainder = span[ClampReduce(span, min, max)..]; + ref double remainderStart = ref MemoryMarshal.GetReference(remainder); + ref double remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - if (remainder.Length > 0) + while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) { - ref double remainderStart = ref MemoryMarshal.GetReference(remainder); - ref double remainderEnd = ref Unsafe.Add(ref remainderStart, remainder.Length); - - while (Unsafe.IsAddressLessThan(ref remainderStart, ref remainderEnd)) - { - remainderStart = Clamp(remainderStart, min, max); + remainderStart = Clamp(remainderStart, min, max); - remainderStart = ref Unsafe.Add(ref remainderStart, 1); - } + remainderStart = ref Unsafe.Add(ref remainderStart, 1); } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ClampReduce(Span span, T min, T max) - where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ClampReduce(Span span, T min, T max) + where T : unmanaged + { + if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) { - if (Vector.IsHardwareAccelerated && span.Length >= Vector.Count) - { - int remainder = ModuloP2(span.Length, Vector.Count); - int adjustedCount = span.Length - remainder; + int remainder = ModuloP2(span.Length, Vector.Count); + int adjustedCount = span.Length - remainder; - if (adjustedCount > 0) - { - ClampImpl(span[..adjustedCount], min, max); - } - - return adjustedCount; + if (adjustedCount > 0) + { + ClampImpl(span[..adjustedCount], min, max); } - return 0; + return adjustedCount; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ClampImpl(Span span, T min, T max) - where T : unmanaged - { - ref T sRef = ref MemoryMarshal.GetReference(span); - var vmin = new Vector(min); - var vmax = new Vector(max); + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ClampImpl(Span span, T min, T max) + where T : unmanaged + { + ref T sRef = ref MemoryMarshal.GetReference(span); + var vmin = new Vector(min); + var vmax = new Vector(max); - int n = span.Length / Vector.Count; - int m = Modulo4(n); - int u = n - m; + int n = span.Length / Vector.Count; + int m = Modulo4(n); + int u = n - m; - ref Vector vs0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - ref Vector vs1 = ref Unsafe.Add(ref vs0, 1); - ref Vector vs2 = ref Unsafe.Add(ref vs0, 2); - ref Vector vs3 = ref Unsafe.Add(ref vs0, 3); - ref Vector vsEnd = ref Unsafe.Add(ref vs0, u); + ref Vector vs0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); + ref Vector vs1 = ref Unsafe.Add(ref vs0, 1); + ref Vector vs2 = ref Unsafe.Add(ref vs0, 2); + ref Vector vs3 = ref Unsafe.Add(ref vs0, 3); + ref Vector vsEnd = ref Unsafe.Add(ref vs0, u); + + while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) + { + vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); + vs1 = Vector.Min(Vector.Max(vmin, vs1), vmax); + vs2 = Vector.Min(Vector.Max(vmin, vs2), vmax); + vs3 = Vector.Min(Vector.Max(vmin, vs3), vmax); + + vs0 = ref Unsafe.Add(ref vs0, 4); + vs1 = ref Unsafe.Add(ref vs1, 4); + vs2 = ref Unsafe.Add(ref vs2, 4); + vs3 = ref Unsafe.Add(ref vs3, 4); + } + + if (m > 0) + { + vs0 = ref vsEnd; + vsEnd = ref Unsafe.Add(ref vsEnd, m); while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) { vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - vs1 = Vector.Min(Vector.Max(vmin, vs1), vmax); - vs2 = Vector.Min(Vector.Max(vmin, vs2), vmax); - vs3 = Vector.Min(Vector.Max(vmin, vs3), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 4); - vs1 = ref Unsafe.Add(ref vs1, 4); - vs2 = ref Unsafe.Add(ref vs2, 4); - vs3 = ref Unsafe.Add(ref vs3, 4); - } - if (m > 0) - { - vs0 = ref vsEnd; - vsEnd = ref Unsafe.Add(ref vsEnd, m); - - while (Unsafe.IsAddressLessThan(ref vs0, ref vsEnd)) - { - vs0 = Vector.Min(Vector.Max(vmin, vs0), vmax); - - vs0 = ref Unsafe.Add(ref vs0, 1); - } + vs0 = ref Unsafe.Add(ref vs0, 1); } } + } - /// - /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// The to premultiply - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Premultiply(ref Vector4 source) - { - float w = source.W; - source *= w; - source.W = w; - } + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Premultiply(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } - /// - /// Reverses the result of premultiplying a vector via . - /// - /// The to premultiply - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnPremultiply(ref Vector4 source) - { - float w = source.W; - source /= w; - source.W = w; - } + /// + /// Reverses the result of premultiplying a vector via . + /// + /// The to premultiply + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnPremultiply(ref Vector4 source) + { + float w = source.W; + source /= w; + source.W = w; + } - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Premultiply(Span vectors) + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Premultiply(Span vectors) + { + if (Avx2.IsSupported && vectors.Length >= 2) { - if (Avx2.IsSupported && vectors.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 source = vectorsBase; - Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl); - vectorsBase = Avx.Blend(Avx.Multiply(source, multiply), source, BlendAlphaControl); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - - if (Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - Premultiply(ref MemoryMarshal.GetReference(vectors[^1..])); - } + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector256 source = vectorsBase; + Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl); + vectorsBase = Avx.Blend(Avx.Multiply(source, multiply), source, BlendAlphaControl); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } - else + + if (Modulo2(vectors.Length) != 0) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Premultiply(ref MemoryMarshal.GetReference(vectors[^1..])); + } + } + else + { + ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) - { - Premultiply(ref vectorsStart); + while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) + { + Premultiply(ref vectorsStart); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); - } + vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); } } + } - /// - /// Bulk variant of - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UnPremultiply(Span vectors) + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnPremultiply(Span vectors) + { + if (Avx2.IsSupported && vectors.Length >= 2) { - if (Avx2.IsSupported && vectors.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); - while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) - { - Vector256 source = vectorsBase; - Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl); - vectorsBase = Avx.Blend(Avx.Divide(source, multiply), source, BlendAlphaControl); - vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); - } - - if (Modulo2(vectors.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - UnPremultiply(ref MemoryMarshal.GetReference(vectors[^1..])); - } - } - else + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) - { - UnPremultiply(ref vectorsStart); + Vector256 source = vectorsBase; + Vector256 multiply = Avx.Shuffle(source, source, ShuffleAlphaControl); + vectorsBase = Avx.Blend(Avx.Divide(source, multiply), source, BlendAlphaControl); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); - } + if (Modulo2(vectors.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + UnPremultiply(ref MemoryMarshal.GetReference(vectors[^1..])); } } - - /// - /// Calculates the cube pow of all the XYZ channels of the input vectors. - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CubePowOnXYZ(Span vectors) + else { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); - ref Vector4 endRef = ref Unsafe.Add(ref baseRef, vectors.Length); + ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - while (Unsafe.IsAddressLessThan(ref baseRef, ref endRef)) + while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) { - Vector4 v = baseRef; - float a = v.W; + UnPremultiply(ref vectorsStart); - // Fast path for the default gamma exposure, which is 3. In this case we can skip - // calling Math.Pow 3 times (one per component), as the method is an internal call and - // introduces quite a bit of overhead. Instead, we can just manually multiply the whole - // pixel in Vector4 format 3 times, and then restore the alpha channel before copying it - // back to the target index in the temporary span. The whole iteration will get completely - // inlined and traslated into vectorized instructions, with much better performance. - v = v * v * v; - v.W = a; - - baseRef = v; - baseRef = ref Unsafe.Add(ref baseRef, 1); + vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); } } + } + + /// + /// Calculates the cube pow of all the XYZ channels of the input vectors. + /// + /// The span of vectors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void CubePowOnXYZ(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + ref Vector4 endRef = ref Unsafe.Add(ref baseRef, vectors.Length); - /// - /// Calculates the cube root of all the XYZ channels of the input vectors. - /// - /// The span of vectors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void CubeRootOnXYZ(Span vectors) + while (Unsafe.IsAddressLessThan(ref baseRef, ref endRef)) { - if (Sse41.IsSupported) - { - ref Vector128 vectors128Ref = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); - ref Vector128 vectors128End = ref Unsafe.Add(ref vectors128Ref, vectors.Length); + Vector4 v = baseRef; + float a = v.W; + + // Fast path for the default gamma exposure, which is 3. In this case we can skip + // calling Math.Pow 3 times (one per component), as the method is an internal call and + // introduces quite a bit of overhead. Instead, we can just manually multiply the whole + // pixel in Vector4 format 3 times, and then restore the alpha channel before copying it + // back to the target index in the temporary span. The whole iteration will get completely + // inlined and traslated into vectorized instructions, with much better performance. + v = v * v * v; + v.W = a; + + baseRef = v; + baseRef = ref Unsafe.Add(ref baseRef, 1); + } + } + + /// + /// Calculates the cube root of all the XYZ channels of the input vectors. + /// + /// The span of vectors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void CubeRootOnXYZ(Span vectors) + { + if (Sse41.IsSupported) + { + ref Vector128 vectors128Ref = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector128 vectors128End = ref Unsafe.Add(ref vectors128Ref, vectors.Length); - var v128_341 = Vector128.Create(341); - Vector128 v128_negativeZero = Vector128.Create(-0.0f).AsInt32(); - Vector128 v128_one = Vector128.Create(1.0f).AsInt32(); + var v128_341 = Vector128.Create(341); + Vector128 v128_negativeZero = Vector128.Create(-0.0f).AsInt32(); + Vector128 v128_one = Vector128.Create(1.0f).AsInt32(); - var v128_13rd = Vector128.Create(1 / 3f); - var v128_23rds = Vector128.Create(2 / 3f); + var v128_13rd = Vector128.Create(1 / 3f); + var v128_23rds = Vector128.Create(2 / 3f); - while (Unsafe.IsAddressLessThan(ref vectors128Ref, ref vectors128End)) + while (Unsafe.IsAddressLessThan(ref vectors128Ref, ref vectors128End)) + { + Vector128 vecx = vectors128Ref; + Vector128 veax = vecx.AsInt32(); + + // If we can use SSE41 instructions, we can vectorize the entire cube root calculation, and also execute it + // directly on 32 bit floating point values. What follows is a vectorized implementation of this method: + // https://www.musicdsp.org/en/latest/Other/206-fast-cube-root-square-root-and-reciprocal-for-x86-sse-cpus.html. + // Furthermore, after the initial setup in vectorized form, we're doing two Newton approximations here + // using a different succession (the same used below), which should be less unstable due to not having cube pow. + veax = Sse2.AndNot(v128_negativeZero, veax); + veax = Sse2.Subtract(veax, v128_one); + veax = Sse2.ShiftRightArithmetic(veax, 10); + veax = Sse41.MultiplyLow(veax, v128_341); + veax = Sse2.Add(veax, v128_one); + veax = Sse2.AndNot(v128_negativeZero, veax); + veax = Sse2.Or(veax, Sse2.And(vecx.AsInt32(), v128_negativeZero)); + + Vector128 y4 = veax.AsSingle(); + + if (Fma.IsSupported) { - Vector128 vecx = vectors128Ref; - Vector128 veax = vecx.AsInt32(); - - // If we can use SSE41 instructions, we can vectorize the entire cube root calculation, and also execute it - // directly on 32 bit floating point values. What follows is a vectorized implementation of this method: - // https://www.musicdsp.org/en/latest/Other/206-fast-cube-root-square-root-and-reciprocal-for-x86-sse-cpus.html. - // Furthermore, after the initial setup in vectorized form, we're doing two Newton approximations here - // using a different succession (the same used below), which should be less unstable due to not having cube pow. - veax = Sse2.AndNot(v128_negativeZero, veax); - veax = Sse2.Subtract(veax, v128_one); - veax = Sse2.ShiftRightArithmetic(veax, 10); - veax = Sse41.MultiplyLow(veax, v128_341); - veax = Sse2.Add(veax, v128_one); - veax = Sse2.AndNot(v128_negativeZero, veax); - veax = Sse2.Or(veax, Sse2.And(vecx.AsInt32(), v128_negativeZero)); - - Vector128 y4 = veax.AsSingle(); - - if (Fma.IsSupported) - { - y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - } - else - { - y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); - } - - y4 = Sse41.Insert(y4, vecx, 0xF0); - - vectors128Ref = y4; - vectors128Ref = ref Unsafe.Add(ref vectors128Ref, 1); + y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); + y4 = Fma.MultiplyAdd(v128_23rds, y4, Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); } - } - else - { - ref Vector4 vectorsRef = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsRef, vectors.Length); - - // Fallback with scalar preprocessing and vectorized approximation steps - while (Unsafe.IsAddressLessThan(ref vectorsRef, ref vectorsEnd)) + else { - Vector4 v = vectorsRef; - - double - x64 = v.X, - y64 = v.Y, - z64 = v.Z; - float a = v.W; - - ulong - xl = *(ulong*)&x64, - yl = *(ulong*)&y64, - zl = *(ulong*)&z64; - - // Here we use a trick to compute the starting value x0 for the cube root. This is because doing - // pow(x, 1 / gamma) is the same as the gamma-th root of x, and since gamme is 3 in this case, - // this means what we actually want is to find the cube root of our clamped values. - // For more info on the constant below, see: - // https://community.intel.com/t5/Intel-C-Compiler/Fast-approximate-of-transcendental-operations/td-p/1044543. - // Here we perform the same trick on all RGB channels separately to help the CPU execute them in paralle, and - // store the alpha channel to preserve it. Then we set these values to the fields of a temporary 128-bit - // register, and use it to accelerate two steps of the Newton approximation using SIMD. - xl = 0x2a9f8a7be393b600 + (xl / 3); - yl = 0x2a9f8a7be393b600 + (yl / 3); - zl = 0x2a9f8a7be393b600 + (zl / 3); - - Vector4 y4; - y4.X = (float)*(double*)&xl; - y4.Y = (float)*(double*)&yl; - y4.Z = (float)*(double*)&zl; - y4.W = 0; - - y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); - y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); - y4.W = a; - - vectorsRef = y4; - vectorsRef = ref Unsafe.Add(ref vectorsRef, 1); + y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); + y4 = Sse.Add(Sse.Multiply(v128_23rds, y4), Sse.Multiply(v128_13rd, Sse.Divide(vecx, Sse.Multiply(y4, y4)))); } + + y4 = Sse41.Insert(y4, vecx, 0xF0); + + vectors128Ref = y4; + vectors128Ref = ref Unsafe.Add(ref vectors128Ref, 1); } } - - /// - /// Performs a linear interpolation between two values based on the given weighting. - /// - /// The first value. - /// The second value. - /// Values between 0 and 1 that indicates the weight of . - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Lerp( - in Vector256 value1, - in Vector256 value2, - in Vector256 amount) + else { - Vector256 diff = Avx.Subtract(value2, value1); - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(diff, amount, value1); - } - else + ref Vector4 vectorsRef = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsRef, vectors.Length); + + // Fallback with scalar preprocessing and vectorized approximation steps + while (Unsafe.IsAddressLessThan(ref vectorsRef, ref vectorsEnd)) { - return Avx.Add(Avx.Multiply(diff, amount), value1); + Vector4 v = vectorsRef; + + double + x64 = v.X, + y64 = v.Y, + z64 = v.Z; + float a = v.W; + + ulong + xl = *(ulong*)&x64, + yl = *(ulong*)&y64, + zl = *(ulong*)&z64; + + // Here we use a trick to compute the starting value x0 for the cube root. This is because doing + // pow(x, 1 / gamma) is the same as the gamma-th root of x, and since gamme is 3 in this case, + // this means what we actually want is to find the cube root of our clamped values. + // For more info on the constant below, see: + // https://community.intel.com/t5/Intel-C-Compiler/Fast-approximate-of-transcendental-operations/td-p/1044543. + // Here we perform the same trick on all RGB channels separately to help the CPU execute them in paralle, and + // store the alpha channel to preserve it. Then we set these values to the fields of a temporary 128-bit + // register, and use it to accelerate two steps of the Newton approximation using SIMD. + xl = 0x2a9f8a7be393b600 + (xl / 3); + yl = 0x2a9f8a7be393b600 + (yl / 3); + zl = 0x2a9f8a7be393b600 + (zl / 3); + + Vector4 y4; + y4.X = (float)*(double*)&xl; + y4.Y = (float)*(double*)&yl; + y4.Z = (float)*(double*)&zl; + y4.W = 0; + + y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); + y4 = (2 / 3f * y4) + (1 / 3f * (v / (y4 * y4))); + y4.W = a; + + vectorsRef = y4; + vectorsRef = ref Unsafe.Add(ref vectorsRef, 1); } } + } - /// - /// Performs a linear interpolation between two values based on the given weighting. - /// - /// The first value. - /// The second value. - /// A value between 0 and 1 that indicates the weight of . - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float Lerp(float value1, float value2, float amount) - => ((value2 - value1) * amount) + value1; - - /// - /// Accumulates 8-bit integers into by - /// widening them to 32-bit integers and performing four additions. - /// - /// - /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) - /// is widened and added onto as such: - /// - /// accumulator += i32(1, 2, 3, 4); - /// accumulator += i32(5, 6, 7, 8); - /// accumulator += i32(9, 10, 11, 12); - /// accumulator += i32(13, 14, 15, 16); - /// - /// - /// The accumulator destination. - /// The values to accumulate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Accumulate(ref Vector accumulator, Vector values) + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// Values between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Lerp( + in Vector256 value1, + in Vector256 value2, + in Vector256 amount) + { + Vector256 diff = Avx.Subtract(value2, value1); + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(diff, amount, value1); + } + else { - Vector.Widen(values, out Vector shortLow, out Vector shortHigh); + return Avx.Add(Avx.Multiply(diff, amount), value1); + } + } - Vector.Widen(shortLow, out Vector intLow, out Vector intHigh); - accumulator += intLow; - accumulator += intHigh; + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// A value between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Lerp(float value1, float value2, float amount) + => ((value2 - value1) * amount) + value1; - Vector.Widen(shortHigh, out intLow, out intHigh); - accumulator += intLow; - accumulator += intHigh; - } + /// + /// Accumulates 8-bit integers into by + /// widening them to 32-bit integers and performing four additions. + /// + /// + /// byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + /// is widened and added onto as such: + /// + /// accumulator += i32(1, 2, 3, 4); + /// accumulator += i32(5, 6, 7, 8); + /// accumulator += i32(9, 10, 11, 12); + /// accumulator += i32(13, 14, 15, 16); + /// + /// + /// The accumulator destination. + /// The values to accumulate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Accumulate(ref Vector accumulator, Vector values) + { + Vector.Widen(values, out Vector shortLow, out Vector shortHigh); - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReduceSum(Vector128 accumulator) - { - // Add odd to even. - Vector128 vsum = Sse2.Add(accumulator, Sse2.Shuffle(accumulator, 0b_11_11_01_01)); + Vector.Widen(shortLow, out Vector intLow, out Vector intHigh); + accumulator += intLow; + accumulator += intHigh; - // Add high to low. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); + Vector.Widen(shortHigh, out intLow, out intHigh); + accumulator += intLow; + accumulator += intHigh; + } - return Sse2.ConvertToInt32(vsum); - } + /// + /// Reduces elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of all elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReduceSum(Vector128 accumulator) + { + // Add odd to even. + Vector128 vsum = Sse2.Add(accumulator, Sse2.Shuffle(accumulator, 0b_11_11_01_01)); - /// - /// Reduces elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of all elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReduceSum(Vector256 accumulator) - { - // Add upper lane to lower lane. - Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); + // Add high to low. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); - // Add odd to even. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); + return Sse2.ConvertToInt32(vsum); + } - // Add high to low. - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); + /// + /// Reduces elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of all elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReduceSum(Vector256 accumulator) + { + // Add upper lane to lower lane. + Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); - return Sse2.ConvertToInt32(vsum); - } + // Add odd to even. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); - /// - /// Reduces even elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of even elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EvenReduceSum(Vector128 accumulator) - { - // Add high to low. - Vector128 vsum = Sse2.Add(accumulator, Sse2.Shuffle(accumulator, 0b_11_10_11_10)); + // Add high to low. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); - return Sse2.ConvertToInt32(vsum); - } + return Sse2.ConvertToInt32(vsum); + } - /// - /// Reduces even elements of the vector into one sum. - /// - /// The accumulator to reduce. - /// The sum of even elements. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EvenReduceSum(Vector256 accumulator) - { - Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane - vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low + /// + /// Reduces even elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of even elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int EvenReduceSum(Vector128 accumulator) + { + // Add high to low. + Vector128 vsum = Sse2.Add(accumulator, Sse2.Shuffle(accumulator, 0b_11_10_11_10)); - // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882 - return Sse2.ConvertToInt32(vsum); - } + return Sse2.ConvertToInt32(vsum); + } - /// - /// Fast division with ceiling for numbers. - /// - /// Divident value. - /// Divisor value. - /// Ceiled division result. - public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; - - /// - /// Tells whether input value is outside of the given range. - /// - /// Value. - /// Minimum value, inclusive. - /// Maximum value, inclusive. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsOutOfRange(int value, int min, int max) - => (uint)(value - min) > (uint)(max - min); + /// + /// Reduces even elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of even elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int EvenReduceSum(Vector256 accumulator) + { + Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low + + // Vector128.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882 + return Sse2.ConvertToInt32(vsum); } + + /// + /// Fast division with ceiling for numbers. + /// + /// Divident value. + /// Divisor value. + /// Ceiled division result. + public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; + + /// + /// Tells whether input value is outside of the given range. + /// + /// Value. + /// Minimum value, inclusive. + /// Maximum value, inclusive. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsOutOfRange(int value, int min, int max) + => (uint)(value - min) > (uint)(max - min); } diff --git a/src/ImageSharp/Common/Helpers/RuntimeUtility.cs b/src/ImageSharp/Common/Helpers/RuntimeUtility.cs index 9c8348c0dc..cb06d8d2fc 100644 --- a/src/ImageSharp/Common/Helpers/RuntimeUtility.cs +++ b/src/ImageSharp/Common/Helpers/RuntimeUtility.cs @@ -1,46 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Common.Helpers +namespace SixLabors.ImageSharp.Common.Helpers; + +/// +/// A helper class that with utility methods for dealing with references, and other low-level details. +/// +internal static class RuntimeUtility { + // Tuple swap uses 2 more IL bytes +#pragma warning disable IDE0180 // Use tuple to swap values /// - /// A helper class that with utility methods for dealing with references, and other low-level details. + /// Swaps the two references. /// - internal static class RuntimeUtility + /// The type to swap. + /// The first item. + /// The second item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Swap(ref T a, ref T b) { - // Tuple swap uses 2 more IL bytes -#pragma warning disable IDE0180 // Use tuple to swap values - /// - /// Swaps the two references. - /// - /// The type to swap. - /// The first item. - /// The second item. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Swap(ref T a, ref T b) - { - T tmp = a; - a = b; - b = tmp; - } + T tmp = a; + a = b; + b = tmp; + } - /// - /// Swaps the two references. - /// - /// The type to swap. - /// The first item. - /// The second item. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Swap(ref Span a, ref Span b) - { - // Tuple swap uses 2 more IL bytes - Span tmp = a; - a = b; - b = tmp; - } -#pragma warning restore IDE0180 // Use tuple to swap values + /// + /// Swaps the two references. + /// + /// The type to swap. + /// The first item. + /// The second item. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Swap(ref Span a, ref Span b) + { + // Tuple swap uses 2 more IL bytes + Span tmp = a; + a = b; + b = tmp; } +#pragma warning restore IDE0180 // Use tuple to swap values } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 9cd7db10d4..45d6e6d13d 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,222 +9,221 @@ // The JIT can detect and optimize rotation idioms ROTL (Rotate Left) // and ROTR (Rotate Right) emitting efficient CPU instructions: // https://github.com/dotnet/coreclr/pull/1830 -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Defines the contract for methods that allow the shuffling of pixel components. +/// Used for shuffling on platforms that do not support Hardware Intrinsics. +/// +internal interface IComponentShuffle { /// - /// Defines the contract for methods that allow the shuffling of pixel components. - /// Used for shuffling on platforms that do not support Hardware Intrinsics. + /// Gets the shuffle control. /// - internal interface IComponentShuffle - { - /// - /// Gets the shuffle control. - /// - byte Control { get; } - - /// - /// Shuffle 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// - /// Implementation can assume that source.Length is less or equal than dest.Length. - /// Loops should iterate using source.Length. - /// - void RunFallbackShuffle(ReadOnlySpan source, Span dest); - } + byte Control { get; } + + /// + /// Shuffle 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// + /// Implementation can assume that source.Length is less or equal than dest.Length. + /// Loops should iterate using source.Length. + /// + void RunFallbackShuffle(ReadOnlySpan source, Span dest); +} - /// - internal interface IShuffle4 : IComponentShuffle +/// +internal interface IShuffle4 : IComponentShuffle +{ +} + +internal readonly struct DefaultShuffle4 : IShuffle4 +{ + private readonly byte p3; + private readonly byte p2; + private readonly byte p1; + private readonly byte p0; + + public DefaultShuffle4(byte p3, byte p2, byte p1, byte p0) { + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + + this.p3 = p3; + this.p2 = p2; + this.p1 = p1; + this.p0 = p0; + this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); } - internal readonly struct DefaultShuffle4 : IShuffle4 + public byte Control { get; } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - private readonly byte p3; - private readonly byte p2; - private readonly byte p1; - private readonly byte p0; + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); + + int p3 = this.p3; + int p2 = this.p2; + int p1 = this.p1; + int p0 = this.p0; - public DefaultShuffle4(byte p3, byte p2, byte p1, byte p0) + for (int i = 0; i < source.Length; i += 4) { - DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); - - this.p3 = p3; - this.p2 = p2; - this.p1 = p1; - this.p0 = p0; - this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); + Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); + Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); + Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); } + } +} - public byte Control { get; } - +internal readonly struct WXYZShuffle4 : IShuffle4 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - int p3 = this.p3; - int p2 = this.p2; - int p1 = this.p1; - int p0 = this.p0; - - for (int i = 0; i < source.Length; i += 4) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); - } - } + get => SimdUtils.Shuffle.MmShuffle(2, 1, 0, 3); } - internal readonly struct WXYZShuffle4 : IShuffle4 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(2, 1, 0, 3); + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // ROTL(8, packed) = [Z Y X W] + Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); } + } +} +internal readonly struct WZYXShuffle4 : IShuffle4 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); - } - } + get => SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3); } - internal readonly struct WZYXShuffle4 : IShuffle4 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(0, 1, 2, 3); + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // REVERSE(packedArgb) = [X Y Z W] + Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); } + } +} +internal readonly struct YZWXShuffle4 : IShuffle4 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); - } - } + get => SimdUtils.Shuffle.MmShuffle(0, 3, 2, 1); } - internal readonly struct YZWXShuffle4 : IShuffle4 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(0, 3, 2, 1); + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // ROTR(8, packedArgb) = [Y Z W X] + Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8); } + } +} +internal readonly struct ZYXWShuffle4 : IShuffle4 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = BitOperations.RotateRight(packed, 8); - } - } + get => SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2); } - internal readonly struct ZYXWShuffle4 : IShuffle4 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(3, 0, 1, 2); + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // tmp1 = [W 0 Y 0] + // tmp2 = [0 Z 0 X] + // tmp3=ROTL(16, tmp2) = [0 X 0 Z] + // tmp1 + tmp3 = [W X Y Z] + uint tmp1 = packed & 0xFF00FF00; + uint tmp2 = packed & 0x00FF00FF; + uint tmp3 = BitOperations.RotateLeft(tmp2, 16); + + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } + } +} +internal readonly struct XWZYShuffle4 : IShuffle4 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [W 0 Y 0] - // tmp2 = [0 Z 0 X] - // tmp3=ROTL(16, tmp2) = [0 X 0 Z] - // tmp1 + tmp3 = [W X Y Z] - uint tmp1 = packed & 0xFF00FF00; - uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } - } + get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0); } - internal readonly struct XWZYShuffle4 : IShuffle4 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control - { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0); - } + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + for (int i = 0; i < n; i++) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [0 Z 0 X] - // tmp2 = [W 0 Y 0] - // tmp3=ROTL(16, tmp2) = [Y 0 W 0] - // tmp1 + tmp3 = [Y Z W X] - uint tmp1 = packed & 0x00FF00FF; - uint tmp2 = packed & 0xFF00FF00; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; - } + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // tmp1 = [0 Z 0 X] + // tmp2 = [W 0 Y 0] + // tmp3=ROTL(16, tmp2) = [Y 0 W 0] + // tmp1 + tmp3 = [Y Z W X] + uint tmp1 = packed & 0x00FF00FF; + uint tmp2 = packed & 0xFF00FF00; + uint tmp3 = BitOperations.RotateLeft(tmp2, 16); + + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs index cb85a550db..76cffd82bf 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IPad3Shuffle4.cs @@ -1,103 +1,101 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +internal interface IPad3Shuffle4 : IComponentShuffle +{ +} + +internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4 { - /// - internal interface IPad3Shuffle4 : IComponentShuffle + private readonly byte p3; + private readonly byte p2; + private readonly byte p1; + private readonly byte p0; + + public DefaultPad3Shuffle4(byte p3, byte p2, byte p1, byte p0) { + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + + this.p3 = p3; + this.p2 = p2; + this.p1 = p1; + this.p0 = p0; + this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); } - internal readonly struct DefaultPad3Shuffle4 : IPad3Shuffle4 + public byte Control { get; } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - private readonly byte p3; - private readonly byte p2; - private readonly byte p1; - private readonly byte p0; + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); - public DefaultPad3Shuffle4(byte p3, byte p2, byte p1, byte p0) - { - DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); - - this.p3 = p3; - this.p2 = p2; - this.p1 = p1; - this.p0 = p0; - this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); - } + int p3 = this.p3; + int p2 = this.p2; + int p1 = this.p1; + int p0 = this.p0; - public byte Control { get; } + Span temp = stackalloc byte[4]; + ref byte t = ref MemoryMarshal.GetReference(temp); + ref uint tu = ref Unsafe.As(ref t); - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + for (int i = 0, j = 0; i < source.Length; i += 3, j += 4) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - int p3 = this.p3; - int p2 = this.p2; - int p1 = this.p1; - int p0 = this.p0; - - Span temp = stackalloc byte[4]; - ref byte t = ref MemoryMarshal.GetReference(temp); - ref uint tu = ref Unsafe.As(ref t); - - for (int i = 0, j = 0; i < source.Length; i += 3, j += 4) - { - ref var s = ref Unsafe.Add(ref sBase, i); - tu = Unsafe.As(ref s) | 0xFF000000; - - Unsafe.Add(ref dBase, j) = Unsafe.Add(ref t, p0); - Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1); - Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2); - Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3); - } + ref var s = ref Unsafe.Add(ref sBase, i); + tu = Unsafe.As(ref s) | 0xFF000000; + + Unsafe.Add(ref dBase, j) = Unsafe.Add(ref t, p0); + Unsafe.Add(ref dBase, j + 1) = Unsafe.Add(ref t, p1); + Unsafe.Add(ref dBase, j + 2) = Unsafe.Add(ref t, p2); + Unsafe.Add(ref dBase, j + 3) = Unsafe.Add(ref t, p3); } } +} - internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 +internal readonly struct XYZWPad3Shuffle4 : IPad3Shuffle4 +{ + public byte Control + { + [MethodImpl(InliningOptions.ShortMethod)] + get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); + + ref byte sEnd = ref Unsafe.Add(ref sBase, source.Length); + ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); + + while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0); + Unsafe.As(ref dBase) = Unsafe.As(ref sBase) | 0xFF000000; + + sBase = ref Unsafe.Add(ref sBase, 3); + dBase = ref Unsafe.Add(ref dBase, 4); } - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - ref byte sEnd = ref Unsafe.Add(ref sBase, source.Length); - ref byte sLoopEnd = ref Unsafe.Subtract(ref sEnd, 4); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - Unsafe.As(ref dBase) = Unsafe.As(ref sBase) | 0xFF000000; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0); - Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1); - Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2); - Unsafe.Add(ref dBase, 3) = byte.MaxValue; - - sBase = ref Unsafe.Add(ref sBase, 3); - dBase = ref Unsafe.Add(ref dBase, 4); - } + Unsafe.Add(ref dBase, 0) = Unsafe.Add(ref sBase, 0); + Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1); + Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2); + Unsafe.Add(ref dBase, 3) = byte.MaxValue; + + sBase = ref Unsafe.Add(ref sBase, 3); + dBase = ref Unsafe.Add(ref dBase, 4); } } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs index d5c8e17ece..9bee9d15ec 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle3.cs @@ -1,53 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +internal interface IShuffle3 : IComponentShuffle { - /// - internal interface IShuffle3 : IComponentShuffle +} + +internal readonly struct DefaultShuffle3 : IShuffle3 +{ + private readonly byte p2; + private readonly byte p1; + private readonly byte p0; + + public DefaultShuffle3(byte p2, byte p1, byte p0) { + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 2, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 2, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 2, nameof(p0)); + + this.p2 = p2; + this.p1 = p1; + this.p0 = p0; + this.Control = SimdUtils.Shuffle.MmShuffle(3, p2, p1, p0); } - internal readonly struct DefaultShuffle3 : IShuffle3 - { - private readonly byte p2; - private readonly byte p1; - private readonly byte p0; + public byte Control { get; } - public DefaultShuffle3(byte p2, byte p1, byte p0) - { - DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 2, nameof(p2)); - DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 2, nameof(p1)); - DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 2, nameof(p0)); - - this.p2 = p2; - this.p1 = p1; - this.p0 = p0; - this.Control = SimdUtils.Shuffle.MmShuffle(3, p2, p1, p0); - } + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + { + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); - public byte Control { get; } + int p2 = this.p2; + int p1 = this.p1; + int p0 = this.p0; - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + for (int i = 0; i < source.Length; i += 3) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - int p2 = this.p2; - int p1 = this.p1; - int p0 = this.p0; - - for (int i = 0; i < source.Length; i += 3) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - } + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); + Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); + Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); } } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs index d50aab005c..90b77b568e 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IShuffle4Slice3.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +internal interface IShuffle4Slice3 : IComponentShuffle +{ +} + +internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3 { - /// - internal interface IShuffle4Slice3 : IComponentShuffle + private readonly byte p2; + private readonly byte p1; + private readonly byte p0; + + public DefaultShuffle4Slice3(byte p3, byte p2, byte p1, byte p0) { + DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); + DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); + DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); + DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); + + this.p2 = p2; + this.p1 = p1; + this.p0 = p0; + this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); } - internal readonly struct DefaultShuffle4Slice3 : IShuffle4Slice3 + public byte Control { get; } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - private readonly byte p2; - private readonly byte p1; - private readonly byte p0; + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); - public DefaultShuffle4Slice3(byte p3, byte p2, byte p1, byte p0) + int p2 = this.p2; + int p1 = this.p1; + int p0 = this.p0; + + for (int i = 0, j = 0; i < dest.Length; i += 3, j += 4) { - DebugGuard.MustBeBetweenOrEqualTo(p3, 0, 3, nameof(p3)); - DebugGuard.MustBeBetweenOrEqualTo(p2, 0, 3, nameof(p2)); - DebugGuard.MustBeBetweenOrEqualTo(p1, 0, 3, nameof(p1)); - DebugGuard.MustBeBetweenOrEqualTo(p0, 0, 3, nameof(p0)); - - this.p2 = p2; - this.p1 = p1; - this.p0 = p0; - this.Control = SimdUtils.Shuffle.MmShuffle(p3, p2, p1, p0); + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + j); + Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j); + Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + j); } + } +} - public byte Control { get; } - +internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 +{ + public byte Control + { [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - int p2 = this.p2; - int p1 = this.p1; - int p0 = this.p0; - - for (int i = 0, j = 0; i < dest.Length; i += 3, j += 4) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + j); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + j); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + j); - } - } + get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0); } - internal readonly struct XYZWShuffle4Slice3 : IShuffle4Slice3 + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) { - public byte Control + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Byte3 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + int n = source.Length / 4; + int m = Numerics.Modulo4(n); + int u = n - m; + + ref uint sLoopEnd = ref Unsafe.Add(ref sBase, u); + ref uint sEnd = ref Unsafe.Add(ref sBase, n); + + while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(3, 2, 1, 0); + Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); + Unsafe.Add(ref dBase, 1) = Unsafe.As(ref Unsafe.Add(ref sBase, 1)); + Unsafe.Add(ref dBase, 2) = Unsafe.As(ref Unsafe.Add(ref sBase, 2)); + Unsafe.Add(ref dBase, 3) = Unsafe.As(ref Unsafe.Add(ref sBase, 3)); + + sBase = ref Unsafe.Add(ref sBase, 4); + dBase = ref Unsafe.Add(ref dBase, 4); } - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Byte3 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - - int n = source.Length / 4; - int m = Numerics.Modulo4(n); - int u = n - m; - - ref uint sLoopEnd = ref Unsafe.Add(ref sBase, u); - ref uint sEnd = ref Unsafe.Add(ref sBase, n); - - while (Unsafe.IsAddressLessThan(ref sBase, ref sLoopEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); - Unsafe.Add(ref dBase, 1) = Unsafe.As(ref Unsafe.Add(ref sBase, 1)); - Unsafe.Add(ref dBase, 2) = Unsafe.As(ref Unsafe.Add(ref sBase, 2)); - Unsafe.Add(ref dBase, 3) = Unsafe.As(ref Unsafe.Add(ref sBase, 3)); - - sBase = ref Unsafe.Add(ref sBase, 4); - dBase = ref Unsafe.Add(ref dBase, 4); - } - - while (Unsafe.IsAddressLessThan(ref sBase, ref sEnd)) - { - Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); - - sBase = ref Unsafe.Add(ref sBase, 1); - dBase = ref Unsafe.Add(ref dBase, 1); - } + Unsafe.Add(ref dBase, 0) = Unsafe.As(ref Unsafe.Add(ref sBase, 0)); + + sBase = ref Unsafe.Add(ref sBase, 1); + dBase = ref Unsafe.Add(ref dBase, 1); } } +} - [StructLayout(LayoutKind.Explicit, Size = 3)] - internal readonly struct Byte3 - { - } +[StructLayout(LayoutKind.Explicit, Size = 3)] +internal readonly struct Byte3 +{ } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index 3a143cda29..2014a2a35b 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -1,184 +1,182 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +internal static partial class SimdUtils { - internal static partial class SimdUtils + /// + /// Implementation methods based on newer API-s (Vector.Widen, Vector.Narrow, Vector.ConvertTo*). + /// Only accelerated only on RyuJIT having dotnet/coreclr#10662 merged (.NET Core 2.1+ .NET 4.7.2+) + /// See: + /// https://github.com/dotnet/coreclr/pull/10662 + /// API Proposal: + /// https://github.com/dotnet/corefx/issues/15957 + /// + public static class ExtendedIntrinsics { + public static bool IsAvailable { get; } = Vector.IsHardwareAccelerated; + /// - /// Implementation methods based on newer API-s (Vector.Widen, Vector.Narrow, Vector.ConvertTo*). - /// Only accelerated only on RyuJIT having dotnet/coreclr#10662 merged (.NET Core 2.1+ .NET 4.7.2+) - /// See: - /// https://github.com/dotnet/coreclr/pull/10662 - /// API Proposal: - /// https://github.com/dotnet/corefx/issues/15957 + /// Widen and convert a vector of values into 2 vectors of -s. /// - public static class ExtendedIntrinsics + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ConvertToSingle( + Vector source, + out Vector dest1, + out Vector dest2) { - public static bool IsAvailable { get; } = Vector.IsHardwareAccelerated; - - /// - /// Widen and convert a vector of values into 2 vectors of -s. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ConvertToSingle( - Vector source, - out Vector dest1, - out Vector dest2) - { - Vector.Widen(source, out Vector i1, out Vector i2); - dest1 = Vector.ConvertToSingle(i1); - dest2 = Vector.ConvertToSingle(i2); - } - - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloatReduce( - ref ReadOnlySpan source, - ref Span dest) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - - if (!IsAvailable) - { - return; - } - - int remainder = Numerics.ModuloP2(source.Length, Vector.Count); - int adjustedCount = source.Length - remainder; + Vector.Widen(source, out Vector i1, out Vector i2); + dest1 = Vector.ConvertToSingle(i1); + dest2 = Vector.ConvertToSingle(i2); + } - if (adjustedCount > 0) - { - ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + if (!IsAvailable) + { + return; } - /// - /// 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!"); + int remainder = Numerics.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; - if (!IsAvailable) - { - return; - } + if (adjustedCount > 0) + { + ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); - int remainder = Numerics.ModuloP2(source.Length, Vector.Count); - int adjustedCount = source.Length - remainder; + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; + } + } - if (adjustedCount > 0) - { - NormalizedFloatToByteSaturate(source[..adjustedCount], dest[..adjustedCount]); + /// + /// 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!"); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + if (!IsAvailable) + { + return; } - /// - /// Implementation , which is faster on new RyuJIT runtime. - /// - internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) - { - VerifySpanInput(source, dest, Vector.Count); + int remainder = Numerics.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; - int n = dest.Length / Vector.Count; + if (adjustedCount > 0) + { + NormalizedFloatToByteSaturate(source[..adjustedCount], dest[..adjustedCount]); - ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; + } + } - for (int i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sourceBase, i); + /// + /// Implementation , which is faster on new RyuJIT runtime. + /// + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + VerifySpanInput(source, dest, Vector.Count); - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); + int n = dest.Length / Vector.Count; - Vector f0 = ConvertToSingle(w0); - Vector f1 = ConvertToSingle(w1); - Vector f2 = ConvertToSingle(w2); - Vector f3 = ConvertToSingle(w3); + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - ref Vector d = ref Unsafe.Add(ref destBase, i * 4); - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sourceBase, i); + + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); + + Vector f0 = ConvertToSingle(w0); + Vector f1 = ConvertToSingle(w1); + Vector f2 = ConvertToSingle(w2); + Vector f3 = ConvertToSingle(w3); + + ref Vector d = ref Unsafe.Add(ref destBase, i * 4); + d = f0; + Unsafe.Add(ref d, 1) = f1; + Unsafe.Add(ref d, 2) = f2; + Unsafe.Add(ref d, 3) = f3; } + } - /// - /// Implementation of , which is faster on new .NET runtime. - /// - internal static void NormalizedFloatToByteSaturate( - ReadOnlySpan source, - Span dest) - { - VerifySpanInput(source, dest, Vector.Count); + /// + /// Implementation of , which is faster on new .NET runtime. + /// + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, Vector.Count); - int n = dest.Length / Vector.Count; + int n = dest.Length / Vector.Count; - ref Vector sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - for (int i = 0; i < n; i++) - { - ref Vector s = ref Unsafe.Add(ref sourceBase, i * 4); + for (int i = 0; i < n; i++) + { + ref Vector s = ref Unsafe.Add(ref sourceBase, i * 4); - Vector f0 = s; - Vector f1 = Unsafe.Add(ref s, 1); - Vector f2 = Unsafe.Add(ref s, 2); - Vector f3 = Unsafe.Add(ref s, 3); + Vector f0 = s; + Vector f1 = Unsafe.Add(ref s, 1); + Vector f2 = Unsafe.Add(ref s, 2); + Vector f3 = Unsafe.Add(ref s, 3); - Vector w0 = ConvertToUInt32(f0); - Vector w1 = ConvertToUInt32(f1); - Vector w2 = ConvertToUInt32(f2); - Vector w3 = ConvertToUInt32(f3); + Vector w0 = ConvertToUInt32(f0); + Vector w1 = ConvertToUInt32(f1); + Vector w2 = ConvertToUInt32(f2); + Vector w3 = ConvertToUInt32(f3); - var u0 = Vector.Narrow(w0, w1); - var u1 = Vector.Narrow(w2, w3); + var u0 = Vector.Narrow(w0, w1); + var u1 = Vector.Narrow(w2, w3); - Unsafe.Add(ref destBase, i) = Vector.Narrow(u0, u1); - } + Unsafe.Add(ref destBase, i) = Vector.Narrow(u0, u1); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector ConvertToUInt32(Vector vf) - { - var maxBytes = new Vector(255f); - vf *= maxBytes; - vf += new Vector(0.5f); - vf = Vector.Min(Vector.Max(vf, Vector.Zero), maxBytes); - var vi = Vector.ConvertToInt32(vf); - return Vector.AsVectorUInt32(vi); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToUInt32(Vector vf) + { + var maxBytes = new Vector(255f); + vf *= maxBytes; + vf += new Vector(0.5f); + vf = Vector.Min(Vector.Max(vf, Vector.Zero), maxBytes); + var vi = Vector.ConvertToInt32(vf); + return Vector.AsVectorUInt32(vi); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector ConvertToSingle(Vector u) - { - var vi = Vector.AsVectorInt32(u); - var v = Vector.ConvertToSingle(vi); - v *= new Vector(1f / 255f); - return v; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToSingle(Vector u) + { + var vi = Vector.AsVectorInt32(u); + var v = Vector.ConvertToSingle(vi); + v *= new Vector(1f / 255f); + return v; } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index 92c5bf5fb4..84760f2815 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -1,146 +1,144 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +internal static partial class SimdUtils { - internal static partial class SimdUtils + /// + /// Fallback implementation based on (128bit). + /// For , efficient software fallback implementations are present, + /// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P + /// + public static class FallbackIntrinsics128 { /// - /// Fallback implementation based on (128bit). - /// For , efficient software fallback implementations are present, - /// and we hope that even mono's JIT is able to emit SIMD instructions for that type :P + /// as many elements as possible, slicing them down (keeping the remainder). /// - public static class FallbackIntrinsics128 + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) { - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloatReduce( - ref ReadOnlySpan source, - ref Span dest) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - int remainder = Numerics.Modulo4(source.Length); - int adjustedCount = source.Length - remainder; + int remainder = Numerics.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; - if (adjustedCount > 0) - { - ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); + if (adjustedCount > 0) + { + ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } + } + + /// + /// 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!"); + + int remainder = Numerics.Modulo4(source.Length); + int adjustedCount = source.Length - remainder; - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void NormalizedFloatToByteSaturateReduce( - ref ReadOnlySpan source, - ref Span dest) + if (adjustedCount > 0) { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + NormalizedFloatToByteSaturate( + source[..adjustedCount], + dest[..adjustedCount]); - int remainder = Numerics.Modulo4(source.Length); - int adjustedCount = source.Length - remainder; + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; + } + } - if (adjustedCount > 0) - { - NormalizedFloatToByteSaturate( - source[..adjustedCount], - dest[..adjustedCount]); + /// + /// Implementation of using . + /// + [MethodImpl(InliningOptions.ColdPath)] + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + VerifySpanInput(source, dest, 4); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + int count = dest.Length / 4; + if (count == 0) + { + return; } - /// - /// Implementation of using . - /// - [MethodImpl(InliningOptions.ColdPath)] - internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) + ref ByteVector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + const float scale = 1f / 255f; + Vector4 d = default; + + for (int i = 0; i < count; i++) { - VerifySpanInput(source, dest, 4); - - int count = dest.Length / 4; - if (count == 0) - { - return; - } - - ref ByteVector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Vector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - - const float scale = 1f / 255f; - Vector4 d = default; - - for (int i = 0; i < count; i++) - { - ref ByteVector4 s = ref Unsafe.Add(ref sBase, i); - d.X = s.X; - d.Y = s.Y; - d.Z = s.Z; - d.W = s.W; - d *= scale; - Unsafe.Add(ref dBase, i) = d; - } + ref ByteVector4 s = ref Unsafe.Add(ref sBase, i); + d.X = s.X; + d.Y = s.Y; + d.Z = s.Z; + d.W = s.W; + d *= scale; + Unsafe.Add(ref dBase, i) = d; } + } - /// - /// Implementation of using . - /// - [MethodImpl(InliningOptions.ColdPath)] - internal static void NormalizedFloatToByteSaturate( - ReadOnlySpan source, - Span dest) + /// + /// Implementation of using . + /// + [MethodImpl(InliningOptions.ColdPath)] + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, 4); + + int count = source.Length / 4; + if (count == 0) { - VerifySpanInput(source, dest, 4); - - int count = source.Length / 4; - if (count == 0) - { - return; - } - - ref Vector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref ByteVector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - - var half = new Vector4(0.5f); - var maxBytes = new Vector4(255f); - - for (int i = 0; i < count; i++) - { - Vector4 s = Unsafe.Add(ref sBase, i); - s *= maxBytes; - s += half; - s = Numerics.Clamp(s, Vector4.Zero, maxBytes); - - ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); - d.X = (byte)s.X; - d.Y = (byte)s.Y; - d.Z = (byte)s.Z; - d.W = (byte)s.W; - } + return; } - [StructLayout(LayoutKind.Sequential)] - private struct ByteVector4 + ref Vector4 sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ByteVector4 dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + + var half = new Vector4(0.5f); + var maxBytes = new Vector4(255f); + + for (int i = 0; i < count; i++) { - public byte X; - public byte Y; - public byte Z; - public byte W; + Vector4 s = Unsafe.Add(ref sBase, i); + s *= maxBytes; + s += half; + s = Numerics.Clamp(s, Vector4.Zero, maxBytes); + + ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); + d.X = (byte)s.X; + d.Y = (byte)s.Y; + d.Z = (byte)s.Z; + d.W = (byte)s.W; } } + + [StructLayout(LayoutKind.Sequential)] + private struct ByteVector4 + { + public byte X; + public byte Y; + public byte Z; + public byte W; + } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index 177f4d72bc..4bc0040c67 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -1,1005 +1,1003 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +internal static partial class SimdUtils { - internal static partial class SimdUtils + public static class HwIntrinsics { - public static class HwIntrinsics - { - public 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 }; + public 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 }; - public static ReadOnlySpan PermuteMaskEvenOdd8x32 => new byte[] { 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; + public static ReadOnlySpan PermuteMaskEvenOdd8x32 => new byte[] { 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; - public static ReadOnlySpan PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 }; + public static ReadOnlySpan PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 }; - private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 }; - internal static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF }; + internal static ReadOnlySpan ExtractRgb => new byte[] { 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF }; - private static ReadOnlySpan ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 }; + private static ReadOnlySpan ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 }; - private static ReadOnlySpan ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 }; + private static ReadOnlySpan ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 }; - private static ReadOnlySpan ShuffleMaskShiftAlpha => - new byte[] - { - 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15, - 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15 - }; + private static ReadOnlySpan ShuffleMaskShiftAlpha => + new byte[] + { + 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15, + 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 3, 7, 11, 15 + }; - public static ReadOnlySpan PermuteMaskShiftAlpha8x32 => - new byte[] - { - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, - 5, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 - }; - - /// - /// Shuffle single-precision (32-bit) floating-point elements in - /// using the control and store the results in . - /// - /// The source span of floats. - /// The destination span of floats. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span dest, - byte control) + public static ReadOnlySpan PermuteMaskShiftAlpha8x32 => + new byte[] { - if (Avx.IsSupported || Sse.IsSupported) - { - int remainder = Avx.IsSupported - ? Numerics.ModuloP2(source.Length, Vector256.Count) - : Numerics.ModuloP2(source.Length, Vector128.Count); + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, + 5, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 + }; + + /// + /// Shuffle single-precision (32-bit) floating-point elements in + /// using the control and store the results in . + /// + /// The source span of floats. + /// The destination span of floats. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4Reduce( + ref ReadOnlySpan source, + ref Span dest, + byte control) + { + if (Avx.IsSupported || Sse.IsSupported) + { + int remainder = Avx.IsSupported + ? Numerics.ModuloP2(source.Length, Vector256.Count) + : Numerics.ModuloP2(source.Length, Vector128.Count); - int adjustedCount = source.Length - remainder; + int adjustedCount = source.Length - remainder; - if (adjustedCount > 0) - { - Shuffle4( - source[..adjustedCount], - dest[..adjustedCount], - control); + if (adjustedCount > 0) + { + Shuffle4( + source[..adjustedCount], + dest[..adjustedCount], + control); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } } + } - /// - /// Shuffle 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span dest, - byte control) + /// + /// Shuffle 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4Reduce( + ref ReadOnlySpan source, + ref Span dest, + byte control) + { + if (Avx2.IsSupported || Ssse3.IsSupported) { - if (Avx2.IsSupported || Ssse3.IsSupported) - { - int remainder = Avx2.IsSupported - ? Numerics.ModuloP2(source.Length, Vector256.Count) - : Numerics.ModuloP2(source.Length, Vector128.Count); + int remainder = Avx2.IsSupported + ? Numerics.ModuloP2(source.Length, Vector256.Count) + : Numerics.ModuloP2(source.Length, Vector128.Count); - int adjustedCount = source.Length - remainder; + int adjustedCount = source.Length - remainder; - if (adjustedCount > 0) - { - Shuffle4( - source[..adjustedCount], - dest[..adjustedCount], - control); + if (adjustedCount > 0) + { + Shuffle4( + source[..adjustedCount], + dest[..adjustedCount], + control); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } } + } - /// - /// Shuffles 8-bit integer triplets within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle3Reduce( - ref ReadOnlySpan source, - ref Span dest, - byte control) + /// + /// Shuffles 8-bit integer triplets within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle3Reduce( + ref ReadOnlySpan source, + ref Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - int remainder = source.Length % (Vector128.Count * 3); + int remainder = source.Length % (Vector128.Count * 3); - int adjustedCount = source.Length - remainder; + int adjustedCount = source.Length - remainder; - if (adjustedCount > 0) - { - Shuffle3( - source[..adjustedCount], - dest[..adjustedCount], - control); + if (adjustedCount > 0) + { + Shuffle3( + source[..adjustedCount], + dest[..adjustedCount], + control); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } } + } - /// - /// Pads then shuffles 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Pad3Shuffle4Reduce( - ref ReadOnlySpan source, - ref Span dest, - byte control) + /// + /// Pads then shuffles 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Pad3Shuffle4Reduce( + ref ReadOnlySpan source, + ref Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - int remainder = source.Length % (Vector128.Count * 3); + int remainder = source.Length % (Vector128.Count * 3); - int sourceCount = source.Length - remainder; - int destCount = sourceCount * 4 / 3; + int sourceCount = source.Length - remainder; + int destCount = sourceCount * 4 / 3; - if (sourceCount > 0) - { - Pad3Shuffle4( - source[..sourceCount], - dest[..destCount], - control); + if (sourceCount > 0) + { + Pad3Shuffle4( + source[..sourceCount], + dest[..destCount], + control); - source = source[sourceCount..]; - dest = dest[destCount..]; - } + source = source[sourceCount..]; + dest = dest[destCount..]; } } + } - /// - /// Shuffles then slices 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Slice3Reduce( - ref ReadOnlySpan source, - ref Span dest, - byte control) + /// + /// Shuffles then slices 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4Slice3Reduce( + ref ReadOnlySpan source, + ref Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - int remainder = source.Length % (Vector128.Count * 4); + int remainder = source.Length % (Vector128.Count * 4); - int sourceCount = source.Length - remainder; - int destCount = sourceCount * 3 / 4; + int sourceCount = source.Length - remainder; + int destCount = sourceCount * 3 / 4; - if (sourceCount > 0) - { - Shuffle4Slice3( - source[..sourceCount], - dest[..destCount], - control); + if (sourceCount > 0) + { + Shuffle4Slice3( + source[..sourceCount], + dest[..destCount], + control); - source = source[sourceCount..]; - dest = dest[destCount..]; - } + source = source[sourceCount..]; + dest = dest[destCount..]; } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4( - ReadOnlySpan source, - Span dest, - byte control) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Shuffle4( + ReadOnlySpan source, + Span dest, + byte control) + { + if (Avx.IsSupported) { - if (Avx.IsSupported) - { - ref Vector256 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector256 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = dest.Length / Vector256.Count; - int m = Numerics.Modulo4(n); - int u = n - m; + int n = dest.Length / Vector256.Count; + int m = Numerics.Modulo4(n); + int u = n - m; - for (int i = 0; i < u; i += 4) - { - ref Vector256 vd0 = ref Unsafe.Add(ref destBase, i); - ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); + for (int i = 0; i < u; i += 4) + { + ref Vector256 vd0 = ref Unsafe.Add(ref destBase, i); + ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); - vd0 = Avx.Permute(vs0, control); - Unsafe.Add(ref vd0, 1) = Avx.Permute(Unsafe.Add(ref vs0, 1), control); - Unsafe.Add(ref vd0, 2) = Avx.Permute(Unsafe.Add(ref vs0, 2), control); - Unsafe.Add(ref vd0, 3) = Avx.Permute(Unsafe.Add(ref vs0, 3), control); - } + vd0 = Avx.Permute(vs0, control); + Unsafe.Add(ref vd0, 1) = Avx.Permute(Unsafe.Add(ref vs0, 1), control); + Unsafe.Add(ref vd0, 2) = Avx.Permute(Unsafe.Add(ref vs0, 2), control); + Unsafe.Add(ref vd0, 3) = Avx.Permute(Unsafe.Add(ref vs0, 3), control); + } - if (m > 0) + if (m > 0) + { + for (int i = u; i < n; i++) { - for (int i = u; i < n; i++) - { - Unsafe.Add(ref destBase, i) = Avx.Permute(Unsafe.Add(ref sourceBase, i), control); - } + Unsafe.Add(ref destBase, i) = Avx.Permute(Unsafe.Add(ref sourceBase, i), control); } } - else - { - // Sse - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + } + else + { + // Sse + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = dest.Length / Vector128.Count; - int m = Numerics.Modulo4(n); - int u = n - m; + int n = dest.Length / Vector128.Count; + int m = Numerics.Modulo4(n); + int u = n - m; - for (int i = 0; i < u; i += 4) - { - ref Vector128 vd0 = ref Unsafe.Add(ref destBase, i); - ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); + for (int i = 0; i < u; i += 4) + { + ref Vector128 vd0 = ref Unsafe.Add(ref destBase, i); + ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); - vd0 = Sse.Shuffle(vs0, vs0, control); + vd0 = Sse.Shuffle(vs0, vs0, control); - Vector128 vs1 = Unsafe.Add(ref vs0, 1); - Unsafe.Add(ref vd0, 1) = Sse.Shuffle(vs1, vs1, control); + Vector128 vs1 = Unsafe.Add(ref vs0, 1); + Unsafe.Add(ref vd0, 1) = Sse.Shuffle(vs1, vs1, control); - Vector128 vs2 = Unsafe.Add(ref vs0, 2); - Unsafe.Add(ref vd0, 2) = Sse.Shuffle(vs2, vs2, control); + Vector128 vs2 = Unsafe.Add(ref vs0, 2); + Unsafe.Add(ref vd0, 2) = Sse.Shuffle(vs2, vs2, control); - Vector128 vs3 = Unsafe.Add(ref vs0, 3); - Unsafe.Add(ref vd0, 3) = Sse.Shuffle(vs3, vs3, control); - } + Vector128 vs3 = Unsafe.Add(ref vs0, 3); + Unsafe.Add(ref vd0, 3) = Sse.Shuffle(vs3, vs3, control); + } - if (m > 0) + if (m > 0) + { + for (int i = u; i < n; i++) { - for (int i = u; i < n; i++) - { - Vector128 vs = Unsafe.Add(ref sourceBase, i); - Unsafe.Add(ref destBase, i) = Sse.Shuffle(vs, vs, control); - } + Vector128 vs = Unsafe.Add(ref sourceBase, i); + Unsafe.Add(ref destBase, i) = Sse.Shuffle(vs, vs, control); } } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4( - ReadOnlySpan source, - Span dest, - byte control) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Shuffle4( + ReadOnlySpan source, + Span dest, + byte control) + { + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - // I've chosen to do this for convenience while we determine what - // shuffle controls to add to the library. - // We can add static ROS instances if need be in the future. - Span bytes = stackalloc byte[Vector256.Count]; - Shuffle.MmShuffleSpan(ref bytes, control); - Vector256 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); + // I've chosen to do this for convenience while we determine what + // shuffle controls to add to the library. + // We can add static ROS instances if need be in the future. + Span bytes = stackalloc byte[Vector256.Count]; + Shuffle.MmShuffleSpan(ref bytes, control); + Vector256 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - ref Vector256 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector256 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = dest.Length / Vector256.Count; - int m = Numerics.Modulo4(n); - int u = n - m; + int n = dest.Length / Vector256.Count; + int m = Numerics.Modulo4(n); + int u = n - m; - for (int i = 0; i < u; i += 4) - { - ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector256 vd0 = ref Unsafe.Add(ref destBase, i); + for (int i = 0; i < u; i += 4) + { + ref Vector256 vs0 = ref Unsafe.Add(ref sourceBase, i); + ref Vector256 vd0 = ref Unsafe.Add(ref destBase, i); - vd0 = Avx2.Shuffle(vs0, vshuffle); - Unsafe.Add(ref vd0, 1) = Avx2.Shuffle(Unsafe.Add(ref vs0, 1), vshuffle); - Unsafe.Add(ref vd0, 2) = Avx2.Shuffle(Unsafe.Add(ref vs0, 2), vshuffle); - Unsafe.Add(ref vd0, 3) = Avx2.Shuffle(Unsafe.Add(ref vs0, 3), vshuffle); - } + vd0 = Avx2.Shuffle(vs0, vshuffle); + Unsafe.Add(ref vd0, 1) = Avx2.Shuffle(Unsafe.Add(ref vs0, 1), vshuffle); + Unsafe.Add(ref vd0, 2) = Avx2.Shuffle(Unsafe.Add(ref vs0, 2), vshuffle); + Unsafe.Add(ref vd0, 3) = Avx2.Shuffle(Unsafe.Add(ref vs0, 3), vshuffle); + } - if (m > 0) + if (m > 0) + { + for (int i = u; i < n; i++) { - for (int i = u; i < n; i++) - { - Unsafe.Add(ref destBase, i) = Avx2.Shuffle(Unsafe.Add(ref sourceBase, i), vshuffle); - } + Unsafe.Add(ref destBase, i) = Avx2.Shuffle(Unsafe.Add(ref sourceBase, i), vshuffle); } } - else - { - // Ssse3 - Span bytes = stackalloc byte[Vector128.Count]; - Shuffle.MmShuffleSpan(ref bytes, control); - Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); + } + else + { + // Ssse3 + Span bytes = stackalloc byte[Vector128.Count]; + Shuffle.MmShuffleSpan(ref bytes, control); + Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = dest.Length / Vector128.Count; - int m = Numerics.Modulo4(n); - int u = n - m; + int n = dest.Length / Vector128.Count; + int m = Numerics.Modulo4(n); + int u = n - m; - for (int i = 0; i < u; i += 4) - { - ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); - ref Vector128 vd0 = ref Unsafe.Add(ref destBase, i); + for (int i = 0; i < u; i += 4) + { + ref Vector128 vs0 = ref Unsafe.Add(ref sourceBase, i); + ref Vector128 vd0 = ref Unsafe.Add(ref destBase, i); - vd0 = Ssse3.Shuffle(vs0, vshuffle); - Unsafe.Add(ref vd0, 1) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 1), vshuffle); - Unsafe.Add(ref vd0, 2) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 2), vshuffle); - Unsafe.Add(ref vd0, 3) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 3), vshuffle); - } + vd0 = Ssse3.Shuffle(vs0, vshuffle); + Unsafe.Add(ref vd0, 1) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 1), vshuffle); + Unsafe.Add(ref vd0, 2) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 2), vshuffle); + Unsafe.Add(ref vd0, 3) = Ssse3.Shuffle(Unsafe.Add(ref vs0, 3), vshuffle); + } - if (m > 0) + if (m > 0) + { + for (int i = u; i < n; i++) { - for (int i = u; i < n; i++) - { - Unsafe.Add(ref destBase, i) = Ssse3.Shuffle(Unsafe.Add(ref sourceBase, i), vshuffle); - } + Unsafe.Add(ref destBase, i) = Ssse3.Shuffle(Unsafe.Add(ref sourceBase, i), vshuffle); } } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle3( - ReadOnlySpan source, - Span dest, - byte control) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Shuffle3( + ReadOnlySpan source, + Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - ref byte vmaskBase = ref MemoryMarshal.GetReference(ShuffleMaskPad4Nx16); - Vector128 vmask = Unsafe.As>(ref vmaskBase); - ref byte vmaskoBase = ref MemoryMarshal.GetReference(ShuffleMaskSlice4Nx16); - Vector128 vmasko = Unsafe.As>(ref vmaskoBase); - Vector128 vmaske = Ssse3.AlignRight(vmasko, vmasko, 12); + ref byte vmaskBase = ref MemoryMarshal.GetReference(ShuffleMaskPad4Nx16); + Vector128 vmask = Unsafe.As>(ref vmaskBase); + ref byte vmaskoBase = ref MemoryMarshal.GetReference(ShuffleMaskSlice4Nx16); + Vector128 vmasko = Unsafe.As>(ref vmaskoBase); + Vector128 vmaske = Ssse3.AlignRight(vmasko, vmasko, 12); - Span bytes = stackalloc byte[Vector128.Count]; - Shuffle.MmShuffleSpan(ref bytes, control); - Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); + Span bytes = stackalloc byte[Vector128.Count]; + Shuffle.MmShuffleSpan(ref bytes, control); + Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / Vector128.Count; + int n = source.Length / Vector128.Count; - for (int i = 0; i < n; i += 3) - { - ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); + for (int i = 0; i < n; i += 3) + { + ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); - Vector128 v0 = vs; - Vector128 v1 = Unsafe.Add(ref vs, 1); - Vector128 v2 = Unsafe.Add(ref vs, 2); - Vector128 v3 = Sse2.ShiftRightLogical128BitLane(v2, 4); + Vector128 v0 = vs; + Vector128 v1 = Unsafe.Add(ref vs, 1); + Vector128 v2 = Unsafe.Add(ref vs, 2); + Vector128 v3 = Sse2.ShiftRightLogical128BitLane(v2, 4); - v2 = Ssse3.AlignRight(v2, v1, 8); - v1 = Ssse3.AlignRight(v1, v0, 12); + v2 = Ssse3.AlignRight(v2, v1, 8); + v1 = Ssse3.AlignRight(v1, v0, 12); - v0 = Ssse3.Shuffle(Ssse3.Shuffle(v0, vmask), vshuffle); - v1 = Ssse3.Shuffle(Ssse3.Shuffle(v1, vmask), vshuffle); - v2 = Ssse3.Shuffle(Ssse3.Shuffle(v2, vmask), vshuffle); - v3 = Ssse3.Shuffle(Ssse3.Shuffle(v3, vmask), vshuffle); + v0 = Ssse3.Shuffle(Ssse3.Shuffle(v0, vmask), vshuffle); + v1 = Ssse3.Shuffle(Ssse3.Shuffle(v1, vmask), vshuffle); + v2 = Ssse3.Shuffle(Ssse3.Shuffle(v2, vmask), vshuffle); + v3 = Ssse3.Shuffle(Ssse3.Shuffle(v3, vmask), vshuffle); - v0 = Ssse3.Shuffle(v0, vmaske); - v1 = Ssse3.Shuffle(v1, vmasko); - v2 = Ssse3.Shuffle(v2, vmaske); - v3 = Ssse3.Shuffle(v3, vmasko); + v0 = Ssse3.Shuffle(v0, vmaske); + v1 = Ssse3.Shuffle(v1, vmasko); + v2 = Ssse3.Shuffle(v2, vmaske); + v3 = Ssse3.Shuffle(v3, vmasko); - v0 = Ssse3.AlignRight(v1, v0, 4); - v3 = Ssse3.AlignRight(v3, v2, 12); + v0 = Ssse3.AlignRight(v1, v0, 4); + v3 = Ssse3.AlignRight(v3, v2, 12); - v1 = Sse2.ShiftLeftLogical128BitLane(v1, 4); - v2 = Sse2.ShiftRightLogical128BitLane(v2, 4); + v1 = Sse2.ShiftLeftLogical128BitLane(v1, 4); + v2 = Sse2.ShiftRightLogical128BitLane(v2, 4); - v1 = Ssse3.AlignRight(v2, v1, 8); + v1 = Ssse3.AlignRight(v2, v1, 8); - ref Vector128 vd = ref Unsafe.Add(ref destBase, i); + ref Vector128 vd = ref Unsafe.Add(ref destBase, i); - vd = v0; - Unsafe.Add(ref vd, 1) = v1; - Unsafe.Add(ref vd, 2) = v3; - } + vd = v0; + Unsafe.Add(ref vd, 1) = v1; + Unsafe.Add(ref vd, 2) = v3; } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Pad3Shuffle4( - ReadOnlySpan source, - Span dest, - byte control) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Pad3Shuffle4( + ReadOnlySpan source, + Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - ref byte vmaskBase = ref MemoryMarshal.GetReference(ShuffleMaskPad4Nx16); - Vector128 vmask = Unsafe.As>(ref vmaskBase); - Vector128 vfill = Vector128.Create(0xff000000ff000000ul).AsByte(); + ref byte vmaskBase = ref MemoryMarshal.GetReference(ShuffleMaskPad4Nx16); + Vector128 vmask = Unsafe.As>(ref vmaskBase); + Vector128 vfill = Vector128.Create(0xff000000ff000000ul).AsByte(); - Span bytes = stackalloc byte[Vector128.Count]; - Shuffle.MmShuffleSpan(ref bytes, control); - Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); + Span bytes = stackalloc byte[Vector128.Count]; + Shuffle.MmShuffleSpan(ref bytes, control); + Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / Vector128.Count; + int n = source.Length / Vector128.Count; - for (int i = 0, j = 0; i < n; i += 3, j += 4) - { - ref Vector128 v0 = ref Unsafe.Add(ref sourceBase, i); - Vector128 v1 = Unsafe.Add(ref v0, 1); - Vector128 v2 = Unsafe.Add(ref v0, 2); - Vector128 v3 = Sse2.ShiftRightLogical128BitLane(v2, 4); + for (int i = 0, j = 0; i < n; i += 3, j += 4) + { + ref Vector128 v0 = ref Unsafe.Add(ref sourceBase, i); + Vector128 v1 = Unsafe.Add(ref v0, 1); + Vector128 v2 = Unsafe.Add(ref v0, 2); + Vector128 v3 = Sse2.ShiftRightLogical128BitLane(v2, 4); - v2 = Ssse3.AlignRight(v2, v1, 8); - v1 = Ssse3.AlignRight(v1, v0, 12); + v2 = Ssse3.AlignRight(v2, v1, 8); + v1 = Ssse3.AlignRight(v1, v0, 12); - ref Vector128 vd = ref Unsafe.Add(ref destBase, j); + ref Vector128 vd = ref Unsafe.Add(ref destBase, j); - vd = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v0, vmask), vfill), vshuffle); - Unsafe.Add(ref vd, 1) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v1, vmask), vfill), vshuffle); - Unsafe.Add(ref vd, 2) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v2, vmask), vfill), vshuffle); - Unsafe.Add(ref vd, 3) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v3, vmask), vfill), vshuffle); - } + vd = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v0, vmask), vfill), vshuffle); + Unsafe.Add(ref vd, 1) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v1, vmask), vfill), vshuffle); + Unsafe.Add(ref vd, 2) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v2, vmask), vfill), vshuffle); + Unsafe.Add(ref vd, 3) = Ssse3.Shuffle(Sse2.Or(Ssse3.Shuffle(v3, vmask), vfill), vshuffle); } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Shuffle4Slice3( - ReadOnlySpan source, - Span dest, - byte control) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Shuffle4Slice3( + ReadOnlySpan source, + Span dest, + byte control) + { + if (Ssse3.IsSupported) { - if (Ssse3.IsSupported) - { - ref byte vmaskoBase = ref MemoryMarshal.GetReference(ShuffleMaskSlice4Nx16); - Vector128 vmasko = Unsafe.As>(ref vmaskoBase); - Vector128 vmaske = Ssse3.AlignRight(vmasko, vmasko, 12); + ref byte vmaskoBase = ref MemoryMarshal.GetReference(ShuffleMaskSlice4Nx16); + Vector128 vmasko = Unsafe.As>(ref vmaskoBase); + Vector128 vmaske = Ssse3.AlignRight(vmasko, vmasko, 12); - Span bytes = stackalloc byte[Vector128.Count]; - Shuffle.MmShuffleSpan(ref bytes, control); - Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); + Span bytes = stackalloc byte[Vector128.Count]; + Shuffle.MmShuffleSpan(ref bytes, control); + Vector128 vshuffle = Unsafe.As>(ref MemoryMarshal.GetReference(bytes)); - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / Vector128.Count; + int n = source.Length / Vector128.Count; - for (int i = 0, j = 0; i < n; i += 4, j += 3) - { - ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); + for (int i = 0, j = 0; i < n; i += 4, j += 3) + { + ref Vector128 vs = ref Unsafe.Add(ref sourceBase, i); - Vector128 v0 = vs; - Vector128 v1 = Unsafe.Add(ref vs, 1); - Vector128 v2 = Unsafe.Add(ref vs, 2); - Vector128 v3 = Unsafe.Add(ref vs, 3); + Vector128 v0 = vs; + Vector128 v1 = Unsafe.Add(ref vs, 1); + Vector128 v2 = Unsafe.Add(ref vs, 2); + Vector128 v3 = Unsafe.Add(ref vs, 3); - v0 = Ssse3.Shuffle(Ssse3.Shuffle(v0, vshuffle), vmaske); - v1 = Ssse3.Shuffle(Ssse3.Shuffle(v1, vshuffle), vmasko); - v2 = Ssse3.Shuffle(Ssse3.Shuffle(v2, vshuffle), vmaske); - v3 = Ssse3.Shuffle(Ssse3.Shuffle(v3, vshuffle), vmasko); + v0 = Ssse3.Shuffle(Ssse3.Shuffle(v0, vshuffle), vmaske); + v1 = Ssse3.Shuffle(Ssse3.Shuffle(v1, vshuffle), vmasko); + v2 = Ssse3.Shuffle(Ssse3.Shuffle(v2, vshuffle), vmaske); + v3 = Ssse3.Shuffle(Ssse3.Shuffle(v3, vshuffle), vmasko); - v0 = Ssse3.AlignRight(v1, v0, 4); - v3 = Ssse3.AlignRight(v3, v2, 12); + v0 = Ssse3.AlignRight(v1, v0, 4); + v3 = Ssse3.AlignRight(v3, v2, 12); - v1 = Sse2.ShiftLeftLogical128BitLane(v1, 4); - v2 = Sse2.ShiftRightLogical128BitLane(v2, 4); + v1 = Sse2.ShiftLeftLogical128BitLane(v1, 4); + v2 = Sse2.ShiftRightLogical128BitLane(v2, 4); - v1 = Ssse3.AlignRight(v2, v1, 8); + v1 = Ssse3.AlignRight(v2, v1, 8); - ref Vector128 vd = ref Unsafe.Add(ref destBase, j); + ref Vector128 vd = ref Unsafe.Add(ref destBase, j); - vd = v0; - Unsafe.Add(ref vd, 1) = v1; - Unsafe.Add(ref vd, 2) = v3; - } + vd = v0; + Unsafe.Add(ref vd, 1) = v1; + Unsafe.Add(ref vd, 2) = v3; } } + } - /// - /// Performs a multiplication and an addition of the . - /// - /// ret = (vm0 * vm1) + va - /// The vector to add to the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(InliningOptions.AlwaysInline)] - public static Vector256 MultiplyAdd( - in Vector256 va, - in Vector256 vm0, - in Vector256 vm1) + /// + /// Performs a multiplication and an addition of the . + /// + /// ret = (vm0 * vm1) + va + /// The vector to add to the intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector256 MultiplyAdd( + in Vector256 va, + in Vector256 vm0, + in Vector256 vm1) + { + if (Fma.IsSupported) { - if (Fma.IsSupported) - { - return Fma.MultiplyAdd(vm1, vm0, va); - } - else - { - return Avx.Add(Avx.Multiply(vm0, vm1), va); - } + return Fma.MultiplyAdd(vm1, vm0, va); } + else + { + return Avx.Add(Avx.Multiply(vm0, vm1), va); + } + } + + /// + /// Performs a multiplication and a substraction of the . + /// + /// ret = (vm0 * vm1) - vs + /// The vector to substract from the intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector256 MultiplySubstract( + in Vector256 vs, + in Vector256 vm0, + in Vector256 vm1) + { + if (Fma.IsSupported) + { + return Fma.MultiplySubtract(vm1, vm0, vs); + } + else + { + return Avx.Subtract(Avx.Multiply(vm0, vm1), vs); + } + } - /// - /// Performs a multiplication and a substraction of the . - /// - /// ret = (vm0 * vm1) - vs - /// The vector to substract from the intermediate result. - /// The first vector to multiply. - /// The second vector to multiply. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector256 MultiplySubstract( - in Vector256 vs, - in Vector256 vm0, - in Vector256 vm1) + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ByteToNormalizedFloatReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (Avx2.IsSupported || Sse2.IsSupported) { - if (Fma.IsSupported) + int remainder; + if (Avx2.IsSupported) { - return Fma.MultiplySubtract(vm1, vm0, vs); + remainder = Numerics.ModuloP2(source.Length, Vector256.Count); } else { - return Avx.Subtract(Avx.Multiply(vm0, vm1), vs); + remainder = Numerics.ModuloP2(source.Length, Vector128.Count); } - } - /// - /// as many elements as possible, slicing them down (keeping the remainder). - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloatReduce( - ref ReadOnlySpan source, - ref Span dest) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + int adjustedCount = source.Length - remainder; - if (Avx2.IsSupported || Sse2.IsSupported) + if (adjustedCount > 0) { - int remainder; - if (Avx2.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } - - int adjustedCount = source.Length - remainder; - - if (adjustedCount > 0) - { - ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); + ByteToNormalizedFloat(source[..adjustedCount], dest[..adjustedCount]); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } } + } - /// - /// Implementation , which is faster on new RyuJIT runtime. - /// - /// - /// Implementation is based on MagicScaler code: - /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L80-L182 - /// - internal static unsafe void ByteToNormalizedFloat( - ReadOnlySpan source, - Span dest) + /// + /// Implementation , which is faster on new RyuJIT runtime. + /// + /// + /// Implementation is based on MagicScaler code: + /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L80-L182 + /// + internal static unsafe void ByteToNormalizedFloat( + ReadOnlySpan source, + Span dest) + { + fixed (byte* sourceBase = source) { - fixed (byte* sourceBase = source) + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - VerifySpanInput(source, dest, Vector256.Count); + VerifySpanInput(source, dest, Vector256.Count); - int n = dest.Length / Vector256.Count; + int n = dest.Length / Vector256.Count; - ref Vector256 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector256 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = Vector256.Create(1 / (float)byte.MaxValue); + var scale = Vector256.Create(1 / (float)byte.MaxValue); - for (int i = 0; i < n; i++) - { - int si = Vector256.Count * i; - Vector256 i0 = Avx2.ConvertToVector256Int32(sourceBase + si); - Vector256 i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256.Count); - Vector256 i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256.Count * 2)); - Vector256 i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256.Count * 3)); - - Vector256 f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0)); - Vector256 f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1)); - Vector256 f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2)); - Vector256 f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3)); - - ref Vector256 d = ref Unsafe.Add(ref destBase, i * 4); - - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } - } - else + for (int i = 0; i < n; i++) { - // Sse - VerifySpanInput(source, dest, Vector128.Count); + int si = Vector256.Count * i; + Vector256 i0 = Avx2.ConvertToVector256Int32(sourceBase + si); + Vector256 i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256.Count); + Vector256 i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256.Count * 2)); + Vector256 i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256.Count * 3)); + + Vector256 f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0)); + Vector256 f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1)); + Vector256 f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2)); + Vector256 f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3)); + + ref Vector256 d = ref Unsafe.Add(ref destBase, i * 4); + + d = f0; + Unsafe.Add(ref d, 1) = f1; + Unsafe.Add(ref d, 2) = f2; + Unsafe.Add(ref d, 3) = f3; + } + } + else + { + // Sse + VerifySpanInput(source, dest, Vector128.Count); - int n = dest.Length / Vector128.Count; + int n = dest.Length / Vector128.Count; - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = Vector128.Create(1 / (float)byte.MaxValue); - Vector128 zero = Vector128.Zero; + var scale = Vector128.Create(1 / (float)byte.MaxValue); + Vector128 zero = Vector128.Zero; - for (int i = 0; i < n; i++) + for (int i = 0; i < n; i++) + { + int si = Vector128.Count * i; + + Vector128 i0, i1, i2, i3; + if (Sse41.IsSupported) + { + i0 = Sse41.ConvertToVector128Int32(sourceBase + si); + i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128.Count); + i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128.Count * 2)); + i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128.Count * 3)); + } + else { - int si = Vector128.Count * i; - - Vector128 i0, i1, i2, i3; - if (Sse41.IsSupported) - { - i0 = Sse41.ConvertToVector128Int32(sourceBase + si); - i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128.Count); - i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128.Count * 2)); - i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128.Count * 3)); - } - else - { - Vector128 b = Sse2.LoadVector128(sourceBase + si); - Vector128 s0 = Sse2.UnpackLow(b, zero).AsInt16(); - Vector128 s1 = Sse2.UnpackHigh(b, zero).AsInt16(); - - i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32(); - i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32(); - i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32(); - i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32(); - } - - Vector128 f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0)); - Vector128 f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1)); - Vector128 f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2)); - Vector128 f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3)); - - ref Vector128 d = ref Unsafe.Add(ref destBase, i * 4); - - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; + Vector128 b = Sse2.LoadVector128(sourceBase + si); + Vector128 s0 = Sse2.UnpackLow(b, zero).AsInt16(); + Vector128 s1 = Sse2.UnpackHigh(b, zero).AsInt16(); + + i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32(); + i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32(); + i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32(); + i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32(); } + + Vector128 f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0)); + Vector128 f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1)); + Vector128 f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2)); + Vector128 f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3)); + + ref Vector128 d = ref Unsafe.Add(ref destBase, i * 4); + + d = f0; + Unsafe.Add(ref d, 1) = f1; + Unsafe.Add(ref d, 2) = f2; + Unsafe.Add(ref d, 3) = f3; } } } + } - /// - /// 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!"); + /// + /// 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 || Sse2.IsSupported) + if (Avx2.IsSupported || Sse2.IsSupported) + { + int remainder; + if (Avx2.IsSupported) { - int remainder; - if (Avx2.IsSupported) - { - remainder = Numerics.ModuloP2(source.Length, Vector256.Count); - } - else - { - remainder = Numerics.ModuloP2(source.Length, Vector128.Count); - } + remainder = Numerics.ModuloP2(source.Length, Vector256.Count); + } + else + { + remainder = Numerics.ModuloP2(source.Length, Vector128.Count); + } - int adjustedCount = source.Length - remainder; + int adjustedCount = source.Length - remainder; - if (adjustedCount > 0) - { - NormalizedFloatToByteSaturate( - source[..adjustedCount], - dest[..adjustedCount]); + if (adjustedCount > 0) + { + NormalizedFloatToByteSaturate( + source[..adjustedCount], + dest[..adjustedCount]); - source = source[adjustedCount..]; - dest = dest[adjustedCount..]; - } + source = source[adjustedCount..]; + dest = dest[adjustedCount..]; } } + } - /// - /// Implementation of , which is faster on new .NET runtime. - /// - /// - /// Implementation is based on MagicScaler code: - /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L541-L622 - /// - internal static void NormalizedFloatToByteSaturate( - ReadOnlySpan source, - Span dest) + /// + /// Implementation of , which is faster on new .NET runtime. + /// + /// + /// Implementation is based on MagicScaler code: + /// https://github.com/saucecontrol/PhotoSauce/blob/b5811908041200488aa18fdfd17df5fc457415dc/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L541-L622 + /// + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - VerifySpanInput(source, dest, Vector256.Count); + VerifySpanInput(source, dest, Vector256.Count); - int n = dest.Length / Vector256.Count; + int n = dest.Length / Vector256.Count; - ref Vector256 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector256 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = Vector256.Create((float)byte.MaxValue); - ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); - Vector256 mask = Unsafe.As>(ref maskBase); + var scale = Vector256.Create((float)byte.MaxValue); + 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); + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); - Vector256 f0 = Avx.Multiply(scale, s); - Vector256 f1 = Avx.Multiply(scale, Unsafe.Add(ref s, 1)); - Vector256 f2 = Avx.Multiply(scale, Unsafe.Add(ref s, 2)); - Vector256 f3 = Avx.Multiply(scale, Unsafe.Add(ref s, 3)); + Vector256 f0 = Avx.Multiply(scale, s); + Vector256 f1 = Avx.Multiply(scale, Unsafe.Add(ref s, 1)); + Vector256 f2 = Avx.Multiply(scale, Unsafe.Add(ref s, 2)); + Vector256 f3 = Avx.Multiply(scale, Unsafe.Add(ref s, 3)); - Vector256 w0 = Avx.ConvertToVector256Int32(f0); - Vector256 w1 = Avx.ConvertToVector256Int32(f1); - Vector256 w2 = Avx.ConvertToVector256Int32(f2); - Vector256 w3 = Avx.ConvertToVector256Int32(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(); + 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; - } + Unsafe.Add(ref destBase, i) = b; } - else - { - // Sse - VerifySpanInput(source, dest, Vector128.Count); + } + else + { + // Sse + VerifySpanInput(source, dest, Vector128.Count); - int n = dest.Length / Vector128.Count; + int n = dest.Length / Vector128.Count; - ref Vector128 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector128 destBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + ref Vector128 destBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = Vector128.Create((float)byte.MaxValue); + var scale = Vector128.Create((float)byte.MaxValue); - for (int i = 0; i < n; i++) - { - ref Vector128 s = ref Unsafe.Add(ref sourceBase, i * 4); + for (int i = 0; i < n; i++) + { + ref Vector128 s = ref Unsafe.Add(ref sourceBase, i * 4); - Vector128 f0 = Sse.Multiply(scale, s); - Vector128 f1 = Sse.Multiply(scale, Unsafe.Add(ref s, 1)); - Vector128 f2 = Sse.Multiply(scale, Unsafe.Add(ref s, 2)); - Vector128 f3 = Sse.Multiply(scale, Unsafe.Add(ref s, 3)); + Vector128 f0 = Sse.Multiply(scale, s); + Vector128 f1 = Sse.Multiply(scale, Unsafe.Add(ref s, 1)); + Vector128 f2 = Sse.Multiply(scale, Unsafe.Add(ref s, 2)); + Vector128 f3 = Sse.Multiply(scale, Unsafe.Add(ref s, 3)); - Vector128 w0 = Sse2.ConvertToVector128Int32(f0); - Vector128 w1 = Sse2.ConvertToVector128Int32(f1); - Vector128 w2 = Sse2.ConvertToVector128Int32(f2); - Vector128 w3 = Sse2.ConvertToVector128Int32(f3); + Vector128 w0 = Sse2.ConvertToVector128Int32(f0); + Vector128 w1 = Sse2.ConvertToVector128Int32(f1); + Vector128 w2 = Sse2.ConvertToVector128Int32(f2); + Vector128 w3 = Sse2.ConvertToVector128Int32(f3); - Vector128 u0 = Sse2.PackSignedSaturate(w0, w1); - Vector128 u1 = Sse2.PackSignedSaturate(w2, w3); + Vector128 u0 = Sse2.PackSignedSaturate(w0, w1); + Vector128 u1 = Sse2.PackSignedSaturate(w2, w3); - Unsafe.Add(ref destBase, i) = Sse2.PackUnsignedSaturate(u0, u1); - } + Unsafe.Add(ref destBase, i) = Sse2.PackUnsignedSaturate(u0, u1); } } + } - internal static void PackFromRgbPlanesAvx2Reduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - ref byte dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + internal static void PackFromRgbPlanesAvx2Reduce( + ref ReadOnlySpan redChannel, + ref ReadOnlySpan greenChannel, + ref ReadOnlySpan blueChannel, + ref Span destination) + { + ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); + ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); + ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); + ref byte dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); - int count = redChannel.Length / Vector256.Count; + int count = redChannel.Length / Vector256.Count; - ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32); - Vector256 control1 = Unsafe.As>(ref control1Bytes); + ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32); + Vector256 control1 = Unsafe.As>(ref control1Bytes); - ref byte control2Bytes = ref MemoryMarshal.GetReference(PermuteMaskShiftAlpha8x32); - Vector256 control2 = Unsafe.As>(ref control2Bytes); - var a = Vector256.Create((byte)255); + ref byte control2Bytes = ref MemoryMarshal.GetReference(PermuteMaskShiftAlpha8x32); + Vector256 control2 = Unsafe.As>(ref control2Bytes); + var a = Vector256.Create((byte)255); - Vector256 shuffleAlpha = Unsafe.As>(ref MemoryMarshal.GetReference(ShuffleMaskShiftAlpha)); + Vector256 shuffleAlpha = Unsafe.As>(ref MemoryMarshal.GetReference(ShuffleMaskShiftAlpha)); - for (int i = 0; i < count; i++) - { - Vector256 r0 = Unsafe.Add(ref rBase, i); - Vector256 g0 = Unsafe.Add(ref gBase, i); - Vector256 b0 = Unsafe.Add(ref bBase, i); - - r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); - g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); - b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); + for (int i = 0; i < count; i++) + { + Vector256 r0 = Unsafe.Add(ref rBase, i); + Vector256 g0 = Unsafe.Add(ref gBase, i); + Vector256 b0 = Unsafe.Add(ref bBase, i); - Vector256 rg = Avx2.UnpackLow(r0, g0); - Vector256 b1 = Avx2.UnpackLow(b0, a); + r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); + g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); + b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); - Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rg = Avx2.UnpackLow(r0, g0); + Vector256 b1 = Avx2.UnpackLow(b0, a); - rg = Avx2.UnpackHigh(r0, g0); - b1 = Avx2.UnpackHigh(b0, a); + Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + rg = Avx2.UnpackHigh(r0, g0); + b1 = Avx2.UnpackHigh(b0, a); - rgb1 = Avx2.Shuffle(rgb1, shuffleAlpha); - rgb2 = Avx2.Shuffle(rgb2, shuffleAlpha); - rgb3 = Avx2.Shuffle(rgb3, shuffleAlpha); - rgb4 = Avx2.Shuffle(rgb4, shuffleAlpha); + Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - rgb1 = Avx2.PermuteVar8x32(rgb1.AsUInt32(), control2).AsByte(); - rgb2 = Avx2.PermuteVar8x32(rgb2.AsUInt32(), control2).AsByte(); - rgb3 = Avx2.PermuteVar8x32(rgb3.AsUInt32(), control2).AsByte(); - rgb4 = Avx2.PermuteVar8x32(rgb4.AsUInt32(), control2).AsByte(); + rgb1 = Avx2.Shuffle(rgb1, shuffleAlpha); + rgb2 = Avx2.Shuffle(rgb2, shuffleAlpha); + rgb3 = Avx2.Shuffle(rgb3, shuffleAlpha); + rgb4 = Avx2.Shuffle(rgb4, shuffleAlpha); - ref byte d1 = ref Unsafe.Add(ref dBase, 24 * 4 * i); - ref byte d2 = ref Unsafe.Add(ref d1, 24); - ref byte d3 = ref Unsafe.Add(ref d2, 24); - ref byte d4 = ref Unsafe.Add(ref d3, 24); + rgb1 = Avx2.PermuteVar8x32(rgb1.AsUInt32(), control2).AsByte(); + rgb2 = Avx2.PermuteVar8x32(rgb2.AsUInt32(), control2).AsByte(); + rgb3 = Avx2.PermuteVar8x32(rgb3.AsUInt32(), control2).AsByte(); + rgb4 = Avx2.PermuteVar8x32(rgb4.AsUInt32(), control2).AsByte(); - Unsafe.As>(ref d1) = rgb1; - Unsafe.As>(ref d2) = rgb2; - Unsafe.As>(ref d3) = rgb3; - Unsafe.As>(ref d4) = rgb4; - } + ref byte d1 = ref Unsafe.Add(ref dBase, 24 * 4 * i); + ref byte d2 = ref Unsafe.Add(ref d1, 24); + ref byte d3 = ref Unsafe.Add(ref d2, 24); + ref byte d4 = ref Unsafe.Add(ref d3, 24); - int slice = count * Vector256.Count; - redChannel = redChannel[slice..]; - greenChannel = greenChannel[slice..]; - blueChannel = blueChannel[slice..]; - destination = destination[slice..]; + Unsafe.As>(ref d1) = rgb1; + Unsafe.As>(ref d2) = rgb2; + Unsafe.As>(ref d3) = rgb3; + Unsafe.As>(ref d4) = rgb4; } - internal static void PackFromRgbPlanesAvx2Reduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) - { - ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - ref Vector256 dBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); + int slice = count * Vector256.Count; + redChannel = redChannel[slice..]; + greenChannel = greenChannel[slice..]; + blueChannel = blueChannel[slice..]; + destination = destination[slice..]; + } - int count = redChannel.Length / Vector256.Count; - ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32); - Vector256 control1 = Unsafe.As>(ref control1Bytes); - var a = Vector256.Create((byte)255); + internal static void PackFromRgbPlanesAvx2Reduce( + ref ReadOnlySpan redChannel, + ref ReadOnlySpan greenChannel, + ref ReadOnlySpan blueChannel, + ref Span destination) + { + ref Vector256 rBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); + ref Vector256 gBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); + ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); + ref Vector256 dBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - for (int i = 0; i < count; i++) - { - Vector256 r0 = Unsafe.Add(ref rBase, i); - Vector256 g0 = Unsafe.Add(ref gBase, i); - Vector256 b0 = Unsafe.Add(ref bBase, i); + int count = redChannel.Length / Vector256.Count; + ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32); + Vector256 control1 = Unsafe.As>(ref control1Bytes); + var a = Vector256.Create((byte)255); - r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); - g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); - b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); + for (int i = 0; i < count; i++) + { + Vector256 r0 = Unsafe.Add(ref rBase, i); + Vector256 g0 = Unsafe.Add(ref gBase, i); + Vector256 b0 = Unsafe.Add(ref bBase, i); - Vector256 rg = Avx2.UnpackLow(r0, g0); - Vector256 b1 = Avx2.UnpackLow(b0, a); + r0 = Avx2.PermuteVar8x32(r0.AsUInt32(), control1).AsByte(); + g0 = Avx2.PermuteVar8x32(g0.AsUInt32(), control1).AsByte(); + b0 = Avx2.PermuteVar8x32(b0.AsUInt32(), control1).AsByte(); - Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rg = Avx2.UnpackLow(r0, g0); + Vector256 b1 = Avx2.UnpackLow(b0, a); - rg = Avx2.UnpackHigh(r0, g0); - b1 = Avx2.UnpackHigh(b0, a); + Vector256 rgb1 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rgb2 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + rg = Avx2.UnpackHigh(r0, g0); + b1 = Avx2.UnpackHigh(b0, a); - ref Vector256 d0 = ref Unsafe.Add(ref dBase, i * 4); - d0 = rgb1; - Unsafe.Add(ref d0, 1) = rgb2; - Unsafe.Add(ref d0, 2) = rgb3; - Unsafe.Add(ref d0, 3) = rgb4; - } + Vector256 rgb3 = Avx2.UnpackLow(rg.AsUInt16(), b1.AsUInt16()).AsByte(); + Vector256 rgb4 = Avx2.UnpackHigh(rg.AsUInt16(), b1.AsUInt16()).AsByte(); - int slice = count * Vector256.Count; - redChannel = redChannel[slice..]; - greenChannel = greenChannel[slice..]; - blueChannel = blueChannel[slice..]; - destination = destination[slice..]; + ref Vector256 d0 = ref Unsafe.Add(ref dBase, i * 4); + d0 = rgb1; + Unsafe.Add(ref d0, 1) = rgb2; + Unsafe.Add(ref d0, 2) = rgb3; + Unsafe.Add(ref d0, 3) = rgb4; } - internal static void UnpackToRgbPlanesAvx2Reduce( - ref Span redChannel, - ref Span greenChannel, - ref Span blueChannel, - ref ReadOnlySpan source) - { - ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); - ref Vector256 destGRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); - ref Vector256 destBRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); - - Vector256 extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); - Vector256 extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); - Vector256 rgb, rg, bx; - Vector256 r, g, b; - - const int bytesPerRgbStride = 24; - int count = (int)((uint)source.Length / 8); - for (int i = 0; i < count; i++) - { - rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); + int slice = count * Vector256.Count; + redChannel = redChannel[slice..]; + greenChannel = greenChannel[slice..]; + blueChannel = blueChannel[slice..]; + destination = destination[slice..]; + } - rgb = Avx2.Shuffle(rgb, extractRgbMask); + internal static void UnpackToRgbPlanesAvx2Reduce( + ref Span redChannel, + ref Span greenChannel, + ref Span blueChannel, + ref ReadOnlySpan source) + { + ref Vector256 rgbByteSpan = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destRRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(redChannel)); + ref Vector256 destGRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(greenChannel)); + ref Vector256 destBRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel)); + + Vector256 extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + Vector256 extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + + const int bytesPerRgbStride = 24; + int count = (int)((uint)source.Length / 8); + for (int i = 0; i < count; i++) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte(); - rg = Avx2.UnpackLow(rgb, Vector256.Zero); - bx = Avx2.UnpackHigh(rgb, Vector256.Zero); + rgb = Avx2.Shuffle(rgb, extractRgbMask); - r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256.Zero).AsInt32()); - g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256.Zero).AsInt32()); - b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256.Zero).AsInt32()); + rg = Avx2.UnpackLow(rgb, Vector256.Zero); + bx = Avx2.UnpackHigh(rgb, Vector256.Zero); - Unsafe.Add(ref destRRef, i) = r; - Unsafe.Add(ref destGRef, i) = g; - Unsafe.Add(ref destBRef, i) = b; - } + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, Vector256.Zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, Vector256.Zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, Vector256.Zero).AsInt32()); - int sliceCount = count * 8; - redChannel = redChannel.Slice(sliceCount); - greenChannel = greenChannel.Slice(sliceCount); - blueChannel = blueChannel.Slice(sliceCount); - source = source.Slice(sliceCount); + Unsafe.Add(ref destRRef, i) = r; + Unsafe.Add(ref destGRef, i) = g; + Unsafe.Add(ref destBRef, i) = b; } + + int sliceCount = count * 8; + redChannel = redChannel.Slice(sliceCount); + greenChannel = greenChannel.Slice(sliceCount); + blueChannel = blueChannel.Slice(sliceCount); + source = source.Slice(sliceCount); } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs index 1643234b13..a01a32f5e1 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs @@ -1,239 +1,237 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +internal static partial class SimdUtils { - internal static partial class SimdUtils + [MethodImpl(InliningOptions.ShortMethod)] + internal static void PackFromRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) { - [MethodImpl(InliningOptions.ShortMethod)] - internal static void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(destination.Length > redChannel.Length + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); + + if (Avx2.IsSupported) { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(destination.Length > redChannel.Length + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); - - if (Avx2.IsSupported) - { - HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - else - { - PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - - PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); + HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); } - - [MethodImpl(InliningOptions.ShortMethod)] - internal static void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) + else { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(destination.Length > redChannel.Length, nameof(destination), "'destination' span should not be shorter than the source channels!"); - - if (Avx2.IsSupported) - { - HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - else - { - PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); - } - - PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); + PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); } - [MethodImpl(InliningOptions.ShortMethod)] - internal static void UnpackToRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) + PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal static void PackFromRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(destination.Length > redChannel.Length, nameof(destination), "'destination' span should not be shorter than the source channels!"); + + if (Avx2.IsSupported) + { + HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); + } + else { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + PackFromRgbPlanesScalarBatchedReduce(ref redChannel, ref greenChannel, ref blueChannel, ref destination); + } + + PackFromRgbPlanesRemainder(redChannel, greenChannel, blueChannel, destination); + } - if (Avx2.IsSupported) - { - HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source); - } + [MethodImpl(InliningOptions.ShortMethod)] + internal static void UnpackToRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); - UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source); + if (Avx2.IsSupported) + { + HwIntrinsics.UnpackToRgbPlanesAvx2Reduce(ref redChannel, ref greenChannel, ref blueChannel, ref source); } - private static void PackFromRgbPlanesScalarBatchedReduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) + UnpackToRgbPlanesScalar(redChannel, greenChannel, blueChannel, source); + } + + private static void PackFromRgbPlanesScalarBatchedReduce( + ref ReadOnlySpan redChannel, + ref ReadOnlySpan greenChannel, + ref ReadOnlySpan blueChannel, + ref Span destination) + { + ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); + ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); + ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); + ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); + + int count = redChannel.Length / 4; + for (int i = 0; i < count; i++) { - ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); - ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); - ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); - - int count = redChannel.Length / 4; - for (int i = 0; i < count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - - ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); - ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); - ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } - - int finished = count * 4; - redChannel = redChannel[finished..]; - greenChannel = greenChannel[finished..]; - blueChannel = blueChannel[finished..]; - destination = destination[finished..]; + ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); + ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); + ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); + ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); + + ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); + ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); + ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); + + d0.R = rr.V0; + d0.G = gg.V0; + d0.B = bb.V0; + + d1.R = rr.V1; + d1.G = gg.V1; + d1.B = bb.V1; + + d2.R = rr.V2; + d2.G = gg.V2; + d2.B = bb.V2; + + d3.R = rr.V3; + d3.G = gg.V3; + d3.B = bb.V3; } - private static void PackFromRgbPlanesScalarBatchedReduce( - ref ReadOnlySpan redChannel, - ref ReadOnlySpan greenChannel, - ref ReadOnlySpan blueChannel, - ref Span destination) + int finished = count * 4; + redChannel = redChannel[finished..]; + greenChannel = greenChannel[finished..]; + blueChannel = blueChannel[finished..]; + destination = destination[finished..]; + } + + private static void PackFromRgbPlanesScalarBatchedReduce( + ref ReadOnlySpan redChannel, + ref ReadOnlySpan greenChannel, + ref ReadOnlySpan blueChannel, + ref Span destination) + { + ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); + ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); + ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); + ref Rgba32 rgb = ref MemoryMarshal.GetReference(destination); + + int count = redChannel.Length / 4; + destination.Fill(new Rgba32(0, 0, 0, 255)); + for (int i = 0; i < count; i++) { - ref ByteTuple4 r = ref Unsafe.As(ref MemoryMarshal.GetReference(redChannel)); - ref ByteTuple4 g = ref Unsafe.As(ref MemoryMarshal.GetReference(greenChannel)); - ref ByteTuple4 b = ref Unsafe.As(ref MemoryMarshal.GetReference(blueChannel)); - ref Rgba32 rgb = ref MemoryMarshal.GetReference(destination); - - int count = redChannel.Length / 4; - destination.Fill(new Rgba32(0, 0, 0, 255)); - for (int i = 0; i < count; i++) - { - ref Rgba32 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgba32 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgba32 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgba32 d3 = ref Unsafe.Add(ref d0, 3); - - ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); - ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); - ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } - - int finished = count * 4; - redChannel = redChannel[finished..]; - greenChannel = greenChannel[finished..]; - blueChannel = blueChannel[finished..]; - destination = destination[finished..]; + ref Rgba32 d0 = ref Unsafe.Add(ref rgb, i * 4); + ref Rgba32 d1 = ref Unsafe.Add(ref d0, 1); + ref Rgba32 d2 = ref Unsafe.Add(ref d0, 2); + ref Rgba32 d3 = ref Unsafe.Add(ref d0, 3); + + ref ByteTuple4 rr = ref Unsafe.Add(ref r, i); + ref ByteTuple4 gg = ref Unsafe.Add(ref g, i); + ref ByteTuple4 bb = ref Unsafe.Add(ref b, i); + + d0.R = rr.V0; + d0.G = gg.V0; + d0.B = bb.V0; + + d1.R = rr.V1; + d1.G = gg.V1; + d1.B = bb.V1; + + d2.R = rr.V2; + d2.G = gg.V2; + d2.B = bb.V2; + + d3.R = rr.V3; + d3.G = gg.V3; + d3.B = bb.V3; } - private static void PackFromRgbPlanesRemainder( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) + int finished = count * 4; + redChannel = redChannel[finished..]; + greenChannel = greenChannel[finished..]; + blueChannel = blueChannel[finished..]; + destination = destination[finished..]; + } + + private static void PackFromRgbPlanesRemainder( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + ref byte r = ref MemoryMarshal.GetReference(redChannel); + ref byte g = ref MemoryMarshal.GetReference(greenChannel); + ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < destination.Length; i++) { - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < destination.Length; i++) - { - ref Rgb24 d = ref Unsafe.Add(ref rgb, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - } + ref Rgb24 d = ref Unsafe.Add(ref rgb, i); + d.R = Unsafe.Add(ref r, i); + d.G = Unsafe.Add(ref g, i); + d.B = Unsafe.Add(ref b, i); } + } + + private static void PackFromRgbPlanesRemainder( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + ref byte r = ref MemoryMarshal.GetReference(redChannel); + ref byte g = ref MemoryMarshal.GetReference(greenChannel); + ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref Rgba32 rgba = ref MemoryMarshal.GetReference(destination); - private static void PackFromRgbPlanesRemainder( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) + for (int i = 0; i < destination.Length; i++) { - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgba32 rgba = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < destination.Length; i++) - { - ref Rgba32 d = ref Unsafe.Add(ref rgba, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - d.A = 255; - } + ref Rgba32 d = ref Unsafe.Add(ref rgba, i); + d.R = Unsafe.Add(ref r, i); + d.G = Unsafe.Add(ref g, i); + d.B = Unsafe.Add(ref b, i); + d.A = 255; } + } + + private static void UnpackToRgbPlanesScalar( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); + DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); + + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); + ref Rgb24 rgb = ref MemoryMarshal.GetReference(source); - private static void UnpackToRgbPlanesScalar( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) + for (int i = 0; i < source.Length; i++) { - DebugGuard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); - DebugGuard.IsTrue(source.Length <= redChannel.Length, nameof(source), "'source' span should not be bigger than the destination channels!"); - - ref float r = ref MemoryMarshal.GetReference(redChannel); - ref float g = ref MemoryMarshal.GetReference(greenChannel); - ref float b = ref MemoryMarshal.GetReference(blueChannel); - ref Rgb24 rgb = ref MemoryMarshal.GetReference(source); - - for (int i = 0; i < source.Length; i++) - { - ref Rgb24 src = ref Unsafe.Add(ref rgb, i); - Unsafe.Add(ref r, i) = src.R; - Unsafe.Add(ref g, i) = src.G; - Unsafe.Add(ref b, i) = src.B; - } + ref Rgb24 src = ref Unsafe.Add(ref rgb, i); + Unsafe.Add(ref r, i) = src.R; + Unsafe.Add(ref g, i) = src.G; + Unsafe.Add(ref b, i) = src.B; } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index 1459601eaf..b91dc2fad2 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -1,266 +1,264 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +internal static partial class SimdUtils { - internal static partial class SimdUtils + /// + /// Shuffle single-precision (32-bit) floating-point elements in + /// using the control and store the results in . + /// + /// The source span of floats. + /// The destination span of floats. + /// The byte control. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4( + ReadOnlySpan source, + Span dest, + byte control) { - /// - /// Shuffle single-precision (32-bit) floating-point elements in - /// using the control and store the results in . - /// - /// The source span of floats. - /// The destination span of floats. - /// The byte control. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4( - ReadOnlySpan source, - Span dest, - byte control) - { - VerifyShuffle4SpanInput(source, dest); + VerifyShuffle4SpanInput(source, dest); - HwIntrinsics.Shuffle4Reduce(ref source, ref dest, control); + HwIntrinsics.Shuffle4Reduce(ref source, ref dest, control); - // Deal with the remainder: - if (source.Length > 0) - { - Shuffle4Remainder(source, dest, control); - } - } - - /// - /// Shuffle 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4( - ReadOnlySpan source, - Span dest, - TShuffle shuffle) - where TShuffle : struct, IShuffle4 + // Deal with the remainder: + if (source.Length > 0) { - VerifyShuffle4SpanInput(source, dest); - - HwIntrinsics.Shuffle4Reduce(ref source, ref dest, shuffle.Control); - - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.RunFallbackShuffle(source, dest); - } + Shuffle4Remainder(source, dest, control); } + } - /// - /// Shuffle 8-bit integer triplets within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle3( - ReadOnlySpan source, - Span dest, - TShuffle shuffle) - where TShuffle : struct, IShuffle3 - { - // Source length should be smaller than dest length, and divisible by 3. - VerifyShuffle3SpanInput(source, dest); + /// + /// Shuffle 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The type of shuffle to perform. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4( + ReadOnlySpan source, + Span dest, + TShuffle shuffle) + where TShuffle : struct, IShuffle4 + { + VerifyShuffle4SpanInput(source, dest); - HwIntrinsics.Shuffle3Reduce(ref source, ref dest, shuffle.Control); + HwIntrinsics.Shuffle4Reduce(ref source, ref dest, shuffle.Control); - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.RunFallbackShuffle(source, dest); - } + // Deal with the remainder: + if (source.Length > 0) + { + shuffle.RunFallbackShuffle(source, dest); } + } - /// - /// Pads then shuffles 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Pad3Shuffle4( - ReadOnlySpan source, - Span dest, - TShuffle shuffle) - where TShuffle : struct, IPad3Shuffle4 - { - VerifyPad3Shuffle4SpanInput(source, dest); + /// + /// Shuffle 8-bit integer triplets within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The type of shuffle to perform. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle3( + ReadOnlySpan source, + Span dest, + TShuffle shuffle) + where TShuffle : struct, IShuffle3 + { + // Source length should be smaller than dest length, and divisible by 3. + VerifyShuffle3SpanInput(source, dest); - HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, shuffle.Control); + HwIntrinsics.Shuffle3Reduce(ref source, ref dest, shuffle.Control); - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.RunFallbackShuffle(source, dest); - } + // Deal with the remainder: + if (source.Length > 0) + { + shuffle.RunFallbackShuffle(source, dest); } + } - /// - /// Shuffles then slices 8-bit integers within 128-bit lanes in - /// using the control and store the results in . - /// - /// The source span of bytes. - /// The destination span of bytes. - /// The type of shuffle to perform. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Shuffle4Slice3( - ReadOnlySpan source, - Span dest, - TShuffle shuffle) - where TShuffle : struct, IShuffle4Slice3 - { - VerifyShuffle4Slice3SpanInput(source, dest); + /// + /// Pads then shuffles 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The type of shuffle to perform. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Pad3Shuffle4( + ReadOnlySpan source, + Span dest, + TShuffle shuffle) + where TShuffle : struct, IPad3Shuffle4 + { + VerifyPad3Shuffle4SpanInput(source, dest); - HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, shuffle.Control); + HwIntrinsics.Pad3Shuffle4Reduce(ref source, ref dest, shuffle.Control); - // Deal with the remainder: - if (source.Length > 0) - { - shuffle.RunFallbackShuffle(source, dest); - } + // Deal with the remainder: + if (source.Length > 0) + { + shuffle.RunFallbackShuffle(source, dest); } + } - private static void Shuffle4Remainder( - ReadOnlySpan source, - Span dest, - byte control) - { - ref float sBase = ref MemoryMarshal.GetReference(source); - ref float dBase = ref MemoryMarshal.GetReference(dest); - Shuffle.InverseMmShuffle(control, out int p3, out int p2, out int p1, out int p0); + /// + /// Shuffles then slices 8-bit integers within 128-bit lanes in + /// using the control and store the results in . + /// + /// The source span of bytes. + /// The destination span of bytes. + /// The type of shuffle to perform. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Shuffle4Slice3( + ReadOnlySpan source, + Span dest, + TShuffle shuffle) + where TShuffle : struct, IShuffle4Slice3 + { + VerifyShuffle4Slice3SpanInput(source, dest); - for (int i = 0; i < source.Length; i += 4) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); - Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); - Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); - Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); - } - } + HwIntrinsics.Shuffle4Slice3Reduce(ref source, ref dest, shuffle.Control); - [Conditional("DEBUG")] - private static void VerifyShuffle4SpanInput(ReadOnlySpan source, Span dest) - where T : struct + // Deal with the remainder: + if (source.Length > 0) { - DebugGuard.IsTrue( - source.Length == dest.Length, - nameof(source), - "Input spans must be of same length!"); - - DebugGuard.IsTrue( - source.Length % 4 == 0, - nameof(source), - "Input spans must be divisable by 4!"); + shuffle.RunFallbackShuffle(source, dest); } + } - [Conditional("DEBUG")] - private static void VerifyShuffle3SpanInput(ReadOnlySpan source, Span dest) - where T : struct - { - DebugGuard.IsTrue( - source.Length <= dest.Length, - nameof(source), - "Source should fit into dest!"); - - DebugGuard.IsTrue( - source.Length % 3 == 0, - nameof(source), - "Input spans must be divisable by 3!"); - } + private static void Shuffle4Remainder( + ReadOnlySpan source, + Span dest, + byte control) + { + ref float sBase = ref MemoryMarshal.GetReference(source); + ref float dBase = ref MemoryMarshal.GetReference(dest); + Shuffle.InverseMmShuffle(control, out int p3, out int p2, out int p1, out int p0); - [Conditional("DEBUG")] - private static void VerifyPad3Shuffle4SpanInput(ReadOnlySpan source, Span dest) + for (int i = 0; i < source.Length; i += 4) { - DebugGuard.IsTrue( - source.Length % 3 == 0, - nameof(source), - "Input span must be divisable by 3!"); - - DebugGuard.IsTrue( - dest.Length % 4 == 0, - nameof(dest), - "Output span must be divisable by 4!"); - - DebugGuard.IsTrue( - source.Length == dest.Length * 3 / 4, - nameof(source), - "Input span must be 3/4 the length of the output span!"); + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, p0 + i); + Unsafe.Add(ref dBase, i + 1) = Unsafe.Add(ref sBase, p1 + i); + Unsafe.Add(ref dBase, i + 2) = Unsafe.Add(ref sBase, p2 + i); + Unsafe.Add(ref dBase, i + 3) = Unsafe.Add(ref sBase, p3 + i); } + } - [Conditional("DEBUG")] - private static void VerifyShuffle4Slice3SpanInput(ReadOnlySpan source, Span dest) - { - DebugGuard.IsTrue( - source.Length % 4 == 0, - nameof(source), - "Input span must be divisable by 4!"); - - DebugGuard.IsTrue( - dest.Length % 3 == 0, - nameof(dest), - "Output span must be divisable by 3!"); - - DebugGuard.IsTrue( - dest.Length >= source.Length * 3 / 4, - nameof(source), - "Output span must be at least 3/4 the length of the input span!"); - } + [Conditional("DEBUG")] + private static void VerifyShuffle4SpanInput(ReadOnlySpan source, Span dest) + where T : struct + { + DebugGuard.IsTrue( + source.Length == dest.Length, + nameof(source), + "Input spans must be of same length!"); + + DebugGuard.IsTrue( + source.Length % 4 == 0, + nameof(source), + "Input spans must be divisable by 4!"); + } + + [Conditional("DEBUG")] + private static void VerifyShuffle3SpanInput(ReadOnlySpan source, Span dest) + where T : struct + { + DebugGuard.IsTrue( + source.Length <= dest.Length, + nameof(source), + "Source should fit into dest!"); + + DebugGuard.IsTrue( + source.Length % 3 == 0, + nameof(source), + "Input spans must be divisable by 3!"); + } - public static class Shuffle + [Conditional("DEBUG")] + private static void VerifyPad3Shuffle4SpanInput(ReadOnlySpan source, Span dest) + { + DebugGuard.IsTrue( + source.Length % 3 == 0, + nameof(source), + "Input span must be divisable by 3!"); + + DebugGuard.IsTrue( + dest.Length % 4 == 0, + nameof(dest), + "Output span must be divisable by 4!"); + + DebugGuard.IsTrue( + source.Length == dest.Length * 3 / 4, + nameof(source), + "Input span must be 3/4 the length of the output span!"); + } + + [Conditional("DEBUG")] + private static void VerifyShuffle4Slice3SpanInput(ReadOnlySpan source, Span dest) + { + DebugGuard.IsTrue( + source.Length % 4 == 0, + nameof(source), + "Input span must be divisable by 4!"); + + DebugGuard.IsTrue( + dest.Length % 3 == 0, + nameof(dest), + "Output span must be divisable by 3!"); + + DebugGuard.IsTrue( + dest.Length >= source.Length * 3 / 4, + nameof(source), + "Output span must be at least 3/4 the length of the input span!"); + } + + public static class Shuffle + { + [MethodImpl(InliningOptions.ShortMethod)] + public static byte MmShuffle(byte p3, byte p2, byte p1, byte p0) + => (byte)((p3 << 6) | (p2 << 4) | (p1 << 2) | p0); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void MmShuffleSpan(ref Span span, byte control) { - [MethodImpl(InliningOptions.ShortMethod)] - public static byte MmShuffle(byte p3, byte p2, byte p1, byte p0) - => (byte)((p3 << 6) | (p2 << 4) | (p1 << 2) | p0); + InverseMmShuffle( + control, + out int p3, + out int p2, + out int p1, + out int p0); - [MethodImpl(InliningOptions.ShortMethod)] - public static void MmShuffleSpan(ref Span span, byte control) - { - InverseMmShuffle( - control, - out int p3, - out int p2, - out int p1, - out int p0); - - ref byte spanBase = ref MemoryMarshal.GetReference(span); - - for (int i = 0; i < span.Length; i += 4) - { - Unsafe.Add(ref spanBase, i) = (byte)(p0 + i); - Unsafe.Add(ref spanBase, i + 1) = (byte)(p1 + i); - Unsafe.Add(ref spanBase, i + 2) = (byte)(p2 + i); - Unsafe.Add(ref spanBase, i + 3) = (byte)(p3 + i); - } - } + ref byte spanBase = ref MemoryMarshal.GetReference(span); - [MethodImpl(InliningOptions.ShortMethod)] - public static void InverseMmShuffle( - byte control, - out int p3, - out int p2, - out int p1, - out int p0) + for (int i = 0; i < span.Length; i += 4) { - p3 = (control >> 6) & 0x3; - p2 = (control >> 4) & 0x3; - p1 = (control >> 2) & 0x3; - p0 = (control >> 0) & 0x3; + Unsafe.Add(ref spanBase, i) = (byte)(p0 + i); + Unsafe.Add(ref spanBase, i + 1) = (byte)(p1 + i); + Unsafe.Add(ref spanBase, i + 2) = (byte)(p2 + i); + Unsafe.Add(ref spanBase, i + 3) = (byte)(p3 + i); } } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void InverseMmShuffle( + byte control, + out int p3, + out int p2, + out int p1, + out int p0) + { + p3 = (control >> 6) & 0x3; + p2 = (control >> 4) & 0x3; + p1 = (control >> 2) & 0x3; + p0 = (control >> 0) & 0x3; + } } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 1a8d50a5d4..497e3cc6a0 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,196 +8,195 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Various extension and utility methods for and utilizing SIMD capabilities +/// +internal static partial class SimdUtils { /// - /// Various extension and utility methods for and utilizing SIMD capabilities + /// 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 HasVector8 { get; } = + Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; + + /// + /// Gets a value indicating whether code is being JIT-ed to SSE instructions + /// where float and integer registers are of size 128 byte. + /// + public static bool HasVector4 { get; } = + Vector.IsHardwareAccelerated && Vector.Count == 4; + + /// + /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. /// - internal static partial class SimdUtils + /// The vector + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 PseudoRound(this Vector4 v) { - /// - /// 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 HasVector8 { get; } = - Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; - - /// - /// Gets a value indicating whether code is being JIT-ed to SSE instructions - /// where float and integer registers are of size 128 byte. - /// - public static bool HasVector4 { get; } = - Vector.IsHardwareAccelerated && Vector.Count == 4; - - /// - /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector4 PseudoRound(this Vector4 v) - { - Vector4 sign = Numerics.Clamp(v, new Vector4(-1), new Vector4(1)); + Vector4 sign = Numerics.Clamp(v, new Vector4(-1), new Vector4(1)); - return v + (sign * 0.5f); - } + return v + (sign * 0.5f); + } - /// - /// Rounds all values in 'v' to the nearest integer following semantics. - /// Source: - /// - /// https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L110 - /// - /// - /// The vector - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Vector FastRound(this Vector v) + /// + /// Rounds all values in 'v' to the nearest integer following semantics. + /// Source: + /// + /// https://github.com/g-truc/glm/blob/master/glm/simd/common.h#L110 + /// + /// + /// The vector + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector FastRound(this Vector v) + { + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - ref Vector256 v256 = ref Unsafe.As, Vector256>(ref v); - Vector256 vRound = Avx.RoundToNearestInteger(v256); - return Unsafe.As, Vector>(ref vRound); - } - else - { - var magic0 = new Vector(int.MinValue); // 0x80000000 - var sgn0 = Vector.AsVectorSingle(magic0); - var and0 = Vector.BitwiseAnd(sgn0, v); - var or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f)); - var add0 = Vector.Add(v, or0); - return Vector.Subtract(add0, or0); - } + ref Vector256 v256 = ref Unsafe.As, Vector256>(ref v); + Vector256 vRound = Avx.RoundToNearestInteger(v256); + return Unsafe.As, Vector>(ref vRound); } - - /// - /// Converts all input -s to -s normalized into [0..1]. - /// should be the of the same size as , - /// but there are no restrictions on the span's length. - /// - /// The source span of bytes - /// The destination span of floats - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) + else { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + var magic0 = new Vector(int.MinValue); // 0x80000000 + var sgn0 = Vector.AsVectorSingle(magic0); + var and0 = Vector.BitwiseAnd(sgn0, v); + var or0 = Vector.BitwiseOr(and0, new Vector(8388608.0f)); + var add0 = Vector.Add(v, or0); + return Vector.Subtract(add0, or0); + } + } - HwIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest); + /// + /// Converts all input -s to -s normalized into [0..1]. + /// should be the of the same size as , + /// but there are no restrictions on the span's length. + /// + /// The source span of bytes + /// The destination span of floats + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - // Also deals with the remainder from previous conversions: - FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest); + HwIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest); - // Deal with the remainder: - if (source.Length > 0) - { - ConvertByteToNormalizedFloatRemainder(source, dest); - } - } + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest); - /// - /// Convert all values normalized into [0..1] from 'source' into 'dest' buffer of . - /// The values are scaled up into [0-255] and rounded, overflows are clamped. - /// should be the of the same size as , - /// but there are no restrictions on the span's length. - /// - /// The source span of floats - /// The destination span of bytes - [MethodImpl(InliningOptions.ShortMethod)] - internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) + // Deal with the remainder: + if (source.Length > 0) { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + ConvertByteToNormalizedFloatRemainder(source, dest); + } + } - HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); + /// + /// Convert all values normalized into [0..1] from 'source' into 'dest' buffer of . + /// The values are scaled up into [0-255] and rounded, overflows are clamped. + /// should be the of the same size as , + /// but there are no restrictions on the span's length. + /// + /// The source span of floats + /// The destination span of bytes + [MethodImpl(InliningOptions.ShortMethod)] + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - // Also deals with the remainder from previous conversions: - FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest); + HwIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); - // Deal with the remainder: - if (source.Length > 0) - { - ConvertNormalizedFloatToByteRemainder(source, dest); - } - } + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest); - [MethodImpl(InliningOptions.ColdPath)] - private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span dest) + // Deal with the remainder: + if (source.Length > 0) { - ref byte sBase = ref MemoryMarshal.GetReference(source); - ref float dBase = ref MemoryMarshal.GetReference(dest); - - // There are at most 3 elements at this point, having a for loop is overkill. - // Let's minimize the no. of instructions! - switch (source.Length) - { - case 3: - Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f; - goto case 2; - case 2: - Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f; - goto case 1; - case 1: - dBase = sBase / 255f; - break; - } + ConvertNormalizedFloatToByteRemainder(source, dest); } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ConvertByteToNormalizedFloatRemainder(ReadOnlySpan source, Span dest) + { + ref byte sBase = ref MemoryMarshal.GetReference(source); + ref float dBase = ref MemoryMarshal.GetReference(dest); - [MethodImpl(InliningOptions.ColdPath)] - private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span dest) + // There are at most 3 elements at this point, having a for loop is overkill. + // Let's minimize the no. of instructions! + switch (source.Length) { - ref float sBase = ref MemoryMarshal.GetReference(source); - ref byte dBase = ref MemoryMarshal.GetReference(dest); - - switch (source.Length) - { - case 3: - Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2)); - goto case 2; - case 2: - Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1)); - goto case 1; - case 1: - dBase = ConvertToByte(sBase); - break; - } + case 3: + Unsafe.Add(ref dBase, 2) = Unsafe.Add(ref sBase, 2) / 255f; + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = Unsafe.Add(ref sBase, 1) / 255f; + goto case 1; + case 1: + dBase = sBase / 255f; + break; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255F) + 0.5F, 0, 255F); + [MethodImpl(InliningOptions.ColdPath)] + private static void ConvertNormalizedFloatToByteRemainder(ReadOnlySpan source, Span dest) + { + ref float sBase = ref MemoryMarshal.GetReference(source); + ref byte dBase = ref MemoryMarshal.GetReference(dest); - [Conditional("DEBUG")] - private static void VerifyHasVector8(string operation) + switch (source.Length) { - if (!HasVector8) - { - throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); - } + case 3: + Unsafe.Add(ref dBase, 2) = ConvertToByte(Unsafe.Add(ref sBase, 2)); + goto case 2; + case 2: + Unsafe.Add(ref dBase, 1) = ConvertToByte(Unsafe.Add(ref sBase, 1)); + goto case 1; + case 1: + dBase = ConvertToByte(sBase); + break; } + } - [Conditional("DEBUG")] - private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) - { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - DebugGuard.IsTrue( - Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, - nameof(source), - $"length should be divisible by {shouldBeDivisibleBy}!"); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static byte ConvertToByte(float f) => (byte)Numerics.Clamp((f * 255F) + 0.5F, 0, 255F); - [Conditional("DEBUG")] - private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) + [Conditional("DEBUG")] + private static void VerifyHasVector8(string operation) + { + if (!HasVector8) { - DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); - DebugGuard.IsTrue( - Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, - nameof(source), - $"length should be divisible by {shouldBeDivisibleBy}!"); + throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); } + } - private struct ByteTuple4 - { - public byte V0; - public byte V1; - public byte V2; - public byte V3; - } + [Conditional("DEBUG")] + private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + DebugGuard.IsTrue( + Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, + nameof(source), + $"length should be divisible by {shouldBeDivisibleBy}!"); + } + + [Conditional("DEBUG")] + private static void VerifySpanInput(ReadOnlySpan source, Span dest, int shouldBeDivisibleBy) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + DebugGuard.IsTrue( + Numerics.ModuloP2(dest.Length, shouldBeDivisibleBy) == 0, + nameof(source), + $"length should be divisible by {shouldBeDivisibleBy}!"); + } + + private struct ByteTuple4 + { + public byte V0; + public byte V1; + public byte V2; + public byte V3; } } diff --git a/src/ImageSharp/Common/Helpers/TestHelpers.cs b/src/ImageSharp/Common/Helpers/TestHelpers.cs index 159fd95d3a..d358b9f016 100644 --- a/src/ImageSharp/Common/Helpers/TestHelpers.cs +++ b/src/ImageSharp/Common/Helpers/TestHelpers.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Common.Helpers +namespace SixLabors.ImageSharp.Common.Helpers; + +/// +/// Internal utilities intended to be only used in tests. +/// +internal static class TestHelpers { /// - /// Internal utilities intended to be only used in tests. + /// This constant is useful to verify the target framework ImageSharp has been built against. + /// Only intended to be used in tests! /// - internal static class TestHelpers - { - /// - /// This constant is useful to verify the target framework ImageSharp has been built against. - /// Only intended to be used in tests! - /// - internal const string ImageSharpBuiltAgainst = + internal const string ImageSharpBuiltAgainst = #if NETCOREAPP3_1 - "netcoreapp3.1"; + "netcoreapp3.1"; #elif NETCOREAPP2_1 - "netcoreapp2.1"; + "netcoreapp2.1"; #elif NETSTANDARD2_1 - "netstandard2.1"; + "netstandard2.1"; #elif NETSTANDARD2_0 - "netstandard2.0"; + "netstandard2.0"; #elif NETSTANDARD1_3 - "netstandard1.3"; + "netstandard1.3"; #else - "net472"; + "net472"; #endif - } } diff --git a/src/ImageSharp/Common/Helpers/TolerantMath.cs b/src/ImageSharp/Common/Helpers/TolerantMath.cs index f244a9169d..b4ed9ee2f2 100644 --- a/src/ImageSharp/Common/Helpers/TolerantMath.cs +++ b/src/ImageSharp/Common/Helpers/TolerantMath.cs @@ -1,106 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Implements basic math operations using tolerant comparison +/// whenever an equality check is needed. +/// +internal readonly struct TolerantMath { + private readonly double epsilon; + + private readonly double negEpsilon; + /// - /// Implements basic math operations using tolerant comparison - /// whenever an equality check is needed. + /// A read-only default instance for using 1e-8 as epsilon. + /// It is a field so it can be passed as an 'in' parameter. + /// Does not necessarily fit all use cases! /// - internal readonly struct TolerantMath + public static readonly TolerantMath Default = new TolerantMath(1e-8); + + public TolerantMath(double epsilon) { - private readonly double epsilon; + DebugGuard.MustBeGreaterThan(epsilon, 0, nameof(epsilon)); - private readonly double negEpsilon; + this.epsilon = epsilon; + this.negEpsilon = -epsilon; + } - /// - /// A read-only default instance for using 1e-8 as epsilon. - /// It is a field so it can be passed as an 'in' parameter. - /// Does not necessarily fit all use cases! - /// - public static readonly TolerantMath Default = new TolerantMath(1e-8); + /// + /// == 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsZero(double a) => a > this.negEpsilon && a < this.epsilon; - public TolerantMath(double epsilon) - { - DebugGuard.MustBeGreaterThan(epsilon, 0, nameof(epsilon)); + /// + /// > 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsPositive(double a) => a > this.epsilon; - this.epsilon = epsilon; - this.negEpsilon = -epsilon; - } + /// + /// < 0 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsNegative(double a) => a < this.negEpsilon; - /// - /// == 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsZero(double a) => a > this.negEpsilon && a < this.epsilon; - - /// - /// > 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsPositive(double a) => a > this.epsilon; - - /// - /// < 0 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsNegative(double a) => a < this.negEpsilon; - - /// - /// == - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool AreEqual(double a, double b) => this.IsZero(a - b); - - /// - /// > - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsGreater(double a, double b) => a > b + this.epsilon; - - /// - /// < - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsLess(double a, double b) => a < b - this.epsilon; - - /// - /// >= - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsGreaterOrEqual(double a, double b) => a >= b - this.epsilon; - - /// - /// <= - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsLessOrEqual(double a, double b) => b >= a - this.epsilon; - - [MethodImpl(InliningOptions.ShortMethod)] - public double Ceiling(double a) - { - double rem = Math.IEEERemainder(a, 1); - if (this.IsZero(rem)) - { - return Math.Round(a); - } + /// + /// == + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool AreEqual(double a, double b) => this.IsZero(a - b); - return Math.Ceiling(a); - } + /// + /// > + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsGreater(double a, double b) => a > b + this.epsilon; - [MethodImpl(InliningOptions.ShortMethod)] - public double Floor(double a) + /// + /// < + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsLess(double a, double b) => a < b - this.epsilon; + + /// + /// >= + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsGreaterOrEqual(double a, double b) => a >= b - this.epsilon; + + /// + /// <= + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsLessOrEqual(double a, double b) => b >= a - this.epsilon; + + [MethodImpl(InliningOptions.ShortMethod)] + public double Ceiling(double a) + { + double rem = Math.IEEERemainder(a, 1); + if (this.IsZero(rem)) { - double rem = Math.IEEERemainder(a, 1); - if (this.IsZero(rem)) - { - return Math.Round(a); - } + return Math.Round(a); + } + + return Math.Ceiling(a); + } - return Math.Floor(a); + [MethodImpl(InliningOptions.ShortMethod)] + public double Floor(double a) + { + double rem = Math.IEEERemainder(a, 1); + if (this.IsZero(rem)) + { + return Math.Round(a); } + + return Math.Floor(a); } } diff --git a/src/ImageSharp/Common/Helpers/UnitConverter.cs b/src/ImageSharp/Common/Helpers/UnitConverter.cs index 09bb40312c..04ecb5afd0 100644 --- a/src/ImageSharp/Common/Helpers/UnitConverter.cs +++ b/src/ImageSharp/Common/Helpers/UnitConverter.cs @@ -5,133 +5,132 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Common.Helpers +namespace SixLabors.ImageSharp.Common.Helpers; + +/// +/// Contains methods for converting values between unit scales. +/// +internal static class UnitConverter { /// - /// Contains methods for converting values between unit scales. + /// The number of centimeters in a meter. + /// 1 cm is equal to exactly 0.01 meters. + /// + private const double CmsInMeter = 1 / 0.01D; + + /// + /// The number of centimeters in an inch. + /// 1 inch is equal to exactly 2.54 centimeters. + /// + private const double CmsInInch = 2.54D; + + /// + /// The number of inches in a meter. + /// 1 inch is equal to exactly 0.0254 meters. + /// + private const double InchesInMeter = 1 / 0.0254D; + + /// + /// The default resolution unit value. /// - internal static class UnitConverter + private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch; + + /// + /// Scales the value from centimeters to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToMeter(double x) => x * CmsInMeter; + + /// + /// Scales the value from meters to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToCm(double x) => x / CmsInMeter; + + /// + /// Scales the value from meters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double MeterToInch(double x) => x / InchesInMeter; + + /// + /// Scales the value from inches to meters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToMeter(double x) => x * InchesInMeter; + + /// + /// Scales the value from centimeters to inches. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double CmToInch(double x) => x / CmsInInch; + + /// + /// Scales the value from inches to centimeters. + /// + /// The value to scale. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static double InchToCm(double x) => x * CmsInInch; + + /// + /// Converts an to a . + /// + /// The EXIF profile containing the value. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) { - /// - /// The number of centimeters in a meter. - /// 1 cm is equal to exactly 0.01 meters. - /// - private const double CmsInMeter = 1 / 0.01D; - - /// - /// The number of centimeters in an inch. - /// 1 inch is equal to exactly 2.54 centimeters. - /// - private const double CmsInInch = 2.54D; - - /// - /// The number of inches in a meter. - /// 1 inch is equal to exactly 0.0254 meters. - /// - private const double InchesInMeter = 1 / 0.0254D; - - /// - /// The default resolution unit value. - /// - private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch; - - /// - /// Scales the value from centimeters to meters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double CmToMeter(double x) => x * CmsInMeter; - - /// - /// Scales the value from meters to centimeters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double MeterToCm(double x) => x / CmsInMeter; - - /// - /// Scales the value from meters to inches. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double MeterToInch(double x) => x / InchesInMeter; - - /// - /// Scales the value from inches to meters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double InchToMeter(double x) => x * InchesInMeter; - - /// - /// Scales the value from centimeters to inches. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double CmToInch(double x) => x / CmsInInch; - - /// - /// Scales the value from inches to centimeters. - /// - /// The value to scale. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static double InchToCm(double x) => x * CmsInInch; - - /// - /// Converts an to a . - /// - /// The EXIF profile containing the value. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static PixelResolutionUnit ExifProfileToResolutionUnit(ExifProfile profile) - { - IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit); + IExifValue resolution = profile.GetValue(ExifTag.ResolutionUnit); - // EXIF is 1, 2, 3 so we minus "1" off the result. - return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1); + // EXIF is 1, 2, 3 so we minus "1" off the result. + return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1); + } + + /// + /// Gets the exif profile resolution values. + /// + /// The resolution unit. + /// The horizontal resolution value. + /// The vertical resolution value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical) + { + switch (unit) + { + case PixelResolutionUnit.AspectRatio: + case PixelResolutionUnit.PixelsPerInch: + case PixelResolutionUnit.PixelsPerCentimeter: + break; + case PixelResolutionUnit.PixelsPerMeter: + + unit = PixelResolutionUnit.PixelsPerCentimeter; + horizontal = MeterToCm(horizontal); + vertical = MeterToCm(vertical); + + break; + default: + unit = PixelResolutionUnit.PixelsPerInch; + break; } - /// - /// Gets the exif profile resolution values. - /// - /// The resolution unit. - /// The horizontal resolution value. - /// The vertical resolution value. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical) + ushort exifUnit = (ushort)(unit + 1); + if (unit == PixelResolutionUnit.AspectRatio) { - switch (unit) - { - case PixelResolutionUnit.AspectRatio: - case PixelResolutionUnit.PixelsPerInch: - case PixelResolutionUnit.PixelsPerCentimeter: - break; - case PixelResolutionUnit.PixelsPerMeter: - - unit = PixelResolutionUnit.PixelsPerCentimeter; - horizontal = MeterToCm(horizontal); - vertical = MeterToCm(vertical); - - break; - default: - unit = PixelResolutionUnit.PixelsPerInch; - break; - } - - ushort exifUnit = (ushort)(unit + 1); - if (unit == PixelResolutionUnit.AspectRatio) - { - return new ExifResolutionValues(exifUnit, null, null); - } - - return new ExifResolutionValues(exifUnit, horizontal, vertical); + return new ExifResolutionValues(exifUnit, null, null); } + + return new ExifResolutionValues(exifUnit, horizontal, vertical); } } diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs index 08fa639435..f73bdd3390 100644 --- a/src/ImageSharp/Common/Tuples/Octet{T}.cs +++ b/src/ImageSharp/Common/Tuples/Octet{T}.cs @@ -4,70 +4,69 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Tuples +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 { /// - /// Contains 8 element value tuples of various types. + /// Loads the fields in a target of from one of type. /// - [StructLayout(LayoutKind.Sequential)] - internal struct Octet - where T : unmanaged + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) { - 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})"; - } + 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; } /// - /// Extension methods for the type. + /// Loads the fields in a target of from one of type. /// - internal static class OctetExtensions + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) { - /// - /// 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; - } + 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/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs index d8234e1b82..3885ef5755 100644 --- a/src/ImageSharp/Compression/Zlib/Adler32.cs +++ b/src/ImageSharp/Compression/Zlib/Adler32.cs @@ -1,338 +1,336 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #pragma warning disable IDE0007 // Use implicit type -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Calculates the 32 bit Adler checksum of a given buffer according to +/// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) +/// +internal static class Adler32 { /// - /// Calculates the 32 bit Adler checksum of a given buffer according to - /// RFC 1950. ZLIB Compressed Data Format Specification version 3.3) + /// The default initial seed value of a Adler32 checksum calculation. /// - internal static class Adler32 - { - /// - /// The default initial seed value of a Adler32 checksum calculation. - /// - public const uint SeedValue = 1U; + public const uint SeedValue = 1U; - // Largest prime smaller than 65536 - private const uint BASE = 65521; + // Largest prime smaller than 65536 + private const uint BASE = 65521; - // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 - private const uint NMAX = 5552; + // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 + private const uint NMAX = 5552; - private const int MinBufferSize = 64; + private const int MinBufferSize = 64; - private const int BlockSize = 1 << 5; + private const int BlockSize = 1 << 5; - // The C# compiler emits this as a compile-time constant embedded in the PE file. - private static ReadOnlySpan Tap1Tap2 => new byte[] - { - 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1 - 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2 - }; - - /// - /// Calculates the Adler32 checksum with the bytes taken from the span. - /// - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Calculate(ReadOnlySpan buffer) - => Calculate(SeedValue, buffer); - - /// - /// Calculates the Adler32 checksum with the bytes taken from the span and seed. - /// - /// The input Adler32 value. - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static uint Calculate(uint adler, ReadOnlySpan buffer) - { - if (buffer.IsEmpty) - { - return adler; - } + // The C# compiler emits this as a compile-time constant embedded in the PE file. + private static ReadOnlySpan Tap1Tap2 => new byte[] + { + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1 + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2 + }; - if (Avx2.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateAvx2(adler, buffer); - } + /// + /// Calculates the Adler32 checksum with the bytes taken from the span. + /// + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) + => Calculate(SeedValue, buffer); - if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) - { - return CalculateSse(adler, buffer); - } + /// + /// Calculates the Adler32 checksum with the bytes taken from the span and seed. + /// + /// The input Adler32 value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static uint Calculate(uint adler, ReadOnlySpan buffer) + { + if (buffer.IsEmpty) + { + return adler; + } - return CalculateScalar(adler, buffer); + if (Avx2.IsSupported && buffer.Length >= MinBufferSize) + { + return CalculateAvx2(adler, buffer); } - // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) + if (Ssse3.IsSupported && buffer.Length >= MinBufferSize) { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; + return CalculateSse(adler, buffer); + } - // Process the data in blocks. - uint length = (uint)buffer.Length; - uint blocks = length / BlockSize; - length -= blocks * BlockSize; + return CalculateScalar(adler, buffer); + } + + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + + // Process the data in blocks. + uint length = (uint)buffer.Length; + uint blocks = length / BlockSize; + length -= blocks * BlockSize; - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) + { + fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) { - fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2)) - { - byte* localBufferPtr = bufferPtr; + byte* localBufferPtr = bufferPtr; - // _mm_setr_epi8 on x86 - Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); - Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); - Vector128 zero = Vector128.Zero; - var ones = Vector128.Create((short)1); + // _mm_setr_epi8 on x86 + Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr); + Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10)); + Vector128 zero = Vector128.Zero; + var ones = Vector128.Create((short)1); - while (blocks > 0) + while (blocks > 0) + { + uint n = NMAX / BlockSize; /* The NMAX constraint. */ + if (n > blocks) { - uint n = NMAX / BlockSize; /* The NMAX constraint. */ - if (n > blocks) - { - n = blocks; - } - - blocks -= n; + n = blocks; + } - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - Vector128 v_ps = Vector128.CreateScalar(s1 * n); - Vector128 v_s2 = Vector128.CreateScalar(s2); - Vector128 v_s1 = Vector128.Zero; + blocks -= n; - do - { - // Load 32 input bytes. - Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); - Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); + // Process n blocks of data. At most NMAX data bytes can be + // processed before s2 must be reduced modulo BASE. + Vector128 v_ps = Vector128.CreateScalar(s1 * n); + Vector128 v_s2 = Vector128.CreateScalar(s2); + Vector128 v_s1 = Vector128.Zero; - // Add previous block byte sum to v_ps. - v_ps = Sse2.Add(v_ps, v_s1); + do + { + // Load 32 input bytes. + Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr); + Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); - // Horizontally add the bytes for s1, multiply-adds the - // bytes by [ 32, 31, 30, ... ] for s2. - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); - Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); + // Add previous block byte sum to v_ps. + v_ps = Sse2.Add(v_ps, v_s1); - v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); - Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); - v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); + // Horizontally add the bytes for s1, multiply-adds the + // bytes by [ 32, 31, 30, ... ] for s2. + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); + Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); - localBufferPtr += BlockSize; - } - while (--n > 0); + v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); + Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); + v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); - v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); + localBufferPtr += BlockSize; + } + while (--n > 0); - // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). - const byte s2301 = 0b1011_0001; // A B C D -> B A D C - const byte s1032 = 0b0100_1110; // A B C D -> C D A B + v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); - v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, s1032)); + // Sum epi32 ints v_s1(s2) and accumulate in s1(s2). + const byte s2301 = 0b1011_0001; // A B C D -> B A D C + const byte s1032 = 0b0100_1110; // A B C D -> C D A B - s1 += v_s1.ToScalar(); + v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, s1032)); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s2301)); - v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s1032)); + s1 += v_s1.ToScalar(); - s2 = v_s2.ToScalar(); + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s2301)); + v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s1032)); - // Reduce. - s1 %= BASE; - s2 %= BASE; - } + s2 = v_s2.ToScalar(); - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } + // Reduce. + s1 %= BASE; + s2 %= BASE; + } - return s1 | (s2 << 16); + if (length > 0) + { + HandleLeftOver(localBufferPtr, length, ref s1, ref s2); } + + return s1 | (s2 << 16); } } + } + + // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint length = (uint)buffer.Length; - // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer) + fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint length = (uint)buffer.Length; + byte* localBufferPtr = bufferPtr; - fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer)) - { - byte* localBufferPtr = bufferPtr; + Vector256 zero = Vector256.Zero; + var dot3v = Vector256.Create((short)1); + var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + + // Process n blocks of data. At most NMAX data bytes can be + // processed before s2 must be reduced modulo BASE. + var vs1 = Vector256.CreateScalar(s1); + var vs2 = Vector256.CreateScalar(s2); - Vector256 zero = Vector256.Zero; - var dot3v = Vector256.Create((short)1); - var dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1); + while (length >= 32) + { + int k = length < NMAX ? (int)length : (int)NMAX; + k -= k % 32; + length -= (uint)k; - // Process n blocks of data. At most NMAX data bytes can be - // processed before s2 must be reduced modulo BASE. - var vs1 = Vector256.CreateScalar(s1); - var vs2 = Vector256.CreateScalar(s2); + Vector256 vs10 = vs1; + Vector256 vs3 = Vector256.Zero; - while (length >= 32) + while (k >= 32) { - int k = length < NMAX ? (int)length : (int)NMAX; - k -= k % 32; - length -= (uint)k; + // Load 32 input bytes. + Vector256 block = Avx.LoadVector256(localBufferPtr); - Vector256 vs10 = vs1; - Vector256 vs3 = Vector256.Zero; + // Sum of abs diff, resulting in 2 x int32's + Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); - while (k >= 32) - { - // Load 32 input bytes. - Vector256 block = Avx.LoadVector256(localBufferPtr); + vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); + vs3 = Avx2.Add(vs3, vs10); - // Sum of abs diff, resulting in 2 x int32's - Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero); + // sum 32 uint8s to 16 shorts. + Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); - vs1 = Avx2.Add(vs1, vs1sad.AsUInt32()); - vs3 = Avx2.Add(vs3, vs10); + // sum 16 shorts to 8 uint32s. + Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); - // sum 32 uint8s to 16 shorts. - Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v); + vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); + vs10 = vs1; - // sum 16 shorts to 8 uint32s. - Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v); + localBufferPtr += BlockSize; + k -= 32; + } - vs2 = Avx2.Add(vsum2.AsUInt32(), vs2); - vs10 = vs1; + // Defer the multiplication with 32 to outside of the loop. + vs3 = Avx2.ShiftLeftLogical(vs3, 5); + vs2 = Avx2.Add(vs2, vs3); - localBufferPtr += BlockSize; - k -= 32; - } - - // Defer the multiplication with 32 to outside of the loop. - vs3 = Avx2.ShiftLeftLogical(vs3, 5); - vs2 = Avx2.Add(vs2, vs3); + s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); + s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); - s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32()); - s2 = (uint)Numerics.ReduceSum(vs2.AsInt32()); + s1 %= BASE; + s2 %= BASE; - s1 %= BASE; - s2 %= BASE; + vs1 = Vector256.CreateScalar(s1); + vs2 = Vector256.CreateScalar(s2); + } - vs1 = Vector256.CreateScalar(s1); - vs2 = Vector256.CreateScalar(s2); - } + if (length > 0) + { + HandleLeftOver(localBufferPtr, length, ref s1, ref s2); + } - if (length > 0) - { - HandleLeftOver(localBufferPtr, length, ref s1, ref s2); - } + return s1 | (s2 << 16); + } + } - return s1 | (s2 << 16); - } + private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2) + { + if (length >= 16) + { + s2 += s1 += localBufferPtr[0]; + s2 += s1 += localBufferPtr[1]; + s2 += s1 += localBufferPtr[2]; + s2 += s1 += localBufferPtr[3]; + s2 += s1 += localBufferPtr[4]; + s2 += s1 += localBufferPtr[5]; + s2 += s1 += localBufferPtr[6]; + s2 += s1 += localBufferPtr[7]; + s2 += s1 += localBufferPtr[8]; + s2 += s1 += localBufferPtr[9]; + s2 += s1 += localBufferPtr[10]; + s2 += s1 += localBufferPtr[11]; + s2 += s1 += localBufferPtr[12]; + s2 += s1 += localBufferPtr[13]; + s2 += s1 += localBufferPtr[14]; + s2 += s1 += localBufferPtr[15]; + + localBufferPtr += 16; + length -= 16; } - private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2) + while (length-- > 0) { - if (length >= 16) - { - s2 += s1 += localBufferPtr[0]; - s2 += s1 += localBufferPtr[1]; - s2 += s1 += localBufferPtr[2]; - s2 += s1 += localBufferPtr[3]; - s2 += s1 += localBufferPtr[4]; - s2 += s1 += localBufferPtr[5]; - s2 += s1 += localBufferPtr[6]; - s2 += s1 += localBufferPtr[7]; - s2 += s1 += localBufferPtr[8]; - s2 += s1 += localBufferPtr[9]; - s2 += s1 += localBufferPtr[10]; - s2 += s1 += localBufferPtr[11]; - s2 += s1 += localBufferPtr[12]; - s2 += s1 += localBufferPtr[13]; - s2 += s1 += localBufferPtr[14]; - s2 += s1 += localBufferPtr[15]; - - localBufferPtr += 16; - length -= 16; - } + s2 += s1 += *localBufferPtr++; + } - while (length-- > 0) - { - s2 += s1 += *localBufferPtr++; - } + if (s1 >= BASE) + { + s1 -= BASE; + } - if (s1 >= BASE) - { - s1 -= BASE; - } + s2 %= BASE; + } - s2 %= BASE; - } + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) + { + uint s1 = adler & 0xFFFF; + uint s2 = (adler >> 16) & 0xFFFF; + uint k; - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer) + fixed (byte* bufferPtr = buffer) { - uint s1 = adler & 0xFFFF; - uint s2 = (adler >> 16) & 0xFFFF; - uint k; + byte* localBufferPtr = bufferPtr; + uint length = (uint)buffer.Length; - fixed (byte* bufferPtr = buffer) + while (length > 0) { - byte* localBufferPtr = bufferPtr; - uint length = (uint)buffer.Length; + k = length < NMAX ? length : NMAX; + length -= k; - while (length > 0) + while (k >= 16) { - k = length < NMAX ? length : NMAX; - length -= k; - - while (k >= 16) - { - s2 += s1 += localBufferPtr[0]; - s2 += s1 += localBufferPtr[1]; - s2 += s1 += localBufferPtr[2]; - s2 += s1 += localBufferPtr[3]; - s2 += s1 += localBufferPtr[4]; - s2 += s1 += localBufferPtr[5]; - s2 += s1 += localBufferPtr[6]; - s2 += s1 += localBufferPtr[7]; - s2 += s1 += localBufferPtr[8]; - s2 += s1 += localBufferPtr[9]; - s2 += s1 += localBufferPtr[10]; - s2 += s1 += localBufferPtr[11]; - s2 += s1 += localBufferPtr[12]; - s2 += s1 += localBufferPtr[13]; - s2 += s1 += localBufferPtr[14]; - s2 += s1 += localBufferPtr[15]; - - localBufferPtr += 16; - k -= 16; - } - - while (k-- > 0) - { - s2 += s1 += *localBufferPtr++; - } + s2 += s1 += localBufferPtr[0]; + s2 += s1 += localBufferPtr[1]; + s2 += s1 += localBufferPtr[2]; + s2 += s1 += localBufferPtr[3]; + s2 += s1 += localBufferPtr[4]; + s2 += s1 += localBufferPtr[5]; + s2 += s1 += localBufferPtr[6]; + s2 += s1 += localBufferPtr[7]; + s2 += s1 += localBufferPtr[8]; + s2 += s1 += localBufferPtr[9]; + s2 += s1 += localBufferPtr[10]; + s2 += s1 += localBufferPtr[11]; + s2 += s1 += localBufferPtr[12]; + s2 += s1 += localBufferPtr[13]; + s2 += s1 += localBufferPtr[14]; + s2 += s1 += localBufferPtr[15]; + + localBufferPtr += 16; + k -= 16; + } - s1 %= BASE; - s2 %= BASE; + while (k-- > 0) + { + s2 += s1 += *localBufferPtr++; } - return (s2 << 16) | s1; + s1 %= BASE; + s2 %= BASE; } + + return (s2 << 16) | s1; } } } diff --git a/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs index 304372d2de..9145ac4a4e 100644 --- a/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs +++ b/src/ImageSharp/Compression/Zlib/Crc32.Lut.cs @@ -1,70 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Contains precalulated tables for scalar calculations. +/// +internal static partial class Crc32 { - /// - /// Contains precalulated tables for scalar calculations. - /// - internal static partial class Crc32 + /// + /// The table of all possible eight bit values for fast scalar lookup. + /// + private static readonly uint[] CrcTable = { - /// - /// The table of all possible eight bit values for fast scalar lookup. - /// - private static readonly uint[] CrcTable = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, - 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, - 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, - 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, - 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, - 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, - 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, - 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, - 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, - 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, - 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, - 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, - 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, - 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, - 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, - 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, - 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, - 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, - 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, - 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, - 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, - 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, - 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, - 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, - 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, - 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, - 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, - 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, - 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, - 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, - 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, - 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, - 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, - 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, - 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, - 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, - 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, - 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, - 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, - 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, - 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, - 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, - 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, - 0x2D02EF8D - }; - } + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, + 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, + 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, + 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, + 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, + 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, + 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, + 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, + 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, + 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, + 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, + 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, + 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, + 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, + 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, + 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, + 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, + 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, + 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, + 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, + 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, + 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, + 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, + 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, + 0x2D02EF8D + }; } diff --git a/src/ImageSharp/Compression/Zlib/Crc32.cs b/src/ImageSharp/Compression/Zlib/Crc32.cs index 757682a3fc..b8665bd43a 100644 --- a/src/ImageSharp/Compression/Zlib/Crc32.cs +++ b/src/ImageSharp/Compression/Zlib/Crc32.cs @@ -1,205 +1,203 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer +/// according to the IEEE 802.3 specification. +/// +internal static partial class Crc32 { /// - /// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer - /// according to the IEEE 802.3 specification. + /// The default initial seed value of a Crc32 checksum calculation. /// - internal static partial class Crc32 + public const uint SeedValue = 0U; + + private const int MinBufferSize = 64; + private const int ChunksizeMask = 15; + + // Definitions of the bit-reflected domain constants k1, k2, k3, etc and + // the CRC32+Barrett polynomials given at the end of the paper. + private static readonly ulong[] K05Poly = { - /// - /// The default initial seed value of a Crc32 checksum calculation. - /// - public const uint SeedValue = 0U; + 0x0154442bd4, 0x01c6e41596, // k1, k2 + 0x01751997d0, 0x00ccaa009e, // k3, k4 + 0x0163cd6124, 0x0000000000, // k5, k0 + 0x01db710641, 0x01f7011641 // polynomial + }; - private const int MinBufferSize = 64; - private const int ChunksizeMask = 15; + /// + /// Calculates the CRC checksum with the bytes taken from the span. + /// + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(ReadOnlySpan buffer) + => Calculate(SeedValue, buffer); - // Definitions of the bit-reflected domain constants k1, k2, k3, etc and - // the CRC32+Barrett polynomials given at the end of the paper. - private static readonly ulong[] K05Poly = + /// + /// Calculates the CRC checksum with the bytes taken from the span and seed. + /// + /// The input CRC value. + /// The readonly span of bytes. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Calculate(uint crc, ReadOnlySpan buffer) + { + if (buffer.IsEmpty) { - 0x0154442bd4, 0x01c6e41596, // k1, k2 - 0x01751997d0, 0x00ccaa009e, // k3, k4 - 0x0163cd6124, 0x0000000000, // k5, k0 - 0x01db710641, 0x01f7011641 // polynomial - }; - - /// - /// Calculates the CRC checksum with the bytes taken from the span. - /// - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Calculate(ReadOnlySpan buffer) - => Calculate(SeedValue, buffer); - - /// - /// Calculates the CRC checksum with the bytes taken from the span and seed. - /// - /// The input CRC value. - /// The readonly span of bytes. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Calculate(uint crc, ReadOnlySpan buffer) + return crc; + } + + if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize) { - if (buffer.IsEmpty) - { - return crc; - } + return ~CalculateSse(~crc, buffer); + } - if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize) - { - return ~CalculateSse(~crc, buffer); - } + return ~CalculateScalar(~crc, buffer); + } - return ~CalculateScalar(~crc, buffer); - } + // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static unsafe uint CalculateSse(uint crc, ReadOnlySpan buffer) + { + int chunksize = buffer.Length & ~ChunksizeMask; + int length = chunksize; - // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static unsafe uint CalculateSse(uint crc, ReadOnlySpan buffer) + fixed (byte* bufferPtr = buffer) { - int chunksize = buffer.Length & ~ChunksizeMask; - int length = chunksize; - - fixed (byte* bufferPtr = buffer) + fixed (ulong* k05PolyPtr = K05Poly) { - fixed (ulong* k05PolyPtr = K05Poly) - { - byte* localBufferPtr = bufferPtr; - ulong* localK05PolyPtr = k05PolyPtr; - - // There's at least one block of 64. - Vector128 x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); - Vector128 x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); - Vector128 x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); - Vector128 x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); - Vector128 x5; + byte* localBufferPtr = bufferPtr; + ulong* localK05PolyPtr = k05PolyPtr; - x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64()); + // There's at least one block of 64. + Vector128 x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); + Vector128 x5; - // k1, k2 - Vector128 x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0); + x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64()); - localBufferPtr += 64; - length -= 64; + // k1, k2 + Vector128 x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0); - // Parallel fold blocks of 64, if any. - while (length >= 64) - { - x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); - Vector128 x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); - Vector128 x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00); - Vector128 x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00); - - x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); - x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11); - x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11); - x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11); - - Vector128 y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); - Vector128 y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); - Vector128 y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); - Vector128 y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); - - x1 = Sse2.Xor(x1, x5); - x2 = Sse2.Xor(x2, x6); - x3 = Sse2.Xor(x3, x7); - x4 = Sse2.Xor(x4, x8); - - x1 = Sse2.Xor(x1, y5); - x2 = Sse2.Xor(x2, y6); - x3 = Sse2.Xor(x3, y7); - x4 = Sse2.Xor(x4, y8); - - localBufferPtr += 64; - length -= 64; - } - - // Fold into 128-bits. - // k3, k4 - x0 = Sse2.LoadVector128(k05PolyPtr + 0x2); + localBufferPtr += 64; + length -= 64; + // Parallel fold blocks of 64, if any. + while (length >= 64) + { x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); - x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); - x1 = Sse2.Xor(x1, x2); - x1 = Sse2.Xor(x1, x5); + Vector128 x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + Vector128 x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00); + Vector128 x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00); - x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); - x1 = Sse2.Xor(x1, x3); - x1 = Sse2.Xor(x1, x5); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11); + x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11); + x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11); + + Vector128 y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); + Vector128 y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); + Vector128 y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); + Vector128 y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); - x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); - x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); - x1 = Sse2.Xor(x1, x4); x1 = Sse2.Xor(x1, x5); + x2 = Sse2.Xor(x2, x6); + x3 = Sse2.Xor(x3, x7); + x4 = Sse2.Xor(x4, x8); - // Single fold blocks of 16, if any. - while (length >= 16) - { - x2 = Sse2.LoadVector128((ulong*)localBufferPtr); + x1 = Sse2.Xor(x1, y5); + x2 = Sse2.Xor(x2, y6); + x3 = Sse2.Xor(x3, y7); + x4 = Sse2.Xor(x4, y8); - x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); - x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); - x1 = Sse2.Xor(x1, x2); - x1 = Sse2.Xor(x1, x5); + localBufferPtr += 64; + length -= 64; + } - localBufferPtr += 16; - length -= 16; - } + // Fold into 128-bits. + // k3, k4 + x0 = Sse2.LoadVector128(k05PolyPtr + 0x2); - // Fold 128 - bits to 64 - bits. - x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10); - x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86 - x1 = Sse2.ShiftRightLogical128BitLane(x1, 8); - x1 = Sse2.Xor(x1, x2); + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x2); + x1 = Sse2.Xor(x1, x5); - // k5, k0 - x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4); + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x3); + x1 = Sse2.Xor(x1, x5); - x2 = Sse2.ShiftRightLogical128BitLane(x1, 4); - x1 = Sse2.And(x1, x3); - x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); - x1 = Sse2.Xor(x1, x2); + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); + x1 = Sse2.Xor(x1, x4); + x1 = Sse2.Xor(x1, x5); - // Barret reduce to 32-bits. - // polynomial - x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6); + // Single fold blocks of 16, if any. + while (length >= 16) + { + x2 = Sse2.LoadVector128((ulong*)localBufferPtr); - x2 = Sse2.And(x1, x3); - x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10); - x2 = Sse2.And(x2, x3); - x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); x1 = Sse2.Xor(x1, x2); + x1 = Sse2.Xor(x1, x5); - crc = (uint)Sse41.Extract(x1.AsInt32(), 1); - return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer[chunksize..]); + localBufferPtr += 16; + length -= 16; } - } - } - [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] - private static uint CalculateScalar(uint crc, ReadOnlySpan buffer) - { - ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); - ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + // Fold 128 - bits to 64 - bits. + x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10); + x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86 + x1 = Sse2.ShiftRightLogical128BitLane(x1, 8); + x1 = Sse2.Xor(x1, x2); - for (int i = 0; i < buffer.Length; i++) - { - crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8); + // k5, k0 + x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4); + + x2 = Sse2.ShiftRightLogical128BitLane(x1, 4); + x1 = Sse2.And(x1, x3); + x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); + x1 = Sse2.Xor(x1, x2); + + // Barret reduce to 32-bits. + // polynomial + x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6); + + x2 = Sse2.And(x1, x3); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10); + x2 = Sse2.And(x2, x3); + x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); + x1 = Sse2.Xor(x1, x2); + + crc = (uint)Sse41.Extract(x1.AsInt32(), 1); + return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer[chunksize..]); } + } + } - return crc; + [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)] + private static uint CalculateScalar(uint crc, ReadOnlySpan buffer) + { + ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan()); + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + + for (int i = 0; i < buffer.Length; i++) + { + crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8); } + + return crc; } } diff --git a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs index e483435f60..87ea8fa583 100644 --- a/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs +++ b/src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs @@ -1,81 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Provides enumeration of available deflate compression levels. +/// +public enum DeflateCompressionLevel { /// - /// Provides enumeration of available deflate compression levels. + /// Level 0. Equivalent to . /// - public enum DeflateCompressionLevel - { - /// - /// Level 0. Equivalent to . - /// - Level0 = 0, + Level0 = 0, - /// - /// No compression. Equivalent to . - /// - NoCompression = Level0, + /// + /// No compression. Equivalent to . + /// + NoCompression = Level0, - /// - /// Level 1. Equivalent to . - /// - Level1 = 1, + /// + /// Level 1. Equivalent to . + /// + Level1 = 1, - /// - /// Best speed compression level. - /// - BestSpeed = Level1, + /// + /// Best speed compression level. + /// + BestSpeed = Level1, - /// - /// Level 2. - /// - Level2 = 2, + /// + /// Level 2. + /// + Level2 = 2, - /// - /// Level 3. - /// - Level3 = 3, + /// + /// Level 3. + /// + Level3 = 3, - /// - /// Level 4. - /// - Level4 = 4, + /// + /// Level 4. + /// + Level4 = 4, - /// - /// Level 5. - /// - Level5 = 5, + /// + /// Level 5. + /// + Level5 = 5, - /// - /// Level 6. Equivalent to . - /// - Level6 = 6, + /// + /// Level 6. Equivalent to . + /// + Level6 = 6, - /// - /// The default compression level. Equivalent to . - /// - DefaultCompression = Level6, + /// + /// The default compression level. Equivalent to . + /// + DefaultCompression = Level6, - /// - /// Level 7. - /// - Level7 = 7, + /// + /// Level 7. + /// + Level7 = 7, - /// - /// Level 8. - /// - Level8 = 8, + /// + /// Level 8. + /// + Level8 = 8, - /// - /// Level 9. Equivalent to . - /// - Level9 = 9, + /// + /// Level 9. Equivalent to . + /// + Level9 = 9, - /// - /// Best compression level. Equivalent to . - /// - BestCompression = Level9, - } + /// + /// Best compression level. Equivalent to . + /// + BestCompression = Level9, } diff --git a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs index d334a02449..8097c4e31d 100644 --- a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs +++ b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs @@ -1,35 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +internal static class DeflateThrowHelper { - internal static class DeflateThrowHelper - { - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNull(string name) => throw new ArgumentNullException(name); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNull(string name) => throw new ArgumentNullException(name); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input."); } diff --git a/src/ImageSharp/Compression/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs index 78736d6204..78da8e8afe 100644 --- a/src/ImageSharp/Compression/Zlib/Deflater.cs +++ b/src/ImageSharp/Compression/Zlib/Deflater.cs @@ -1,293 +1,291 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// This class compresses input with the deflate algorithm described in RFC 1951. +/// It has several compression levels and three different strategies described below. +/// +internal sealed class Deflater : IDisposable { /// - /// This class compresses input with the deflate algorithm described in RFC 1951. - /// It has several compression levels and three different strategies described below. + /// The best and slowest compression level. This tries to find very + /// long and distant string repetitions. + /// + public const int BestCompression = 9; + + /// + /// The worst but fastest compression level. + /// + public const int BestSpeed = 1; + + /// + /// The default compression level. + /// + public const int DefaultCompression = -1; + + /// + /// This level won't compress at all but output uncompressed blocks. + /// + public const int NoCompression = 0; + + /// + /// The compression method. This is the only method supported so far. + /// There is no need to use this constant at all. + /// + public const int Deflated = 8; + + /// + /// Compression level. + /// + private int level; + + /// + /// The current state. /// - internal sealed class Deflater : IDisposable + private int state; + + private DeflaterEngine engine; + private bool isDisposed; + + private const int IsFlushing = 0x04; + private const int IsFinishing = 0x08; + private const int BusyState = 0x10; + private const int FlushingState = 0x14; + private const int FinishingState = 0x1c; + private const int FinishedState = 0x1e; + private const int ClosedState = 0x7f; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The compression level, a value between NoCompression and BestCompression. + /// + /// if level is out of range. + public Deflater(MemoryAllocator memoryAllocator, int level) + { + if (level == DefaultCompression) + { + level = 6; + } + else if (level < NoCompression || level > BestCompression) + { + throw new ArgumentOutOfRangeException(nameof(level)); + } + + // TODO: Possibly provide DeflateStrategy as an option. + this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default); + + this.SetLevel(level); + this.Reset(); + } + + /// + /// Compression Level as an enum for safer use + /// + public enum CompressionLevel { /// /// The best and slowest compression level. This tries to find very /// long and distant string repetitions. /// - public const int BestCompression = 9; + BestCompression = Deflater.BestCompression, /// /// The worst but fastest compression level. /// - public const int BestSpeed = 1; + BestSpeed = Deflater.BestSpeed, /// /// The default compression level. /// - public const int DefaultCompression = -1; + DefaultCompression = Deflater.DefaultCompression, /// /// This level won't compress at all but output uncompressed blocks. /// - public const int NoCompression = 0; + NoCompression = Deflater.NoCompression, /// /// The compression method. This is the only method supported so far. /// There is no need to use this constant at all. /// - public const int Deflated = 8; - - /// - /// Compression level. - /// - private int level; - - /// - /// The current state. - /// - private int state; + Deflated = Deflater.Deflated + } - private DeflaterEngine engine; - private bool isDisposed; + /// + /// Gets a value indicating whetherthe stream was finished and no more output bytes + /// are available. + /// + public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed; - private const int IsFlushing = 0x04; - private const int IsFinishing = 0x08; - private const int BusyState = 0x10; - private const int FlushingState = 0x14; - private const int FinishingState = 0x1c; - private const int FinishedState = 0x1e; - private const int ClosedState = 0x7f; + /// + /// Gets a value indicating whether the input buffer is empty. + /// You should then call setInput(). + /// NOTE: This method can also return true when the stream + /// was finished. + /// + public bool IsNeedingInput => this.engine.NeedsInput(); - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The compression level, a value between NoCompression and BestCompression. - /// - /// if level is out of range. - public Deflater(MemoryAllocator memoryAllocator, int level) - { - if (level == DefaultCompression) - { - level = 6; - } - else if (level < NoCompression || level > BestCompression) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } + /// + /// Resets the deflater. The deflater acts afterwards as if it was + /// just created with the same compression level and strategy as it + /// had before. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.state = BusyState; + this.engine.Pending.Reset(); + this.engine.Reset(); + } - // TODO: Possibly provide DeflateStrategy as an option. - this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default); + /// + /// Flushes the current input block. Further calls to Deflate() will + /// produce enough output to inflate everything in the current input + /// block. It is used by DeflaterOutputStream to implement Flush(). + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Flush() => this.state |= IsFlushing; - this.SetLevel(level); - this.Reset(); - } + /// + /// Finishes the deflater with the current input block. It is an error + /// to give more input after this method was called. This method must + /// be called to force all bytes to be flushed. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Finish() => this.state |= IsFlushing | IsFinishing; - /// - /// Compression Level as an enum for safer use - /// - public enum CompressionLevel + /// + /// Sets the data which should be compressed next. This should be + /// only called when needsInput indicates that more input is needed. + /// The given byte array should not be changed, before needsInput() returns + /// true again. + /// + /// The buffer containing the input data. + /// The start of the data. + /// The number of data bytes of input. + /// + /// if the buffer was finished or if previous input is still pending. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void SetInput(byte[] input, int offset, int count) + { + if ((this.state & IsFinishing) != 0) { - /// - /// The best and slowest compression level. This tries to find very - /// long and distant string repetitions. - /// - BestCompression = Deflater.BestCompression, - - /// - /// The worst but fastest compression level. - /// - BestSpeed = Deflater.BestSpeed, - - /// - /// The default compression level. - /// - DefaultCompression = Deflater.DefaultCompression, - - /// - /// This level won't compress at all but output uncompressed blocks. - /// - NoCompression = Deflater.NoCompression, - - /// - /// The compression method. This is the only method supported so far. - /// There is no need to use this constant at all. - /// - Deflated = Deflater.Deflated + DeflateThrowHelper.ThrowAlreadyFinished(); } - /// - /// Gets a value indicating whetherthe stream was finished and no more output bytes - /// are available. - /// - public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed; - - /// - /// Gets a value indicating whether the input buffer is empty. - /// You should then call setInput(). - /// NOTE: This method can also return true when the stream - /// was finished. - /// - public bool IsNeedingInput => this.engine.NeedsInput(); + this.engine.SetInput(input, offset, count); + } - /// - /// Resets the deflater. The deflater acts afterwards as if it was - /// just created with the same compression level and strategy as it - /// had before. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() + /// + /// Sets the compression level. There is no guarantee of the exact + /// position of the change, but if you call this when needsInput is + /// true the change of compression level will occur somewhere near + /// before the end of the so far given input. + /// + /// + /// the new compression level. + /// + public void SetLevel(int level) + { + if (level == DefaultCompression) { - this.state = BusyState; - this.engine.Pending.Reset(); - this.engine.Reset(); + level = 6; } - - /// - /// Flushes the current input block. Further calls to Deflate() will - /// produce enough output to inflate everything in the current input - /// block. It is used by DeflaterOutputStream to implement Flush(). - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Flush() => this.state |= IsFlushing; - - /// - /// Finishes the deflater with the current input block. It is an error - /// to give more input after this method was called. This method must - /// be called to force all bytes to be flushed. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Finish() => this.state |= IsFlushing | IsFinishing; - - /// - /// Sets the data which should be compressed next. This should be - /// only called when needsInput indicates that more input is needed. - /// The given byte array should not be changed, before needsInput() returns - /// true again. - /// - /// The buffer containing the input data. - /// The start of the data. - /// The number of data bytes of input. - /// - /// if the buffer was finished or if previous input is still pending. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void SetInput(byte[] input, int offset, int count) + else if (level < NoCompression || level > BestCompression) { - if ((this.state & IsFinishing) != 0) - { - DeflateThrowHelper.ThrowAlreadyFinished(); - } - - this.engine.SetInput(input, offset, count); + throw new ArgumentOutOfRangeException(nameof(level)); } - /// - /// Sets the compression level. There is no guarantee of the exact - /// position of the change, but if you call this when needsInput is - /// true the change of compression level will occur somewhere near - /// before the end of the so far given input. - /// - /// - /// the new compression level. - /// - public void SetLevel(int level) + if (this.level != level) { - if (level == DefaultCompression) - { - level = 6; - } - else if (level < NoCompression || level > BestCompression) - { - throw new ArgumentOutOfRangeException(nameof(level)); - } + this.level = level; + this.engine.SetLevel(level); + } + } - if (this.level != level) - { - this.level = level; - this.engine.SetLevel(level); - } + /// + /// Deflates the current input block to the given array. + /// + /// Buffer to store the compressed data. + /// Offset into the output array. + /// The maximum number of bytes that may be stored. + /// + /// The number of compressed bytes added to the output, or 0 if either + /// or returns true or length is zero. + /// + public int Deflate(Span output, int offset, int length) + { + int origLength = length; + + if (this.state == ClosedState) + { + DeflateThrowHelper.ThrowAlreadyClosed(); } - /// - /// Deflates the current input block to the given array. - /// - /// Buffer to store the compressed data. - /// Offset into the output array. - /// The maximum number of bytes that may be stored. - /// - /// The number of compressed bytes added to the output, or 0 if either - /// or returns true or length is zero. - /// - public int Deflate(Span output, int offset, int length) + while (true) { - int origLength = length; + int count = this.engine.Pending.Flush(output, offset, length); + offset += count; + length -= count; - if (this.state == ClosedState) + if (length == 0 || this.state == FinishedState) { - DeflateThrowHelper.ThrowAlreadyClosed(); + break; } - while (true) + if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0)) { - int count = this.engine.Pending.Flush(output, offset, length); - offset += count; - length -= count; - - if (length == 0 || this.state == FinishedState) - { - break; - } - - if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0)) + switch (this.state) { - switch (this.state) - { - case BusyState: - // We need more input now - return origLength - length; - - case FlushingState: - if (this.level != NoCompression) + case BusyState: + // We need more input now + return origLength - length; + + case FlushingState: + if (this.level != NoCompression) + { + // We have to supply some lookahead. 8 bit lookahead + // is needed by the zlib inflater, and we must fill + // the next byte, so that all bits are flushed. + int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7); + while (neededbits > 0) { - // We have to supply some lookahead. 8 bit lookahead - // is needed by the zlib inflater, and we must fill - // the next byte, so that all bits are flushed. - int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7); - while (neededbits > 0) - { - // Write a static tree block consisting solely of an EOF: - this.engine.Pending.WriteBits(2, 10); - neededbits -= 10; - } + // Write a static tree block consisting solely of an EOF: + this.engine.Pending.WriteBits(2, 10); + neededbits -= 10; } + } - this.state = BusyState; - break; + this.state = BusyState; + break; - case FinishingState: - this.engine.Pending.AlignToByte(); - this.state = FinishedState; - break; - } + case FinishingState: + this.engine.Pending.AlignToByte(); + this.state = FinishedState; + break; } } - - return origLength - length; } - /// - public void Dispose() + return origLength - length; + } + + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.engine.Dispose(); - this.engine = null; - this.isDisposed = true; - } + this.engine.Dispose(); + this.engine = null; + this.isDisposed = true; } } } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs index 507952fd36..b623149470 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs @@ -4,146 +4,145 @@ // using System; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// This class contains constants used for deflation. +/// +internal static class DeflaterConstants { /// - /// This class contains constants used for deflation. - /// - internal static class DeflaterConstants - { - /// - /// Set to true to enable debugging - /// - public const bool DEBUGGING = false; - - /// - /// Written to Zip file to identify a stored block - /// - public const int STORED_BLOCK = 0; - - /// - /// Identifies static tree in Zip file - /// - public const int STATIC_TREES = 1; - - /// - /// Identifies dynamic tree in Zip file - /// - public const int DYN_TREES = 2; - - /// - /// Header flag indicating a preset dictionary for deflation - /// - public const int PRESET_DICT = 0x20; - - /// - /// Sets internal buffer sizes for Huffman encoding - /// - public const int DEFAULT_MEM_LEVEL = 8; - - /// - /// Internal compression engine constant - /// - public const int MAX_MATCH = 258; - - /// - /// Internal compression engine constant - /// - public const int MIN_MATCH = 3; - - /// - /// Internal compression engine constant - /// - public const int MAX_WBITS = 15; - - /// - /// Internal compression engine constant - /// - public const int WSIZE = 1 << MAX_WBITS; - - /// - /// Internal compression engine constant - /// - public const int WMASK = WSIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; - - /// - /// Internal compression engine constant - /// - public const int HASH_SIZE = 1 << HASH_BITS; - - /// - /// Internal compression engine constant - /// - public const int HASH_MASK = HASH_SIZE - 1; - - /// - /// Internal compression engine constant - /// - public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; - - /// - /// Internal compression engine constant - /// - public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; - - /// - /// Internal compression engine constant - /// - public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; - - /// - /// Internal compression engine constant - /// - public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); - - /// - /// Internal compression engine constant - /// - public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_STORED = 0; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_FAST = 1; - - /// - /// Internal compression engine constant - /// - public const int DEFLATE_SLOW = 2; - - /// - /// Internal compression engine constant - /// - public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; - - /// - /// Internal compression engine constant - /// - public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; - - /// - /// Internal compression engine constant - /// - public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; - - /// - /// Internal compression engine constant - /// - public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; - } + /// Set to true to enable debugging + /// + public const bool DEBUGGING = false; + + /// + /// Written to Zip file to identify a stored block + /// + public const int STORED_BLOCK = 0; + + /// + /// Identifies static tree in Zip file + /// + public const int STATIC_TREES = 1; + + /// + /// Identifies dynamic tree in Zip file + /// + public const int DYN_TREES = 2; + + /// + /// Header flag indicating a preset dictionary for deflation + /// + public const int PRESET_DICT = 0x20; + + /// + /// Sets internal buffer sizes for Huffman encoding + /// + public const int DEFAULT_MEM_LEVEL = 8; + + /// + /// Internal compression engine constant + /// + public const int MAX_MATCH = 258; + + /// + /// Internal compression engine constant + /// + public const int MIN_MATCH = 3; + + /// + /// Internal compression engine constant + /// + public const int MAX_WBITS = 15; + + /// + /// Internal compression engine constant + /// + public const int WSIZE = 1 << MAX_WBITS; + + /// + /// Internal compression engine constant + /// + public const int WMASK = WSIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7; + + /// + /// Internal compression engine constant + /// + public const int HASH_SIZE = 1 << HASH_BITS; + + /// + /// Internal compression engine constant + /// + public const int HASH_MASK = HASH_SIZE - 1; + + /// + /// Internal compression engine constant + /// + public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH; + + /// + /// Internal compression engine constant + /// + public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + + /// + /// Internal compression engine constant + /// + public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD; + + /// + /// Internal compression engine constant + /// + public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8); + + /// + /// Internal compression engine constant + /// + public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5); + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_STORED = 0; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_FAST = 1; + + /// + /// Internal compression engine constant + /// + public const int DEFLATE_SLOW = 2; + + /// + /// Internal compression engine constant + /// + public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 }; + + /// + /// Internal compression engine constant + /// + public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 }; + + /// + /// Internal compression engine constant + /// + public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 }; } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs index 2d19aabb57..2fc1d2743e 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs @@ -1,871 +1,869 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Strategies for deflater +/// +internal enum DeflateStrategy +{ + /// + /// The default strategy + /// + Default = 0, + + /// + /// This strategy will only allow longer string repetitions. It is + /// useful for random data with a small character set. + /// + Filtered = 1, + + /// + /// This strategy will not look for string repetitions at all. It + /// only encodes with Huffman trees (which means, that more common + /// characters get a smaller encoding. + /// + HuffmanOnly = 2 +} + +// DEFLATE ALGORITHM: +// +// The uncompressed stream is inserted into the window array. When +// the window array is full the first half is thrown away and the +// second half is copied to the beginning. +// +// The head array is a hash table. Three characters build a hash value +// and they the value points to the corresponding index in window of +// the last string with this hash. The prev array implements a +// linked list of matches with the same hash: prev[index & WMASK] points +// to the previous index with the same hash. +// + +/// +/// Low level compression engine for deflate algorithm which uses a 32K sliding window +/// with secondary compression from Huffman/Shannon-Fano codes. +/// +internal sealed unsafe class DeflaterEngine : IDisposable { + private const int TooFar = 4096; + + // Hash index of string to be inserted + private int insertHashIndex; + + private int matchStart; + + // Length of best match + private int matchLen; + + // Set if previous match exists + private bool prevAvailable; + + private int blockStart; + /// - /// Strategies for deflater + /// Points to the current character in the window. /// - internal enum DeflateStrategy + private int strstart; + + /// + /// lookahead is the number of characters starting at strstart in + /// window that are valid. + /// So window[strstart] until window[strstart+lookahead-1] are valid + /// characters. + /// + private int lookahead; + + /// + /// The current compression function. + /// + private int compressionFunction; + + /// + /// The input data for compression. + /// + private byte[] inputBuf; + + /// + /// The offset into inputBuf, where input data starts. + /// + private int inputOff; + + /// + /// The end offset of the input data. + /// + private int inputEnd; + + private readonly DeflateStrategy strategy; + private DeflaterHuffman huffman; + private bool isDisposed; + + /// + /// Hashtable, hashing three characters to an index for window, so + /// that window[index]..window[index+2] have this hash code. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. + /// + private IMemoryOwner headMemoryOwner; + private MemoryHandle headMemoryHandle; + private readonly Memory head; + private readonly short* pinnedHeadPointer; + + /// + /// prev[index & WMASK] points to the previous index that has the + /// same hash code as the string starting at index. This way + /// entries with the same hash code are in a linked list. + /// Note that the array should really be unsigned short, so you need + /// to and the values with 0xFFFF. + /// + private IMemoryOwner prevMemoryOwner; + private MemoryHandle prevMemoryHandle; + private readonly Memory prev; + private readonly short* pinnedPrevPointer; + + /// + /// This array contains the part of the uncompressed stream that + /// is of relevance. The current character is indexed by strstart. + /// + private IMemoryOwner windowMemoryOwner; + private MemoryHandle windowMemoryHandle; + private readonly Memory window; + private readonly byte* pinnedWindowPointer; + + private int maxChain; + private int maxLazy; + private int niceLength; + private int goodLength; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The deflate strategy to use. + public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy) { - /// - /// The default strategy - /// - Default = 0, - - /// - /// This strategy will only allow longer string repetitions. It is - /// useful for random data with a small character set. - /// - Filtered = 1, - - /// - /// This strategy will not look for string repetitions at all. It - /// only encodes with Huffman trees (which means, that more common - /// characters get a smaller encoding. - /// - HuffmanOnly = 2 + this.huffman = new DeflaterHuffman(memoryAllocator); + this.Pending = this.huffman.Pending; + this.strategy = strategy; + + // Create pinned pointers to the various buffers to allow indexing + // without bounds checks. + this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE); + this.window = this.windowMemoryOwner.Memory; + this.windowMemoryHandle = this.window.Pin(); + this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; + + this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); + this.head = this.headMemoryOwner.Memory; + this.headMemoryHandle = this.head.Pin(); + this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; + + this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); + this.prev = this.prevMemoryOwner.Memory; + this.prevMemoryHandle = this.prev.Pin(); + this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; + + // We start at index 1, to avoid an implementation deficiency, that + // we cannot build a repeat pattern at index 0. + this.blockStart = this.strstart = 1; } - // DEFLATE ALGORITHM: - // - // The uncompressed stream is inserted into the window array. When - // the window array is full the first half is thrown away and the - // second half is copied to the beginning. - // - // The head array is a hash table. Three characters build a hash value - // and they the value points to the corresponding index in window of - // the last string with this hash. The prev array implements a - // linked list of matches with the same hash: prev[index & WMASK] points - // to the previous index with the same hash. - // + /// + /// Gets the pending buffer to use. + /// + public DeflaterPendingBuffer Pending { get; } /// - /// Low level compression engine for deflate algorithm which uses a 32K sliding window - /// with secondary compression from Huffman/Shannon-Fano codes. + /// Deflate drives actual compression of data /// - internal sealed unsafe class DeflaterEngine : IDisposable + /// True to flush input buffers + /// Finish deflation with the current input. + /// Returns true if progress has been made. + public bool Deflate(bool flush, bool finish) { - private const int TooFar = 4096; - - // Hash index of string to be inserted - private int insertHashIndex; - - private int matchStart; - - // Length of best match - private int matchLen; - - // Set if previous match exists - private bool prevAvailable; - - private int blockStart; - - /// - /// Points to the current character in the window. - /// - private int strstart; - - /// - /// lookahead is the number of characters starting at strstart in - /// window that are valid. - /// So window[strstart] until window[strstart+lookahead-1] are valid - /// characters. - /// - private int lookahead; - - /// - /// The current compression function. - /// - private int compressionFunction; - - /// - /// The input data for compression. - /// - private byte[] inputBuf; - - /// - /// The offset into inputBuf, where input data starts. - /// - private int inputOff; - - /// - /// The end offset of the input data. - /// - private int inputEnd; - - private readonly DeflateStrategy strategy; - private DeflaterHuffman huffman; - private bool isDisposed; - - /// - /// Hashtable, hashing three characters to an index for window, so - /// that window[index]..window[index+2] have this hash code. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private IMemoryOwner headMemoryOwner; - private MemoryHandle headMemoryHandle; - private readonly Memory head; - private readonly short* pinnedHeadPointer; - - /// - /// prev[index & WMASK] points to the previous index that has the - /// same hash code as the string starting at index. This way - /// entries with the same hash code are in a linked list. - /// Note that the array should really be unsigned short, so you need - /// to and the values with 0xFFFF. - /// - private IMemoryOwner prevMemoryOwner; - private MemoryHandle prevMemoryHandle; - private readonly Memory prev; - private readonly short* pinnedPrevPointer; - - /// - /// This array contains the part of the uncompressed stream that - /// is of relevance. The current character is indexed by strstart. - /// - private IMemoryOwner windowMemoryOwner; - private MemoryHandle windowMemoryHandle; - private readonly Memory window; - private readonly byte* pinnedWindowPointer; - - private int maxChain; - private int maxLazy; - private int niceLength; - private int goodLength; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The deflate strategy to use. - public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy) + bool progress = false; + do { - this.huffman = new DeflaterHuffman(memoryAllocator); - this.Pending = this.huffman.Pending; - this.strategy = strategy; - - // Create pinned pointers to the various buffers to allow indexing - // without bounds checks. - this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE); - this.window = this.windowMemoryOwner.Memory; - this.windowMemoryHandle = this.window.Pin(); - this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; - - this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE); - this.head = this.headMemoryOwner.Memory; - this.headMemoryHandle = this.head.Pin(); - this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; - - this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE); - this.prev = this.prevMemoryOwner.Memory; - this.prevMemoryHandle = this.prev.Pin(); - this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; - - // We start at index 1, to avoid an implementation deficiency, that - // we cannot build a repeat pattern at index 0. - this.blockStart = this.strstart = 1; - } + this.FillWindow(); + bool canFlush = flush && (this.inputOff == this.inputEnd); - /// - /// Gets the pending buffer to use. - /// - public DeflaterPendingBuffer Pending { get; } - - /// - /// Deflate drives actual compression of data - /// - /// True to flush input buffers - /// Finish deflation with the current input. - /// Returns true if progress has been made. - public bool Deflate(bool flush, bool finish) - { - bool progress = false; - do + switch (this.compressionFunction) { - this.FillWindow(); - bool canFlush = flush && (this.inputOff == this.inputEnd); + case DeflaterConstants.DEFLATE_STORED: + progress = this.DeflateStored(canFlush, finish); + break; - switch (this.compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - progress = this.DeflateStored(canFlush, finish); - break; + case DeflaterConstants.DEFLATE_FAST: + progress = this.DeflateFast(canFlush, finish); + break; - case DeflaterConstants.DEFLATE_FAST: - progress = this.DeflateFast(canFlush, finish); - break; + case DeflaterConstants.DEFLATE_SLOW: + progress = this.DeflateSlow(canFlush, finish); + break; - case DeflaterConstants.DEFLATE_SLOW: - progress = this.DeflateSlow(canFlush, finish); - break; - - default: - DeflateThrowHelper.ThrowUnknownCompression(); - break; - } + default: + DeflateThrowHelper.ThrowUnknownCompression(); + break; } - while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made - return progress; } + while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made + return progress; + } - /// - /// Sets input data to be deflated. Should only be called when - /// returns true - /// - /// The buffer containing input data. - /// The offset of the first byte of data. - /// The number of bytes of data to use as input. - public void SetInput(byte[] buffer, int offset, int count) + /// + /// Sets input data to be deflated. Should only be called when + /// returns true + /// + /// The buffer containing input data. + /// The offset of the first byte of data. + /// The number of bytes of data to use as input. + public void SetInput(byte[] buffer, int offset, int count) + { + if (buffer is null) { - if (buffer is null) - { - DeflateThrowHelper.ThrowNull(nameof(buffer)); - } - - if (offset < 0) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(offset)); - } + DeflateThrowHelper.ThrowNull(nameof(buffer)); + } - if (count < 0) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(count)); - } + if (offset < 0) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(offset)); + } - if (this.inputOff < this.inputEnd) - { - DeflateThrowHelper.ThrowNotProcessed(); - } + if (count < 0) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(count)); + } - int end = offset + count; + if (this.inputOff < this.inputEnd) + { + DeflateThrowHelper.ThrowNotProcessed(); + } - // We want to throw an ArgumentOutOfRangeException early. - // The check is very tricky: it also handles integer wrap around. - if ((offset > end) || (end > buffer.Length)) - { - DeflateThrowHelper.ThrowOutOfRange(nameof(count)); - } + int end = offset + count; - this.inputBuf = buffer; - this.inputOff = offset; - this.inputEnd = end; + // We want to throw an ArgumentOutOfRangeException early. + // The check is very tricky: it also handles integer wrap around. + if ((offset > end) || (end > buffer.Length)) + { + DeflateThrowHelper.ThrowOutOfRange(nameof(count)); } - /// - /// Determines if more input is needed. - /// - /// Return true if input is needed via SetInput - [MethodImpl(InliningOptions.ShortMethod)] - public bool NeedsInput() => this.inputEnd == this.inputOff; - - /// - /// Reset internal state - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() + this.inputBuf = buffer; + this.inputOff = offset; + this.inputEnd = end; + } + + /// + /// Determines if more input is needed. + /// + /// Return true if input is needed via SetInput + [MethodImpl(InliningOptions.ShortMethod)] + public bool NeedsInput() => this.inputEnd == this.inputOff; + + /// + /// Reset internal state + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.huffman.Reset(); + this.blockStart = this.strstart = 1; + this.lookahead = 0; + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + this.head.Span[..DeflaterConstants.HASH_SIZE].Clear(); + this.prev.Span[..DeflaterConstants.WSIZE].Clear(); + } + + /// + /// Set the deflate level (0-9) + /// + /// The value to set the level to. + public void SetLevel(int level) + { + if (level is < 0 or > 9) { - this.huffman.Reset(); - this.blockStart = this.strstart = 1; - this.lookahead = 0; - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - this.head.Span[..DeflaterConstants.HASH_SIZE].Clear(); - this.prev.Span[..DeflaterConstants.WSIZE].Clear(); + DeflateThrowHelper.ThrowOutOfRange(nameof(level)); } - /// - /// Set the deflate level (0-9) - /// - /// The value to set the level to. - public void SetLevel(int level) + this.goodLength = DeflaterConstants.GOOD_LENGTH[level]; + this.maxLazy = DeflaterConstants.MAX_LAZY[level]; + this.niceLength = DeflaterConstants.NICE_LENGTH[level]; + this.maxChain = DeflaterConstants.MAX_CHAIN[level]; + + if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction) { - if (level is < 0 or > 9) + switch (this.compressionFunction) { - DeflateThrowHelper.ThrowOutOfRange(nameof(level)); - } - - this.goodLength = DeflaterConstants.GOOD_LENGTH[level]; - this.maxLazy = DeflaterConstants.MAX_LAZY[level]; - this.niceLength = DeflaterConstants.NICE_LENGTH[level]; - this.maxChain = DeflaterConstants.MAX_CHAIN[level]; + case DeflaterConstants.DEFLATE_STORED: + if (this.strstart > this.blockStart) + { + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } - if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction) - { - switch (this.compressionFunction) - { - case DeflaterConstants.DEFLATE_STORED: - if (this.strstart > this.blockStart) - { - this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } + this.UpdateHash(); + break; - this.UpdateHash(); - break; + case DeflaterConstants.DEFLATE_FAST: + if (this.strstart > this.blockStart) + { + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } - case DeflaterConstants.DEFLATE_FAST: - if (this.strstart > this.blockStart) - { - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } + break; - break; + case DeflaterConstants.DEFLATE_SLOW: + if (this.prevAvailable) + { + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF); + } - case DeflaterConstants.DEFLATE_SLOW: - if (this.prevAvailable) - { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF); - } + if (this.strstart > this.blockStart) + { + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); + this.blockStart = this.strstart; + } - if (this.strstart > this.blockStart) - { - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false); - this.blockStart = this.strstart; - } + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + break; + } - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - break; - } + this.compressionFunction = DeflaterConstants.COMPR_FUNC[level]; + } + } - this.compressionFunction = DeflaterConstants.COMPR_FUNC[level]; - } + /// + /// Fill the window + /// + public void FillWindow() + { + // If the window is almost full and there is insufficient lookahead, + // move the upper half to the lower one to make room in the upper half. + if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) + { + this.SlideWindow(); } - /// - /// Fill the window - /// - public void FillWindow() + // If there is not enough lookahead, but still some input left, read in the input. + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd) { - // If the window is almost full and there is insufficient lookahead, - // move the upper half to the lower one to make room in the upper half. - if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST) - { - this.SlideWindow(); - } + int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart; - // If there is not enough lookahead, but still some input left, read in the input. - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd) + if (more > this.inputEnd - this.inputOff) { - int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart; - - if (more > this.inputEnd - this.inputOff) - { - more = this.inputEnd - this.inputOff; - } + more = this.inputEnd - this.inputOff; + } - Unsafe.CopyBlockUnaligned( - ref this.window.Span[this.strstart + this.lookahead], - ref this.inputBuf[this.inputOff], - unchecked((uint)more)); + Unsafe.CopyBlockUnaligned( + ref this.window.Span[this.strstart + this.lookahead], + ref this.inputBuf[this.inputOff], + unchecked((uint)more)); - this.inputOff += more; - this.lookahead += more; - } + this.inputOff += more; + this.lookahead += more; + } - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - this.UpdateHash(); - } + if (this.lookahead >= DeflaterConstants.MIN_MATCH) + { + this.UpdateHash(); } + } - /// - public void Dispose() + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.huffman.Dispose(); + this.huffman.Dispose(); - this.windowMemoryHandle.Dispose(); - this.windowMemoryOwner.Dispose(); + this.windowMemoryHandle.Dispose(); + this.windowMemoryOwner.Dispose(); - this.headMemoryHandle.Dispose(); - this.headMemoryOwner.Dispose(); + this.headMemoryHandle.Dispose(); + this.headMemoryOwner.Dispose(); - this.prevMemoryHandle.Dispose(); - this.prevMemoryOwner.Dispose(); + this.prevMemoryHandle.Dispose(); + this.prevMemoryOwner.Dispose(); - this.windowMemoryOwner = null; - this.headMemoryOwner = null; - this.prevMemoryOwner = null; - this.huffman = null; + this.windowMemoryOwner = null; + this.headMemoryOwner = null; + this.prevMemoryOwner = null; + this.huffman = null; - this.isDisposed = true; - } + this.isDisposed = true; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private void UpdateHash() - { - byte* pinned = this.pinnedWindowPointer; - this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1]; - } + [MethodImpl(InliningOptions.ShortMethod)] + private void UpdateHash() + { + byte* pinned = this.pinnedWindowPointer; + this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1]; + } - /// - /// Inserts the current string in the head hash and returns the previous - /// value for this hash. - /// - /// The previous hash value - [MethodImpl(InliningOptions.ShortMethod)] - private int InsertString() + /// + /// Inserts the current string in the head hash and returns the previous + /// value for this hash. + /// + /// The previous hash value + [MethodImpl(InliningOptions.ShortMethod)] + private int InsertString() + { + short match; + int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; + + short* pinnedHead = this.pinnedHeadPointer; + this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash]; + pinnedHead[hash] = unchecked((short)this.strstart); + this.insertHashIndex = hash; + return match & 0xFFFF; + } + + private void SlideWindow() + { + Unsafe.CopyBlockUnaligned( + ref this.window.Span[0], + ref this.window.Span[DeflaterConstants.WSIZE], + DeflaterConstants.WSIZE); + + this.matchStart -= DeflaterConstants.WSIZE; + this.strstart -= DeflaterConstants.WSIZE; + this.blockStart -= DeflaterConstants.WSIZE; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). + short* pinnedHead = this.pinnedHeadPointer; + for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) { - short match; - int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK; - - short* pinnedHead = this.pinnedHeadPointer; - this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash]; - pinnedHead[hash] = unchecked((short)this.strstart); - this.insertHashIndex = hash; - return match & 0xFFFF; + int m = pinnedHead[i] & 0xFFFF; + pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); } - private void SlideWindow() + // Slide the prev table. + short* pinnedPrev = this.pinnedPrevPointer; + for (int i = 0; i < DeflaterConstants.WSIZE; i++) { - Unsafe.CopyBlockUnaligned( - ref this.window.Span[0], - ref this.window.Span[DeflaterConstants.WSIZE], - DeflaterConstants.WSIZE); - - this.matchStart -= DeflaterConstants.WSIZE; - this.strstart -= DeflaterConstants.WSIZE; - this.blockStart -= DeflaterConstants.WSIZE; - - // Slide the hash table (could be avoided with 32 bit values - // at the expense of memory usage). - short* pinnedHead = this.pinnedHeadPointer; - for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) - { - int m = pinnedHead[i] & 0xFFFF; - pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } - - // Slide the prev table. - short* pinnedPrev = this.pinnedPrevPointer; - for (int i = 0; i < DeflaterConstants.WSIZE; i++) - { - int m = pinnedPrev[i] & 0xFFFF; - pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); - } + int m = pinnedPrev[i] & 0xFFFF; + pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0); } + } - /// - /// - /// Find the best (longest) string in the window matching the - /// string starting at strstart. - /// - /// - /// Preconditions: - /// - /// strstart + DeflaterConstants.MAX_MATCH <= window.length. - /// - /// - /// The current match. - /// True if a match greater than the minimum length is found - [MethodImpl(InliningOptions.HotPath)] - private bool FindLongestMatch(int curMatch) - { - int match; - int scan = this.strstart; + /// + /// + /// Find the best (longest) string in the window matching the + /// string starting at strstart. + /// + /// + /// Preconditions: + /// + /// strstart + DeflaterConstants.MAX_MATCH <= window.length. + /// + /// + /// The current match. + /// True if a match greater than the minimum length is found + [MethodImpl(InliningOptions.HotPath)] + private bool FindLongestMatch(int curMatch) + { + int match; + int scan = this.strstart; - // scanMax is the highest position that we can look at - int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1; - int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); + // scanMax is the highest position that we can look at + int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1; + int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0); - int chainLength = this.maxChain; - int niceLength = Math.Min(this.niceLength, this.lookahead); + int chainLength = this.maxChain; + int niceLength = Math.Min(this.niceLength, this.lookahead); - int matchStrt = this.matchStart; - int matchLength = this.matchLen; - matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1); - this.matchLen = matchLength; + int matchStrt = this.matchStart; + int matchLength = this.matchLen; + matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1); + this.matchLen = matchLength; - if (scan > scanMax - matchLength) - { - return false; - } + if (scan > scanMax - matchLength) + { + return false; + } - int scanEndPosition = scan + matchLength; + int scanEndPosition = scan + matchLength; - byte* pinnedWindow = this.pinnedWindowPointer; - int scanStart = this.strstart; - byte scanEnd1 = pinnedWindow[scanEndPosition - 1]; - byte scanEnd = pinnedWindow[scanEndPosition]; + byte* pinnedWindow = this.pinnedWindowPointer; + int scanStart = this.strstart; + byte scanEnd1 = pinnedWindow[scanEndPosition - 1]; + byte scanEnd = pinnedWindow[scanEndPosition]; - // Do not waste too much time if we already have a good match: - if (matchLength >= this.goodLength) + // Do not waste too much time if we already have a good match: + if (matchLength >= this.goodLength) + { + chainLength >>= 2; + } + + short* pinnedPrev = this.pinnedPrevPointer; + do + { + match = curMatch; + scan = scanStart; + + int matchEndPosition = match + matchLength; + if (pinnedWindow[matchEndPosition] != scanEnd + || pinnedWindow[matchEndPosition - 1] != scanEnd1 + || pinnedWindow[match] != pinnedWindow[scan] + || pinnedWindow[++match] != pinnedWindow[++scan]) { - chainLength >>= 2; + continue; } - short* pinnedPrev = this.pinnedPrevPointer; - do + // scan is set to strstart+1 and the comparison passed, so + // scanMax - scan is the maximum number of bytes we can compare. + // below we compare 8 bytes at a time, so first we compare + // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 + // n & (8 - 1) == n % 8. + switch ((scanMax - scan) & 7) { - match = curMatch; - scan = scanStart; - - int matchEndPosition = match + matchLength; - if (pinnedWindow[matchEndPosition] != scanEnd - || pinnedWindow[matchEndPosition - 1] != scanEnd1 - || pinnedWindow[match] != pinnedWindow[scan] - || pinnedWindow[++match] != pinnedWindow[++scan]) - { - continue; - } - - // scan is set to strstart+1 and the comparison passed, so - // scanMax - scan is the maximum number of bytes we can compare. - // below we compare 8 bytes at a time, so first we compare - // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8 - // n & (8 - 1) == n % 8. - switch ((scanMax - scan) & 7) - { - case 1: - if (pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } - + case 1: + if (pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 2: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 2: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 3: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 3: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 4: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 4: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 5: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 5: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 6: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 6: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; + } - case 7: - if (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]) - { - break; - } + break; + case 7: + if (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]) + { break; - } + } + + break; + } - if (pinnedWindow[scan] == pinnedWindow[match]) + if (pinnedWindow[scan] == pinnedWindow[match]) + { + // We check for insufficient lookahead only every 8th comparison; + // the 256th check will be made at strstart + 258 unless lookahead is + // exhausted first. + do { - // We check for insufficient lookahead only every 8th comparison; - // the 256th check will be made at strstart + 258 unless lookahead is - // exhausted first. - do + if (scan == scanMax) { - if (scan == scanMax) - { - ++scan; // advance to first position not matched - ++match; + ++scan; // advance to first position not matched + ++match; - break; - } + break; } - while (pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match] - && pinnedWindow[++scan] == pinnedWindow[++match]); } + while (pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match] + && pinnedWindow[++scan] == pinnedWindow[++match]); + } - if (scan - scanStart > matchLength) - { - matchStrt = curMatch; - matchLength = scan - scanStart; - - if (matchLength >= niceLength) - { - break; - } + if (scan - scanStart > matchLength) + { + matchStrt = curMatch; + matchLength = scan - scanStart; - scanEnd1 = pinnedWindow[scan - 1]; - scanEnd = pinnedWindow[scan]; + if (matchLength >= niceLength) + { + break; } - } - while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0); - this.matchStart = matchStrt; - this.matchLen = matchLength; - return matchLength >= DeflaterConstants.MIN_MATCH; + scanEnd1 = pinnedWindow[scan - 1]; + scanEnd = pinnedWindow[scan]; + } } + while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0); - private bool DeflateStored(bool flush, bool finish) + this.matchStart = matchStrt; + this.matchLen = matchLength; + return matchLength >= DeflaterConstants.MIN_MATCH; + } + + private bool DeflateStored(bool flush, bool finish) + { + if (!flush && (this.lookahead == 0)) { - if (!flush && (this.lookahead == 0)) - { - return false; - } + return false; + } - this.strstart += this.lookahead; - this.lookahead = 0; + this.strstart += this.lookahead; + this.lookahead = 0; - int storedLength = this.strstart - this.blockStart; + int storedLength = this.strstart - this.blockStart; - if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full - (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window - flush) + if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full + (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window + flush) + { + bool lastBlock = finish; + if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) { - bool lastBlock = finish; - if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) - { - storedLength = DeflaterConstants.MAX_BLOCK_SIZE; - lastBlock = false; - } - - this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock); - this.blockStart += storedLength; - return !(lastBlock || storedLength == 0); + storedLength = DeflaterConstants.MAX_BLOCK_SIZE; + lastBlock = false; } - return true; + this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock); + this.blockStart += storedLength; + return !(lastBlock || storedLength == 0); } - private bool DeflateFast(bool flush, bool finish) + return true; + } + + private bool DeflateFast(bool flush, bool finish) + { + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) { - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) + return false; + } + + const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; + while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (this.lookahead == 0) { + // We are flushing everything + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; return false; } - const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; - while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + if (this.strstart > windowLen) { - if (this.lookahead == 0) - { - // We are flushing everything - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); - this.blockStart = this.strstart; - return false; - } + // slide window, as FindLongestMatch needs this. + // This should only happen when flushing and the window + // is almost full. + this.SlideWindow(); + } - if (this.strstart > windowLen) - { - // slide window, as FindLongestMatch needs this. - // This should only happen when flushing and the window - // is almost full. - this.SlideWindow(); - } + int hashHead; + if (this.lookahead >= DeflaterConstants.MIN_MATCH && + (hashHead = this.InsertString()) != 0 && + this.strategy != DeflateStrategy.HuffmanOnly && + this.strstart - hashHead <= DeflaterConstants.MAX_DIST && + this.FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen); - int hashHead; - if (this.lookahead >= DeflaterConstants.MIN_MATCH && - (hashHead = this.InsertString()) != 0 && - this.strategy != DeflateStrategy.HuffmanOnly && - this.strstart - hashHead <= DeflaterConstants.MAX_DIST && - this.FindLongestMatch(hashHead)) + this.lookahead -= this.matchLen; + if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH) { - // longestMatch sets matchStart and matchLen - bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen); - - this.lookahead -= this.matchLen; - if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH) + while (--this.matchLen > 0) { - while (--this.matchLen > 0) - { - ++this.strstart; - this.InsertString(); - } - ++this.strstart; - } - else - { - this.strstart += this.matchLen; - if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1) - { - this.UpdateHash(); - } + this.InsertString(); } - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - if (!full) - { - continue; - } + ++this.strstart; } else { - // No match found - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff); - ++this.strstart; - --this.lookahead; + this.strstart += this.matchLen; + if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1) + { + this.UpdateHash(); + } } - if (this.huffman.IsFull()) + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + if (!full) { - bool lastBlock = finish && (this.lookahead == 0); - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock); - this.blockStart = this.strstart; - return !lastBlock; + continue; } } + else + { + // No match found + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff); + ++this.strstart; + --this.lookahead; + } - return true; + if (this.huffman.IsFull()) + { + bool lastBlock = finish && (this.lookahead == 0); + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock); + this.blockStart = this.strstart; + return !lastBlock; + } } - private bool DeflateSlow(bool flush, bool finish) + return true; + } + + private bool DeflateSlow(bool flush, bool finish) + { + if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) { - if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush) - { - return false; - } + return false; + } - const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; - while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD; + while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) + { + if (this.lookahead == 0) { - if (this.lookahead == 0) + if (this.prevAvailable) { - if (this.prevAvailable) - { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); - } + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); + } - this.prevAvailable = false; + this.prevAvailable = false; - // We are flushing everything - this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); - this.blockStart = this.strstart; - return false; - } + // We are flushing everything + this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish); + this.blockStart = this.strstart; + return false; + } - if (this.strstart >= windowLen) - { - // slide window, as FindLongestMatch needs this. - // This should only happen when flushing and the window - // is almost full. - this.SlideWindow(); - } + if (this.strstart >= windowLen) + { + // slide window, as FindLongestMatch needs this. + // This should only happen when flushing and the window + // is almost full. + this.SlideWindow(); + } - int prevMatch = this.matchStart; - int prevLen = this.matchLen; - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - int hashHead = this.InsertString(); + int prevMatch = this.matchStart; + int prevLen = this.matchLen; + if (this.lookahead >= DeflaterConstants.MIN_MATCH) + { + int hashHead = this.InsertString(); - if (this.strategy != DeflateStrategy.HuffmanOnly && - hashHead != 0 && - this.strstart - hashHead <= DeflaterConstants.MAX_DIST && - this.FindLongestMatch(hashHead)) + if (this.strategy != DeflateStrategy.HuffmanOnly && + hashHead != 0 && + this.strstart - hashHead <= DeflaterConstants.MAX_DIST && + this.FindLongestMatch(hashHead)) + { + // longestMatch sets matchStart and matchLen + // Discard match if too small and too far away + if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) { - // longestMatch sets matchStart and matchLen - // Discard match if too small and too far away - if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar))) - { - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - } + this.matchLen = DeflaterConstants.MIN_MATCH - 1; } } + } - // previous match was better - if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) + // previous match was better + if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) + { + this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen); + prevLen -= 2; + do { - this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen); - prevLen -= 2; - do - { - this.strstart++; - this.lookahead--; - if (this.lookahead >= DeflaterConstants.MIN_MATCH) - { - this.InsertString(); - } - } - while (--prevLen > 0); - this.strstart++; this.lookahead--; - this.prevAvailable = false; - this.matchLen = DeflaterConstants.MIN_MATCH - 1; - } - else - { - if (this.prevAvailable) + if (this.lookahead >= DeflaterConstants.MIN_MATCH) { - this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); + this.InsertString(); } - - this.prevAvailable = true; - this.strstart++; - this.lookahead--; } + while (--prevLen > 0); - if (this.huffman.IsFull()) + this.strstart++; + this.lookahead--; + this.prevAvailable = false; + this.matchLen = DeflaterConstants.MIN_MATCH - 1; + } + else + { + if (this.prevAvailable) { - int len = this.strstart - this.blockStart; - if (this.prevAvailable) - { - len--; - } - - bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; - this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock); - this.blockStart += len; - return !lastBlock; + this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff); } + + this.prevAvailable = true; + this.strstart++; + this.lookahead--; } - return true; + if (this.huffman.IsFull()) + { + int len = this.strstart - this.blockStart; + if (this.prevAvailable) + { + len--; + } + + bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; + this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock); + this.blockStart += len; + return !lastBlock; + } } + + return true; } } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs index 6536601129..34058b43a8 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs @@ -1,988 +1,986 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Performs Deflate Huffman encoding. +/// +internal sealed unsafe class DeflaterHuffman : IDisposable { - /// - /// Performs Deflate Huffman encoding. - /// - internal sealed unsafe class DeflaterHuffman : IDisposable - { - private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); + private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6); - // The number of literal codes. - private const int LiteralNumber = 286; + // The number of literal codes. + private const int LiteralNumber = 286; - // Number of distance codes - private const int DistanceNumber = 30; + // Number of distance codes + private const int DistanceNumber = 30; - // Number of codes used to transfer bit lengths - private const int BitLengthNumber = 19; + // Number of codes used to transfer bit lengths + private const int BitLengthNumber = 19; - // Repeat previous bit length 3-6 times (2 bits of repeat count) - private const int Repeat3To6 = 16; + // Repeat previous bit length 3-6 times (2 bits of repeat count) + private const int Repeat3To6 = 16; - // Repeat a zero length 3-10 times (3 bits of repeat count) - private const int Repeat3To10 = 17; + // Repeat a zero length 3-10 times (3 bits of repeat count) + private const int Repeat3To10 = 17; - // Repeat a zero length 11-138 times (7 bits of repeat count) - private const int Repeat11To138 = 18; + // Repeat a zero length 11-138 times (7 bits of repeat count) + private const int Repeat11To138 = 18; - private const int EofSymbol = 256; + private const int EofSymbol = 256; - private Tree literalTree; - private Tree distTree; - private Tree blTree; + private Tree literalTree; + private Tree distTree; + private Tree blTree; - // Buffer for distances - private readonly IMemoryOwner distanceMemoryOwner; - private readonly short* pinnedDistanceBuffer; - private MemoryHandle distanceBufferHandle; + // Buffer for distances + private readonly IMemoryOwner distanceMemoryOwner; + private readonly short* pinnedDistanceBuffer; + private MemoryHandle distanceBufferHandle; - private readonly IMemoryOwner literalMemoryOwner; - private readonly short* pinnedLiteralBuffer; - private MemoryHandle literalBufferHandle; + private readonly IMemoryOwner literalMemoryOwner; + private readonly short* pinnedLiteralBuffer; + private MemoryHandle literalBufferHandle; - private int lastLiteral; - private int extraBits; - private bool isDisposed; + private int lastLiteral; + private int extraBits; + private bool isDisposed; - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - public DeflaterHuffman(MemoryAllocator memoryAllocator) - { - this.Pending = new DeflaterPendingBuffer(memoryAllocator); + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + public DeflaterHuffman(MemoryAllocator memoryAllocator) + { + this.Pending = new DeflaterPendingBuffer(memoryAllocator); - this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15); - this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); - this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); + this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15); + this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); + this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); - this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize); - this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin(); - this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; + this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin(); + this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; - this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize); - this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin(); - this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; - } + this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize); + this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin(); + this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; + } #pragma warning disable SA1201 // Elements should appear in the correct order - // See RFC 1951 3.2.6 - // Literal codes - private static readonly short[] StaticLCodes = new short[] - { - 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, - 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, - 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, - 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, - 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, - 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, - 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, - 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, - 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19, - 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499, - 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491, - 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507, - 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487, - 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503, - 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495, - 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511, - 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, - 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163 - }; - - private static ReadOnlySpan StaticLLength => new byte[] - { - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8 - }; - - // Distance codes and lengths. - private static readonly short[] StaticDCodes = new short[] - { - 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, - 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23 - }; + // See RFC 1951 3.2.6 + // Literal codes + private static readonly short[] StaticLCodes = new short[] + { + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, + 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, + 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, + 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19, + 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499, + 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491, + 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507, + 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487, + 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503, + 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495, + 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511, + 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36, + 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163 + }; + + private static ReadOnlySpan StaticLLength => new byte[] + { + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8 + }; + + // Distance codes and lengths. + private static readonly short[] StaticDCodes = new short[] + { + 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14, + 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23 + }; - private static ReadOnlySpan StaticDLength => new byte[] - { - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 - }; + private static ReadOnlySpan StaticDLength => new byte[] + { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + }; #pragma warning restore SA1201 // Elements should appear in the correct order - /// - /// 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 - }; + /// + /// 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 - }; + 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. - /// - public DeflaterPendingBuffer Pending { get; private set; } + /// + /// Gets the pending buffer to use. + /// + public DeflaterPendingBuffer Pending { get; private set; } - /// - /// Reset internal state - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() + /// + /// Reset internal state + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.lastLiteral = 0; + this.extraBits = 0; + this.literalTree.Reset(); + this.distTree.Reset(); + this.blTree.Reset(); + } + + /// + /// Write all trees to pending buffer + /// + /// The number/rank of treecodes to send. + public void SendAllTrees(int blTreeCodes) + { + this.blTree.BuildCodes(); + this.literalTree.BuildCodes(); + this.distTree.BuildCodes(); + 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.lastLiteral = 0; - this.extraBits = 0; - this.literalTree.Reset(); - this.distTree.Reset(); - this.blTree.Reset(); + this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); } - /// - /// Write all trees to pending buffer - /// - /// The number/rank of treecodes to send. - public void SendAllTrees(int blTreeCodes) - { - this.blTree.BuildCodes(); - this.literalTree.BuildCodes(); - this.distTree.BuildCodes(); - 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); - } + this.literalTree.WriteTree(this.Pending, this.blTree); + this.distTree.WriteTree(this.Pending, this.blTree); + } - this.literalTree.WriteTree(this.Pending, this.blTree); - this.distTree.WriteTree(this.Pending, this.blTree); - } + /// + /// Compress current buffer writing data to pending buffer + /// + public void CompressBlock() + { + DeflaterPendingBuffer pendingBuffer = this.Pending; + short* pinnedDistance = this.pinnedDistanceBuffer; + short* pinnedLiteral = this.pinnedLiteralBuffer; - /// - /// Compress current buffer writing data to pending buffer - /// - public void CompressBlock() + for (int i = 0; i < this.lastLiteral; i++) { - DeflaterPendingBuffer pendingBuffer = this.Pending; - short* pinnedDistance = this.pinnedDistanceBuffer; - short* pinnedLiteral = this.pinnedLiteralBuffer; - - for (int i = 0; i < this.lastLiteral; i++) + int litlen = pinnedLiteral[i] & 0xFF; + int dist = pinnedDistance[i]; + if (dist-- != 0) { - int litlen = pinnedLiteral[i] & 0xFF; - int dist = pinnedDistance[i]; - if (dist-- != 0) - { - int lc = Lcode(litlen); - this.literalTree.WriteSymbol(pendingBuffer, lc); + int lc = Lcode(litlen); + this.literalTree.WriteSymbol(pendingBuffer, lc); - int bits = (lc - 261) / 4; - if (bits > 0 && bits <= 5) - { - this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits); - } + int bits = (lc - 261) / 4; + if (bits > 0 && bits <= 5) + { + this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits); + } - int dc = Dcode(dist); - this.distTree.WriteSymbol(pendingBuffer, dc); + int dc = Dcode(dist); + this.distTree.WriteSymbol(pendingBuffer, dc); - bits = (dc >> 1) - 1; - if (bits > 0) - { - this.Pending.WriteBits(dist & ((1 << bits) - 1), bits); - } - } - else + bits = (dc >> 1) - 1; + if (bits > 0) { - this.literalTree.WriteSymbol(pendingBuffer, litlen); + this.Pending.WriteBits(dist & ((1 << bits) - 1), bits); } } - - this.literalTree.WriteSymbol(pendingBuffer, EofSymbol); + else + { + this.literalTree.WriteSymbol(pendingBuffer, litlen); + } } - /// - /// Flush block to output with no compression - /// - /// Data to write - /// Index of first byte to write - /// Count of bytes to write - /// True if this is the last block - [MethodImpl(InliningOptions.ShortMethod)] - public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) - { - this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); - this.Pending.AlignToByte(); - this.Pending.WriteShort(storedLength); - this.Pending.WriteShort(~storedLength); - this.Pending.WriteBlock(stored, storedOffset, storedLength); - this.Reset(); - } + this.literalTree.WriteSymbol(pendingBuffer, EofSymbol); + } - /// - /// Flush block to output with compression - /// - /// Data to flush - /// Index of first byte to flush - /// Count of bytes to flush - /// True if this is the last block - public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) - { - this.literalTree.Frequencies[EofSymbol]++; + /// + /// Flush block to output with no compression + /// + /// Data to write + /// Index of first byte to write + /// Count of bytes to write + /// True if this is the last block + [MethodImpl(InliningOptions.ShortMethod)] + public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) + { + this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); + this.Pending.AlignToByte(); + this.Pending.WriteShort(storedLength); + this.Pending.WriteShort(~storedLength); + this.Pending.WriteBlock(stored, storedOffset, storedLength); + this.Reset(); + } - // Build trees - this.literalTree.BuildTree(); - this.distTree.BuildTree(); + /// + /// Flush block to output with compression + /// + /// Data to flush + /// Index of first byte to flush + /// Count of bytes to flush + /// True if this is the last block + public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock) + { + this.literalTree.Frequencies[EofSymbol]++; - // Calculate bitlen frequency - this.literalTree.CalcBLFreq(this.blTree); - this.distTree.CalcBLFreq(this.blTree); + // Build trees + this.literalTree.BuildTree(); + this.distTree.BuildTree(); - // Build bitlen tree - this.blTree.BuildTree(); + // Calculate bitlen frequency + this.literalTree.CalcBLFreq(this.blTree); + this.distTree.CalcBLFreq(this.blTree); - int blTreeCodes = 4; + // Build bitlen tree + this.blTree.BuildTree(); - for (int i = 18; i > blTreeCodes; i--) - { - if (this.blTree.Length[BitLengthOrder[i]] > 0) - { - blTreeCodes = i + 1; - } - } + int blTreeCodes = 4; - int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() - + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() - + this.extraBits; - - int static_len = this.extraBits; - ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); - for (int i = 0; i < LiteralNumber; i++) - { - static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); - } - - ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); - for (int i = 0; i < DistanceNumber; i++) + for (int i = 18; i > blTreeCodes; i--) + { + if (this.blTree.Length[BitLengthOrder[i]] > 0) { - static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); + blTreeCodes = i + 1; } + } - if (opt_len >= static_len) - { - // Force static trees - opt_len = static_len; - } + int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength() + + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength() + + this.extraBits; - if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) - { - // Store Block - this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); - } - else if (opt_len == static_len) - { - // Encode with static tree - this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); - this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength); - this.distTree.SetStaticCodes(StaticDCodes, StaticDLength); - this.CompressBlock(); - this.Reset(); - } - else - { - // Encode with dynamic tree - this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); - this.SendAllTrees(blTreeCodes); - this.CompressBlock(); - this.Reset(); - } + int static_len = this.extraBits; + ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength); + for (int i = 0; i < LiteralNumber; i++) + { + static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); } - /// - /// Get value indicating if internal buffer is full - /// - /// true if buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsFull() => this.lastLiteral >= BufferSize; + ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength); + for (int i = 0; i < DistanceNumber; i++) + { + static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); + } - /// - /// Add literal to buffer - /// - /// Literal value to add to buffer. - /// Value indicating internal buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool TallyLit(int literal) + if (opt_len >= static_len) { - this.pinnedDistanceBuffer[this.lastLiteral] = 0; - this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal; - this.literalTree.Frequencies[literal]++; - return this.IsFull(); + // Force static trees + opt_len = static_len; } - /// - /// Add distance code and length to literal and distance trees - /// - /// Distance code - /// Length - /// Value indicating if internal buffer is full - [MethodImpl(InliningOptions.ShortMethod)] - public bool TallyDist(int distance, int length) + if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3) + { + // Store Block + this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + } + else if (opt_len == static_len) { - this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance; - this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3); + // Encode with static tree + this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3); + this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength); + this.distTree.SetStaticCodes(StaticDCodes, StaticDLength); + this.CompressBlock(); + this.Reset(); + } + else + { + // Encode with dynamic tree + this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3); + this.SendAllTrees(blTreeCodes); + this.CompressBlock(); + this.Reset(); + } + } - int lc = Lcode(length - 3); - this.literalTree.Frequencies[lc]++; - if (lc >= 265 && lc < 285) - { - this.extraBits += (lc - 261) / 4; - } + /// + /// Get value indicating if internal buffer is full + /// + /// true if buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsFull() => this.lastLiteral >= BufferSize; - int dc = Dcode(distance - 1); - this.distTree.Frequencies[dc]++; - if (dc >= 4) - { - this.extraBits += (dc >> 1) - 1; - } + /// + /// Add literal to buffer + /// + /// Literal value to add to buffer. + /// Value indicating internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool TallyLit(int literal) + { + this.pinnedDistanceBuffer[this.lastLiteral] = 0; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal; + this.literalTree.Frequencies[literal]++; + return this.IsFull(); + } - return this.IsFull(); - } + /// + /// Add distance code and length to literal and distance trees + /// + /// Distance code + /// Length + /// Value indicating if internal buffer is full + [MethodImpl(InliningOptions.ShortMethod)] + public bool TallyDist(int distance, int length) + { + this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance; + this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3); - /// - /// Reverse the bits of a 16 bit value. - /// - /// Value to reverse bits - /// Value with bits reversed - [MethodImpl(InliningOptions.ShortMethod)] - public static short BitReverse(int toReverse) + int lc = Lcode(length - 3); + this.literalTree.Frequencies[lc]++; + if (lc >= 265 && lc < 285) { - /* 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)); + this.extraBits += (lc - 261) / 4; } - /// - public void Dispose() + int dc = Dcode(distance - 1); + this.distTree.Frequencies[dc]++; + if (dc >= 4) { - if (!this.isDisposed) - { - this.Pending.Dispose(); - this.distanceBufferHandle.Dispose(); - this.distanceMemoryOwner.Dispose(); - this.literalBufferHandle.Dispose(); - this.literalMemoryOwner.Dispose(); - - this.literalTree.Dispose(); - this.blTree.Dispose(); - this.distTree.Dispose(); - - this.Pending = null; - this.literalTree = null; - this.blTree = null; - this.distTree = null; - this.isDisposed = true; - } + this.extraBits += (dc >> 1) - 1; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Lcode(int length) - { - if (length == 255) - { - return 285; - } + return this.IsFull(); + } - int code = 257; - while (length >= 8) - { - code += 4; - length >>= 1; - } + /// + /// Reverse the bits of a 16 bit value. + /// + /// Value to reverse bits + /// Value with bits reversed + [MethodImpl(InliningOptions.ShortMethod)] + public static short BitReverse(int toReverse) + { + /* 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)); + } - return code + length; + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.Pending.Dispose(); + this.distanceBufferHandle.Dispose(); + this.distanceMemoryOwner.Dispose(); + this.literalBufferHandle.Dispose(); + this.literalMemoryOwner.Dispose(); + + this.literalTree.Dispose(); + this.blTree.Dispose(); + this.distTree.Dispose(); + + this.Pending = null; + this.literalTree = null; + this.blTree = null; + this.distTree = null; + this.isDisposed = true; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Dcode(int distance) + [MethodImpl(InliningOptions.ShortMethod)] + private static int Lcode(int length) + { + if (length == 255) { - int code = 0; - while (distance >= 4) - { - code += 2; - distance >>= 1; - } + return 285; + } - return code + distance; + int code = 257; + while (length >= 8) + { + code += 4; + length >>= 1; } - private sealed class Tree : IDisposable + return code + length; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Dcode(int distance) + { + int code = 0; + while (distance >= 4) { - private readonly int minNumCodes; - private readonly int[] bitLengthCounts; - private readonly int maxLength; - private bool isDisposed; + code += 2; + distance >>= 1; + } - private readonly int elementCount; + return code + distance; + } - private readonly MemoryAllocator memoryAllocator; + private sealed class Tree : IDisposable + { + private readonly int minNumCodes; + private readonly int[] bitLengthCounts; + private readonly int maxLength; + private bool isDisposed; - private IMemoryOwner codesMemoryOwner; - private MemoryHandle codesMemoryHandle; - private readonly short* codes; + private readonly int elementCount; - private IMemoryOwner frequenciesMemoryOwner; - private MemoryHandle frequenciesMemoryHandle; + private readonly MemoryAllocator memoryAllocator; - private IMemoryOwner lengthsMemoryOwner; - private MemoryHandle lengthsMemoryHandle; + private IMemoryOwner codesMemoryOwner; + private MemoryHandle codesMemoryHandle; + private readonly short* codes; - public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) - { - this.memoryAllocator = memoryAllocator; - this.elementCount = elements; - this.minNumCodes = minCodes; - this.maxLength = maxLength; + private IMemoryOwner frequenciesMemoryOwner; + private MemoryHandle frequenciesMemoryHandle; - this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements); - this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); - this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; + private IMemoryOwner lengthsMemoryOwner; + private MemoryHandle lengthsMemoryHandle; - this.lengthsMemoryOwner = memoryAllocator.Allocate(elements); - this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); - this.Length = (byte*)this.lengthsMemoryHandle.Pointer; + public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) + { + this.memoryAllocator = memoryAllocator; + this.elementCount = elements; + this.minNumCodes = minCodes; + this.maxLength = maxLength; - this.codesMemoryOwner = memoryAllocator.Allocate(elements); - this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin(); - this.codes = (short*)this.codesMemoryHandle.Pointer; + this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements); + this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); + this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; - // Maxes out at 15. - this.bitLengthCounts = new int[maxLength]; - } + this.lengthsMemoryOwner = memoryAllocator.Allocate(elements); + this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); + this.Length = (byte*)this.lengthsMemoryHandle.Pointer; - public int NumCodes { get; private set; } + this.codesMemoryOwner = memoryAllocator.Allocate(elements); + this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin(); + this.codes = (short*)this.codesMemoryHandle.Pointer; - public short* Frequencies { get; } + // Maxes out at 15. + this.bitLengthCounts = new int[maxLength]; + } - public byte* Length { get; } + public int NumCodes { get; private set; } - /// - /// Resets the internal state of the tree - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() - { - this.frequenciesMemoryOwner.Memory.Span.Clear(); - this.lengthsMemoryOwner.Memory.Span.Clear(); - this.codesMemoryOwner.Memory.Span.Clear(); - } + public short* Frequencies { get; } - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code) - => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]); - - /// - /// Set static codes and length - /// - /// new codes - /// length for new codes - [MethodImpl(InliningOptions.ShortMethod)] - public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths) + public byte* Length { get; } + + /// + /// Resets the internal state of the tree + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.frequenciesMemoryOwner.Memory.Span.Clear(); + this.lengthsMemoryOwner.Memory.Span.Clear(); + this.codesMemoryOwner.Memory.Span.Clear(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code) + => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]); + + /// + /// Set static codes and length + /// + /// new codes + /// length for new codes + [MethodImpl(InliningOptions.ShortMethod)] + public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths) + { + staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span); + staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span); + } + + /// + /// Build dynamic codes and lengths + /// + public void BuildCodes() + { + // Maxes out at 15 * 4 + Span nextCode = stackalloc int[this.maxLength]; + ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode); + ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); + + int code = 0; + for (int bits = 0; bits < this.maxLength; bits++) { - staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span); - staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span); + Unsafe.Add(ref nextCodeRef, bits) = code; + code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits); } - /// - /// Build dynamic codes and lengths - /// - public void BuildCodes() + for (int i = 0; i < this.NumCodes; i++) { - // Maxes out at 15 * 4 - Span nextCode = stackalloc int[this.maxLength]; - ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode); - ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); - - int code = 0; - for (int bits = 0; bits < this.maxLength; bits++) - { - Unsafe.Add(ref nextCodeRef, bits) = code; - code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits); - } - - for (int i = 0; i < this.NumCodes; i++) + int bits = this.Length[i]; + if (bits > 0) { - int bits = this.Length[i]; - if (bits > 0) - { - this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1)); - Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits); - } + this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1)); + Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits); } } + } - [MethodImpl(InliningOptions.HotPath)] - public void BuildTree() + [MethodImpl(InliningOptions.HotPath)] + public void BuildTree() + { + int numSymbols = this.elementCount; + + // heap is a priority queue, sorted by frequency, least frequent + // nodes first. The heap is a binary tree, with the property, that + // the parent node is smaller than both child nodes. This assures + // that the smallest node is the first parent. + // + // The binary tree is encoded in an array: 0 is root node and + // the nodes 2*n+1, 2*n+2 are the child nodes of node n. + // Maxes out at 286 * 4 so too large for the stack. + using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols)) { - int numSymbols = this.elementCount; - - // heap is a priority queue, sorted by frequency, least frequent - // nodes first. The heap is a binary tree, with the property, that - // the parent node is smaller than both child nodes. This assures - // that the smallest node is the first parent. - // - // The binary tree is encoded in an array: 0 is root node and - // the nodes 2*n+1, 2*n+2 are the child nodes of node n. - // Maxes out at 286 * 4 so too large for the stack. - using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols)) - { - ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span); + ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span); - int heapLen = 0; - int maxCode = 0; - for (int n = 0; n < numSymbols; n++) + int heapLen = 0; + int maxCode = 0; + for (int n = 0; n < numSymbols; n++) + { + int freq = this.Frequencies[n]; + if (freq != 0) { - int freq = this.Frequencies[n]; - if (freq != 0) + // Insert n into heap + int pos = heapLen++; + int ppos; + while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq) { - // Insert n into heap - int pos = heapLen++; - int ppos; - while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq) - { - Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); - pos = ppos; - } + Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos); + pos = ppos; + } - Unsafe.Add(ref heapRef, pos) = n; + Unsafe.Add(ref heapRef, pos) = n; - maxCode = n; - } + maxCode = n; } + } + + // We could encode a single literal with 0 bits but then we + // don't see the literals. Therefore we force at least two + // literals to avoid this case. We don't care about order in + // this case, both literals get a 1 bit code. + while (heapLen < 2) + { + Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0; + } + + this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); - // We could encode a single literal with 0 bits but then we - // don't see the literals. Therefore we force at least two - // literals to avoid this case. We don't care about order in - // this case, both literals get a 1 bit code. - while (heapLen < 2) + int numLeafs = heapLen; + int childrenLength = (4 * heapLen) - 2; + using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength)) + using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1)) + { + ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span); + ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span); + int numNodes = numLeafs; + + for (int i = 0; i < heapLen; i++) { - Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0; + int node = Unsafe.Add(ref heapRef, i); + int i2 = 2 * i; + Unsafe.Add(ref childrenRef, i2) = node; + Unsafe.Add(ref childrenRef, i2 + 1) = -1; + Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8; + Unsafe.Add(ref heapRef, i) = i; } - this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes); - - int numLeafs = heapLen; - int childrenLength = (4 * heapLen) - 2; - using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength)) - using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1)) + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + do { - ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span); - ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span); - int numNodes = numLeafs; + int first = Unsafe.Add(ref heapRef, 0); + int last = Unsafe.Add(ref heapRef, --heapLen); - for (int i = 0; i < heapLen; i++) - { - int node = Unsafe.Add(ref heapRef, i); - int i2 = 2 * i; - Unsafe.Add(ref childrenRef, i2) = node; - Unsafe.Add(ref childrenRef, i2 + 1) = -1; - Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8; - Unsafe.Add(ref heapRef, i) = i; - } + // Propagate the hole to the leafs of the heap + int ppos = 0; + int path = 1; - // Construct the Huffman tree by repeatedly combining the least two - // frequent nodes. - do + while (path < heapLen) { - int first = Unsafe.Add(ref heapRef, 0); - int last = Unsafe.Add(ref heapRef, --heapLen); - - // Propagate the hole to the leafs of the heap - int ppos = 0; - int path = 1; - - while (path < heapLen) + if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) { - if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) - { - path++; - } - - Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); - ppos = path; - path = (path * 2) + 1; + path++; } - // Now propagate the last element down along path. Normally - // it shouldn't go too deep. - int lastVal = Unsafe.Add(ref valuesRef, last); - while ((path = ppos) > 0 - && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) - { - Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); - } + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (path * 2) + 1; + } - Unsafe.Add(ref heapRef, path) = last; + // Now propagate the last element down along path. Normally + // it shouldn't go too deep. + int lastVal = Unsafe.Add(ref valuesRef, last); + while ((path = ppos) > 0 + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + { + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + } - int second = Unsafe.Add(ref heapRef, 0); + Unsafe.Add(ref heapRef, path) = last; - // Create a new node father of first and second - last = numNodes++; - Unsafe.Add(ref childrenRef, 2 * last) = first; - Unsafe.Add(ref childrenRef, (2 * last) + 1) = second; - int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF); - Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1; + int second = Unsafe.Add(ref heapRef, 0); - // Again, propagate the hole to the leafs - ppos = 0; - path = 1; + // Create a new node father of first and second + last = numNodes++; + Unsafe.Add(ref childrenRef, 2 * last) = first; + Unsafe.Add(ref childrenRef, (2 * last) + 1) = second; + int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF); + Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1; - while (path < heapLen) - { - if (path + 1 < heapLen - && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) - { - path++; - } - - Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); - ppos = path; - path = (ppos * 2) + 1; - } + // Again, propagate the hole to the leafs + ppos = 0; + path = 1; - // Now propagate the new element down along path - while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) + while (path < heapLen) + { + if (path + 1 < heapLen + && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1))) { - Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); + path++; } - Unsafe.Add(ref heapRef, path) = last; + Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path); + ppos = path; + path = (ppos * 2) + 1; } - while (heapLen > 1); - if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1) + // Now propagate the new element down along path + while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal) { - DeflateThrowHelper.ThrowHeapViolated(); + Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos); } - this.BuildLength(childrenMemoryOwner.Memory.Span); + Unsafe.Add(ref heapRef, path) = last; } + while (heapLen > 1); + + if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1) + { + DeflateThrowHelper.ThrowHeapViolated(); + } + + this.BuildLength(childrenMemoryOwner.Memory.Span); } } + } - /// - /// Get encoded length - /// - /// Encoded length, the sum of frequencies * lengths - [MethodImpl(InliningOptions.ShortMethod)] - public int GetEncodedLength() + /// + /// Get encoded length + /// + /// Encoded length, the sum of frequencies * lengths + [MethodImpl(InliningOptions.ShortMethod)] + public int GetEncodedLength() + { + int len = 0; + for (int i = 0; i < this.elementCount; i++) { - int len = 0; - for (int i = 0; i < this.elementCount; i++) - { - len += this.Frequencies[i] * this.Length[i]; - } - - return len; + len += this.Frequencies[i] * this.Length[i]; } - /// - /// Scan a literal or distance tree to determine the frequencies of the codes - /// in the bit length tree. - /// - public void CalcBLFreq(Tree blTree) - { - int maxCount; // max repeat count - int minCount; // min repeat count - int count; // repeat count of the current code - int curLen = -1; // length of current code + return len; + } + + /// + /// Scan a literal or distance tree to determine the frequencies of the codes + /// in the bit length tree. + /// + public void CalcBLFreq(Tree blTree) + { + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code - int i = 0; - while (i < this.NumCodes) + int i = 0; + while (i < this.NumCodes) + { + count = 1; + int nextlen = this.Length[i]; + if (nextlen == 0) { - count = 1; - int nextlen = this.Length[i]; - if (nextlen == 0) - { - maxCount = 138; - minCount = 3; - } - else + maxCount = 138; + minCount = 3; + } + else + { + maxCount = 6; + minCount = 3; + if (curLen != nextlen) { - maxCount = 6; - minCount = 3; - if (curLen != nextlen) - { - blTree.Frequencies[nextlen]++; - count = 0; - } + blTree.Frequencies[nextlen]++; + count = 0; } + } - curLen = nextlen; - i++; + curLen = nextlen; + i++; - while (i < this.NumCodes && curLen == this.Length[i]) + while (i < this.NumCodes && curLen == this.Length[i]) + { + i++; + if (++count >= maxCount) { - i++; - if (++count >= maxCount) - { - break; - } + break; } + } - if (count < minCount) - { - blTree.Frequencies[curLen] += (short)count; - } - else if (curLen != 0) - { - blTree.Frequencies[Repeat3To6]++; - } - else if (count <= 10) - { - blTree.Frequencies[Repeat3To10]++; - } - else - { - blTree.Frequencies[Repeat11To138]++; - } + if (count < minCount) + { + blTree.Frequencies[curLen] += (short)count; + } + else if (curLen != 0) + { + blTree.Frequencies[Repeat3To6]++; + } + else if (count <= 10) + { + blTree.Frequencies[Repeat3To10]++; + } + else + { + blTree.Frequencies[Repeat11To138]++; } } + } - /// - /// Write the tree values. - /// - /// The pending buffer. - /// The tree to write. - public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) - { - int maxCount; // max repeat count - int minCount; // min repeat count - int count; // repeat count of the current code - int curLen = -1; // length of current code + /// + /// Write the tree values. + /// + /// The pending buffer. + /// The tree to write. + public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree) + { + int maxCount; // max repeat count + int minCount; // min repeat count + int count; // repeat count of the current code + int curLen = -1; // length of current code - int i = 0; - while (i < this.NumCodes) + int i = 0; + while (i < this.NumCodes) + { + count = 1; + int nextlen = this.Length[i]; + if (nextlen == 0) { - count = 1; - int nextlen = this.Length[i]; - if (nextlen == 0) - { - maxCount = 138; - minCount = 3; - } - else + maxCount = 138; + minCount = 3; + } + else + { + maxCount = 6; + minCount = 3; + if (curLen != nextlen) { - maxCount = 6; - minCount = 3; - if (curLen != nextlen) - { - bitLengthTree.WriteSymbol(pendingBuffer, nextlen); - count = 0; - } + bitLengthTree.WriteSymbol(pendingBuffer, nextlen); + count = 0; } + } - curLen = nextlen; - i++; + curLen = nextlen; + i++; - while (i < this.NumCodes && curLen == this.Length[i]) + while (i < this.NumCodes && curLen == this.Length[i]) + { + i++; + if (++count >= maxCount) { - i++; - if (++count >= maxCount) - { - break; - } + break; } + } - if (count < minCount) - { - while (count-- > 0) - { - bitLengthTree.WriteSymbol(pendingBuffer, curLen); - } - } - else if (curLen != 0) - { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); - pendingBuffer.WriteBits(count - 3, 2); - } - else if (count <= 10) - { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10); - pendingBuffer.WriteBits(count - 3, 3); - } - else + if (count < minCount) + { + while (count-- > 0) { - bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138); - pendingBuffer.WriteBits(count - 11, 7); + bitLengthTree.WriteSymbol(pendingBuffer, curLen); } } + else if (curLen != 0) + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6); + pendingBuffer.WriteBits(count - 3, 2); + } + else if (count <= 10) + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10); + pendingBuffer.WriteBits(count - 3, 3); + } + else + { + bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138); + pendingBuffer.WriteBits(count - 11, 7); + } } + } - private void BuildLength(ReadOnlySpan children) - { - byte* lengthPtr = this.Length; - ref int childrenRef = ref MemoryMarshal.GetReference(children); - ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); + private void BuildLength(ReadOnlySpan children) + { + byte* lengthPtr = this.Length; + ref int childrenRef = ref MemoryMarshal.GetReference(children); + ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts); - int maxLen = this.maxLength; - int numNodes = children.Length >> 1; - int numLeafs = (numNodes + 1) >> 1; - int overflow = 0; + int maxLen = this.maxLength; + int numNodes = children.Length >> 1; + int numLeafs = (numNodes + 1) >> 1; + int overflow = 0; - Array.Clear(this.bitLengthCounts, 0, maxLen); + Array.Clear(this.bitLengthCounts, 0, maxLen); - // First calculate optimal bit lengths - using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean)) - { - ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span); + // First calculate optimal bit lengths + using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean)) + { + ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span); - for (int i = numNodes - 1; i >= 0; i--) + for (int i = numNodes - 1; i >= 0; i--) + { + if (children[(2 * i) + 1] != -1) { - if (children[(2 * i) + 1] != -1) + int bitLength = Unsafe.Add(ref lengthsRef, i) + 1; + if (bitLength > maxLen) { - int bitLength = Unsafe.Add(ref lengthsRef, i) + 1; - if (bitLength > maxLen) - { - bitLength = maxLen; - overflow++; - } - - Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength; - } - else - { - // A leaf node - int bitLength = Unsafe.Add(ref lengthsRef, i); - Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++; - lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i); + bitLength = maxLen; + overflow++; } + + Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength; + } + else + { + // A leaf node + int bitLength = Unsafe.Add(ref lengthsRef, i); + Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++; + lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i); } } + } + + if (overflow == 0) + { + return; + } - if (overflow == 0) + int incrBitLen = maxLen - 1; + do + { + // Find the first bit length which could increase: + while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0) { - return; } - int incrBitLen = maxLen - 1; + // Move this node one down and remove a corresponding + // number of overflow nodes. do { - // Find the first bit length which could increase: - while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0) - { - } - - // Move this node one down and remove a corresponding - // number of overflow nodes. - do - { - Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--; - Unsafe.Add(ref bitLengthCountsRef, ++incrBitLen)++; - overflow -= 1 << (maxLen - 1 - incrBitLen); - } - while (overflow > 0 && incrBitLen < maxLen - 1); + Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--; + Unsafe.Add(ref bitLengthCountsRef, ++incrBitLen)++; + overflow -= 1 << (maxLen - 1 - incrBitLen); } - while (overflow > 0); - - // We may have overshot above. Move some nodes from maxLength to - // maxLength-1 in that case. - Unsafe.Add(ref bitLengthCountsRef, maxLen - 1) += overflow; - Unsafe.Add(ref bitLengthCountsRef, maxLen - 2) -= overflow; - - // Now recompute all bit lengths, scanning in increasing - // frequency. It is simpler to reconstruct all lengths instead of - // fixing only the wrong ones. This idea is taken from 'ar' - // written by Haruhiko Okumura. - // - // The nodes were inserted with decreasing frequency into the childs - // array. - int nodeIndex = 2 * numLeafs; - for (int bits = maxLen; bits != 0; bits--) + while (overflow > 0 && incrBitLen < maxLen - 1); + } + while (overflow > 0); + + // We may have overshot above. Move some nodes from maxLength to + // maxLength-1 in that case. + Unsafe.Add(ref bitLengthCountsRef, maxLen - 1) += overflow; + Unsafe.Add(ref bitLengthCountsRef, maxLen - 2) -= overflow; + + // Now recompute all bit lengths, scanning in increasing + // frequency. It is simpler to reconstruct all lengths instead of + // fixing only the wrong ones. This idea is taken from 'ar' + // written by Haruhiko Okumura. + // + // The nodes were inserted with decreasing frequency into the childs + // array. + int nodeIndex = 2 * numLeafs; + for (int bits = maxLen; bits != 0; bits--) + { + int n = Unsafe.Add(ref bitLengthCountsRef, bits - 1); + while (n > 0) { - int n = Unsafe.Add(ref bitLengthCountsRef, bits - 1); - while (n > 0) + int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++); + if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1) { - int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++); - if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1) - { - // We found another leaf - lengthPtr[Unsafe.Add(ref childrenRef, childIndex)] = (byte)bits; - n--; - } + // We found another leaf + lengthPtr[Unsafe.Add(ref childrenRef, childIndex)] = (byte)bits; + n--; } } } + } - public void Dispose() + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.frequenciesMemoryHandle.Dispose(); - this.frequenciesMemoryOwner.Dispose(); + this.frequenciesMemoryHandle.Dispose(); + this.frequenciesMemoryOwner.Dispose(); - this.lengthsMemoryHandle.Dispose(); - this.lengthsMemoryOwner.Dispose(); + this.lengthsMemoryHandle.Dispose(); + this.lengthsMemoryOwner.Dispose(); - this.codesMemoryHandle.Dispose(); - this.codesMemoryOwner.Dispose(); + this.codesMemoryHandle.Dispose(); + this.codesMemoryOwner.Dispose(); - this.frequenciesMemoryOwner = null; - this.lengthsMemoryOwner = null; - this.codesMemoryOwner = null; + this.frequenciesMemoryOwner = null; + this.lengthsMemoryOwner = null; + this.codesMemoryOwner = null; - this.isDisposed = true; - } + this.isDisposed = true; } } } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs index 5b8673da8e..6d7903caa1 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs @@ -1,148 +1,145 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// A special stream deflating or compressing the bytes that are +/// written to it. It uses a Deflater to perform actual deflating. +/// +internal sealed class DeflaterOutputStream : Stream { + private const int BufferLength = 512; + private IMemoryOwner memoryOwner; + private readonly Memory buffer; + private Deflater deflater; + private readonly Stream rawStream; + private bool isDisposed; + /// - /// A special stream deflating or compressing the bytes that are - /// written to it. It uses a Deflater to perform actual deflating. + /// Initializes a new instance of the class. /// - internal sealed class DeflaterOutputStream : Stream + /// The memory allocator to use for buffer allocations. + /// The output stream where deflated output is written. + /// The compression level. + public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) { - private const int BufferLength = 512; - private IMemoryOwner memoryOwner; - private readonly Memory buffer; - private Deflater deflater; - private readonly Stream rawStream; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The output stream where deflated output is written. - /// The compression level. - public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) - { - this.rawStream = rawStream; - this.memoryOwner = memoryAllocator.Allocate(BufferLength); - this.buffer = this.memoryOwner.Memory; - this.deflater = new Deflater(memoryAllocator, compressionLevel); - } + this.rawStream = rawStream; + this.memoryOwner = memoryAllocator.Allocate(BufferLength); + this.buffer = this.memoryOwner.Memory; + this.deflater = new Deflater(memoryAllocator, compressionLevel); + } - /// - public override bool CanRead => false; + /// + public override bool CanRead => false; - /// - public override bool CanSeek => false; + /// + public override bool CanSeek => false; - /// - public override bool CanWrite => this.rawStream.CanWrite; + /// + public override bool CanWrite => this.rawStream.CanWrite; - /// - public override long Length => this.rawStream.Length; + /// + public override long Length => this.rawStream.Length; - /// - public override long Position - { - get => this.rawStream.Position; + /// + public override long Position + { + get => this.rawStream.Position; - set => throw new NotSupportedException(); - } + set => throw new NotSupportedException(); + } - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - /// - public override void SetLength(long value) => throw new NotSupportedException(); + /// + public override void SetLength(long value) => throw new NotSupportedException(); - /// - public override int ReadByte() => throw new NotSupportedException(); + /// + public override int ReadByte() => throw new NotSupportedException(); - /// - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + /// + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - /// - public override void Flush() - { - this.deflater.Flush(); - this.Deflate(true); - this.rawStream.Flush(); - } + /// + public override void Flush() + { + this.deflater.Flush(); + this.Deflate(true); + this.rawStream.Flush(); + } - /// - public override void Write(byte[] buffer, int offset, int count) - { - this.deflater.SetInput(buffer, offset, count); - this.Deflate(); - } + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.deflater.SetInput(buffer, offset, count); + this.Deflate(); + } - private void Deflate() => this.Deflate(false); + private void Deflate() => this.Deflate(false); - private void Deflate(bool flushing) + private void Deflate(bool flushing) + { + while (flushing || !this.deflater.IsNeedingInput) { - while (flushing || !this.deflater.IsNeedingInput) - { - int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); + int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); - if (deflateCount <= 0) - { - break; - } - - this.rawStream.Write(this.buffer.Span[..deflateCount]); - } - - if (!this.deflater.IsNeedingInput) + if (deflateCount <= 0) { - DeflateThrowHelper.ThrowNoDeflate(); + break; } + + this.rawStream.Write(this.buffer.Span[..deflateCount]); } - private void Finish() + if (!this.deflater.IsNeedingInput) { - this.deflater.Finish(); - while (!this.deflater.IsFinished) - { - int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); - if (len <= 0) - { - break; - } - - this.rawStream.Write(this.buffer.Span[..len]); - } + DeflateThrowHelper.ThrowNoDeflate(); + } + } - if (!this.deflater.IsFinished) + private void Finish() + { + this.deflater.Finish(); + while (!this.deflater.IsFinished) + { + int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength); + if (len <= 0) { - DeflateThrowHelper.ThrowNoDeflate(); + break; } - this.rawStream.Flush(); + this.rawStream.Write(this.buffer.Span[..len]); } - /// - protected override void Dispose(bool disposing) + if (!this.deflater.IsFinished) { - if (!this.isDisposed) + DeflateThrowHelper.ThrowNoDeflate(); + } + + this.rawStream.Flush(); + } + + /// + protected override void Dispose(bool disposing) + { + if (!this.isDisposed) + { + if (disposing) { - if (disposing) - { - this.Finish(); - this.deflater.Dispose(); - this.memoryOwner.Dispose(); - } - - this.deflater = null; - this.memoryOwner = null; - this.isDisposed = true; - base.Dispose(disposing); + this.Finish(); + this.deflater.Dispose(); + this.memoryOwner.Dispose(); } + + this.deflater = null; + this.memoryOwner = null; + this.isDisposed = true; + base.Dispose(disposing); } } } diff --git a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs index 1ab6349f02..d2e0e27943 100644 --- a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs +++ b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs @@ -1,188 +1,186 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Stores pending data for writing data to the Deflater. +/// +internal sealed unsafe class DeflaterPendingBuffer : IDisposable { + private readonly Memory buffer; + private readonly byte* pinnedBuffer; + private IMemoryOwner bufferMemoryOwner; + private MemoryHandle bufferMemoryHandle; + + private int start; + private int end; + private uint bits; + private bool isDisposed; + /// - /// Stores pending data for writing data to the Deflater. + /// Initializes a new instance of the class. /// - internal sealed unsafe class DeflaterPendingBuffer : IDisposable + /// The memory allocator to use for buffer allocations. + public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) { - private readonly Memory buffer; - private readonly byte* pinnedBuffer; - private IMemoryOwner bufferMemoryOwner; - private MemoryHandle bufferMemoryHandle; - - private int start; - private int end; - private uint bits; - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) - { - this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE); - this.buffer = this.bufferMemoryOwner.Memory; - this.bufferMemoryHandle = this.buffer.Pin(); - this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; - } + this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE); + this.buffer = this.bufferMemoryOwner.Memory; + this.bufferMemoryHandle = this.buffer.Pin(); + this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; + } - /// - /// Gets the number of bits written to the buffer. - /// - public int BitCount { get; private set; } - - /// - /// Gets a value indicating whether indicates the buffer has been flushed. - /// - public bool IsFlushed => this.end == 0; - - /// - /// Clear internal state/buffers. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() => this.start = this.end = this.BitCount = 0; - - /// - /// Write a short value to buffer LSB first. - /// - /// The value to write. - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteShort(int value) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)value); - pinned[this.end++] = unchecked((byte)(value >> 8)); - } + /// + /// Gets the number of bits written to the buffer. + /// + public int BitCount { get; private set; } - /// - /// Write a block of data to the internal buffer. - /// - /// The data to write. - /// The offset of first byte to write. - /// The number of bytes to write. - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBlock(ReadOnlySpan block, int offset, int length) - { - Unsafe.CopyBlockUnaligned( - ref this.buffer.Span[this.end], - ref MemoryMarshal.GetReference(block[offset..]), - unchecked((uint)length)); + /// + /// Gets a value indicating whether indicates the buffer has been flushed. + /// + public bool IsFlushed => this.end == 0; - this.end += length; - } + /// + /// Clear internal state/buffers. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() => this.start = this.end = this.BitCount = 0; - /// - /// Aligns internal buffer on a byte boundary. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void AlignToByte() - { - if (this.BitCount > 0) - { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)this.bits); - if (this.BitCount > 8) - { - pinned[this.end++] = unchecked((byte)(this.bits >> 8)); - } - } + /// + /// Write a short value to buffer LSB first. + /// + /// The value to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShort(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)value); + pinned[this.end++] = unchecked((byte)(value >> 8)); + } - this.bits = 0; - this.BitCount = 0; - } + /// + /// Write a block of data to the internal buffer. + /// + /// The data to write. + /// The offset of first byte to write. + /// The number of bytes to write. + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBlock(ReadOnlySpan block, int offset, int length) + { + Unsafe.CopyBlockUnaligned( + ref this.buffer.Span[this.end], + ref MemoryMarshal.GetReference(block[offset..]), + unchecked((uint)length)); + + this.end += length; + } - /// - /// Write bits to internal buffer - /// - /// source of bits - /// number of bits to write - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteBits(int b, int count) + /// + /// Aligns internal buffer on a byte boundary. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void AlignToByte() + { + if (this.BitCount > 0) { - this.bits |= (uint)(b << this.BitCount); - this.BitCount += count; - if (this.BitCount >= 16) + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)this.bits); + if (this.BitCount > 8) { - byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)this.bits); pinned[this.end++] = unchecked((byte)(this.bits >> 8)); - this.bits >>= 16; - this.BitCount -= 16; } } - /// - /// Write a short value to internal buffer most significant byte first - /// - /// The value to write - [MethodImpl(InliningOptions.ShortMethod)] - public void WriteShortMSB(int value) + this.bits = 0; + this.BitCount = 0; + } + + /// + /// Write bits to internal buffer + /// + /// source of bits + /// number of bits to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteBits(int b, int count) + { + this.bits |= (uint)(b << this.BitCount); + this.BitCount += count; + if (this.BitCount >= 16) { byte* pinned = this.pinnedBuffer; - pinned[this.end++] = unchecked((byte)(value >> 8)); - pinned[this.end++] = unchecked((byte)value); + pinned[this.end++] = unchecked((byte)this.bits); + pinned[this.end++] = unchecked((byte)(this.bits >> 8)); + this.bits >>= 16; + this.BitCount -= 16; } + } - /// - /// Flushes the pending buffer into the given output array. - /// If the output array is to small, only a partial flush is done. - /// - /// The output array. - /// The offset into output array. - /// The maximum number of bytes to store. - /// The number of bytes flushed. - public int Flush(Span output, int offset, int length) + /// + /// Write a short value to internal buffer most significant byte first + /// + /// The value to write + [MethodImpl(InliningOptions.ShortMethod)] + public void WriteShortMSB(int value) + { + byte* pinned = this.pinnedBuffer; + pinned[this.end++] = unchecked((byte)(value >> 8)); + pinned[this.end++] = unchecked((byte)value); + } + + /// + /// Flushes the pending buffer into the given output array. + /// If the output array is to small, only a partial flush is done. + /// + /// The output array. + /// The offset into output array. + /// The maximum number of bytes to store. + /// The number of bytes flushed. + public int Flush(Span output, int offset, int length) + { + if (this.BitCount >= 8) { - if (this.BitCount >= 8) - { - this.pinnedBuffer[this.end++] = unchecked((byte)this.bits); - this.bits >>= 8; - this.BitCount -= 8; - } + this.pinnedBuffer[this.end++] = unchecked((byte)this.bits); + this.bits >>= 8; + this.BitCount -= 8; + } - if (length > this.end - this.start) - { - length = this.end - this.start; - - Unsafe.CopyBlockUnaligned( - ref output[offset], - ref this.buffer.Span[this.start], - unchecked((uint)length)); - this.start = 0; - this.end = 0; - } - else - { - Unsafe.CopyBlockUnaligned( - ref output[offset], - ref this.buffer.Span[this.start], - unchecked((uint)length)); - this.start += length; - } + if (length > this.end - this.start) + { + length = this.end - this.start; - return length; + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); + this.start = 0; + this.end = 0; } + else + { + Unsafe.CopyBlockUnaligned( + ref output[offset], + ref this.buffer.Span[this.start], + unchecked((uint)length)); + this.start += length; + } + + return length; + } - /// - public void Dispose() + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.bufferMemoryHandle.Dispose(); - this.bufferMemoryOwner.Dispose(); - this.bufferMemoryOwner = null; - this.isDisposed = true; - } + this.bufferMemoryHandle.Dispose(); + this.bufferMemoryOwner.Dispose(); + this.bufferMemoryOwner = null; + this.isDisposed = true; } } } diff --git a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs index 5bccf470d4..b46283534d 100644 --- a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs @@ -1,182 +1,179 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. +/// +internal sealed class ZlibDeflateStream : Stream { /// - /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// Computes the checksum for the data stream. + /// + private uint adler = Adler32.SeedValue; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The stream responsible for compressing the input stream. /// - internal sealed class ZlibDeflateStream : Stream + private DeflaterOutputStream deflateStream; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level) + : this(memoryAllocator, stream, (PngCompressionLevel)level) { - /// - /// The raw stream containing the uncompressed image data. - /// - private readonly Stream rawStream; - - /// - /// Computes the checksum for the data stream. - /// - private uint adler = Adler32.SeedValue; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The stream responsible for compressing the input stream. - /// - private DeflaterOutputStream deflateStream; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The stream to compress. - /// The compression level. - public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level) - : this(memoryAllocator, stream, (PngCompressionLevel)level) + } + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator to use for buffer allocations. + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, PngCompressionLevel level) + { + int compressionLevel = (int)level; + this.rawStream = stream; + + // Write the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + const int Cmf = 0x78; + int flg = 218; + + // http://stackoverflow.com/a/2331025/277304 + if (compressionLevel >= 5 && compressionLevel <= 6) { + flg = 156; + } + else if (compressionLevel >= 3 && compressionLevel <= 4) + { + flg = 94; + } + else if (compressionLevel <= 2) + { + flg = 1; } - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator to use for buffer allocations. - /// The stream to compress. - /// The compression level. - public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, PngCompressionLevel level) + // Just in case + flg -= ((Cmf * 256) + flg) % 31; + + if (flg < 0) { - int compressionLevel = (int)level; - this.rawStream = stream; - - // Write the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - const int Cmf = 0x78; - int flg = 218; - - // http://stackoverflow.com/a/2331025/277304 - if (compressionLevel >= 5 && compressionLevel <= 6) - { - flg = 156; - } - else if (compressionLevel >= 3 && compressionLevel <= 4) - { - flg = 94; - } - else if (compressionLevel <= 2) - { - flg = 1; - } - - // Just in case - flg -= ((Cmf * 256) + flg) % 31; - - if (flg < 0) - { - flg += 31; - } - - this.rawStream.WriteByte(Cmf); - this.rawStream.WriteByte((byte)flg); - - this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); + flg += 31; } - /// - public override bool CanRead => false; + this.rawStream.WriteByte(Cmf); + this.rawStream.WriteByte((byte)flg); - /// - public override bool CanSeek => false; + this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel); + } - /// - public override bool CanWrite => this.rawStream.CanWrite; + /// + public override bool CanRead => false; - /// - public override long Length => this.rawStream.Length; + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => this.rawStream.CanWrite; + + /// + public override long Length => this.rawStream.Length; + + /// + public override long Position + { + get + { + return this.rawStream.Position; + } - /// - public override long Position + set { - get - { - return this.rawStream.Position; - } - - set - { - throw new NotSupportedException(); - } + throw new NotSupportedException(); } + } - /// - public override void Flush() => this.deflateStream.Flush(); + /// + public override void Flush() => this.deflateStream.Flush(); - /// - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + /// + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - /// - public override void SetLength(long value) => throw new NotSupportedException(); + /// + public override void SetLength(long value) => throw new NotSupportedException(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override void Write(byte[] buffer, int offset, int count) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override void Write(byte[] buffer, int offset, int count) + { + this.deflateStream.Write(buffer, offset, count); + this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count)); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - this.deflateStream.Write(buffer, offset, count); - this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count)); + return; } - /// - protected override void Dispose(bool disposing) + if (disposing) { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // dispose managed resources - this.deflateStream.Dispose(); - - // Add the crc - uint crc = this.adler; - this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); - this.rawStream.WriteByte((byte)(crc & 0xFF)); - } - - this.deflateStream = null; - - base.Dispose(disposing); - this.isDisposed = true; + // dispose managed resources + this.deflateStream.Dispose(); + + // Add the crc + uint crc = this.adler; + this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); + this.rawStream.WriteByte((byte)(crc & 0xFF)); } + + this.deflateStream = null; + + base.Dispose(disposing); + this.isDisposed = true; } } diff --git a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs index ed5766435d..410333dc48 100644 --- a/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs @@ -1,282 +1,279 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.IO.Compression; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Compression.Zlib +namespace SixLabors.ImageSharp.Compression.Zlib; + +/// +/// Provides methods and properties for deframing streams from PNGs. +/// +internal sealed class ZlibInflateStream : Stream { /// - /// Provides methods and properties for deframing streams from PNGs. + /// Used to read the Adler-32 and Crc-32 checksums. + /// We don't actually use this for anything so it doesn't + /// have to be threadsafe. /// - internal sealed class ZlibInflateStream : Stream - { - /// - /// Used to read the Adler-32 and Crc-32 checksums. - /// We don't actually use this for anything so it doesn't - /// have to be threadsafe. - /// - private static readonly byte[] ChecksumBuffer = new byte[4]; - - /// - /// A default delegate to get more data from the inner stream. - /// - private static readonly Func GetDataNoOp = () => 0; - - /// - /// The inner raw memory stream. - /// - private readonly BufferedReadStream innerStream; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The current data remaining to be read. - /// - private int currentDataRemaining; - - /// - /// Delegate to get more data once we've exhausted the current data remaining. - /// - private readonly Func getData; - - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - public ZlibInflateStream(BufferedReadStream innerStream) - : this(innerStream, GetDataNoOp) - { - } + private static readonly byte[] ChecksumBuffer = new byte[4]; - /// - /// Initializes a new instance of the class. - /// - /// The inner raw stream. - /// A delegate to get more data from the inner stream. - public ZlibInflateStream(BufferedReadStream innerStream, Func getData) - { - this.innerStream = innerStream; - this.getData = getData; - } + /// + /// A default delegate to get more data from the inner stream. + /// + private static readonly Func GetDataNoOp = () => 0; - /// - public override bool CanRead => this.innerStream.CanRead; + /// + /// The inner raw memory stream. + /// + private readonly BufferedReadStream innerStream; - /// - public override bool CanSeek => false; + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; - /// - public override bool CanWrite => throw new NotSupportedException(); + /// + /// The current data remaining to be read. + /// + private int currentDataRemaining; - /// - public override long Length => throw new NotSupportedException(); + /// + /// Delegate to get more data once we've exhausted the current data remaining. + /// + private readonly Func getData; - /// - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + /// + /// Initializes a new instance of the class. + /// + /// The inner raw stream. + public ZlibInflateStream(BufferedReadStream innerStream) + : this(innerStream, GetDataNoOp) + { + } - /// - /// Gets the compressed stream over the deframed inner stream. - /// - public DeflateStream CompressedStream { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The inner raw stream. + /// A delegate to get more data from the inner stream. + public ZlibInflateStream(BufferedReadStream innerStream, Func getData) + { + this.innerStream = innerStream; + this.getData = getData; + } - /// - /// Adds new bytes from a frame found in the original stream. - /// - /// The current remaining data according to the chunk length. - /// Whether the chunk to be inflated is a critical chunk. - /// The . - public bool AllocateNewBytes(int bytes, bool isCriticalChunk) - { - this.currentDataRemaining = bytes; - if (this.CompressedStream is null) - { - return this.InitializeInflateStream(isCriticalChunk); - } + /// + public override bool CanRead => this.innerStream.CanRead; - return true; - } + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => throw new NotSupportedException(); + + /// + public override long Length => throw new NotSupportedException(); - /// - public override void Flush() => throw new NotSupportedException(); + /// + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + /// + /// Gets the compressed stream over the deframed inner stream. + /// + public DeflateStream CompressedStream { get; private set; } - /// - public override int ReadByte() + /// + /// Adds new bytes from a frame found in the original stream. + /// + /// The current remaining data according to the chunk length. + /// Whether the chunk to be inflated is a critical chunk. + /// The . + public bool AllocateNewBytes(int bytes, bool isCriticalChunk) + { + this.currentDataRemaining = bytes; + if (this.CompressedStream is null) { - this.currentDataRemaining--; - return this.innerStream.ReadByte(); + return this.InitializeInflateStream(isCriticalChunk); } - /// - public override int Read(byte[] buffer, int offset, int count) + return true; + } + + /// + public override void Flush() => throw new NotSupportedException(); + + /// + public override int ReadByte() + { + this.currentDataRemaining--; + return this.innerStream.ReadByte(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (this.currentDataRemaining == 0) { + // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks. + this.currentDataRemaining = this.getData(); + if (this.currentDataRemaining == 0) { - // Last buffer was read in its entirety, let's make sure we don't actually have more in additional IDAT chunks. - this.currentDataRemaining = this.getData(); - - if (this.currentDataRemaining == 0) - { - return 0; - } + return 0; } + } - int bytesToRead = Math.Min(count, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - long innerStreamLength = this.innerStream.Length; - - // Keep reading data until we've reached the end of the stream or filled the buffer. - int bytesRead = 0; - offset += totalBytesRead; - while (this.currentDataRemaining == 0 && totalBytesRead < count) - { - this.currentDataRemaining = this.getData(); + int bytesToRead = Math.Min(count, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + int totalBytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + long innerStreamLength = this.innerStream.Length; - if (this.currentDataRemaining == 0) - { - return totalBytesRead; - } + // Keep reading data until we've reached the end of the stream or filled the buffer. + int bytesRead = 0; + offset += totalBytesRead; + while (this.currentDataRemaining == 0 && totalBytesRead < count) + { + this.currentDataRemaining = this.getData(); - offset += bytesRead; + if (this.currentDataRemaining == 0) + { + return totalBytesRead; + } - if (offset >= innerStreamLength || offset >= count) - { - return totalBytesRead; - } + offset += bytesRead; - bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); - this.currentDataRemaining -= bytesToRead; - bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); - totalBytesRead += bytesRead; + if (offset >= innerStreamLength || offset >= count) + { + return totalBytesRead; } - return totalBytesRead; + bytesToRead = Math.Min(count - totalBytesRead, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + totalBytesRead += bytesRead; } - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + return totalBytesRead; + } - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } - /// - public override void Write(byte[] buffer, int offset, int count) + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - throw new NotSupportedException(); + return; } - /// - protected override void Dispose(bool disposing) + if (disposing) { - if (this.isDisposed) + // Dispose managed resources. + if (this.CompressedStream != null) { - return; + this.CompressedStream.Dispose(); + this.CompressedStream = null; } + } - if (disposing) - { - // Dispose managed resources. - if (this.CompressedStream != null) - { - this.CompressedStream.Dispose(); - this.CompressedStream = null; - } - } + base.Dispose(disposing); - base.Dispose(disposing); + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; + private bool InitializeInflateStream(bool isCriticalChunk) + { + // Read the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = this.innerStream.ReadByte(); + int flag = this.innerStream.ReadByte(); + this.currentDataRemaining -= 2; + if (cmf == -1 || flag == -1) + { + return false; } - private bool InitializeInflateStream(bool isCriticalChunk) + if ((cmf & 0x0F) == 8) { - // Read the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - int cmf = this.innerStream.ReadByte(); - int flag = this.innerStream.ReadByte(); - this.currentDataRemaining -= 2; - if (cmf == -1 || flag == -1) - { - return false; - } - - if ((cmf & 0x0F) == 8) - { - // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. - int cinfo = (cmf & 0xF0) >> 4; + // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. + int cinfo = (cmf & 0xF0) >> 4; - if (cinfo > 7) - { - if (isCriticalChunk) - { - // Values of CINFO above 7 are not allowed in RFC1950. - // CINFO is not defined in this specification for CM not equal to 8. - throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); - } - else - { - return false; - } - } - } - else + if (cinfo > 7) { if (isCriticalChunk) { - throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); + // Values of CINFO above 7 are not allowed in RFC1950. + // CINFO is not defined in this specification for CM not equal to 8. + throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); } else { return false; } } - - // The preset dictionary. - bool fdict = (flag & 32) != 0; - if (fdict) + } + else + { + if (isCriticalChunk) { - // We don't need this for inflate so simply skip by the next four bytes. - // https://tools.ietf.org/html/rfc1950#page-6 - this.innerStream.Read(ChecksumBuffer, 0, 4); - this.currentDataRemaining -= 4; + throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); } + else + { + return false; + } + } - // Initialize the deflate BufferedReadStream. - this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); - - return true; + // The preset dictionary. + bool fdict = (flag & 32) != 0; + if (fdict) + { + // We don't need this for inflate so simply skip by the next four bytes. + // https://tools.ietf.org/html/rfc1950#page-6 + this.innerStream.Read(ChecksumBuffer, 0, 4); + this.currentDataRemaining -= 4; } + + // Initialize the deflate BufferedReadStream. + this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true); + + return true; } } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 76c2b31b38..b299405758 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -17,213 +15,212 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Provides configuration which allows altering default behaviour or extending the library. +/// +public sealed class Configuration { /// - /// Provides configuration which allows altering default behaviour or extending the library. + /// A lazily initialized configuration default instance. /// - public sealed class Configuration + private static readonly Lazy Lazy = new(CreateDefaultInstance); + private const int DefaultStreamProcessingBufferSize = 8096; + private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; + private int maxDegreeOfParallelism = Environment.ProcessorCount; + private MemoryAllocator memoryAllocator = MemoryAllocator.Default; + + /// + /// Initializes a new instance of the class. + /// + public Configuration() { - /// - /// A lazily initialized configuration default instance. - /// - private static readonly Lazy Lazy = new(CreateDefaultInstance); - private const int DefaultStreamProcessingBufferSize = 8096; - private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; - private int maxDegreeOfParallelism = Environment.ProcessorCount; - private MemoryAllocator memoryAllocator = MemoryAllocator.Default; - - /// - /// Initializes a new instance of the class. - /// - public Configuration() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// A collection of configuration modules to register. - public Configuration(params IConfigurationModule[] configurationModules) + /// + /// Initializes a new instance of the class. + /// + /// A collection of configuration modules to register. + public Configuration(params IConfigurationModule[] configurationModules) + { + if (configurationModules != null) { - if (configurationModules != null) + foreach (IConfigurationModule p in configurationModules) { - foreach (IConfigurationModule p in configurationModules) - { - p.Configure(this); - } + p.Configure(this); } } + } - /// - /// Gets the default instance. - /// - public static Configuration Default { get; } = Lazy.Value; - - /// - /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms - /// configured with this instance. - /// Initialized with by default. - /// - public int MaxDegreeOfParallelism - { - get => this.maxDegreeOfParallelism; - set - { - if (value is 0 or < -1) - { - throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); - } - - this.maxDegreeOfParallelism = value; - } - } + /// + /// Gets the default instance. + /// + public static Configuration Default { get; } = Lazy.Value; - /// - /// Gets or sets the size of the buffer to use when working with streams. - /// Initialized with by default. - /// - public int StreamProcessingBufferSize + /// + /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms + /// configured with this instance. + /// Initialized with by default. + /// + public int MaxDegreeOfParallelism + { + get => this.maxDegreeOfParallelism; + set { - get => this.streamProcessingBufferSize; - set + if (value is 0 or < -1) { - if (value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(this.StreamProcessingBufferSize)); - } - - this.streamProcessingBufferSize = value; + throw new ArgumentOutOfRangeException(nameof(this.MaxDegreeOfParallelism)); } + + this.maxDegreeOfParallelism = value; } + } - /// - /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. - /// - /// - /// Contiguous allocations are not possible, if the image needs a buffer larger than . - /// - public bool PreferContiguousImageBuffers { get; set; } - - /// - /// Gets a set of properties for the Configuration. - /// - /// This can be used for storing global settings and defaults to be accessible to processors. - public IDictionary Properties { get; } = new ConcurrentDictionary(); - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; - - /// - /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. - /// - public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; - - /// - /// Gets or the that is currently in use. - /// - public ImageFormatManager ImageFormatsManager { get; private set; } = new ImageFormatManager(); - - /// - /// Gets or sets the that is currently in use. - /// Defaults to . - /// - /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process. - /// In case you need to customize it, you can ensure this by changing - /// - /// - /// It's possible to reduce allocator footprint by assigning a custom instance created with - /// , but note that since the default pooling - /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. - /// You can ensure this by altering the allocator of , or by implementing custom application logic that - /// manages allocator lifetime. - /// - /// If an allocator has to be dropped for some reason, - /// shall be invoked after disposing all associated instances. - /// - public MemoryAllocator MemoryAllocator + /// + /// Gets or sets the size of the buffer to use when working with streams. + /// Initialized with by default. + /// + public int StreamProcessingBufferSize + { + get => this.streamProcessingBufferSize; + set { - get => this.memoryAllocator; - set + if (value <= 0) { - Guard.NotNull(value, nameof(this.MemoryAllocator)); - this.memoryAllocator = value; + throw new ArgumentOutOfRangeException(nameof(this.StreamProcessingBufferSize)); } + + this.streamProcessingBufferSize = value; } + } + + /// + /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. + /// + /// + /// Contiguous allocations are not possible, if the image needs a buffer larger than . + /// + public bool PreferContiguousImageBuffers { get; set; } - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; - - /// - /// Gets or sets the filesystem helper for accessing the local file system. - /// - internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); - - /// - /// Gets or sets the working buffer size hint for image processors. - /// The default value is 1MB. - /// - /// - /// 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; - - /// - /// Gets or sets the image operations provider factory. - /// - internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); - - /// - /// Registers a new format provider. - /// - /// The configuration provider to call configure on. - public void Configure(IConfigurationModule configuration) + /// + /// Gets a set of properties for the Configuration. + /// + /// This can be used for storing global settings and defaults to be accessible to processors. + public IDictionary Properties { get; } = new ConcurrentDictionary(); + + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; + + /// + /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. + /// + public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; + + /// + /// Gets or the that is currently in use. + /// + public ImageFormatManager ImageFormatsManager { get; private set; } = new ImageFormatManager(); + + /// + /// Gets or sets the that is currently in use. + /// Defaults to . + /// + /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process. + /// In case you need to customize it, you can ensure this by changing + /// + /// + /// It's possible to reduce allocator footprint by assigning a custom instance created with + /// , but note that since the default pooling + /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. + /// You can ensure this by altering the allocator of , or by implementing custom application logic that + /// manages allocator lifetime. + /// + /// If an allocator has to be dropped for some reason, + /// shall be invoked after disposing all associated instances. + /// + public MemoryAllocator MemoryAllocator + { + get => this.memoryAllocator; + set { - Guard.NotNull(configuration, nameof(configuration)); - configuration.Configure(this); + Guard.NotNull(value, nameof(this.MemoryAllocator)); + this.memoryAllocator = value; } + } - /// - /// Creates a shallow copy of the . - /// - /// A new configuration instance. - public Configuration Clone() => new() - { - MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, - StreamProcessingBufferSize = this.StreamProcessingBufferSize, - ImageFormatsManager = this.ImageFormatsManager, - memoryAllocator = this.memoryAllocator, - ImageOperationsProvider = this.ImageOperationsProvider, - ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem, - WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, - }; - - /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// . - /// . - /// . - /// . - /// . - /// - /// The default configuration of . - internal static Configuration CreateDefaultInstance() => new( - new PngConfigurationModule(), - new JpegConfigurationModule(), - new GifConfigurationModule(), - new BmpConfigurationModule(), - new PbmConfigurationModule(), - new TgaConfigurationModule(), - new TiffConfigurationModule(), - new WebpConfigurationModule()); + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; + + /// + /// Gets or sets the filesystem helper for accessing the local file system. + /// + internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); + + /// + /// Gets or sets the working buffer size hint for image processors. + /// The default value is 1MB. + /// + /// + /// 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; + + /// + /// Gets or sets the image operations provider factory. + /// + internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); + + /// + /// Registers a new format provider. + /// + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); } + + /// + /// Creates a shallow copy of the . + /// + /// A new configuration instance. + public Configuration Clone() => new() + { + MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, + StreamProcessingBufferSize = this.StreamProcessingBufferSize, + ImageFormatsManager = this.ImageFormatsManager, + memoryAllocator = this.memoryAllocator, + ImageOperationsProvider = this.ImageOperationsProvider, + ReadOrigin = this.ReadOrigin, + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, + }; + + /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// . + /// . + /// . + /// . + /// . + /// + /// The default configuration of . + internal static Configuration CreateDefaultInstance() => new( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule(), + new PbmConfigurationModule(), + new TgaConfigurationModule(), + new TiffConfigurationModule(), + new WebpConfigurationModule()); } diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs index 2cd286cd6f..b66cede3a0 100644 --- a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs +++ b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs @@ -1,95 +1,91 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; +namespace SixLabors.ImageSharp.Diagnostics; -namespace SixLabors.ImageSharp.Diagnostics +/// +/// Represents the method to handle . +/// +/// The allocation stack trace. +public delegate void UndisposedAllocationDelegate(string allocationStackTrace); + +/// +/// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects. +/// +public static class MemoryDiagnostics { - /// - /// Represents the method to handle . - /// - /// The allocation stack trace. - public delegate void UndisposedAllocationDelegate(string allocationStackTrace); + private static int totalUndisposedAllocationCount; + + private static UndisposedAllocationDelegate undisposedAllocation; + private static int undisposedAllocationSubscriptionCounter; + private static readonly object SyncRoot = new(); /// - /// Utilities to track memory usage and detect memory leaks from not disposing ImageSharp objects. + /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer. + /// The event brings significant overhead, and is intended to be used for troubleshooting only. + /// For production diagnostics, use . /// - public static class MemoryDiagnostics + public static event UndisposedAllocationDelegate UndisposedAllocation { - private static int totalUndisposedAllocationCount; - - private static UndisposedAllocationDelegate undisposedAllocation; - private static int undisposedAllocationSubscriptionCounter; - private static readonly object SyncRoot = new(); - - /// - /// Fires when an ImageSharp object's undisposed memory resource leaks to the finalizer. - /// The event brings significant overhead, and is intended to be used for troubleshooting only. - /// For production diagnostics, use . - /// - public static event UndisposedAllocationDelegate UndisposedAllocation + add { - add + lock (SyncRoot) { - lock (SyncRoot) - { - undisposedAllocationSubscriptionCounter++; - undisposedAllocation += value; - } + undisposedAllocationSubscriptionCounter++; + undisposedAllocation += value; } + } - remove + remove + { + lock (SyncRoot) { - lock (SyncRoot) - { - undisposedAllocation -= value; - undisposedAllocationSubscriptionCounter--; - } + undisposedAllocation -= value; + undisposedAllocationSubscriptionCounter--; } } + } - /// - /// Fires when ImageSharp allocates memory from a MemoryAllocator - /// - internal static event Action MemoryAllocated; + /// + /// Fires when ImageSharp allocates memory from a MemoryAllocator + /// + internal static event Action MemoryAllocated; + + /// + /// Fires when ImageSharp releases memory allocated from a MemoryAllocator + /// + internal static event Action MemoryReleased; - /// - /// Fires when ImageSharp releases memory allocated from a MemoryAllocator - /// - internal static event Action MemoryReleased; + /// + /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. + /// + public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; - /// - /// Gets a value indicating the total number of memory resource objects leaked to the finalizer. - /// - public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount; + internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; - internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; + internal static void IncrementTotalUndisposedAllocationCount() + { + Interlocked.Increment(ref totalUndisposedAllocationCount); + MemoryAllocated?.Invoke(); + } - internal static void IncrementTotalUndisposedAllocationCount() - { - Interlocked.Increment(ref totalUndisposedAllocationCount); - MemoryAllocated?.Invoke(); - } + internal static void DecrementTotalUndisposedAllocationCount() + { + Interlocked.Decrement(ref totalUndisposedAllocationCount); + MemoryReleased?.Invoke(); + } - internal static void DecrementTotalUndisposedAllocationCount() + internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) + { + if (undisposedAllocation is null) { - Interlocked.Decrement(ref totalUndisposedAllocationCount); - MemoryReleased?.Invoke(); + return; } - internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) - { - if (undisposedAllocation is null) - { - return; - } - - // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. - ThreadPool.QueueUserWorkItem( - stackTrace => undisposedAllocation?.Invoke(stackTrace), - allocationStackTrace, - preferLocal: false); - } + // Schedule on the ThreadPool, to avoid user callback messing up the finalizer thread. + ThreadPool.QueueUserWorkItem( + stackTrace => undisposedAllocation?.Invoke(stackTrace), + allocationStackTrace, + preferLocal: false); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs index 88dee6100a..b77f7de790 100644 --- a/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpArrayFileHeader.cs @@ -1,53 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct BmpArrayFileHeader { - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct BmpArrayFileHeader + public BmpArrayFileHeader(short type, int size, int offsetToNext, short width, short height) + { + this.Type = type; + this.Size = size; + this.OffsetToNext = offsetToNext; + this.ScreenWidth = width; + this.ScreenHeight = height; + } + + /// + /// Gets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x41 (Hex code points for B and A). + /// + public short Type { get; } + + /// + /// Gets the size of this header. + /// + public int Size { get; } + + /// + /// Gets the offset to next OS2BMPARRAYFILEHEADER. + /// This offset is calculated from the starting byte of the file. A value of zero indicates that this header is for the last image in the array list. + /// + public int OffsetToNext { get; } + + /// + /// Gets the width of the image display in pixels. + /// + public short ScreenWidth { get; } + + /// + /// Gets the height of the image display in pixels. + /// + public short ScreenHeight { get; } + + public static BmpArrayFileHeader Parse(Span data) { - public BmpArrayFileHeader(short type, int size, int offsetToNext, short width, short height) - { - this.Type = type; - this.Size = size; - this.OffsetToNext = offsetToNext; - this.ScreenWidth = width; - this.ScreenHeight = height; - } - - /// - /// Gets the Bitmap identifier. - /// The field used to identify the bitmap file: 0x42 0x41 (Hex code points for B and A). - /// - public short Type { get; } - - /// - /// Gets the size of this header. - /// - public int Size { get; } - - /// - /// Gets the offset to next OS2BMPARRAYFILEHEADER. - /// This offset is calculated from the starting byte of the file. A value of zero indicates that this header is for the last image in the array list. - /// - public int OffsetToNext { get; } - - /// - /// Gets the width of the image display in pixels. - /// - public short ScreenWidth { get; } - - /// - /// Gets the height of the image display in pixels. - /// - public short ScreenHeight { get; } - - public static BmpArrayFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } + return MemoryMarshal.Cast(data)[0]; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index f66883c203..5700bb444c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -1,46 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Enumerates the available bits per pixel the bitmap encoder supports. +/// +public enum BmpBitsPerPixel : short { /// - /// Enumerates the available bits per pixel the bitmap encoder supports. + /// 1 bit per pixel. + /// + Pixel1 = 1, + + /// + /// 2 bits per pixel. + /// + Pixel2 = 2, + + /// + /// 4 bits per pixel. + /// + Pixel4 = 4, + + /// + /// 8 bits per pixel. Each pixel consists of 1 byte. + /// + Pixel8 = 8, + + /// + /// 16 bits per pixel. Each pixel consists of 2 bytes. + /// + Pixel16 = 16, + + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 24, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. /// - public enum BmpBitsPerPixel : short - { - /// - /// 1 bit per pixel. - /// - Pixel1 = 1, - - /// - /// 2 bits per pixel. - /// - Pixel2 = 2, - - /// - /// 4 bits per pixel. - /// - Pixel4 = 4, - - /// - /// 8 bits per pixel. Each pixel consists of 1 byte. - /// - Pixel8 = 8, - - /// - /// 16 bits per pixel. Each pixel consists of 2 bytes. - /// - Pixel16 = 16, - - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Pixel24 = 24, - - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes. - /// - Pixel32 = 32 - } + Pixel32 = 32 } diff --git a/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs b/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs index 0bab53a2be..ad2bda9b61 100644 --- a/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs +++ b/src/ImageSharp/Formats/Bmp/BmpColorSpace.cs @@ -2,36 +2,35 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Enum for the different color spaces. +/// +internal enum BmpColorSpace { /// - /// Enum for the different color spaces. + /// This value implies that endpoints and gamma values are given in the appropriate fields. /// - internal enum BmpColorSpace - { - /// - /// This value implies that endpoints and gamma values are given in the appropriate fields. - /// - LCS_CALIBRATED_RGB = 0, + LCS_CALIBRATED_RGB = 0, - /// - /// The Windows default color space ('Win '). - /// - LCS_WINDOWS_COLOR_SPACE = 1466527264, + /// + /// The Windows default color space ('Win '). + /// + LCS_WINDOWS_COLOR_SPACE = 1466527264, - /// - /// Specifies that the bitmap is in sRGB color space ('sRGB'). - /// - LCS_sRGB = 1934772034, + /// + /// Specifies that the bitmap is in sRGB color space ('sRGB'). + /// + LCS_sRGB = 1934772034, - /// - /// This value indicates that bV5ProfileData points to the file name of the profile to use (gamma and endpoints values are ignored). - /// - PROFILE_LINKED = 1279872587, + /// + /// This value indicates that bV5ProfileData points to the file name of the profile to use (gamma and endpoints values are ignored). + /// + PROFILE_LINKED = 1279872587, - /// - /// This value indicates that bV5ProfileData points to a memory buffer that contains the profile to be used (gamma and endpoints values are ignored). - /// - PROFILE_EMBEDDED = 1296188740 - } + /// + /// This value indicates that bV5ProfileData points to a memory buffer that contains the profile to be used (gamma and endpoints values are ignored). + /// + PROFILE_EMBEDDED = 1296188740 } diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs index 2749462e3c..bac8ae6fac 100644 --- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs +++ b/src/ImageSharp/Formats/Bmp/BmpCompression.cs @@ -1,76 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Defines the compression type of the image data +/// in the bitmap file. +/// +internal enum BmpCompression : int { /// - /// Defines the compression type of the image data - /// in the bitmap file. + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// The format depends on the number of bits, stored in the info header. + /// If the number of bits are one, four or eight each pixel data is + /// a index to the palette. If the number of bits are sixteen, + /// twenty-four or thirty-two each pixel contains a color. /// - internal enum BmpCompression : int - { - /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// The format depends on the number of bits, stored in the info header. - /// If the number of bits are one, four or eight each pixel data is - /// a index to the palette. If the number of bits are sixteen, - /// twenty-four or thirty-two each pixel contains a color. - /// - RGB = 0, + RGB = 0, - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next byte will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. - /// - RLE8 = 1, + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next byte will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// + RLE8 = 1, - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next two half bytes will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. - /// - RLE4 = 2, + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next two half bytes will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// + RLE4 = 2, - /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// - BitFields = 3, + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// + BitFields = 3, - /// - /// The bitmap contains a JPG image. - /// Not supported at the moment. - /// - JPEG = 4, + /// + /// The bitmap contains a JPG image. + /// Not supported at the moment. + /// + JPEG = 4, - /// - /// The bitmap contains a PNG image. - /// Not supported at the moment. - /// - PNG = 5, + /// + /// The bitmap contains a PNG image. + /// Not supported at the moment. + /// + PNG = 5, - /// - /// Introduced with Windows CE. - /// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color - /// masks that specify the red, green, blue, and alpha components of each pixel. - /// - BI_ALPHABITFIELDS = 6, + /// + /// Introduced with Windows CE. + /// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color + /// masks that specify the red, green, blue, and alpha components of each pixel. + /// + BI_ALPHABITFIELDS = 6, - /// - /// OS/2 specific compression type. - /// Similar to run length encoding of 4 and 8 bit. - /// The only difference is that run values encoded are three bytes in size (one byte per RGB color component), - /// rather than four or eight bits in size. - /// - /// Note: Because compression value of 4 is ambiguous for BI_RGB for windows and RLE24 for OS/2, the enum value is remapped - /// to a different value, to be clearly separate from valid windows values. - /// - RLE24 = 100, - } + /// + /// OS/2 specific compression type. + /// Similar to run length encoding of 4 and 8 bit. + /// The only difference is that run values encoded are three bytes in size (one byte per RGB color component), + /// rather than four or eight bits in size. + /// + /// Note: Because compression value of 4 is ambiguous for BI_RGB for windows and RLE24 for OS/2, the enum value is remapped + /// to a different value, to be clearly separate from valid windows values. + /// + RLE24 = 100, } diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index cff81d58e3..05303058ef 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Registers the image encoders, decoders and mime type detectors for the bmp format. +/// +public sealed class BmpConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the bmp format. - /// - public sealed class BmpConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder()); - configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder()); + configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs index e4954bb1e7..5cf0c97324 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConstants.cs @@ -1,59 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Bmp; -namespace SixLabors.ImageSharp.Formats.Bmp +/// +/// Defines constants relating to BMPs +/// +internal static class BmpConstants { /// - /// Defines constants relating to BMPs + /// The list of mimetypes that equate to a bmp. /// - internal static class BmpConstants + public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; + + /// + /// The list of file extensions that equate to a bmp. + /// + public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; + + /// + /// Valid magic bytes markers identifying a Bitmap file. + /// + internal static class TypeMarkers { /// - /// The list of mimetypes that equate to a bmp. + /// Single-image BMP file that may have been created under Windows or OS/2. + /// + public const int Bitmap = 0x4D42; + + /// + /// OS/2 Bitmap Array. + /// + public const int BitmapArray = 0x4142; + + /// + /// OS/2 Color Icon. + /// + public const int ColorIcon = 0x4943; + + /// + /// OS/2 Color Pointer. /// - public static readonly IEnumerable MimeTypes = new[] { "image/bmp", "image/x-windows-bmp" }; + public const int ColorPointer = 0x5043; /// - /// The list of file extensions that equate to a bmp. + /// OS/2 Icon. /// - public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" }; + public const int Icon = 0x4349; /// - /// Valid magic bytes markers identifying a Bitmap file. + /// OS/2 Pointer. /// - internal static class TypeMarkers - { - /// - /// Single-image BMP file that may have been created under Windows or OS/2. - /// - public const int Bitmap = 0x4D42; - - /// - /// OS/2 Bitmap Array. - /// - public const int BitmapArray = 0x4142; - - /// - /// OS/2 Color Icon. - /// - public const int ColorIcon = 0x4943; - - /// - /// OS/2 Color Pointer. - /// - public const int ColorPointer = 0x5043; - - /// - /// OS/2 Icon. - /// - public const int Icon = 0x4349; - - /// - /// OS/2 Pointer. - /// - public const int Pointer = 0x5450; - } + public const int Pointer = 0x5450; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 0b93cf01fc..35d5690c7d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,49 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Image decoder for generating an image out of a Windows bitmap stream. +/// +public class BmpDecoder : IImageDecoderSpecialized { - /// - /// Image decoder for generating an image out of a Windows bitmap stream. - /// - public class BmpDecoder : IImageDecoderSpecialized + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new BmpDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + return new BmpDecoderCore(new() { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); + } - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - /// - Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - Image image = new BmpDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + /// + Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options.GeneralOptions, image); + Image image = new BmpDecoderCore(options).Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options.GeneralOptions, image); - /// - Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoderSpecialized.Decode(BmpDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 0b4b987490..1629d66843 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1,12 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; -using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -14,1068 +12,1047 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Performs the bitmap decoding operation. +/// +/// +/// A useful decoding source example can be found at +/// +internal sealed class BmpDecoderCore : IImageDecoderInternals { /// - /// Performs the bitmap decoding operation. + /// The default mask for the red part of the color for 16 bit rgb bitmaps. /// - /// - /// A useful decoding source example can be found at - /// - internal sealed class BmpDecoderCore : IImageDecoderInternals + private const int DefaultRgb16RMask = 0x7C00; + + /// + /// The default mask for the green part of the color for 16 bit rgb bitmaps. + /// + private const int DefaultRgb16GMask = 0x3E0; + + /// + /// The default mask for the blue part of the color for 16 bit rgb bitmaps. + /// + private const int DefaultRgb16BMask = 0x1F; + + /// + /// RLE flag value that indicates following byte has special meaning. + /// + private const int RleCommand = 0x00; + + /// + /// RLE flag value marking end of a scan line. + /// + private const int RleEndOfLine = 0x00; + + /// + /// RLE flag value marking end of bitmap data. + /// + private const int RleEndOfBitmap = 0x01; + + /// + /// RLE flag value marking the start of [x,y] offset instruction. + /// + private const int RleDelta = 0x02; + + /// + /// The stream to decode from. + /// + private BufferedReadStream stream; + + /// + /// The metadata. + /// + private ImageMetadata metadata; + + /// + /// The bitmap specific metadata. + /// + private BmpMetadata bmpMetadata; + + /// + /// The file header containing general information. + /// + private BmpFileHeader fileHeader; + + /// + /// Indicates which bitmap file marker was read. + /// + private BmpFileMarkerType fileMarkerType; + + /// + /// The info header containing detailed information about the bitmap. + /// + private BmpInfoHeader infoHeader; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// How to deal with skipped pixels, + /// which can occur during decoding run length encoded bitmaps. + /// + private readonly RleSkippedPixelHandling rleSkippedPixelHandling; + + /// + /// Initializes a new instance of the class. + /// + /// The options. + public BmpDecoderCore(BmpDecoderOptions options) { - /// - /// The default mask for the red part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16RMask = 0x7C00; - - /// - /// The default mask for the green part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16GMask = 0x3E0; - - /// - /// The default mask for the blue part of the color for 16 bit rgb bitmaps. - /// - private const int DefaultRgb16BMask = 0x1F; - - /// - /// RLE flag value that indicates following byte has special meaning. - /// - private const int RleCommand = 0x00; - - /// - /// RLE flag value marking end of a scan line. - /// - private const int RleEndOfLine = 0x00; - - /// - /// RLE flag value marking end of bitmap data. - /// - private const int RleEndOfBitmap = 0x01; - - /// - /// RLE flag value marking the start of [x,y] offset instruction. - /// - private const int RleDelta = 0x02; - - /// - /// The stream to decode from. - /// - private BufferedReadStream stream; - - /// - /// The metadata. - /// - private ImageMetadata metadata; - - /// - /// The bitmap specific metadata. - /// - private BmpMetadata bmpMetadata; - - /// - /// The file header containing general information. - /// - private BmpFileHeader fileHeader; - - /// - /// Indicates which bitmap file marker was read. - /// - private BmpFileMarkerType fileMarkerType; - - /// - /// The info header containing detailed information about the bitmap. - /// - private BmpInfoHeader infoHeader; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// How to deal with skipped pixels, - /// which can occur during decoding run length encoded bitmaps. - /// - private readonly RleSkippedPixelHandling rleSkippedPixelHandling; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - public BmpDecoderCore(BmpDecoderOptions options) - { - this.Options = options.GeneralOptions; - this.rleSkippedPixelHandling = options.RleSkippedPixelHandling; - this.configuration = options.GeneralOptions.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - } + this.Options = options.GeneralOptions; + this.rleSkippedPixelHandling = options.RleSkippedPixelHandling; + this.configuration = options.GeneralOptions.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - public DecoderOptions Options { get; } + /// + public DecoderOptions Options { get; } - /// - public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); + /// + public Size Dimensions => new(this.infoHeader.Width, this.infoHeader.Height); - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Image image = null; + try { - Image image = null; - try - { - int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); + int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); - image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); + image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + Buffer2D pixels = image.GetRootFramePixelBuffer(); - switch (this.infoHeader.Compression) - { - case BmpCompression.RGB: - if (this.infoHeader.BitsPerPixel == 32) - { - if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) - { - this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else - { - this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - } - else if (this.infoHeader.BitsPerPixel == 24) - { - this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel == 16) + switch (this.infoHeader.Compression) + { + case BmpCompression.RGB: + if (this.infoHeader.BitsPerPixel == 32) + { + if (this.bmpMetadata.InfoHeaderType == BmpInfoHeaderType.WinVersion3) { - this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } - else if (this.infoHeader.BitsPerPixel <= 8) + else { - this.ReadRgbPalette( - pixels, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - bytesPerColorMapEntry, - inverted); + this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } + } + else if (this.infoHeader.BitsPerPixel == 24) + { + this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel <= 8) + { + this.ReadRgbPalette( + pixels, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + bytesPerColorMapEntry, + inverted); + } - break; - - case BmpCompression.RLE24: - this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); + break; - break; + case BmpCompression.RLE24: + this.ReadRle24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); - case BmpCompression.RLE8: - case BmpCompression.RLE4: - this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); + break; - break; + case BmpCompression.RLE8: + case BmpCompression.RLE4: + this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); - case BmpCompression.BitFields: - case BmpCompression.BI_ALPHABITFIELDS: - this.ReadBitFields(pixels, inverted); + break; - break; + case BmpCompression.BitFields: + case BmpCompression.BI_ALPHABITFIELDS: + this.ReadBitFields(pixels, inverted); - default: - BmpThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of bitmap files."); + break; - break; - } + default: + BmpThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of bitmap files."); - return image; - } - catch (IndexOutOfRangeException e) - { - image?.Dispose(); - throw new ImageFormatException("Bitmap does not have a valid format.", e); - } - catch - { - image?.Dispose(); - throw; + break; } - } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + return image; + } + catch (IndexOutOfRangeException e) { - this.ReadImageHeaders(stream, out _, out _); - return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); + image?.Dispose(); + throw new ImageFormatException("Bitmap does not have a valid format.", e); } + catch + { + image?.Dispose(); + throw; + } + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ReadImageHeaders(stream, out _, out _); + return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata); + } + + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; + + /// + /// Calculates the amount of bytes to pad a row. + /// + /// The image width. + /// The pixel component count. + /// + /// The padding. + /// + private static int CalculatePadding(int width, int componentCount) + { + int padding = (width * componentCount) % 4; - /// - /// Returns the y- value based on the given height. - /// - /// The y- value representing the current row. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; - - /// - /// Calculates the amount of bytes to pad a row. - /// - /// The image width. - /// The pixel component count. - /// - /// The padding. - /// - private static int CalculatePadding(int width, int componentCount) + if (padding != 0) { - int padding = (width * componentCount) % 4; + padding = 4 - padding; + } - if (padding != 0) - { - padding = 4 - padding; - } + return padding; + } - return padding; + /// + /// Decodes a bitmap containing the BITFIELDS Compression type. For each color channel, there will be a bitmask + /// which will be used to determine which bits belong to that channel. + /// + /// The pixel format. + /// The output pixel buffer containing the decoded image. + /// Whether the bitmap is inverted. + private void ReadBitFields(Buffer2D pixels, bool inverted) + where TPixel : unmanaged, IPixel + { + if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask); + } + else + { + this.ReadRgb32BitFields( + pixels, + this.infoHeader.Width, + this.infoHeader.Height, + inverted, + this.infoHeader.RedMask, + this.infoHeader.GreenMask, + this.infoHeader.BlueMask, + this.infoHeader.AlphaMask); } + } - /// - /// Decodes a bitmap containing the BITFIELDS Compression type. For each color channel, there will be a bitmask - /// which will be used to determine which bits belong to that channel. - /// - /// The pixel format. - /// The output pixel buffer containing the decoded image. - /// Whether the bitmap is inverted. - private void ReadBitFields(Buffer2D pixels, bool inverted) - where TPixel : unmanaged, IPixel + /// + /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data. + /// Compressed RLE8 stream is uncompressed by + /// Compressed RLE4 stream is uncompressed by + /// + /// The pixel format. + /// The compression type. Either RLE4 or RLE8. + /// The to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// 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 : unmanaged, IPixel + { + TPixel color = default; + 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)) { - if (this.infoHeader.BitsPerPixel == 16) + Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.Memory.Span; + if (compression is BmpCompression.RLE8) { - this.ReadRgb16( - pixels, - this.infoHeader.Width, - this.infoHeader.Height, - inverted, - this.infoHeader.RedMask, - this.infoHeader.GreenMask, - this.infoHeader.BlueMask); + this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } else { - this.ReadRgb32BitFields( - pixels, - this.infoHeader.Width, - this.infoHeader.Height, - inverted, - this.infoHeader.RedMask, - this.infoHeader.GreenMask, - this.infoHeader.BlueMask, - this.infoHeader.AlphaMask); + this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } - } - /// - /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data. - /// Compressed RLE8 stream is uncompressed by - /// Compressed RLE4 stream is uncompressed by - /// - /// The pixel format. - /// The compression type. Either RLE4 or RLE8. - /// The to assign the palette to. - /// The containing the colors. - /// The width of the bitmap. - /// 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 : unmanaged, IPixel - { - TPixel color = default; - 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)) + for (int y = 0; y < height; y++) { - Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - Span undefinedPixelsSpan = undefinedPixels.Memory.Span; - Span bufferSpan = buffer.Memory.Span; - if (compression is BmpCompression.RLE8) - { - this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); - } - else - { - this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); - } + int newY = Invert(y, height, inverted); + int rowStartIdx = y * width; + Span bufferRow = bufferSpan.Slice(rowStartIdx, width); + Span pixelRow = pixels.DangerousGetRowSpan(newY); - for (int y = 0; y < height; y++) + bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; + if (rowHasUndefinedPixels) { - int newY = Invert(y, height, inverted); - int rowStartIdx = y * width; - Span bufferRow = bufferSpan.Slice(rowStartIdx, width); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - - bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; - if (rowHasUndefinedPixels) + // Slow path with undefined pixels. + for (int x = 0; x < width; x++) { - // Slow path with undefined pixels. - for (int x = 0; x < width; x++) + byte colorIdx = bufferRow[x]; + if (undefinedPixelsSpan[rowStartIdx + x]) { - byte colorIdx = bufferRow[x]; - if (undefinedPixelsSpan[rowStartIdx + x]) - { - switch (this.rleSkippedPixelHandling) - { - case RleSkippedPixelHandling.FirstColorOfPalette: - color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); - break; - case RleSkippedPixelHandling.Transparent: - color.FromScaledVector4(Vector4.Zero); - break; - - // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - default: - color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); - break; - } - } - else + switch (this.rleSkippedPixelHandling) { - color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); + case RleSkippedPixelHandling.FirstColorOfPalette: + color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); + break; + case RleSkippedPixelHandling.Transparent: + color.FromScaledVector4(Vector4.Zero); + break; + + // Default handling for skipped pixels is black (which is what System.Drawing is also doing). + default: + color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + break; } - - pixelRow[x] = color; } - } - else - { - // Fast path without any undefined pixels. - for (int x = 0; x < width; x++) + else { - color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); - pixelRow[x] = color; + color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4])); } + + pixelRow[x] = color; + } + } + else + { + // Fast path without any undefined pixels. + for (int x = 0; x < width; x++) + { + color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4])); + pixelRow[x] = color; } } } } + } - /// - /// Looks up color values and builds the image from de-compressed RLE24. - /// - /// The pixel format. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRle24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel + /// + /// Looks up color values and builds the image from de-compressed RLE24. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRle24(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) + using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { - TPixel color = default; - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, 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(); + Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.GetSpan(); - this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); - for (int y = 0; y < height; y++) + this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; + if (rowHasUndefinedPixels) { - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; - if (rowHasUndefinedPixels) + // Slow path with undefined pixels. + int yMulWidth = y * width; + int rowStartIdx = yMulWidth * 3; + for (int x = 0; x < width; x++) { - // Slow path with undefined pixels. - int yMulWidth = y * width; - int rowStartIdx = yMulWidth * 3; - for (int x = 0; x < width; x++) + int idx = rowStartIdx + (x * 3); + if (undefinedPixelsSpan[yMulWidth + x]) { - int idx = rowStartIdx + (x * 3); - if (undefinedPixelsSpan[yMulWidth + x]) - { - switch (this.rleSkippedPixelHandling) - { - case RleSkippedPixelHandling.FirstColorOfPalette: - color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - break; - case RleSkippedPixelHandling.Transparent: - color.FromScaledVector4(Vector4.Zero); - break; - - // Default handling for skipped pixels is black (which is what System.Drawing is also doing). - default: - color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); - break; - } - } - else + switch (this.rleSkippedPixelHandling) { - color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + case RleSkippedPixelHandling.FirstColorOfPalette: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case RleSkippedPixelHandling.Transparent: + color.FromScaledVector4(Vector4.Zero); + break; + + // Default handling for skipped pixels is black (which is what System.Drawing is also doing). + default: + color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + break; } - - pixelRow[x] = color; } - } - else - { - // Fast path without any undefined pixels. - int rowStartIdx = y * width * 3; - for (int x = 0; x < width; x++) + else { - int idx = rowStartIdx + (x * 3); color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - pixelRow[x] = color; } + + pixelRow[x] = color; + } + } + else + { + // Fast path without any undefined pixels. + int rowStartIdx = y * width * 3; + for (int x = 0; x < width; x++) + { + int idx = rowStartIdx + (x * 3); + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + pixelRow[x] = color; } } } } + } - /// - /// Produce uncompressed bitmap data from a RLE4 stream. - /// - /// - /// RLE4 is a 2-byte run-length encoding. - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes. - ///
- /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track over skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle4(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span cmd = stackalloc byte[2]; - int count = 0; + /// + /// Produce uncompressed bitmap data from a RLE4 stream. + /// + /// + /// RLE4 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + /// Keeps track over skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle4(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) + { + Span cmd = stackalloc byte[2]; + int count = 0; - while (count < buffer.Length) + while (count < buffer.Length) + { + if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - if (this.stream.Read(cmd, 0, cmd.Length) != 2) - { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); - } + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap."); + } - if (cmd[0] == RleCommand) + if (cmd[0] == RleCommand) + { + switch (cmd[1]) { - switch (cmd[1]) - { - case RleEndOfBitmap: - int skipEoB = buffer.Length - count; - RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfBitmap: + int skipEoB = buffer.Length - count; + RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); - return; + return; - case RleEndOfLine: - count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfLine: + count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - case RleDelta: - int dx = this.stream.ReadByte(); - int dy = this.stream.ReadByte(); - count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - default: - // If the second byte > 2, we are in 'absolute mode'. - // The second byte contains the number of color indexes that follow. - int max = cmd[1]; - int bytesToRead = (max + 1) / 2; + default: + // If the second byte > 2, we are in 'absolute mode'. + // The second byte contains the number of color indexes that follow. + int max = cmd[1]; + int bytesToRead = (max + 1) / 2; - byte[] run = new byte[bytesToRead]; + byte[] run = new byte[bytesToRead]; - this.stream.Read(run, 0, run.Length); + this.stream.Read(run, 0, run.Length); - int idx = 0; - for (int i = 0; i < max; i++) + int idx = 0; + for (int i = 0; i < max; i++) + { + byte twoPixels = run[idx]; + if (i % 2 == 0) + { + buffer[count++] = (byte)((twoPixels >> 4) & 0xF); + } + else { - byte twoPixels = run[idx]; - if (i % 2 == 0) - { - buffer[count++] = (byte)((twoPixels >> 4) & 0xF); - } - else - { - buffer[count++] = (byte)(twoPixels & 0xF); - idx++; - } + buffer[count++] = (byte)(twoPixels & 0xF); + idx++; } + } - // Absolute mode data is aligned to two-byte word-boundary. - int padding = bytesToRead & 1; + // Absolute mode data is aligned to two-byte word-boundary. + int padding = bytesToRead & 1; - this.stream.Skip(padding); + this.stream.Skip(padding); - break; - } + break; } - else - { - int max = cmd[0]; + } + else + { + int max = cmd[0]; - // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. - byte twoPixels = cmd[1]; - byte rightPixel = (byte)(twoPixels & 0xF); - byte leftPixel = (byte)((twoPixels >> 4) & 0xF); + // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits. + byte twoPixels = cmd[1]; + byte rightPixel = (byte)(twoPixels & 0xF); + byte leftPixel = (byte)((twoPixels >> 4) & 0xF); - for (int idx = 0; idx < max; idx++) + for (int idx = 0; idx < max; idx++) + { + if (idx % 2 == 0) { - if (idx % 2 == 0) - { - buffer[count] = leftPixel; - } - else - { - buffer[count] = rightPixel; - } - - count++; + buffer[count] = leftPixel; + } + else + { + buffer[count] = rightPixel; } + + count++; } } } + } - /// - /// Produce uncompressed bitmap data from a RLE8 stream. - /// - /// - /// RLE8 is a 2-byte run-length encoding. - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and second byte is the color for the run. - ///
- /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track of skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle8(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span cmd = stackalloc byte[2]; - int count = 0; + /// + /// Produce uncompressed bitmap data from a RLE8 stream. + /// + /// + /// RLE8 is a 2-byte run-length encoding. + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and second byte is the color for the run. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + /// Keeps track of skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle8(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) + { + Span cmd = stackalloc byte[2]; + int count = 0; - while (count < buffer.Length) + while (count < buffer.Length) + { + if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - if (this.stream.Read(cmd, 0, cmd.Length) != 2) - { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); - } + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap."); + } - if (cmd[0] == RleCommand) + if (cmd[0] == RleCommand) + { + switch (cmd[1]) { - switch (cmd[1]) - { - case RleEndOfBitmap: - int skipEoB = buffer.Length - count; - RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfBitmap: + int skipEoB = buffer.Length - count; + RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); - return; + return; - case RleEndOfLine: - count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfLine: + count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - case RleDelta: - int dx = this.stream.ReadByte(); - int dy = this.stream.ReadByte(); - count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - default: - // If the second byte > 2, we are in 'absolute mode'. - // Take this number of bytes from the stream as uncompressed data. - int length = cmd[1]; + default: + // If the second byte > 2, we are in 'absolute mode'. + // Take this number of bytes from the stream as uncompressed data. + int length = cmd[1]; - byte[] run = new byte[length]; + byte[] run = new byte[length]; - this.stream.Read(run, 0, run.Length); + this.stream.Read(run, 0, run.Length); - run.AsSpan().CopyTo(buffer[count..]); + run.AsSpan().CopyTo(buffer[count..]); - count += run.Length; + count += run.Length; - // Absolute mode data is aligned to two-byte word-boundary. - int padding = length & 1; + // Absolute mode data is aligned to two-byte word-boundary. + int padding = length & 1; - this.stream.Skip(padding); + this.stream.Skip(padding); - break; - } + break; } - else - { - int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0] - byte colorIdx = cmd[1]; // store the value to avoid the repeated indexer access inside the loop. + } + else + { + int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0] + byte colorIdx = cmd[1]; // store the value to avoid the repeated indexer access inside the loop. - for (; count < max; count++) - { - buffer[count] = colorIdx; - } + for (; count < max; count++) + { + buffer[count] = colorIdx; } } } + } - /// - /// Produce uncompressed bitmap data from a RLE24 stream. - /// - /// - ///
If first byte is 0, the second byte may have special meaning. - ///
Otherwise, the first byte is the length of the run and following three bytes are the color for the run. - ///
- /// The width of the bitmap. - /// Buffer for uncompressed data. - /// Keeps track of skipped and therefore undefined pixels. - /// Keeps track of rows, which have undefined pixels. - private void UncompressRle24(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) - { - Span cmd = stackalloc byte[2]; - int uncompressedPixels = 0; + /// + /// Produce uncompressed bitmap data from a RLE24 stream. + /// + /// + ///
If first byte is 0, the second byte may have special meaning. + ///
Otherwise, the first byte is the length of the run and following three bytes are the color for the run. + ///
+ /// The width of the bitmap. + /// Buffer for uncompressed data. + /// Keeps track of skipped and therefore undefined pixels. + /// Keeps track of rows, which have undefined pixels. + private void UncompressRle24(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels) + { + Span cmd = stackalloc byte[2]; + int uncompressedPixels = 0; - while (uncompressedPixels < buffer.Length) + while (uncompressedPixels < buffer.Length) + { + if (this.stream.Read(cmd, 0, cmd.Length) != 2) { - if (this.stream.Read(cmd, 0, cmd.Length) != 2) - { - BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); - } + BmpThrowHelper.ThrowInvalidImageContentException("Failed to read 2 bytes from stream while uncompressing RLE24 bitmap."); + } - if (cmd[0] == RleCommand) + if (cmd[0] == RleCommand) + { + switch (cmd[1]) { - switch (cmd[1]) - { - case RleEndOfBitmap: - int skipEoB = (buffer.Length - (uncompressedPixels * 3)) / 3; - RleSkipEndOfBitmap(uncompressedPixels, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfBitmap: + int skipEoB = (buffer.Length - (uncompressedPixels * 3)) / 3; + RleSkipEndOfBitmap(uncompressedPixels, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels); - return; + return; - case RleEndOfLine: - uncompressedPixels += RleSkipEndOfLine(uncompressedPixels, w, undefinedPixels, rowsWithUndefinedPixels); + case RleEndOfLine: + uncompressedPixels += RleSkipEndOfLine(uncompressedPixels, w, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - case RleDelta: - int dx = this.stream.ReadByte(); - int dy = this.stream.ReadByte(); - uncompressedPixels += RleSkipDelta(uncompressedPixels, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); + case RleDelta: + int dx = this.stream.ReadByte(); + int dy = this.stream.ReadByte(); + uncompressedPixels += RleSkipDelta(uncompressedPixels, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels); - break; + break; - default: - // If the second byte > 2, we are in 'absolute mode'. - // Take this number of bytes from the stream as uncompressed data. - int length = cmd[1]; + default: + // If the second byte > 2, we are in 'absolute mode'. + // Take this number of bytes from the stream as uncompressed data. + int length = cmd[1]; - byte[] run = new byte[length * 3]; + byte[] run = new byte[length * 3]; - this.stream.Read(run, 0, run.Length); + this.stream.Read(run, 0, run.Length); - run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]); + run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]); - uncompressedPixels += length; + uncompressedPixels += length; - // Absolute mode data is aligned to two-byte word-boundary. - int padding = run.Length & 1; + // Absolute mode data is aligned to two-byte word-boundary. + int padding = run.Length & 1; - this.stream.Skip(padding); + this.stream.Skip(padding); - break; - } + break; } - else - { - int max = uncompressedPixels + cmd[0]; - byte blueIdx = cmd[1]; - byte greenIdx = (byte)this.stream.ReadByte(); - byte redIdx = (byte)this.stream.ReadByte(); + } + else + { + int max = uncompressedPixels + cmd[0]; + byte blueIdx = cmd[1]; + byte greenIdx = (byte)this.stream.ReadByte(); + byte redIdx = (byte)this.stream.ReadByte(); - int bufferIdx = uncompressedPixels * 3; - for (; uncompressedPixels < max; uncompressedPixels++) - { - buffer[bufferIdx++] = blueIdx; - buffer[bufferIdx++] = greenIdx; - buffer[bufferIdx++] = redIdx; - } + int bufferIdx = uncompressedPixels * 3; + for (; uncompressedPixels < max; uncompressedPixels++) + { + buffer[bufferIdx++] = blueIdx; + buffer[bufferIdx++] = greenIdx; + buffer[bufferIdx++] = redIdx; } } } + } - /// - /// Keeps track of skipped / undefined pixels, when the EndOfBitmap command occurs. - /// - /// The already processed pixel count. - /// The width of the image. - /// The skipped pixel count. - /// The undefined pixels. - /// Rows with undefined pixels. - private static void RleSkipEndOfBitmap( - int count, - int w, - int skipPixelCount, - Span undefinedPixels, - Span rowsWithUndefinedPixels) + /// + /// Keeps track of skipped / undefined pixels, when the EndOfBitmap command occurs. + /// + /// The already processed pixel count. + /// The width of the image. + /// The skipped pixel count. + /// The undefined pixels. + /// Rows with undefined pixels. + private static void RleSkipEndOfBitmap( + int count, + int w, + int skipPixelCount, + Span undefinedPixels, + Span rowsWithUndefinedPixels) + { + for (int i = count; i < count + skipPixelCount; i++) { - for (int i = count; i < count + skipPixelCount; i++) - { - undefinedPixels[i] = true; - } - - int skippedRowIdx = count / w; - int skippedRows = (skipPixelCount / w) - 1; - int lastSkippedRow = Math.Min(skippedRowIdx + skippedRows, rowsWithUndefinedPixels.Length - 1); - for (int i = skippedRowIdx; i <= lastSkippedRow; i++) - { - rowsWithUndefinedPixels[i] = true; - } + undefinedPixels[i] = true; } - /// - /// Keeps track of undefined / skipped pixels, when the EndOfLine command occurs. - /// - /// The already uncompressed pixel count. - /// The width of image. - /// The undefined pixels. - /// The rows with undefined pixels. - /// The number of skipped pixels. - private static int RleSkipEndOfLine(int count, int w, Span undefinedPixels, Span rowsWithUndefinedPixels) + int skippedRowIdx = count / w; + int skippedRows = (skipPixelCount / w) - 1; + int lastSkippedRow = Math.Min(skippedRowIdx + skippedRows, rowsWithUndefinedPixels.Length - 1); + for (int i = skippedRowIdx; i <= lastSkippedRow; i++) { - rowsWithUndefinedPixels[count / w] = true; - int remainingPixelsInRow = count % w; - if (remainingPixelsInRow > 0) - { - int skipEoL = w - remainingPixelsInRow; - for (int i = count; i < count + skipEoL; i++) - { - undefinedPixels[i] = true; - } - - return skipEoL; - } - - return 0; + rowsWithUndefinedPixels[i] = true; } + } - /// - /// Keeps track of undefined / skipped pixels, when the delta command occurs. - /// - /// The count. - /// The width of the image. - /// Delta skip in x direction. - /// Delta skip in y direction. - /// The undefined pixels. - /// The rows with undefined pixels. - /// The number of skipped pixels. - private static int RleSkipDelta( - int count, - int w, - int dx, - int dy, - Span undefinedPixels, - Span rowsWithUndefinedPixels) + /// + /// Keeps track of undefined / skipped pixels, when the EndOfLine command occurs. + /// + /// The already uncompressed pixel count. + /// The width of image. + /// The undefined pixels. + /// The rows with undefined pixels. + /// The number of skipped pixels. + private static int RleSkipEndOfLine(int count, int w, Span undefinedPixels, Span rowsWithUndefinedPixels) + { + rowsWithUndefinedPixels[count / w] = true; + int remainingPixelsInRow = count % w; + if (remainingPixelsInRow > 0) { - int skipDelta = (w * dy) + dx; - for (int i = count; i < count + skipDelta; i++) + int skipEoL = w - remainingPixelsInRow; + for (int i = count; i < count + skipEoL; i++) { undefinedPixels[i] = true; } - int skippedRowIdx = count / w; - int lastSkippedRow = Math.Min(skippedRowIdx + dy, rowsWithUndefinedPixels.Length - 1); - for (int i = skippedRowIdx; i <= lastSkippedRow; i++) - { - rowsWithUndefinedPixels[i] = true; - } + return skipEoL; + } + + return 0; + } - return skipDelta; + /// + /// Keeps track of undefined / skipped pixels, when the delta command occurs. + /// + /// The count. + /// The width of the image. + /// Delta skip in x direction. + /// Delta skip in y direction. + /// The undefined pixels. + /// The rows with undefined pixels. + /// The number of skipped pixels. + private static int RleSkipDelta( + int count, + int w, + int dx, + int dy, + Span undefinedPixels, + Span rowsWithUndefinedPixels) + { + int skipDelta = (w * dy) + dx; + for (int i = count; i < count + skipDelta; i++) + { + undefinedPixels[i] = true; } - /// - /// Reads the color palette from the stream. - /// - /// The pixel format. - /// The to assign the palette to. - /// The containing the colors. - /// The width of the bitmap. - /// The height of the bitmap. - /// The number of bits per pixel. - /// Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps - /// 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 : unmanaged, IPixel + int skippedRowIdx = count / w; + int lastSkippedRow = Math.Min(skippedRowIdx + dy, rowsWithUndefinedPixels.Length - 1); + for (int i = skippedRowIdx; i <= lastSkippedRow; i++) { - // Pixels per byte (bits per pixel). - int ppb = 8 / bitsPerPixel; + rowsWithUndefinedPixels[i] = true; + } + + return skipDelta; + } - int arrayWidth = (width + ppb - 1) / ppb; + /// + /// Reads the color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// The number of bits per pixel. + /// Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps + /// 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 : unmanaged, IPixel + { + // Pixels per byte (bits per pixel). + int ppb = 8 / bitsPerPixel; - // Bit mask - int mask = 0xFF >> (8 - bitsPerPixel); + int arrayWidth = (width + ppb - 1) / ppb; - // Rows are aligned on 4 byte boundaries. - int padding = arrayWidth % 4; - if (padding != 0) - { - padding = 4 - padding; - } + // Bit mask + int mask = 0xFF >> (8 - bitsPerPixel); - using IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean); - TPixel color = default; - Span rowSpan = row.GetSpan(); + // Rows are aligned on 4 byte boundaries. + int padding = arrayWidth % 4; + if (padding != 0) + { + padding = 4 - padding; + } - for (int y = 0; y < height; y++) + using IMemoryOwner row = this.memoryAllocator.Allocate(arrayWidth + padding, AllocationOptions.Clean); + TPixel color = default; + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + int newY = Invert(y, height, inverted); + if (this.stream.Read(rowSpan) == 0) { - int newY = Invert(y, height, inverted); - if (this.stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } - int offset = 0; - Span pixelRow = pixels.DangerousGetRowSpan(newY); + int offset = 0; + Span pixelRow = pixels.DangerousGetRowSpan(newY); - for (int x = 0; x < arrayWidth; x++) + for (int x = 0; x < arrayWidth; x++) + { + int colOffset = x * ppb; + for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) { - int colOffset = x * ppb; - for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++) - { - int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - - color.FromBgr24(Unsafe.As(ref colors[colorIndex])); - pixelRow[newX] = color; - } + int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry; - offset++; + color.FromBgr24(Unsafe.As(ref colors[colorIndex])); + pixelRow[newX] = color; } + + offset++; } } + } - /// - /// Reads the 16 bit color palette from the stream. - /// - /// The pixel format. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - /// The bitmask for the red channel. - /// 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 : unmanaged, IPixel - { - int padding = CalculatePadding(width, 2); - int stride = (width * 2) + padding; - TPixel color = default; + /// + /// Reads the 16 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + /// The bitmask for the red channel. + /// 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 : unmanaged, IPixel + { + int padding = CalculatePadding(width, 2); + int stride = (width * 2) + padding; + TPixel color = default; - int rightShiftRedMask = CalculateRightShift((uint)redMask); - int rightShiftGreenMask = CalculateRightShift((uint)greenMask); - int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); - // Each color channel contains either 5 or 6 Bits values. - int redMaskBits = CountBits((uint)redMask); - int greenMaskBits = CountBits((uint)greenMask); - int blueMaskBits = CountBits((uint)blueMask); + // Each color channel contains either 5 or 6 Bits values. + int redMaskBits = CountBits((uint)redMask); + int greenMaskBits = CountBits((uint)greenMask); + int blueMaskBits = CountBits((uint)blueMask); - using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); - Span bufferSpan = buffer.GetSpan(); + using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); + Span bufferSpan = buffer.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + if (this.stream.Read(bufferSpan) == 0) { - if (this.stream.Read(bufferSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.DangerousGetRowSpan(newY); - int offset = 0; - for (int x = 0; x < width; x++) - { - short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan[offset..]); + int offset = 0; + for (int x = 0; x < width; x++) + { + short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan[offset..]); - // Rescale values, so the values range from 0 to 255. - int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); - int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); - int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); - Rgb24 rgb = new((byte)r, (byte)g, (byte)b); + // Rescale values, so the values range from 0 to 255. + int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); + int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); + int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); + Rgb24 rgb = new((byte)r, (byte)g, (byte)b); - color.FromRgb24(rgb); - pixelRow[x] = color; - offset += 2; - } + color.FromRgb24(rgb); + pixelRow[x] = color; + offset += 2; } } + } - /// - /// Performs final shifting from a 5bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); - - /// - /// Performs final shifting from a 6bit value to an 8bit one. - /// - /// The masked and shifted value. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); - - /// - /// Reads the 24 bit color palette from the stream. - /// - /// The pixel format. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 3); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding); - Span rowSpan = row.GetSpan(); + /// + /// Performs final shifting from a 5bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); - for (int y = 0; y < height; y++) - { - if (this.stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + /// + /// Performs final shifting from a 6bit value to an 8bit one. + /// + /// The masked and shifted value. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4)); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - PixelOperations.Instance.FromBgr24Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); + /// + /// Reads the 24 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : unmanaged, IPixel + { + int padding = CalculatePadding(width, 3); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + if (this.stream.Read(rowSpan) == 0) + { + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); } + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); + PixelOperations.Instance.FromBgr24Bytes( + this.configuration, + rowSpan, + pixelSpan, + width); } + } - /// - /// Reads the 32 bit color palette from the stream. - /// - /// The pixel format. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel - { - int padding = CalculatePadding(width, 4); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); - Span rowSpan = row.GetSpan(); + /// + /// Reads the 32 bit color palette from the stream. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : unmanaged, IPixel + { + int padding = CalculatePadding(width, 4); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + if (this.stream.Read(rowSpan) == 0) { - if (this.stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); } + + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + rowSpan, + pixelSpan, + width); } + } - /// - /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. - /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. - /// - /// The pixel format. - /// The to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - /// Whether the bitmap is inverted. - private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : unmanaged, IPixel + /// + /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. + /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. + /// + /// The pixel format. + /// The to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + /// Whether the bitmap is inverted. + private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) + where TPixel : unmanaged, IPixel + { + int padding = CalculatePadding(width, 4); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); + using IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width); + Span rowSpan = row.GetSpan(); + Span bgraRowSpan = bgraRow.GetSpan(); + long currentPosition = this.stream.Position; + bool hasAlpha = false; + + // Loop though the rows checking each pixel. We start by assuming it's + // an BGR0 image. If we hit a non-zero alpha value, then we know it's + // actually a BGRA image, and change tactics accordingly. + for (int y = 0; y < height; y++) { - int padding = CalculatePadding(width, 4); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding); - using IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width); - Span rowSpan = row.GetSpan(); - Span bgraRowSpan = bgraRow.GetSpan(); - long currentPosition = this.stream.Position; - bool hasAlpha = false; - - // Loop though the rows checking each pixel. We start by assuming it's - // an BGR0 image. If we hit a non-zero alpha value, then we know it's - // actually a BGRA image, and change tactics accordingly. - for (int y = 0; y < height; y++) + if (this.stream.Read(rowSpan) == 0) { - if (this.stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - bgraRowSpan, - width); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + rowSpan, + bgraRowSpan, + width); - // Check each pixel in the row to see if it has an alpha value. - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - if (bgra.A > 0) - { - hasAlpha = true; - break; - } - } - - if (hasAlpha) + // Check each pixel in the row to see if it has an alpha value. + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + if (bgra.A > 0) { + hasAlpha = true; break; } } - // Reset our stream for a second pass. - this.stream.Position = currentPosition; - - // Process the pixels in bulk taking the raw alpha component value. if (hasAlpha) { - for (int y = 0; y < height; y++) - { - if (this.stream.Read(rowSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } - - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - pixelSpan, - width); - } - - return; + break; } + } - // Slow path. We need to set each alpha component value to fully opaque. + // Reset our stream for a second pass. + this.stream.Position = currentPosition; + + // Process the pixels in bulk taking the raw alpha component value. + if (hasAlpha) + { for (int y = 0; y < height; y++) { if (this.stream.Read(rowSpan) == 0) @@ -1083,407 +1060,427 @@ private void ReadRgb32Slow(Buffer2D pixels, int width, int heigh BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); } - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowSpan, - bgraRowSpan, - width); - int newY = Invert(y, height, inverted); Span pixelSpan = pixels.DangerousGetRowSpan(newY); - for (int x = 0; x < width; x++) - { - Bgra32 bgra = bgraRowSpan[x]; - bgra.A = byte.MaxValue; - ref TPixel pixel = ref pixelSpan[x]; - pixel.FromBgra32(bgra); - } + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + rowSpan, + pixelSpan, + width); } + + return; } - /// - /// Decode an 32 Bit Bitmap containing a bitmask for each color channel. - /// - /// The pixel format. - /// The output pixel buffer containing the decoded image. - /// The width of the image. - /// The height of the image. - /// Whether the bitmap is inverted. - /// The bitmask for the red channel. - /// The bitmask for the green channel. - /// 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 : unmanaged, IPixel + // Slow path. We need to set each alpha component value to fully opaque. + for (int y = 0; y < height; y++) { - TPixel color = default; - int padding = CalculatePadding(width, 4); - int stride = (width * 4) + padding; - - int rightShiftRedMask = CalculateRightShift((uint)redMask); - int rightShiftGreenMask = CalculateRightShift((uint)greenMask); - int rightShiftBlueMask = CalculateRightShift((uint)blueMask); - int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask); - - int bitsRedMask = CountBits((uint)redMask); - int bitsGreenMask = CountBits((uint)greenMask); - int bitsBlueMask = CountBits((uint)blueMask); - int bitsAlphaMask = CountBits((uint)alphaMask); - float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask)); - float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask)); - float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask)); - uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); - float invMaxValueAlpha = 1.0f / maxValueAlpha; - - bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; - - using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); - Span bufferSpan = buffer.GetSpan(); - - for (int y = 0; y < height; y++) + if (this.stream.Read(rowSpan) == 0) { - if (this.stream.Read(bufferSpan) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); - } - - int newY = Invert(y, height, inverted); - Span pixelRow = pixels.DangerousGetRowSpan(newY); + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } - int offset = 0; - for (int x = 0; x < width; x++) - { - uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan[offset..]); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + rowSpan, + bgraRowSpan, + width); - if (unusualBitMask) - { - uint r = (uint)(temp & redMask) >> rightShiftRedMask; - uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; - uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; - float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; - Vector4 vector4 = new( - r * invMaxValueRed, - g * invMaxValueGreen, - b * invMaxValueBlue, - alpha); - color.FromScaledVector4(vector4); - } - else - { - byte r = (byte)((temp & redMask) >> rightShiftRedMask); - byte g = (byte)((temp & greenMask) >> rightShiftGreenMask); - byte b = (byte)((temp & blueMask) >> rightShiftBlueMask); - byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255; - color.FromRgba32(new Rgba32(r, g, b, a)); - } + int newY = Invert(y, height, inverted); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); - pixelRow[x] = color; - offset += 4; - } + for (int x = 0; x < width; x++) + { + Bgra32 bgra = bgraRowSpan[x]; + bgra.A = byte.MaxValue; + ref TPixel pixel = ref pixelSpan[x]; + pixel.FromBgra32(bgra); } } + } - /// - /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right). - /// - /// The color bit mask. - /// Number of bits to shift right. - private static int CalculateRightShift(uint n) + /// + /// Decode an 32 Bit Bitmap containing a bitmask for each color channel. + /// + /// The pixel format. + /// The output pixel buffer containing the decoded image. + /// The width of the image. + /// The height of the image. + /// Whether the bitmap is inverted. + /// The bitmask for the red channel. + /// The bitmask for the green channel. + /// 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 : unmanaged, IPixel + { + TPixel color = default; + int padding = CalculatePadding(width, 4); + int stride = (width * 4) + padding; + + int rightShiftRedMask = CalculateRightShift((uint)redMask); + int rightShiftGreenMask = CalculateRightShift((uint)greenMask); + int rightShiftBlueMask = CalculateRightShift((uint)blueMask); + int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask); + + int bitsRedMask = CountBits((uint)redMask); + int bitsGreenMask = CountBits((uint)greenMask); + int bitsBlueMask = CountBits((uint)blueMask); + int bitsAlphaMask = CountBits((uint)alphaMask); + float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask)); + float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask)); + float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask)); + uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask); + float invMaxValueAlpha = 1.0f / maxValueAlpha; + + bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; + + using IMemoryOwner buffer = this.memoryAllocator.Allocate(stride); + Span bufferSpan = buffer.GetSpan(); + + for (int y = 0; y < height; y++) { - int count = 0; - while (n > 0) + if (this.stream.Read(bufferSpan) == 0) { - if ((1 & n) == 0) + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!"); + } + + int newY = Invert(y, height, inverted); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + + int offset = 0; + for (int x = 0; x < width; x++) + { + uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan[offset..]); + + if (unusualBitMask) { - count++; + uint r = (uint)(temp & redMask) >> rightShiftRedMask; + uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; + uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; + float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; + Vector4 vector4 = new( + r * invMaxValueRed, + g * invMaxValueGreen, + b * invMaxValueBlue, + alpha); + color.FromScaledVector4(vector4); } else { - break; + byte r = (byte)((temp & redMask) >> rightShiftRedMask); + byte g = (byte)((temp & greenMask) >> rightShiftGreenMask); + byte b = (byte)((temp & blueMask) >> rightShiftBlueMask); + byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255; + color.FromRgba32(new Rgba32(r, g, b, a)); } - n >>= 1; + pixelRow[x] = color; + offset += 4; } - - return count; } + } - /// - /// Counts none zero bits. - /// - /// A color mask. - /// The none zero bits. - private static int CountBits(uint n) + /// + /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right). + /// + /// The color bit mask. + /// Number of bits to shift right. + private static int CalculateRightShift(uint n) + { + int count = 0; + while (n > 0) { - int count = 0; - while (n != 0) + if ((1 & n) == 0) { count++; - n &= n - 1; + } + else + { + break; } - return count; + n >>= 1; } - /// - /// Reads the from the stream. - /// - private void ReadInfoHeader() + return count; + } + + /// + /// Counts none zero bits. + /// + /// A color mask. + /// The none zero bits. + private static int CountBits(uint n) + { + int count = 0; + while (n != 0) { - Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; - long infoHeaderStart = this.stream.Position; + count++; + n &= n - 1; + } - // Resolution is stored in PPM. - this.metadata = new ImageMetadata - { - ResolutionUnits = PixelResolutionUnit.PixelsPerMeter - }; + return count; + } - // Read the header size. - this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); + /// + /// Reads the from the stream. + /// + private void ReadInfoHeader() + { + Span buffer = stackalloc byte[BmpInfoHeader.MaxHeaderSize]; + long infoHeaderStart = this.stream.Position; - int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); - if (headerSize is < BmpInfoHeader.CoreSize or > BmpInfoHeader.MaxHeaderSize) - { - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); - } + // Resolution is stored in PPM. + this.metadata = new ImageMetadata + { + ResolutionUnits = PixelResolutionUnit.PixelsPerMeter + }; - // Read the rest of the header. - this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); + // Read the header size. + this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); - BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; - if (headerSize == BmpInfoHeader.CoreSize) - { - // 12 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion2; - this.infoHeader = BmpInfoHeader.ParseCore(buffer); - } - else if (headerSize == BmpInfoHeader.Os22ShortSize) - { - // 16 bytes - infoHeaderType = BmpInfoHeaderType.Os2Version2Short; - this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer); - } - else if (headerSize == BmpInfoHeader.SizeV3) - { - // == 40 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion3; - this.infoHeader = BmpInfoHeader.ParseV3(buffer); + int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer); + if (headerSize is < BmpInfoHeader.CoreSize or > BmpInfoHeader.MaxHeaderSize) + { + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'."); + } - // If the info header is BMP version 3 and the compression type is BITFIELDS, - // color masks for each color channel follow the info header. - if (this.infoHeader.Compression == BmpCompression.BitFields) - { - byte[] bitfieldsBuffer = new byte[12]; - this.stream.Read(bitfieldsBuffer, 0, 12); - Span data = bitfieldsBuffer.AsSpan(); - this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); - this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); - this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); - } - else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) - { - byte[] bitfieldsBuffer = new byte[16]; - this.stream.Read(bitfieldsBuffer, 0, 16); - Span data = bitfieldsBuffer.AsSpan(); - this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); - this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); - this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); - this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); - } - } - else if (headerSize == BmpInfoHeader.AdobeV3Size) - { - // == 52 bytes - infoHeaderType = BmpInfoHeaderType.AdobeVersion3; - this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false); - } - else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize) - { - // == 56 bytes - infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha; - this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true); - } - else if (headerSize == BmpInfoHeader.Os2v2Size) - { - // == 64 bytes - infoHeaderType = BmpInfoHeaderType.Os2Version2; - this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer); - } - else if (headerSize == BmpInfoHeader.SizeV4) - { - // == 108 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion4; - this.infoHeader = BmpInfoHeader.ParseV4(buffer); - } - else if (headerSize > BmpInfoHeader.SizeV4) - { - // > 108 bytes - infoHeaderType = BmpInfoHeaderType.WinVersion5; - this.infoHeader = BmpInfoHeader.ParseV5(buffer); - if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0) - { - // Read color profile. - long streamPosition = this.stream.Position; - byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; - this.stream.Position = infoHeaderStart + this.infoHeader.ProfileData; - this.stream.Read(iccProfileData); - this.metadata.IccProfile = new IccProfile(iccProfileData); - this.stream.Position = streamPosition; - } - } - else - { - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'."); - } + // Read the rest of the header. + this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize); + + BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2; + if (headerSize == BmpInfoHeader.CoreSize) + { + // 12 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion2; + this.infoHeader = BmpInfoHeader.ParseCore(buffer); + } + else if (headerSize == BmpInfoHeader.Os22ShortSize) + { + // 16 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2Short; + this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer); + } + else if (headerSize == BmpInfoHeader.SizeV3) + { + // == 40 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion3; + this.infoHeader = BmpInfoHeader.ParseV3(buffer); - if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) + // If the info header is BMP version 3 and the compression type is BITFIELDS, + // color masks for each color channel follow the info header. + if (this.infoHeader.Compression == BmpCompression.BitFields) { - this.metadata.HorizontalResolution = this.infoHeader.XPelsPerMeter; - this.metadata.VerticalResolution = this.infoHeader.YPelsPerMeter; + byte[] bitfieldsBuffer = new byte[12]; + this.stream.Read(bitfieldsBuffer, 0, 12); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); } - else + else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) { - // Convert default metadata values to PPM. - this.metadata.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution)); - this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); + byte[] bitfieldsBuffer = new byte[16]; + this.stream.Read(bitfieldsBuffer, 0, 16); + Span data = bitfieldsBuffer.AsSpan(); + this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]); + this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); + this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); + this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); } - - short bitsPerPixel = this.infoHeader.BitsPerPixel; - this.bmpMetadata = this.metadata.GetBmpMetadata(); - this.bmpMetadata.InfoHeaderType = infoHeaderType; - this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; } - - /// - /// Reads the from the stream. - /// - private void ReadFileHeader() + else if (headerSize == BmpInfoHeader.AdobeV3Size) { - Span buffer = stackalloc byte[BmpFileHeader.Size]; - this.stream.Read(buffer, 0, BmpFileHeader.Size); - - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); - switch (fileTypeMarker) + // == 52 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false); + } + else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize) + { + // == 56 bytes + infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha; + this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true); + } + else if (headerSize == BmpInfoHeader.Os2v2Size) + { + // == 64 bytes + infoHeaderType = BmpInfoHeaderType.Os2Version2; + this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer); + } + else if (headerSize == BmpInfoHeader.SizeV4) + { + // == 108 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion4; + this.infoHeader = BmpInfoHeader.ParseV4(buffer); + } + else if (headerSize > BmpInfoHeader.SizeV4) + { + // > 108 bytes + infoHeaderType = BmpInfoHeaderType.WinVersion5; + this.infoHeader = BmpInfoHeader.ParseV5(buffer); + if (this.infoHeader.ProfileData != 0 && this.infoHeader.ProfileSize != 0) { - case BmpConstants.TypeMarkers.Bitmap: - this.fileMarkerType = BmpFileMarkerType.Bitmap; - this.fileHeader = BmpFileHeader.Parse(buffer); - break; - case BmpConstants.TypeMarkers.BitmapArray: - this.fileMarkerType = BmpFileMarkerType.BitmapArray; - - // Because we only decode the first bitmap in the array, the array header will be ignored. - // The bitmap file header of the first image follows the array header. - this.stream.Read(buffer, 0, BmpFileHeader.Size); - this.fileHeader = BmpFileHeader.Parse(buffer); - if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) - { - BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'."); - } - - break; - - default: - BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{fileTypeMarker}'."); - break; + // Read color profile. + long streamPosition = this.stream.Position; + byte[] iccProfileData = new byte[this.infoHeader.ProfileSize]; + this.stream.Position = infoHeaderStart + this.infoHeader.ProfileData; + this.stream.Read(iccProfileData); + this.metadata.IccProfile = new IccProfile(iccProfileData); + this.stream.Position = streamPosition; } } + else + { + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'."); + } - /// - /// Reads the and from the stream and sets the corresponding fields. - /// - /// The input stream. - /// Whether the image orientation is inverted. - /// The color palette. - /// Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps - /// the bytes per color palette entry's can be 3 bytes instead of 4. - private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) + if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) { - this.stream = stream; - - this.ReadFileHeader(); - this.ReadInfoHeader(); - - // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 - // If the height is negative, then this is a Windows bitmap whose origin - // is the upper-left corner and not the lower-left. The inverted flag - // indicates a lower-left origin.Our code will be outputting an - // upper-left origin pixel array. - inverted = false; - if (this.infoHeader.Height < 0) - { - inverted = true; - this.infoHeader.Height = -this.infoHeader.Height; - } + this.metadata.HorizontalResolution = this.infoHeader.XPelsPerMeter; + this.metadata.VerticalResolution = this.infoHeader.YPelsPerMeter; + } + else + { + // Convert default metadata values to PPM. + this.metadata.HorizontalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultHorizontalResolution)); + this.metadata.VerticalResolution = Math.Round(UnitConverter.InchToMeter(ImageMetadata.DefaultVerticalResolution)); + } - int bytesPerColorMapEntry = 4; - int colorMapSizeBytes = -1; - if (this.infoHeader.ClrUsed == 0) - { - if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8) + short bitsPerPixel = this.infoHeader.BitsPerPixel; + this.bmpMetadata = this.metadata.GetBmpMetadata(); + this.bmpMetadata.InfoHeaderType = infoHeaderType; + this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; + } + + /// + /// Reads the from the stream. + /// + private void ReadFileHeader() + { + Span buffer = stackalloc byte[BmpFileHeader.Size]; + this.stream.Read(buffer, 0, BmpFileHeader.Size); + + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(buffer); + switch (fileTypeMarker) + { + case BmpConstants.TypeMarkers.Bitmap: + this.fileMarkerType = BmpFileMarkerType.Bitmap; + this.fileHeader = BmpFileHeader.Parse(buffer); + break; + case BmpConstants.TypeMarkers.BitmapArray: + this.fileMarkerType = BmpFileMarkerType.BitmapArray; + + // Because we only decode the first bitmap in the array, the array header will be ignored. + // The bitmap file header of the first image follows the array header. + this.stream.Read(buffer, 0, BmpFileHeader.Size); + this.fileHeader = BmpFileHeader.Parse(buffer); + if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap) { - switch (this.fileMarkerType) - { - case BmpFileMarkerType.Bitmap: - colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; - int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); - bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; - - // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. - bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); - - break; - case BmpFileMarkerType.BitmapArray: - case BmpFileMarkerType.ColorIcon: - case BmpFileMarkerType.ColorPointer: - case BmpFileMarkerType.Icon: - case BmpFileMarkerType.Pointer: - // OS/2 bitmaps always have 3 colors per color palette entry. - bytesPerColorMapEntry = 3; - colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry; - break; - } + BmpThrowHelper.ThrowNotSupportedException($"Unsupported bitmap file inside a BitmapArray file. File header bitmap type marker '{this.fileHeader.Type}'."); } - } - else - { - colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry; - } - palette = null; + break; + + default: + BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{fileTypeMarker}'."); + break; + } + } - if (colorMapSizeBytes > 0) + /// + /// Reads the and from the stream and sets the corresponding fields. + /// + /// The input stream. + /// Whether the image orientation is inverted. + /// The color palette. + /// Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps + /// the bytes per color palette entry's can be 3 bytes instead of 4. + private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out byte[] palette) + { + this.stream = stream; + + this.ReadFileHeader(); + this.ReadInfoHeader(); + + // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 + // If the height is negative, then this is a Windows bitmap whose origin + // is the upper-left corner and not the lower-left. The inverted flag + // indicates a lower-left origin.Our code will be outputting an + // upper-left origin pixel array. + inverted = false; + if (this.infoHeader.Height < 0) + { + inverted = true; + this.infoHeader.Height = -this.infoHeader.Height; + } + + int bytesPerColorMapEntry = 4; + int colorMapSizeBytes = -1; + if (this.infoHeader.ClrUsed == 0) + { + if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8) { - // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit. - // Make sure, that we will not read pass the bitmap offset (starting position of image data). - if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) + switch (this.fileMarkerType) { - BmpThrowHelper.ThrowInvalidImageContentException( - $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset."); - } + case BmpFileMarkerType.Bitmap: + colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; + int colorCountForBitDepth = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); + bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; - palette = new byte[colorMapSizeBytes]; + // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. + bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); - if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0) - { - BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!"); + break; + case BmpFileMarkerType.BitmapArray: + case BmpFileMarkerType.ColorIcon: + case BmpFileMarkerType.ColorPointer: + case BmpFileMarkerType.Icon: + case BmpFileMarkerType.Pointer: + // OS/2 bitmaps always have 3 colors per color palette entry. + bytesPerColorMapEntry = 3; + colorMapSizeBytes = ColorNumerics.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * bytesPerColorMapEntry; + break; } } + } + else + { + colorMapSizeBytes = this.infoHeader.ClrUsed * bytesPerColorMapEntry; + } - int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; - if ((skipAmount + (int)this.stream.Position) > this.stream.Length) + palette = null; + + if (colorMapSizeBytes > 0) + { + // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit. + // Make sure, that we will not read pass the bitmap offset (starting position of image data). + if ((this.stream.Position + colorMapSizeBytes) > this.fileHeader.Offset) { - BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length."); + BmpThrowHelper.ThrowInvalidImageContentException( + $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset."); } - if (skipAmount > 0) + palette = new byte[colorMapSizeBytes]; + + if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0) { - this.stream.Skip(skipAmount); + BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!"); } + } - return bytesPerColorMapEntry; + int skipAmount = this.fileHeader.Offset - (int)this.stream.Position; + if ((skipAmount + (int)this.stream.Position) > this.stream.Length) + { + BmpThrowHelper.ThrowInvalidImageContentException("Invalid file header offset found. Offset is greater than the stream length."); } + + if (skipAmount > 0) + { + this.stream.Skip(skipAmount); + } + + return bytesPerColorMapEntry; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs index 1f5ce08d1c..26c4e7ec58 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderOptions.cs @@ -1,20 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Configuration options for decoding Windows Bitmap images. +/// +public sealed class BmpDecoderOptions : ISpecializedDecoderOptions { + /// + public DecoderOptions GeneralOptions { get; set; } = new(); + /// - /// Configuration options for decoding Windows Bitmap images. + /// Gets or sets the value indicating how to deal with skipped pixels, + /// which can occur during decoding run length encoded bitmaps. /// - public sealed class BmpDecoderOptions : ISpecializedDecoderOptions - { - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - - /// - /// Gets or sets the value indicating how to deal with skipped pixels, - /// which can occur during decoding run length encoded bitmaps. - /// - public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } - } + public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 25669b3f9b..a410a862b5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -1,53 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Image encoder for writing an image to a stream as a Windows bitmap. +/// +public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { /// - /// Image encoder for writing an image to a stream as a Windows bitmap. + /// Gets or sets the number of bits per pixel. /// - public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions - { - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel? BitsPerPixel { get; set; } + public BmpBitsPerPixel? BitsPerPixel { get; set; } - /// - /// Gets or sets a value indicating whether the encoder should support transparency. - /// Note: Transparency support only works together with 32 bits per pixel. This option will - /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. - /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. - /// - public bool SupportTransparency { get; set; } + /// + /// Gets or sets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. + /// + public bool SupportTransparency { get; set; } - /// - /// Gets or sets the quantizer for reducing the color count for 8-Bit images. - /// Defaults to Wu Quantizer. - /// - public IQuantizer Quantizer { get; set; } + /// + /// Gets or sets the quantizer for reducing the color count for 8-Bit images. + /// Defaults to Wu Quantizer. + /// + public IQuantizer Quantizer { get; set; } - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b216fd7a57..471e741826 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -1,12 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; @@ -15,693 +12,692 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Image encoder for writing an image to a stream as a Windows bitmap. +/// +internal sealed class BmpEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream as a Windows bitmap. + /// The amount to pad each row by. /// - internal sealed class BmpEncoderCore : IImageEncoderInternals - { - /// - /// The amount to pad each row by. - /// - private int padding; - - /// - /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32AlphaMask = 0xFF << 24; - - /// - /// The mask for the red part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32RedMask = 0xFF << 16; - - /// - /// The mask for the green part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32GreenMask = 0xFF << 8; - - /// - /// The mask for the blue part of the color for 32 bit rgba bitmaps. - /// - private const int Rgba32BlueMask = 0xFF; - - /// - /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize8Bit = 1024; - - /// - /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize4Bit = 64; - - /// - /// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize2Bit = 16; - - /// - /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. - /// - private const int ColorPaletteSize1Bit = 8; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// The color depth, in number of bits per pixel. - /// - private BmpBitsPerPixel? bitsPerPixel; - - /// - /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. - /// In this case the compression type BITFIELDS will be used. - /// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info. - /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. - /// - private BmpInfoHeaderType infoHeaderType; - - /// - /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images. - /// - private readonly IQuantizer quantizer; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options. - /// The memory manager. - public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.bitsPerPixel = options.BitsPerPixel; - this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; - this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; - } + private int padding; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); - this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; - - short bpp = (short)this.bitsPerPixel; - int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); + /// + /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32AlphaMask = 0xFF << 24; - int colorPaletteSize = this.bitsPerPixel switch - { - BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit, - BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit, - BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit, - BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit, - _ => 0 - }; - - byte[] iccProfileData = null; - int iccProfileSize = 0; - if (metadata.IccProfile != null) - { - this.infoHeaderType = BmpInfoHeaderType.WinVersion5; - iccProfileData = metadata.IccProfile.ToByteArray(); - iccProfileSize = iccProfileData.Length; - } + /// + /// The mask for the red part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32RedMask = 0xFF << 16; - int infoHeaderSize = this.infoHeaderType switch - { - BmpInfoHeaderType.WinVersion3 => BmpInfoHeader.SizeV3, - BmpInfoHeaderType.WinVersion4 => BmpInfoHeader.SizeV4, - BmpInfoHeaderType.WinVersion5 => BmpInfoHeader.SizeV5, - _ => BmpInfoHeader.SizeV3 - }; + /// + /// The mask for the green part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32GreenMask = 0xFF << 8; - BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData); + /// + /// The mask for the blue part of the color for 32 bit rgba bitmaps. + /// + private const int Rgba32BlueMask = 0xFF; - Span buffer = stackalloc byte[infoHeaderSize]; + /// + /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize8Bit = 1024; - WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); - this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); - this.WriteImage(stream, image.Frames.RootFrame); - WriteColorProfile(stream, iccProfileData, buffer); + /// + /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize4Bit = 64; - stream.Flush(); - } + /// + /// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize2Bit = 16; - /// - /// Creates the bitmap information header. - /// - /// The width of the image. - /// The height of the image. - /// Size of the information header. - /// The bits per pixel. - /// The bytes per line. - /// The metadata. - /// The icc profile data. - /// The bitmap information header. - private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[] iccProfileData) - { - int hResolution = 0; - int vResolution = 0; + /// + /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize1Bit = 8; - if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio - && metadata.HorizontalResolution > 0 - && metadata.VerticalResolution > 0) - { - switch (metadata.ResolutionUnits) - { - case PixelResolutionUnit.PixelsPerInch: + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); - break; + /// + /// The global configuration. + /// + private Configuration configuration; - case PixelResolutionUnit.PixelsPerCentimeter: + /// + /// The color depth, in number of bits per pixel. + /// + private BmpBitsPerPixel? bitsPerPixel; - hResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.VerticalResolution)); - break; + /// + /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. + /// In this case the compression type BITFIELDS will be used. + /// If the image contains a color profile, a bitmap v5 header is written, which is needed to write this info. + /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. + /// + private BmpInfoHeaderType infoHeaderType; - case PixelResolutionUnit.PixelsPerMeter: - hResolution = (int)Math.Round(metadata.HorizontalResolution); - vResolution = (int)Math.Round(metadata.VerticalResolution); + /// + /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images. + /// + private readonly IQuantizer quantizer; - break; - } - } + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.bitsPerPixel = options.BitsPerPixel; + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.infoHeaderType = options.SupportTransparency ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion3; + } - BmpInfoHeader infoHeader = new( - headerSize: infoHeaderSize, - height: height, - width: width, - bitsPerPixel: bpp, - planes: 1, - imageSize: height * bytesPerLine, - clrUsed: 0, - clrImportant: 0, - xPelsPerMeter: hResolution, - yPelsPerMeter: vResolution); - - if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) - { - infoHeader.AlphaMask = Rgba32AlphaMask; - infoHeader.RedMask = Rgba32RedMask; - infoHeader.GreenMask = Rgba32GreenMask; - infoHeader.BlueMask = Rgba32BlueMask; - infoHeader.Compression = BmpCompression.BitFields; - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null) - { - infoHeader.ProfileSize = iccProfileData.Length; - infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED; - infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES; - } + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); + this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; - return infoHeader; - } + short bpp = (short)this.bitsPerPixel; + int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); + this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); - /// - /// Writes the color profile to the stream. - /// - /// The stream to write to. - /// The color profile data. - /// The buffer. - private static void WriteColorProfile(Stream stream, byte[] iccProfileData, Span buffer) + int colorPaletteSize = this.bitsPerPixel switch { - if (iccProfileData != null) - { - // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. - int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size; - stream.Write(iccProfileData); - BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData); - stream.Position = BmpFileHeader.Size + 112; - stream.Write(buffer[..4]); - } - } - - /// - /// Writes the bitmap file header. - /// - /// The stream to write the header to. - /// Size of the bitmap information header. - /// Size of the color palette. - /// The size in bytes of the color profile. - /// The information header to write. - /// The buffer to write to. - private static void WriteBitmapFileHeader(Stream stream, int infoHeaderSize, int colorPaletteSize, int iccProfileSize, BmpInfoHeader infoHeader, Span buffer) + BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit, + BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit, + BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit, + BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit, + _ => 0 + }; + + byte[] iccProfileData = null; + int iccProfileSize = 0; + if (metadata.IccProfile != null) { - BmpFileHeader fileHeader = new( - type: BmpConstants.TypeMarkers.Bitmap, - fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize, - reserved: 0, - offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); - - fileHeader.WriteTo(buffer); - stream.Write(buffer, 0, BmpFileHeader.Size); + this.infoHeaderType = BmpInfoHeaderType.WinVersion5; + iccProfileData = metadata.IccProfile.ToByteArray(); + iccProfileSize = iccProfileData.Length; } - /// - /// Writes the bitmap information header. - /// - /// The stream to write info header into. - /// The information header. - /// The buffer. - /// Size of the information header. - private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span buffer, int infoHeaderSize) + int infoHeaderSize = this.infoHeaderType switch { - switch (this.infoHeaderType) - { - case BmpInfoHeaderType.WinVersion3: - infoHeader.WriteV3Header(buffer); - break; - case BmpInfoHeaderType.WinVersion4: - infoHeader.WriteV4Header(buffer); - break; - case BmpInfoHeaderType.WinVersion5: - infoHeader.WriteV5Header(buffer); - break; - } + BmpInfoHeaderType.WinVersion3 => BmpInfoHeader.SizeV3, + BmpInfoHeaderType.WinVersion4 => BmpInfoHeader.SizeV4, + BmpInfoHeaderType.WinVersion5 => BmpInfoHeader.SizeV5, + _ => BmpInfoHeader.SizeV3 + }; - stream.Write(buffer, 0, infoHeaderSize); - } + BmpInfoHeader infoHeader = this.CreateBmpInfoHeader(image.Width, image.Height, infoHeaderSize, bpp, bytesPerLine, metadata, iccProfileData); + + Span buffer = stackalloc byte[infoHeaderSize]; + + WriteBitmapFileHeader(stream, infoHeaderSize, colorPaletteSize, iccProfileSize, infoHeader, buffer); + this.WriteBitmapInfoHeader(stream, infoHeader, buffer, infoHeaderSize); + this.WriteImage(stream, image.Frames.RootFrame); + WriteColorProfile(stream, iccProfileData, buffer); + + stream.Flush(); + } + + /// + /// Creates the bitmap information header. + /// + /// The width of the image. + /// The height of the image. + /// Size of the information header. + /// The bits per pixel. + /// The bytes per line. + /// The metadata. + /// The icc profile data. + /// The bitmap information header. + private BmpInfoHeader CreateBmpInfoHeader(int width, int height, int infoHeaderSize, short bpp, int bytesPerLine, ImageMetadata metadata, byte[] iccProfileData) + { + int hResolution = 0; + int vResolution = 0; - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The to write to. - /// - /// The containing pixel data. - /// - private void WriteImage(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + if (metadata.ResolutionUnits != PixelResolutionUnit.AspectRatio + && metadata.HorizontalResolution > 0 + && metadata.VerticalResolution > 0) { - Buffer2D pixels = image.PixelBuffer; - switch (this.bitsPerPixel) + switch (metadata.ResolutionUnits) { - case BmpBitsPerPixel.Pixel32: - this.Write32BitPixelData(stream, pixels); - break; + case PixelResolutionUnit.PixelsPerInch: - case BmpBitsPerPixel.Pixel24: - this.Write24BitPixelData(stream, pixels); + hResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.InchToMeter(metadata.VerticalResolution)); break; - case BmpBitsPerPixel.Pixel16: - this.Write16BitPixelData(stream, pixels); - break; + case PixelResolutionUnit.PixelsPerCentimeter: - case BmpBitsPerPixel.Pixel8: - this.Write8BitPixelData(stream, image); + hResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.CmToMeter(metadata.VerticalResolution)); break; - case BmpBitsPerPixel.Pixel4: - this.Write4BitPixelData(stream, image); - break; + case PixelResolutionUnit.PixelsPerMeter: + hResolution = (int)Math.Round(metadata.HorizontalResolution); + vResolution = (int)Math.Round(metadata.VerticalResolution); - case BmpBitsPerPixel.Pixel2: - this.Write2BitPixelData(stream, image); - break; - - case BmpBitsPerPixel.Pixel1: - this.Write1BitPixelData(stream, image); break; } } - private IMemoryOwner AllocateRow(int width, int bytesPerPixel) - => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); - - /// - /// Writes 32-bit data with a color palette to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write32BitPixelData(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + BmpInfoHeader infoHeader = new( + headerSize: infoHeaderSize, + height: height, + width: width, + bitsPerPixel: bpp, + planes: 1, + imageSize: height * bytesPerLine, + clrUsed: 0, + clrImportant: 0, + xPelsPerMeter: hResolution, + yPelsPerMeter: vResolution); + + if ((this.infoHeaderType is BmpInfoHeaderType.WinVersion4 or BmpInfoHeaderType.WinVersion5) && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); - Span rowSpan = row.GetSpan(); + infoHeader.AlphaMask = Rgba32AlphaMask; + infoHeader.RedMask = Rgba32RedMask; + infoHeader.GreenMask = Rgba32GreenMask; + infoHeader.BlueMask = Rgba32BlueMask; + infoHeader.Compression = BmpCompression.BitFields; + } - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } + if (this.infoHeaderType is BmpInfoHeaderType.WinVersion5 && metadata.IccProfile != null) + { + infoHeader.ProfileSize = iccProfileData.Length; + infoHeader.CsType = BmpColorSpace.PROFILE_EMBEDDED; + infoHeader.Intent = BmpRenderingIntent.LCS_GM_IMAGES; } - /// - /// Writes 24-bit pixel data with a color palette to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write24BitPixelData(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + return infoHeader; + } + + /// + /// Writes the color profile to the stream. + /// + /// The stream to write to. + /// The color profile data. + /// The buffer. + private static void WriteColorProfile(Stream stream, byte[] iccProfileData, Span buffer) + { + if (iccProfileData != null) { - int width = pixels.Width; - int rowBytesWithoutPadding = width * 3; - using IMemoryOwner row = this.AllocateRow(width, 3); - Span rowSpan = row.GetSpan(); + // The offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. + int streamPositionAfterImageData = (int)stream.Position - BmpFileHeader.Size; + stream.Write(iccProfileData); + BinaryPrimitives.WriteInt32LittleEndian(buffer, streamPositionAfterImageData); + stream.Position = BmpFileHeader.Size + 112; + stream.Write(buffer[..4]); + } + } - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - width); - stream.Write(rowSpan); - } + /// + /// Writes the bitmap file header. + /// + /// The stream to write the header to. + /// Size of the bitmap information header. + /// Size of the color palette. + /// The size in bytes of the color profile. + /// The information header to write. + /// The buffer to write to. + private static void WriteBitmapFileHeader(Stream stream, int infoHeaderSize, int colorPaletteSize, int iccProfileSize, BmpInfoHeader infoHeader, Span buffer) + { + BmpFileHeader fileHeader = new( + type: BmpConstants.TypeMarkers.Bitmap, + fileSize: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize + iccProfileSize + infoHeader.ImageSize, + reserved: 0, + offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); + + fileHeader.WriteTo(buffer); + stream.Write(buffer, 0, BmpFileHeader.Size); + } + + /// + /// Writes the bitmap information header. + /// + /// The stream to write info header into. + /// The information header. + /// The buffer. + /// Size of the information header. + private void WriteBitmapInfoHeader(Stream stream, BmpInfoHeader infoHeader, Span buffer, int infoHeaderSize) + { + switch (this.infoHeaderType) + { + case BmpInfoHeaderType.WinVersion3: + infoHeader.WriteV3Header(buffer); + break; + case BmpInfoHeaderType.WinVersion4: + infoHeader.WriteV4Header(buffer); + break; + case BmpInfoHeaderType.WinVersion5: + infoHeader.WriteV5Header(buffer); + break; } - /// - /// Writes 16-bit pixel data with a color palette to the stream. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - private void Write16BitPixelData(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + stream.Write(buffer, 0, infoHeaderSize); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WriteImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Buffer2D pixels = image.PixelBuffer; + switch (this.bitsPerPixel) { - int width = pixels.Width; - int rowBytesWithoutPadding = width * 2; - using IMemoryOwner row = this.AllocateRow(width, 2); - Span rowSpan = row.GetSpan(); + case BmpBitsPerPixel.Pixel32: + this.Write32BitPixelData(stream, pixels); + break; - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); + case BmpBitsPerPixel.Pixel24: + this.Write24BitPixelData(stream, pixels); + break; - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - row.Slice(0, rowBytesWithoutPadding), - pixelSpan.Length); + case BmpBitsPerPixel.Pixel16: + this.Write16BitPixelData(stream, pixels); + break; - stream.Write(rowSpan); - } + case BmpBitsPerPixel.Pixel8: + this.Write8BitPixelData(stream, image); + break; + + case BmpBitsPerPixel.Pixel4: + this.Write4BitPixelData(stream, image); + break; + + case BmpBitsPerPixel.Pixel2: + this.Write2BitPixelData(stream, image); + break; + + case BmpBitsPerPixel.Pixel1: + this.Write1BitPixelData(stream, image); + break; } + } + + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); + + /// + /// Writes 32-bit data with a color palette to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write32BitPixelData(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); - /// - /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - private void Write8BitPixelData(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + for (int y = pixels.Height - 1; y >= 0; y--) { - bool isL8 = typeof(TPixel) == typeof(L8); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize8Bit, AllocationOptions.Clean); - Span colorPalette = colorPaletteBuffer.GetSpan(); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); + } + } - if (isL8) - { - this.Write8BitPixelData(stream, image, colorPalette); - } - else - { - this.Write8BitColor(stream, image, colorPalette); - } + /// + /// Writes 24-bit pixel data with a color palette to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write24BitPixelData(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int rowBytesWithoutPadding = width * 3; + using IMemoryOwner row = this.AllocateRow(width, 3); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + width); + stream.Write(rowSpan); } + } - /// - /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// 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 : unmanaged, IPixel + /// + /// Writes 16-bit pixel data with a color palette to the stream. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write16BitPixelData(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int rowBytesWithoutPadding = width * 2; + using IMemoryOwner row = this.AllocateRow(width, 2); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + Span pixelSpan = pixels.DangerousGetRowSpan(y); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + row.Slice(0, rowBytesWithoutPadding), + pixelSpan.Length); - for (int y = image.Height - 1; y >= 0; y--) - { - ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y); - stream.Write(pixelSpan); + stream.Write(rowSpan); + } + } - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } - } + /// + /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write8BitPixelData(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + bool isL8 = typeof(TPixel) == typeof(L8); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize8Bit, AllocationOptions.Clean); + Span colorPalette = colorPaletteBuffer.GetSpan(); + + if (isL8) + { + this.Write8BitPixelData(stream, image, colorPalette); + } + else + { + this.Write8BitColor(stream, image, colorPalette); } + } + + /// + /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// 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 : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - /// - /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - /// A byte span of size 1024 for the color palette. - private void Write8BitPixelData(Stream stream, ImageFrame image, Span colorPalette) - where TPixel : unmanaged, IPixel + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + for (int y = image.Height - 1; y >= 0; y--) { - // Create a color palette with 256 different gray values. - for (int i = 0; i <= 255; i++) - { - int idx = i * 4; - byte grayValue = (byte)i; - colorPalette[idx] = grayValue; - colorPalette[idx + 1] = grayValue; - colorPalette[idx + 2] = grayValue; - - // Padding byte, always 0. - colorPalette[idx + 3] = 0; - } + ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y); + stream.Write(pixelSpan); - stream.Write(colorPalette); - Buffer2D imageBuffer = image.PixelBuffer; - for (int y = image.Height - 1; y >= 0; y--) + for (int i = 0; i < this.padding; i++) { - ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y); - ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); - stream.Write(outputPixelRow); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + stream.WriteByte(0); } } + } - /// - /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - private void Write4BitPixelData(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + /// + /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + /// A byte span of size 1024 for the color palette. + private void Write8BitPixelData(Stream stream, ImageFrame image, Span colorPalette) + where TPixel : unmanaged, IPixel + { + // Create a color palette with 256 different gray values. + for (int i = 0; i <= 255; i++) { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() - { - MaxColors = 16 - }); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean); - - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - - ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); - int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; - for (int y = image.Height - 1; y >= 0; y--) - { - pixelRowSpan = quantized.DangerousGetRowSpan(y); - - int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; - for (int i = 0; i < endIdx; i += 2) - { - stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); - } + int idx = i * 4; + byte grayValue = (byte)i; + colorPalette[idx] = grayValue; + colorPalette[idx + 1] = grayValue; + colorPalette[idx + 2] = grayValue; + + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + } - if (pixelRowSpan.Length % 2 != 0) - { - stream.WriteByte((byte)((pixelRowSpan[^1] << 4) | 0)); - } + stream.Write(colorPalette); + Buffer2D imageBuffer = image.PixelBuffer; + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y); + ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); + stream.Write(outputPixelRow); - for (int i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); - } + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); } } + } - /// - /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - private void Write2BitPixelData(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + /// + /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write4BitPixelData(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() - { - MaxColors = 4 - }); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean); - - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - - ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); - int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding; - for (int y = image.Height - 1; y >= 0; y--) - { - pixelRowSpan = quantized.DangerousGetRowSpan(y); + MaxColors = 16 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize4Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); + int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + pixelRowSpan = quantized.DangerousGetRowSpan(y); - int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4; - int i = 0; - for (i = 0; i < endIdx; i += 4) - { - stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3])); - } + int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; + for (int i = 0; i < endIdx; i += 2) + { + stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); + } - if (pixelRowSpan.Length % 4 != 0) - { - int shift = 6; - byte pixelData = 0; - for (; i < pixelRowSpan.Length; i++) - { - pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift)); - shift -= 2; - } - - stream.WriteByte(pixelData); - } + if (pixelRowSpan.Length % 2 != 0) + { + stream.WriteByte((byte)((pixelRowSpan[^1] << 4) | 0)); + } - for (i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); - } + for (int i = 0; i < rowPadding; i++) + { + stream.WriteByte(0); } } + } - /// - /// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The containing pixel data. - private void Write1BitPixelData(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + /// + /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write2BitPixelData(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() - { - MaxColors = 2 - }); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean); - - Span colorPalette = colorPaletteBuffer.GetSpan(); - ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; - this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - - ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0); - int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; - for (int y = image.Height - 1; y >= 0; y--) + MaxColors = 4 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); + int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + pixelRowSpan = quantized.DangerousGetRowSpan(y); + + int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4; + int i = 0; + for (i = 0; i < endIdx; i += 4) { - quantizedPixelRow = quantized.DangerousGetRowSpan(y); + stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3])); + } - int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; - for (int i = 0; i < endIdx; i += 8) + if (pixelRowSpan.Length % 4 != 0) + { + int shift = 6; + byte pixelData = 0; + for (; i < pixelRowSpan.Length; i++) { - Write1BitPalette(stream, i, i + 8, quantizedPixelRow); + pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift)); + shift -= 2; } - if (quantizedPixelRow.Length % 8 != 0) - { - int startIdx = quantizedPixelRow.Length - 7; - endIdx = quantizedPixelRow.Length; - Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); - } + stream.WriteByte(pixelData); + } - for (int i = 0; i < rowPadding; i++) - { - stream.WriteByte(0); - } + for (i = 0; i < rowPadding; i++) + { + stream.WriteByte(0); } } + } - /// - /// Writes the color palette to the stream. The color palette has 4 bytes for each entry. - /// - /// The type of the pixel. - /// The to write to. - /// The color palette from the quantized image. - /// A temporary byte span to write the color palette to. - private void WriteColorPalette(Stream stream, ReadOnlySpan quantizedColorPalette, Span colorPalette) - where TPixel : unmanaged, IPixel + /// + /// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write1BitPixelData(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() { - int quantizedColorBytes = quantizedColorPalette.Length * 4; - PixelOperations.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast(colorPalette[..quantizedColorBytes])); - Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); - for (int i = 0; i < colorPaletteAsUInt.Length; i++) + MaxColors = 2 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize1Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0); + int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + quantizedPixelRow = quantized.DangerousGetRowSpan(y); + + int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; + for (int i = 0; i < endIdx; i += 8) { - colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0. + Write1BitPalette(stream, i, i + 8, quantizedPixelRow); } - stream.Write(colorPalette); - } + if (quantizedPixelRow.Length % 8 != 0) + { + int startIdx = quantizedPixelRow.Length - 7; + endIdx = quantizedPixelRow.Length; + Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); + } - /// - /// Writes a 1-bit palette. - /// - /// The stream to write the palette to. - /// The start index. - /// The end index. - /// A quantized pixel row. - private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan quantizedPixelRow) - { - int shift = 7; - byte indices = 0; - for (int j = startIdx; j < endIdx; j++) + for (int i = 0; i < rowPadding; i++) { - indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift)); - shift--; + stream.WriteByte(0); } + } + } + + /// + /// Writes the color palette to the stream. The color palette has 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The color palette from the quantized image. + /// A temporary byte span to write the color palette to. + private void WriteColorPalette(Stream stream, ReadOnlySpan quantizedColorPalette, Span colorPalette) + where TPixel : unmanaged, IPixel + { + int quantizedColorBytes = quantizedColorPalette.Length * 4; + PixelOperations.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast(colorPalette[..quantizedColorBytes])); + Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); + for (int i = 0; i < colorPaletteAsUInt.Length; i++) + { + colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0. + } - stream.WriteByte(indices); + stream.Write(colorPalette); + } + + /// + /// Writes a 1-bit palette. + /// + /// The stream to write the palette to. + /// The start index. + /// The end index. + /// A quantized pixel row. + private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan quantizedPixelRow) + { + int shift = 7; + byte indices = 0; + for (int j = startIdx; j < endIdx; j++) + { + indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift)); + shift--; } + + stream.WriteByte(indices); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs index 25254d2103..19aa366238 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileHeader.cs @@ -1,69 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Stores general information about the Bitmap file. +/// +/// +/// +/// The first two bytes of the Bitmap file format +/// (thus the Bitmap header) are stored in big-endian order. +/// All of the other integer values are stored in little-endian format +/// (i.e. least-significant byte first). +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct BmpFileHeader { /// - /// Stores general information about the Bitmap file. - /// + /// Defines the size of the data structure in the bitmap file. /// - /// - /// The first two bytes of the Bitmap file format - /// (thus the Bitmap header) are stored in big-endian order. - /// All of the other integer values are stored in little-endian format - /// (i.e. least-significant byte first). - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct BmpFileHeader - { - /// - /// Defines the size of the data structure in the bitmap file. - /// - public const int Size = 14; + public const int Size = 14; - public BmpFileHeader(short type, int fileSize, int reserved, int offset) - { - this.Type = type; - this.FileSize = fileSize; - this.Reserved = reserved; - this.Offset = offset; - } + public BmpFileHeader(short type, int fileSize, int reserved, int offset) + { + this.Type = type; + this.FileSize = fileSize; + this.Reserved = reserved; + this.Offset = offset; + } - /// - /// Gets the Bitmap identifier. - /// The field used to identify the bitmap file: 0x42 0x4D - /// (Hex code points for B and M) - /// - public short Type { get; } + /// + /// Gets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x4D + /// (Hex code points for B and M) + /// + public short Type { get; } - /// - /// Gets the size of the bitmap file in bytes. - /// - public int FileSize { get; } + /// + /// Gets the size of the bitmap file in bytes. + /// + public int FileSize { get; } - /// - /// Gets any reserved data; actual value depends on the application - /// that creates the image. - /// - public int Reserved { get; } + /// + /// Gets any reserved data; actual value depends on the application + /// that creates the image. + /// + public int Reserved { get; } - /// - /// Gets the offset, i.e. starting address, of the byte where - /// the bitmap data can be found. - /// - public int Offset { get; } + /// + /// Gets the offset, i.e. starting address, of the byte where + /// the bitmap data can be found. + /// + public int Offset { get; } - public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; + public static BmpFileHeader Parse(Span data) => MemoryMarshal.Cast(data)[0]; - public void WriteTo(Span buffer) - { - ref BmpFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + public void WriteTo(Span buffer) + { + ref BmpFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - dest = this; - } + dest = this; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs index eb6640ba94..d9f46df805 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFileMarkerType.cs @@ -1,41 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Indicates which bitmap file marker was read. +/// +public enum BmpFileMarkerType { /// - /// Indicates which bitmap file marker was read. + /// Single-image BMP file that may have been created under Windows or OS/2. /// - public enum BmpFileMarkerType - { - /// - /// Single-image BMP file that may have been created under Windows or OS/2. - /// - Bitmap, + Bitmap, - /// - /// OS/2 Bitmap Array. - /// - BitmapArray, + /// + /// OS/2 Bitmap Array. + /// + BitmapArray, - /// - /// OS/2 Color Icon. - /// - ColorIcon, + /// + /// OS/2 Color Icon. + /// + ColorIcon, - /// - /// OS/2 Color Pointer. - /// - ColorPointer, + /// + /// OS/2 Color Pointer. + /// + ColorPointer, - /// - /// OS/2 Icon. - /// - Icon, + /// + /// OS/2 Icon. + /// + Icon, - /// - /// OS/2 Pointer. - /// - Pointer - } + /// + /// OS/2 Pointer. + /// + Pointer } diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index 95820043a4..b0d8453a7e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Bmp; -namespace SixLabors.ImageSharp.Formats.Bmp +/// +/// Registers the image encoders, decoders and mime type detectors for the bmp format. +/// +public sealed class BmpFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the bmp format. - /// - public sealed class BmpFormat : IImageFormat + private BmpFormat() { - private BmpFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static BmpFormat Instance { get; } = new BmpFormat(); + /// + /// Gets the current instance. + /// + public static BmpFormat Instance { get; } = new BmpFormat(); - /// - public string Name => "BMP"; + /// + public string Name => "BMP"; - /// - public string DefaultMimeType => "image/bmp"; + /// + public string DefaultMimeType => "image/bmp"; - /// - public IEnumerable MimeTypes => BmpConstants.MimeTypes; + /// + public IEnumerable MimeTypes => BmpConstants.MimeTypes; - /// - public IEnumerable FileExtensions => BmpConstants.FileExtensions; + /// + public IEnumerable FileExtensions => BmpConstants.FileExtensions; - /// - public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata(); - } + /// + public BmpMetadata CreateDefaultFormatMetadata() => new BmpMetadata(); } diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 9c920f2bfc..a10f6b74a9 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -1,34 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Detects bmp file headers. +/// +public sealed class BmpImageFormatDetector : IImageFormatDetector { - /// - /// Detects bmp file headers. - /// - public sealed class BmpImageFormatDetector : IImageFormatDetector + /// + public int HeaderSize => 2; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) { - /// - public int HeaderSize => 2; + return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; + } - /// - public IImageFormat DetectFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); + return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; } - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - if (header.Length >= this.HeaderSize) - { - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); - return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; - } - - return false; - } + return false; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs index ac3bc9d3cc..ff02131497 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs @@ -1,433 +1,360 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// This block of bytes tells the application detailed information +/// about the image, which will be used to display the image on +/// the screen. +/// +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal struct BmpInfoHeader { /// - /// This block of bytes tells the application detailed information - /// about the image, which will be used to display the image on - /// the screen. - /// + /// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file. + /// + public const int CoreSize = 12; + + /// + /// Defines the size of the short variant of the OS22XBITMAPHEADER data structure in the bitmap file. + /// + public const int Os22ShortSize = 16; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file. + /// + public const int SizeV3 = 40; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// + public const int AdobeV3Size = 52; + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it. + /// + public const int AdobeV3WithAlphaSize = 56; + + /// + /// Size of a IBM OS/2 2.x bitmap header. + /// + public const int Os2v2Size = 64; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file. + /// + public const int SizeV4 = 108; + + /// + /// Defines the size of the BITMAPINFOHEADER (BMP Version 5) data structure in the bitmap file. + /// + public const int SizeV5 = 124; + + /// + /// Defines the size of the biggest supported header data structure in the bitmap file. + /// + public const int MaxHeaderSize = SizeV5; + + /// + /// Defines the size of the field. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct BmpInfoHeader + public const int HeaderSizeSize = 4; + + public BmpInfoHeader( + int headerSize, + int width, + int height, + short planes, + short bitsPerPixel, + BmpCompression compression = default, + int imageSize = 0, + int xPelsPerMeter = 0, + int yPelsPerMeter = 0, + int clrUsed = 0, + int clrImportant = 0, + int redMask = 0, + int greenMask = 0, + int blueMask = 0, + int alphaMask = 0, + BmpColorSpace csType = 0, + int redX = 0, + int redY = 0, + int redZ = 0, + int greenX = 0, + int greenY = 0, + int greenZ = 0, + int blueX = 0, + int blueY = 0, + int blueZ = 0, + int gammeRed = 0, + int gammeGreen = 0, + int gammeBlue = 0, + BmpRenderingIntent intent = BmpRenderingIntent.Invalid, + int profileData = 0, + int profileSize = 0, + int reserved = 0) { - /// - /// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file. - /// - public const int CoreSize = 12; - - /// - /// Defines the size of the short variant of the OS22XBITMAPHEADER data structure in the bitmap file. - /// - public const int Os22ShortSize = 16; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file. - /// - public const int SizeV3 = 40; - - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. - /// - public const int AdobeV3Size = 52; - - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it. - /// - public const int AdobeV3WithAlphaSize = 56; - - /// - /// Size of a IBM OS/2 2.x bitmap header. - /// - public const int Os2v2Size = 64; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file. - /// - public const int SizeV4 = 108; - - /// - /// Defines the size of the BITMAPINFOHEADER (BMP Version 5) data structure in the bitmap file. - /// - public const int SizeV5 = 124; - - /// - /// Defines the size of the biggest supported header data structure in the bitmap file. - /// - public const int MaxHeaderSize = SizeV5; - - /// - /// Defines the size of the field. - /// - public const int HeaderSizeSize = 4; - - public BmpInfoHeader( - int headerSize, - int width, - int height, - short planes, - short bitsPerPixel, - BmpCompression compression = default, - int imageSize = 0, - int xPelsPerMeter = 0, - int yPelsPerMeter = 0, - int clrUsed = 0, - int clrImportant = 0, - int redMask = 0, - int greenMask = 0, - int blueMask = 0, - int alphaMask = 0, - BmpColorSpace csType = 0, - int redX = 0, - int redY = 0, - int redZ = 0, - int greenX = 0, - int greenY = 0, - int greenZ = 0, - int blueX = 0, - int blueY = 0, - int blueZ = 0, - int gammeRed = 0, - int gammeGreen = 0, - int gammeBlue = 0, - BmpRenderingIntent intent = BmpRenderingIntent.Invalid, - int profileData = 0, - int profileSize = 0, - int reserved = 0) - { - this.HeaderSize = headerSize; - this.Width = width; - this.Height = height; - this.Planes = planes; - this.BitsPerPixel = bitsPerPixel; - this.Compression = compression; - this.ImageSize = imageSize; - this.XPelsPerMeter = xPelsPerMeter; - this.YPelsPerMeter = yPelsPerMeter; - this.ClrUsed = clrUsed; - this.ClrImportant = clrImportant; - this.RedMask = redMask; - this.GreenMask = greenMask; - this.BlueMask = blueMask; - this.AlphaMask = alphaMask; - this.CsType = csType; - this.RedX = redX; - this.RedY = redY; - this.RedZ = redZ; - this.GreenX = greenX; - this.GreenY = greenY; - this.GreenZ = greenZ; - this.BlueX = blueX; - this.BlueY = blueY; - this.BlueZ = blueZ; - this.GammaRed = gammeRed; - this.GammaGreen = gammeGreen; - this.GammaBlue = gammeBlue; - this.Intent = intent; - this.ProfileData = profileData; - this.ProfileSize = profileSize; - this.Reserved = reserved; - } + this.HeaderSize = headerSize; + this.Width = width; + this.Height = height; + this.Planes = planes; + this.BitsPerPixel = bitsPerPixel; + this.Compression = compression; + this.ImageSize = imageSize; + this.XPelsPerMeter = xPelsPerMeter; + this.YPelsPerMeter = yPelsPerMeter; + this.ClrUsed = clrUsed; + this.ClrImportant = clrImportant; + this.RedMask = redMask; + this.GreenMask = greenMask; + this.BlueMask = blueMask; + this.AlphaMask = alphaMask; + this.CsType = csType; + this.RedX = redX; + this.RedY = redY; + this.RedZ = redZ; + this.GreenX = greenX; + this.GreenY = greenY; + this.GreenZ = greenZ; + this.BlueX = blueX; + this.BlueY = blueY; + this.BlueZ = blueZ; + this.GammaRed = gammeRed; + this.GammaGreen = gammeGreen; + this.GammaBlue = gammeBlue; + this.Intent = intent; + this.ProfileData = profileData; + this.ProfileSize = profileSize; + this.Reserved = reserved; + } - /// - /// Gets or sets the size of this header. - /// - public int HeaderSize { get; set; } - - /// - /// Gets or sets the bitmap width in pixels (signed integer). - /// - public int Width { get; set; } - - /// - /// Gets or sets the bitmap height in pixels (signed integer). - /// - public int Height { get; set; } - - /// - /// Gets or sets the number of color planes being used. Must be set to 1. - /// - public short Planes { get; set; } - - /// - /// Gets or sets the number of bits per pixel, which is the color depth of the image. - /// Typical values are 1, 4, 8, 16, 24 and 32. - /// - public short BitsPerPixel { get; set; } - - /// - /// Gets or sets the compression method being used. - /// See the next table for a list of possible values. - /// - public BmpCompression Compression { get; set; } - - /// - /// Gets or sets the image size. This is the size of the raw bitmap data (see below), - /// and should not be confused with the file size. - /// - public int ImageSize { get; set; } - - /// - /// Gets or sets the horizontal resolution of the image. - /// (pixel per meter, signed integer) - /// - public int XPelsPerMeter { get; set; } - - /// - /// Gets or sets the vertical resolution of the image. - /// (pixel per meter, signed integer) - /// - public int YPelsPerMeter { get; set; } - - /// - /// Gets or sets the number of colors in the color palette, - /// or 0 to default to 2^n. - /// - public int ClrUsed { get; set; } - - /// - /// Gets or sets the number of important colors used, - /// or 0 when every color is important{ get; set; } generally ignored. - /// - public int ClrImportant { get; set; } - - /// - /// Gets or sets red color mask. This is used with the BITFIELDS decoding. - /// - public int RedMask { get; set; } - - /// - /// Gets or sets green color mask. This is used with the BITFIELDS decoding. - /// - public int GreenMask { get; set; } - - /// - /// Gets or sets blue color mask. This is used with the BITFIELDS decoding. - /// - public int BlueMask { get; set; } - - /// - /// Gets or sets alpha color mask. This is not used yet. - /// - public int AlphaMask { get; set; } - - /// - /// Gets or sets the Color space type. Not used yet. - /// - public BmpColorSpace CsType { get; set; } - - /// - /// Gets or sets the X coordinate of red endpoint. Not used yet. - /// - public int RedX { get; set; } - - /// - /// Gets or sets the Y coordinate of red endpoint. Not used yet. - /// - public int RedY { get; set; } - - /// - /// Gets or sets the Z coordinate of red endpoint. Not used yet. - /// - public int RedZ { get; set; } - - /// - /// Gets or sets the X coordinate of green endpoint. Not used yet. - /// - public int GreenX { get; set; } - - /// - /// Gets or sets the Y coordinate of green endpoint. Not used yet. - /// - public int GreenY { get; set; } - - /// - /// Gets or sets the Z coordinate of green endpoint. Not used yet. - /// - public int GreenZ { get; set; } - - /// - /// Gets or sets the X coordinate of blue endpoint. Not used yet. - /// - public int BlueX { get; set; } - - /// - /// Gets or sets the Y coordinate of blue endpoint. Not used yet. - /// - public int BlueY { get; set; } - - /// - /// Gets or sets the Z coordinate of blue endpoint. Not used yet. - /// - public int BlueZ { get; set; } - - /// - /// Gets or sets the Gamma red coordinate scale value. Not used yet. - /// - public int GammaRed { get; set; } - - /// - /// Gets or sets the Gamma green coordinate scale value. Not used yet. - /// - public int GammaGreen { get; set; } - - /// - /// Gets or sets the Gamma blue coordinate scale value. Not used yet. - /// - public int GammaBlue { get; set; } - - /// - /// Gets or sets the rendering intent for bitmap. - /// - public BmpRenderingIntent Intent { get; set; } - - /// - /// Gets or sets the offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. - /// - public int ProfileData { get; set; } - - /// - /// Gets or sets the size, in bytes, of embedded profile data. - /// - public int ProfileSize { get; set; } - - /// - /// Gets or sets the reserved value. - /// - public int Reserved { get; set; } - - /// - /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)), - height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)), - bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2))); - - /// - /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height - /// are 4 bytes instead of 2, resulting in 16 bytes total. - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); - - /// - /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), - compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), - imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), - xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), - yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), - clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), - clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); - - /// - /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. - /// 52 bytes without the alpha mask, 56 bytes with the alpha mask. - /// - /// The data to parse. - /// Indicates, if the alpha bitmask is present. - /// The parsed header. - /// - public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), - compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), - imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), - xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), - yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), - clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), - clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), - redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), - greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), - blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), - alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); - - /// - /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are - /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any - /// useful information for decoding the image. - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) - { - BmpInfoHeader infoHeader = new( - headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), - width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), - height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), - planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), - bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); - - // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. - // Map the OS/2 value to the windows values. - switch (BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4))) - { - case 0: - infoHeader.Compression = BmpCompression.RGB; - break; - case 1: - infoHeader.Compression = BmpCompression.RLE8; - break; - case 2: - infoHeader.Compression = BmpCompression.RLE4; - break; - case 4: - infoHeader.Compression = BmpCompression.RLE24; - break; - default: - // Compression type 3 (1DHuffman) is not supported. - BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); - break; - } - - infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); - infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); - infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); - infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); - infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); - - // The following 24 bytes of the header are omitted. - return infoHeader; - } + /// + /// Gets or sets the size of this header. + /// + public int HeaderSize { get; set; } + + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets the number of color planes being used. Must be set to 1. + /// + public short Planes { get; set; } + + /// + /// Gets or sets the number of bits per pixel, which is the color depth of the image. + /// Typical values are 1, 4, 8, 16, 24 and 32. + /// + public short BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression method being used. + /// See the next table for a list of possible values. + /// + public BmpCompression Compression { get; set; } + + /// + /// Gets or sets the image size. This is the size of the raw bitmap data (see below), + /// and should not be confused with the file size. + /// + public int ImageSize { get; set; } + + /// + /// Gets or sets the horizontal resolution of the image. + /// (pixel per meter, signed integer) + /// + public int XPelsPerMeter { get; set; } + + /// + /// Gets or sets the vertical resolution of the image. + /// (pixel per meter, signed integer) + /// + public int YPelsPerMeter { get; set; } + + /// + /// Gets or sets the number of colors in the color palette, + /// or 0 to default to 2^n. + /// + public int ClrUsed { get; set; } + + /// + /// Gets or sets the number of important colors used, + /// or 0 when every color is important{ get; set; } generally ignored. + /// + public int ClrImportant { get; set; } + + /// + /// Gets or sets red color mask. This is used with the BITFIELDS decoding. + /// + public int RedMask { get; set; } + + /// + /// Gets or sets green color mask. This is used with the BITFIELDS decoding. + /// + public int GreenMask { get; set; } + + /// + /// Gets or sets blue color mask. This is used with the BITFIELDS decoding. + /// + public int BlueMask { get; set; } + + /// + /// Gets or sets alpha color mask. This is not used yet. + /// + public int AlphaMask { get; set; } + + /// + /// Gets or sets the Color space type. Not used yet. + /// + public BmpColorSpace CsType { get; set; } + + /// + /// Gets or sets the X coordinate of red endpoint. Not used yet. + /// + public int RedX { get; set; } + + /// + /// Gets or sets the Y coordinate of red endpoint. Not used yet. + /// + public int RedY { get; set; } + + /// + /// Gets or sets the Z coordinate of red endpoint. Not used yet. + /// + public int RedZ { get; set; } + + /// + /// Gets or sets the X coordinate of green endpoint. Not used yet. + /// + public int GreenX { get; set; } + + /// + /// Gets or sets the Y coordinate of green endpoint. Not used yet. + /// + public int GreenY { get; set; } - /// - /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - public static BmpInfoHeader ParseV4(ReadOnlySpan data) => new( + /// + /// Gets or sets the Z coordinate of green endpoint. Not used yet. + /// + public int GreenZ { get; set; } + + /// + /// Gets or sets the X coordinate of blue endpoint. Not used yet. + /// + public int BlueX { get; set; } + + /// + /// Gets or sets the Y coordinate of blue endpoint. Not used yet. + /// + public int BlueY { get; set; } + + /// + /// Gets or sets the Z coordinate of blue endpoint. Not used yet. + /// + public int BlueZ { get; set; } + + /// + /// Gets or sets the Gamma red coordinate scale value. Not used yet. + /// + public int GammaRed { get; set; } + + /// + /// Gets or sets the Gamma green coordinate scale value. Not used yet. + /// + public int GammaGreen { get; set; } + + /// + /// Gets or sets the Gamma blue coordinate scale value. Not used yet. + /// + public int GammaBlue { get; set; } + + /// + /// Gets or sets the rendering intent for bitmap. + /// + public BmpRenderingIntent Intent { get; set; } + + /// + /// Gets or sets the offset, in bytes, from the beginning of the BITMAPV5HEADER structure to the start of the profile data. + /// + public int ProfileData { get; set; } + + /// + /// Gets or sets the size, in bytes, of embedded profile data. + /// + public int ProfileSize { get; set; } + + /// + /// Gets or sets the reserved value. + /// + public int Reserved { get; set; } + + /// + /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseCore(ReadOnlySpan data) => new( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), + width: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(4, 2)), + height: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(6, 2)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(8, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(10, 2))); + + /// + /// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height + /// are 4 bytes instead of 2, resulting in 16 bytes total. + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseOs22Short(ReadOnlySpan data) => new( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); + + /// + /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV3(ReadOnlySpan data) => new( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4))); + + /// + /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it. + /// 52 bytes without the alpha mask, 56 bytes with the alpha mask. + /// + /// The data to parse. + /// Indicates, if the alpha bitmask is present. + /// The parsed header. + /// + public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan data, bool withAlpha = true) => new( headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), @@ -442,104 +369,175 @@ public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), - alphaMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)), - csType: (BmpColorSpace)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(56, 4)), - redX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(60, 4)), - redY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(64, 4)), - redZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(68, 4)), - greenX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(72, 4)), - greenY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(76, 4)), - greenZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(80, 4)), - blueX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(84, 4)), - blueY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(88, 4)), - blueZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(92, 4)), - gammeRed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(96, 4)), - gammeGreen: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(100, 4)), - gammeBlue: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(104, 4))); - - /// - /// Parses the full BMP Version 5 BITMAPINFOHEADER header (124 bytes). - /// - /// The data to parse. - /// The parsed header. - /// - /// Invalid size. - public static BmpInfoHeader ParseV5(ReadOnlySpan data) - { - if (data.Length < SizeV5) - { - throw new ArgumentException($"Must be {SizeV5} bytes. Was {data.Length} bytes.", nameof(data)); - } + alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0); - return MemoryMarshal.Cast(data)[0]; - } + /// + /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are + /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any + /// useful information for decoding the image. + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data) + { + BmpInfoHeader infoHeader = new( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2))); - /// - /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes). - /// - /// The buffer to write to. - public void WriteV3Header(Span buffer) + // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps. + // Map the OS/2 value to the windows values. + switch (BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4))) { - buffer.Clear(); - BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV3); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); + case 0: + infoHeader.Compression = BmpCompression.RGB; + break; + case 1: + infoHeader.Compression = BmpCompression.RLE8; + break; + case 2: + infoHeader.Compression = BmpCompression.RLE4; + break; + case 4: + infoHeader.Compression = BmpCompression.RLE24; + break; + default: + // Compression type 3 (1DHuffman) is not supported. + BmpThrowHelper.ThrowInvalidImageContentException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24."); + break; } - /// - /// Writes a complete Bitmap V4 header to a buffer. - /// - /// The buffer to write to. - public void WriteV4Header(Span buffer) + infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)); + infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)); + infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)); + infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)); + infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)); + + // The following 24 bytes of the header are omitted. + return infoHeader; + } + + /// + /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + public static BmpInfoHeader ParseV4(ReadOnlySpan data) => new( + headerSize: BinaryPrimitives.ReadInt32LittleEndian(data[..4]), + width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)), + height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)), + planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)), + bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)), + compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)), + imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)), + xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)), + yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)), + clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)), + clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)), + redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)), + greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)), + blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)), + alphaMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)), + csType: (BmpColorSpace)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(56, 4)), + redX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(60, 4)), + redY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(64, 4)), + redZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(68, 4)), + greenX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(72, 4)), + greenY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(76, 4)), + greenZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(80, 4)), + blueX: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(84, 4)), + blueY: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(88, 4)), + blueZ: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(92, 4)), + gammeRed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(96, 4)), + gammeGreen: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(100, 4)), + gammeBlue: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(104, 4))); + + /// + /// Parses the full BMP Version 5 BITMAPINFOHEADER header (124 bytes). + /// + /// The data to parse. + /// The parsed header. + /// + /// Invalid size. + public static BmpInfoHeader ParseV5(ReadOnlySpan data) + { + if (data.Length < SizeV5) { - buffer.Clear(); - BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV4); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); - BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(40, 4), this.RedMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(44, 4), this.GreenMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(48, 4), this.BlueMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(52, 4), this.AlphaMask); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(56, 4), (int)this.CsType); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(60, 4), this.RedX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(64, 4), this.RedY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(68, 4), this.RedZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(72, 4), this.GreenX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(76, 4), this.GreenY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(80, 4), this.GreenZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(84, 4), this.BlueX); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(88, 4), this.BlueY); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(92, 4), this.BlueZ); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(96, 4), this.GammaRed); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(100, 4), this.GammaGreen); - BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue); + throw new ArgumentException($"Must be {SizeV5} bytes. Was {data.Length} bytes.", nameof(data)); } - /// - /// Writes a complete Bitmap V5 header to a buffer. - /// - /// The buffer to write to. - public void WriteV5Header(Span buffer) - { - ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + return MemoryMarshal.Cast(data)[0]; + } - dest = this; - } + /// + /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes). + /// + /// The buffer to write to. + public void WriteV3Header(Span buffer) + { + buffer.Clear(); + BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV3); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); + } + + /// + /// Writes a complete Bitmap V4 header to a buffer. + /// + /// The buffer to write to. + public void WriteV4Header(Span buffer) + { + buffer.Clear(); + BinaryPrimitives.WriteInt32LittleEndian(buffer[..4], SizeV4); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes); + BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(40, 4), this.RedMask); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(44, 4), this.GreenMask); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(48, 4), this.BlueMask); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(52, 4), this.AlphaMask); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(56, 4), (int)this.CsType); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(60, 4), this.RedX); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(64, 4), this.RedY); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(68, 4), this.RedZ); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(72, 4), this.GreenX); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(76, 4), this.GreenY); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(80, 4), this.GreenZ); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(84, 4), this.BlueX); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(88, 4), this.BlueY); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(92, 4), this.BlueZ); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(96, 4), this.GammaRed); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(100, 4), this.GammaGreen); + BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(104, 4), this.GammaBlue); + } + + /// + /// Writes a complete Bitmap V5 header to a buffer. + /// + /// The buffer to write to. + public void WriteV5Header(Span buffer) + { + ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + dest = this; } } diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs index 34b063ae42..026ad54030 100644 --- a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs +++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs @@ -1,51 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header. +/// +public enum BmpInfoHeaderType { /// - /// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header. - /// - public enum BmpInfoHeaderType - { - /// - /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x). - /// - WinVersion2 = 12, - - /// - /// Short variant of the OS/2 Version 2 bitmap header. - /// - Os2Version2Short = 16, - - /// - /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT). - /// - WinVersion3 = 40, - - /// - /// Adobe variant of the BMP Version 3 header. - /// - AdobeVersion3 = 52, - - /// - /// Adobe variant of the BMP Version 3 header with an alpha mask. - /// - AdobeVersion3WithAlpha = 56, - - /// - /// BMP Version 2.x header (IBM OS/2 2.x). - /// - Os2Version2 = 64, - - /// - /// BMP Version 4 header (Microsoft Windows 95). - /// - WinVersion4 = 108, - - /// - /// BMP Version 5 header (Windows NT 5.0, 98 or later). - /// - WinVersion5 = 124, - } + /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x). + /// + WinVersion2 = 12, + + /// + /// Short variant of the OS/2 Version 2 bitmap header. + /// + Os2Version2Short = 16, + + /// + /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT). + /// + WinVersion3 = 40, + + /// + /// Adobe variant of the BMP Version 3 header. + /// + AdobeVersion3 = 52, + + /// + /// Adobe variant of the BMP Version 3 header with an alpha mask. + /// + AdobeVersion3WithAlpha = 56, + + /// + /// BMP Version 2.x header (IBM OS/2 2.x). + /// + Os2Version2 = 64, + + /// + /// BMP Version 4 header (Microsoft Windows 95). + /// + WinVersion4 = 108, + + /// + /// BMP Version 5 header (Windows NT 5.0, 98 or later). + /// + WinVersion5 = 124, } diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs index 4a0725a7ae..a2ed1d21d0 100644 --- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs +++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs @@ -1,43 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Provides Bmp specific metadata information for the image. +/// +public class BmpMetadata : IDeepCloneable { /// - /// Provides Bmp specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class BmpMetadata : IDeepCloneable + public BmpMetadata() { - /// - /// Initializes a new instance of the class. - /// - public BmpMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private BmpMetadata(BmpMetadata other) - { - this.BitsPerPixel = other.BitsPerPixel; - this.InfoHeaderType = other.InfoHeaderType; - } + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private BmpMetadata(BmpMetadata other) + { + this.BitsPerPixel = other.BitsPerPixel; + this.InfoHeaderType = other.InfoHeaderType; + } - /// - /// Gets or sets the bitmap info header type. - /// - public BmpInfoHeaderType InfoHeaderType { get; set; } + /// + /// Gets or sets the bitmap info header type. + /// + public BmpInfoHeaderType InfoHeaderType { get; set; } - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - /// - public IDeepCloneable DeepClone() => new BmpMetadata(this); + /// + public IDeepCloneable DeepClone() => new BmpMetadata(this); - // TODO: Colors used once we support encoding palette bmps. - } + // TODO: Colors used once we support encoding palette bmps. } diff --git a/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs b/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs index 2000a27941..87a1f19cc7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs +++ b/src/ImageSharp/Formats/Bmp/BmpRenderingIntent.cs @@ -2,36 +2,35 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Enum for the different rendering intent's. +/// +internal enum BmpRenderingIntent { /// - /// Enum for the different rendering intent's. + /// Invalid default value. /// - internal enum BmpRenderingIntent - { - /// - /// Invalid default value. - /// - Invalid = 0, + Invalid = 0, - /// - /// Maintains saturation. Used for business charts and other situations in which undithered colors are required. - /// - LCS_GM_BUSINESS = 1, + /// + /// Maintains saturation. Used for business charts and other situations in which undithered colors are required. + /// + LCS_GM_BUSINESS = 1, - /// - /// Maintains colorimetric match. Used for graphic designs and named colors. - /// - LCS_GM_GRAPHICS = 2, + /// + /// Maintains colorimetric match. Used for graphic designs and named colors. + /// + LCS_GM_GRAPHICS = 2, - /// - /// Maintains contrast. Used for photographs and natural images. - /// - LCS_GM_IMAGES = 4, + /// + /// Maintains contrast. Used for photographs and natural images. + /// + LCS_GM_IMAGES = 4, - /// - /// Maintains the white point. Matches the colors to their nearest color in the destination gamut. - /// - LCS_GM_ABS_COLORIMETRIC = 8, - } + /// + /// Maintains the white point. Matches the colors to their nearest color in the destination gamut. + /// + LCS_GM_ABS_COLORIMETRIC = 8, } diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs index 5fa760b8ee..2137f54dc0 100644 --- a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs +++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs @@ -1,27 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +internal static class BmpThrowHelper { - internal static class BmpThrowHelper - { - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowNotSupportedException(string errorMessage) - => throw new NotSupportedException(errorMessage); - } + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string errorMessage) + => throw new NotSupportedException(errorMessage); } diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index 9c035119d8..c2ce99ec71 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -3,29 +3,28 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Configuration options for use during bmp encoding. +/// +internal interface IBmpEncoderOptions { /// - /// Configuration options for use during bmp encoding. + /// Gets the number of bits per pixel. /// - internal interface IBmpEncoderOptions - { - /// - /// Gets the number of bits per pixel. - /// - BmpBitsPerPixel? BitsPerPixel { get; } + BmpBitsPerPixel? BitsPerPixel { get; } - /// - /// Gets a value indicating whether the encoder should support transparency. - /// Note: Transparency support only works together with 32 bits per pixel. This option will - /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. - /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. - /// - bool SupportTransparency { get; } + /// + /// Gets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. + /// + bool SupportTransparency { get; } - /// - /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images. - /// - IQuantizer Quantizer { get; } - } + /// + /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images. + /// + IQuantizer Quantizer { get; } } diff --git a/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs index 5c7f9c2d45..5297d0c989 100644 --- a/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Bmp/MetadataExtensions.cs @@ -4,18 +4,17 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the bmp format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the bmp format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static BmpMetadata GetBmpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(BmpFormat.Instance); } diff --git a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs index 4e23947c1a..9c0cdb94c0 100644 --- a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs +++ b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Bmp +namespace SixLabors.ImageSharp.Formats.Bmp; + +/// +/// Defines possible options, how skipped pixels during decoding of run length encoded bitmaps should be treated. +/// +public enum RleSkippedPixelHandling : int { /// - /// Defines possible options, how skipped pixels during decoding of run length encoded bitmaps should be treated. + /// Undefined pixels should be black. This is the default behavior and equal to how System.Drawing handles undefined pixels. /// - public enum RleSkippedPixelHandling : int - { - /// - /// Undefined pixels should be black. This is the default behavior and equal to how System.Drawing handles undefined pixels. - /// - Black = 0, + Black = 0, - /// - /// Undefined pixels should be transparent. - /// - Transparent = 1, + /// + /// Undefined pixels should be transparent. + /// + Transparent = 1, - /// - /// Undefined pixels should have the first color of the palette. - /// - FirstColorOfPalette = 2 - } + /// + /// Undefined pixels should have the first color of the palette. + /// + FirstColorOfPalette = 2 } diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index e21f21f7b2..3e3f1aa50d 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Provides general configuration options for decoding image formats. +/// +public sealed class DecoderOptions { + private static readonly Lazy LazyOptions = new(() => new()); + + private uint maxFrames = int.MaxValue; + + /// + /// Gets the shared default general decoder options instance. + /// + internal static DecoderOptions Default { get; } = LazyOptions.Value; + + /// + /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. + /// + public Configuration Configuration { get; set; } = Configuration.Default; + + /// + /// Gets or sets the target size to decode the image into. + /// + public Size? TargetSize { get; set; } + + /// + /// Gets or sets the sampler to use when resizing during decoding. + /// + public IResampler Sampler { get; set; } = KnownResamplers.Box; + + /// + /// Gets or sets a value indicating whether to ignore encoded metadata when decoding. + /// + public bool SkipMetadata { get; set; } + /// - /// Provides general configuration options for decoding image formats. + /// Gets or sets the maximum number of image frames to decode, inclusive. /// - public sealed class DecoderOptions - { - private static readonly Lazy LazyOptions = new(() => new()); - - private uint maxFrames = int.MaxValue; - - /// - /// Gets the shared default general decoder options instance. - /// - internal static DecoderOptions Default { get; } = LazyOptions.Value; - - /// - /// Gets or sets a custom Configuration instance to be used by the image processing pipeline. - /// - public Configuration Configuration { get; set; } = Configuration.Default; - - /// - /// Gets or sets the target size to decode the image into. - /// - public Size? TargetSize { get; set; } - - /// - /// Gets or sets the sampler to use when resizing during decoding. - /// - public IResampler Sampler { get; set; } = KnownResamplers.Box; - - /// - /// Gets or sets a value indicating whether to ignore encoded metadata when decoding. - /// - public bool SkipMetadata { get; set; } - - /// - /// Gets or sets the maximum number of image frames to decode, inclusive. - /// - public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); } - } + public uint MaxFrames { get => this.maxFrames; set => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); } } diff --git a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs index a33dc1707e..0f65f46022 100644 --- a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs +++ b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Provides enumeration for the available color table modes. +/// +public enum GifColorTableMode { /// - /// Provides enumeration for the available color table modes. + /// A single color table is calculated from the first frame and reused for subsequent frames. /// - public enum GifColorTableMode - { - /// - /// A single color table is calculated from the first frame and reused for subsequent frames. - /// - Global, + Global, - /// - /// A unique color table is calculated for each frame. - /// - Local - } + /// + /// A unique color table is calculated for each frame. + /// + Local } diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 108026ffec..8c8067adaf 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Registers the image encoders, decoders and mime type detectors for the gif format. +/// +public sealed class GifConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the gif format. - /// - public sealed class GifConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder()); - configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder()); + configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 4aa18bacb1..796b5506c7 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,136 +1,133 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Text; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Constants that define specific points within a Gif. +/// +internal static class GifConstants { /// - /// Constants that define specific points within a Gif. + /// The file type. + /// + public const string FileType = "GIF"; + + /// + /// The file version. + /// + public const string FileVersion = "89a"; + + /// + /// The extension block introducer !. + /// + public const byte ExtensionIntroducer = 0x21; + + /// + /// The graphic control label. + /// + public const byte GraphicControlLabel = 0xF9; + + /// + /// The application extension label. + /// + public const byte ApplicationExtensionLabel = 0xFF; + + /// + /// The application block size. + /// + public const byte ApplicationBlockSize = 11; + + /// + /// The application identification. + /// + public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; + + /// + /// The Netscape looping application sub block size. + /// + public const byte NetscapeLoopingSubBlockSize = 3; + + /// + /// The comment label. + /// + public const byte CommentLabel = 0xFE; + + /// + /// The maximum length of a comment data sub-block is 255. + /// + public const int MaxCommentSubBlockLength = 255; + + /// + /// The image descriptor label ,. + /// + public const byte ImageDescriptorLabel = 0x2C; + + /// + /// The plain text label. + /// + public const byte PlainTextLabel = 0x01; + + /// + /// The image label introducer ,. + /// + public const byte ImageLabel = 0x2C; + + /// + /// The terminator. + /// + public const byte Terminator = 0; + + /// + /// The end introducer trailer ;. + /// + public const byte EndIntroducer = 0x3B; + + /// + /// The character encoding to use when reading and writing comments - (ASCII 7bit). + /// + public static readonly Encoding Encoding = Encoding.ASCII; + + /// + /// The collection of mimetypes that equate to a Gif. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/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' + }; + + /// + /// Gets the ASCII encoded application identification bytes. /// - internal static class GifConstants + internal static ReadOnlySpan XmpApplicationIdentificationBytes => new[] { - /// - /// The file type. - /// - public const string FileType = "GIF"; - - /// - /// The file version. - /// - public const string FileVersion = "89a"; - - /// - /// The extension block introducer !. - /// - public const byte ExtensionIntroducer = 0x21; - - /// - /// The graphic control label. - /// - public const byte GraphicControlLabel = 0xF9; - - /// - /// The application extension label. - /// - public const byte ApplicationExtensionLabel = 0xFF; - - /// - /// The application block size. - /// - public const byte ApplicationBlockSize = 11; - - /// - /// The application identification. - /// - public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; - - /// - /// The Netscape looping application sub block size. - /// - public const byte NetscapeLoopingSubBlockSize = 3; - - /// - /// The comment label. - /// - public const byte CommentLabel = 0xFE; - - /// - /// The maximum length of a comment data sub-block is 255. - /// - public const int MaxCommentSubBlockLength = 255; - - /// - /// The image descriptor label ,. - /// - public const byte ImageDescriptorLabel = 0x2C; - - /// - /// The plain text label. - /// - public const byte PlainTextLabel = 0x01; - - /// - /// The image label introducer ,. - /// - public const byte ImageLabel = 0x2C; - - /// - /// The terminator. - /// - public const byte Terminator = 0; - - /// - /// The end introducer trailer ;. - /// - public const byte EndIntroducer = 0x3B; - - /// - /// The character encoding to use when reading and writing comments - (ASCII 7bit). - /// - public static readonly Encoding Encoding = Encoding.ASCII; - - /// - /// The collection of mimetypes that equate to a Gif. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/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' - }; - - /// - /// Gets the ASCII encoded application identification bytes. - /// - internal static ReadOnlySpan XmpApplicationIdentificationBytes => new[] - { - (byte)'X', (byte)'M', (byte)'P', - (byte)' ', (byte)'D', (byte)'a', - (byte)'t', (byte)'a', - (byte)'X', (byte)'M', (byte)'P' - }; - } + (byte)'X', (byte)'M', (byte)'P', + (byte)' ', (byte)'D', (byte)'a', + (byte)'t', (byte)'a', + (byte)'X', (byte)'M', (byte)'P' + }; } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index a737ce74ed..cf8f4637ef 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,42 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Decoder for generating an image out of a gif encoded stream. +/// +public sealed class GifDecoder : IImageDecoder { - /// - /// Decoder for generating an image out of a gif encoded stream. - /// - public sealed class GifDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + return new GifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } - GifDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options, image); + GifDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options, image); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoder)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index b307ffd60f..ef29863d4f 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -1,693 +1,689 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Performs the gif decoding operation. +/// +internal sealed class GifDecoderCore : IImageDecoderInternals { /// - /// Performs the gif decoding operation. + /// The temp buffer used to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// The currently loaded stream. + /// + private BufferedReadStream stream; + + /// + /// The global color table. + /// + private IMemoryOwner globalColorTable; + + /// + /// The area to restore. + /// + private Rectangle? restoreArea; + + /// + /// The logical screen descriptor. + /// + private GifLogicalScreenDescriptor logicalScreenDescriptor; + + /// + /// The graphics control extension. + /// + private GifGraphicControlExtension graphicsControlExtension; + + /// + /// The image descriptor. + /// + private GifImageDescriptor imageDescriptor; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The maximum number of frames to decode. Inclusive. + /// + private readonly uint maxFrames; + + /// + /// Whether to skip metadata during decode. + /// + private readonly bool skipMetadata; + + /// + /// The abstract metadata. + /// + private ImageMetadata metadata; + + /// + /// The gif specific metadata. /// - internal sealed class GifDecoderCore : IImageDecoderInternals + private GifMetadata gifMetadata; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public GifDecoderCore(DecoderOptions options) { - /// - /// The temp buffer used to reduce allocations. - /// - private readonly byte[] buffer = new byte[16]; - - /// - /// The currently loaded stream. - /// - private BufferedReadStream stream; - - /// - /// The global color table. - /// - private IMemoryOwner globalColorTable; - - /// - /// The area to restore. - /// - private Rectangle? restoreArea; - - /// - /// The logical screen descriptor. - /// - private GifLogicalScreenDescriptor logicalScreenDescriptor; - - /// - /// The graphics control extension. - /// - private GifGraphicControlExtension graphicsControlExtension; - - /// - /// The image descriptor. - /// - private GifImageDescriptor imageDescriptor; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// Whether to skip metadata during decode. - /// - private readonly bool skipMetadata; - - /// - /// The abstract metadata. - /// - private ImageMetadata metadata; - - /// - /// The gif specific metadata. - /// - private GifMetadata gifMetadata; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public GifDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.maxFrames = options.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } + this.Options = options; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - public DecoderOptions Options { get; } + /// + public DecoderOptions Options { get; } - /// - public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); + /// + public Size Dimensions => new(this.imageDescriptor.Width, this.imageDescriptor.Height); - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + uint frameCount = 0; + Image image = null; + ImageFrame previousFrame = null; + try { - uint frameCount = 0; - Image image = null; - ImageFrame previousFrame = null; - try - { - this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); + this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); - // Loop though the respective gif parts and read the data. - int nextFlag = stream.ReadByte(); - while (nextFlag != GifConstants.Terminator) + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) + { + if (nextFlag == GifConstants.ImageLabel) { - if (nextFlag == GifConstants.ImageLabel) - { - if (previousFrame != null && ++frameCount == this.maxFrames) - { - break; - } - - this.ReadFrame(ref image, ref previousFrame); - } - else if (nextFlag == GifConstants.ExtensionIntroducer) - { - switch (stream.ReadByte()) - { - case GifConstants.GraphicControlLabel: - this.ReadGraphicalControlExtension(); - break; - case GifConstants.CommentLabel: - this.ReadComments(); - break; - case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(); - break; - case GifConstants.PlainTextLabel: - this.SkipBlock(); // Not supported by any known decoder. - break; - } - } - else if (nextFlag == GifConstants.EndIntroducer) + if (previousFrame != null && ++frameCount == this.maxFrames) { break; } - nextFlag = stream.ReadByte(); - if (nextFlag == -1) + this.ReadFrame(ref image, ref previousFrame); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + switch (stream.ReadByte()) { - break; + case GifConstants.GraphicControlLabel: + this.ReadGraphicalControlExtension(); + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.ReadApplicationExtension(); + break; + case GifConstants.PlainTextLabel: + this.SkipBlock(); // Not supported by any known decoder. + break; } } - } - finally - { - this.globalColorTable?.Dispose(); - } + else if (nextFlag == GifConstants.EndIntroducer) + { + break; + } - return image; + nextFlag = stream.ReadByte(); + if (nextFlag == -1) + { + break; + } + } + } + finally + { + this.globalColorTable?.Dispose(); } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + return image; + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + try { - try - { - this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); + this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); - // Loop though the respective gif parts and read the data. - int nextFlag = stream.ReadByte(); - while (nextFlag != GifConstants.Terminator) + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) + { + if (nextFlag == GifConstants.ImageLabel) { - if (nextFlag == GifConstants.ImageLabel) - { - this.ReadImageDescriptor(); - } - else if (nextFlag == GifConstants.ExtensionIntroducer) - { - switch (stream.ReadByte()) - { - case GifConstants.GraphicControlLabel: - this.SkipBlock(); // Skip graphic control extension block - break; - case GifConstants.CommentLabel: - this.ReadComments(); - break; - case GifConstants.ApplicationExtensionLabel: - this.ReadApplicationExtension(); - break; - case GifConstants.PlainTextLabel: - this.SkipBlock(); // Not supported by any known decoder. - break; - } - } - else if (nextFlag == GifConstants.EndIntroducer) + this.ReadImageDescriptor(); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + switch (stream.ReadByte()) { - break; + case GifConstants.GraphicControlLabel: + this.SkipBlock(); // Skip graphic control extension block + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.ReadApplicationExtension(); + break; + case GifConstants.PlainTextLabel: + this.SkipBlock(); // Not supported by any known decoder. + break; } + } + else if (nextFlag == GifConstants.EndIntroducer) + { + break; + } - nextFlag = stream.ReadByte(); - if (nextFlag == -1) - { - break; - } + nextFlag = stream.ReadByte(); + if (nextFlag == -1) + { + break; } } - finally - { - this.globalColorTable?.Dispose(); - } - - return new ImageInfo( - new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), - this.logicalScreenDescriptor.Width, - this.logicalScreenDescriptor.Height, - this.metadata); } - - /// - /// Reads the graphic control extension. - /// - private void ReadGraphicalControlExtension() + finally { - int bytesRead = this.stream.Read(this.buffer, 0, 6); - if (bytesRead != 6) - { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); - } - - this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); + this.globalColorTable?.Dispose(); } - /// - /// Reads the image descriptor. - /// - private void ReadImageDescriptor() + return new ImageInfo( + new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), + this.logicalScreenDescriptor.Width, + this.logicalScreenDescriptor.Height, + this.metadata); + } + + /// + /// Reads the graphic control extension. + /// + private void ReadGraphicalControlExtension() + { + int bytesRead = this.stream.Read(this.buffer, 0, 6); + if (bytesRead != 6) { - int bytesRead = this.stream.Read(this.buffer, 0, 9); - if (bytesRead != 9) - { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); - } + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension"); + } - this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); - if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) - { - GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); - } + this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); + } + + /// + /// Reads the image descriptor. + /// + private void ReadImageDescriptor() + { + int bytesRead = this.stream.Read(this.buffer, 0, 9); + if (bytesRead != 9) + { + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor"); } - /// - /// Reads the logical screen descriptor. - /// - private void ReadLogicalScreenDescriptor() + this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); + if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) { - int bytesRead = this.stream.Read(this.buffer, 0, 7); - if (bytesRead != 7) - { - GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); - } + GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0"); + } + } - this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); + /// + /// Reads the logical screen descriptor. + /// + private void ReadLogicalScreenDescriptor() + { + int bytesRead = this.stream.Read(this.buffer, 0, 7); + if (bytesRead != 7) + { + GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor"); } - /// - /// Reads the application extension block parsing any animation or XMP information - /// if present. - /// - private void ReadApplicationExtension() + this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); + } + + /// + /// Reads the application extension block parsing any animation or XMP information + /// if present. + /// + private void ReadApplicationExtension() + { + int appLength = this.stream.ReadByte(); + + // If the length is 11 then it's a valid extension and most likely + // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this. + if (appLength == GifConstants.ApplicationBlockSize) { - int appLength = this.stream.ReadByte(); + this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); + bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); - // If the length is 11 then it's a valid extension and most likely - // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this. - if (appLength == GifConstants.ApplicationBlockSize) + if (isXmp && !this.skipMetadata) { - this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); - bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); - - if (isXmp && !this.skipMetadata) + var extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator); + if (extension.Data.Length > 0) { - var extension = GifXmpApplicationExtension.Read(this.stream, this.memoryAllocator); - if (extension.Data.Length > 0) - { - this.metadata.XmpProfile = new XmpProfile(extension.Data); - } - - return; + this.metadata.XmpProfile = new XmpProfile(extension.Data); } - else - { - int subBlockSize = this.stream.ReadByte(); - // TODO: There's also a NETSCAPE buffer extension. - // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension - if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) - { - this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; - this.stream.Skip(1); // Skip the terminator. - return; - } + return; + } + else + { + int subBlockSize = this.stream.ReadByte(); - // Could be something else not supported yet. - // Skip the subblock and terminator. - this.SkipBlock(subBlockSize); + // TODO: There's also a NETSCAPE buffer extension. + // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension + if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) + { + this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); + this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; + this.stream.Skip(1); // Skip the terminator. + return; } - return; + // Could be something else not supported yet. + // Skip the subblock and terminator. + this.SkipBlock(subBlockSize); } - this.SkipBlock(appLength); // Not supported by any known decoder. + return; + } + + this.SkipBlock(appLength); // Not supported by any known decoder. + } + + /// + /// Skips over a block or reads its terminator. + /// The length of the block to skip. + /// + private void SkipBlock(int blockSize = 0) + { + if (blockSize > 0) + { + this.stream.Skip(blockSize); } - /// - /// Skips over a block or reads its terminator. - /// The length of the block to skip. - /// - private void SkipBlock(int blockSize = 0) + int flag; + + while ((flag = this.stream.ReadByte()) > 0) { - if (blockSize > 0) + this.stream.Skip(flag); + } + } + + /// + /// Reads the gif comments. + /// + private void ReadComments() + { + int length; + + var stringBuilder = new StringBuilder(); + while ((length = this.stream.ReadByte()) != 0) + { + if (length > GifConstants.MaxCommentSubBlockLength) { - this.stream.Skip(blockSize); + GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); } - int flag; - - while ((flag = this.stream.ReadByte()) > 0) + if (this.skipMetadata) { - this.stream.Skip(flag); + this.stream.Seek(length, SeekOrigin.Current); + continue; } + + using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); + Span commentsSpan = commentsBuffer.GetSpan(); + + this.stream.Read(commentsSpan); + string commentPart = GifConstants.Encoding.GetString(commentsSpan); + stringBuilder.Append(commentPart); } - /// - /// Reads the gif comments. - /// - private void ReadComments() + if (stringBuilder.Length > 0) { - int length; + this.gifMetadata.Comments.Add(stringBuilder.ToString()); + } + } - var stringBuilder = new StringBuilder(); - while ((length = this.stream.ReadByte()) != 0) - { - if (length > GifConstants.MaxCommentSubBlockLength) - { - GifThrowHelper.ThrowInvalidImageContentException($"Gif comment length '{length}' exceeds max '{GifConstants.MaxCommentSubBlockLength}' of a comment data block"); - } + /// + /// Reads an individual gif frame. + /// + /// The pixel format. + /// The image to decode the information to. + /// The previous frame. + private void ReadFrame(ref Image image, ref ImageFrame previousFrame) + where TPixel : unmanaged, IPixel + { + this.ReadImageDescriptor(); - if (this.skipMetadata) - { - this.stream.Seek(length, SeekOrigin.Current); - continue; - } + IMemoryOwner localColorTable = null; + Buffer2D indices = null; + try + { + // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. + if (this.imageDescriptor.LocalColorTableFlag) + { + int length = this.imageDescriptor.LocalColorTableSize * 3; + localColorTable = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + this.stream.Read(localColorTable.GetSpan()); + } - using IMemoryOwner commentsBuffer = this.memoryAllocator.Allocate(length); - Span commentsSpan = commentsBuffer.GetSpan(); + indices = this.configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); + this.ReadFrameIndices(indices); - this.stream.Read(commentsSpan); - string commentPart = GifConstants.Encoding.GetString(commentsSpan); - stringBuilder.Append(commentPart); + Span rawColorTable = default; + if (localColorTable != null) + { + rawColorTable = localColorTable.GetSpan(); } - - if (stringBuilder.Length > 0) + else if (this.globalColorTable != null) { - this.gifMetadata.Comments.Add(stringBuilder.ToString()); + rawColorTable = this.globalColorTable.GetSpan(); } - } - /// - /// Reads an individual gif frame. - /// - /// The pixel format. - /// The image to decode the information to. - /// The previous frame. - private void ReadFrame(ref Image image, ref ImageFrame previousFrame) - where TPixel : unmanaged, IPixel - { - this.ReadImageDescriptor(); + ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); + this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); - IMemoryOwner localColorTable = null; - Buffer2D indices = null; - try - { - // Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. - if (this.imageDescriptor.LocalColorTableFlag) - { - int length = this.imageDescriptor.LocalColorTableSize * 3; - localColorTable = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); - this.stream.Read(localColorTable.GetSpan()); - } + // Skip any remaining blocks + this.SkipBlock(); + } + finally + { + localColorTable?.Dispose(); + indices?.Dispose(); + } + } - indices = this.configuration.MemoryAllocator.Allocate2D(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); - this.ReadFrameIndices(indices); + /// + /// Reads the frame indices marking the color to use for each pixel. + /// + /// The 2D pixel buffer to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadFrameIndices(Buffer2D indices) + { + int minCodeSize = this.stream.ReadByte(); + using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); + lzwDecoder.DecodePixels(minCodeSize, indices); + } - Span rawColorTable = default; - if (localColorTable != null) - { - rawColorTable = localColorTable.GetSpan(); - } - else if (this.globalColorTable != null) - { - rawColorTable = this.globalColorTable.GetSpan(); - } + /// + /// Reads the frames colors, mapping indices to colors. + /// + /// The pixel format. + /// The image to decode the information to. + /// The previous frame. + /// The indexed pixels. + /// The color table containing the available colors. + /// The + private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Buffer2D indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) + where TPixel : unmanaged, IPixel + { + int imageWidth = this.logicalScreenDescriptor.Width; + int imageHeight = this.logicalScreenDescriptor.Height; + bool transFlag = this.graphicsControlExtension.TransparencyFlag; - ReadOnlySpan colorTable = MemoryMarshal.Cast(rawColorTable); - this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor); + ImageFrame prevFrame = null; + ImageFrame currentFrame = null; + ImageFrame imageFrame; - // Skip any remaining blocks - this.SkipBlock(); + if (previousFrame is null) + { + if (!transFlag) + { + image = new Image(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); } - finally + else { - localColorTable?.Dispose(); - indices?.Dispose(); + // This initializes the image to become fully transparent because the alpha channel is zero. + image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); } - } - /// - /// Reads the frame indices marking the color to use for each pixel. - /// - /// The 2D pixel buffer to write to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadFrameIndices(Buffer2D indices) + this.SetFrameMetadata(image.Frames.RootFrame.Metadata); + + imageFrame = image.Frames.RootFrame; + } + else { - int minCodeSize = this.stream.ReadByte(); - using var lzwDecoder = new LzwDecoder(this.configuration.MemoryAllocator, this.stream); - lzwDecoder.DecodePixels(minCodeSize, indices); + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious) + { + prevFrame = previousFrame; + } + + currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection + + this.SetFrameMetadata(currentFrame.Metadata); + + imageFrame = currentFrame; + + this.RestoreToBackground(imageFrame); } - /// - /// Reads the frames colors, mapping indices to colors. - /// - /// The pixel format. - /// The image to decode the information to. - /// The previous frame. - /// The indexed pixels. - /// The color table containing the available colors. - /// The - private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Buffer2D indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) - where TPixel : unmanaged, IPixel + if (colorTable.Length == 0) { - int imageWidth = this.logicalScreenDescriptor.Width; - int imageHeight = this.logicalScreenDescriptor.Height; - bool transFlag = this.graphicsControlExtension.TransparencyFlag; + return; + } - ImageFrame prevFrame = null; - ImageFrame currentFrame = null; - ImageFrame imageFrame; + int interlacePass = 0; // The interlace pass + int interlaceIncrement = 8; // The interlacing line increment + int interlaceY = 0; // The current interlaced line + int descriptorTop = descriptor.Top; + int descriptorBottom = descriptorTop + descriptor.Height; + int descriptorLeft = descriptor.Left; + int descriptorRight = descriptorLeft + descriptor.Width; + byte transIndex = this.graphicsControlExtension.TransparencyIndex; + int colorTableMaxIdx = colorTable.Length - 1; + + for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) + { + ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop)); - if (previousFrame is null) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - if (!transFlag) - { - image = new Image(this.configuration, imageWidth, imageHeight, Color.Black.ToPixel(), this.metadata); - } - else + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) { - // This initializes the image to become fully transparent because the alpha channel is zero. - image = new Image(this.configuration, imageWidth, imageHeight, this.metadata); + interlacePass++; + switch (interlacePass) + { + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; + } } - this.SetFrameMetadata(image.Frames.RootFrame.Metadata); - - imageFrame = image.Frames.RootFrame; + writeY = interlaceY + descriptor.Top; + interlaceY += interlaceIncrement; } else { - if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious) - { - prevFrame = previousFrame; - } - - currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection - - this.SetFrameMetadata(currentFrame.Metadata); - - imageFrame = currentFrame; - - this.RestoreToBackground(imageFrame); + writeY = y; } - if (colorTable.Length == 0) - { - return; - } + ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY)); - int interlacePass = 0; // The interlace pass - int interlaceIncrement = 8; // The interlacing line increment - int interlaceY = 0; // The current interlaced line - int descriptorTop = descriptor.Top; - int descriptorBottom = descriptorTop + descriptor.Height; - int descriptorLeft = descriptor.Left; - int descriptorRight = descriptorLeft + descriptor.Width; - byte transIndex = this.graphicsControlExtension.TransparencyIndex; - int colorTableMaxIdx = colorTable.Length - 1; - - for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) + if (!transFlag) { - ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop)); - - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + // #403 The left + width value can be larger than the image width + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) - { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } - } - - writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; + int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); + ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); + Rgb24 rgb = colorTable[index]; + pixel.FromRgb24(rgb); } - - ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY)); - - if (!transFlag) + } + else + { + for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) { - // #403 The left + width value can be larger than the image width - for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) + int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); + if (transIndex != index) { - int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); Rgb24 rgb = colorTable[index]; pixel.FromRgb24(rgb); } } - else - { - for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) - { - int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx); - if (transIndex != index) - { - ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); - Rgb24 rgb = colorTable[index]; - pixel.FromRgb24(rgb); - } - } - } } + } - if (prevFrame != null) - { - previousFrame = prevFrame; - return; - } + if (prevFrame != null) + { + previousFrame = prevFrame; + return; + } - previousFrame = currentFrame ?? image.Frames.RootFrame; + previousFrame = currentFrame ?? image.Frames.RootFrame; - if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground) - { - this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); - } + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground) + { + this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); } + } - /// - /// Restores the current frame area to the background. - /// - /// The pixel format. - /// The frame. - private void RestoreToBackground(ImageFrame frame) - where TPixel : unmanaged, IPixel + /// + /// Restores the current frame area to the background. + /// + /// The pixel format. + /// The frame. + private void RestoreToBackground(ImageFrame frame) + where TPixel : unmanaged, IPixel + { + if (this.restoreArea is null) { - if (this.restoreArea is null) - { - return; - } + return; + } - var interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value); - Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(interest); - pixelRegion.Clear(); + var interest = Rectangle.Intersect(frame.Bounds(), this.restoreArea.Value); + Buffer2DRegion pixelRegion = frame.PixelBuffer.GetRegion(interest); + pixelRegion.Clear(); - this.restoreArea = null; + this.restoreArea = null; + } + + /// + /// Sets the frames metadata. + /// + /// The metadata. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetFrameMetadata(ImageFrameMetadata meta) + { + GifFrameMetadata gifMeta = meta.GetGifMetadata(); + if (this.graphicsControlExtension.DelayTime > 0) + { + gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; } - /// - /// Sets the frames metadata. - /// - /// The metadata. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetFrameMetadata(ImageFrameMetadata meta) + // Frames can either use the global table or their own local table. + if (this.logicalScreenDescriptor.GlobalColorTableFlag + && this.logicalScreenDescriptor.GlobalColorTableSize > 0) { - GifFrameMetadata gifMeta = meta.GetGifMetadata(); - if (this.graphicsControlExtension.DelayTime > 0) - { - gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; - } + gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize; + } + else if (this.imageDescriptor.LocalColorTableFlag + && this.imageDescriptor.LocalColorTableSize > 0) + { + gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize; + } + + gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; + } + + /// + /// Reads the logical screen descriptor and global color table blocks + /// + /// The stream containing image data. + private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) + { + this.stream = stream; + + // Skip the identifier + this.stream.Skip(6); + this.ReadLogicalScreenDescriptor(); + + var meta = new ImageMetadata(); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + if (this.logicalScreenDescriptor.PixelAspectRatio > 0) + { + meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; + float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; - // Frames can either use the global table or their own local table. - if (this.logicalScreenDescriptor.GlobalColorTableFlag - && this.logicalScreenDescriptor.GlobalColorTableSize > 0) + if (ratio > 1) { - gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize; + meta.HorizontalResolution = ratio; + meta.VerticalResolution = 1; } - else if (this.imageDescriptor.LocalColorTableFlag - && this.imageDescriptor.LocalColorTableSize > 0) + else { - gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize; + meta.VerticalResolution = 1 / ratio; + meta.HorizontalResolution = 1; } - - gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; } - /// - /// Reads the logical screen descriptor and global color table blocks - /// - /// The stream containing image data. - private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream stream) - { - this.stream = stream; - - // Skip the identifier - this.stream.Skip(6); - this.ReadLogicalScreenDescriptor(); - - var meta = new ImageMetadata(); - - // The Pixel Aspect Ratio is defined to be the quotient of the pixel's - // width over its height. The value range in this field allows - // specification of the widest pixel of 4:1 to the tallest pixel of - // 1:4 in increments of 1/64th. - // - // Values : 0 - No aspect ratio information is given. - // 1..255 - Value used in the computation. - // - // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 - if (this.logicalScreenDescriptor.PixelAspectRatio > 0) - { - meta.ResolutionUnits = PixelResolutionUnit.AspectRatio; - float ratio = (this.logicalScreenDescriptor.PixelAspectRatio + 15) / 64F; - - if (ratio > 1) - { - meta.HorizontalResolution = ratio; - meta.VerticalResolution = 1; - } - else - { - meta.VerticalResolution = 1 / ratio; - meta.HorizontalResolution = 1; - } - } + this.metadata = meta; + this.gifMetadata = meta.GetGifMetadata(); + this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag + ? GifColorTableMode.Global + : GifColorTableMode.Local; - this.metadata = meta; - this.gifMetadata = meta.GetGifMetadata(); - this.gifMetadata.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag - ? GifColorTableMode.Global - : GifColorTableMode.Local; + if (this.logicalScreenDescriptor.GlobalColorTableFlag) + { + int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; + this.gifMetadata.GlobalColorTableLength = globalColorTableLength; - if (this.logicalScreenDescriptor.GlobalColorTableFlag) + if (globalColorTableLength > 0) { - int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - this.gifMetadata.GlobalColorTableLength = globalColorTableLength; - - if (globalColorTableLength > 0) - { - this.globalColorTable = this.memoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); + this.globalColorTable = this.memoryAllocator.Allocate(globalColorTableLength, AllocationOptions.Clean); - // Read the global color table data from the stream - stream.Read(this.globalColorTable.GetSpan()); - } + // Read the global color table data from the stream + stream.Read(this.globalColorTable.GetSpan()); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs index d5823e44ff..12b4239c4f 100644 --- a/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs +++ b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs @@ -1,38 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Provides enumeration for instructing the decoder what to do with the last image +/// in an animation sequence. +/// section 23 +/// +public enum GifDisposalMethod { /// - /// Provides enumeration for instructing the decoder what to do with the last image - /// in an animation sequence. - /// section 23 + /// No disposal specified. + /// The decoder is not required to take any action. /// - public enum GifDisposalMethod - { - /// - /// No disposal specified. - /// The decoder is not required to take any action. - /// - Unspecified = 0, + Unspecified = 0, - /// - /// Do not dispose. - /// The graphic is to be left in place. - /// - NotDispose = 1, + /// + /// Do not dispose. + /// The graphic is to be left in place. + /// + NotDispose = 1, - /// - /// Restore to background color. - /// The area used by the graphic must be restored to the background color. - /// - RestoreToBackground = 2, + /// + /// Restore to background color. + /// The area used by the graphic must be restored to the background color. + /// + RestoreToBackground = 2, - /// - /// Restore to previous. - /// The decoder is required to restore the area overwritten by the - /// graphic with what was there prior to rendering the graphic. - /// - RestoreToPrevious = 3 - } + /// + /// Restore to previous. + /// The decoder is required to restore the area overwritten by the + /// graphic with what was there prior to rendering the graphic. + /// + RestoreToPrevious = 3 } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 937e65db94..b6441db102 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -1,52 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Image encoder for writing image data to a stream in gif format. +/// +public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { /// - /// Image encoder for writing image data to a stream in gif format. + /// Gets or sets the quantizer for reducing the color count. + /// Defaults to the /// - public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions - { - /// - /// Gets or sets the quantizer for reducing the color count. - /// Defaults to the - /// - public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; + public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; - /// - /// Gets or sets the color table mode: Global or local. - /// - public GifColorTableMode? ColorTableMode { get; set; } + /// + /// Gets or sets the color table mode: Global or local. + /// + public GifColorTableMode? ColorTableMode { get; set; } - /// - /// Gets or sets the used for quantization - /// when building a global color table in case of . - /// - public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy(); + /// + /// Gets or sets the used for quantization + /// when building a global color table in case of . + /// + public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy(); - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new GifEncoderCore(image.GetConfiguration(), this); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new GifEncoderCore(image.GetConfiguration(), this); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new GifEncoderCore(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new GifEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index f354e42f32..cfd4ba36a6 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -1,12 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -14,512 +11,511 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Implements the GIF encoding protocol. +/// +internal sealed class GifEncoderCore : IImageEncoderInternals { /// - /// Implements the GIF encoding protocol. + /// Used for allocating memory during processing operations. /// - internal sealed class GifEncoderCore : IImageEncoderInternals - { - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Configuration bound to the encoding operation. - /// - private readonly Configuration configuration; - - /// - /// A reusable buffer used to reduce allocations. - /// - private readonly byte[] buffer = new byte[20]; - - /// - /// The quantizer used to generate the color palette. - /// - private readonly IQuantizer quantizer; - - /// - /// The color table mode: Global or local. - /// - private GifColorTableMode? colorTableMode; - - /// - /// The number of bits requires to store the color palette. - /// - private int bitDepth; - - /// - /// The pixel sampling strategy for global quantization. - /// - private readonly IPixelSamplingStrategy pixelSamplingStrategy; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The options for the encoder. - public GifEncoderCore(Configuration configuration, IGifEncoderOptions options) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.quantizer = options.Quantizer; - this.colorTableMode = options.ColorTableMode; - this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy; - } + private readonly MemoryAllocator memoryAllocator; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + /// + /// Configuration bound to the encoding operation. + /// + private readonly Configuration configuration; - ImageMetadata metadata = image.Metadata; - GifMetadata gifMetadata = metadata.GetGifMetadata(); - this.colorTableMode ??= gifMetadata.ColorTableMode; - bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; + /// + /// A reusable buffer used to reduce allocations. + /// + private readonly byte[] buffer = new byte[20]; - // Quantize the image returning a palette. - IndexedImageFrame quantized; + /// + /// The quantizer used to generate the color palette. + /// + private readonly IQuantizer quantizer; - using (IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration)) - { - if (useGlobalTable) - { - frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); - quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); - } - else - { - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); - } - } + /// + /// The color table mode: Global or local. + /// + private GifColorTableMode? colorTableMode; - // Get the number of bits. - this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); + /// + /// The number of bits requires to store the color palette. + /// + private int bitDepth; - // Write the header. - WriteHeader(stream); + /// + /// The pixel sampling strategy for global quantization. + /// + private readonly IPixelSamplingStrategy pixelSamplingStrategy; - // Write the LSD. - int index = GetTransparentIndex(quantized); - this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, index, useGlobalTable, stream); + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The options for the encoder. + public GifEncoderCore(Configuration configuration, IGifEncoderOptions options) + { + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.quantizer = options.Quantizer; + this.colorTableMode = options.ColorTableMode; + this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy; + } - if (useGlobalTable) - { - this.WriteColorTable(quantized, stream); - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - // Write the comments. - this.WriteComments(gifMetadata, stream); + ImageMetadata metadata = image.Metadata; + GifMetadata gifMetadata = metadata.GetGifMetadata(); + this.colorTableMode ??= gifMetadata.ColorTableMode; + bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; - // Write application extensions. - XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile); + // Quantize the image returning a palette. + IndexedImageFrame quantized; + using (IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration)) + { if (useGlobalTable) { - this.EncodeGlobal(image, quantized, index, stream); + frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } else { - this.EncodeLocal(image, quantized, stream); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); } + } - // Clean up. - quantized.Dispose(); + // Get the number of bits. + this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - stream.WriteByte(GifConstants.EndIntroducer); - } + // Write the header. + WriteHeader(stream); - private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) - where TPixel : unmanaged, IPixel + // Write the LSD. + int index = GetTransparentIndex(quantized); + this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, index, useGlobalTable, stream); + + if (useGlobalTable) { - // 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. - PaletteQuantizer paletteFrameQuantizer = default; - bool quantizerInitialized = false; - for (int i = 0; i < image.Frames.Count; i++) - { - ImageFrame frame = image.Frames[i]; - ImageFrameMetadata metadata = frame.Metadata; - GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); - this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); - this.WriteImageDescriptor(frame, false, stream); + this.WriteColorTable(quantized, stream); + } - if (i == 0) - { - this.WriteImageData(quantized, stream); - } - else - { - if (!quantizerInitialized) - { - quantizerInitialized = true; - paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); - } + // Write the comments. + this.WriteComments(gifMetadata, stream); - using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); - this.WriteImageData(paletteQuantized, stream); - } - } + // Write application extensions. + XmpProfile xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile); - paletteFrameQuantizer.Dispose(); + if (useGlobalTable) + { + this.EncodeGlobal(image, quantized, index, stream); } - - private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) - where TPixel : unmanaged, IPixel + else { - ImageFrame previousFrame = null; - GifFrameMetadata previousMeta = null; - 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) - { - // Allow each frame to be encoded at whatever color depth the frame designates if set. - if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength - && frameMetadata.ColorTableLength > 0) - { - QuantizerOptions options = new() - { - Dither = this.quantizer.Options.Dither, - DitherScale = this.quantizer.Options.DitherScale, - MaxColors = frameMetadata.ColorTableLength - }; - - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, options); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - } - else - { - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); - quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - } - } + this.EncodeLocal(image, quantized, stream); + } - this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); - this.WriteGraphicalControlExtension(frameMetadata, GetTransparentIndex(quantized), stream); - this.WriteImageDescriptor(frame, true, stream); - this.WriteColorTable(quantized, stream); - this.WriteImageData(quantized, stream); + // Clean up. + quantized.Dispose(); - quantized.Dispose(); - quantized = null; // So next frame can regenerate it - previousFrame = frame; - previousMeta = frameMetadata; - } - } + stream.WriteByte(GifConstants.EndIntroducer); + } - /// - /// Returns the index of the most transparent color in the palette. - /// - /// The quantized frame. - /// The pixel format. - /// - /// The . - /// - private static int GetTransparentIndex(IndexedImageFrame quantized) - where TPixel : unmanaged, 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. + PaletteQuantizer paletteFrameQuantizer = default; + bool quantizerInitialized = false; + for (int i = 0; i < image.Frames.Count; i++) { - // Transparent pixels are much more likely to be found at the end of a palette. - int index = -1; - ReadOnlySpan paletteSpan = quantized.Palette.Span; + ImageFrame frame = image.Frames[i]; + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); + this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream); + this.WriteImageDescriptor(frame, false, stream); - 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 = rgbaSpan.Length - 1; i >= 0; i--) + if (i == 0) + { + this.WriteImageData(quantized, stream); + } + else { - if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) + if (!quantizerInitialized) { - index = i; + quantizerInitialized = true; + paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, quantized.Palette); } - } - return index; + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.WriteImageData(paletteQuantized, stream); + } } - /// - /// Writes the file header signature and version to the stream. - /// - /// The stream to write to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); - - /// - /// Writes the logical screen descriptor to the stream. - /// - /// The image metadata. - /// The image width. - /// The image height. - /// The transparency index to set the default background index to. - /// Whether to use a global or local color table. - /// The stream to write to. - private void WriteLogicalScreenDescriptor( - ImageMetadata metadata, - int width, - int height, - int transparencyIndex, - bool useGlobalTable, - Stream stream) + paletteFrameQuantizer.Dispose(); + } + + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel + { + ImageFrame previousFrame = null; + GifFrameMetadata previousMeta = null; + for (int i = 0; i < image.Frames.Count; i++) { - byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1); - - // The Pixel Aspect Ratio is defined to be the quotient of the pixel's - // width over its height. The value range in this field allows - // specification of the widest pixel of 4:1 to the tallest pixel of - // 1:4 in increments of 1/64th. - // - // Values : 0 - No aspect ratio information is given. - // 1..255 - Value used in the computation. - // - // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 - byte ratio = 0; - - if (metadata.ResolutionUnits == PixelResolutionUnit.AspectRatio) + ImageFrame frame = image.Frames[i]; + ImageFrameMetadata metadata = frame.Metadata; + GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); + if (quantized is null) { - double hr = metadata.HorizontalResolution; - double vr = metadata.VerticalResolution; - if (hr != vr) + // Allow each frame to be encoded at whatever color depth the frame designates if set. + if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength + && frameMetadata.ColorTableLength > 0) { - if (hr > vr) + QuantizerOptions options = new() { - ratio = (byte)((hr * 64) - 15); - } - else - { - ratio = (byte)((1 / vr * 64) - 15); - } + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale, + MaxColors = frameMetadata.ColorTableLength + }; + + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, options); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + } + else + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); + quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } } - GifLogicalScreenDescriptor descriptor = new( - width: (ushort)width, - height: (ushort)height, - packed: packedValue, - backgroundColorIndex: unchecked((byte)transparencyIndex), - ratio); - - descriptor.WriteTo(this.buffer); + this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); + this.WriteGraphicalControlExtension(frameMetadata, GetTransparentIndex(quantized), stream); + this.WriteImageDescriptor(frame, true, stream); + this.WriteColorTable(quantized, stream); + this.WriteImageData(quantized, stream); - stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size); + quantized.Dispose(); + quantized = null; // So next frame can regenerate it + previousFrame = frame; + previousMeta = frameMetadata; } + } - /// - /// Writes the application extension to the stream. - /// - /// The stream to write to. - /// The frame count fo this image. - /// The animated image repeat count. - /// The XMP metadata profile. Null if profile is not to be written. - private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile xmpProfile) - { - // Application Extension: Loop repeat count. - if (frameCount > 1 && repeatCount != 1) - { - GifNetscapeLoopingApplicationExtension loopingExtension = new(repeatCount); - this.WriteExtension(loopingExtension, stream); - } + /// + /// Returns the index of the most transparent color in the palette. + /// + /// The quantized frame. + /// The pixel format. + /// + /// The . + /// + private static 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; + ReadOnlySpan paletteSpan = quantized.Palette.Span; - // Application Extension: XMP Profile. - if (xmpProfile != null) - { - GifXmpApplicationExtension xmpExtension = new(xmpProfile.Data); - this.WriteExtension(xmpExtension, stream); - } - } + 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); - /// - /// Writes the image comments to the stream. - /// - /// The metadata to be extract the comment data. - /// The stream to write to. - private void WriteComments(GifMetadata metadata, Stream stream) + for (int i = rgbaSpan.Length - 1; i >= 0; i--) { - if (metadata.Comments.Count == 0) + if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) { - return; + index = i; } + } + + return index; + } - for (int i = 0; i < metadata.Comments.Count; i++) + /// + /// Writes the file header signature and version to the stream. + /// + /// The stream to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); + + /// + /// Writes the logical screen descriptor to the stream. + /// + /// The image metadata. + /// The image width. + /// The image height. + /// The transparency index to set the default background index to. + /// Whether to use a global or local color table. + /// The stream to write to. + private void WriteLogicalScreenDescriptor( + ImageMetadata metadata, + int width, + int height, + int transparencyIndex, + bool useGlobalTable, + Stream stream) + { + byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1); + + // The Pixel Aspect Ratio is defined to be the quotient of the pixel's + // width over its height. The value range in this field allows + // specification of the widest pixel of 4:1 to the tallest pixel of + // 1:4 in increments of 1/64th. + // + // Values : 0 - No aspect ratio information is given. + // 1..255 - Value used in the computation. + // + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + byte ratio = 0; + + if (metadata.ResolutionUnits == PixelResolutionUnit.AspectRatio) + { + double hr = metadata.HorizontalResolution; + double vr = metadata.VerticalResolution; + if (hr != vr) { - string comment = metadata.Comments[i]; - this.buffer[0] = GifConstants.ExtensionIntroducer; - this.buffer[1] = GifConstants.CommentLabel; - stream.Write(this.buffer, 0, 2); - - // 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) + if (hr > vr) { - WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); + ratio = (byte)((hr * 64) - 15); } - - // Write the length bytes, if any, to another sub block. - if (idx < comment.Length) + else { - int remaining = comment.Length - idx; - WriteCommentSubBlock(stream, commentSpan, idx, remaining); + ratio = (byte)((1 / vr * 64) - 15); } - - stream.WriteByte(GifConstants.Terminator); } } - /// - /// Writes a comment sub-block to the stream. - /// - /// The stream to write to. - /// Comment as a Span. - /// Current start index. - /// The length of the string to write. Should not exceed 255 bytes. - private static void WriteCommentSubBlock(Stream stream, ReadOnlySpan commentSpan, int idx, int length) + GifLogicalScreenDescriptor descriptor = new( + width: (ushort)width, + height: (ushort)height, + packed: packedValue, + backgroundColorIndex: unchecked((byte)transparencyIndex), + ratio); + + descriptor.WriteTo(this.buffer); + + stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size); + } + + /// + /// Writes the application extension to the stream. + /// + /// The stream to write to. + /// The frame count fo this image. + /// The animated image repeat count. + /// The XMP metadata profile. Null if profile is not to be written. + private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile xmpProfile) + { + // Application Extension: Loop repeat count. + if (frameCount > 1 && repeatCount != 1) { - string subComment = commentSpan.Slice(idx, length).ToString(); - byte[] subCommentBytes = GifConstants.Encoding.GetBytes(subComment); - stream.WriteByte((byte)length); - stream.Write(subCommentBytes, 0, length); + GifNetscapeLoopingApplicationExtension loopingExtension = new(repeatCount); + this.WriteExtension(loopingExtension, stream); } - /// - /// Writes the graphics control extension to the stream. - /// - /// The metadata of the image or frame. - /// The index of the color in the color palette to make transparent. - /// The stream to write to. - private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int transparencyIndex, Stream stream) + // Application Extension: XMP Profile. + if (xmpProfile != null) { - byte packedValue = GifGraphicControlExtension.GetPackedValue( - disposalMethod: metadata.DisposalMethod, - transparencyFlag: transparencyIndex > -1); - - GifGraphicControlExtension extension = new( - packed: packedValue, - delayTime: (ushort)metadata.FrameDelay, - transparencyIndex: unchecked((byte)transparencyIndex)); - - this.WriteExtension(extension, stream); + GifXmpApplicationExtension xmpExtension = new(xmpProfile.Data); + this.WriteExtension(xmpExtension, stream); } + } - /// - /// Writes the provided extension to the stream. - /// - /// The type of gif extension. - /// The extension to write to the stream. - /// The stream to write to. - private void WriteExtension(TGifExtension extension, Stream stream) - where TGifExtension : struct, IGifExtension + /// + /// Writes the image comments to the stream. + /// + /// The metadata to be extract the comment data. + /// The stream to write to. + private void WriteComments(GifMetadata metadata, Stream stream) + { + if (metadata.Comments.Count == 0) { - IMemoryOwner owner = null; - Span extensionBuffer; - int extensionSize = extension.ContentLength; + return; + } - if (extensionSize == 0) - { - return; - } - else if (extensionSize > this.buffer.Length - 3) + for (int 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); + + // 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) { - owner = this.memoryAllocator.Allocate(extensionSize + 3); - extensionBuffer = owner.GetSpan(); + WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); } - else + + // Write the length bytes, if any, to another sub block. + if (idx < comment.Length) { - extensionBuffer = this.buffer; + int remaining = comment.Length - idx; + WriteCommentSubBlock(stream, commentSpan, idx, remaining); } - extensionBuffer[0] = GifConstants.ExtensionIntroducer; - extensionBuffer[1] = extension.Label; + stream.WriteByte(GifConstants.Terminator); + } + } + + /// + /// Writes a comment sub-block to the stream. + /// + /// The stream to write to. + /// Comment as a Span. + /// Current start index. + /// The length of the string to write. Should not exceed 255 bytes. + private static void WriteCommentSubBlock(Stream stream, ReadOnlySpan commentSpan, int idx, int length) + { + string subComment = commentSpan.Slice(idx, length).ToString(); + byte[] subCommentBytes = GifConstants.Encoding.GetBytes(subComment); + stream.WriteByte((byte)length); + stream.Write(subCommentBytes, 0, length); + } - extension.WriteTo(extensionBuffer[2..]); + /// + /// Writes the graphics control extension to the stream. + /// + /// The metadata of the image or frame. + /// The index of the color in the color palette to make transparent. + /// The stream to write to. + private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int transparencyIndex, Stream stream) + { + byte packedValue = GifGraphicControlExtension.GetPackedValue( + disposalMethod: metadata.DisposalMethod, + transparencyFlag: transparencyIndex > -1); - extensionBuffer[extensionSize + 2] = GifConstants.Terminator; + GifGraphicControlExtension extension = new( + packed: packedValue, + delayTime: (ushort)metadata.FrameDelay, + transparencyIndex: unchecked((byte)transparencyIndex)); - stream.Write(extensionBuffer, 0, extensionSize + 3); - owner?.Dispose(); - } + this.WriteExtension(extension, stream); + } + + /// + /// Writes the provided extension to the stream. + /// + /// The type of gif extension. + /// The extension to write to the stream. + /// The stream to write to. + private void WriteExtension(TGifExtension extension, Stream stream) + where TGifExtension : struct, IGifExtension + { + IMemoryOwner owner = null; + Span extensionBuffer; + int extensionSize = extension.ContentLength; - /// - /// Writes the image descriptor to the stream. - /// - /// The pixel format. - /// The to be encoded. - /// Whether to use the global color table. - /// The stream to write to. - private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream) - where TPixel : unmanaged, IPixel + if (extensionSize == 0) { - byte packedValue = GifImageDescriptor.GetPackedValue( - localColorTableFlag: hasColorTable, - interfaceFlag: false, - sortFlag: false, - localColorTableSize: this.bitDepth - 1); - - GifImageDescriptor descriptor = new( - left: 0, - top: 0, - width: (ushort)image.Width, - height: (ushort)image.Height, - packed: packedValue); - - descriptor.WriteTo(this.buffer); - - stream.Write(this.buffer, 0, GifImageDescriptor.Size); + return; } - - /// - /// Writes the color table to the stream. - /// - /// The pixel format. - /// The to encode. - /// The stream to write to. - private void WriteColorTable(IndexedImageFrame image, Stream stream) - where TPixel : unmanaged, IPixel + else if (extensionSize > this.buffer.Length - 3) + { + owner = this.memoryAllocator.Allocate(extensionSize + 3); + extensionBuffer = owner.GetSpan(); + } + else { - // The maximum number of colors for the bit depth - int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); + extensionBuffer = this.buffer; + } - using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength, AllocationOptions.Clean); - Span colorTableSpan = colorTable.GetSpan(); + extensionBuffer[0] = GifConstants.ExtensionIntroducer; + extensionBuffer[1] = extension.Label; - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.Span, - colorTableSpan, - image.Palette.Length); + extension.WriteTo(extensionBuffer[2..]); - stream.Write(colorTableSpan); - } + extensionBuffer[extensionSize + 2] = GifConstants.Terminator; - /// - /// Writes the image pixel data to the stream. - /// - /// The pixel format. - /// The containing indexed pixels. - /// The stream to write to. - private void WriteImageData(IndexedImageFrame image, Stream stream) - where TPixel : unmanaged, IPixel - { - using LzwEncoder encoder = new(this.memoryAllocator, (byte)this.bitDepth); - encoder.Encode(((IPixelSource)image).PixelBuffer, stream); - } + stream.Write(extensionBuffer, 0, extensionSize + 3); + owner?.Dispose(); + } + + /// + /// Writes the image descriptor to the stream. + /// + /// The pixel format. + /// The to be encoded. + /// Whether to use the global color table. + /// The stream to write to. + private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream) + where TPixel : unmanaged, IPixel + { + byte packedValue = GifImageDescriptor.GetPackedValue( + localColorTableFlag: hasColorTable, + interfaceFlag: false, + sortFlag: false, + localColorTableSize: this.bitDepth - 1); + + GifImageDescriptor descriptor = new( + left: 0, + top: 0, + width: (ushort)image.Width, + height: (ushort)image.Height, + packed: packedValue); + + descriptor.WriteTo(this.buffer); + + stream.Write(this.buffer, 0, GifImageDescriptor.Size); + } + + /// + /// Writes the color table to the stream. + /// + /// The pixel format. + /// The to encode. + /// The stream to write to. + private void WriteColorTable(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel + { + // The maximum number of colors for the bit depth + int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); + + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength, AllocationOptions.Clean); + Span colorTableSpan = colorTable.GetSpan(); + + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette.Span, + colorTableSpan, + image.Palette.Length); + + stream.Write(colorTableSpan); + } + + /// + /// Writes the image pixel data to the stream. + /// + /// The pixel format. + /// The containing indexed pixels. + /// The stream to write to. + private void WriteImageData(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel + { + using LzwEncoder encoder = new(this.memoryAllocator, (byte)this.bitDepth); + encoder.Encode(((IPixelSource)image).PixelBuffer, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index aa05d93cc4..df302c7eac 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -1,40 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Formats.Gif +/// +/// Registers the image encoders, decoders and mime type detectors for the gif format. +/// +public sealed class GifFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the gif format. - /// - public sealed class GifFormat : IImageFormat + private GifFormat() { - private GifFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static GifFormat Instance { get; } = new(); + /// + /// Gets the current instance. + /// + public static GifFormat Instance { get; } = new(); - /// - public string Name => "GIF"; + /// + public string Name => "GIF"; - /// - public string DefaultMimeType => "image/gif"; + /// + public string DefaultMimeType => "image/gif"; - /// - public IEnumerable MimeTypes => GifConstants.MimeTypes; + /// + public IEnumerable MimeTypes => GifConstants.MimeTypes; - /// - public IEnumerable FileExtensions => GifConstants.FileExtensions; + /// + public IEnumerable FileExtensions => GifConstants.FileExtensions; - /// - public GifMetadata CreateDefaultFormatMetadata() => new(); + /// + public GifMetadata CreateDefaultFormatMetadata() => new(); - /// - public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new(); - } + /// + public GifFrameMetadata CreateDefaultFormatFrameMetadata() => new(); } diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs index 2ebf73d7e6..82694eab9a 100644 --- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs @@ -1,54 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Provides Gif specific metadata information for the image frame. +/// +public class GifFrameMetadata : IDeepCloneable { /// - /// Provides Gif specific metadata information for the image frame. + /// Initializes a new instance of the class. /// - public class GifFrameMetadata : IDeepCloneable + public GifFrameMetadata() { - /// - /// Initializes a new instance of the class. - /// - public GifFrameMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private GifFrameMetadata(GifFrameMetadata other) - { - this.ColorTableLength = other.ColorTableLength; - this.FrameDelay = other.FrameDelay; - this.DisposalMethod = other.DisposalMethod; - } + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifFrameMetadata(GifFrameMetadata other) + { + this.ColorTableLength = other.ColorTableLength; + this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; + } - /// - /// Gets or sets the length of the color table for paletted images. - /// If not 0, then this field indicates the maximum number of colors to use when quantizing the - /// image frame. - /// - public int ColorTableLength { get; set; } + /// + /// Gets or sets the length of the color table for paletted images. + /// If not 0, then this field indicates the maximum number of colors to use when quantizing the + /// image frame. + /// + public int ColorTableLength { get; set; } - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public int FrameDelay { get; set; } + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } - /// - /// Gets or sets the disposal method for animated images. - /// Primarily used in Gif animation, this field indicates the way in which the graphic is to - /// be treated after being displayed. - /// - public GifDisposalMethod DisposalMethod { get; set; } + /// + /// Gets or sets the disposal method for animated images. + /// Primarily used in Gif animation, this field indicates the way in which the graphic is to + /// be treated after being displayed. + /// + public GifDisposalMethod DisposalMethod { get; set; } - /// - public IDeepCloneable DeepClone() => new GifFrameMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new GifFrameMetadata(this); } diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index 4f006b799f..3f657610c9 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -1,33 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Formats.Gif +/// +/// Detects gif file headers +/// +public sealed class GifImageFormatDetector : IImageFormatDetector { - /// - /// Detects gif file headers - /// - public sealed class GifImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 6; + /// + public int HeaderSize => 6; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) - { - return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; - } + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; + } - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - return header.Length >= this.HeaderSize && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - header[5] == 0x61; // a - } + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a } } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index de09347bef..52019c3354 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Formats.Gif +/// +/// Provides Gif specific metadata information for the image. +/// +public class GifMetadata : IDeepCloneable { /// - /// Provides Gif specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class GifMetadata : IDeepCloneable + public GifMetadata() { - /// - /// Initializes a new instance of the class. - /// - public GifMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private GifMetadata(GifMetadata other) + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private GifMetadata(GifMetadata other) + { + this.RepeatCount = other.RepeatCount; + this.ColorTableMode = other.ColorTableMode; + this.GlobalColorTableLength = other.GlobalColorTableLength; + + for (int i = 0; i < other.Comments.Count; i++) { - this.RepeatCount = other.RepeatCount; - this.ColorTableMode = other.ColorTableMode; - this.GlobalColorTableLength = other.GlobalColorTableLength; - - for (int i = 0; i < other.Comments.Count; i++) - { - this.Comments.Add(other.Comments[i]); - } + this.Comments.Add(other.Comments[i]); } - - /// - /// Gets or sets the number of times any animation is repeated. - /// - /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. - /// - /// - public ushort RepeatCount { get; set; } = 1; - - /// - /// Gets or sets the color table mode. - /// - public GifColorTableMode ColorTableMode { get; set; } - - /// - /// Gets or sets the length of the global color table if present. - /// - public int GlobalColorTableLength { get; set; } - - /// - /// Gets or sets the the collection of comments about the graphics, credits, descriptions or any - /// other type of non-control and non-graphic data. - /// - public IList Comments { get; set; } = new List(); - - /// - public IDeepCloneable DeepClone() => new GifMetadata(this); } + + /// + /// Gets or sets the number of times any animation is repeated. + /// + /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. + /// + /// + public ushort RepeatCount { get; set; } = 1; + + /// + /// Gets or sets the color table mode. + /// + public GifColorTableMode ColorTableMode { get; set; } + + /// + /// Gets or sets the length of the global color table if present. + /// + public int GlobalColorTableLength { get; set; } + + /// + /// Gets or sets the the collection of comments about the graphics, credits, descriptions or any + /// other type of non-control and non-graphic data. + /// + public IList Comments { get; set; } = new List(); + + /// + public IDeepCloneable DeepClone() => new GifMetadata(this); } diff --git a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs index 88fb264923..5ba1da6b5c 100644 --- a/src/ImageSharp/Formats/Gif/GifThrowHelper.cs +++ b/src/ImageSharp/Formats/Gif/GifThrowHelper.cs @@ -1,28 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +internal static class GifThrowHelper { - internal static class GifThrowHelper - { - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); - /// - /// Cold path optimization for throwing 's. - /// - /// The error message for the exception. - /// The exception that is the cause of the current exception, or a null reference - /// if no inner exception is specified. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); - } + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); } diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs index dfc1e69c79..1ed68a9177 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -1,31 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// The configuration options used for encoding gifs. +/// +internal interface IGifEncoderOptions { /// - /// The configuration options used for encoding gifs. + /// Gets the quantizer used to generate the color palette. /// - internal interface IGifEncoderOptions - { - /// - /// Gets the quantizer used to generate the color palette. - /// - IQuantizer Quantizer { get; } + IQuantizer Quantizer { get; } - /// - /// Gets the color table mode: Global or local. - /// - GifColorTableMode? ColorTableMode { get; } + /// + /// Gets the color table mode: Global or local. + /// + GifColorTableMode? ColorTableMode { get; } - /// - /// Gets the used for quantization when building a global color table. - /// - IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; } - } + /// + /// Gets the used for quantization when building a global color table. + /// + IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; } } diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 893fde55e6..1d63611fad 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -1,267 +1,265 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Decompresses and decodes data using the dynamic LZW algorithms. +/// +internal sealed class LzwDecoder : IDisposable { /// - /// Decompresses and decodes data using the dynamic LZW algorithms. + /// The max decoder pixel stack size. + /// + private const int MaxStackSize = 4096; + + /// + /// The null code. /// - internal sealed class LzwDecoder : IDisposable + private const int NullCode = -1; + + /// + /// The stream to decode. + /// + private readonly BufferedReadStream stream; + + /// + /// The prefix buffer. + /// + private readonly IMemoryOwner prefix; + + /// + /// The suffix buffer. + /// + private readonly IMemoryOwner suffix; + + /// + /// The pixel stack buffer. + /// + private readonly IMemoryOwner pixelStack; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The to use for buffer allocations. + /// The stream to read from. + /// is null. + public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream) { - /// - /// The max decoder pixel stack size. - /// - private const int MaxStackSize = 4096; - - /// - /// The null code. - /// - private const int NullCode = -1; - - /// - /// The stream to decode. - /// - private readonly BufferedReadStream stream; - - /// - /// The prefix buffer. - /// - private readonly IMemoryOwner prefix; - - /// - /// The suffix buffer. - /// - private readonly IMemoryOwner suffix; - - /// - /// The pixel stack buffer. - /// - private readonly IMemoryOwner pixelStack; - - /// - /// Initializes a new instance of the class - /// and sets the stream, where the compressed data should be read from. - /// - /// The to use for buffer allocations. - /// The stream to read from. - /// is null. - public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream) - { - this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); + this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); - this.prefix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); - this.suffix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); - this.pixelStack = memoryAllocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); - } + this.prefix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.suffix = memoryAllocator.Allocate(MaxStackSize, AllocationOptions.Clean); + this.pixelStack = memoryAllocator.Allocate(MaxStackSize + 1, AllocationOptions.Clean); + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// Minimum code size of the data. + /// The pixel array to decode to. + public void DecodePixels(int minCodeSize, Buffer2D pixels) + { + // Calculate the clear code. The value of the clear code is 2 ^ minCodeSize + int clearCode = 1 << minCodeSize; - /// - /// Decodes and decompresses all pixel indices from the stream. - /// - /// Minimum code size of the data. - /// The pixel array to decode to. - public void DecodePixels(int minCodeSize, Buffer2D pixels) + // It is possible to specify a larger LZW minimum code size than the palette length in bits + // which may leave a gap in the codes where no colors are assigned. + // http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression + if (minCodeSize < 2 || clearCode > MaxStackSize) { - // Calculate the clear code. The value of the clear code is 2 ^ minCodeSize - int clearCode = 1 << minCodeSize; + // Don't attempt to decode the frame indices. + // Theoretically we could determine a min code size from the length of the provided + // color palette but we won't bother since the image is most likely corrupted. + GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code."); + } - // It is possible to specify a larger LZW minimum code size than the palette length in bits - // which may leave a gap in the codes where no colors are assigned. - // http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression - if (minCodeSize < 2 || clearCode > MaxStackSize) - { - // Don't attempt to decode the frame indices. - // Theoretically we could determine a min code size from the length of the provided - // color palette but we won't bother since the image is most likely corrupted. - GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code."); - } + // The resulting index table length. + int width = pixels.Width; + int height = pixels.Height; + int length = width * height; + + int codeSize = minCodeSize + 1; - // The resulting index table length. - int width = pixels.Width; - int height = pixels.Height; - int length = width * height; + // Calculate the end code + int endCode = clearCode + 1; - int codeSize = minCodeSize + 1; + // Calculate the available code. + int availableCode = clearCode + 2; - // Calculate the end code - int endCode = clearCode + 1; + // Jillzhangs Code see: http://giflib.codeplex.com/ + // Adapted from John Cristy's ImageMagick. + int code; + int oldCode = NullCode; + int codeMask = (1 << codeSize) - 1; + int bits = 0; - // Calculate the available code. - int availableCode = clearCode + 2; + int top = 0; + int count = 0; + int bi = 0; + int xyz = 0; - // Jillzhangs Code see: http://giflib.codeplex.com/ - // Adapted from John Cristy's ImageMagick. - int code; - int oldCode = NullCode; - int codeMask = (1 << codeSize) - 1; - int bits = 0; + int data = 0; + int first = 0; - int top = 0; - int count = 0; - int bi = 0; - int xyz = 0; + ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); + ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); + ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); - int data = 0; - int first = 0; + for (code = 0; code < clearCode; code++) + { + Unsafe.Add(ref suffixRef, code) = (byte)code; + } - ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); - ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); - ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); + Span buffer = stackalloc byte[byte.MaxValue]; - for (code = 0; code < clearCode; code++) + int y = 0; + int x = 0; + int rowMax = width; + ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y)); + while (xyz < length) + { + // Reset row reference. + if (xyz == rowMax) { - Unsafe.Add(ref suffixRef, code) = (byte)code; + x = 0; + pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y)); + rowMax = (y * width) + width; } - Span buffer = stackalloc byte[byte.MaxValue]; - - int y = 0; - int x = 0; - int rowMax = width; - ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y)); - while (xyz < length) + if (top == 0) { - // Reset row reference. - if (xyz == rowMax) - { - x = 0; - pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y)); - rowMax = (y * width) + width; - } - - if (top == 0) + if (bits < codeSize) { - if (bits < codeSize) + // Load bytes until there are enough bits for a code. + if (count == 0) { - // Load bytes until there are enough bits for a code. + // Read a new data block. + count = this.ReadBlock(buffer); if (count == 0) { - // Read a new data block. - count = this.ReadBlock(buffer); - if (count == 0) - { - break; - } - - bi = 0; + break; } - data += buffer[bi] << bits; - - bits += 8; - bi++; - count--; - continue; - } - - // Get the next code - code = data & codeMask; - data >>= codeSize; - bits -= codeSize; - - // Interpret the code - if (code > availableCode || code == endCode) - { - break; + bi = 0; } - if (code == clearCode) - { - // Reset the decoder - codeSize = minCodeSize + 1; - codeMask = (1 << codeSize) - 1; - availableCode = clearCode + 2; - oldCode = NullCode; - continue; - } + data += buffer[bi] << bits; - if (oldCode == NullCode) - { - Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); - oldCode = code; - first = code; - continue; - } + bits += 8; + bi++; + count--; + continue; + } - int inCode = code; - if (code == availableCode) - { - Unsafe.Add(ref pixelStackRef, top++) = (byte)first; + // Get the next code + code = data & codeMask; + data >>= codeSize; + bits -= codeSize; - code = oldCode; - } + // Interpret the code + if (code > availableCode || code == endCode) + { + break; + } - while (code > clearCode) - { - Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); - code = Unsafe.Add(ref prefixRef, code); - } + if (code == clearCode) + { + // Reset the decoder + codeSize = minCodeSize + 1; + codeMask = (1 << codeSize) - 1; + availableCode = clearCode + 2; + oldCode = NullCode; + continue; + } - int suffixCode = Unsafe.Add(ref suffixRef, code); - first = suffixCode; - Unsafe.Add(ref pixelStackRef, top++) = suffixCode; + if (oldCode == NullCode) + { + Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); + oldCode = code; + first = code; + continue; + } - // Fix for Gifs that have "deferred clear code" as per here : - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - if (availableCode < MaxStackSize) - { - Unsafe.Add(ref prefixRef, availableCode) = oldCode; - Unsafe.Add(ref suffixRef, availableCode) = first; - availableCode++; - if (availableCode == codeMask + 1 && availableCode < MaxStackSize) - { - codeSize++; - codeMask = (1 << codeSize) - 1; - } - } + int inCode = code; + if (code == availableCode) + { + Unsafe.Add(ref pixelStackRef, top++) = (byte)first; - oldCode = inCode; + code = oldCode; } - // Pop a pixel off the pixel stack. - top--; + while (code > clearCode) + { + Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code); + code = Unsafe.Add(ref prefixRef, code); + } - // Clear missing pixels - xyz++; - Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top); - } - } + int suffixCode = Unsafe.Add(ref suffixRef, code); + first = suffixCode; + Unsafe.Add(ref pixelStackRef, top++) = suffixCode; - /// - /// Reads the next data block from the stream. A data block begins with a byte, - /// which defines the size of the block, followed by the block itself. - /// - /// The buffer to store the block in. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadBlock(Span buffer) - { - int bufferSize = this.stream.ReadByte(); + // Fix for Gifs that have "deferred clear code" as per here : + // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 + if (availableCode < MaxStackSize) + { + Unsafe.Add(ref prefixRef, availableCode) = oldCode; + Unsafe.Add(ref suffixRef, availableCode) = first; + availableCode++; + if (availableCode == codeMask + 1 && availableCode < MaxStackSize) + { + codeSize++; + codeMask = (1 << codeSize) - 1; + } + } - if (bufferSize < 1) - { - return 0; + oldCode = inCode; } - int count = this.stream.Read(buffer, 0, bufferSize); + // Pop a pixel off the pixel stack. + top--; - return count != bufferSize ? 0 : bufferSize; + // Clear missing pixels + xyz++; + Unsafe.Add(ref pixelsRowRef, x++) = (byte)Unsafe.Add(ref pixelStackRef, top); } + } + + /// + /// Reads the next data block from the stream. A data block begins with a byte, + /// which defines the size of the block, followed by the block itself. + /// + /// The buffer to store the block in. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadBlock(Span buffer) + { + int bufferSize = this.stream.ReadByte(); - /// - public void Dispose() + if (bufferSize < 1) { - this.prefix.Dispose(); - this.suffix.Dispose(); - this.pixelStack.Dispose(); + return 0; } + + int count = this.stream.Read(buffer, 0, bufferSize); + + return count != bufferSize ? 0 : bufferSize; + } + + /// + public void Dispose() + { + this.prefix.Dispose(); + this.suffix.Dispose(); + this.pixelStack.Dispose(); } } diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index 1146bcf911..fe27e7bf93 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -1,424 +1,421 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Encodes and compresses the image data using dynamic Lempel-Ziv compression. +/// +/// +/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 +/// +/// GIFCOMPR.C - GIF Image compression routines +/// +/// +/// Lempel-Ziv compression based on 'compress'. GIF modifications by +/// David Rowley (mgardi@watdcsu.waterloo.edu) +/// +/// GIF Image compression - modified 'compress' +/// +/// Based on: compress.c - File compression ala IEEE Computer, June 1984. +/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) +/// Jim McKie (decvax!mcvax!jim) +/// Steve Davies (decvax!vax135!petsd!peora!srd) +/// Ken Turkowski (decvax!decwrl!turtlevax!ken) +/// James A. Woods (decvax!ihnp4!ames!jaw) +/// Joe Orost (decvax!vax135!petsd!joe) +/// +/// +internal sealed class LzwEncoder : IDisposable { /// - /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. + /// 80% occupancy /// - /// - /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 - /// - /// GIFCOMPR.C - GIF Image compression routines - /// - /// - /// Lempel-Ziv compression based on 'compress'. GIF modifications by - /// David Rowley (mgardi@watdcsu.waterloo.edu) - /// - /// GIF Image compression - modified 'compress' - /// - /// Based on: compress.c - File compression ala IEEE Computer, June 1984. - /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) - /// Jim McKie (decvax!mcvax!jim) - /// Steve Davies (decvax!vax135!petsd!peora!srd) - /// Ken Turkowski (decvax!decwrl!turtlevax!ken) - /// James A. Woods (decvax!ihnp4!ames!jaw) - /// Joe Orost (decvax!vax135!petsd!joe) - /// - /// - internal sealed class LzwEncoder : IDisposable + private const int HashSize = 5003; + + /// + /// The amount to shift each code. + /// + private const int HashShift = 4; + + /// + /// Mask used when shifting pixel values + /// + private static readonly int[] Masks = { - /// - /// 80% occupancy - /// - private const int HashSize = 5003; - - /// - /// The amount to shift each code. - /// - private const int HashShift = 4; - - /// - /// Mask used when shifting pixel values - /// - private static readonly int[] Masks = - { - 0b0, - 0b1, - 0b11, - 0b111, - 0b1111, - 0b11111, - 0b111111, - 0b1111111, - 0b11111111, - 0b111111111, - 0b1111111111, - 0b11111111111, - 0b111111111111, - 0b1111111111111, - 0b11111111111111, - 0b111111111111111, - 0b1111111111111111 - }; - - /// - /// The maximum number of bits/code. - /// - private const int MaxBits = 12; - - /// - /// Should NEVER generate this code. - /// - private const int MaxMaxCode = 1 << MaxBits; - - /// - /// The initial code size. - /// - private readonly int initialCodeSize; - - /// - /// The hash table. - /// - private readonly IMemoryOwner hashTable; - - /// - /// The code table. - /// - private readonly IMemoryOwner codeTable; - - /// - /// Define the storage for the packet accumulator. - /// - private readonly byte[] accumulators = new byte[256]; - - /// - /// Number of bits/code - /// - private int bitCount; - - /// - /// maximum code, given bitCount - /// - private int maxCode; - - /// - /// First unused entry - /// - private int freeEntry; - - /// - /// Block compression parameters -- after all codes are used up, - /// and compression rate changes, start over. - /// - private bool clearFlag; - - /// - /// Algorithm: use open addressing double hashing (no chaining) on the - /// prefix code / next character combination. We do a variant of Knuth's - /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime - /// secondary probe. Here, the modular division first probe is gives way - /// to a faster exclusive-or manipulation. Also do block compression with - /// an adaptive reset, whereby the code table is cleared when the compression - /// ratio decreases, but after the table fills. The variable-length output - /// codes are re-sized at this point, and a special CLEAR code is generated - /// for the decompressor. Late addition: construct the table according to - /// file size for noticeable speed improvement on small files. Please direct - /// questions about this implementation to ames!jaw. - /// - private int globalInitialBits; - - /// - /// The clear code. - /// - private int clearCode; - - /// - /// The end-of-file code. - /// - private int eofCode; - - /// - /// Output the given code. - /// Inputs: - /// code: A bitCount-bit integer. If == -1, then EOF. This assumes - /// that bitCount =< wordsize - 1. - /// Outputs: - /// Outputs code to the file. - /// Assumptions: - /// Chars are 8 bits long. - /// Algorithm: - /// Maintain a BITS character long buffer (so that 8 codes will - /// fit in it exactly). Use the VAX insv instruction to insert each - /// code in turn. When the buffer fills up empty it and start over. - /// - private int currentAccumulator; - - /// - /// The current bits. - /// - private int currentBits; - - /// - /// Number of characters so far in this 'packet' - /// - private int accumulatorCount; - - /// - /// Initializes a new instance of the class. - /// - /// The to use for buffer allocations. - /// The color depth in bits. - public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) - { - this.initialCodeSize = Math.Max(2, colorDepth); - this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); - this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); - } + 0b0, + 0b1, + 0b11, + 0b111, + 0b1111, + 0b11111, + 0b111111, + 0b1111111, + 0b11111111, + 0b111111111, + 0b1111111111, + 0b11111111111, + 0b111111111111, + 0b1111111111111, + 0b11111111111111, + 0b111111111111111, + 0b1111111111111111 + }; - /// - /// Encodes and compresses the indexed pixels to the stream. - /// - /// The 2D buffer of indexed pixels. - /// The stream to write to. - public void Encode(Buffer2D indexedPixels, Stream stream) - { - // Write "initial code size" byte - stream.WriteByte((byte)this.initialCodeSize); + /// + /// The maximum number of bits/code. + /// + private const int MaxBits = 12; - // Compress and write the pixel data - this.Compress(indexedPixels, this.initialCodeSize + 1, stream); + /// + /// Should NEVER generate this code. + /// + private const int MaxMaxCode = 1 << MaxBits; - // Write block terminator - stream.WriteByte(GifConstants.Terminator); - } + /// + /// The initial code size. + /// + private readonly int initialCodeSize; - /// - /// Gets the maximum code value. - /// - /// The number of bits - /// See - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; - - /// - /// Add a character to the end of the current packet, and if it is 254 characters, - /// flush the packet to disk. - /// - /// The character to add. - /// The reference to the storage for packet accumulators - /// The stream to write to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AddCharacter(byte c, ref byte accumulatorsRef, Stream stream) + /// + /// The hash table. + /// + private readonly IMemoryOwner hashTable; + + /// + /// The code table. + /// + private readonly IMemoryOwner codeTable; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; + + /// + /// Number of bits/code + /// + private int bitCount; + + /// + /// maximum code, given bitCount + /// + private int maxCode; + + /// + /// First unused entry + /// + private int freeEntry; + + /// + /// Block compression parameters -- after all codes are used up, + /// and compression rate changes, start over. + /// + private bool clearFlag; + + /// + /// Algorithm: use open addressing double hashing (no chaining) on the + /// prefix code / next character combination. We do a variant of Knuth's + /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + /// secondary probe. Here, the modular division first probe is gives way + /// to a faster exclusive-or manipulation. Also do block compression with + /// an adaptive reset, whereby the code table is cleared when the compression + /// ratio decreases, but after the table fills. The variable-length output + /// codes are re-sized at this point, and a special CLEAR code is generated + /// for the decompressor. Late addition: construct the table according to + /// file size for noticeable speed improvement on small files. Please direct + /// questions about this implementation to ames!jaw. + /// + private int globalInitialBits; + + /// + /// The clear code. + /// + private int clearCode; + + /// + /// The end-of-file code. + /// + private int eofCode; + + /// + /// Output the given code. + /// Inputs: + /// code: A bitCount-bit integer. If == -1, then EOF. This assumes + /// that bitCount =< wordsize - 1. + /// Outputs: + /// Outputs code to the file. + /// Assumptions: + /// Chars are 8 bits long. + /// Algorithm: + /// Maintain a BITS character long buffer (so that 8 codes will + /// fit in it exactly). Use the VAX insv instruction to insert each + /// code in turn. When the buffer fills up empty it and start over. + /// + private int currentAccumulator; + + /// + /// The current bits. + /// + private int currentBits; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The color depth in bits. + public LzwEncoder(MemoryAllocator memoryAllocator, int colorDepth) + { + this.initialCodeSize = Math.Max(2, colorDepth); + this.hashTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + this.codeTable = memoryAllocator.Allocate(HashSize, AllocationOptions.Clean); + } + + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The 2D buffer of indexed pixels. + /// The stream to write to. + public void Encode(Buffer2D indexedPixels, Stream stream) + { + // Write "initial code size" byte + stream.WriteByte((byte)this.initialCodeSize); + + // Compress and write the pixel data + this.Compress(indexedPixels, this.initialCodeSize + 1, stream); + + // Write block terminator + stream.WriteByte(GifConstants.Terminator); + } + + /// + /// Gets the maximum code value. + /// + /// The number of bits + /// See + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetMaxcode(int bitCount) => (1 << bitCount) - 1; + + /// + /// Add a character to the end of the current packet, and if it is 254 characters, + /// flush the packet to disk. + /// + /// The character to add. + /// The reference to the storage for packet accumulators + /// The stream to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddCharacter(byte c, ref byte accumulatorsRef, Stream stream) + { + Unsafe.Add(ref accumulatorsRef, this.accumulatorCount++) = c; + if (this.accumulatorCount >= 254) { - Unsafe.Add(ref accumulatorsRef, this.accumulatorCount++) = c; - if (this.accumulatorCount >= 254) - { - this.FlushPacket(stream); - } + this.FlushPacket(stream); } + } - /// - /// Table clear for block compress. - /// - /// The output stream. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ClearBlock(Stream stream) - { - this.ResetCodeTable(); - this.freeEntry = this.clearCode + 2; - this.clearFlag = true; + /// + /// Table clear for block compress. + /// + /// The output stream. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearBlock(Stream stream) + { + this.ResetCodeTable(); + this.freeEntry = this.clearCode + 2; + this.clearFlag = true; - this.Output(this.clearCode, stream); - } + this.Output(this.clearCode, stream); + } - /// - /// Reset the code table. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); - - /// - /// Compress the packets to the stream. - /// - /// The 2D buffer of indexed pixels. - /// The initial bits. - /// The stream to write to. - private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) - { - // Set up the globals: globalInitialBits - initial number of bits - this.globalInitialBits = initialBits; + /// + /// Reset the code table. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetCodeTable() => this.hashTable.GetSpan().Fill(-1); - // Set up the necessary values - this.clearFlag = false; - this.bitCount = this.globalInitialBits; - this.maxCode = GetMaxcode(this.bitCount); - this.clearCode = 1 << (initialBits - 1); - this.eofCode = this.clearCode + 1; - this.freeEntry = this.clearCode + 2; - this.accumulatorCount = 0; // Clear packet + /// + /// Compress the packets to the stream. + /// + /// The 2D buffer of indexed pixels. + /// The initial bits. + /// The stream to write to. + private void Compress(Buffer2D indexedPixels, int initialBits, Stream stream) + { + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = initialBits; + + // Set up the necessary values + this.clearFlag = false; + this.bitCount = this.globalInitialBits; + this.maxCode = GetMaxcode(this.bitCount); + this.clearCode = 1 << (initialBits - 1); + this.eofCode = this.clearCode + 1; + this.freeEntry = this.clearCode + 2; + this.accumulatorCount = 0; // Clear packet - this.ResetCodeTable(); // Clear hash table - this.Output(this.clearCode, stream); + this.ResetCodeTable(); // Clear hash table + this.Output(this.clearCode, stream); - ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); - ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); + ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.GetSpan()); + ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.GetSpan()); - int entry = indexedPixels[0, 0]; + int entry = indexedPixels[0, 0]; + + for (int y = 0; y < indexedPixels.Height; y++) + { + ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y)); + int offsetX = y == 0 ? 1 : 0; - for (int y = 0; y < indexedPixels.Height; y++) + for (int x = offsetX; x < indexedPixels.Width; x++) { - ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y)); - int offsetX = y == 0 ? 1 : 0; + int code = Unsafe.Add(ref rowSpanRef, x); + int freeCode = (code << MaxBits) + entry; + int hashIndex = (code << HashShift) ^ entry; - for (int x = offsetX; x < indexedPixels.Width; x++) + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - int code = Unsafe.Add(ref rowSpanRef, x); - int freeCode = (code << MaxBits) + entry; - int hashIndex = (code << HashShift) ^ entry; + entry = Unsafe.Add(ref codeTableRef, hashIndex); + continue; + } - if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) + // Non-empty slot + if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0) + { + int disp = 1; + if (hashIndex != 0) { - entry = Unsafe.Add(ref codeTableRef, hashIndex); - continue; + disp = HashSize - hashIndex; } - // Non-empty slot - if (Unsafe.Add(ref hashTableRef, hashIndex) >= 0) + do { - int disp = 1; - if (hashIndex != 0) + if ((hashIndex -= disp) < 0) { - disp = HashSize - hashIndex; + hashIndex += HashSize; } - do - { - if ((hashIndex -= disp) < 0) - { - hashIndex += HashSize; - } - - if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) - { - entry = Unsafe.Add(ref codeTableRef, hashIndex); - break; - } - } - while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0); - if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - continue; + entry = Unsafe.Add(ref codeTableRef, hashIndex); + break; } } + while (Unsafe.Add(ref hashTableRef, hashIndex) >= 0); - this.Output(entry, stream); - entry = code; - if (this.freeEntry < MaxMaxCode) - { - Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable - Unsafe.Add(ref hashTableRef, hashIndex) = freeCode; - } - else + if (Unsafe.Add(ref hashTableRef, hashIndex) == freeCode) { - this.ClearBlock(stream); + continue; } } - } - // Output the final code. - this.Output(entry, stream); - this.Output(this.eofCode, stream); + this.Output(entry, stream); + entry = code; + if (this.freeEntry < MaxMaxCode) + { + Unsafe.Add(ref codeTableRef, hashIndex) = this.freeEntry++; // code -> hashtable + Unsafe.Add(ref hashTableRef, hashIndex) = freeCode; + } + else + { + this.ClearBlock(stream); + } + } } - /// - /// Flush the packet to disk and reset the accumulator. - /// - /// The output stream. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FlushPacket(Stream outStream) + // Output the final code. + this.Output(entry, stream); + this.Output(this.eofCode, stream); + } + + /// + /// Flush the packet to disk and reset the accumulator. + /// + /// The output stream. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FlushPacket(Stream outStream) + { + outStream.WriteByte((byte)this.accumulatorCount); + outStream.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + + /// + /// Output the current code to the stream. + /// + /// The code. + /// The stream to write to. + private void Output(int code, Stream outs) + { + ref byte accumulatorsRef = ref MemoryMarshal.GetReference(this.accumulators.AsSpan()); + this.currentAccumulator &= Masks[this.currentBits]; + + if (this.currentBits > 0) { - outStream.WriteByte((byte)this.accumulatorCount); - outStream.Write(this.accumulators, 0, this.accumulatorCount); - this.accumulatorCount = 0; + this.currentAccumulator |= code << this.currentBits; } + else + { + this.currentAccumulator = code; + } + + this.currentBits += this.bitCount; - /// - /// Output the current code to the stream. - /// - /// The code. - /// The stream to write to. - private void Output(int code, Stream outs) + while (this.currentBits >= 8) { - ref byte accumulatorsRef = ref MemoryMarshal.GetReference(this.accumulators.AsSpan()); - this.currentAccumulator &= Masks[this.currentBits]; + this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } - if (this.currentBits > 0) + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (this.freeEntry > this.maxCode || this.clearFlag) + { + if (this.clearFlag) { - this.currentAccumulator |= code << this.currentBits; + this.maxCode = GetMaxcode(this.bitCount = this.globalInitialBits); + this.clearFlag = false; } else { - this.currentAccumulator = code; + ++this.bitCount; + this.maxCode = this.bitCount == MaxBits + ? MaxMaxCode + : GetMaxcode(this.bitCount); } + } - this.currentBits += this.bitCount; - - while (this.currentBits >= 8) + if (code == this.eofCode) + { + // At EOF, write the rest of the buffer. + while (this.currentBits > 0) { this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); this.currentAccumulator >>= 8; this.currentBits -= 8; } - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (this.freeEntry > this.maxCode || this.clearFlag) - { - if (this.clearFlag) - { - this.maxCode = GetMaxcode(this.bitCount = this.globalInitialBits); - this.clearFlag = false; - } - else - { - ++this.bitCount; - this.maxCode = this.bitCount == MaxBits - ? MaxMaxCode - : GetMaxcode(this.bitCount); - } - } - - if (code == this.eofCode) + if (this.accumulatorCount > 0) { - // At EOF, write the rest of the buffer. - while (this.currentBits > 0) - { - this.AddCharacter((byte)(this.currentAccumulator & 0xFF), ref accumulatorsRef, outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; - } - - if (this.accumulatorCount > 0) - { - this.FlushPacket(outs); - } + this.FlushPacket(outs); } } + } - /// - public void Dispose() - { - this.hashTable?.Dispose(); - this.codeTable?.Dispose(); - } + /// + public void Dispose() + { + this.hashTable?.Dispose(); + this.codeTable?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs index 64bce03a26..2c1a3cf0d7 100644 --- a/src/ImageSharp/Formats/Gif/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Gif/MetadataExtensions.cs @@ -4,25 +4,24 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the gif format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the gif format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static GifMetadata GetGifMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); + /// The metadata this method extends. + /// The . + public static GifMetadata GetGifMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); - /// - /// Gets the gif format specific metadata for the image frame. - /// - /// The metadata this method extends. - /// The . - public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); - } + /// + /// Gets the gif format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static GifFrameMetadata GetGifMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(GifFormat.Instance); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index 7dcf38f586..db2fbd79c3 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -1,106 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// The Graphic Control Extension contains parameters used when +/// processing a graphic rendering block. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct GifGraphicControlExtension : IGifExtension { + public GifGraphicControlExtension( + byte packed, + ushort delayTime, + byte transparencyIndex) + { + this.BlockSize = 4; + this.Packed = packed; + this.DelayTime = delayTime; + this.TransparencyIndex = transparencyIndex; + } + /// - /// The Graphic Control Extension contains parameters used when - /// processing a graphic rendering block. + /// Gets the size of the block. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct GifGraphicControlExtension : IGifExtension - { - public GifGraphicControlExtension( - byte packed, - ushort delayTime, - byte transparencyIndex) - { - this.BlockSize = 4; - this.Packed = packed; - this.DelayTime = delayTime; - this.TransparencyIndex = transparencyIndex; - } + public byte BlockSize { get; } - /// - /// Gets the size of the block. - /// - public byte BlockSize { get; } - - /// - /// Gets the packed disposalMethod and transparencyFlag value. - /// - public byte Packed { get; } - - /// - /// Gets the delay time in of hundredths (1/100) of a second - /// to wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public ushort DelayTime { get; } - - /// - /// Gets the transparency index. - /// The Transparency Index is such that when encountered, the corresponding pixel - /// of the display device is not modified and processing goes on to the next pixel. - /// - public byte TransparencyIndex { get; } - - /// - /// Gets the disposal method which indicates the way in which the - /// graphic is to be treated after being displayed. - /// - public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2); - - /// - /// Gets a value indicating whether transparency flag is to be set. - /// This indicates whether a transparency index is given in the Transparent Index field. - /// - public bool TransparencyFlag => (this.Packed & 0x01) == 1; - - byte IGifExtension.Label => GifConstants.GraphicControlLabel; - - int IGifExtension.ContentLength => 5; - - public int WriteTo(Span buffer) - { - ref GifGraphicControlExtension dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + /// + /// Gets the packed disposalMethod and transparencyFlag value. + /// + public byte Packed { get; } - dest = this; + /// + /// Gets the delay time in of hundredths (1/100) of a second + /// to wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public ushort DelayTime { get; } - return ((IGifExtension)this).ContentLength; - } + /// + /// Gets the transparency index. + /// The Transparency Index is such that when encountered, the corresponding pixel + /// of the display device is not modified and processing goes on to the next pixel. + /// + public byte TransparencyIndex { get; } - public static GifGraphicControlExtension Parse(ReadOnlySpan buffer) - => MemoryMarshal.Cast(buffer)[0]; + /// + /// Gets the disposal method which indicates the way in which the + /// graphic is to be treated after being displayed. + /// + public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2); - public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) - { - /* - Reserved | 3 Bits - Disposal Method | 3 Bits - User Input Flag | 1 Bit - Transparent Color Flag | 1 Bit - */ + /// + /// Gets a value indicating whether transparency flag is to be set. + /// This indicates whether a transparency index is given in the Transparent Index field. + /// + public bool TransparencyFlag => (this.Packed & 0x01) == 1; + + byte IGifExtension.Label => GifConstants.GraphicControlLabel; + + int IGifExtension.ContentLength => 5; + + public int WriteTo(Span buffer) + { + ref GifGraphicControlExtension dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + dest = this; - byte value = 0; + return ((IGifExtension)this).ContentLength; + } + + public static GifGraphicControlExtension Parse(ReadOnlySpan buffer) + => MemoryMarshal.Cast(buffer)[0]; + + public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) + { + /* + Reserved | 3 Bits + Disposal Method | 3 Bits + User Input Flag | 1 Bit + Transparent Color Flag | 1 Bit + */ - value |= (byte)((int)disposalMethod << 2); + byte value = 0; - if (userInputFlag) - { - value |= 1 << 1; - } + value |= (byte)((int)disposalMethod << 2); - if (transparencyFlag) - { - value |= 1; - } + if (userInputFlag) + { + value |= 1 << 1; + } - return value; + if (transparencyFlag) + { + value |= 1; } + + return value; } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs index 9f7755b013..e9e7e1b3e1 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs @@ -1,116 +1,114 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// Each image in the Data Stream is composed of an Image Descriptor, +/// an optional Local Color Table, and the image data. +/// Each image must fit within the boundaries of the +/// Logical Screen, as defined in the Logical Screen Descriptor. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct GifImageDescriptor { + public const int Size = 10; + + public GifImageDescriptor( + ushort left, + ushort top, + ushort width, + ushort height, + byte packed) + { + this.Left = left; + this.Top = top; + this.Width = width; + this.Height = height; + this.Packed = packed; + } + /// - /// Each image in the Data Stream is composed of an Image Descriptor, - /// an optional Local Color Table, and the image data. - /// Each image must fit within the boundaries of the - /// Logical Screen, as defined in the Logical Screen Descriptor. + /// Gets the column number, in pixels, of the left edge of the image, + /// with respect to the left edge of the Logical Screen. + /// Leftmost column of the Logical Screen is 0. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct GifImageDescriptor - { - public const int Size = 10; - - public GifImageDescriptor( - ushort left, - ushort top, - ushort width, - ushort height, - byte packed) - { - this.Left = left; - this.Top = top; - this.Width = width; - this.Height = height; - this.Packed = packed; - } + public ushort Left { get; } - /// - /// Gets the column number, in pixels, of the left edge of the image, - /// with respect to the left edge of the Logical Screen. - /// Leftmost column of the Logical Screen is 0. - /// - public ushort Left { get; } + /// + /// Gets the row number, in pixels, of the top edge of the image with + /// respect to the top edge of the Logical Screen. + /// Top row of the Logical Screen is 0. + /// + public ushort Top { get; } - /// - /// Gets the row number, in pixels, of the top edge of the image with - /// respect to the top edge of the Logical Screen. - /// Top row of the Logical Screen is 0. - /// - public ushort Top { get; } + /// + /// Gets the width of the image in pixels. + /// + public ushort Width { get; } - /// - /// Gets the width of the image in pixels. - /// - public ushort Width { get; } + /// + /// Gets the height of the image in pixels. + /// + public ushort Height { get; } - /// - /// Gets the height of the image in pixels. - /// - public ushort Height { get; } + /// + /// Gets the packed value of localColorTableFlag, interlaceFlag, sortFlag, and localColorTableSize. + /// + public byte Packed { get; } - /// - /// Gets the packed value of localColorTableFlag, interlaceFlag, sortFlag, and localColorTableSize. - /// - public byte Packed { get; } + public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; - public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; + public int LocalColorTableSize => 2 << (this.Packed & 0x07); - public int LocalColorTableSize => 2 << (this.Packed & 0x07); + public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1; - public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1; + public void WriteTo(Span buffer) + { + buffer[0] = GifConstants.ImageDescriptorLabel; - public void WriteTo(Span buffer) - { - buffer[0] = GifConstants.ImageDescriptorLabel; + ref GifImageDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer[1..])); - ref GifImageDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer[1..])); + dest = this; + } + + public static GifImageDescriptor Parse(ReadOnlySpan buffer) + { + return MemoryMarshal.Cast(buffer)[0]; + } - dest = this; + public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize) + { + /* + Local Color Table Flag | 1 Bit + Interlace Flag | 1 Bit + Sort Flag | 1 Bit + Reserved | 2 Bits + Size of Local Color Table | 3 Bits + */ + + byte value = 0; + + if (localColorTableFlag) + { + value |= 1 << 7; } - public static GifImageDescriptor Parse(ReadOnlySpan buffer) + if (interfaceFlag) { - return MemoryMarshal.Cast(buffer)[0]; + value |= 1 << 6; } - public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize) + if (sortFlag) { - /* - Local Color Table Flag | 1 Bit - Interlace Flag | 1 Bit - Sort Flag | 1 Bit - Reserved | 2 Bits - Size of Local Color Table | 3 Bits - */ - - byte value = 0; - - if (localColorTableFlag) - { - value |= 1 << 7; - } - - if (interfaceFlag) - { - value |= 1 << 6; - } - - if (sortFlag) - { - value |= 1 << 5; - } - - value |= (byte)localColorTableSize; - - return value; + value |= 1 << 5; } + + value |= (byte)localColorTableSize; + + return value; } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs index 4b2dad895a..46b588bca6 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs @@ -1,133 +1,131 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +/// +/// The Logical Screen Descriptor contains the parameters +/// necessary to define the area of the display device +/// within which the images will be rendered +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct GifLogicalScreenDescriptor { + public const int Size = 7; + + public GifLogicalScreenDescriptor( + ushort width, + ushort height, + byte packed, + byte backgroundColorIndex, + byte pixelAspectRatio = 0) + { + this.Width = width; + this.Height = height; + this.Packed = packed; + this.BackgroundColorIndex = backgroundColorIndex; + this.PixelAspectRatio = pixelAspectRatio; + } + /// - /// The Logical Screen Descriptor contains the parameters - /// necessary to define the area of the display device - /// within which the images will be rendered + /// Gets the width, in pixels, of the Logical Screen where the images will + /// be rendered in the displaying device. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct GifLogicalScreenDescriptor - { - public const int Size = 7; - - public GifLogicalScreenDescriptor( - ushort width, - ushort height, - byte packed, - byte backgroundColorIndex, - byte pixelAspectRatio = 0) - { - this.Width = width; - this.Height = height; - this.Packed = packed; - this.BackgroundColorIndex = backgroundColorIndex; - this.PixelAspectRatio = pixelAspectRatio; - } + public ushort Width { get; } - /// - /// Gets the width, in pixels, of the Logical Screen where the images will - /// be rendered in the displaying device. - /// - public ushort Width { get; } - - /// - /// Gets the height, in pixels, of the Logical Screen where the images will be - /// rendered in the displaying device. - /// - public ushort Height { get; } - - /// - /// Gets the packed value consisting of: - /// globalColorTableFlag, colorResolution, sortFlag, and sizeOfGlobalColorTable. - /// - public byte Packed { get; } - - /// - /// Gets the index at the Global Color Table for the Background Color. - /// The Background Color is the color used for those - /// pixels on the screen that are not covered by an image. - /// - public byte BackgroundColorIndex { get; } - - /// - /// Gets the pixel aspect ratio. - /// - public byte PixelAspectRatio { get; } - - /// - /// Gets a value indicating whether a flag denoting the presence of a Global Color Table - /// should be set. - /// If the flag is set, the Global Color Table will included after - /// the Logical Screen Descriptor. - /// - public bool GlobalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; - - /// - /// Gets the global color table size. - /// If the Global Color Table Flag is set, - /// the value in this field is used to calculate the number of - /// bytes contained in the Global Color Table. - /// - public int GlobalColorTableSize => 2 << (this.Packed & 0x07); - - /// - /// Gets the color depth, in number of bits per pixel. - /// The lowest 3 packed bits represent the bit depth minus 1. - /// - public int BitsPerPixel => (this.Packed & 0x07) + 1; - - public void WriteTo(Span buffer) - { - ref GifLogicalScreenDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + /// + /// Gets the height, in pixels, of the Logical Screen where the images will be + /// rendered in the displaying device. + /// + public ushort Height { get; } - dest = this; - } + /// + /// Gets the packed value consisting of: + /// globalColorTableFlag, colorResolution, sortFlag, and sizeOfGlobalColorTable. + /// + public byte Packed { get; } - public static GifLogicalScreenDescriptor Parse(ReadOnlySpan buffer) - { - GifLogicalScreenDescriptor result = MemoryMarshal.Cast(buffer)[0]; + /// + /// Gets the index at the Global Color Table for the Background Color. + /// The Background Color is the color used for those + /// pixels on the screen that are not covered by an image. + /// + public byte BackgroundColorIndex { get; } - if (result.GlobalColorTableSize > 255 * 4) - { - throw new ImageFormatException($"Invalid gif colormap size '{result.GlobalColorTableSize}'"); - } + /// + /// Gets the pixel aspect ratio. + /// + public byte PixelAspectRatio { get; } - return result; - } + /// + /// Gets a value indicating whether a flag denoting the presence of a Global Color Table + /// should be set. + /// If the flag is set, the Global Color Table will included after + /// the Logical Screen Descriptor. + /// + public bool GlobalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; - public static byte GetPackedValue(bool globalColorTableFlag, int colorResolution, bool sortFlag, int globalColorTableSize) + /// + /// Gets the global color table size. + /// If the Global Color Table Flag is set, + /// the value in this field is used to calculate the number of + /// bytes contained in the Global Color Table. + /// + public int GlobalColorTableSize => 2 << (this.Packed & 0x07); + + /// + /// Gets the color depth, in number of bits per pixel. + /// The lowest 3 packed bits represent the bit depth minus 1. + /// + public int BitsPerPixel => (this.Packed & 0x07) + 1; + + public void WriteTo(Span buffer) + { + ref GifLogicalScreenDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + dest = this; + } + + public static GifLogicalScreenDescriptor Parse(ReadOnlySpan buffer) + { + GifLogicalScreenDescriptor result = MemoryMarshal.Cast(buffer)[0]; + + if (result.GlobalColorTableSize > 255 * 4) { - /* - Global Color Table Flag | 1 Bit - Color Resolution | 3 Bits - Sort Flag | 1 Bit - Size of Global Color Table | 3 Bits - */ + throw new ImageFormatException($"Invalid gif colormap size '{result.GlobalColorTableSize}'"); + } - byte value = 0; + return result; + } - if (globalColorTableFlag) - { - value |= 1 << 7; - } + public static byte GetPackedValue(bool globalColorTableFlag, int colorResolution, bool sortFlag, int globalColorTableSize) + { + /* + Global Color Table Flag | 1 Bit + Color Resolution | 3 Bits + Sort Flag | 1 Bit + Size of Global Color Table | 3 Bits + */ - value |= (byte)(colorResolution << 4); + byte value = 0; - if (sortFlag) - { - value |= 1 << 3; - } + if (globalColorTableFlag) + { + value |= 1 << 7; + } - value |= (byte)globalColorTableSize; + value |= (byte)(colorResolution << 4); - return value; + if (sortFlag) + { + value |= 1 << 3; } + + value |= (byte)globalColorTableSize; + + return value; } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 8965ae345d..e413896751 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -1,46 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension { - internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension - { - public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount; + public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount; - public byte Label => GifConstants.ApplicationExtensionLabel; + public byte Label => GifConstants.ApplicationExtensionLabel; - public int ContentLength => 16; + public int ContentLength => 16; - /// - /// Gets the repeat count. - /// 0 means loop indefinitely. Count is set as play n + 1 times. - /// - public ushort RepeatCount { get; } + /// + /// Gets the repeat count. + /// 0 means loop indefinitely. Count is set as play n + 1 times. + /// + public ushort RepeatCount { get; } - public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan buffer) - { - ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer[..2]); - return new GifNetscapeLoopingApplicationExtension(repeatCount); - } + public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan buffer) + { + ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer[..2]); + return new GifNetscapeLoopingApplicationExtension(repeatCount); + } - public int WriteTo(Span buffer) - { - buffer[0] = GifConstants.ApplicationBlockSize; + public int WriteTo(Span buffer) + { + buffer[0] = GifConstants.ApplicationBlockSize; - // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); + // Write NETSCAPE2.0 + GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); - // Application Data ---- - buffer[12] = 3; // Application block length (always 3) - buffer[13] = 1; // Data sub-block identity (always 1) + // Application Data ---- + buffer[12] = 3; // Application block length (always 3) + buffer[13] = 1; // Data sub-block identity (always 1) - // 0 means loop indefinitely. Count is set as play n + 1 times. - BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); + // 0 means loop indefinitely. Count is set as play n + 1 times. + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); - return this.ContentLength; // Length - Introducer + Label + Terminator. - } + return this.ContentLength; // Length - Introducer + Label + Terminator. } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs index 9f8d8e4d07..45f3a92805 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs @@ -1,98 +1,95 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.Formats.Gif; + +internal readonly struct GifXmpApplicationExtension : IGifExtension { - internal readonly struct GifXmpApplicationExtension : IGifExtension + public GifXmpApplicationExtension(byte[] data) => this.Data = data; + + public byte Label => GifConstants.ApplicationExtensionLabel; + + // size : 1 + // identifier : 11 + // magic trailer : 257 + public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0; + + /// + /// Gets the raw Data. + /// + public byte[] Data { get; } + + /// + /// Reads the XMP metadata from the specified stream. + /// + /// The stream to read from. + /// The memory allocator. + /// The XMP metadata + /// Thrown if the XMP block is not properly terminated. + public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator) { - public GifXmpApplicationExtension(byte[] data) => this.Data = data; - - public byte Label => GifConstants.ApplicationExtensionLabel; - - // size : 1 - // identifier : 11 - // magic trailer : 257 - public int ContentLength => (this.Data.Length > 0) ? this.Data.Length + 269 : 0; - - /// - /// Gets the raw Data. - /// - public byte[] Data { get; } - - /// - /// Reads the XMP metadata from the specified stream. - /// - /// The stream to read from. - /// The memory allocator. - /// The XMP metadata - /// Thrown if the XMP block is not properly terminated. - public static GifXmpApplicationExtension Read(Stream stream, MemoryAllocator allocator) - { - byte[] xmpBytes = ReadXmpData(stream, allocator); - - // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF - int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0 - byte[] buffer = Array.Empty(); - if (xmpLength > 0) - { - buffer = new byte[xmpLength]; - xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer); - stream.Skip(1); // Skip the terminator. - } + byte[] xmpBytes = ReadXmpData(stream, allocator); - return new GifXmpApplicationExtension(buffer); + // Exclude the "magic trailer", see XMP Specification Part 3, 1.1.2 GIF + int xmpLength = xmpBytes.Length - 256; // 257 - unread 0x0 + byte[] buffer = Array.Empty(); + if (xmpLength > 0) + { + buffer = new byte[xmpLength]; + xmpBytes.AsSpan(0, xmpLength).CopyTo(buffer); + stream.Skip(1); // Skip the terminator. } - public int WriteTo(Span buffer) - { - int bytesWritten = 0; - buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; + return new GifXmpApplicationExtension(buffer); + } - // Write "XMP DataXMP" - ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; - idBytes.CopyTo(buffer[bytesWritten..]); - bytesWritten += idBytes.Length; + public int WriteTo(Span buffer) + { + int bytesWritten = 0; + buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; - // XMP Data itself - this.Data.CopyTo(buffer[bytesWritten..]); - bytesWritten += this.Data.Length; + // Write "XMP DataXMP" + ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; + idBytes.CopyTo(buffer[bytesWritten..]); + bytesWritten += idBytes.Length; - // Write the Magic Trailer - buffer[bytesWritten++] = 0x01; - for (byte i = 255; i > 0; i--) - { - buffer[bytesWritten++] = i; - } - - buffer[bytesWritten++] = 0x00; + // XMP Data itself + this.Data.CopyTo(buffer[bytesWritten..]); + bytesWritten += this.Data.Length; - return this.ContentLength; + // Write the Magic Trailer + buffer[bytesWritten++] = 0x01; + for (byte i = 255; i > 0; i--) + { + buffer[bytesWritten++] = i; } - private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator) + buffer[bytesWritten++] = 0x00; + + return this.ContentLength; + } + + private static byte[] ReadXmpData(Stream stream, MemoryAllocator allocator) + { + using ChunkedMemoryStream bytes = new(allocator); + + // XMP data doesn't have a fixed length nor is there an indicator of the length. + // So we simply read one byte at a time until we hit the 0x0 value at the end + // of the magic trailer or the end of the stream. + // Using ChunkedMemoryStream reduces the array resize allocation normally associated + // with writing from a non fixed-size buffer. + while (true) { - using ChunkedMemoryStream bytes = new(allocator); - - // XMP data doesn't have a fixed length nor is there an indicator of the length. - // So we simply read one byte at a time until we hit the 0x0 value at the end - // of the magic trailer or the end of the stream. - // Using ChunkedMemoryStream reduces the array resize allocation normally associated - // with writing from a non fixed-size buffer. - while (true) + int b = stream.ReadByte(); + if (b <= 0) { - int b = stream.ReadByte(); - if (b <= 0) - { - return bytes.ToArray(); - } - - bytes.WriteByte((byte)b); + return bytes.ToArray(); } + + bytes.WriteByte((byte)b); } } } diff --git a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs index 59cef0d2e6..d7fdb8b46f 100644 --- a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs @@ -1,30 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Gif; -namespace SixLabors.ImageSharp.Formats.Gif +/// +/// A base interface for GIF extensions. +/// +public interface IGifExtension { /// - /// A base interface for GIF extensions. + /// Gets the label identifying the extensions. /// - public interface IGifExtension - { - /// - /// Gets the label identifying the extensions. - /// - byte Label { get; } + byte Label { get; } - /// - /// Gets the length of the contents of this extension. - /// - int ContentLength { get; } + /// + /// Gets the length of the contents of this extension. + /// + int ContentLength { get; } - /// - /// Writes the extension data to the buffer. - /// - /// The buffer to write the extension to. - /// The number of bytes written to the buffer. - int WriteTo(Span buffer); - } + /// + /// Writes the extension data to the buffer. + /// + /// The buffer to write the extension to. + /// The number of bytes written to the buffer. + int WriteTo(Span buffer); } diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index 949e2133ad..7052bc49fe 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -1,43 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Encapsulates properties and methods required for decoding an image from a stream. +/// +public interface IImageDecoder : IImageInfoDetector { /// - /// Encapsulates properties and methods required for decoding an image from a stream. + /// Decodes the image from the specified stream to an of a specific pixel type. /// - public interface IImageDecoder : IImageInfoDetector - { - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The pixel format. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The pixel format. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; - /// - /// Decodes the image from the specified stream to an . - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - } + /// + /// Decodes the image from the specified stream to an . + /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/IImageDecoderInternals.cs b/src/ImageSharp/Formats/IImageDecoderInternals.cs index 63596266fb..d8cb1c6627 100644 --- a/src/ImageSharp/Formats/IImageDecoderInternals.cs +++ b/src/ImageSharp/Formats/IImageDecoderInternals.cs @@ -1,53 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Abstraction for shared internals for XXXDecoderCore implementations to be used with . +/// +internal interface IImageDecoderInternals { /// - /// Abstraction for shared internals for XXXDecoderCore implementations to be used with . + /// Gets the general decoder options. /// - internal interface IImageDecoderInternals - { - /// - /// Gets the general decoder options. - /// - DecoderOptions Options { get; } + DecoderOptions Options { get; } - /// - /// Gets the dimensions of the image being decoded. - /// - Size Dimensions { get; } + /// + /// Gets the dimensions of the image being decoded. + /// + Size Dimensions { get; } - /// - /// Decodes the image from the specified stream. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The token to monitor for cancellation requests. - /// is null. - /// The decoded image. - /// - /// Cancellable synchronous method. In case of cancellation, - /// an shall be thrown which will be handled on the call site. - /// - Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; + /// + /// Decodes the image from the specified stream. + /// + /// The pixel format. + /// The stream, where the image should be decoded from. Cannot be null. + /// The token to monitor for cancellation requests. + /// is null. + /// The decoded image. + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; - /// - /// Reads the raw image information from the specified stream. - /// - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// - /// Cancellable synchronous method. In case of cancellation, - /// an shall be thrown which will be handled on the call site. - /// - IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); - } + /// + /// Reads the raw image information from the specified stream. + /// + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// + /// Cancellable synchronous method. In case of cancellation, + /// an shall be thrown which will be handled on the call site. + /// + IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs index 93a3abb7a7..77cffe34ca 100644 --- a/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs +++ b/src/ImageSharp/Formats/IImageDecoderSpecialized{T}.cs @@ -1,45 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// The base class for all specialized image decoders. +/// +/// The type of specialized options. +public interface IImageDecoderSpecialized : IImageDecoder + where T : ISpecializedDecoderOptions { /// - /// The base class for all specialized image decoders. + /// Decodes the image from the specified stream to an of a specific pixel type. /// - /// The type of specialized options. - public interface IImageDecoderSpecialized : IImageDecoder - where T : ISpecializedDecoderOptions - { - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The pixel format. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(T options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The pixel format. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public Image Decode(T options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The . - /// Thrown if the encoded image contains errors. - public Image Decode(T options, Stream stream, CancellationToken cancellationToken); - } + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The . + /// Thrown if the encoded image contains errors. + public Image Decode(T options, Stream stream, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 40831ed7c3..112c38bd5a 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -1,36 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Encapsulates properties and methods required for encoding an image to a stream. +/// +public interface IImageEncoder { /// - /// Encapsulates properties and methods required for encoding an image to a stream. + /// Encodes the image to the specified stream from the . /// - public interface IImageEncoder - { - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel; + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Formats/IImageEncoderInternals.cs b/src/ImageSharp/Formats/IImageEncoderInternals.cs index 824a08f5a7..ca9c474e13 100644 --- a/src/ImageSharp/Formats/IImageEncoderInternals.cs +++ b/src/ImageSharp/Formats/IImageEncoderInternals.cs @@ -1,25 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Abstraction for shared internals for ***DecoderCore implementations to be used with . +/// +internal interface IImageEncoderInternals { /// - /// Abstraction for shared internals for ***DecoderCore implementations to be used with . + /// Encodes the image. /// - internal interface IImageEncoderInternals - { - /// - /// Encodes the image. - /// - /// The image. - /// The stream. - /// The token to monitor for cancellation requests. - /// The pixel type. - void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel; - } + /// The image. + /// The stream. + /// The token to monitor for cancellation requests. + /// The pixel type. + void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index c853ca413a..b983219777 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.Formats +/// +/// Defines the contract for an image format. +/// +public interface IImageFormat { /// - /// Defines the contract for an image format. + /// Gets the name that describes this image format. /// - public interface IImageFormat - { - /// - /// Gets the name that describes this image format. - /// - string Name { get; } + string Name { get; } - /// - /// Gets the default mimetype that the image format uses - /// - string DefaultMimeType { get; } + /// + /// Gets the default mimetype that the image format uses + /// + string DefaultMimeType { get; } - /// - /// Gets all the mimetypes that have been used by this image format. - /// - IEnumerable MimeTypes { get; } + /// + /// Gets all the mimetypes that have been used by this image format. + /// + IEnumerable MimeTypes { get; } - /// - /// Gets the file extensions this image format commonly uses. - /// - IEnumerable FileExtensions { get; } - } + /// + /// Gets the file extensions this image format commonly uses. + /// + IEnumerable FileExtensions { get; } +} +/// +/// Defines the contract for an image format containing metadata. +/// +/// The type of format metadata. +public interface IImageFormat : IImageFormat + where TFormatMetadata : class +{ /// - /// Defines the contract for an image format containing metadata. + /// Creates a default instance of the format metadata. /// - /// The type of format metadata. - public interface IImageFormat : IImageFormat - where TFormatMetadata : class - { - /// - /// Creates a default instance of the format metadata. - /// - /// The . - TFormatMetadata CreateDefaultFormatMetadata(); - } + /// The . + TFormatMetadata CreateDefaultFormatMetadata(); +} +/// +/// Defines the contract for an image format containing metadata with multiple frames. +/// +/// The type of format metadata. +/// The type of format frame metadata. +public interface IImageFormat : IImageFormat + where TFormatMetadata : class + where TFormatFrameMetadata : class +{ /// - /// Defines the contract for an image format containing metadata with multiple frames. + /// Creates a default instance of the format frame metadata. /// - /// The type of format metadata. - /// The type of format frame metadata. - public interface IImageFormat : IImageFormat - where TFormatMetadata : class - where TFormatFrameMetadata : class - { - /// - /// Creates a default instance of the format frame metadata. - /// - /// The . - TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); - } + /// The . + TFormatFrameMetadata CreateDefaultFormatFrameMetadata(); } diff --git a/src/ImageSharp/Formats/IImageFormatDetector.cs b/src/ImageSharp/Formats/IImageFormatDetector.cs index 4175cf0ece..17f565d2b2 100644 --- a/src/ImageSharp/Formats/IImageFormatDetector.cs +++ b/src/ImageSharp/Formats/IImageFormatDetector.cs @@ -1,26 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.Formats +/// +/// Used for detecting mime types from a file header +/// +public interface IImageFormatDetector { /// - /// Used for detecting mime types from a file header + /// Gets the size of the header for this image type. /// - public interface IImageFormatDetector - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } + /// The size of the header. + int HeaderSize { get; } - /// - /// Detect mimetype - /// - /// The containing the file header. - /// returns the mime type of detected otherwise returns null - IImageFormat DetectFormat(ReadOnlySpan header); - } + /// + /// Detect mimetype + /// + /// The containing the file header. + /// returns the mime type of detected otherwise returns null + IImageFormat DetectFormat(ReadOnlySpan header); } diff --git a/src/ImageSharp/Formats/IImageInfoDetector.cs b/src/ImageSharp/Formats/IImageInfoDetector.cs index 1bc8a44092..ab5f536ff1 100644 --- a/src/ImageSharp/Formats/IImageInfoDetector.cs +++ b/src/ImageSharp/Formats/IImageInfoDetector.cs @@ -1,27 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; +namespace SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.Formats +/// +/// Encapsulates methods used for detecting the raw image information without fully decoding it. +/// +public interface IImageInfoDetector { /// - /// Encapsulates methods used for detecting the raw image information without fully decoding it. + /// Reads the raw image information from the specified stream. /// - public interface IImageInfoDetector - { - /// - /// Reads the raw image information from the specified stream. - /// - /// - /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. - /// - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); - } + /// + /// This method is designed to support the ImageSharp internal infrastructure and is not recommended for direct use. + /// + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object. + /// Thrown if the encoded image contains errors. + IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs index eacb901838..75f643d0c6 100644 --- a/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs +++ b/src/ImageSharp/Formats/ISpecializedDecoderOptions.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Provides specialized configuration options for decoding image formats. +/// +public interface ISpecializedDecoderOptions { /// - /// Provides specialized configuration options for decoding image formats. + /// Gets or sets the general decoder options. /// - public interface ISpecializedDecoderOptions - { - /// - /// Gets or sets the general decoder options. - /// - DecoderOptions GeneralOptions { get; set; } - } + DecoderOptions GeneralOptions { get; set; } } diff --git a/src/ImageSharp/Formats/ImageDecoderExtensions.cs b/src/ImageSharp/Formats/ImageDecoderExtensions.cs index 0e3bcdf9a0..a18974bbd2 100644 --- a/src/ImageSharp/Formats/ImageDecoderExtensions.cs +++ b/src/ImageSharp/Formats/ImageDecoderExtensions.cs @@ -1,182 +1,178 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Extensions methods for and . +/// +public static class ImageDecoderExtensions { /// - /// Extensions methods for and . + /// Reads the raw image information from the specified stream. /// - public static class ImageDecoderExtensions - { - /// - /// Reads the raw image information from the specified stream. - /// - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The object. - /// Thrown if the encoded image contains errors. - public static IImageInfo Identify(this IImageDecoder decoder, DecoderOptions options, Stream stream) - => Image.WithSeekableStream( - options, - stream, - s => decoder.Identify(options, s, default)); + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The object. + /// Thrown if the encoded image contains errors. + public static IImageInfo Identify(this IImageDecoder decoder, DecoderOptions options, Stream stream) + => Image.WithSeekableStream( + options, + stream, + s => decoder.Identify(options, s, default)); - /// - /// Reads the raw image information from the specified stream. - /// - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// The object. - /// Thrown if the encoded image contains errors. - public static Task IdentifyAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - => Image.WithSeekableStreamAsync( - options, - stream, - (s, ct) => decoder.Identify(options, s, ct), - cancellationToken); + /// + /// Reads the raw image information from the specified stream. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// The object. + /// Thrown if the encoded image contains errors. + public static Task IdentifyAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Identify(options, s, ct), + cancellationToken); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - => Image.WithSeekableStream( - options, - stream, - s => decoder.Decode(options, s, default)); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) + where TPixel : unmanaged, IPixel + => Image.WithSeekableStream( + options, + stream, + s => decoder.Decode(options, s, default)); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) - => Image.WithSeekableStream( - options, - stream, - s => decoder.Decode(options, s, default)); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoder decoder, DecoderOptions options, Stream stream) + => Image.WithSeekableStream( + options, + stream, + s => decoder.Decode(options, s, default)); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The pixel format. - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task> DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => Image.WithSeekableStreamAsync( - options, - stream, - (s, ct) => decoder.Decode(options, s, ct), - cancellationToken); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The pixel format. + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task> DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The decoder. - /// The general decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - => Image.WithSeekableStreamAsync( - options, - stream, - (s, ct) => decoder.Decode(options, s, ct), - cancellationToken); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The decoder. + /// The general decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task DecodeAsync(this IImageDecoder decoder, DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + => Image.WithSeekableStreamAsync( + options, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The pixel format. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) - where T : ISpecializedDecoderOptions - where TPixel : unmanaged, IPixel - => Image.WithSeekableStream( - options.GeneralOptions, - stream, - s => decoder.Decode(options, s, default)); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => Image.WithSeekableStream( + options.GeneralOptions, + stream, + s => decoder.Decode(options, s, default)); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The . - /// Thrown if the encoded image contains errors. - public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) - where T : ISpecializedDecoderOptions - => Image.WithSeekableStream( - options.GeneralOptions, - stream, - s => decoder.Decode(options, s, default)); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The . + /// Thrown if the encoded image contains errors. + public static Image Decode(this IImageDecoderSpecialized decoder, T options, Stream stream) + where T : ISpecializedDecoderOptions + => Image.WithSeekableStream( + options.GeneralOptions, + stream, + s => decoder.Decode(options, s, default)); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The pixel format. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task> DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) - where T : ISpecializedDecoderOptions - where TPixel : unmanaged, IPixel - => Image.WithSeekableStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => decoder.Decode(options, s, ct), - cancellationToken); + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The pixel format. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task> DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + where TPixel : unmanaged, IPixel + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); - /// - /// Decodes the image from the specified stream to an of a specific pixel type. - /// - /// The type of specialized options. - /// The decoder. - /// The specialized decoder options. - /// The containing image data. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - /// Thrown if the encoded image contains errors. - public static Task DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) - where T : ISpecializedDecoderOptions - => Image.WithSeekableStreamAsync( - options.GeneralOptions, - stream, - (s, ct) => decoder.Decode(options, s, ct), - cancellationToken); - } + /// + /// Decodes the image from the specified stream to an of a specific pixel type. + /// + /// The type of specialized options. + /// The decoder. + /// The specialized decoder options. + /// The containing image data. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + /// Thrown if the encoded image contains errors. + public static Task DecodeAsync(this IImageDecoderSpecialized decoder, T options, Stream stream, CancellationToken cancellationToken = default) + where T : ISpecializedDecoderOptions + => Image.WithSeekableStreamAsync( + options.GeneralOptions, + stream, + (s, ct) => decoder.Decode(options, s, ct), + cancellationToken); } diff --git a/src/ImageSharp/Formats/ImageDecoderUtilities.cs b/src/ImageSharp/Formats/ImageDecoderUtilities.cs index 37a4ebe5fe..42f15cf976 100644 --- a/src/ImageSharp/Formats/ImageDecoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageDecoderUtilities.cs @@ -1,109 +1,105 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Utility methods for . +/// +internal static class ImageDecoderUtilities { /// - /// Utility methods for . + /// Performs a resize operation against the decoded image. If the target size is not set, or the image size + /// already matches the target size, the image is untouched. /// - internal static class ImageDecoderUtilities + /// The decoder options. + /// The decoded image. + public static void Resize(DecoderOptions options, Image image) { - /// - /// Performs a resize operation against the decoded image. If the target size is not set, or the image size - /// already matches the target size, the image is untouched. - /// - /// The decoder options. - /// The decoded image. - public static void Resize(DecoderOptions options, Image image) + if (ShouldResize(options, image)) { - if (ShouldResize(options, image)) + ResizeOptions resizeOptions = new() { - ResizeOptions resizeOptions = new() - { - Size = options.TargetSize.Value, - Sampler = options.Sampler, - Mode = ResizeMode.Max - }; + Size = options.TargetSize.Value, + Sampler = options.Sampler, + Mode = ResizeMode.Max + }; - image.Mutate(x => x.Resize(resizeOptions)); - } + image.Mutate(x => x.Resize(resizeOptions)); } + } - /// - /// Determines whether the decoded image should be resized. - /// - /// The decoder options. - /// The decoded image. - /// if the image should be resized, otherwise; . - private static bool ShouldResize(DecoderOptions options, Image image) + /// + /// Determines whether the decoded image should be resized. + /// + /// The decoder options. + /// The decoded image. + /// if the image should be resized, otherwise; . + private static bool ShouldResize(DecoderOptions options, Image image) + { + if (options.TargetSize is null) { - if (options.TargetSize is null) - { - return false; - } - - Size targetSize = options.TargetSize.Value; - Size currentSize = image.Size(); - return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; + return false; } - internal static IImageInfo Identify( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - { - using var bufferedReadStream = new BufferedReadStream(configuration, stream); + Size targetSize = options.TargetSize.Value; + Size currentSize = image.Size(); + return currentSize.Width != targetSize.Width && currentSize.Height != targetSize.Height; + } - try - { - return decoder.Identify(bufferedReadStream, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - throw new InvalidImageContentException(decoder.Dimensions, ex); - } + internal static IImageInfo Identify( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + CancellationToken cancellationToken) + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); + + try + { + return decoder.Identify(bufferedReadStream, cancellationToken); + } + catch (InvalidMemoryOperationException ex) + { + throw new InvalidImageContentException(decoder.Dimensions, ex); } + } - internal static Image Decode( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); + internal static Image Decode( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => decoder.Decode(configuration, stream, DefaultLargeImageExceptionFactory, cancellationToken); - internal static Image Decode( - this IImageDecoderInternals decoder, - Configuration configuration, - Stream stream, - Func largeImageExceptionFactory, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - using var bufferedReadStream = new BufferedReadStream(configuration, stream); + internal static Image Decode( + this IImageDecoderInternals decoder, + Configuration configuration, + Stream stream, + Func largeImageExceptionFactory, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using var bufferedReadStream = new BufferedReadStream(configuration, stream); - try - { - return decoder.Decode(bufferedReadStream, cancellationToken); - } - catch (InvalidMemoryOperationException ex) - { - throw largeImageExceptionFactory(ex, decoder.Dimensions); - } + try + { + return decoder.Decode(bufferedReadStream, cancellationToken); + } + catch (InvalidMemoryOperationException ex) + { + throw largeImageExceptionFactory(ex, decoder.Dimensions); } - - private static InvalidImageContentException DefaultLargeImageExceptionFactory( - InvalidMemoryOperationException memoryOperationException, - Size dimensions) => - new(dimensions, memoryOperationException); } + + private static InvalidImageContentException DefaultLargeImageExceptionFactory( + InvalidMemoryOperationException memoryOperationException, + Size dimensions) => + new(dimensions, memoryOperationException); } diff --git a/src/ImageSharp/Formats/ImageEncoderUtilities.cs b/src/ImageSharp/Formats/ImageEncoderUtilities.cs index d9fb701501..94f74ea253 100644 --- a/src/ImageSharp/Formats/ImageEncoderUtilities.cs +++ b/src/ImageSharp/Formats/ImageEncoderUtilities.cs @@ -1,61 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +internal static class ImageEncoderUtilities { - internal static class ImageEncoderUtilities + public static async Task EncodeAsync( + this IImageEncoderInternals encoder, + Image image, + Stream stream, + CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - public static async Task EncodeAsync( - this IImageEncoderInternals encoder, - Image image, - Stream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + Configuration configuration = image.GetConfiguration(); + if (stream.CanSeek) + { + await DoEncodeAsync(stream).ConfigureAwait(false); + } + else { - Configuration configuration = image.GetConfiguration(); - if (stream.CanSeek) + using var ms = new MemoryStream(); + await DoEncodeAsync(ms); + ms.Position = 0; + await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) + .ConfigureAwait(false); + } + + Task DoEncodeAsync(Stream innerStream) + { + try { - await DoEncodeAsync(stream).ConfigureAwait(false); + encoder.Encode(image, innerStream, cancellationToken); + return Task.CompletedTask; } - else + catch (OperationCanceledException) { - using var ms = new MemoryStream(); - await DoEncodeAsync(ms); - ms.Position = 0; - await ms.CopyToAsync(stream, configuration.StreamProcessingBufferSize, cancellationToken) - .ConfigureAwait(false); + return Task.FromCanceled(cancellationToken); } - - Task DoEncodeAsync(Stream innerStream) + catch (Exception ex) { - try - { - encoder.Encode(image, innerStream, cancellationToken); - return Task.CompletedTask; - } - catch (OperationCanceledException) - { - return Task.FromCanceled(cancellationToken); - } - catch (Exception ex) - { - return Task.FromException(ex); - } + return Task.FromException(ex); } } - - public static void Encode( - this IImageEncoderInternals encoder, - Image image, - Stream stream) - where TPixel : unmanaged, IPixel - => encoder.Encode(image, stream, default); } + + public static void Encode( + this IImageEncoderInternals encoder, + Image image, + Stream stream) + where TPixel : unmanaged, IPixel + => encoder.Encode(image, stream, default); } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index 91f44e01d5..f8763b72fd 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -2,9 +2,6 @@ // Licensed under the Six Labors Split License. // -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Bmp; @@ -16,828 +13,827 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Tiff; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class ImageExtensions { /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsBmpAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream) - => SaveAsBmp(source, stream, null); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsBmpAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsGifAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream) - => SaveAsGif(source, stream, null); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsGifAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); - - /// - /// Saves the image to the given stream with the Gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsJpegAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream) - => SaveAsJpeg(source, stream, null); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsJpegAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); - - /// - /// Saves the image to the given stream with the Jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsPbmAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsPbm(this Image source, Stream stream) - => SaveAsPbm(source, stream, null); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsPbmAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); - - /// - /// Saves the image to the given stream with the Pbm format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsPngAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream) - => SaveAsPng(source, stream, null); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsPngAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); - - /// - /// Saves the image to the given stream with the Png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsTgaAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream) - => SaveAsTga(source, stream, null); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsTgaAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tga format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsWebpAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream) - => SaveAsWebp(source, stream, null); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsWebpAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); - - /// - /// Saves the image to the given stream with the Webp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken) - => SaveAsTiffAsync(source, path, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), - cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsTiff(this Image source, Stream stream) - => SaveAsTiff(source, stream, null); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAsTiffAsync(source, stream, null, cancellationToken); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); - - /// - /// Saves the image to the given stream with the Tiff format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), - cancellationToken); - - } + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsBmp(this Image source, string path) => SaveAsBmp(source, path, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path) => SaveAsBmpAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsBmpAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsBmp(this Image source, string path, BmpEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, string path, BmpEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsBmp(this Image source, Stream stream) + => SaveAsBmp(source, stream, null); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsBmpAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsBmpAsync(this Image source, Stream stream, BmpEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsGif(this Image source, string path) => SaveAsGif(source, path, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path) => SaveAsGifAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsGifAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsGif(this Image source, string path, GifEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, string path, GifEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsGif(this Image source, Stream stream) + => SaveAsGif(source, stream, null); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsGifAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); + + /// + /// Saves the image to the given stream with the Gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsGifAsync(this Image source, Stream stream, GifEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsJpeg(this Image source, string path) => SaveAsJpeg(source, path, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path) => SaveAsJpegAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsJpegAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsJpeg(this Image source, string path, JpegEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, string path, JpegEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this Image source, Stream stream) + => SaveAsJpeg(source, stream, null); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsJpegAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); + + /// + /// Saves the image to the given stream with the Jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsJpegAsync(this Image source, Stream stream, JpegEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsPbmAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPbm(this Image source, Stream stream) + => SaveAsPbm(source, stream, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsPbmAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsPng(this Image source, string path) => SaveAsPng(source, path, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path) => SaveAsPngAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsPngAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsPng(this Image source, string path, PngEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, string path, PngEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPng(this Image source, Stream stream) + => SaveAsPng(source, stream, null); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsPngAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); + + /// + /// Saves the image to the given stream with the Png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPngAsync(this Image source, Stream stream, PngEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsTga(this Image source, string path) => SaveAsTga(source, path, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path) => SaveAsTgaAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsTgaAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsTga(this Image source, string path, TgaEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, string path, TgaEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsTga(this Image source, Stream stream) + => SaveAsTga(source, stream, null); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsTgaAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tga format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTgaAsync(this Image source, Stream stream, TgaEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsWebpAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsWebp(this Image source, Stream stream) + => SaveAsWebp(source, stream, null); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsWebpAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance)); + + /// + /// Saves the image to the given stream with the Webp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsTiffAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsTiff(this Image source, Stream stream) + => SaveAsTiff(source, stream, null); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsTiffAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance)); + + /// + /// Saves the image to the given stream with the Tiff format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance), + cancellationToken); + } diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index 2a756b4837..9ca3a0f223 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -5,9 +5,6 @@ // Licensed under the Six Labors Split License. // -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; <# @@ -31,121 +28,120 @@ using SixLabors.ImageSharp.Formats.<#= fmt #>; } #> -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class ImageExtensions { - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { <# foreach (string fmt in formats) { #> - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, CancellationToken cancellationToken) - => SaveAs<#= fmt #>Async(source, path, null, cancellationToken); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path, CancellationToken cancellationToken) + => SaveAs<#= fmt #>Async(source, path, null, cancellationToken); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// Thrown if the path is null. - public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) => - source.Save( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAs<#= fmt #>(this Image source, string path, <#= fmt #>Encoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the path is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - path, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), - cancellationToken); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, string path, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), + cancellationToken); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAs<#= fmt #>(this Image source, Stream stream) - => SaveAs<#= fmt #>(source, stream, null); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAs<#= fmt #>(this Image source, Stream stream) + => SaveAs<#= fmt #>(source, stream, null); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default) - => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAs<#= fmt #>Async(source, stream, null, cancellationToken); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) - => source.Save( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public static void SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance)); - /// - /// Saves the image to the given stream with the <#= fmt #> format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream is null. - /// A representing the asynchronous operation. - public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => - source.SaveAsync( - stream, - encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), - cancellationToken); + /// + /// Saves the image to the given stream with the <#= fmt #> format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAs<#= fmt #>Async(this Image source, Stream stream, <#= fmt #>Encoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance), + cancellationToken); <# - } +} #> - } } diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index 78e797dcd7..9f22c62294 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -1,193 +1,189 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Collection of Image Formats to be used in class. +/// +public class ImageFormatManager { /// - /// Collection of Image Formats to be used in class. + /// Used for locking against as there is no ConcurrentSet type. + /// + /// + private static readonly object HashLock = new(); + + /// + /// The list of supported keyed to mime types. /// - public class ImageFormatManager + private readonly ConcurrentDictionary mimeTypeEncoders = new(); + + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new(); + + /// + /// The list of supported s. + /// + private readonly HashSet imageFormats = new(); + + /// + /// The list of supported s. + /// + private ConcurrentBag imageFormatDetectors = new(); + + /// + /// Initializes a new instance of the class. + /// + public ImageFormatManager() { - /// - /// Used for locking against as there is no ConcurrentSet type. - /// - /// - private static readonly object HashLock = new(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeEncoders = new(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeDecoders = new(); - - /// - /// The list of supported s. - /// - private readonly HashSet imageFormats = new(); - - /// - /// The list of supported s. - /// - private ConcurrentBag imageFormatDetectors = new(); - - /// - /// Initializes a new instance of the class. - /// - public ImageFormatManager() - { - } + } - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize { get; private set; } - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.imageFormats; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable FormatDetectors => this.imageFormatDetectors; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - - /// - /// Registers a new format provider. - /// - /// The format to register as a known format. - public void AddImageFormat(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); - Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize { get; private set; } - lock (HashLock) - { - if (!this.imageFormats.Contains(format)) - { - this.imageFormats.Add(format); - } - } - } + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.imageFormats; - /// - /// For the specified file extensions type find the e . - /// - /// The extension to discover - /// The if found otherwise null - public IImageFormat FindFormatByFileExtension(string extension) - { - Guard.NotNullOrWhiteSpace(extension, nameof(extension)); + /// + /// Gets the currently registered s. + /// + internal IEnumerable FormatDetectors => this.imageFormatDetectors; - if (extension[0] == '.') - { - extension = extension[1..]; - } + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); - } + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - /// - /// For the specified mime type find the . - /// - /// The mime-type to discover - /// The if found; otherwise null - public IImageFormat FindFormatByMimeType(string mimeType) - => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - - /// - /// Sets a specific image encoder as the encoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The encoder to use, - public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(encoder, nameof(encoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); - } + /// + /// Registers a new format provider. + /// + /// The format to register as a known format. + public void AddImageFormat(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); - /// - /// Sets a specific image decoder as the decoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The decoder to use, - public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) + lock (HashLock) { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(decoder, nameof(decoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + if (!this.imageFormats.Contains(format)) + { + this.imageFormats.Add(format); + } } + } - /// - /// Removes all the registered image format detectors. - /// - public void ClearImageFormatDetectors() => this.imageFormatDetectors = new(); + /// + /// For the specified file extensions type find the e . + /// + /// The extension to discover + /// The if found otherwise null + public IImageFormat FindFormatByFileExtension(string extension) + { + Guard.NotNullOrWhiteSpace(extension, nameof(extension)); - /// - /// Adds a new detector for detecting mime types. - /// - /// The detector to add - public void AddImageFormatDetector(IImageFormatDetector detector) + if (extension[0] == '.') { - Guard.NotNull(detector, nameof(detector)); - this.imageFormatDetectors.Add(detector); - this.SetMaxHeaderSize(); + extension = extension[1..]; } - /// - /// For the specified mime type find the decoder. - /// - /// The format to discover - /// The if found otherwise null - public IImageDecoder FindDecoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); + return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + } - return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder) - ? decoder - : null; - } + /// + /// For the specified mime type find the . + /// + /// The mime-type to discover + /// The if found; otherwise null + public IImageFormat FindFormatByMimeType(string mimeType) + => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - /// - /// For the specified mime type find the encoder. - /// - /// The format to discover - /// The if found otherwise null - public IImageEncoder FindEncoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); + /// + /// Sets a specific image encoder as the encoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } - return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder) - ? encoder - : null; - } + /// + /// Sets a specific image decoder as the decoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The decoder to use, + public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(decoder, nameof(decoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + } - /// - /// Sets the max header size. - /// - private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); + /// + /// Removes all the registered image format detectors. + /// + public void ClearImageFormatDetectors() => this.imageFormatDetectors = new(); + + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(IImageFormatDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.imageFormatDetectors.Add(detector); + this.SetMaxHeaderSize(); } + + /// + /// For the specified mime type find the decoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageDecoder FindDecoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder) + ? decoder + : null; + } + + /// + /// For the specified mime type find the encoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageEncoder FindEncoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + + return this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder) + ? encoder + : null; + } + + /// + /// Sets the max header size. + /// + private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs index 82eb8324a2..91095a849e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -4,34 +4,33 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal unsafe partial struct Block8x8 { - internal unsafe partial struct Block8x8 - { - [FieldOffset(0)] - public Vector128 V0; - [FieldOffset(16)] - public Vector128 V1; - [FieldOffset(32)] - public Vector128 V2; - [FieldOffset(48)] - public Vector128 V3; - [FieldOffset(64)] - public Vector128 V4; - [FieldOffset(80)] - public Vector128 V5; - [FieldOffset(96)] - public Vector128 V6; - [FieldOffset(112)] - public Vector128 V7; + [FieldOffset(0)] + public Vector128 V0; + [FieldOffset(16)] + public Vector128 V1; + [FieldOffset(32)] + public Vector128 V2; + [FieldOffset(48)] + public Vector128 V3; + [FieldOffset(64)] + public Vector128 V4; + [FieldOffset(80)] + public Vector128 V5; + [FieldOffset(96)] + public Vector128 V6; + [FieldOffset(112)] + public Vector128 V7; - [FieldOffset(0)] - public Vector256 V01; - [FieldOffset(32)] - public Vector256 V23; - [FieldOffset(64)] - public Vector256 V45; - [FieldOffset(96)] - public Vector256 V67; - } + [FieldOffset(0)] + public Vector256 V01; + [FieldOffset(32)] + public Vector256 V23; + [FieldOffset(64)] + public Vector256 V45; + [FieldOffset(96)] + public Vector256 V67; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 8462cf64b8..325d6c26f5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,293 +9,292 @@ using System.Text; using SixLabors.ImageSharp.Common.Helpers; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// 8x8 matrix of coefficients. +/// +// ReSharper disable once InconsistentNaming +[StructLayout(LayoutKind.Explicit)] +internal unsafe partial struct Block8x8 { /// - /// 8x8 matrix of coefficients. + /// A number of scalar coefficients in a /// - // ReSharper disable once InconsistentNaming - [StructLayout(LayoutKind.Explicit)] - internal unsafe partial struct Block8x8 - { - /// - /// A number of scalar coefficients in a - /// - public const int Size = 64; + public const int Size = 64; #pragma warning disable IDE0051 // Remove unused private member - /// - /// A placeholder buffer so the actual struct occupies exactly 64 * 2 bytes. - /// - /// - /// This is not used directly in the code. - /// - [FieldOffset(0)] - private fixed short data[Size]; + /// + /// A placeholder buffer so the actual struct occupies exactly 64 * 2 bytes. + /// + /// + /// This is not used directly in the code. + /// + [FieldOffset(0)] + private fixed short data[Size]; #pragma warning restore IDE0051 - /// - /// Gets or sets a value at the given index - /// - /// The index - /// The value - public short this[int idx] + /// + /// Gets or sets a value at the given index + /// + /// The index + /// The value + public short this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - - ref short selfRef = ref Unsafe.As(ref this); - return Unsafe.Add(ref selfRef, idx); - } + DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - - ref short selfRef = ref Unsafe.As(ref this); - Unsafe.Add(ref selfRef, idx) = value; - } + ref short selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, idx); } - /// - /// Gets or sets a value in a row+column of the 8x8 block - /// - /// The x position index in the row - /// The column index - /// The value - public short this[int x, int y] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - get => this[(y * 8) + x]; - set => this[(y * 8) + x] = value; - } + DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - public static Block8x8 Load(Span data) - { - Unsafe.SkipInit(out Block8x8 result); - result.LoadFrom(data); - return result; + ref short selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, idx) = value; } + } - /// - /// Convert to - /// - public Block8x8F AsFloatBlock() - { - Block8x8F result = default; - result.LoadFrom(ref this); - return result; - } + /// + /// Gets or sets a value in a row+column of the 8x8 block + /// + /// The x position index in the row + /// The column index + /// The value + public short this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } - /// - /// Copy all elements to an array of . - /// - public short[] ToArray() - { - short[] result = new short[Size]; - this.CopyTo(result); - return result; - } + public static Block8x8 Load(Span data) + { + Unsafe.SkipInit(out Block8x8 result); + result.LoadFrom(data); + return result; + } - /// - /// Copy elements into 'destination' Span of values - /// - public void CopyTo(Span destination) - { - ref byte selfRef = ref Unsafe.As(ref this); - ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destination)); - Unsafe.CopyBlockUnaligned(ref destRef, ref selfRef, Size * sizeof(short)); - } + /// + /// Convert to + /// + public Block8x8F AsFloatBlock() + { + Block8x8F result = default; + result.LoadFrom(ref this); + return result; + } - /// - /// Copy elements into 'destination' Span of values - /// - public void CopyTo(Span destination) - { - for (int i = 0; i < Size; i++) - { - destination[i] = this[i]; - } - } + /// + /// Copy all elements to an array of . + /// + public short[] ToArray() + { + short[] result = new short[Size]; + this.CopyTo(result); + return result; + } + + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) + { + ref byte selfRef = ref Unsafe.As(ref this); + ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destination)); + Unsafe.CopyBlockUnaligned(ref destRef, ref selfRef, Size * sizeof(short)); + } - public static Block8x8 Load(ReadOnlySpan data) + /// + /// Copy elements into 'destination' Span of values + /// + public void CopyTo(Span destination) + { + for (int i = 0; i < Size; i++) { - Unsafe.SkipInit(out Block8x8 result); - result.LoadFrom(data); - return result; + destination[i] = this[i]; } + } - public void LoadFrom(ReadOnlySpan source) + public static Block8x8 Load(ReadOnlySpan data) + { + Unsafe.SkipInit(out Block8x8 result); + result.LoadFrom(data); + return result; + } + + public void LoadFrom(ReadOnlySpan source) + { + for (int i = 0; i < Size; i++) { - for (int i = 0; i < Size; i++) - { - this[i] = source[i]; - } + this[i] = source[i]; } + } - /// - /// Load raw 16bit integers from source. - /// - /// Source - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(Span source) - { - ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref byte destRef = ref Unsafe.As(ref this); + /// + /// Load raw 16bit integers from source. + /// + /// Source + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(Span source) + { + ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref byte destRef = ref Unsafe.As(ref this); - Unsafe.CopyBlockUnaligned(ref destRef, ref sourceRef, Size * sizeof(short)); - } + Unsafe.CopyBlockUnaligned(ref destRef, ref sourceRef, Size * sizeof(short)); + } - /// - /// Cast and copy -s from the beginning of 'source' span. - /// - public void LoadFrom(Span source) + /// + /// Cast and copy -s from the beginning of 'source' span. + /// + public void LoadFrom(Span source) + { + for (int i = 0; i < Size; i++) { - for (int i = 0; i < Size; i++) - { - this[i] = (short)source[i]; - } + this[i] = (short)source[i]; } + } - /// - public override string ToString() + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + for (int i = 0; i < Size; i++) { - var sb = new StringBuilder(); - sb.Append('['); - for (int i = 0; i < Size; i++) + sb.Append(this[i]); + if (i < Size - 1) { - sb.Append(this[i]); - if (i < Size - 1) - { - sb.Append(','); - } + sb.Append(','); } - - sb.Append(']'); - return sb.ToString(); } - /// - /// Returns index of the last non-zero element in given matrix. - /// - /// - /// Index of the last non-zero element. Returns -1 if all elements are equal to zero. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public nint GetLastNonZeroIndex() - { - if (Avx2.IsSupported) - { - const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + sb.Append(']'); + return sb.ToString(); + } - Vector256 zero16 = Vector256.Zero; + /// + /// Returns index of the last non-zero element in given matrix. + /// + /// + /// Index of the last non-zero element. Returns -1 if all elements are equal to zero. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public nint GetLastNonZeroIndex() + { + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); - ref Vector256 mcuStride = ref Unsafe.As>(ref this); + Vector256 zero16 = Vector256.Zero; - for (nint i = 3; i >= 0; i--) - { - int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i), zero16).AsByte()); - - if (areEqual != equalityMask) - { - // Each 2 bits represents comparison operation for each 2-byte element in input vectors - // LSB represents first element in the stride - // MSB represents last element in the stride - // lzcnt operation would calculate number of zero numbers at the end - - // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements - // So we need to invert it - int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); - - // As input number is represented by 2 bits in the mask, we need to divide lzcnt result by 2 - // to get the exact number of zero elements in the stride - int strideRelativeIndex = 15 - (lzcnt / 2); - return (i * 16) + strideRelativeIndex; - } - } + ref Vector256 mcuStride = ref Unsafe.As>(ref this); - return -1; - } - else + for (nint i = 3; i >= 0; i--) { - nint index = Size - 1; - ref short elemRef = ref Unsafe.As(ref this); + int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i), zero16).AsByte()); - while (index >= 0 && Unsafe.Add(ref elemRef, index) == 0) + if (areEqual != equalityMask) { - index--; + // Each 2 bits represents comparison operation for each 2-byte element in input vectors + // LSB represents first element in the stride + // MSB represents last element in the stride + // lzcnt operation would calculate number of zero numbers at the end + + // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements + // So we need to invert it + int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); + + // As input number is represented by 2 bits in the mask, we need to divide lzcnt result by 2 + // to get the exact number of zero elements in the stride + int strideRelativeIndex = 15 - (lzcnt / 2); + return (i * 16) + strideRelativeIndex; } - - return index; } - } - /// - /// Transpose the block inplace. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInplace() + return -1; + } + else { + nint index = Size - 1; ref short elemRef = ref Unsafe.As(ref this); - // row #0 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); - - // row #1 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); - - // row #2 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); - - // row #3 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); - - // row #4 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); - - // row #5 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); - - // row #6 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); - } - - /// - /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. - /// - public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) - { - long result = 0; - for (int i = 0; i < Size; i++) + while (index >= 0 && Unsafe.Add(ref elemRef, index) == 0) { - int d = a[i] - b[i]; - result += Math.Abs(d); + index--; } - return result; + return index; } } + + /// + /// Transpose the block inplace. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInplace() + { + ref short elemRef = ref Unsafe.As(ref this); + + // row #0 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); + + // row #1 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); + + // row #2 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); + + // row #3 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); + + // row #4 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); + + // row #5 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); + + // row #6 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); + } + + /// + /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. + /// + public static long TotalDifference(ref Block8x8 a, ref Block8x8 b) + { + long result = 0; + for (int i = 0; i < Size; i++) + { + int d = a[i] - b[i]; + result += Math.Abs(d); + } + + return result; + } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 4c40659261..d1cb3559b2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -1,155 +1,153 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; // -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal partial struct Block8x8F { - internal partial struct Block8x8F + /// + /// Level shift by +maximum/2, clip to [0, maximum] + /// + public void NormalizeColorsInPlace(float maximum) { - /// - /// Level shift by +maximum/2, clip to [0, maximum] - /// - public void NormalizeColorsInPlace(float maximum) - { - var CMin4 = new Vector4(0F); - var CMax4 = new Vector4(maximum); - var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + var CMin4 = new Vector4(0F); + var CMax4 = new Vector4(maximum); + var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); - this.V0L = Numerics.Clamp(this.V0L + COff4, CMin4, CMax4); - this.V0R = Numerics.Clamp(this.V0R + COff4, CMin4, CMax4); - this.V1L = Numerics.Clamp(this.V1L + COff4, CMin4, CMax4); - this.V1R = Numerics.Clamp(this.V1R + COff4, CMin4, CMax4); - this.V2L = Numerics.Clamp(this.V2L + COff4, CMin4, CMax4); - this.V2R = Numerics.Clamp(this.V2R + COff4, CMin4, CMax4); - this.V3L = Numerics.Clamp(this.V3L + COff4, CMin4, CMax4); - this.V3R = Numerics.Clamp(this.V3R + COff4, CMin4, CMax4); - this.V4L = Numerics.Clamp(this.V4L + COff4, CMin4, CMax4); - this.V4R = Numerics.Clamp(this.V4R + COff4, CMin4, CMax4); - this.V5L = Numerics.Clamp(this.V5L + COff4, CMin4, CMax4); - this.V5R = Numerics.Clamp(this.V5R + COff4, CMin4, CMax4); - this.V6L = Numerics.Clamp(this.V6L + COff4, CMin4, CMax4); - this.V6R = Numerics.Clamp(this.V6R + COff4, CMin4, CMax4); - this.V7L = Numerics.Clamp(this.V7L + COff4, CMin4, CMax4); - this.V7R = Numerics.Clamp(this.V7R + COff4, CMin4, CMax4); - } + this.V0L = Numerics.Clamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Numerics.Clamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Numerics.Clamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Numerics.Clamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Numerics.Clamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Numerics.Clamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Numerics.Clamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Numerics.Clamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Numerics.Clamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Numerics.Clamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Numerics.Clamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Numerics.Clamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Numerics.Clamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Numerics.Clamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Numerics.Clamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Numerics.Clamp(this.V7R + COff4, CMin4, CMax4); + } - /// - /// AVX2-only variant for executing and in one step. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector8(float maximum) - { - var off = new Vector(MathF.Ceiling(maximum / 2)); - var max = new Vector(maximum); + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void NormalizeColorsAndRoundInPlaceVector8(float maximum) + { + var off = new Vector(MathF.Ceiling(maximum / 2)); + var max = new Vector(maximum); + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); - ref Vector row0 = ref Unsafe.As>(ref this.V0L); - row0 = NormalizeAndRound(row0, off, max); - - ref Vector row1 = ref Unsafe.As>(ref this.V1L); - row1 = NormalizeAndRound(row1, off, max); - - ref Vector row2 = ref Unsafe.As>(ref this.V2L); - row2 = NormalizeAndRound(row2, off, max); - - ref Vector row3 = ref Unsafe.As>(ref this.V3L); - row3 = NormalizeAndRound(row3, off, max); - - ref Vector row4 = ref Unsafe.As>(ref this.V4L); - row4 = NormalizeAndRound(row4, off, max); - - ref Vector row5 = ref Unsafe.As>(ref this.V5L); - row5 = NormalizeAndRound(row5, off, max); - - ref Vector row6 = ref Unsafe.As>(ref this.V6L); - row6 = NormalizeAndRound(row6, off, max); - - ref Vector row7 = ref Unsafe.As>(ref this.V7L); - row7 = NormalizeAndRound(row7, off, max); - - } + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + + } - /// - /// Fill the block from 'source' doing short -> float conversion. - /// - public void LoadFromInt16Scalar(ref Block8x8 source) - { - ref short selfRef = ref Unsafe.As(ref source); + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFromInt16Scalar(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); - this.V0L.X = Unsafe.Add(ref selfRef, 0); - this.V0L.Y = Unsafe.Add(ref selfRef, 1); - this.V0L.Z = Unsafe.Add(ref selfRef, 2); - this.V0L.W = Unsafe.Add(ref selfRef, 3); - this.V0R.X = Unsafe.Add(ref selfRef, 4); - this.V0R.Y = Unsafe.Add(ref selfRef, 5); - this.V0R.Z = Unsafe.Add(ref selfRef, 6); - this.V0R.W = Unsafe.Add(ref selfRef, 7); + this.V0L.X = Unsafe.Add(ref selfRef, 0); + this.V0L.Y = Unsafe.Add(ref selfRef, 1); + this.V0L.Z = Unsafe.Add(ref selfRef, 2); + this.V0L.W = Unsafe.Add(ref selfRef, 3); + this.V0R.X = Unsafe.Add(ref selfRef, 4); + this.V0R.Y = Unsafe.Add(ref selfRef, 5); + this.V0R.Z = Unsafe.Add(ref selfRef, 6); + this.V0R.W = Unsafe.Add(ref selfRef, 7); - this.V1L.X = Unsafe.Add(ref selfRef, 8); - this.V1L.Y = Unsafe.Add(ref selfRef, 9); - this.V1L.Z = Unsafe.Add(ref selfRef, 10); - this.V1L.W = Unsafe.Add(ref selfRef, 11); - this.V1R.X = Unsafe.Add(ref selfRef, 12); - this.V1R.Y = Unsafe.Add(ref selfRef, 13); - this.V1R.Z = Unsafe.Add(ref selfRef, 14); - this.V1R.W = Unsafe.Add(ref selfRef, 15); + this.V1L.X = Unsafe.Add(ref selfRef, 8); + this.V1L.Y = Unsafe.Add(ref selfRef, 9); + this.V1L.Z = Unsafe.Add(ref selfRef, 10); + this.V1L.W = Unsafe.Add(ref selfRef, 11); + this.V1R.X = Unsafe.Add(ref selfRef, 12); + this.V1R.Y = Unsafe.Add(ref selfRef, 13); + this.V1R.Z = Unsafe.Add(ref selfRef, 14); + this.V1R.W = Unsafe.Add(ref selfRef, 15); - this.V2L.X = Unsafe.Add(ref selfRef, 16); - this.V2L.Y = Unsafe.Add(ref selfRef, 17); - this.V2L.Z = Unsafe.Add(ref selfRef, 18); - this.V2L.W = Unsafe.Add(ref selfRef, 19); - this.V2R.X = Unsafe.Add(ref selfRef, 20); - this.V2R.Y = Unsafe.Add(ref selfRef, 21); - this.V2R.Z = Unsafe.Add(ref selfRef, 22); - this.V2R.W = Unsafe.Add(ref selfRef, 23); + this.V2L.X = Unsafe.Add(ref selfRef, 16); + this.V2L.Y = Unsafe.Add(ref selfRef, 17); + this.V2L.Z = Unsafe.Add(ref selfRef, 18); + this.V2L.W = Unsafe.Add(ref selfRef, 19); + this.V2R.X = Unsafe.Add(ref selfRef, 20); + this.V2R.Y = Unsafe.Add(ref selfRef, 21); + this.V2R.Z = Unsafe.Add(ref selfRef, 22); + this.V2R.W = Unsafe.Add(ref selfRef, 23); - this.V3L.X = Unsafe.Add(ref selfRef, 24); - this.V3L.Y = Unsafe.Add(ref selfRef, 25); - this.V3L.Z = Unsafe.Add(ref selfRef, 26); - this.V3L.W = Unsafe.Add(ref selfRef, 27); - this.V3R.X = Unsafe.Add(ref selfRef, 28); - this.V3R.Y = Unsafe.Add(ref selfRef, 29); - this.V3R.Z = Unsafe.Add(ref selfRef, 30); - this.V3R.W = Unsafe.Add(ref selfRef, 31); + this.V3L.X = Unsafe.Add(ref selfRef, 24); + this.V3L.Y = Unsafe.Add(ref selfRef, 25); + this.V3L.Z = Unsafe.Add(ref selfRef, 26); + this.V3L.W = Unsafe.Add(ref selfRef, 27); + this.V3R.X = Unsafe.Add(ref selfRef, 28); + this.V3R.Y = Unsafe.Add(ref selfRef, 29); + this.V3R.Z = Unsafe.Add(ref selfRef, 30); + this.V3R.W = Unsafe.Add(ref selfRef, 31); - this.V4L.X = Unsafe.Add(ref selfRef, 32); - this.V4L.Y = Unsafe.Add(ref selfRef, 33); - this.V4L.Z = Unsafe.Add(ref selfRef, 34); - this.V4L.W = Unsafe.Add(ref selfRef, 35); - this.V4R.X = Unsafe.Add(ref selfRef, 36); - this.V4R.Y = Unsafe.Add(ref selfRef, 37); - this.V4R.Z = Unsafe.Add(ref selfRef, 38); - this.V4R.W = Unsafe.Add(ref selfRef, 39); + this.V4L.X = Unsafe.Add(ref selfRef, 32); + this.V4L.Y = Unsafe.Add(ref selfRef, 33); + this.V4L.Z = Unsafe.Add(ref selfRef, 34); + this.V4L.W = Unsafe.Add(ref selfRef, 35); + this.V4R.X = Unsafe.Add(ref selfRef, 36); + this.V4R.Y = Unsafe.Add(ref selfRef, 37); + this.V4R.Z = Unsafe.Add(ref selfRef, 38); + this.V4R.W = Unsafe.Add(ref selfRef, 39); - this.V5L.X = Unsafe.Add(ref selfRef, 40); - this.V5L.Y = Unsafe.Add(ref selfRef, 41); - this.V5L.Z = Unsafe.Add(ref selfRef, 42); - this.V5L.W = Unsafe.Add(ref selfRef, 43); - this.V5R.X = Unsafe.Add(ref selfRef, 44); - this.V5R.Y = Unsafe.Add(ref selfRef, 45); - this.V5R.Z = Unsafe.Add(ref selfRef, 46); - this.V5R.W = Unsafe.Add(ref selfRef, 47); + this.V5L.X = Unsafe.Add(ref selfRef, 40); + this.V5L.Y = Unsafe.Add(ref selfRef, 41); + this.V5L.Z = Unsafe.Add(ref selfRef, 42); + this.V5L.W = Unsafe.Add(ref selfRef, 43); + this.V5R.X = Unsafe.Add(ref selfRef, 44); + this.V5R.Y = Unsafe.Add(ref selfRef, 45); + this.V5R.Z = Unsafe.Add(ref selfRef, 46); + this.V5R.W = Unsafe.Add(ref selfRef, 47); - this.V6L.X = Unsafe.Add(ref selfRef, 48); - this.V6L.Y = Unsafe.Add(ref selfRef, 49); - this.V6L.Z = Unsafe.Add(ref selfRef, 50); - this.V6L.W = Unsafe.Add(ref selfRef, 51); - this.V6R.X = Unsafe.Add(ref selfRef, 52); - this.V6R.Y = Unsafe.Add(ref selfRef, 53); - this.V6R.Z = Unsafe.Add(ref selfRef, 54); - this.V6R.W = Unsafe.Add(ref selfRef, 55); + this.V6L.X = Unsafe.Add(ref selfRef, 48); + this.V6L.Y = Unsafe.Add(ref selfRef, 49); + this.V6L.Z = Unsafe.Add(ref selfRef, 50); + this.V6L.W = Unsafe.Add(ref selfRef, 51); + this.V6R.X = Unsafe.Add(ref selfRef, 52); + this.V6R.Y = Unsafe.Add(ref selfRef, 53); + this.V6R.Z = Unsafe.Add(ref selfRef, 54); + this.V6R.W = Unsafe.Add(ref selfRef, 55); - this.V7L.X = Unsafe.Add(ref selfRef, 56); - this.V7L.Y = Unsafe.Add(ref selfRef, 57); - this.V7L.Z = Unsafe.Add(ref selfRef, 58); - this.V7L.W = Unsafe.Add(ref selfRef, 59); - this.V7R.X = Unsafe.Add(ref selfRef, 60); - this.V7R.Y = Unsafe.Add(ref selfRef, 61); - this.V7R.Z = Unsafe.Add(ref selfRef, 62); - this.V7R.W = Unsafe.Add(ref selfRef, 63); - } - } + this.V7L.X = Unsafe.Add(ref selfRef, 56); + this.V7L.Y = Unsafe.Add(ref selfRef, 57); + this.V7L.Z = Unsafe.Add(ref selfRef, 58); + this.V7L.W = Unsafe.Add(ref selfRef, 59); + this.V7R.X = Unsafe.Add(ref selfRef, 60); + this.V7R.Y = Unsafe.Add(ref selfRef, 61); + this.V7R.Z = Unsafe.Add(ref selfRef, 62); + this.V7R.W = Unsafe.Add(ref selfRef, 63); + } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index 7d5a347a12..aa211ea22b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -11,7 +11,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,87 +18,86 @@ using System.Runtime.CompilerServices; <# char[] coordz = {'X', 'Y', 'Z', 'W'}; #> -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal partial struct Block8x8F { - internal partial struct Block8x8F + /// + /// Level shift by +maximum/2, clip to [0, maximum] + /// + public void NormalizeColorsInPlace(float maximum) { - /// - /// Level shift by +maximum/2, clip to [0, maximum] - /// - public void NormalizeColorsInPlace(float maximum) - { - var CMin4 = new Vector4(0F); - var CMax4 = new Vector4(maximum); - var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); + var CMin4 = new Vector4(0F); + var CMax4 = new Vector4(maximum); + var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); - <# + <# - PushIndent(" "); + PushIndent(" "); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) + { + for (int j = 0; j < 2; j++) { - for (int j = 0; j < 2; j++) - { - char side = j == 0 ? 'L' : 'R'; - Write($"this.V{i}{side} = Numerics.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); - } + char side = j == 0 ? 'L' : 'R'; + Write($"this.V{i}{side} = Numerics.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } - PopIndent(); - #> } + PopIndent(); + #> + } - /// - /// AVX2-only variant for executing and in one step. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInPlaceVector8(float maximum) + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void NormalizeColorsAndRoundInPlaceVector8(float maximum) + { + var off = new Vector(MathF.Ceiling(maximum / 2)); + var max = new Vector(maximum); + <# + + for (int i = 0; i < 8; i++) { - var off = new Vector(MathF.Ceiling(maximum / 2)); - var max = new Vector(maximum); - <# + #> - for (int i = 0; i < 8; i++) - { - #> + ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L); + row<#=i#> = NormalizeAndRound(row<#=i#>, off, max); + <# + } + #> - ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L); - row<#=i#> = NormalizeAndRound(row<#=i#>, off, max); - <# - } - #> + } - } + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFromInt16Scalar(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); - /// - /// Fill the block from 'source' doing short -> float conversion. - /// - public void LoadFromInt16Scalar(ref Block8x8 source) + <# + PushIndent(" "); + for (int j = 0; j < 8; j++) { - ref short selfRef = ref Unsafe.As(ref source); - - <# - PushIndent(" "); - for (int j = 0; j < 8; j++) + for (int i = 0; i < 8; i++) { - for (int i = 0; i < 8; i++) - { - char destCoord = coordz[i % 4]; - char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; + char destCoord = coordz[i % 4]; + char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; - if(j > 0 && i == 0){ - WriteLine(""); - } + if(j > 0 && i == 0){ + WriteLine(""); + } - char srcCoord = coordz[j % 4]; - char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; + char srcCoord = coordz[j % 4]; + char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; - var expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n"; - Write(expression); + var expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n"; + Write(expression); - } } - PopIndent(); - #> } - } + PopIndent(); + #> + } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs index 4c19510b7e..78c82f11a0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs @@ -1,146 +1,144 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal partial struct Block8x8F { - internal partial struct Block8x8F + /// + /// A number of rows of 8 scalar coefficients each in + /// + public const int RowCount = 8; + + [FieldOffset(0)] + public Vector256 V0; + [FieldOffset(32)] + public Vector256 V1; + [FieldOffset(64)] + public Vector256 V2; + [FieldOffset(96)] + public Vector256 V3; + [FieldOffset(128)] + public Vector256 V4; + [FieldOffset(160)] + public Vector256 V5; + [FieldOffset(192)] + public Vector256 V6; + [FieldOffset(224)] + public Vector256 V7; + + private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) { - /// - /// A number of rows of 8 scalar coefficients each in - /// - public const int RowCount = 8; - - [FieldOffset(0)] - public Vector256 V0; - [FieldOffset(32)] - public Vector256 V1; - [FieldOffset(64)] - public Vector256 V2; - [FieldOffset(96)] - public Vector256 V3; - [FieldOffset(128)] - public Vector256 V4; - [FieldOffset(160)] - public Vector256 V5; - [FieldOffset(192)] - public Vector256 V6; - [FieldOffset(224)] - public Vector256 V7; - - private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) - { - DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); + DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); - ref Vector256 aBase = ref a.V0; - ref Vector256 bBase = ref b.V0; + ref Vector256 aBase = ref a.V0; + ref Vector256 bBase = ref b.V0; - ref Vector256 destRef = ref dest.V01; - var multiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7); + ref Vector256 destRef = ref dest.V01; + var multiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7); - for (nint i = 0; i < 8; i += 2) - { - Vector256 row0 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); - Vector256 row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); + for (nint i = 0; i < 8; i += 2) + { + Vector256 row0 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); + Vector256 row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); - Vector256 row = Avx2.PackSignedSaturate(row0, row1); - row = Avx2.PermuteVar8x32(row.AsInt32(), multiplyIntoInt16ShuffleMask).AsInt16(); + Vector256 row = Avx2.PackSignedSaturate(row0, row1); + row = Avx2.PermuteVar8x32(row.AsInt32(), multiplyIntoInt16ShuffleMask).AsInt16(); - Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row; - } + Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row; } + } - private static void MultiplyIntoInt16_Sse2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) - { - DebugGuard.IsTrue(Sse2.IsSupported, "Sse2 support is required to run this operation!"); + private static void MultiplyIntoInt16_Sse2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest) + { + DebugGuard.IsTrue(Sse2.IsSupported, "Sse2 support is required to run this operation!"); - ref Vector128 aBase = ref Unsafe.As>(ref a); - ref Vector128 bBase = ref Unsafe.As>(ref b); + ref Vector128 aBase = ref Unsafe.As>(ref a); + ref Vector128 bBase = ref Unsafe.As>(ref b); - ref Vector128 destBase = ref Unsafe.As>(ref dest); + ref Vector128 destBase = ref Unsafe.As>(ref dest); - for (int i = 0; i < 16; i += 2) - { - Vector128 left = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); - Vector128 right = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); + for (int i = 0; i < 16; i += 2) + { + Vector128 left = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0))); + Vector128 right = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1))); - Vector128 row = Sse2.PackSignedSaturate(left, right); - Unsafe.Add(ref destBase, (IntPtr)((uint)i / 2)) = row; - } + Vector128 row = Sse2.PackSignedSaturate(left, right); + Unsafe.Add(ref destBase, (IntPtr)((uint)i / 2)) = row; } + } - private void TransposeInplace_Avx() - { - // https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536 - Vector256 r0 = Avx.InsertVector128( - this.V0, - Unsafe.As>(ref this.V4L), - 1); - - Vector256 r1 = Avx.InsertVector128( - this.V1, - Unsafe.As>(ref this.V5L), - 1); - - Vector256 r2 = Avx.InsertVector128( - this.V2, - Unsafe.As>(ref this.V6L), - 1); - - Vector256 r3 = Avx.InsertVector128( - this.V3, - Unsafe.As>(ref this.V7L), - 1); - - Vector256 r4 = Avx.InsertVector128( - Unsafe.As>(ref this.V0R).ToVector256(), - Unsafe.As>(ref this.V4R), - 1); - - Vector256 r5 = Avx.InsertVector128( - Unsafe.As>(ref this.V1R).ToVector256(), - Unsafe.As>(ref this.V5R), - 1); - - Vector256 r6 = Avx.InsertVector128( - Unsafe.As>(ref this.V2R).ToVector256(), - Unsafe.As>(ref this.V6R), - 1); - - Vector256 r7 = Avx.InsertVector128( - Unsafe.As>(ref this.V3R).ToVector256(), - Unsafe.As>(ref this.V7R), - 1); - - Vector256 t0 = Avx.UnpackLow(r0, r1); - Vector256 t2 = Avx.UnpackLow(r2, r3); - Vector256 v = Avx.Shuffle(t0, t2, 0x4E); - this.V0 = Avx.Blend(t0, v, 0xCC); - this.V1 = Avx.Blend(t2, v, 0x33); - - Vector256 t4 = Avx.UnpackLow(r4, r5); - Vector256 t6 = Avx.UnpackLow(r6, r7); - v = Avx.Shuffle(t4, t6, 0x4E); - this.V4 = Avx.Blend(t4, v, 0xCC); - this.V5 = Avx.Blend(t6, v, 0x33); - - Vector256 t1 = Avx.UnpackHigh(r0, r1); - Vector256 t3 = Avx.UnpackHigh(r2, r3); - v = Avx.Shuffle(t1, t3, 0x4E); - this.V2 = Avx.Blend(t1, v, 0xCC); - this.V3 = Avx.Blend(t3, v, 0x33); - - Vector256 t5 = Avx.UnpackHigh(r4, r5); - Vector256 t7 = Avx.UnpackHigh(r6, r7); - v = Avx.Shuffle(t5, t7, 0x4E); - this.V6 = Avx.Blend(t5, v, 0xCC); - this.V7 = Avx.Blend(t7, v, 0x33); - } + private void TransposeInplace_Avx() + { + // https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536 + Vector256 r0 = Avx.InsertVector128( + this.V0, + Unsafe.As>(ref this.V4L), + 1); + + Vector256 r1 = Avx.InsertVector128( + this.V1, + Unsafe.As>(ref this.V5L), + 1); + + Vector256 r2 = Avx.InsertVector128( + this.V2, + Unsafe.As>(ref this.V6L), + 1); + + Vector256 r3 = Avx.InsertVector128( + this.V3, + Unsafe.As>(ref this.V7L), + 1); + + Vector256 r4 = Avx.InsertVector128( + Unsafe.As>(ref this.V0R).ToVector256(), + Unsafe.As>(ref this.V4R), + 1); + + Vector256 r5 = Avx.InsertVector128( + Unsafe.As>(ref this.V1R).ToVector256(), + Unsafe.As>(ref this.V5R), + 1); + + Vector256 r6 = Avx.InsertVector128( + Unsafe.As>(ref this.V2R).ToVector256(), + Unsafe.As>(ref this.V6R), + 1); + + Vector256 r7 = Avx.InsertVector128( + Unsafe.As>(ref this.V3R).ToVector256(), + Unsafe.As>(ref this.V7R), + 1); + + Vector256 t0 = Avx.UnpackLow(r0, r1); + Vector256 t2 = Avx.UnpackLow(r2, r3); + Vector256 v = Avx.Shuffle(t0, t2, 0x4E); + this.V0 = Avx.Blend(t0, v, 0xCC); + this.V1 = Avx.Blend(t2, v, 0x33); + + Vector256 t4 = Avx.UnpackLow(r4, r5); + Vector256 t6 = Avx.UnpackLow(r6, r7); + v = Avx.Shuffle(t4, t6, 0x4E); + this.V4 = Avx.Blend(t4, v, 0xCC); + this.V5 = Avx.Blend(t6, v, 0x33); + + Vector256 t1 = Avx.UnpackHigh(r0, r1); + Vector256 t3 = Avx.UnpackHigh(r2, r3); + v = Avx.Shuffle(t1, t3, 0x4E); + this.V2 = Avx.Blend(t1, v, 0xCC); + this.V3 = Avx.Blend(t3, v, 0x33); + + Vector256 t5 = Avx.UnpackHigh(r4, r5); + Vector256 t7 = Avx.UnpackHigh(r6, r7); + v = Avx.Shuffle(t5, t7, 0x4E); + this.V6 = Avx.Blend(t5, v, 0xCC); + this.V7 = Avx.Blend(t7, v, 0x33); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs index e50175ffa0..813d9c37b7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopy.cs @@ -6,154 +6,153 @@ // ReSharper disable UseObjectOrCollectionInitializer // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal partial struct Block8x8F { - internal partial struct Block8x8F - { - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => - CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyFrom(ref float areaOrigin, int areaStride) => + CopyFrom1x1Scale(ref Unsafe.As(ref areaOrigin), ref Unsafe.As(ref this), areaStride); - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) { - if (horizontalScale == 1 && verticalScale == 1) - { - CopyTo1x1Scale(ref Unsafe.As(ref this), ref Unsafe.As(ref areaOrigin), areaStride); - return; - } - - if (horizontalScale == 2 && verticalScale == 2) - { - this.CopyTo2x2Scale(ref areaOrigin, areaStride); - return; - } + CopyTo1x1Scale(ref Unsafe.As(ref this), ref Unsafe.As(ref areaOrigin), areaStride); + return; + } - // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + if (horizontalScale == 2 && verticalScale == 2) + { + this.CopyTo2x2Scale(ref areaOrigin, areaStride); + return; } - private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) + // TODO: Optimize: implement all cases with scale-specific, loopless code! + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); + } + + private void CopyTo2x2Scale(ref float areaOrigin, int areaStride) + { + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = (int)((uint)areaStride / 2); + + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride); + WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride) { - ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); - int destStride = (int)((uint)areaStride / 2); - - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 0, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 1, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 2, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 3, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 4, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 5, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 6, destStride); - WidenCopyRowImpl2x2(ref this.V0L, ref destBase, 7, destStride); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void WidenCopyRowImpl2x2(ref Vector4 selfBase, ref Vector2 destBase, nint row, nint destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - nint offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - var xyLeft = new Vector4(sLeft.X); - xyLeft.Z = sLeft.Y; - xyLeft.W = sLeft.Y; - - var zwLeft = new Vector4(sLeft.Z); - zwLeft.Z = sLeft.W; - zwLeft.W = sLeft.W; - - var xyRight = new Vector4(sRight.X); - xyRight.Z = sRight.Y; - xyRight.W = sRight.Y; - - var zwRight = new Vector4(sRight.Z); - zwRight.Z = sRight.W; - zwRight.W = sRight.W; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } + ref Vector4 sLeft = ref Unsafe.Add(ref selfBase, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + nint offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X); + xyLeft.Z = sLeft.Y; + xyLeft.W = sLeft.Y; + + var zwLeft = new Vector4(sLeft.Z); + zwLeft.Z = sLeft.W; + zwLeft.W = sLeft.W; + + var xyRight = new Vector4(sRight.X); + xyRight.Z = sRight.Y; + xyRight.W = sRight.Y; + + var zwRight = new Vector4(sRight.Z); + zwRight.Z = sRight.W; + zwRight.W = sRight.W; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; } + } - [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + [MethodImpl(InliningOptions.ColdPath)] + private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 8; y++) { - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; + int yy = y * verticalScale; + int y8 = y * 8; - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; - float value = this[y8 + x]; - nint baseIdx = (yy * areaStride) + xx; + float value = this[y8 + x]; + nint baseIdx = (yy * areaStride) + xx; - for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride) + for (nint i = 0; i < verticalScale; i++, baseIdx += areaStride) + { + for (nint j = 0; j < horizontalScale; j++) { - for (nint j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, baseIdx + j) = value; - } + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; } } } } + } - private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride) + private static void CopyTo1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row) { - int destStride = areaStride * sizeof(float); - - CopyRowImpl(ref origin, ref dest, destStride, 0); - CopyRowImpl(ref origin, ref dest, destStride, 1); - CopyRowImpl(ref origin, ref dest, destStride, 2); - CopyRowImpl(ref origin, ref dest, destStride, 3); - CopyRowImpl(ref origin, ref dest, destStride, 4); - CopyRowImpl(ref origin, ref dest, destStride, 5); - CopyRowImpl(ref origin, ref dest, destStride, 6); - CopyRowImpl(ref origin, ref dest, destStride, 7); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void CopyRowImpl(ref byte origin, ref byte dest, int destStride, int row) - { - origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float)); - dest = ref Unsafe.Add(ref dest, row * destStride); - Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); - } + origin = ref Unsafe.Add(ref origin, row * 8 * sizeof(float)); + dest = ref Unsafe.Add(ref dest, row * destStride); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); } + } - private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride) + private static void CopyFrom1x1Scale(ref byte origin, ref byte dest, int areaStride) + { + int destStride = areaStride * sizeof(float); + + CopyRowImpl(ref origin, ref dest, destStride, 0); + CopyRowImpl(ref origin, ref dest, destStride, 1); + CopyRowImpl(ref origin, ref dest, destStride, 2); + CopyRowImpl(ref origin, ref dest, destStride, 3); + CopyRowImpl(ref origin, ref dest, destStride, 4); + CopyRowImpl(ref origin, ref dest, destStride, 5); + CopyRowImpl(ref origin, ref dest, destStride, 6); + CopyRowImpl(ref origin, ref dest, destStride, 7); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row) { - int destStride = areaStride * sizeof(float); - - CopyRowImpl(ref origin, ref dest, destStride, 0); - CopyRowImpl(ref origin, ref dest, destStride, 1); - CopyRowImpl(ref origin, ref dest, destStride, 2); - CopyRowImpl(ref origin, ref dest, destStride, 3); - CopyRowImpl(ref origin, ref dest, destStride, 4); - CopyRowImpl(ref origin, ref dest, destStride, 5); - CopyRowImpl(ref origin, ref dest, destStride, 6); - CopyRowImpl(ref origin, ref dest, destStride, 7); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void CopyRowImpl(ref byte origin, ref byte dest, int sourceStride, int row) - { - origin = ref Unsafe.Add(ref origin, row * sourceStride); - dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float)); - Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); - } + origin = ref Unsafe.Add(ref origin, row * sourceStride); + dest = ref Unsafe.Add(ref dest, row * 8 * sizeof(float)); + Unsafe.CopyBlock(ref dest, ref origin, 8 * sizeof(float)); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 16d95f99f6..3023a01c9c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,438 +10,425 @@ using SixLabors.ImageSharp.Common.Helpers; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// 8x8 matrix of coefficients. +/// +[StructLayout(LayoutKind.Explicit)] +internal partial struct Block8x8F : IEquatable { /// - /// 8x8 matrix of coefficients. + /// A number of scalar coefficients in a /// - [StructLayout(LayoutKind.Explicit)] - internal partial struct Block8x8F : IEquatable - { - /// - /// A number of scalar coefficients in a - /// - public const int Size = 64; + public const int Size = 64; #pragma warning disable SA1600 // ElementsMustBeDocumented - [FieldOffset(0)] - public Vector4 V0L; - [FieldOffset(16)] - public Vector4 V0R; - - [FieldOffset(32)] - public Vector4 V1L; - [FieldOffset(48)] - public Vector4 V1R; - - [FieldOffset(64)] - public Vector4 V2L; - [FieldOffset(80)] - public Vector4 V2R; - - [FieldOffset(96)] - public Vector4 V3L; - [FieldOffset(112)] - public Vector4 V3R; - - [FieldOffset(128)] - public Vector4 V4L; - [FieldOffset(144)] - public Vector4 V4R; - - [FieldOffset(160)] - public Vector4 V5L; - [FieldOffset(176)] - public Vector4 V5R; - - [FieldOffset(192)] - public Vector4 V6L; - [FieldOffset(208)] - public Vector4 V6R; - - [FieldOffset(224)] - public Vector4 V7L; - [FieldOffset(240)] - public Vector4 V7R; + [FieldOffset(0)] + public Vector4 V0L; + [FieldOffset(16)] + public Vector4 V0R; + + [FieldOffset(32)] + public Vector4 V1L; + [FieldOffset(48)] + public Vector4 V1R; + + [FieldOffset(64)] + public Vector4 V2L; + [FieldOffset(80)] + public Vector4 V2R; + + [FieldOffset(96)] + public Vector4 V3L; + [FieldOffset(112)] + public Vector4 V3R; + + [FieldOffset(128)] + public Vector4 V4L; + [FieldOffset(144)] + public Vector4 V4R; + + [FieldOffset(160)] + public Vector4 V5L; + [FieldOffset(176)] + public Vector4 V5R; + + [FieldOffset(192)] + public Vector4 V6L; + [FieldOffset(208)] + public Vector4 V6R; + + [FieldOffset(224)] + public Vector4 V7L; + [FieldOffset(240)] + public Vector4 V7R; #pragma warning restore SA1600 // ElementsMustBeDocumented - /// - /// Get/Set scalar elements at a given index - /// - /// The index - /// The float value at the specified index - public float this[int idx] + /// + /// Get/Set scalar elements at a given index + /// + /// The index + /// The float value at the specified index + public float this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - ref float selfRef = ref Unsafe.As(ref this); - return Unsafe.Add(ref selfRef, (nint)(uint)idx); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); - ref float selfRef = ref Unsafe.As(ref this); - Unsafe.Add(ref selfRef, (nint)(uint)idx) = value; - } + DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); + ref float selfRef = ref Unsafe.As(ref this); + return Unsafe.Add(ref selfRef, (nint)(uint)idx); } - public float this[int x, int y] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - get => this[(y * 8) + x]; - set => this[(y * 8) + x] = value; + DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx)); + ref float selfRef = ref Unsafe.As(ref this); + Unsafe.Add(ref selfRef, (nint)(uint)idx) = value; } + } - public static Block8x8F Load(Span data) - { - Block8x8F result = default; - result.LoadFrom(data); - return result; - } + public float this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } - /// - /// Load raw 32bit floating point data from source. - /// - /// Source - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(Span source) - { - ref byte s = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref byte d = ref Unsafe.As(ref this); + public static Block8x8F Load(Span data) + { + Block8x8F result = default; + result.LoadFrom(data); + return result; + } - Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); - } + /// + /// Load raw 32bit floating point data from source. + /// + /// Source + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(Span source) + { + ref byte s = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref byte d = ref Unsafe.As(ref this); + + Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); + } - /// - /// Load raw 32bit floating point data from source - /// - /// Source - public unsafe void LoadFrom(Span source) + /// + /// Load raw 32bit floating point data from source + /// + /// Source + public unsafe void LoadFrom(Span source) + { + fixed (Vector4* ptr = &this.V0L) { - fixed (Vector4* ptr = &this.V0L) + float* fp = (float*)ptr; + for (int i = 0; i < Size; i++) { - float* fp = (float*)ptr; - for (int i = 0; i < Size; i++) - { - fp[i] = source[i]; - } + fp[i] = source[i]; } } + } - /// - /// Copy raw 32bit floating point data to dest - /// - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void ScaledCopyTo(float[] dest) + /// + /// Copy raw 32bit floating point data to dest + /// + /// Destination + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe void ScaledCopyTo(float[] dest) + { + fixed (void* ptr = &this.V0L) { - fixed (void* ptr = &this.V0L) - { - Marshal.Copy((IntPtr)ptr, dest, 0, Size); - } + Marshal.Copy((IntPtr)ptr, dest, 0, Size); } + } - public float[] ToArray() + public float[] ToArray() + { + float[] result = new float[Size]; + this.ScaledCopyTo(result); + return result; + } + + /// + /// Multiply all elements of the block. + /// + /// The value to multiply by. + [MethodImpl(InliningOptions.ShortMethod)] + public void MultiplyInPlace(float value) + { + if (Avx.IsSupported) { - float[] result = new float[Size]; - this.ScaledCopyTo(result); - return result; + Vector256 valueVec = Vector256.Create(value); + this.V0 = Avx.Multiply(this.V0, valueVec); + this.V1 = Avx.Multiply(this.V1, valueVec); + this.V2 = Avx.Multiply(this.V2, valueVec); + this.V3 = Avx.Multiply(this.V3, valueVec); + this.V4 = Avx.Multiply(this.V4, valueVec); + this.V5 = Avx.Multiply(this.V5, valueVec); + this.V6 = Avx.Multiply(this.V6, valueVec); + this.V7 = Avx.Multiply(this.V7, valueVec); } - - /// - /// Multiply all elements of the block. - /// - /// The value to multiply by. - [MethodImpl(InliningOptions.ShortMethod)] - public void MultiplyInPlace(float value) + else { - if (Avx.IsSupported) - { - Vector256 valueVec = Vector256.Create(value); - this.V0 = Avx.Multiply(this.V0, valueVec); - this.V1 = Avx.Multiply(this.V1, valueVec); - this.V2 = Avx.Multiply(this.V2, valueVec); - this.V3 = Avx.Multiply(this.V3, valueVec); - this.V4 = Avx.Multiply(this.V4, valueVec); - this.V5 = Avx.Multiply(this.V5, valueVec); - this.V6 = Avx.Multiply(this.V6, valueVec); - this.V7 = Avx.Multiply(this.V7, valueVec); - } - else - { - Vector4 valueVec = new(value); - this.V0L *= valueVec; - this.V0R *= valueVec; - this.V1L *= valueVec; - this.V1R *= valueVec; - this.V2L *= valueVec; - this.V2R *= valueVec; - this.V3L *= valueVec; - this.V3R *= valueVec; - this.V4L *= valueVec; - this.V4R *= valueVec; - this.V5L *= valueVec; - this.V5R *= valueVec; - this.V6L *= valueVec; - this.V6R *= valueVec; - this.V7L *= valueVec; - this.V7R *= valueVec; - } + Vector4 valueVec = new(value); + this.V0L *= valueVec; + this.V0R *= valueVec; + this.V1L *= valueVec; + this.V1R *= valueVec; + this.V2L *= valueVec; + this.V2R *= valueVec; + this.V3L *= valueVec; + this.V3R *= valueVec; + this.V4L *= valueVec; + this.V4R *= valueVec; + this.V5L *= valueVec; + this.V5R *= valueVec; + this.V6L *= valueVec; + this.V6R *= valueVec; + this.V7L *= valueVec; + this.V7R *= valueVec; } + } - /// - /// Multiply all elements of the block by the corresponding elements of 'other'. - /// - /// The other block. - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void MultiplyInPlace(ref Block8x8F other) + /// + /// Multiply all elements of the block by the corresponding elements of 'other'. + /// + /// The other block. + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe void MultiplyInPlace(ref Block8x8F other) + { + if (Avx.IsSupported) { - if (Avx.IsSupported) - { - this.V0 = Avx.Multiply(this.V0, other.V0); - this.V1 = Avx.Multiply(this.V1, other.V1); - this.V2 = Avx.Multiply(this.V2, other.V2); - this.V3 = Avx.Multiply(this.V3, other.V3); - this.V4 = Avx.Multiply(this.V4, other.V4); - this.V5 = Avx.Multiply(this.V5, other.V5); - this.V6 = Avx.Multiply(this.V6, other.V6); - this.V7 = Avx.Multiply(this.V7, other.V7); - } - else - { - this.V0L *= other.V0L; - this.V0R *= other.V0R; - this.V1L *= other.V1L; - this.V1R *= other.V1R; - this.V2L *= other.V2L; - this.V2R *= other.V2R; - this.V3L *= other.V3L; - this.V3R *= other.V3R; - this.V4L *= other.V4L; - this.V4R *= other.V4R; - this.V5L *= other.V5L; - this.V5R *= other.V5R; - this.V6L *= other.V6L; - this.V6R *= other.V6R; - this.V7L *= other.V7L; - this.V7R *= other.V7R; - } + this.V0 = Avx.Multiply(this.V0, other.V0); + this.V1 = Avx.Multiply(this.V1, other.V1); + this.V2 = Avx.Multiply(this.V2, other.V2); + this.V3 = Avx.Multiply(this.V3, other.V3); + this.V4 = Avx.Multiply(this.V4, other.V4); + this.V5 = Avx.Multiply(this.V5, other.V5); + this.V6 = Avx.Multiply(this.V6, other.V6); + this.V7 = Avx.Multiply(this.V7, other.V7); } - - /// - /// Adds a vector to all elements of the block. - /// - /// The added vector. - [MethodImpl(InliningOptions.ShortMethod)] - public void AddInPlace(float value) + else { - if (Avx.IsSupported) - { - Vector256 valueVec = Vector256.Create(value); - this.V0 = Avx.Add(this.V0, valueVec); - this.V1 = Avx.Add(this.V1, valueVec); - this.V2 = Avx.Add(this.V2, valueVec); - this.V3 = Avx.Add(this.V3, valueVec); - this.V4 = Avx.Add(this.V4, valueVec); - this.V5 = Avx.Add(this.V5, valueVec); - this.V6 = Avx.Add(this.V6, valueVec); - this.V7 = Avx.Add(this.V7, valueVec); - } - else - { - Vector4 valueVec = new(value); - this.V0L += valueVec; - this.V0R += valueVec; - this.V1L += valueVec; - this.V1R += valueVec; - this.V2L += valueVec; - this.V2R += valueVec; - this.V3L += valueVec; - this.V3R += valueVec; - this.V4L += valueVec; - this.V4R += valueVec; - this.V5L += valueVec; - this.V5R += valueVec; - this.V6L += valueVec; - this.V6R += valueVec; - this.V7L += valueVec; - this.V7R += valueVec; - } + this.V0L *= other.V0L; + this.V0R *= other.V0R; + this.V1L *= other.V1L; + this.V1R *= other.V1R; + this.V2L *= other.V2L; + this.V2R *= other.V2R; + this.V3L *= other.V3L; + this.V3R *= other.V3R; + this.V4L *= other.V4L; + this.V4R *= other.V4R; + this.V5L *= other.V5L; + this.V5R *= other.V5R; + this.V6L *= other.V6L; + this.V6R *= other.V6R; + this.V7L *= other.V7L; + this.V7R *= other.V7R; } + } - /// - /// Quantize input block, transpose, apply zig-zag ordering and store as . - /// - /// Source block. - /// Destination block. - /// The quantization table. - public static void Quantize(ref Block8x8F block, ref Block8x8 dest, ref Block8x8F qt) + /// + /// Adds a vector to all elements of the block. + /// + /// The added vector. + [MethodImpl(InliningOptions.ShortMethod)] + public void AddInPlace(float value) + { + if (Avx.IsSupported) { - if (Avx2.IsSupported) - { - MultiplyIntoInt16_Avx2(ref block, ref qt, ref dest); - ZigZag.ApplyTransposingZigZagOrderingAvx2(ref dest); - } - else if (Ssse3.IsSupported) - { - MultiplyIntoInt16_Sse2(ref block, ref qt, ref dest); - ZigZag.ApplyTransposingZigZagOrderingSsse3(ref dest); - } - else - { - for (int i = 0; i < Size; i++) - { - int idx = ZigZag.TransposingOrder[i]; - float quantizedVal = block[idx] * qt[idx]; - quantizedVal += quantizedVal < 0 ? -0.5f : 0.5f; - dest[i] = (short)quantizedVal; - } - } + Vector256 valueVec = Vector256.Create(value); + this.V0 = Avx.Add(this.V0, valueVec); + this.V1 = Avx.Add(this.V1, valueVec); + this.V2 = Avx.Add(this.V2, valueVec); + this.V3 = Avx.Add(this.V3, valueVec); + this.V4 = Avx.Add(this.V4, valueVec); + this.V5 = Avx.Add(this.V5, valueVec); + this.V6 = Avx.Add(this.V6, valueVec); + this.V7 = Avx.Add(this.V7, valueVec); } - - public void RoundInto(ref Block8x8 dest) + else { - for (int i = 0; i < Size; i++) - { - float val = this[i]; - if (val < 0) - { - val -= 0.5f; - } - else - { - val += 0.5f; - } - - dest[i] = (short)val; - } + Vector4 valueVec = new(value); + this.V0L += valueVec; + this.V0R += valueVec; + this.V1L += valueVec; + this.V1R += valueVec; + this.V2L += valueVec; + this.V2R += valueVec; + this.V3L += valueVec; + this.V3R += valueVec; + this.V4L += valueVec; + this.V4R += valueVec; + this.V5L += valueVec; + this.V5R += valueVec; + this.V6L += valueVec; + this.V6R += valueVec; + this.V7L += valueVec; + this.V7R += valueVec; } + } - public Block8x8 RoundAsInt16Block() + /// + /// Quantize input block, transpose, apply zig-zag ordering and store as . + /// + /// Source block. + /// Destination block. + /// The quantization table. + public static void Quantize(ref Block8x8F block, ref Block8x8 dest, ref Block8x8F qt) + { + if (Avx2.IsSupported) { - Block8x8 result = default; - this.RoundInto(ref result); - return result; + MultiplyIntoInt16_Avx2(ref block, ref qt, ref dest); + ZigZag.ApplyTransposingZigZagOrderingAvx2(ref dest); } - - /// - /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. - /// - /// The maximum value. - public void NormalizeColorsAndRoundInPlace(float maximum) + else if (Ssse3.IsSupported) { - if (SimdUtils.HasVector8) - { - this.NormalizeColorsAndRoundInPlaceVector8(maximum); - } - else + MultiplyIntoInt16_Sse2(ref block, ref qt, ref dest); + ZigZag.ApplyTransposingZigZagOrderingSsse3(ref dest); + } + else + { + for (int i = 0; i < Size; i++) { - this.NormalizeColorsInPlace(maximum); - this.RoundInPlace(); + int idx = ZigZag.TransposingOrder[i]; + float quantizedVal = block[idx] * qt[idx]; + quantizedVal += quantizedVal < 0 ? -0.5f : 0.5f; + dest[i] = (short)quantizedVal; } } + } - public void DE_NormalizeColors(float maximum) + public void RoundInto(ref Block8x8 dest) + { + for (int i = 0; i < Size; i++) { - if (SimdUtils.HasVector8) + float val = this[i]; + if (val < 0) { - this.NormalizeColorsAndRoundInPlaceVector8(maximum); + val -= 0.5f; } else { - this.NormalizeColorsInPlace(maximum); - this.RoundInPlace(); + val += 0.5f; } + + dest[i] = (short)val; } + } - /// - /// Rounds all values in the block. - /// - public void RoundInPlace() + public Block8x8 RoundAsInt16Block() + { + Block8x8 result = default; + this.RoundInto(ref result); + return result; + } + + /// + /// Level shift by +maximum/2, clip to [0..maximum], and round all the values in the block. + /// + /// The maximum value. + public void NormalizeColorsAndRoundInPlace(float maximum) + { + if (SimdUtils.HasVector8) { - for (int i = 0; i < Size; i++) - { - this[i] = MathF.Round(this[i]); - } + this.NormalizeColorsAndRoundInPlaceVector8(maximum); } - - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref Block8x8 source) + else { - if (SimdUtils.HasVector8) - { - this.LoadFromInt16ExtendedAvx2(ref source); - return; - } + this.NormalizeColorsInPlace(maximum); + this.RoundInPlace(); + } + } - this.LoadFromInt16Scalar(ref source); + public void DE_NormalizeColors(float maximum) + { + if (SimdUtils.HasVector8) + { + this.NormalizeColorsAndRoundInPlaceVector8(maximum); + } + else + { + this.NormalizeColorsInPlace(maximum); + this.RoundInPlace(); } + } - /// - /// Loads values from using extended AVX2 intrinsics. - /// - /// The source - public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) + /// + /// Rounds all values in the block. + /// + public void RoundInPlace() + { + for (int i = 0; i < Size; i++) { - DebugGuard.IsTrue( - SimdUtils.HasVector8, - "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); - - ref Vector sRef = ref Unsafe.As>(ref source); - ref Vector dRef = ref Unsafe.As>(ref this); - - // Vector.Count == 16 on AVX2 - // We can process 2 block rows in a single step - SimdUtils.ExtendedIntrinsics.ConvertToSingle(sRef, out Vector top, out Vector bottom); - dRef = top; - Unsafe.Add(ref dRef, 1) = bottom; - - SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 1), out top, out bottom); - Unsafe.Add(ref dRef, 2) = top; - Unsafe.Add(ref dRef, 3) = bottom; - - SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 2), out top, out bottom); - Unsafe.Add(ref dRef, 4) = top; - Unsafe.Add(ref dRef, 5) = bottom; - - SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 3), out top, out bottom); - Unsafe.Add(ref dRef, 6) = top; - Unsafe.Add(ref dRef, 7) = bottom; + this[i] = MathF.Round(this[i]); } + } - /// - /// Compares entire 8x8 block to a single scalar value. - /// - /// Value to compare to. - public bool EqualsToScalar(int value) + [MethodImpl(InliningOptions.ShortMethod)] + public void LoadFrom(ref Block8x8 source) + { + if (SimdUtils.HasVector8) { - if (Avx2.IsSupported) - { - const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + this.LoadFromInt16ExtendedAvx2(ref source); + return; + } - Vector256 targetVector = Vector256.Create(value); - ref Vector256 blockStride = ref this.V0; + this.LoadFromInt16Scalar(ref source); + } - for (int i = 0; i < RowCount; i++) - { - Vector256 areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector); - if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask) - { - return false; - } - } + /// + /// Loads values from using extended AVX2 intrinsics. + /// + /// The source + public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) + { + DebugGuard.IsTrue( + SimdUtils.HasVector8, + "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); + + ref Vector sRef = ref Unsafe.As>(ref source); + ref Vector dRef = ref Unsafe.As>(ref this); + + // Vector.Count == 16 on AVX2 + // We can process 2 block rows in a single step + SimdUtils.ExtendedIntrinsics.ConvertToSingle(sRef, out Vector top, out Vector bottom); + dRef = top; + Unsafe.Add(ref dRef, 1) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 1), out top, out bottom); + Unsafe.Add(ref dRef, 2) = top; + Unsafe.Add(ref dRef, 3) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 2), out top, out bottom); + Unsafe.Add(ref dRef, 4) = top; + Unsafe.Add(ref dRef, 5) = bottom; + + SimdUtils.ExtendedIntrinsics.ConvertToSingle(Unsafe.Add(ref sRef, 3), out top, out bottom); + Unsafe.Add(ref dRef, 6) = top; + Unsafe.Add(ref dRef, 7) = bottom; + } - return true; - } + /// + /// Compares entire 8x8 block to a single scalar value. + /// + /// Value to compare to. + public bool EqualsToScalar(int value) + { + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); - ref float scalars = ref Unsafe.As(ref this); + Vector256 targetVector = Vector256.Create(value); + ref Vector256 blockStride = ref this.V0; - for (int i = 0; i < Size; i++) + for (int i = 0; i < RowCount; i++) { - if ((int)Unsafe.Add(ref scalars, i) != value) + Vector256 areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector); + if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask) { return false; } @@ -451,142 +437,154 @@ public bool EqualsToScalar(int value) return true; } - /// - public bool Equals(Block8x8F other) - => this.V0L == other.V0L - && this.V0R == other.V0R - && this.V1L == other.V1L - && this.V1R == other.V1R - && this.V2L == other.V2L - && this.V2R == other.V2R - && this.V3L == other.V3L - && this.V3R == other.V3R - && this.V4L == other.V4L - && this.V4R == other.V4R - && this.V5L == other.V5L - && this.V5R == other.V5R - && this.V6L == other.V6L - && this.V6R == other.V6R - && this.V7L == other.V7L - && this.V7R == other.V7R; - - /// - public override bool Equals(object obj) => this.Equals((Block8x8F)obj); - - /// - public override int GetHashCode() - { - int left = HashCode.Combine( - this.V0L, - this.V1L, - this.V2L, - this.V3L, - this.V4L, - this.V5L, - this.V6L, - this.V7L); - - int right = HashCode.Combine( - this.V0R, - this.V1R, - this.V2R, - this.V3R, - this.V4R, - this.V5R, - this.V6R, - this.V7R); - - return HashCode.Combine(left, right); - } + ref float scalars = ref Unsafe.As(ref this); - /// - public override string ToString() + for (int i = 0; i < Size; i++) { - StringBuilder sb = new(); - sb.Append('['); - for (int i = 0; i < Size - 1; i++) + if ((int)Unsafe.Add(ref scalars, i) != value) { - sb.Append(this[i]).Append(','); + return false; } - - sb.Append(this[Size - 1]).Append(']'); - return sb.ToString(); } - /// - /// Transpose the block inplace. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TransposeInplace() + return true; + } + + /// + public bool Equals(Block8x8F other) + => this.V0L == other.V0L + && this.V0R == other.V0R + && this.V1L == other.V1L + && this.V1R == other.V1R + && this.V2L == other.V2L + && this.V2R == other.V2R + && this.V3L == other.V3L + && this.V3R == other.V3R + && this.V4L == other.V4L + && this.V4R == other.V4R + && this.V5L == other.V5L + && this.V5R == other.V5R + && this.V6L == other.V6L + && this.V6R == other.V6R + && this.V7L == other.V7L + && this.V7R == other.V7R; + + /// + public override bool Equals(object obj) => this.Equals((Block8x8F)obj); + + /// + public override int GetHashCode() + { + int left = HashCode.Combine( + this.V0L, + this.V1L, + this.V2L, + this.V3L, + this.V4L, + this.V5L, + this.V6L, + this.V7L); + + int right = HashCode.Combine( + this.V0R, + this.V1R, + this.V2R, + this.V3R, + this.V4R, + this.V5R, + this.V6R, + this.V7R); + + return HashCode.Combine(left, right); + } + + /// + public override string ToString() + { + StringBuilder sb = new(); + sb.Append('['); + for (int i = 0; i < Size - 1; i++) { - if (Avx.IsSupported) - { - this.TransposeInplace_Avx(); - } - else - { - this.TransposeInplace_Scalar(); - } + sb.Append(this[i]).Append(','); } - /// - /// Scalar inplace transpose implementation for - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void TransposeInplace_Scalar() + sb.Append(this[Size - 1]).Append(']'); + return sb.ToString(); + } + + /// + /// Transpose the block inplace. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInplace() + { + if (Avx.IsSupported) { - ref float elemRef = ref Unsafe.As(ref this); - - // row #0 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); - - // row #1 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); - - // row #2 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); - - // row #3 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); - - // row #4 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); - - // row #5 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); - - // row #6 - RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); + this.TransposeInplace_Avx(); } - - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + else { - row += off; - row = Vector.Max(row, Vector.Zero); - row = Vector.Min(row, max); - return row.FastRound(); + this.TransposeInplace_Scalar(); } } + + /// + /// Scalar inplace transpose implementation for + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void TransposeInplace_Scalar() + { + ref float elemRef = ref Unsafe.As(ref this); + + // row #0 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); + + // row #1 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); + + // row #2 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); + + // row #3 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); + + // row #4 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); + + // row #5 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); + + // row #6 + RuntimeUtility.Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + { + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); + } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs index 63e112dd77..7d7b7e1859 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs @@ -1,96 +1,94 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class CmykAvx : JpegColorConverterAvx { - internal sealed class CmykAvx : JpegColorConverterAvx + public CmykAvx(int precision) + : base(JpegColorSpace.Cmyk, precision) { - public CmykAvx(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 c3Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - // Used for the color conversion - var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); + // Used for the color conversion + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c = ref Unsafe.Add(ref c0Base, i); + ref Vector256 m = ref Unsafe.Add(ref c1Base, i); + ref Vector256 y = ref Unsafe.Add(ref c2Base, i); + Vector256 k = Unsafe.Add(ref c3Base, i); - k = Avx.Multiply(k, scale); - c = Avx.Multiply(c, k); - m = Avx.Multiply(m, k); - y = Avx.Multiply(y, k); - } + k = Avx.Multiply(k, scale); + c = Avx.Multiply(c, k); + m = Avx.Multiply(m, k); + y = Avx.Multiply(y, k); } + } - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destC = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destM = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 destK = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - var scale = Vector256.Create(maxValue); + var scale = Vector256.Create(maxValue); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - Vector256 ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i)); - Vector256 mtmp = Avx.Subtract(scale, Unsafe.Add(ref srcG, i)); - Vector256 ytmp = Avx.Subtract(scale, Unsafe.Add(ref srcB, i)); - Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + Vector256 ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i)); + Vector256 mtmp = Avx.Subtract(scale, Unsafe.Add(ref srcG, i)); + Vector256 ytmp = Avx.Subtract(scale, Unsafe.Add(ref srcB, i)); + Vector256 ktmp = Avx.Min(ctmp, Avx.Min(mtmp, ytmp)); - Vector256 kMask = Avx.CompareNotEqual(ktmp, scale); + Vector256 kMask = Avx.CompareNotEqual(ktmp, scale); - ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); - mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); - ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + ctmp = Avx.And(Avx.Divide(Avx.Subtract(ctmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + mtmp = Avx.And(Avx.Divide(Avx.Subtract(mtmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); + ytmp = Avx.And(Avx.Divide(Avx.Subtract(ytmp, ktmp), Avx.Subtract(scale, ktmp)), kMask); - Unsafe.Add(ref destC, i) = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); - Unsafe.Add(ref destM, i) = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); - Unsafe.Add(ref destY, i) = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); - Unsafe.Add(ref destK, i) = Avx.Subtract(scale, ktmp); - } + Unsafe.Add(ref destC, i) = Avx.Subtract(scale, Avx.Multiply(ctmp, scale)); + Unsafe.Add(ref destM, i) = Avx.Subtract(scale, Avx.Multiply(mtmp, scale)); + Unsafe.Add(ref destY, i) = Avx.Subtract(scale, Avx.Multiply(ytmp, scale)); + Unsafe.Add(ref destK, i) = Avx.Subtract(scale, ktmp); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs index 70d47b9b79..6d0013b88d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs @@ -1,81 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class CmykScalar : JpegColorConverterScalar { - internal sealed class CmykScalar : JpegColorConverterScalar + public CmykScalar(int precision) + : base(JpegColorSpace.Cmyk, precision) { - public CmykScalar(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) => - ConvertToRgbInplace(values, this.MaximumValue); + /// + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertToRgbInplace(values, this.MaximumValue); - /// - public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgb(values, this.MaximumValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.MaximumValue, r, g, b); - public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; - float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i]; - float m = c1[i]; - float y = c2[i]; - float k = c3[i]; + float scale = 1 / (maxValue * maxValue); + for (int i = 0; i < c0.Length; i++) + { + float c = c0[i]; + float m = c1[i]; + float y = c2[i]; + float k = c3[i]; - k *= scale; - c0[i] = c * k; - c1[i] = m * k; - c2[i] = y * k; - } + k *= scale; + c0[i] = c * k; + c1[i] = m * k; + c2[i] = y * k; } + } + + public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span r, Span g, Span b) + { + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; + Span k = values.Component3; - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span r, Span g, Span b) + for (int i = 0; i < c.Length; i++) { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; + float ctmp = 255f - r[i]; + float mtmp = 255f - g[i]; + float ytmp = 255f - b[i]; + float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - for (int i = 0; i < c.Length; i++) + if (ktmp >= 255f) { - float ctmp = 255f - r[i]; - float mtmp = 255f - g[i]; - float ytmp = 255f - b[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255f) - { - ctmp = 0f; - mtmp = 0f; - ytmp = 0f; - } - else - { - ctmp = (ctmp - ktmp) / (255f - ktmp); - mtmp = (mtmp - ktmp) / (255f - ktmp); - ytmp = (ytmp - ktmp) / (255f - ktmp); - } - - c[i] = maxValue - (ctmp * maxValue); - m[i] = maxValue - (mtmp * maxValue); - y[i] = maxValue - (ytmp * maxValue); - k[i] = maxValue - ktmp; + ctmp = 0f; + mtmp = 0f; + ytmp = 0f; + } + else + { + ctmp = (ctmp - ktmp) / (255f - ktmp); + mtmp = (mtmp - ktmp) / (255f - ktmp); + ytmp = (ytmp - ktmp) / (255f - ktmp); } + + c[i] = maxValue - (ctmp * maxValue); + m[i] = maxValue - (mtmp * maxValue); + y[i] = maxValue - (ytmp * maxValue); + k[i] = maxValue - ktmp; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs index 6d7688bcd8..93dcd378c8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs @@ -1,106 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class CmykVector : JpegColorConverterVector { - internal sealed class CmykVector : JpegColorConverterVector + public CmykVector(int precision) + : base(JpegColorSpace.Cmyk, precision) { - public CmykVector(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } + } - /// - protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector mBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector yBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector mBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector yBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector c = ref Unsafe.Add(ref cBase, i); - ref Vector m = ref Unsafe.Add(ref mBase, i); - ref Vector y = ref Unsafe.Add(ref yBase, i); - Vector k = Unsafe.Add(ref kBase, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } + ref Vector c = ref Unsafe.Add(ref cBase, i); + ref Vector m = ref Unsafe.Add(ref mBase, i); + ref Vector y = ref Unsafe.Add(ref yBase, i); + Vector k = Unsafe.Add(ref kBase, i); + + k *= scale; + c *= k; + m *= k; + y *= k; } + } - /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) - => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => CmykScalar.ConvertToRgbInplace(values, this.MaximumValue); - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, r, g, b); - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgbInplaceRemainder(values, this.MaximumValue, r, g, b); - public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) + public static void ConvertFromRgbInplaceVectorized(in ComponentValues values, float maxValue, Span r, Span g, Span b) + { + ref Vector destC = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destM = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector destK = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(r)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(g)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); + + // Used for the color conversion + var scale = new Vector(maxValue); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - ref Vector destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(r)); - ref Vector srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(g)); - ref Vector srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(b)); - - // Used for the color conversion - var scale = new Vector(maxValue); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - Vector ctmp = scale - Unsafe.Add(ref srcR, i); - Vector mtmp = scale - Unsafe.Add(ref srcG, i); - Vector ytmp = scale - Unsafe.Add(ref srcB, i); - Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); - - var kMask = Vector.Equals(ktmp, scale); - ctmp = Vector.AndNot((ctmp - ktmp) / (scale - ktmp), kMask.As()); - mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As()); - ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As()); - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } + Vector ctmp = scale - Unsafe.Add(ref srcR, i); + Vector mtmp = scale - Unsafe.Add(ref srcG, i); + Vector ytmp = scale - Unsafe.Add(ref srcB, i); + Vector ktmp = Vector.Min(ctmp, Vector.Min(mtmp, ytmp)); + + var kMask = Vector.Equals(ktmp, scale); + ctmp = Vector.AndNot((ctmp - ktmp) / (scale - ktmp), kMask.As()); + mtmp = Vector.AndNot((mtmp - ktmp) / (scale - ktmp), kMask.As()); + ytmp = Vector.AndNot((ytmp - ktmp) / (scale - ktmp), kMask.As()); + + Unsafe.Add(ref destC, i) = scale - (ctmp * scale); + Unsafe.Add(ref destM, i) = scale - (mtmp * scale); + Unsafe.Add(ref destY, i) = scale - (ytmp * scale); + Unsafe.Add(ref destK, i) = scale - ktmp; } - - public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) - => CmykScalar.ConvertFromRgb(values, maxValue, r, g, b); } + + public static void ConvertFromRgbInplaceRemainder(in ComponentValues values, float maxValue, Span r, Span g, Span b) + => CmykScalar.ConvertFromRgb(values, maxValue, r, g, b); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs index 0fc1fcd551..9cdbe71e84 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs @@ -1,69 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class GrayscaleAvx : JpegColorConverterAvx { - internal sealed class GrayscaleAvx : JpegColorConverterAvx + public GrayscaleAvx(int precision) + : base(JpegColorSpace.Grayscale, precision) { - public GrayscaleAvx(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); + // Used for the color conversion + var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - c0 = Avx.Multiply(c0, scale); - } + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = Avx.Multiply(c0, scale); } + } - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destLuminance = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + ref Vector256 srcRed = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcGreen = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcBlue = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - // Used for the color conversion - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); + // Used for the color conversion + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref srcRed, i); - ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) + { + ref Vector256 r = ref Unsafe.Add(ref srcRed, i); + ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); + ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); - // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - } + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs index 2e5129f328..16dd7c768a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs @@ -1,50 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class GrayscaleScalar : JpegColorConverterScalar { - internal sealed class GrayscaleScalar : JpegColorConverterScalar + public GrayscaleScalar(int precision) + : base(JpegColorSpace.Grayscale, precision) { - public GrayscaleScalar(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertToRgbInplace(values.Component0, this.MaximumValue); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values.Component0, this.MaximumValue); - /// - public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) - => ConvertCoreInplaceFromRgb(values, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertCoreInplaceFromRgb(values, r, g, b); - internal static void ConvertToRgbInplace(Span values, float maxValue) - { - ref float valuesRef = ref MemoryMarshal.GetReference(values); - float scale = 1 / maxValue; + internal static void ConvertToRgbInplace(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = 1 / maxValue; - for (nint i = 0; i < values.Length; i++) - { - Unsafe.Add(ref valuesRef, i) *= scale; - } + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; } + } + + internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + Span c0 = values.Component0; - internal static void ConvertCoreInplaceFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + for (int i = 0; i < c0.Length; i++) { - Span c0 = values.Component0; - - for (int i = 0; i < c0.Length; i++) - { - // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) - float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]); - c0[i] = luma; - } + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + float luma = (0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i]); + c0[i] = luma; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs index 0f903c0519..4d2355b95d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs @@ -1,74 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class GrayScaleVector : JpegColorConverterVector { - internal sealed class GrayScaleVector : JpegColorConverterVector + public GrayScaleVector(int precision) + : base(JpegColorSpace.Grayscale, precision) { - public GrayScaleVector(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } + } - /// - protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) - { - ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - var scale = new Vector(1 / this.MaximumValue); + var scale = new Vector(1 / this.MaximumValue); - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector c0 = ref Unsafe.Add(ref cBase, i); - c0 *= scale; - } + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; } + } - /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) - => GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => GrayscaleScalar.ConvertToRgbInplace(values.Component0, this.MaximumValue); - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector destLuma = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector destLuma = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - var rMult = new Vector(0.299f); - var gMult = new Vector(0.587f); - var bMult = new Vector(0.114f); + var rMult = new Vector(0.299f); + var gMult = new Vector(0.587f); + var bMult = new Vector(0.114f); - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - Vector r = Unsafe.Add(ref srcR, i); - Vector g = Unsafe.Add(ref srcR, i); - Vector b = Unsafe.Add(ref srcR, i); + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) + { + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcR, i); + Vector b = Unsafe.Add(ref srcR, i); - // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuma, i) = (rMult * r) + (gMult * g) + (bMult * b); - } + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuma, i) = (rMult * r) + (gMult * g) + (bMult * b); } - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) - => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => GrayscaleScalar.ConvertCoreInplaceFromRgb(values, r, g, b); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs index 08247e7dac..b6c5117d44 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs @@ -1,54 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class RgbAvx : JpegColorConverterAvx { - internal sealed class RgbAvx : JpegColorConverterAvx + public RgbAvx(int precision) + : base(JpegColorSpace.RGB, precision) { - public RgbAvx(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInplace(in ComponentValues values) - { - ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + } - // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref rBase, i); - ref Vector256 g = ref Unsafe.Add(ref gBase, i); - ref Vector256 b = ref Unsafe.Add(ref bBase, i); - r = Avx.Multiply(r, scale); - g = Avx.Multiply(g, scale); - b = Avx.Multiply(b, scale); - } - } + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + // Used for the color conversion + var scale = Vector256.Create(1 / this.MaximumValue); + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); + ref Vector256 r = ref Unsafe.Add(ref rBase, i); + ref Vector256 g = ref Unsafe.Add(ref gBase, i); + ref Vector256 b = ref Unsafe.Add(ref bBase, i); + r = Avx.Multiply(r, scale); + g = Avx.Multiply(g, scale); + b = Avx.Multiply(b, scale); } } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + rLane.CopyTo(values.Component0); + gLane.CopyTo(values.Component1); + bLane.CopyTo(values.Component2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs index a3de4493fd..a43b7ef842 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs @@ -1,40 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class RgbScalar : JpegColorConverterScalar { - internal sealed class RgbScalar : JpegColorConverterScalar + public RgbScalar(int precision) + : base(JpegColorSpace.RGB, precision) { - public RgbScalar(int precision) - : base(JpegColorSpace.RGB, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertToRgbInplace(values, this.MaximumValue); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values, this.MaximumValue); - /// - public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgb(values, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, r, g, b); - internal static void ConvertToRgbInplace(ComponentValues values, float maxValue) - { - GrayscaleScalar.ConvertToRgbInplace(values.Component0, maxValue); - GrayscaleScalar.ConvertToRgbInplace(values.Component1, maxValue); - GrayscaleScalar.ConvertToRgbInplace(values.Component2, maxValue); - } + internal static void ConvertToRgbInplace(ComponentValues values, float maxValue) + { + GrayscaleScalar.ConvertToRgbInplace(values.Component0, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component1, maxValue); + GrayscaleScalar.ConvertToRgbInplace(values.Component2, maxValue); + } - internal static void ConvertFromRgb(ComponentValues values, Span r, Span g, Span b) - { - r.CopyTo(values.Component0); - g.CopyTo(values.Component1); - b.CopyTo(values.Component2); - } + internal static void ConvertFromRgb(ComponentValues values, Span r, Span g, Span b) + { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs index ba36733804..e51b0df4d2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs @@ -1,61 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class RgbVector : JpegColorConverterVector { - internal sealed class RgbVector : JpegColorConverterVector + public RgbVector(int precision) + : base(JpegColorSpace.RGB, precision) { - public RgbVector(int precision) - : base(JpegColorSpace.RGB, precision) - { - } + } - /// - protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) - { - ref Vector rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - var scale = new Vector(1 / this.MaximumValue); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - ref Vector r = ref Unsafe.Add(ref rBase, i); - ref Vector g = ref Unsafe.Add(ref gBase, i); - ref Vector b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) - => RgbScalar.ConvertToRgbInplace(values, this.MaximumValue); + var scale = new Vector(1 / this.MaximumValue); - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - r.CopyTo(values.Component0); - g.CopyTo(values.Component1); - b.CopyTo(values.Component2); + ref Vector r = ref Unsafe.Add(ref rBase, i); + ref Vector g = ref Unsafe.Add(ref gBase, i); + ref Vector b = ref Unsafe.Add(ref bBase, i); + r *= scale; + g *= scale; + b *= scale; } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => RgbScalar.ConvertToRgbInplace(values, this.MaximumValue); - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) - => RgbScalar.ConvertFromRgb(values, r, g, b); + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span r, Span g, Span b) + { + r.CopyTo(values.Component0); + g.CopyTo(values.Component1); + b.CopyTo(values.Component2); } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => RgbScalar.ConvertFromRgb(values, r, g, b); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs index 62e0fc1b62..081b985dbb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -9,114 +8,113 @@ using static SixLabors.ImageSharp.SimdUtils; // ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YCbCrAvx : JpegColorConverterAvx { - internal sealed class YCbCrAvx : JpegColorConverterAvx + public YCbCrAvx(int precision) + : base(JpegColorSpace.YCbCr, precision) { - public YCbCrAvx(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(-this.HalfValue); + var scale = Vector256.Create(1 / this.MaximumValue); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); + + // Walking 8 elements at one step: + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - var chromaOffset = Vector256.Create(-this.HalfValue); - var scale = Vector256.Create(1 / this.MaximumValue); - var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector256 y = c0; - Vector256 cb = Avx.Add(c1, chromaOffset); - Vector256 cr = Avx.Add(c2, chromaOffset); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); - Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); - - r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); - g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); - b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); - - c0 = r; - c1 = g; - c2 = b; - } + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + + Vector256 y = c0; + Vector256 cb = Avx.Add(c1, chromaOffset); + Vector256 cr = Avx.Add(c2, chromaOffset); + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); + Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); + Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); + + r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); + g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); + b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); + + c0 = r; + c1 = g; + c2 = b; } + } - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector256 srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector256 srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) { - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - var chromaOffset = Vector256.Create(this.HalfValue); - - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i); - Vector256 g = Unsafe.Add(ref srcG, i); - Vector256 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } + Vector256 r = Unsafe.Add(ref srcR, i); + Vector256 g = Unsafe.Add(ref srcG, i); + Vector256 b = Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs index 7fd6283287..e3e5a452a8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs @@ -1,75 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YCbCrScalar : JpegColorConverterScalar { - internal sealed class YCbCrScalar : JpegColorConverterScalar - { - // derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; - public YCbCrScalar(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } + public YCbCrScalar(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); - /// - public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgb(values, this.HalfValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, r, g, b); - public static void ConvertToRgbInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; + public static void ConvertToRgbInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; - float scale = 1 / maxValue; + float scale = 1 / maxValue; - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; + for (int i = 0; i < c0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; - } + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; } + } - public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; + public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) + { + Span y = values.Component0; + Span cb = values.Component1; + Span cr = values.Component2; - for (int i = 0; i < y.Length; i++) - { - float r = rLane[i]; - float g = gLane[i]; - float b = bLane[i]; + for (int i = 0; i < y.Length; i++) + { + float r = rLane[i]; + float g = gLane[i]; + float b = bLane[i]; - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs index f747d5523d..85211d4abf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs @@ -1,128 +1,126 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable ImpureMethodCallOnReadonlyValueField -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YCbCrVector : JpegColorConverterVector { - internal sealed class YCbCrVector : JpegColorConverterVector + public YCbCrVector(int precision) + : base(JpegColorSpace.YCbCr, precision) { - public YCbCrVector(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } + } - /// - protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var chromaOffset = new Vector(-this.HalfValue); + + var scale = new Vector(1 / this.MaximumValue); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - ref Vector c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - var chromaOffset = new Vector(-this.HalfValue); - - var scale = new Vector(1 / this.MaximumValue); - var rCrMult = new Vector(YCbCrScalar.RCrMult); - var gCbMult = new Vector(-YCbCrScalar.GCbMult); - var gCrMult = new Vector(-YCbCrScalar.GCrMult); - var bCbMult = new Vector(YCbCrScalar.BCbMult); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - Vector y = Unsafe.Add(ref c0Base, i); - Vector cb = Unsafe.Add(ref c1Base, i) + chromaOffset; - Vector cr = Unsafe.Add(ref c2Base, i) + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector r = y + (cr * rCrMult); - Vector g = y + (cb * gCbMult) + (cr * gCrMult); - Vector b = y + (cb * bCbMult); - - r = r.FastRound(); - g = g.FastRound(); - b = b.FastRound(); - r *= scale; - g *= scale; - b *= scale; - - c0 = r; - c1 = g; - c2 = b; - } + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + Vector y = Unsafe.Add(ref c0Base, i); + Vector cb = Unsafe.Add(ref c1Base, i) + chromaOffset; + Vector cr = Unsafe.Add(ref c2Base, i) + chromaOffset; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); + + r = r.FastRound(); + g = g.FastRound(); + b = b.FastRound(); + r *= scale; + g *= scale; + b *= scale; + + c0 = r; + c1 = g; + c2 = b; } + } - /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) - => YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YCbCrScalar.ConvertToRgbInplace(values, this.MaximumValue, this.HalfValue); - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector srcR = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector srcG = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector srcB = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - ref Vector destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - var chromaOffset = new Vector(this.HalfValue); - - var rYMult = new Vector(0.299f); - var gYMult = new Vector(0.587f); - var bYMult = new Vector(0.114f); - - var rCbMult = new Vector(0.168736f); - var gCbMult = new Vector(0.331264f); - var bCbMult = new Vector(0.5f); - - var rCrMult = new Vector(0.5f); - var gCrMult = new Vector(0.418688f); - var bCrMult = new Vector(0.081312f); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - Vector r = Unsafe.Add(ref srcR, i); - Vector g = Unsafe.Add(ref srcG, i); - Vector b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); - Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); - Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); - } + Vector r = Unsafe.Add(ref srcR, i); + Vector g = Unsafe.Add(ref srcG, i); + Vector b = Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); } - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) - => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, r, g, b); } + + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, r, g, b); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs index dea9fa0a73..1f79cbffb6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKAvx.cs @@ -1,133 +1,131 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YccKAvx : JpegColorConverterAvx { - internal sealed class YccKAvx : JpegColorConverterAvx + public YccKAvx(int precision) + : base(JpegColorSpace.Ycck, precision) { - public YccKAvx(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector256 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector256 kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + // Used for the color conversion + var chromaOffset = Vector256.Create(-this.HalfValue); + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); + var max = Vector256.Create(this.MaximumValue); + var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); + + // Walking 8 elements at one step: + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - var chromaOffset = Vector256.Create(-this.HalfValue); - var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - var max = Vector256.Create(this.MaximumValue); - var rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - var gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - var gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - var bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 y = c0; - Vector256 cb = Avx.Add(c1, chromaOffset); - Vector256 cr = Avx.Add(c2, chromaOffset); - Vector256 scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); - Vector256 g = - HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); - Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); - - r = Avx.Subtract(max, Avx.RoundToNearestInteger(r)); - g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); - b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); - - r = Avx.Multiply(r, scaledK); - g = Avx.Multiply(g, scaledK); - b = Avx.Multiply(b, scaledK); - - c0 = r; - c1 = g; - c2 = b; - } + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + // k = kVals[i] / 256F; + ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); + Vector256 y = c0; + Vector256 cb = Avx.Add(c1, chromaOffset); + Vector256 cr = Avx.Add(c2, chromaOffset); + Vector256 scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale); + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); + Vector256 g = + HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); + Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); + + r = Avx.Subtract(max, Avx.RoundToNearestInteger(r)); + g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); + b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); + + r = Avx.Multiply(r, scaledK); + g = Avx.Multiply(g, scaledK); + b = Avx.Multiply(b, scaledK); + + c0 = r; + c1 = g; + c2 = b; } + } - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykAvx.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector256 destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector256 destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector256 destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + ref Vector256 srcR = ref destY; + ref Vector256 srcG = ref destCb; + ref Vector256 srcB = ref destCr; + + // Used for the color conversion + var maxSampleValue = Vector256.Create(this.MaximumValue); + + var chromaOffset = Vector256.Create(this.HalfValue); + + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + + nint n = values.Component0.Length / Vector256.Count; + for (nint i = 0; i < n; i++) { - // rgb -> cmyk - CmykAvx.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = ref destY; - ref Vector256 srcG = ref destCb; - ref Vector256 srcB = ref destCr; - - // Used for the color conversion - var maxSampleValue = Vector256.Create(this.MaximumValue); - - var chromaOffset = Vector256.Create(this.HalfValue); - - var f0299 = Vector256.Create(0.299f); - var f0587 = Vector256.Create(0.587f); - var f0114 = Vector256.Create(0.114f); - var fn0168736 = Vector256.Create(-0.168736f); - var fn0331264 = Vector256.Create(-0.331264f); - var fn0418688 = Vector256.Create(-0.418688f); - var fn0081312F = Vector256.Create(-0.081312F); - var f05 = Vector256.Create(0.5f); - - nint n = values.Component0.Length / Vector256.Count; - for (nint i = 0; i < n; i++) - { - Vector256 r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i)); - Vector256 g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i)); - Vector256 b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i)); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); - Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); - Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } + Vector256 r = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcR, i)); + Vector256 g = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcG, i)); + Vector256 b = Avx.Subtract(maxSampleValue, Unsafe.Add(ref srcB, i)); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Vector256 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + Vector256 cb = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + Vector256 cr = Avx.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + + Unsafe.Add(ref destY, i) = y; + Unsafe.Add(ref destCb, i) = cb; + Unsafe.Add(ref destCr, i) = cr; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs index dc58a70edf..e47572b02a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YccKScalar : JpegColorConverterScalar { - internal sealed class YccKScalar : JpegColorConverterScalar - { - // derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; + // derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; - public YccKScalar(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } + public YccKScalar(int precision) + : base(JpegColorSpace.Ycck, precision) + { + } - /// - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + /// + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); - /// - public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); + /// + public override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, r, g, b); - public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; + public static void ConvertToRgpInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; - float scale = 1 / (maxValue * maxValue); + float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - float scaledK = c3[i] * scale; + for (int i = 0; i < values.Component0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; + float scaledK = c3[i] * scale; - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; - } + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; } + } - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); + public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); - // cmyk -> ycck - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; + // cmyk -> ycck + Span c = values.Component0; + Span m = values.Component1; + Span y = values.Component2; - for (int i = 0; i < y.Length; i++) - { - float r = maxValue - c[i]; - float g = maxValue - m[i]; - float b = maxValue - y[i]; + for (int i = 0; i < y.Length; i++) + { + float r = maxValue - c[i]; + float g = maxValue - m[i]; + float b = maxValue - y[i]; - // k value is passed untouched from rgb -> cmyk conversion - c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } + // k value is passed untouched from rgb -> cmyk conversion + c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); + m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); + y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs index ef43dca408..91a6cedc06 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector.cs @@ -1,138 +1,136 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + internal sealed class YccKVector : JpegColorConverterVector { - internal sealed class YccKVector : JpegColorConverterVector + public YccKVector(int precision) + : base(JpegColorSpace.Ycck, precision) { - public YccKVector(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } + } - /// - protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + /// + protected override void ConvertToRgbInplaceVectorized(in ComponentValues values) + { + ref Vector c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector c1Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector c2Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Vector kBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + + var chromaOffset = new Vector(-this.HalfValue); + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); + var max = new Vector(this.MaximumValue); + var rCrMult = new Vector(YCbCrScalar.RCrMult); + var gCbMult = new Vector(-YCbCrScalar.GCbMult); + var gCrMult = new Vector(-YCbCrScalar.GCrMult); + var bCbMult = new Vector(YCbCrScalar.BCbMult); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - ref Vector c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - var chromaOffset = new Vector(-this.HalfValue); - var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); - var max = new Vector(this.MaximumValue); - var rCrMult = new Vector(YCbCrScalar.RCrMult); - var gCbMult = new Vector(-YCbCrScalar.GCbMult); - var gCrMult = new Vector(-YCbCrScalar.GCrMult); - var bCbMult = new Vector(YCbCrScalar.BCbMult); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector c2 = ref Unsafe.Add(ref c2Base, i); - - Vector y = c0; - Vector cb = c1 + chromaOffset; - Vector cr = c2 + chromaOffset; - Vector scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector r = y + (cr * rCrMult); - Vector g = y + (cb * gCbMult) + (cr * gCrMult); - Vector b = y + (cb * bCbMult); - - r = (max - r.FastRound()) * scaledK; - g = (max - g.FastRound()) * scaledK; - b = (max - b.FastRound()) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } + // y = yVals[i]; + // cb = cbVals[i] - 128F; + // cr = crVals[i] - 128F; + // k = kVals[i] / 256F; + ref Vector c0 = ref Unsafe.Add(ref c0Base, i); + ref Vector c1 = ref Unsafe.Add(ref c1Base, i); + ref Vector c2 = ref Unsafe.Add(ref c2Base, i); + + Vector y = c0; + Vector cb = c1 + chromaOffset; + Vector cr = c2 + chromaOffset; + Vector scaledK = Unsafe.Add(ref kBase, i) * scale; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); + + r = (max - r.FastRound()) * scaledK; + g = (max - g.FastRound()) * scaledK; + b = (max - b.FastRound()) * scaledK; + + c0 = r; + c1 = g; + c2 = b; } + } + + /// + protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) + => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + + /// + protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + // rgb -> cmyk + CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); + + // cmyk -> ycck + ref Vector destY = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector destCb = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector destCr = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - /// - protected override void ConvertToRgbInplaceScalarRemainder(in ComponentValues values) - => YccKScalar.ConvertToRgpInplace(values, this.MaximumValue, this.HalfValue); + ref Vector srcR = ref destY; + ref Vector srcG = ref destCb; + ref Vector srcB = ref destCr; - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) + var maxSampleValue = new Vector(this.MaximumValue); + + var chromaOffset = new Vector(this.HalfValue); + + var rYMult = new Vector(0.299f); + var gYMult = new Vector(0.587f); + var bYMult = new Vector(0.114f); + + var rCbMult = new Vector(0.168736f); + var gCbMult = new Vector(0.331264f); + var bCbMult = new Vector(0.5f); + + var rCrMult = new Vector(0.5f); + var gCrMult = new Vector(0.418688f); + var bCrMult = new Vector(0.081312f); + + nint n = values.Component0.Length / Vector.Count; + for (nint i = 0; i < n; i++) { - // rgb -> cmyk - CmykVector.ConvertFromRgbInplaceVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector srcR = ref destY; - ref Vector srcG = ref destCb; - ref Vector srcB = ref destCr; - - var maxSampleValue = new Vector(this.MaximumValue); - - var chromaOffset = new Vector(this.HalfValue); - - var rYMult = new Vector(0.299f); - var gYMult = new Vector(0.587f); - var bYMult = new Vector(0.114f); - - var rCbMult = new Vector(0.168736f); - var gCbMult = new Vector(0.331264f); - var bCbMult = new Vector(0.5f); - - var rCrMult = new Vector(0.5f); - var gCrMult = new Vector(0.418688f); - var bCrMult = new Vector(0.081312f); - - nint n = values.Component0.Length / Vector.Count; - for (nint i = 0; i < n; i++) - { - Vector r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); - Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); - Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); - } + Vector r = maxSampleValue - Unsafe.Add(ref srcR, i); + Vector g = maxSampleValue - Unsafe.Add(ref srcG, i); + Vector b = maxSampleValue - Unsafe.Add(ref srcB, i); + + // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) + // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) + // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) + Unsafe.Add(ref destY, i) = (rYMult * r) + (gYMult * g) + (bYMult * b); + Unsafe.Add(ref destCb, i) = chromaOffset - (rCbMult * r) - (gCbMult * g) + (bCbMult * b); + Unsafe.Add(ref destCr, i) = chromaOffset + (rCrMult * r) - (gCrMult * g) - (bCrMult * b); } + } - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) - { - // rgb -> cmyk - CmykScalar.ConvertFromRgb(in values, this.MaximumValue, r, g, b); + /// + protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span r, Span g, Span b) + { + // rgb -> cmyk + CmykScalar.ConvertFromRgb(in values, this.MaximumValue, r, g, b); - // cmyk -> ycck - YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); - } + // cmyk -> ycck + YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, r, g, b); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs index c9b0054c3b..a3e41bdc6b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterAvx.cs @@ -3,33 +3,32 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + /// + /// abstract base for implementations + /// based on instructions. + /// + /// + /// Converters of this family would expect input buffers lengths to be + /// divisible by 8 without a remainder. + /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. + /// DO NOT pass test data of invalid size to these converters as they + /// potentially won't do a bound check and return a false positive result. + /// + internal abstract class JpegColorConverterAvx : JpegColorConverterBase { - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterAvx : JpegColorConverterBase + protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) { - protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } + } - public static bool IsSupported => Avx.IsSupported; + public static bool IsSupported => Avx.IsSupported; - public sealed override bool IsAvailable => IsSupported; + public sealed override bool IsAvailable => IsSupported; - public sealed override int ElementsPerBatch => Vector256.Count; - } + public sealed override int ElementsPerBatch => Vector256.Count; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index c159ed7d21..f0aa2c321d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -1,340 +1,337 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Encapsulates the conversion of color channels from jpeg image to RGB channels. +/// +internal abstract partial class JpegColorConverterBase { /// - /// Encapsulates the conversion of color channels from jpeg image to RGB channels. + /// The available converters + /// + private static readonly JpegColorConverterBase[] Converters = CreateConverters(); + + /// + /// Initializes a new instance of the class. /// - internal abstract partial class JpegColorConverterBase + /// The color space. + /// The precision in bits. + protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision) { - /// - /// The available converters - /// - private static readonly JpegColorConverterBase[] Converters = CreateConverters(); + this.ColorSpace = colorSpace; + this.Precision = precision; + this.MaximumValue = MathF.Pow(2, precision) - 1; + this.HalfValue = MathF.Ceiling(this.MaximumValue / 2); + } - /// - /// Initializes a new instance of the class. - /// - /// The color space. - /// The precision in bits. - protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision) + /// + /// Gets a value indicating whether this is available + /// on the current runtime and CPU architecture. + /// + public abstract bool IsAvailable { get; } + + /// + /// Gets a value indicating how many pixels are processed in a single batch. + /// + /// + /// This generally should be equal to register size, + /// e.g. 1 for scalar implementation, 8 for AVX implementation and so on. + /// + public abstract int ElementsPerBatch { get; } + + /// + /// Gets the of this converter. + /// + public JpegColorSpace ColorSpace { get; } + + /// + /// Gets the Precision of this converter in bits. + /// + public int Precision { get; } + + /// + /// Gets the maximum value of a sample + /// + private float MaximumValue { get; } + + /// + /// Gets the half of the maximum value of a sample + /// + private float HalfValue { get; } + + /// + /// Returns the corresponding to the given + /// + /// The color space. + /// The precision in bits. + /// Invalid colorspace. + public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision) + { + JpegColorConverterBase converter = Array.Find( + Converters, + c => c.ColorSpace == colorSpace + && c.Precision == precision); + + if (converter is null) { - this.ColorSpace = colorSpace; - this.Precision = precision; - this.MaximumValue = MathF.Pow(2, precision) - 1; - this.HalfValue = MathF.Ceiling(this.MaximumValue / 2); + throw new InvalidImageContentException($"Could not find any converter for JpegColorSpace {colorSpace}!"); } - /// - /// Gets a value indicating whether this is available - /// on the current runtime and CPU architecture. - /// - public abstract bool IsAvailable { get; } + return converter; + } - /// - /// Gets a value indicating how many pixels are processed in a single batch. - /// - /// - /// This generally should be equal to register size, - /// e.g. 1 for scalar implementation, 8 for AVX implementation and so on. - /// - public abstract int ElementsPerBatch { get; } + /// + /// Converts planar jpeg component values in to RGB color space inplace. + /// + /// The input/ouptut as a stack-only struct + public abstract void ConvertToRgbInplace(in ComponentValues values); - /// - /// Gets the of this converter. - /// - public JpegColorSpace ColorSpace { get; } + /// + /// Converts RGB lanes to jpeg component values. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); - /// - /// Gets the Precision of this converter in bits. - /// - public int Precision { get; } + /// + /// Returns the s for all supported colorspaces and precisions. + /// + private static JpegColorConverterBase[] CreateConverters() + { + // 5 color types with 2 supported precisions: 8 bit & 12 bit + const int colorConvertersCount = 5 * 2; + + JpegColorConverterBase[] converters = new JpegColorConverterBase[colorConvertersCount]; + + // 8-bit converters + converters[0] = GetYCbCrConverter(8); + converters[1] = GetYccKConverter(8); + converters[2] = GetCmykConverter(8); + converters[3] = GetGrayScaleConverter(8); + converters[4] = GetRgbConverter(8); + + // 12-bit converters + converters[5] = GetYCbCrConverter(12); + converters[6] = GetYccKConverter(12); + converters[7] = GetCmykConverter(12); + converters[8] = GetGrayScaleConverter(12); + converters[9] = GetRgbConverter(12); + + return converters; + } - /// - /// Gets the maximum value of a sample - /// - private float MaximumValue { get; } + /// + /// Returns the s for the YCbCr colorspace. + /// + /// The precision in bits. + private static JpegColorConverterBase GetYCbCrConverter(int precision) + { + if (JpegColorConverterAvx.IsSupported) + { + return new YCbCrAvx(precision); + } - /// - /// Gets the half of the maximum value of a sample - /// - private float HalfValue { get; } + if (JpegColorConverterVector.IsSupported) + { + return new YCbCrVector(precision); + } - /// - /// Returns the corresponding to the given - /// - /// The color space. - /// The precision in bits. - /// Invalid colorspace. - public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision) + return new YCbCrScalar(precision); + } + + /// + /// Returns the s for the YccK colorspace. + /// + /// The precision in bits. + private static JpegColorConverterBase GetYccKConverter(int precision) + { + if (JpegColorConverterAvx.IsSupported) + { + return new YccKAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new YccKVector(precision); + } + + return new YccKScalar(precision); + } + + /// + /// Returns the s for the CMYK colorspace. + /// + /// The precision in bits. + private static JpegColorConverterBase GetCmykConverter(int precision) + { + if (JpegColorConverterAvx.IsSupported) + { + return new CmykAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) { - JpegColorConverterBase converter = Array.Find( - Converters, - c => c.ColorSpace == colorSpace - && c.Precision == precision); + return new CmykVector(precision); + } + + return new CmykScalar(precision); + } - if (converter is null) - { - throw new InvalidImageContentException($"Could not find any converter for JpegColorSpace {colorSpace}!"); - } + /// + /// Returns the s for the gray scale colorspace. + /// + /// The precision in bits. + private static JpegColorConverterBase GetGrayScaleConverter(int precision) + { + if (JpegColorConverterAvx.IsSupported) + { + return new GrayscaleAvx(precision); + } - return converter; + if (JpegColorConverterVector.IsSupported) + { + return new GrayScaleVector(precision); } + return new GrayscaleScalar(precision); + } + + /// + /// Returns the s for the RGB colorspace. + /// + /// The precision in bits. + private static JpegColorConverterBase GetRgbConverter(int precision) + { + if (JpegColorConverterAvx.IsSupported) + { + return new RgbAvx(precision); + } + + if (JpegColorConverterVector.IsSupported) + { + return new RgbScalar(precision); + } + + return new GrayscaleScalar(precision); + } + + /// + /// A stack-only struct to reference the input buffers using -s. + /// +#pragma warning disable SA1206 // Declaration keywords should follow order + public readonly ref struct ComponentValues +#pragma warning restore SA1206 // Declaration keywords should follow order + { /// - /// Converts planar jpeg component values in to RGB color space inplace. + /// The component count /// - /// The input/ouptut as a stack-only struct - public abstract void ConvertToRgbInplace(in ComponentValues values); + public readonly int ComponentCount; /// - /// Converts RGB lanes to jpeg component values. + /// The component 0 (eg. Y) /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - public abstract void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane); + public readonly Span Component0; /// - /// Returns the s for all supported colorspaces and precisions. + /// The component 1 (eg. Cb). In case of grayscale, it points to . /// - private static JpegColorConverterBase[] CreateConverters() - { - // 5 color types with 2 supported precisions: 8 bit & 12 bit - const int colorConvertersCount = 5 * 2; - - JpegColorConverterBase[] converters = new JpegColorConverterBase[colorConvertersCount]; - - // 8-bit converters - converters[0] = GetYCbCrConverter(8); - converters[1] = GetYccKConverter(8); - converters[2] = GetCmykConverter(8); - converters[3] = GetGrayScaleConverter(8); - converters[4] = GetRgbConverter(8); - - // 12-bit converters - converters[5] = GetYCbCrConverter(12); - converters[6] = GetYccKConverter(12); - converters[7] = GetCmykConverter(12); - converters[8] = GetGrayScaleConverter(12); - converters[9] = GetRgbConverter(12); - - return converters; - } + public readonly Span Component1; /// - /// Returns the s for the YCbCr colorspace. + /// The component 2 (eg. Cr). In case of grayscale, it points to . /// - /// The precision in bits. - private static JpegColorConverterBase GetYCbCrConverter(int precision) - { - if (JpegColorConverterAvx.IsSupported) - { - return new YCbCrAvx(precision); - } - - if (JpegColorConverterVector.IsSupported) - { - return new YCbCrVector(precision); - } + public readonly Span Component2; - return new YCbCrScalar(precision); - } + /// + /// The component 4 + /// + public readonly Span Component3; /// - /// Returns the s for the YccK colorspace. + /// Initializes a new instance of the struct. /// - /// The precision in bits. - private static JpegColorConverterBase GetYccKConverter(int precision) + /// List of component buffers. + /// Row to convert + public ComponentValues(IReadOnlyList> componentBuffers, int row) { - if (JpegColorConverterAvx.IsSupported) - { - return new YccKAvx(precision); - } + DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers)); + + this.ComponentCount = componentBuffers.Count; - if (JpegColorConverterVector.IsSupported) - { - return new YccKVector(precision); - } + this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); - return new YccKScalar(precision); + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; } /// - /// Returns the s for the CMYK colorspace. + /// Initializes a new instance of the struct. /// - /// The precision in bits. - private static JpegColorConverterBase GetCmykConverter(int precision) + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) { - if (JpegColorConverterAvx.IsSupported) - { - return new CmykAvx(precision); - } + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - if (JpegColorConverterVector.IsSupported) - { - return new CmykVector(precision); - } + this.ComponentCount = processors.Count; - return new CmykScalar(precision); + this.Component0 = processors[0].GetColorBufferRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } /// - /// Returns the s for the gray scale colorspace. + /// Initializes a new instance of the struct. /// - /// The precision in bits. - private static JpegColorConverterBase GetGrayScaleConverter(int precision) + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) { - if (JpegColorConverterAvx.IsSupported) - { - return new GrayscaleAvx(precision); - } + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); + + this.ComponentCount = processors.Count; - if (JpegColorConverterVector.IsSupported) - { - return new GrayScaleVector(precision); - } + this.Component0 = processors[0].GetColorBufferRowSpan(row); - return new GrayscaleScalar(precision); + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } - /// - /// Returns the s for the RGB colorspace. - /// - /// The precision in bits. - private static JpegColorConverterBase GetRgbConverter(int precision) + internal ComponentValues( + int componentCount, + Span c0, + Span c1, + Span c2, + Span c3) { - if (JpegColorConverterAvx.IsSupported) - { - return new RgbAvx(precision); - } - - if (JpegColorConverterVector.IsSupported) - { - return new RgbScalar(precision); - } - - return new GrayscaleScalar(precision); + this.ComponentCount = componentCount; + this.Component0 = c0; + this.Component1 = c1; + this.Component2 = c2; + this.Component3 = c3; } - /// - /// A stack-only struct to reference the input buffers using -s. - /// -#pragma warning disable SA1206 // Declaration keywords should follow order - public readonly ref struct ComponentValues -#pragma warning restore SA1206 // Declaration keywords should follow order + public ComponentValues Slice(int start, int length) { - /// - /// The component count - /// - public readonly int ComponentCount; - - /// - /// The component 0 (eg. Y) - /// - public readonly Span Component0; - - /// - /// The component 1 (eg. Cb). In case of grayscale, it points to . - /// - public readonly Span Component1; - - /// - /// The component 2 (eg. Cr). In case of grayscale, it points to . - /// - public readonly Span Component2; - - /// - /// The component 4 - /// - public readonly Span Component3; - - /// - /// Initializes a new instance of the struct. - /// - /// List of component buffers. - /// Row to convert - public ComponentValues(IReadOnlyList> componentBuffers, int row) - { - DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers)); - - this.ComponentCount = componentBuffers.Count; - - this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; - } - - /// - /// Initializes a new instance of the struct. - /// - /// List of component color processors. - /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) - { - DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - - this.ComponentCount = processors.Count; - - this.Component0 = processors[0].GetColorBufferRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; - } - - /// - /// Initializes a new instance of the struct. - /// - /// List of component color processors. - /// Row to convert - public ComponentValues(IReadOnlyList processors, int row) - { - DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - - this.ComponentCount = processors.Count; - - this.Component0 = processors[0].GetColorBufferRowSpan(row); - - // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; - } - - internal ComponentValues( - int componentCount, - Span c0, - Span c1, - Span c2, - Span c3) - { - this.ComponentCount = componentCount; - this.Component0 = c0; - this.Component1 = c1; - this.Component2 = c2; - this.Component3 = c3; - } - - public ComponentValues Slice(int start, int length) - { - Span c0 = this.Component0.Slice(start, length); - Span c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : Span.Empty; - Span c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : Span.Empty; - Span c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : Span.Empty; - - return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); - } + Span c0 = this.Component0.Slice(start, length); + Span c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : Span.Empty; + Span c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : Span.Empty; + Span c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : Span.Empty; + + return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs index 9de8e16a27..13e5c6d5b2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs @@ -1,24 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + /// + /// abstract base for implementations + /// based on scalar instructions. + /// + internal abstract class JpegColorConverterScalar : JpegColorConverterBase { - /// - /// abstract base for implementations - /// based on scalar instructions. - /// - internal abstract class JpegColorConverterScalar : JpegColorConverterBase + protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) { - protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } + } - public sealed override bool IsAvailable => true; + public sealed override bool IsAvailable => true; - public sealed override int ElementsPerBatch => 1; - } + public sealed override int ElementsPerBatch => 1; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs index e618652989..5f9688a795 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs @@ -1,131 +1,129 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase { - internal abstract partial class JpegColorConverterBase + /// + /// abstract base for implementations + /// based on API. + /// + /// + /// Converters of this family can work with data of any size. + /// Even though real life data is guaranteed to be of size + /// divisible by 8 newer SIMD instructions like AVX512 won't work with + /// such data out of the box. These converters have fallback code + /// for 'remainder' data. + /// + internal abstract class JpegColorConverterVector : JpegColorConverterBase { + protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + /// - /// abstract base for implementations - /// based on API. + /// Gets a value indicating whether this converter is supported on current hardware. /// - /// - /// Converters of this family can work with data of any size. - /// Even though real life data is guaranteed to be of size - /// divisible by 8 newer SIMD instructions like AVX512 won't work with - /// such data out of the box. These converters have fallback code - /// for 'remainder' data. - /// - internal abstract class JpegColorConverterVector : JpegColorConverterBase + public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + + /// + public sealed override bool IsAvailable => IsSupported; + + public override int ElementsPerBatch => Vector.Count; + + /// + public sealed override void ConvertToRgbInplace(in ComponentValues values) { - protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); + + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); + + int simdCount = length - remainder; + if (simdCount > 0) { + this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount)); } - /// - /// Gets a value indicating whether this converter is supported on current hardware. - /// - public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX1/AVX2 implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) + { + this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder)); + } + } - /// - public sealed override bool IsAvailable => IsSupported; + /// + public sealed override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - public override int ElementsPerBatch => Vector.Count; + int length = values.Component0.Length; + int remainder = (int)((uint)length % (uint)Vector.Count); - /// - public sealed override void ConvertToRgbInplace(in ComponentValues values) + int simdCount = length - remainder; + if (simdCount > 0) { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInplaceVectorized(values.Slice(0, simdCount)); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertToRgbInplaceScalarRemainder(values.Slice(simdCount, remainder)); - } + this.ConvertFromRgbVectorized( + values.Slice(0, simdCount), + r.Slice(0, simdCount), + g.Slice(0, simdCount), + b.Slice(0, simdCount)); } - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span r, Span g, Span b) + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX1/AVX2 implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels + if (remainder > 0) { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - r.Slice(0, simdCount), - g.Slice(0, simdCount), - b.Slice(0, simdCount)); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - r.Slice(simdCount, remainder), - g.Slice(simdCount, remainder), - b.Slice(simdCount, remainder)); - } + this.ConvertFromRgbScalarRemainder( + values.Slice(simdCount, remainder), + r.Slice(simdCount, remainder), + g.Slice(simdCount, remainder), + b.Slice(simdCount, remainder)); } - - /// - /// Converts planar jpeg component values in - /// to RGB color space inplace using API. - /// - /// The input/ouptut as a stack-only struct - protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/ouptut as a stack-only struct - protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); } + + /// + /// Converts planar jpeg component values in + /// to RGB color space inplace using API. + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceVectorized(in ComponentValues values); + + /// + /// Converts remainder of the planar jpeg component values after + /// conversion in . + /// + /// The input/ouptut as a stack-only struct + protected abstract void ConvertToRgbInplaceScalarRemainder(in ComponentValues values); + + /// + /// Converts RGB lanes to jpeg component values using API. + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); + + /// + /// Converts remainder of RGB lanes to jpeg component values after + /// conversion in . + /// + /// Jpeg component values. + /// Red colors lane. + /// Green colors lane. + /// Blue colors lane. + protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs b/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs index 0c0b0e7924..49766ace3d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ComponentType.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal enum ComponentType { - internal enum ComponentType - { - Huffman = 0, + Huffman = 0, - Arithmetic = 1 - } + Arithmetic = 1 } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs index acf43092c6..b3463b5169 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs @@ -1,110 +1,106 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +/// +/// Provides information about the Adobe marker segment. +/// +/// See the included 5116.DCT.pdf file in the source for more information. +internal readonly struct AdobeMarker : IEquatable { /// - /// Provides information about the Adobe marker segment. + /// Gets the length of an adobe marker segment. /// - /// See the included 5116.DCT.pdf file in the source for more information. - internal readonly struct AdobeMarker : IEquatable - { - /// - /// Gets the length of an adobe marker segment. - /// - public const int Length = 12; + public const int Length = 12; - /// - /// Initializes a new instance of the struct. - /// - /// The DCT encode version - /// The horizontal downsampling hint used for DCT encoding - /// The vertical downsampling hint used for DCT encoding - /// The color transform model used - private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform) - { - this.DCTEncodeVersion = dctEncodeVersion; - this.APP14Flags0 = app14Flags0; - this.APP14Flags1 = app14Flags1; - this.ColorTransform = colorTransform; - } + /// + /// Initializes a new instance of the struct. + /// + /// The DCT encode version + /// The horizontal downsampling hint used for DCT encoding + /// The vertical downsampling hint used for DCT encoding + /// The color transform model used + private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1, byte colorTransform) + { + this.DCTEncodeVersion = dctEncodeVersion; + this.APP14Flags0 = app14Flags0; + this.APP14Flags1 = app14Flags1; + this.ColorTransform = colorTransform; + } - /// - /// Gets the DCT Encode Version - /// - public short DCTEncodeVersion { get; } + /// + /// Gets the DCT Encode Version + /// + public short DCTEncodeVersion { get; } - /// - /// Gets the horizontal downsampling hint used for DCT encoding - /// 0x0 : (none - Chop) - /// Bit 15 : Encoded with Blend=1 downsampling. - /// - public short APP14Flags0 { get; } + /// + /// Gets the horizontal downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) + /// Bit 15 : Encoded with Blend=1 downsampling. + /// + public short APP14Flags0 { get; } - /// - /// Gets the vertical downsampling hint used for DCT encoding - /// 0x0 : (none - Chop) - /// Bit 15 : Encoded with Blend=1 downsampling - /// - public short APP14Flags1 { get; } + /// + /// Gets the vertical downsampling hint used for DCT encoding + /// 0x0 : (none - Chop) + /// Bit 15 : Encoded with Blend=1 downsampling + /// + public short APP14Flags1 { get; } - /// - /// Gets the colorspace transform model used - /// 00 : Unknown (RGB or CMYK) - /// 01 : YCbCr - /// 02 : YCCK - /// - public byte ColorTransform { get; } + /// + /// Gets the colorspace transform model used + /// 00 : Unknown (RGB or CMYK) + /// 01 : YCbCr + /// 02 : YCCK + /// + public byte ColorTransform { get; } - /// - /// Converts the specified byte array representation of an Adobe marker to its equivalent and - /// returns a value that indicates whether the conversion succeeded. - /// - /// The byte array containing metadata to parse. - /// The marker to return. - public static bool TryParse(byte[] bytes, out AdobeMarker marker) + /// + /// Converts the specified byte array representation of an Adobe marker to its equivalent and + /// returns a value that indicates whether the conversion succeeded. + /// + /// The byte array containing metadata to parse. + /// The marker to return. + public static bool TryParse(byte[] bytes, out AdobeMarker marker) + { + if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker)) { - if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker)) - { - short dctEncodeVersion = (short)((bytes[5] << 8) | bytes[6]); - short app14Flags0 = (short)((bytes[7] << 8) | bytes[8]); - short app14Flags1 = (short)((bytes[9] << 8) | bytes[10]); - byte colorTransform = bytes[11]; + short dctEncodeVersion = (short)((bytes[5] << 8) | bytes[6]); + short app14Flags0 = (short)((bytes[7] << 8) | bytes[8]); + short app14Flags1 = (short)((bytes[9] << 8) | bytes[10]); + byte colorTransform = bytes[11]; - marker = new AdobeMarker(dctEncodeVersion, app14Flags0, app14Flags1, colorTransform); - return true; - } - - marker = default; - return false; + marker = new AdobeMarker(dctEncodeVersion, app14Flags0, app14Flags1, colorTransform); + return true; } - /// - public bool Equals(AdobeMarker other) - { - return this.DCTEncodeVersion == other.DCTEncodeVersion - && this.APP14Flags0 == other.APP14Flags0 - && this.APP14Flags1 == other.APP14Flags1 - && this.ColorTransform == other.ColorTransform; - } + marker = default; + return false; + } - /// - public override bool Equals(object obj) - { - return obj is AdobeMarker other && this.Equals(other); - } + /// + public bool Equals(AdobeMarker other) + { + return this.DCTEncodeVersion == other.DCTEncodeVersion + && this.APP14Flags0 == other.APP14Flags0 + && this.APP14Flags1 == other.APP14Flags1 + && this.ColorTransform == other.ColorTransform; + } - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.DCTEncodeVersion, - this.APP14Flags0, - this.APP14Flags1, - this.ColorTransform); - } + /// + public override bool Equals(object obj) + { + return obj is AdobeMarker other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.DCTEncodeVersion, + this.APP14Flags0, + this.APP14Flags1, + this.ColorTransform); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs index 043df57069..869b9ae01b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingComponent.cs @@ -3,28 +3,27 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +internal class ArithmeticDecodingComponent : JpegComponent { - internal class ArithmeticDecodingComponent : JpegComponent + public ArithmeticDecodingComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + : base(memoryAllocator, frame, id, horizontalFactor, verticalFactor, quantizationTableIndex, index) { - public ArithmeticDecodingComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) - : base(memoryAllocator, frame, id, horizontalFactor, verticalFactor, quantizationTableIndex, index) - { - } + } - /// - /// Gets or sets the dc context. - /// - public int DcContext { get; set; } + /// + /// Gets or sets the dc context. + /// + public int DcContext { get; set; } - /// - /// Gets or sets the dc statistics. - /// - public ArithmeticStatistics DcStatistics { get; set; } + /// + /// Gets or sets the dc statistics. + /// + public ArithmeticStatistics DcStatistics { get; set; } - /// - /// Gets or sets the ac statistics. - /// - public ArithmeticStatistics AcStatistics { get; set; } - } + /// + /// Gets or sets the ac statistics. + /// + public ArithmeticStatistics AcStatistics { get; set; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs index 4c02852d53..f62dfbb6fa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticDecodingTable.cs @@ -1,43 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +internal class ArithmeticDecodingTable { - internal class ArithmeticDecodingTable + public ArithmeticDecodingTable(byte tableClass, byte identifier) { - public ArithmeticDecodingTable(byte tableClass, byte identifier) - { - this.TableClass = tableClass; - this.Identifier = identifier; - } + this.TableClass = tableClass; + this.Identifier = identifier; + } - public byte TableClass { get; } + public byte TableClass { get; } - public byte Identifier { get; } + public byte Identifier { get; } - public byte ConditioningTableValue { get; private set; } + public byte ConditioningTableValue { get; private set; } - public int DcL { get; private set; } + public int DcL { get; private set; } - public int DcU { get; private set; } + public int DcU { get; private set; } - public int AcKx { get; private set; } + public int AcKx { get; private set; } - public void Configure(byte conditioningTableValue) + public void Configure(byte conditioningTableValue) + { + this.ConditioningTableValue = conditioningTableValue; + if (this.TableClass == 0) + { + this.DcL = conditioningTableValue & 0x0F; + this.DcU = conditioningTableValue >> 4; + this.AcKx = 0; + } + else { - this.ConditioningTableValue = conditioningTableValue; - if (this.TableClass == 0) - { - this.DcL = conditioningTableValue & 0x0F; - this.DcU = conditioningTableValue >> 4; - this.AcKx = 0; - } - else - { - this.DcL = 0; - this.DcU = 0; - this.AcKx = conditioningTableValue; - } + this.DcL = 0; + this.DcU = 0; + this.AcKx = conditioningTableValue; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs index 21727bc99c..a62efa8125 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs @@ -1,973 +1,720 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Decodes a arithmetic encoded spectral scan. +/// Based on https://github.com/yigolden/JpegLibrary/blob/main/src/JpegLibrary/ScanDecoder/JpegArithmeticScanDecoder.cs +/// +internal class ArithmeticScanDecoder : IJpegScanDecoder { + private readonly BufferedReadStream stream; + + private int c; + private int a; + private int ct; + /// - /// Decodes a arithmetic encoded spectral scan. - /// Based on https://github.com/yigolden/JpegLibrary/blob/main/src/JpegLibrary/ScanDecoder/JpegArithmeticScanDecoder.cs + /// instance containing decoding-related information. /// - internal class ArithmeticScanDecoder : IJpegScanDecoder - { - private readonly BufferedReadStream stream; + private JpegFrame frame; - private int c; - private int a; - private int ct; + /// + /// Shortcut for .Components. + /// + private IJpegComponent[] components; - /// - /// instance containing decoding-related information. - /// - private JpegFrame frame; + /// + /// Number of component in the current scan. + /// + private int scanComponentCount; - /// - /// Shortcut for .Components. - /// - private IJpegComponent[] components; + /// + /// The reset interval determined by RST markers. + /// + private int restartInterval; - /// - /// Number of component in the current scan. - /// - private int scanComponentCount; + /// + /// How many mcu's are left to do. + /// + private int todo; - /// - /// The reset interval determined by RST markers. - /// - private int restartInterval; + private readonly SpectralConverter spectralConverter; - /// - /// How many mcu's are left to do. - /// - private int todo; + private JpegBitReader scanBuffer; - private readonly SpectralConverter spectralConverter; + private ArithmeticDecodingTable[] dcDecodingTables; - private JpegBitReader scanBuffer; + private ArithmeticDecodingTable[] acDecodingTables; - private ArithmeticDecodingTable[] dcDecodingTables; + private readonly byte[] fixedBin = { 113, 0, 0, 0 }; - private ArithmeticDecodingTable[] acDecodingTables; + private readonly CancellationToken cancellationToken; - private readonly byte[] fixedBin = { 113, 0, 0, 0 }; + private static readonly int[] ArithmeticTable = + { + Pack(0x5a1d, 1, 1, 1), + Pack(0x2586, 14, 2, 0), + Pack(0x1114, 16, 3, 0), + Pack(0x080b, 18, 4, 0), + Pack(0x03d8, 20, 5, 0), + Pack(0x01da, 23, 6, 0), + Pack(0x00e5, 25, 7, 0), + Pack(0x006f, 28, 8, 0), + Pack(0x0036, 30, 9, 0), + Pack(0x001a, 33, 10, 0), + Pack(0x000d, 35, 11, 0), + Pack(0x0006, 9, 12, 0), + Pack(0x0003, 10, 13, 0), + Pack(0x0001, 12, 13, 0), + Pack(0x5a7f, 15, 15, 1), + Pack(0x3f25, 36, 16, 0), + Pack(0x2cf2, 38, 17, 0), + Pack(0x207c, 39, 18, 0), + Pack(0x17b9, 40, 19, 0), + Pack(0x1182, 42, 20, 0), + Pack(0x0cef, 43, 21, 0), + Pack(0x09a1, 45, 22, 0), + Pack(0x072f, 46, 23, 0), + Pack(0x055c, 48, 24, 0), + Pack(0x0406, 49, 25, 0), + Pack(0x0303, 51, 26, 0), + Pack(0x0240, 52, 27, 0), + Pack(0x01b1, 54, 28, 0), + Pack(0x0144, 56, 29, 0), + Pack(0x00f5, 57, 30, 0), + Pack(0x00b7, 59, 31, 0), + Pack(0x008a, 60, 32, 0), + Pack(0x0068, 62, 33, 0), + Pack(0x004e, 63, 34, 0), + Pack(0x003b, 32, 35, 0), + Pack(0x002c, 33, 9, 0), + Pack(0x5ae1, 37, 37, 1), + Pack(0x484c, 64, 38, 0), + Pack(0x3a0d, 65, 39, 0), + Pack(0x2ef1, 67, 40, 0), + Pack(0x261f, 68, 41, 0), + Pack(0x1f33, 69, 42, 0), + Pack(0x19a8, 70, 43, 0), + Pack(0x1518, 72, 44, 0), + Pack(0x1177, 73, 45, 0), + Pack(0x0e74, 74, 46, 0), + Pack(0x0bfb, 75, 47, 0), + Pack(0x09f8, 77, 48, 0), + Pack(0x0861, 78, 49, 0), + Pack(0x0706, 79, 50, 0), + Pack(0x05cd, 48, 51, 0), + Pack(0x04de, 50, 52, 0), + Pack(0x040f, 50, 53, 0), + Pack(0x0363, 51, 54, 0), + Pack(0x02d4, 52, 55, 0), + Pack(0x025c, 53, 56, 0), + Pack(0x01f8, 54, 57, 0), + Pack(0x01a4, 55, 58, 0), + Pack(0x0160, 56, 59, 0), + Pack(0x0125, 57, 60, 0), + Pack(0x00f6, 58, 61, 0), + Pack(0x00cb, 59, 62, 0), + Pack(0x00ab, 61, 63, 0), + Pack(0x008f, 61, 32, 0), + Pack(0x5b12, 65, 65, 1), + Pack(0x4d04, 80, 66, 0), + Pack(0x412c, 81, 67, 0), + Pack(0x37d8, 82, 68, 0), + Pack(0x2fe8, 83, 69, 0), + Pack(0x293c, 84, 70, 0), + Pack(0x2379, 86, 71, 0), + Pack(0x1edf, 87, 72, 0), + Pack(0x1aa9, 87, 73, 0), + Pack(0x174e, 72, 74, 0), + Pack(0x1424, 72, 75, 0), + Pack(0x119c, 74, 76, 0), + Pack(0x0f6b, 74, 77, 0), + Pack(0x0d51, 75, 78, 0), + Pack(0x0bb6, 77, 79, 0), + Pack(0x0a40, 77, 48, 0), + Pack(0x5832, 80, 81, 1), + Pack(0x4d1c, 88, 82, 0), + Pack(0x438e, 89, 83, 0), + Pack(0x3bdd, 90, 84, 0), + Pack(0x34ee, 91, 85, 0), + Pack(0x2eae, 92, 86, 0), + Pack(0x299a, 93, 87, 0), + Pack(0x2516, 86, 71, 0), + Pack(0x5570, 88, 89, 1), + Pack(0x4ca9, 95, 90, 0), + Pack(0x44d9, 96, 91, 0), + Pack(0x3e22, 97, 92, 0), + Pack(0x3824, 99, 93, 0), + Pack(0x32b4, 99, 94, 0), + Pack(0x2e17, 93, 86, 0), + Pack(0x56a8, 95, 96, 1), + Pack(0x4f46, 101, 97, 0), + Pack(0x47e5, 102, 98, 0), + Pack(0x41cf, 103, 99, 0), + Pack(0x3c3d, 104, 100, 0), + Pack(0x375e, 99, 93, 0), + Pack(0x5231, 105, 102, 0), + Pack(0x4c0f, 106, 103, 0), + Pack(0x4639, 107, 104, 0), + Pack(0x415e, 103, 99, 0), + Pack(0x5627, 105, 106, 1), + Pack(0x50e7, 108, 107, 0), + Pack(0x4b85, 109, 103, 0), + Pack(0x5597, 110, 109, 0), + Pack(0x504f, 111, 107, 0), + Pack(0x5a10, 110, 111, 1), + Pack(0x5522, 112, 109, 0), + Pack(0x59eb, 112, 111, 1), + + // This last entry is used for fixed probability estimate of 0.5 + // as suggested in Section 10.3 Table 5 of ITU-T Rec. T.851. + Pack(0x5a1d, 113, 113, 0) + }; + + private readonly List statistics = new(); - private readonly CancellationToken cancellationToken; + /// + /// Initializes a new instance of the class. + /// + /// The input stream. + /// Spectral to pixel converter. + /// The token to monitor cancellation. + public ArithmeticScanDecoder(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken) + { + this.stream = stream; + this.spectralConverter = converter; + this.cancellationToken = cancellationToken; - private static readonly int[] ArithmeticTable = - { - Pack(0x5a1d, 1, 1, 1), - Pack(0x2586, 14, 2, 0), - Pack(0x1114, 16, 3, 0), - Pack(0x080b, 18, 4, 0), - Pack(0x03d8, 20, 5, 0), - Pack(0x01da, 23, 6, 0), - Pack(0x00e5, 25, 7, 0), - Pack(0x006f, 28, 8, 0), - Pack(0x0036, 30, 9, 0), - Pack(0x001a, 33, 10, 0), - Pack(0x000d, 35, 11, 0), - Pack(0x0006, 9, 12, 0), - Pack(0x0003, 10, 13, 0), - Pack(0x0001, 12, 13, 0), - Pack(0x5a7f, 15, 15, 1), - Pack(0x3f25, 36, 16, 0), - Pack(0x2cf2, 38, 17, 0), - Pack(0x207c, 39, 18, 0), - Pack(0x17b9, 40, 19, 0), - Pack(0x1182, 42, 20, 0), - Pack(0x0cef, 43, 21, 0), - Pack(0x09a1, 45, 22, 0), - Pack(0x072f, 46, 23, 0), - Pack(0x055c, 48, 24, 0), - Pack(0x0406, 49, 25, 0), - Pack(0x0303, 51, 26, 0), - Pack(0x0240, 52, 27, 0), - Pack(0x01b1, 54, 28, 0), - Pack(0x0144, 56, 29, 0), - Pack(0x00f5, 57, 30, 0), - Pack(0x00b7, 59, 31, 0), - Pack(0x008a, 60, 32, 0), - Pack(0x0068, 62, 33, 0), - Pack(0x004e, 63, 34, 0), - Pack(0x003b, 32, 35, 0), - Pack(0x002c, 33, 9, 0), - Pack(0x5ae1, 37, 37, 1), - Pack(0x484c, 64, 38, 0), - Pack(0x3a0d, 65, 39, 0), - Pack(0x2ef1, 67, 40, 0), - Pack(0x261f, 68, 41, 0), - Pack(0x1f33, 69, 42, 0), - Pack(0x19a8, 70, 43, 0), - Pack(0x1518, 72, 44, 0), - Pack(0x1177, 73, 45, 0), - Pack(0x0e74, 74, 46, 0), - Pack(0x0bfb, 75, 47, 0), - Pack(0x09f8, 77, 48, 0), - Pack(0x0861, 78, 49, 0), - Pack(0x0706, 79, 50, 0), - Pack(0x05cd, 48, 51, 0), - Pack(0x04de, 50, 52, 0), - Pack(0x040f, 50, 53, 0), - Pack(0x0363, 51, 54, 0), - Pack(0x02d4, 52, 55, 0), - Pack(0x025c, 53, 56, 0), - Pack(0x01f8, 54, 57, 0), - Pack(0x01a4, 55, 58, 0), - Pack(0x0160, 56, 59, 0), - Pack(0x0125, 57, 60, 0), - Pack(0x00f6, 58, 61, 0), - Pack(0x00cb, 59, 62, 0), - Pack(0x00ab, 61, 63, 0), - Pack(0x008f, 61, 32, 0), - Pack(0x5b12, 65, 65, 1), - Pack(0x4d04, 80, 66, 0), - Pack(0x412c, 81, 67, 0), - Pack(0x37d8, 82, 68, 0), - Pack(0x2fe8, 83, 69, 0), - Pack(0x293c, 84, 70, 0), - Pack(0x2379, 86, 71, 0), - Pack(0x1edf, 87, 72, 0), - Pack(0x1aa9, 87, 73, 0), - Pack(0x174e, 72, 74, 0), - Pack(0x1424, 72, 75, 0), - Pack(0x119c, 74, 76, 0), - Pack(0x0f6b, 74, 77, 0), - Pack(0x0d51, 75, 78, 0), - Pack(0x0bb6, 77, 79, 0), - Pack(0x0a40, 77, 48, 0), - Pack(0x5832, 80, 81, 1), - Pack(0x4d1c, 88, 82, 0), - Pack(0x438e, 89, 83, 0), - Pack(0x3bdd, 90, 84, 0), - Pack(0x34ee, 91, 85, 0), - Pack(0x2eae, 92, 86, 0), - Pack(0x299a, 93, 87, 0), - Pack(0x2516, 86, 71, 0), - Pack(0x5570, 88, 89, 1), - Pack(0x4ca9, 95, 90, 0), - Pack(0x44d9, 96, 91, 0), - Pack(0x3e22, 97, 92, 0), - Pack(0x3824, 99, 93, 0), - Pack(0x32b4, 99, 94, 0), - Pack(0x2e17, 93, 86, 0), - Pack(0x56a8, 95, 96, 1), - Pack(0x4f46, 101, 97, 0), - Pack(0x47e5, 102, 98, 0), - Pack(0x41cf, 103, 99, 0), - Pack(0x3c3d, 104, 100, 0), - Pack(0x375e, 99, 93, 0), - Pack(0x5231, 105, 102, 0), - Pack(0x4c0f, 106, 103, 0), - Pack(0x4639, 107, 104, 0), - Pack(0x415e, 103, 99, 0), - Pack(0x5627, 105, 106, 1), - Pack(0x50e7, 108, 107, 0), - Pack(0x4b85, 109, 103, 0), - Pack(0x5597, 110, 109, 0), - Pack(0x504f, 111, 107, 0), - Pack(0x5a10, 110, 111, 1), - Pack(0x5522, 112, 109, 0), - Pack(0x59eb, 112, 111, 1), - - // This last entry is used for fixed probability estimate of 0.5 - // as suggested in Section 10.3 Table 5 of ITU-T Rec. T.851. - Pack(0x5a1d, 113, 113, 0) - }; - - private readonly List statistics = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - /// Spectral to pixel converter. - /// The token to monitor cancellation. - public ArithmeticScanDecoder(BufferedReadStream stream, SpectralConverter converter, CancellationToken cancellationToken) - { - this.stream = stream; - this.spectralConverter = converter; - this.cancellationToken = cancellationToken; + this.c = 0; + this.a = 0; + this.ct = -16; // Force reading 2 initial bytes to fill C. + } - this.c = 0; - this.a = 0; - this.ct = -16; // Force reading 2 initial bytes to fill C. + /// + public int ResetInterval + { + set + { + this.restartInterval = value; + this.todo = value; } + } + + /// + public int SpectralStart { get; set; } + + /// + public int SpectralEnd { get; set; } - /// - public int ResetInterval + /// + public int SuccessiveHigh { get; set; } + + /// + public int SuccessiveLow { get; set; } + + public void InitDecodingTables(List arithmeticDecodingTables) + { + for (int i = 0; i < this.components.Length; i++) { - set - { - this.restartInterval = value; - this.todo = value; - } + ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; + this.dcDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, true, component.DcTableId); + component.DcStatistics = this.CreateOrGetStatisticsBin(true, component.DcTableId); + this.acDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, false, component.AcTableId); + component.AcStatistics = this.CreateOrGetStatisticsBin(false, component.AcTableId); } + } - /// - public int SpectralStart { get; set; } + private ref byte GetFixedBinReference() => ref this.fixedBin[0]; - /// - public int SpectralEnd { get; set; } + /// + /// Decodes the entropy coded data. + /// + /// Component count in the current scan. + public void ParseEntropyCodedData(int scanComponentCount) + { + this.cancellationToken.ThrowIfCancellationRequested(); - /// - public int SuccessiveHigh { get; set; } + this.scanComponentCount = scanComponentCount; - /// - public int SuccessiveLow { get; set; } + this.scanBuffer = new JpegBitReader(this.stream); - public void InitDecodingTables(List arithmeticDecodingTables) + this.frame.AllocateComponents(); + + if (this.frame.Progressive) { - for (int i = 0; i < this.components.Length; i++) - { - ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; - this.dcDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, true, component.DcTableId); - component.DcStatistics = this.CreateOrGetStatisticsBin(true, component.DcTableId); - this.acDecodingTables[i] = GetArithmeticTable(arithmeticDecodingTables, false, component.AcTableId); - component.AcStatistics = this.CreateOrGetStatisticsBin(false, component.AcTableId); - } + this.ParseProgressiveData(); + } + else + { + this.ParseBaselineData(); } - private ref byte GetFixedBinReference() => ref this.fixedBin[0]; - - /// - /// Decodes the entropy coded data. - /// - /// Component count in the current scan. - public void ParseEntropyCodedData(int scanComponentCount) + if (this.scanBuffer.HasBadMarker()) { - this.cancellationToken.ThrowIfCancellationRequested(); + this.stream.Position = this.scanBuffer.MarkerPosition; + } + } - this.scanComponentCount = scanComponentCount; + /// + public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.frame = frame; + this.components = frame.Components; - this.scanBuffer = new JpegBitReader(this.stream); + this.dcDecodingTables = new ArithmeticDecodingTable[this.components.Length]; + this.acDecodingTables = new ArithmeticDecodingTable[this.components.Length]; - this.frame.AllocateComponents(); + this.spectralConverter.InjectFrameData(frame, jpegData); + } - if (this.frame.Progressive) - { - this.ParseProgressiveData(); - } - else - { - this.ParseBaselineData(); - } + private static ArithmeticDecodingTable GetArithmeticTable(List arithmeticDecodingTables, bool isDcTable, int identifier) + { + int tableClass = isDcTable ? 0 : 1; - if (this.scanBuffer.HasBadMarker()) + foreach (ArithmeticDecodingTable item in arithmeticDecodingTables) + { + if (item.TableClass == tableClass && item.Identifier == identifier) { - this.stream.Position = this.scanBuffer.MarkerPosition; + return item; } } - /// - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.components = frame.Components; - - this.dcDecodingTables = new ArithmeticDecodingTable[this.components.Length]; - this.acDecodingTables = new ArithmeticDecodingTable[this.components.Length]; - - this.spectralConverter.InjectFrameData(frame, jpegData); - } + return null; + } - private static ArithmeticDecodingTable GetArithmeticTable(List arithmeticDecodingTables, bool isDcTable, int identifier) + private ArithmeticStatistics CreateOrGetStatisticsBin(bool dc, int identifier, bool reset = false) + { + foreach (ArithmeticStatistics item in this.statistics) { - int tableClass = isDcTable ? 0 : 1; - - foreach (ArithmeticDecodingTable item in arithmeticDecodingTables) + if (item.IsDcStatistics == dc && item.Identifier == identifier) { - if (item.TableClass == tableClass && item.Identifier == identifier) + if (reset) { - return item; + item.Reset(); } - } - return null; + return item; + } } - private ArithmeticStatistics CreateOrGetStatisticsBin(bool dc, int identifier, bool reset = false) + ArithmeticStatistics statistic = new(dc, identifier); + this.statistics.Add(statistic); + return statistic; + } + + private void ParseBaselineData() + { + for (int i = 0; i < this.components.Length; i++) { - foreach (ArithmeticStatistics item in this.statistics) - { - if (item.IsDcStatistics == dc && item.Identifier == identifier) - { - if (reset) - { - item.Reset(); - } + ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i]; + component.DcPredictor = 0; + component.DcContext = 0; + component.DcStatistics?.Reset(); + component.AcStatistics?.Reset(); + } - return item; - } - } + this.Reset(); - ArithmeticStatistics statistic = new(dc, identifier); - this.statistics.Add(statistic); - return statistic; + if (this.scanComponentCount != 1) + { + this.spectralConverter.PrepareForDecoding(); + this.ParseBaselineDataInterleaved(); + this.spectralConverter.CommitConversion(); } + else if (this.frame.ComponentCount == 1) + { + this.spectralConverter.PrepareForDecoding(); + this.ParseBaselineDataSingleComponent(); + this.spectralConverter.CommitConversion(); + } + else + { + this.ParseBaselineDataNonInterleaved(); + } + } + + private void ParseProgressiveData() + { + this.CheckProgressiveData(); - private void ParseBaselineData() + foreach (ArithmeticDecodingComponent component in this.components) { - for (int i = 0; i < this.components.Length; i++) + if (this.SpectralStart == 0 && this.SuccessiveHigh == 0) { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i]; component.DcPredictor = 0; component.DcContext = 0; component.DcStatistics?.Reset(); - component.AcStatistics?.Reset(); } - this.Reset(); - - if (this.scanComponentCount != 1) + if (this.SpectralStart != 0) { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataInterleaved(); - this.spectralConverter.CommitConversion(); - } - else if (this.frame.ComponentCount == 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataSingleComponent(); - this.spectralConverter.CommitConversion(); - } - else - { - this.ParseBaselineDataNonInterleaved(); + component.AcStatistics?.Reset(); } } - private void ParseProgressiveData() - { - this.CheckProgressiveData(); + this.Reset(); - foreach (ArithmeticDecodingComponent component in this.components) - { - if (this.SpectralStart == 0 && this.SuccessiveHigh == 0) - { - component.DcPredictor = 0; - component.DcContext = 0; - component.DcStatistics?.Reset(); - } - - if (this.SpectralStart != 0) - { - component.AcStatistics?.Reset(); - } - } - - this.Reset(); - - if (this.scanComponentCount == 1) - { - this.ParseProgressiveDataNonInterleaved(); - } - else - { - this.ParseProgressiveDataInterleaved(); - } + if (this.scanComponentCount == 1) + { + this.ParseProgressiveDataNonInterleaved(); + } + else + { + this.ParseProgressiveDataInterleaved(); } + } - private void CheckProgressiveData() + private void CheckProgressiveData() + { + // Validate successive scan parameters. + // Logic has been adapted from libjpeg. + // See Table B.3 – Scan header parameter size and values. itu-t81.pdf + bool invalid = false; + if (this.SpectralStart == 0) { - // Validate successive scan parameters. - // Logic has been adapted from libjpeg. - // See Table B.3 – Scan header parameter size and values. itu-t81.pdf - bool invalid = false; - if (this.SpectralStart == 0) + if (this.SpectralEnd != 0) { - if (this.SpectralEnd != 0) - { - invalid = true; - } + invalid = true; } - else + } + else + { + // Need not check Ss/Se < 0 since they came from unsigned bytes. + if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) { - // Need not check Ss/Se < 0 since they came from unsigned bytes. - if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) - { - invalid = true; - } - - // AC scans may have only one component. - if (this.scanComponentCount != 1) - { - invalid = true; - } + invalid = true; } - if (this.SuccessiveHigh != 0) + // AC scans may have only one component. + if (this.scanComponentCount != 1) { - // Successive approximation refinement scan: must have Al = Ah-1. - if (this.SuccessiveHigh - 1 != this.SuccessiveLow) - { - invalid = true; - } + invalid = true; } + } - // TODO: How does this affect 12bit jpegs. - // According to libjpeg the range covers 8bit only? - if (this.SuccessiveLow > 13) + if (this.SuccessiveHigh != 0) + { + // Successive approximation refinement scan: must have Al = Ah-1. + if (this.SuccessiveHigh - 1 != this.SuccessiveLow) { invalid = true; } + } - if (invalid) - { - JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); - } + // TODO: How does this affect 12bit jpegs. + // According to libjpeg the range covers 8bit only? + if (this.SuccessiveLow > 13) + { + invalid = true; } - private void ParseBaselineDataInterleaved() + if (invalid) { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader reader = ref this.scanBuffer; + JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); + } + } - for (int j = 0; j < mcusPerColumn; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); + private void ParseBaselineDataInterleaved() + { + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref JpegBitReader reader = ref this.scanBuffer; - // Decode from binary to spectral. - for (int i = 0; i < mcusPerLine; i++) + for (int j = 0; j < mcusPerColumn; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + // Decode from binary to spectral. + for (int i = 0; i < mcusPerLine; i++) + { + // Scan an interleaved mcu... process components in order. + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < this.scanComponentCount; k++) { - // Scan an interleaved mcu... process components in order. - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; + int order = this.frame.ComponentOrder[k]; + ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; + ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; + ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component. - int mcuColMulh = mcuCol * h; - for (int y = 0; y < v; y++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component. + int mcuColMulh = mcuCol * h; + for (int y = 0; y < v; y++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int x = 0; x < h; x++) + for (int x = 0; x < h; x++) + { + if (reader.NoData) { - if (reader.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(); - return; - } - - int blockCol = mcuColMulh + x; - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol), - ref acDecodingTable, - ref dcDecodingTable); + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); + return; } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval. - mcu++; - this.HandleRestart(); - } - // Convert from spectral to actual pixels via given converter. - this.spectralConverter.ConvertStrideBaseline(); - } - } + int blockCol = mcuColMulh + x; - private void ParseBaselineDataSingleComponent() - { - ArithmeticDecodingComponent component = this.frame.Components[0] as ArithmeticDecodingComponent; - int mcuLines = this.frame.McusPerColumn; - int w = component.WidthInBlocks; - int h = component.SamplingFactors.Height; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - ref JpegBitReader reader = ref this.scanBuffer; - - for (int i = 0; i < mcuLines; i++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - // Decode from binary to spectral. - for (int j = 0; j < h; j++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int k = 0; k < w; k++) - { - if (reader.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(); - return; + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol), + ref acDecodingTable, + ref dcDecodingTable); } - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, (nint)(uint)k), - ref acDecodingTable, - ref dcDecodingTable); - - this.HandleRestart(); } } - // Convert from spectral to actual pixels via given converter. - this.spectralConverter.ConvertStrideBaseline(); + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval. + mcu++; + this.HandleRestart(); } + + // Convert from spectral to actual pixels via given converter. + this.spectralConverter.ConvertStrideBaseline(); } + } - private void ParseBaselineDataNonInterleaved() - { - ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[this.frame.ComponentOrder[0]]; - ref JpegBitReader reader = ref this.scanBuffer; + private void ParseBaselineDataSingleComponent() + { + ArithmeticDecodingComponent component = this.frame.Components[0] as ArithmeticDecodingComponent; + int mcuLines = this.frame.McusPerColumn; + int w = component.WidthInBlocks; + int h = component.SamplingFactors.Height; + ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; + ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; + ref JpegBitReader reader = ref this.scanBuffer; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; + for (int i = 0; i < mcuLines; i++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + // Decode from binary to spectral. for (int j = 0; j < h; j++) { - this.cancellationToken.ThrowIfCancellationRequested(); Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int i = 0; i < w; i++) + for (int k = 0; k < w; k++) { if (reader.NoData) { + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); return; } this.DecodeBlockBaseline( component, - ref Unsafe.Add(ref blockRef, (nint)(uint)i), + ref Unsafe.Add(ref blockRef, (nint)(uint)k), ref acDecodingTable, ref dcDecodingTable); this.HandleRestart(); } } - } - - private void ParseProgressiveDataInterleaved() - { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader reader = ref this.scanBuffer; - for (int j = 0; j < mcusPerColumn; j++) - { - for (int i = 0; i < mcusPerLine; i++) - { - // Scan an interleaved mcu... process components in order. - int mcuRow = Math.DivRem(mcu, mcusPerLine, out int mcuCol); - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; - - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component. - int mcuColMulh = mcuCol * h; - for (int y = 0; y < v; y++) - { - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int x = 0; x < h; x++) - { - if (reader.NoData) - { - return; - } - - int blockCol = mcuColMulh + x; - - this.DecodeBlockProgressiveDc( - component, - ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol), - ref dcDecodingTable); - } - } - } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval. - mcu++; - this.HandleRestart(); - } - } + // Convert from spectral to actual pixels via given converter. + this.spectralConverter.ConvertStrideBaseline(); } + } - private void ParseProgressiveDataNonInterleaved() - { - ArithmeticDecodingComponent component = this.components[this.frame.ComponentOrder[0]] as ArithmeticDecodingComponent; - ref JpegBitReader reader = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - if (this.SpectralStart == 0) - { - ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int i = 0; i < w; i++) - { - if (reader.NoData) - { - return; - } - - this.DecodeBlockProgressiveDc( - component, - ref Unsafe.Add(ref blockRef, (nint)(uint)i), - ref dcDecodingTable); - - this.HandleRestart(); - } - } - } - else - { - ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + private void ParseBaselineDataNonInterleaved() + { + ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[this.frame.ComponentOrder[0]]; + ref JpegBitReader reader = ref this.scanBuffer; - for (int i = 0; i < w; i++) - { - if (reader.NoData) - { - return; - } + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - this.DecodeBlockProgressiveAc( - component, - ref Unsafe.Add(ref blockRef, (nint)(uint)i), - ref acDecodingTable); + ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; + ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - this.HandleRestart(); - } - } - } - } - - private void DecodeBlockProgressiveDc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable dcTable) + for (int j = 0; j < h; j++) { - if (dcTable == null) - { - JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing"); - } - - ref JpegBitReader reader = ref this.scanBuffer; - ref short blockDataRef = ref Unsafe.As(ref block); + this.cancellationToken.ThrowIfCancellationRequested(); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - if (this.SuccessiveHigh == 0) + for (int i = 0; i < w; i++) { - // First scan - // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients. - - // Table F.4: Point to statistics bin S0 for DC coefficient coding. - ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), component.DcContext); - - // Figure F.19: Decode_DC_DIFF - if (this.DecodeBinaryDecision(ref reader, ref st) == 0) + if (reader.NoData) { - component.DcContext = 0; + return; } - else - { - // Figure F.21: Decoding nonzero value v. - // Figure F.22: Decoding the sign of v. - int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)); - st = ref Unsafe.Add(ref st, (nint)(uint)(2 + sign)); - - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) - { - st = ref component.DcStatistics.GetReference(20); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - st = ref Unsafe.Add(ref st, 1); - } - } + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, (nint)(uint)i), + ref acDecodingTable, + ref dcDecodingTable); - // Section F.1.4.4.1.2: Establish dc_context conditioning category. - if (m < (int)((1L << dcTable.DcL) >> 1)) - { - component.DcContext = 0; // Zero diff category. - } - else if (m > (int)((1L << dcTable.DcU) >> 1)) - { - component.DcContext = 12 + (sign * 4); // Large diff category. - } - else - { - component.DcContext = 4 + (sign * 4); // Small diff category. - } - - int v = m; - - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; - } - } - - v += 1; - if (sign != 0) - { - v = -v; - } - - component.DcPredictor = (short)(component.DcPredictor + v); - } - - blockDataRef = (short)(component.DcPredictor << this.SuccessiveLow); - } - else - { - // Refinement scan. - ref byte st = ref this.GetFixedBinReference(); - - blockDataRef |= (short)(this.DecodeBinaryDecision(ref reader, ref st) << this.SuccessiveLow); + this.HandleRestart(); } } + } - private void DecodeBlockProgressiveAc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable acTable) - { - ref JpegBitReader reader = ref this.scanBuffer; - ref short blockDataRef = ref Unsafe.As(ref block); - - ArithmeticStatistics acStatistics = component.AcStatistics; - if (acStatistics == null || acTable == null) - { - JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing"); - } - - if (this.SuccessiveHigh == 0) - { - // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients. - - // Figure F.20: Decode_AC_coefficients. - int start = this.SpectralStart; - int end = this.SpectralEnd; - int low = this.SuccessiveLow; - - for (int k = start; k <= end; k++) - { - ref byte st = ref acStatistics.GetReference(3 * (k - 1)); - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - break; - } - - while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0) - { - st = ref Unsafe.Add(ref st, 3); - k++; - if (k > 63) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - } + private void ParseProgressiveDataInterleaved() + { + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref JpegBitReader reader = ref this.scanBuffer; + + for (int j = 0; j < mcusPerColumn; j++) + { + for (int i = 0; i < mcusPerLine; i++) + { + // Scan an interleaved mcu... process components in order. + int mcuRow = Math.DivRem(mcu, mcusPerLine, out int mcuCol); + for (int k = 0; k < this.scanComponentCount; k++) + { + int order = this.frame.ComponentOrder[k]; + ArithmeticDecodingComponent component = this.components[order] as ArithmeticDecodingComponent; + ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - // Figure F.21: Decoding nonzero value v. - // Figure F.22: Decoding the sign of v. - int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()); - st = ref Unsafe.Add(ref st, 2); + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - // Figure F.23: Decoding the magnitude category of v. - int m = this.DecodeBinaryDecision(ref reader, ref st); - if (m != 0) + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component. + int mcuColMulh = mcuCol * h; + for (int y = 0; y < v; y++) { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int x = 0; x < h; x++) { - m <<= 1; - st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217); - while (this.DecodeBinaryDecision(ref reader, ref st) != 0) + if (reader.NoData) { - if ((m <<= 1) == 0x8000) - { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); - } - - st = ref Unsafe.Add(ref st, 1); + return; } - } - } - int v = m; + int blockCol = mcuColMulh + x; - // Figure F.24: Decoding the magnitude bit pattern of v. - st = ref Unsafe.Add(ref st, 14); - while ((m >>= 1) != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) - { - v |= m; + this.DecodeBlockProgressiveDc( + component, + ref Unsafe.Add(ref blockRef, (nint)(uint)blockCol), + ref dcDecodingTable); } } - - v += 1; - if (sign != 0) - { - v = -v; - } - - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)(v << low); } - } - else - { - // Refinement scan. - this.ReadBlockProgressiveAcRefined(acStatistics, ref blockDataRef); + + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval. + mcu++; + this.HandleRestart(); } } + } - private void ReadBlockProgressiveAcRefined(ArithmeticStatistics acStatistics, ref short blockDataRef) - { - ref JpegBitReader reader = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; + private void ParseProgressiveDataNonInterleaved() + { + ArithmeticDecodingComponent component = this.components[this.frame.ComponentOrder[0]] as ArithmeticDecodingComponent; + ref JpegBitReader reader = ref this.scanBuffer; - int p1 = 1 << this.SuccessiveLow; - int m1 = -1 << this.SuccessiveLow; + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - // Establish EOBx (previous stage end-of-block) index. - int kex = end; - for (; kex > 0; kex--) - { - if (Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[kex]) != 0) - { - break; - } - } + if (this.SpectralStart == 0) + { + ref ArithmeticDecodingTable dcDecodingTable = ref this.dcDecodingTables[component.DcTableId]; - for (int k = start; k <= end; k++) + for (int j = 0; j < h; j++) { - ref byte st = ref acStatistics.GetReference(3 * (k - 1)); - if (k > kex) + this.cancellationToken.ThrowIfCancellationRequested(); + + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) { - if (this.DecodeBinaryDecision(ref reader, ref st) != 0) + if (reader.NoData) { - break; + return; } - } - while (true) - { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - if (coef != 0) - { - if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 2)) != 0) - { - coef = (short)(coef + (coef < 0 ? m1 : p1)); - } + this.DecodeBlockProgressiveDc( + component, + ref Unsafe.Add(ref blockRef, (nint)(uint)i), + ref dcDecodingTable); - break; - } + this.HandleRestart(); + } + } + } + else + { + ref ArithmeticDecodingTable acDecodingTable = ref this.acDecodingTables[component.AcTableId]; - if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) != 0) - { - bool flag = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()) != 0; - coef = (short)(coef + (flag ? m1 : p1)); + for (int j = 0; j < h; j++) + { + this.cancellationToken.ThrowIfCancellationRequested(); - break; - } + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - st = ref Unsafe.Add(ref st, 3); - k++; - if (k > end) + for (int i = 0; i < w; i++) + { + if (reader.NoData) { - JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); + return; } + + this.DecodeBlockProgressiveAc( + component, + ref Unsafe.Add(ref blockRef, (nint)(uint)i), + ref acDecodingTable); + + this.HandleRestart(); } } } + } - private void DecodeBlockBaseline( - ArithmeticDecodingComponent component, - ref Block8x8 destinationBlock, - ref ArithmeticDecodingTable acTable, - ref ArithmeticDecodingTable dcTable) + private void DecodeBlockProgressiveDc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable dcTable) + { + if (dcTable == null) { - if (acTable is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing."); - } - - if (dcTable is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing."); - } + JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing"); + } - ref JpegBitReader reader = ref this.scanBuffer; - ref short destinationRef = ref Unsafe.As(ref destinationBlock); + ref JpegBitReader reader = ref this.scanBuffer; + ref short blockDataRef = ref Unsafe.As(ref block); + if (this.SuccessiveHigh == 0) + { + // First scan // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients. // Table F.4: Point to statistics bin S0 for DC coefficient coding. ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), component.DcContext); - /* Figure F.19: Decode_DC_DIFF */ + // Figure F.19: Decode_DC_DIFF if (this.DecodeBinaryDecision(ref reader, ref st) == 0) { component.DcContext = 0; } else { - // Figure F.21: Decoding nonzero value v - // Figure F.22: Decoding the sign of v + // Figure F.21: Decoding nonzero value v. + // Figure F.22: Decoding the sign of v. int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)); st = ref Unsafe.Add(ref st, (nint)(uint)(2 + sign)); @@ -975,7 +722,6 @@ private void DecodeBlockBaseline( int m = this.DecodeBinaryDecision(ref reader, ref st); if (m != 0) { - // Table F.4: X1 = 20 st = ref component.DcStatistics.GetReference(20); while (this.DecodeBinaryDecision(ref reader, ref st) != 0) { @@ -991,15 +737,15 @@ private void DecodeBlockBaseline( // Section F.1.4.4.1.2: Establish dc_context conditioning category. if (m < (int)((1L << dcTable.DcL) >> 1)) { - component.DcContext = 0; // zero diff category + component.DcContext = 0; // Zero diff category. } else if (m > (int)((1L << dcTable.DcU) >> 1)) { - component.DcContext = 12 + (sign * 4); // large diff category + component.DcContext = 12 + (sign * 4); // Large diff category. } else { - component.DcContext = 4 + (sign * 4); // small diff category + component.DcContext = 4 + (sign * 4); // Small diff category. } int v = m; @@ -1023,17 +769,42 @@ private void DecodeBlockBaseline( component.DcPredictor = (short)(component.DcPredictor + v); } - destinationRef = (short)component.DcPredictor; + blockDataRef = (short)(component.DcPredictor << this.SuccessiveLow); + } + else + { + // Refinement scan. + ref byte st = ref this.GetFixedBinReference(); + + blockDataRef |= (short)(this.DecodeBinaryDecision(ref reader, ref st) << this.SuccessiveLow); + } + } + + private void DecodeBlockProgressiveAc(ArithmeticDecodingComponent component, ref Block8x8 block, ref ArithmeticDecodingTable acTable) + { + ref JpegBitReader reader = ref this.scanBuffer; + ref short blockDataRef = ref Unsafe.As(ref block); + + ArithmeticStatistics acStatistics = component.AcStatistics; + if (acStatistics == null || acTable == null) + { + JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing"); + } + if (this.SuccessiveHigh == 0) + { // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients. - ArithmeticStatistics acStatistics = component.AcStatistics; - for (int k = 1; k <= 63; k++) + // Figure F.20: Decode_AC_coefficients. + int start = this.SpectralStart; + int end = this.SpectralEnd; + int low = this.SuccessiveLow; + + for (int k = start; k <= end; k++) { - st = ref acStatistics.GetReference(3 * (k - 1)); + ref byte st = ref acStatistics.GetReference(3 * (k - 1)); if (this.DecodeBinaryDecision(ref reader, ref st) != 0) { - // EOB flag. break; } @@ -1090,151 +861,376 @@ private void DecodeBlockBaseline( v = -v; } - Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v; + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)(v << low); + } + } + else + { + // Refinement scan. + this.ReadBlockProgressiveAcRefined(acStatistics, ref blockDataRef); + } + } + + private void ReadBlockProgressiveAcRefined(ArithmeticStatistics acStatistics, ref short blockDataRef) + { + ref JpegBitReader reader = ref this.scanBuffer; + int start = this.SpectralStart; + int end = this.SpectralEnd; + + int p1 = 1 << this.SuccessiveLow; + int m1 = -1 << this.SuccessiveLow; + + // Establish EOBx (previous stage end-of-block) index. + int kex = end; + for (; kex > 0; kex--) + { + if (Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[kex]) != 0) + { + break; } } - [MethodImpl(InliningOptions.ShortMethod)] - private bool HandleRestart() + for (int k = start; k <= end; k++) { - if (this.restartInterval > 0 && (--this.todo) == 0) + ref byte st = ref acStatistics.GetReference(3 * (k - 1)); + if (k > kex) { - if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) + if (this.DecodeBinaryDecision(ref reader, ref st) != 0) { - if (!this.scanBuffer.FindNextMarker()) - { - return false; - } + break; } + } - this.todo = this.restartInterval; - - foreach (ArithmeticDecodingComponent component in this.components) + while (true) + { + ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); + if (coef != 0) { - component.DcPredictor = 0; - component.DcContext = 0; - component.DcStatistics?.Reset(); - component.AcStatistics?.Reset(); - } + if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 2)) != 0) + { + coef = (short)(coef + (coef < 0 ? m1 : p1)); + } - this.Reset(); + break; + } - if (this.scanBuffer.HasRestartMarker()) + if (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) != 0) { - this.Reset(); - return true; + bool flag = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()) != 0; + coef = (short)(coef + (flag ? m1 : p1)); + + break; } - if (this.scanBuffer.HasBadMarker()) + st = ref Unsafe.Add(ref st, 3); + k++; + if (k > end) { - this.stream.Position = this.scanBuffer.MarkerPosition; - this.Reset(); - return true; + JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); } } + } + } + + private void DecodeBlockBaseline( + ArithmeticDecodingComponent component, + ref Block8x8 destinationBlock, + ref ArithmeticDecodingTable acTable, + ref ArithmeticDecodingTable dcTable) + { + if (acTable is null) + { + JpegThrowHelper.ThrowInvalidImageContentException("AC table is missing."); + } - return false; + if (dcTable is null) + { + JpegThrowHelper.ThrowInvalidImageContentException("DC table is missing."); } - [MethodImpl(InliningOptions.ShortMethod)] - private void Reset() + ref JpegBitReader reader = ref this.scanBuffer; + ref short destinationRef = ref Unsafe.As(ref destinationBlock); + + // Sections F.2.4.1 & F.1.4.4.1: Decoding of DC coefficients. + + // Table F.4: Point to statistics bin S0 for DC coefficient coding. + ref byte st = ref Unsafe.Add(ref component.DcStatistics.GetReference(), component.DcContext); + + /* Figure F.19: Decode_DC_DIFF */ + if (this.DecodeBinaryDecision(ref reader, ref st) == 0) + { + component.DcContext = 0; + } + else { - for (int i = 0; i < this.components.Length; i++) + // Figure F.21: Decoding nonzero value v + // Figure F.22: Decoding the sign of v + int sign = this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)); + st = ref Unsafe.Add(ref st, (nint)(uint)(2 + sign)); + + // Figure F.23: Decoding the magnitude category of v. + int m = this.DecodeBinaryDecision(ref reader, ref st); + if (m != 0) { - ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; - component.DcPredictor = 0; + // Table F.4: X1 = 20 + st = ref component.DcStatistics.GetReference(20); + while (this.DecodeBinaryDecision(ref reader, ref st) != 0) + { + if ((m <<= 1) == 0x8000) + { + JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); + } + + st = ref Unsafe.Add(ref st, 1); + } + } + + // Section F.1.4.4.1.2: Establish dc_context conditioning category. + if (m < (int)((1L << dcTable.DcL) >> 1)) + { + component.DcContext = 0; // zero diff category + } + else if (m > (int)((1L << dcTable.DcU) >> 1)) + { + component.DcContext = 12 + (sign * 4); // large diff category + } + else + { + component.DcContext = 4 + (sign * 4); // small diff category + } + + int v = m; + + // Figure F.24: Decoding the magnitude bit pattern of v. + st = ref Unsafe.Add(ref st, 14); + while ((m >>= 1) != 0) + { + if (this.DecodeBinaryDecision(ref reader, ref st) != 0) + { + v |= m; + } } - this.c = 0; - this.a = 0; - this.ct = -16; // Force reading 2 initial bytes to fill C. + v += 1; + if (sign != 0) + { + v = -v; + } - this.scanBuffer.Reset(); + component.DcPredictor = (short)(component.DcPredictor + v); } - private int DecodeBinaryDecision(ref JpegBitReader reader, ref byte st) + destinationRef = (short)component.DcPredictor; + + // Sections F.2.4.2 & F.1.4.4.2: Decoding of AC coefficients. + ArithmeticStatistics acStatistics = component.AcStatistics; + + for (int k = 1; k <= 63; k++) { - // Renormalization & data input per section D.2.6 - while (this.a < 0x8000) + st = ref acStatistics.GetReference(3 * (k - 1)); + if (this.DecodeBinaryDecision(ref reader, ref st) != 0) + { + // EOB flag. + break; + } + + while (this.DecodeBinaryDecision(ref reader, ref Unsafe.Add(ref st, 1)) == 0) { - if (--this.ct < 0) + st = ref Unsafe.Add(ref st, 3); + k++; + if (k > 63) { - // Need to fetch next data byte. - reader.CheckBits(); - int data = reader.GetBits(8); + JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); + } + } - // Insert data into C register. - this.c = (this.c << 8) | data; + // Figure F.21: Decoding nonzero value v. + // Figure F.22: Decoding the sign of v. + int sign = this.DecodeBinaryDecision(ref reader, ref this.GetFixedBinReference()); + st = ref Unsafe.Add(ref st, 2); - // Update bit shift counter. - if ((this.ct += 8) < 0) + // Figure F.23: Decoding the magnitude category of v. + int m = this.DecodeBinaryDecision(ref reader, ref st); + if (m != 0) + { + if (this.DecodeBinaryDecision(ref reader, ref st) != 0) + { + m <<= 1; + st = ref acStatistics.GetReference(k <= acTable.AcKx ? 189 : 217); + while (this.DecodeBinaryDecision(ref reader, ref st) != 0) { - // Need more initial bytes. - if (++this.ct == 0) + if ((m <<= 1) == 0x8000) { - // Got 2 initial bytes -> re-init A and exit loop - this.a = 0x8000; // e->a = 0x10000L after loop exit + JpegThrowHelper.ThrowInvalidImageContentException("Invalid arithmetic code."); } + + st = ref Unsafe.Add(ref st, 1); } } - - this.a <<= 1; } - // Fetch values from our compact representation of Table D.3(D.2): - // Qe values and probability estimation state machine - int sv = st; - int qe = ArithmeticTable[sv & 0x7f]; - byte nl = (byte)qe; - qe >>= 8; // Next_Index_LPS + Switch_MPS - byte nm = (byte)qe; - qe >>= 8; // Next_Index_MPS - - // Decode & estimation procedures per sections D.2.4 & D.2.5 - int temp = this.a - qe; - this.a = temp; - temp <<= this.ct; - if (this.c >= temp) - { - this.c -= temp; + int v = m; - // Conditional LPS (less probable symbol) exchange - if (this.a < qe) - { - this.a = qe; - st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS - } - else + // Figure F.24: Decoding the magnitude bit pattern of v. + st = ref Unsafe.Add(ref st, 14); + while ((m >>= 1) != 0) + { + if (this.DecodeBinaryDecision(ref reader, ref st) != 0) { - this.a = qe; - st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS - sv ^= 0x80; // Exchange LPS/MPS + v |= m; } } - else if (this.a < 0x8000) + + v += 1; + if (sign != 0) + { + v = -v; + } + + Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private bool HandleRestart() + { + if (this.restartInterval > 0 && (--this.todo) == 0) + { + if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) { - // Conditional MPS (more probable symbol) exchange - if (this.a < qe) + if (!this.scanBuffer.FindNextMarker()) { - st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS - sv ^= 0x80; // Exchange LPS/MPS + return false; } - else + } + + this.todo = this.restartInterval; + + foreach (ArithmeticDecodingComponent component in this.components) + { + component.DcPredictor = 0; + component.DcContext = 0; + component.DcStatistics?.Reset(); + component.AcStatistics?.Reset(); + } + + this.Reset(); + + if (this.scanBuffer.HasRestartMarker()) + { + this.Reset(); + return true; + } + + if (this.scanBuffer.HasBadMarker()) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + this.Reset(); + return true; + } + } + + return false; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void Reset() + { + for (int i = 0; i < this.components.Length; i++) + { + ArithmeticDecodingComponent component = this.components[i] as ArithmeticDecodingComponent; + component.DcPredictor = 0; + } + + this.c = 0; + this.a = 0; + this.ct = -16; // Force reading 2 initial bytes to fill C. + + this.scanBuffer.Reset(); + } + + private int DecodeBinaryDecision(ref JpegBitReader reader, ref byte st) + { + // Renormalization & data input per section D.2.6 + while (this.a < 0x8000) + { + if (--this.ct < 0) + { + // Need to fetch next data byte. + reader.CheckBits(); + int data = reader.GetBits(8); + + // Insert data into C register. + this.c = (this.c << 8) | data; + + // Update bit shift counter. + if ((this.ct += 8) < 0) { - st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS + // Need more initial bytes. + if (++this.ct == 0) + { + // Got 2 initial bytes -> re-init A and exit loop + this.a = 0x8000; // e->a = 0x10000L after loop exit + } } } - return sv >> 7; + this.a <<= 1; + } + + // Fetch values from our compact representation of Table D.3(D.2): + // Qe values and probability estimation state machine + int sv = st; + int qe = ArithmeticTable[sv & 0x7f]; + byte nl = (byte)qe; + qe >>= 8; // Next_Index_LPS + Switch_MPS + byte nm = (byte)qe; + qe >>= 8; // Next_Index_MPS + + // Decode & estimation procedures per sections D.2.4 & D.2.5 + int temp = this.a - qe; + this.a = temp; + temp <<= this.ct; + if (this.c >= temp) + { + this.c -= temp; + + // Conditional LPS (less probable symbol) exchange + if (this.a < qe) + { + this.a = qe; + st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS + } + else + { + this.a = qe; + st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS + sv ^= 0x80; // Exchange LPS/MPS + } + } + else if (this.a < 0x8000) + { + // Conditional MPS (more probable symbol) exchange + if (this.a < qe) + { + st = (byte)((sv & 0x80) ^ nl); // Estimate_after_LPS + sv ^= 0x80; // Exchange LPS/MPS + } + else + { + st = (byte)((sv & 0x80) ^ nm); // Estimate_after_MPS + } } - // The following function specifies the packing of the four components - // into the compact INT32 representation. - // Note that this formula must match the actual arithmetic encoder and decoder implementation. The implementation has to be changed - // if this formula is changed. - // The current organization is leaned on Markus Kuhn's JBIG implementation (jbig_tab.c). - [MethodImpl(InliningOptions.ShortMethod)] - private static int Pack(int a, int b, int c, int d) - => (a << 16) | (c << 8) | (d << 7) | b; + return sv >> 7; } + + // The following function specifies the packing of the four components + // into the compact INT32 representation. + // Note that this formula must match the actual arithmetic encoder and decoder implementation. The implementation has to be changed + // if this formula is changed. + // The current organization is leaned on Markus Kuhn's JBIG implementation (jbig_tab.c). + [MethodImpl(InliningOptions.ShortMethod)] + private static int Pack(int a, int b, int c, int d) + => (a << 16) | (c << 8) | (d << 7) | b; } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs index 251c1d1c3f..1e890d8269 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticStatistics.cs @@ -1,29 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +internal class ArithmeticStatistics { - internal class ArithmeticStatistics - { - private readonly byte[] statistics; + private readonly byte[] statistics; - public ArithmeticStatistics(bool dc, int identifier) - { - this.IsDcStatistics = dc; - this.Identifier = identifier; - this.statistics = dc ? new byte[64] : new byte[256]; - } + public ArithmeticStatistics(bool dc, int identifier) + { + this.IsDcStatistics = dc; + this.Identifier = identifier; + this.statistics = dc ? new byte[64] : new byte[256]; + } - public bool IsDcStatistics { get; private set; } + public bool IsDcStatistics { get; private set; } - public int Identifier { get; private set; } + public int Identifier { get; private set; } - public ref byte GetReference() => ref this.statistics[0]; + public ref byte GetReference() => ref this.statistics[0]; - public ref byte GetReference(int offset) => ref this.statistics[offset]; + public ref byte GetReference(int offset) => ref this.statistics[offset]; - public void Reset() => this.statistics.AsSpan().Clear(); - } + public void Reset() => this.statistics.AsSpan().Clear(); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs index 87e85686ca..f0cc4d5e82 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/ComponentProcessor.cs @@ -1,65 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Base class for processing component spectral data and converting it to raw color data. +/// +internal abstract class ComponentProcessor : IDisposable { - /// - /// Base class for processing component spectral data and converting it to raw color data. - /// - internal abstract class ComponentProcessor : IDisposable + public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize) { - public ComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, Size postProcessorBufferSize, IJpegComponent component, int blockSize) - { - this.Frame = frame; - this.Component = component; - - this.BlockAreaSize = component.SubSamplingDivisors * blockSize; - this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( - postProcessorBufferSize.Width, - postProcessorBufferSize.Height, - this.BlockAreaSize.Height); - } + this.Frame = frame; + this.Component = component; + + this.BlockAreaSize = component.SubSamplingDivisors * blockSize; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + this.BlockAreaSize.Height); + } - protected JpegFrame Frame { get; } + protected JpegFrame Frame { get; } - protected IJpegComponent Component { get; } + protected IJpegComponent Component { get; } - protected Buffer2D ColorBuffer { get; } + protected Buffer2D ColorBuffer { get; } - protected Size BlockAreaSize { get; } + protected Size BlockAreaSize { get; } - /// - /// Converts spectral data to color data accessible via . - /// - /// Spectral row index to convert. - public abstract void CopyBlocksToColorBuffer(int row); + /// + /// Converts spectral data to color data accessible via . + /// + /// Spectral row index to convert. + public abstract void CopyBlocksToColorBuffer(int row); - /// - /// Clears spectral buffers. - /// - /// - /// Should only be called during baseline interleaved decoding. - /// - public void ClearSpectralBuffers() + /// + /// Clears spectral buffers. + /// + /// + /// Should only be called during baseline interleaved decoding. + /// + public void ClearSpectralBuffers() + { + Buffer2D spectralBlocks = this.Component.SpectralBlocks; + for (int i = 0; i < spectralBlocks.Height; i++) { - Buffer2D spectralBlocks = this.Component.SpectralBlocks; - for (int i = 0; i < spectralBlocks.Height; i++) - { - spectralBlocks.DangerousGetRowSpan(i).Clear(); - } + spectralBlocks.DangerousGetRowSpan(i).Clear(); } + } - /// - /// Gets converted color buffer row. - /// - /// Row index. - /// Color buffer row. - public Span GetColorBufferRowSpan(int row) => - this.ColorBuffer.DangerousGetRowSpan(row); + /// + /// Gets converted color buffer row. + /// + /// Row index. + /// Color buffer row. + public Span GetColorBufferRowSpan(int row) => + this.ColorBuffer.DangerousGetRowSpan(row); - public void Dispose() => this.ColorBuffer.Dispose(); - } + public void Dispose() => this.ColorBuffer.Dispose(); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs index 80cc689e3b..79e25a67a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DirectComponentProcessor.cs @@ -1,72 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Processes component spectral data and converts it to color data in 1-to-1 scale. +/// +internal sealed class DirectComponentProcessor : ComponentProcessor { - /// - /// Processes component spectral data and converts it to color data in 1-to-1 scale. - /// - internal sealed class DirectComponentProcessor : ComponentProcessor + private Block8x8F dequantizationTable; + + public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8) { - private Block8x8F dequantizationTable; + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } - public DirectComponentProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, blockSize: 8) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - FloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } + public override void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; + float maximumValue = this.Frame.MaxColorChannelValue; - float maximumValue = this.Frame.MaxColorChannelValue; + int destAreaStride = this.ColorBuffer.Width; - int destAreaStride = this.ColorBuffer.Width; + int blocksRowsPerStep = this.Component.SamplingFactors.Height; - int blocksRowsPerStep = this.Component.SamplingFactors.Height; + int yBlockStart = spectralStep * blocksRowsPerStep; - int yBlockStart = spectralStep * blocksRowsPerStep; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; + Block8x8F workspaceBlock = default; + + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; - Block8x8F workspaceBlock = default; + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int y = 0; y < blocksRowsPerStep; y++) + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // Dequantize - workspaceBlock.MultiplyInPlace(ref this.dequantizationTable); - - // Convert from spectral to color - FloatingPointDCT.TransformIDCT(ref workspaceBlock); - - // To conform better to libjpeg we actually NEED TO loose precision here. - // This is because they store blocks as Int16 between all the operations. - // To be "more accurate", we need to emulate this by rounding! - workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); - - // Write to color buffer acording to sampling factors - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - workspaceBlock.ScaledCopyTo( - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // Dequantize + workspaceBlock.MultiplyInPlace(ref this.dequantizationTable); + + // Convert from spectral to color + FloatingPointDCT.TransformIDCT(ref workspaceBlock); + + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // To be "more accurate", we need to emulate this by rounding! + workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); + + // Write to color buffer acording to sampling factors + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + workspaceBlock.ScaledCopyTo( + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs index 801b2a3fbd..51d8d03593 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor2.cs @@ -1,98 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Processes component spectral data and converts it to color data in 2-to-1 scale. +/// +internal sealed class DownScalingComponentProcessor2 : ComponentProcessor { - /// - /// Processes component spectral data and converts it to color data in 2-to-1 scale. - /// - internal sealed class DownScalingComponentProcessor2 : ComponentProcessor - { - private Block8x8F dequantizationTable; + private Block8x8F dequantizationTable; - public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 4) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } + public DownScalingComponentProcessor2(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, 4) + { + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; + public override void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue / 2); + float maximumValue = this.Frame.MaxColorChannelValue; + float normalizationValue = MathF.Ceiling(maximumValue / 2); - int destAreaStride = this.ColorBuffer.Width; + int destAreaStride = this.ColorBuffer.Width; - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; + int blocksRowsPerStep = this.Component.SamplingFactors.Height; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; - Block8x8F workspaceBlock = default; + Block8x8F workspaceBlock = default; - int yBlockStart = spectralStep * blocksRowsPerStep; + int yBlockStart = spectralStep * blocksRowsPerStep; - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - ref workspaceBlock, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // IDCT/Normalization/Range + ScaledFloatingPointDCT.TransformIDCT_4x4(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); + + // Save to the intermediate buffer + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + ScaledCopyTo( + ref workspaceBlock, + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) - { - // TODO: Optimize: implement all cases with scale-specific, loopless code! - CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); + [MethodImpl(InliningOptions.ShortMethod)] + public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { + // TODO: Optimize: implement all cases with scale-specific, loopless code! + CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + [MethodImpl(InliningOptions.ColdPath)] + static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 4; y++) { - for (int y = 0; y < 4; y++) + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 4; x++) { - int yy = y * verticalScale; - int y8 = y * 8; + int xx = x * horizontalScale; - for (int x = 0; x < 4; x++) - { - int xx = x * horizontalScale; + float value = block[y8 + x]; - float value = block[y8 + x]; + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; - for (int i = 0; i < verticalScale; i++) + for (int j = 0; j < horizontalScale; j++) { - int baseIdx = ((yy + i) * areaStride) + xx; - - for (int j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value; - } + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs index 1c63abc932..b8a40f53b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor4.cs @@ -1,98 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Processes component spectral data and converts it to color data in 4-to-1 scale. +/// +internal sealed class DownScalingComponentProcessor4 : ComponentProcessor { - /// - /// Processes component spectral data and converts it to color data in 4-to-1 scale. - /// - internal sealed class DownScalingComponentProcessor4 : ComponentProcessor - { - private Block8x8F dequantizationTable; + private Block8x8F dequantizationTable; - public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 2) - { - this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; - ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); - } + public DownScalingComponentProcessor4(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, 2) + { + this.dequantizationTable = rawJpeg.QuantizationTables[component.QuantizationTableIndex]; + ScaledFloatingPointDCT.AdjustToIDCT(ref this.dequantizationTable); + } - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; + public override void CopyBlocksToColorBuffer(int spectralStep) + { + Buffer2D spectralBuffer = this.Component.SpectralBlocks; - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue / 2); + float maximumValue = this.Frame.MaxColorChannelValue; + float normalizationValue = MathF.Ceiling(maximumValue / 2); - int destAreaStride = this.ColorBuffer.Width; + int destAreaStride = this.ColorBuffer.Width; - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; + int blocksRowsPerStep = this.Component.SamplingFactors.Height; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; - Block8x8F workspaceBlock = default; + Block8x8F workspaceBlock = default; - int yBlockStart = spectralStep * blocksRowsPerStep; + int yBlockStart = spectralStep * blocksRowsPerStep; - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.BlockAreaSize.Height; + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // Integer to float - workspaceBlock.LoadFrom(ref blockRow[xBlock]); - - // IDCT/Normalization/Range - ScaledFloatingPointDCT.TransformIDCT_2x2(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - ref workspaceBlock, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // Integer to float + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // IDCT/Normalization/Range + ScaledFloatingPointDCT.TransformIDCT_2x2(ref workspaceBlock, ref this.dequantizationTable, normalizationValue, maximumValue); + + // Save to the intermediate buffer + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + ScaledCopyTo( + ref workspaceBlock, + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) - { - // TODO: Optimize: implement all cases with scale-specific, loopless code! - CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); + [MethodImpl(InliningOptions.ShortMethod)] + public static void ScaledCopyTo(ref Block8x8F block, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { + // TODO: Optimize: implement all cases with scale-specific, loopless code! + CopyArbitraryScale(ref block, ref destRef, destStrideWidth, horizontalScale, verticalScale); - [MethodImpl(InliningOptions.ColdPath)] - static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + [MethodImpl(InliningOptions.ColdPath)] + static void CopyArbitraryScale(ref Block8x8F block, ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) + { + for (int y = 0; y < 2; y++) { - for (int y = 0; y < 2; y++) + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 2; x++) { - int yy = y * verticalScale; - int y8 = y * 8; + int xx = x * horizontalScale; - for (int x = 0; x < 2; x++) - { - int xx = x * horizontalScale; + float value = block[y8 + x]; - float value = block[y8 + x]; + for (int i = 0; i < verticalScale; i++) + { + int baseIdx = ((yy + i) * areaStride) + xx; - for (int i = 0; i < verticalScale; i++) + for (int j = 0; j < horizontalScale; j++) { - int baseIdx = ((yy + i) * areaStride) + xx; - - for (int j = 0; j < horizontalScale; j++) - { - // area[xx + j, yy + i] = value; - Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value; - } + // area[xx + j, yy + i] = value; + Unsafe.Add(ref areaOrigin, (nint)(uint)(baseIdx + j)) = value; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs index 03f0de7411..121b745465 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ComponentProcessors/DownScalingComponentProcessor8.cs @@ -1,88 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Processes component spectral data and converts it to color data in 8-to-1 scale. +/// +internal sealed class DownScalingComponentProcessor8 : ComponentProcessor { - /// - /// Processes component spectral data and converts it to color data in 8-to-1 scale. - /// - internal sealed class DownScalingComponentProcessor8 : ComponentProcessor + private readonly float dcDequantizatizer; + + public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) + : base(memoryAllocator, frame, postProcessorBufferSize, component, 1) + => this.dcDequantizatizer = 0.125f * rawJpeg.QuantizationTables[component.QuantizationTableIndex][0]; + + public override void CopyBlocksToColorBuffer(int spectralStep) { - private readonly float dcDequantizatizer; + Buffer2D spectralBuffer = this.Component.SpectralBlocks; - public DownScalingComponentProcessor8(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component) - : base(memoryAllocator, frame, postProcessorBufferSize, component, 1) - => this.dcDequantizatizer = 0.125f * rawJpeg.QuantizationTables[component.QuantizationTableIndex][0]; + float maximumValue = this.Frame.MaxColorChannelValue; + float normalizationValue = MathF.Ceiling(maximumValue / 2); - public override void CopyBlocksToColorBuffer(int spectralStep) - { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; + int destAreaStride = this.ColorBuffer.Width; - float maximumValue = this.Frame.MaxColorChannelValue; - float normalizationValue = MathF.Ceiling(maximumValue / 2); + int blocksRowsPerStep = this.Component.SamplingFactors.Height; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; - int destAreaStride = this.ColorBuffer.Width; + int yBlockStart = spectralStep * blocksRowsPerStep; - int blocksRowsPerStep = this.Component.SamplingFactors.Height; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; + for (int y = 0; y < blocksRowsPerStep; y++) + { + int yBuffer = y * this.BlockAreaSize.Height; - int yBlockStart = spectralStep * blocksRowsPerStep; + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int y = 0; y < blocksRowsPerStep; y++) + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { - int yBuffer = y * this.BlockAreaSize.Height; - - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizatizer, normalizationValue, maximumValue); - - // Save to the intermediate buffer - int xColorBufferStart = xBlock * this.BlockAreaSize.Width; - ScaledCopyTo( - dc, - ref colorBufferRow[xColorBufferStart], - destAreaStride, - subSamplingDivisors.Width, - subSamplingDivisors.Height); - } + float dc = ScaledFloatingPointDCT.TransformIDCT_1x1(blockRow[xBlock][0], this.dcDequantizatizer, normalizationValue, maximumValue); + + // Save to the intermediate buffer + int xColorBufferStart = xBlock * this.BlockAreaSize.Width; + ScaledCopyTo( + dc, + ref colorBufferRow[xColorBufferStart], + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void ScaledCopyTo(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + [MethodImpl(InliningOptions.ShortMethod)] + public static void ScaledCopyTo(float value, ref float destRef, int destStrideWidth, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) { - if (horizontalScale == 1 && verticalScale == 1) - { - destRef = value; - return; - } + destRef = value; + return; + } - if (horizontalScale == 2 && verticalScale == 2) - { - destRef = value; - Unsafe.Add(ref destRef, 1) = value; - Unsafe.Add(ref destRef, 0 + (nint)(uint)destStrideWidth) = value; - Unsafe.Add(ref destRef, 1 + (nint)(uint)destStrideWidth) = value; - return; - } + if (horizontalScale == 2 && verticalScale == 2) + { + destRef = value; + Unsafe.Add(ref destRef, 1) = value; + Unsafe.Add(ref destRef, 0 + (nint)(uint)destStrideWidth) = value; + Unsafe.Add(ref destRef, 1 + (nint)(uint)destStrideWidth) = value; + return; + } - // TODO: Optimize: implement all cases with scale-specific, loopless code! - for (int y = 0; y < verticalScale; y++) + // TODO: Optimize: implement all cases with scale-specific, loopless code! + for (int y = 0; y < verticalScale; y++) + { + for (int x = 0; x < horizontalScale; x++) { - for (int x = 0; x < horizontalScale; x++) - { - Unsafe.Add(ref destRef, (nint)(uint)x) = value; - } - - destRef = ref Unsafe.Add(ref destRef, (nint)(uint)destStrideWidth); + Unsafe.Add(ref destRef, (nint)(uint)x) = value; } + + destRef = ref Unsafe.Add(ref destRef, (nint)(uint)destStrideWidth); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 6f57dff99c..91abce4d74 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -1,714 +1,676 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Decodes the Huffman encoded spectral scan. +/// Originally ported from +/// with additional fixes for both performance and common encoding errors. +/// +internal class HuffmanScanDecoder : IJpegScanDecoder { + private readonly BufferedReadStream stream; + + /// + /// instance containing decoding-related information. + /// + private JpegFrame frame; + + /// + /// Shortcut for .Components. + /// + private IJpegComponent[] components; + + /// + /// Number of component in the current scan. + /// + private int scanComponentCount; + + /// + /// The reset interval determined by RST markers. + /// + private int restartInterval; + + /// + /// How many mcu's are left to do. + /// + private int todo; + + /// + /// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. + /// + private int eobrun; + + /// + /// The DC Huffman tables. + /// + private readonly HuffmanTable[] dcHuffmanTables; + + /// + /// The AC Huffman tables. + /// + private readonly HuffmanTable[] acHuffmanTables; + + private JpegBitReader scanBuffer; + + private readonly SpectralConverter spectralConverter; + + private readonly CancellationToken cancellationToken; + /// - /// Decodes the Huffman encoded spectral scan. - /// Originally ported from - /// with additional fixes for both performance and common encoding errors. + /// Initializes a new instance of the class. /// - internal class HuffmanScanDecoder : IJpegScanDecoder + /// The input stream. + /// Spectral to pixel converter. + /// The token to monitor cancellation. + public HuffmanScanDecoder( + BufferedReadStream stream, + SpectralConverter converter, + CancellationToken cancellationToken) { - private readonly BufferedReadStream stream; - - /// - /// instance containing decoding-related information. - /// - private JpegFrame frame; - - /// - /// Shortcut for .Components. - /// - private IJpegComponent[] components; - - /// - /// Number of component in the current scan. - /// - private int scanComponentCount; - - /// - /// The reset interval determined by RST markers. - /// - private int restartInterval; - - /// - /// How many mcu's are left to do. - /// - private int todo; - - /// - /// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. - /// - private int eobrun; - - /// - /// The DC Huffman tables. - /// - private readonly HuffmanTable[] dcHuffmanTables; - - /// - /// The AC Huffman tables. - /// - private readonly HuffmanTable[] acHuffmanTables; - - private JpegBitReader scanBuffer; - - private readonly SpectralConverter spectralConverter; - - private readonly CancellationToken cancellationToken; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - /// Spectral to pixel converter. - /// The token to monitor cancellation. - public HuffmanScanDecoder( - BufferedReadStream stream, - SpectralConverter converter, - CancellationToken cancellationToken) - { - this.stream = stream; - this.spectralConverter = converter; - this.cancellationToken = cancellationToken; - - // TODO: this is actually a variable value depending on component count - const int maxTables = 4; - this.dcHuffmanTables = new HuffmanTable[maxTables]; - this.acHuffmanTables = new HuffmanTable[maxTables]; - } + this.stream = stream; + this.spectralConverter = converter; + this.cancellationToken = cancellationToken; + + // TODO: this is actually a variable value depending on component count + const int maxTables = 4; + this.dcHuffmanTables = new HuffmanTable[maxTables]; + this.acHuffmanTables = new HuffmanTable[maxTables]; + } - /// - /// Sets reset interval determined by RST markers. - /// - public int ResetInterval + /// + /// Sets reset interval determined by RST markers. + /// + public int ResetInterval + { + set { - set - { - this.restartInterval = value; - this.todo = value; - } + this.restartInterval = value; + this.todo = value; } + } - // The spectral selection start. - public int SpectralStart { get; set; } - - // The spectral selection end. - public int SpectralEnd { get; set; } + // The spectral selection start. + public int SpectralStart { get; set; } - // The successive approximation high bit end. - public int SuccessiveHigh { get; set; } + // The spectral selection end. + public int SpectralEnd { get; set; } - // The successive approximation low bit end. - public int SuccessiveLow { get; set; } + // The successive approximation high bit end. + public int SuccessiveHigh { get; set; } - /// - public void ParseEntropyCodedData(int scanComponentCount) - { - this.cancellationToken.ThrowIfCancellationRequested(); + // The successive approximation low bit end. + public int SuccessiveLow { get; set; } - this.scanComponentCount = scanComponentCount; + /// + public void ParseEntropyCodedData(int scanComponentCount) + { + this.cancellationToken.ThrowIfCancellationRequested(); - this.scanBuffer = new JpegBitReader(this.stream); + this.scanComponentCount = scanComponentCount; - this.frame.AllocateComponents(); + this.scanBuffer = new JpegBitReader(this.stream); - if (!this.frame.Progressive) - { - this.ParseBaselineData(); - } - else - { - this.ParseProgressiveData(); - } + this.frame.AllocateComponents(); - if (this.scanBuffer.HasBadMarker()) - { - this.stream.Position = this.scanBuffer.MarkerPosition; - } + if (!this.frame.Progressive) + { + this.ParseBaselineData(); } - - /// - public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + else { - this.frame = frame; - this.components = frame.Components; + this.ParseProgressiveData(); + } - this.spectralConverter.InjectFrameData(frame, jpegData); + if (this.scanBuffer.HasBadMarker()) + { + this.stream.Position = this.scanBuffer.MarkerPosition; } + } - private void ParseBaselineData() + /// + public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.frame = frame; + this.components = frame.Components; + + this.spectralConverter.InjectFrameData(frame, jpegData); + } + + private void ParseBaselineData() + { + if (this.scanComponentCount != 1) { - if (this.scanComponentCount != 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataInterleaved(); - this.spectralConverter.CommitConversion(); - } - else if (this.frame.ComponentCount == 1) - { - this.spectralConverter.PrepareForDecoding(); - this.ParseBaselineDataSingleComponent(); - this.spectralConverter.CommitConversion(); - } - else - { - this.ParseBaselineDataNonInterleaved(); - } + this.spectralConverter.PrepareForDecoding(); + this.ParseBaselineDataInterleaved(); + this.spectralConverter.CommitConversion(); } + else if (this.frame.ComponentCount == 1) + { + this.spectralConverter.PrepareForDecoding(); + this.ParseBaselineDataSingleComponent(); + this.spectralConverter.CommitConversion(); + } + else + { + this.ParseBaselineDataNonInterleaved(); + } + } - private void ParseBaselineDataInterleaved() + private void ParseBaselineDataInterleaved() + { + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref JpegBitReader buffer = ref this.scanBuffer; + + for (int j = 0; j < mcusPerColumn; j++) { - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader buffer = ref this.scanBuffer; + this.cancellationToken.ThrowIfCancellationRequested(); - for (int j = 0; j < mcusPerColumn; j++) + // decode from binary to spectral + for (int i = 0; i < mcusPerLine; i++) { - this.cancellationToken.ThrowIfCancellationRequested(); - - // decode from binary to spectral - for (int i = 0; i < mcusPerLine; i++) + // Scan an interleaved mcu... process components in order + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < this.scanComponentCount; k++) { - // Scan an interleaved mcu... process components in order - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - var component = this.components[order] as JpegComponent; + int order = this.frame.ComponentOrder[k]; + var component = this.components[order] as JpegComponent; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int x = 0; x < h; x++) + for (int x = 0; x < h; x++) + { + if (buffer.NoData) { - if (buffer.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(); - return; - } - - int blockCol = (mcuCol * h) + x; - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, blockCol), - ref dcHuffmanTable, - ref acHuffmanTable); + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); + return; } + + int blockCol = (mcuCol * h) + x; + + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable, + ref acHuffmanTable); } } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - this.HandleRestart(); } - // Convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStrideBaseline(); + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + this.HandleRestart(); } + + // Convert from spectral to actual pixels via given converter + this.spectralConverter.ConvertStrideBaseline(); } + } + + private void ParseBaselineDataNonInterleaved() + { + var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; + ref JpegBitReader buffer = ref this.scanBuffer; + + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - private void ParseBaselineDataNonInterleaved() + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int j = 0; j < h; j++) { - var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; - ref JpegBitReader buffer = ref this.scanBuffer; + this.cancellationToken.ThrowIfCancellationRequested(); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (int i = 0; i < w; i++) + { + if (buffer.NoData) + { + return; + } - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; + this.DecodeBlockBaseline( + component, + ref Unsafe.Add(ref blockRef, i), + ref dcHuffmanTable, + ref acHuffmanTable); - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + this.HandleRestart(); + } + } + } + + private void ParseBaselineDataSingleComponent() + { + JpegComponent component = this.frame.Components[0]; + int mcuLines = this.frame.McusPerColumn; + int w = component.WidthInBlocks; + int h = component.SamplingFactors.Height; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + ref JpegBitReader buffer = ref this.scanBuffer; + + for (int i = 0; i < mcuLines; i++) + { + this.cancellationToken.ThrowIfCancellationRequested(); + // decode from binary to spectral for (int j = 0; j < h; j++) { - this.cancellationToken.ThrowIfCancellationRequested(); Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int i = 0; i < w; i++) + for (int k = 0; k < w; k++) { if (buffer.NoData) { + // It is very likely that some spectral data was decoded before we've encountered 'end of scan' + // so we need to decode what's left and return (or maybe throw?) + this.spectralConverter.ConvertStrideBaseline(); return; } this.DecodeBlockBaseline( component, - ref Unsafe.Add(ref blockRef, i), + ref Unsafe.Add(ref blockRef, k), ref dcHuffmanTable, ref acHuffmanTable); this.HandleRestart(); } } + + // Convert from spectral to actual pixels via given converter + this.spectralConverter.ConvertStrideBaseline(); } + } - private void ParseBaselineDataSingleComponent() + private void CheckProgressiveData() + { + // Validate successive scan parameters. + // Logic has been adapted from libjpeg. + // See Table B.3 – Scan header parameter size and values. itu-t81.pdf + bool invalid = false; + if (this.SpectralStart == 0) { - JpegComponent component = this.frame.Components[0]; - int mcuLines = this.frame.McusPerColumn; - int w = component.WidthInBlocks; - int h = component.SamplingFactors.Height; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - ref JpegBitReader buffer = ref this.scanBuffer; - - for (int i = 0; i < mcuLines; i++) + if (this.SpectralEnd != 0) { - this.cancellationToken.ThrowIfCancellationRequested(); - - // decode from binary to spectral - for (int j = 0; j < h; j++) - { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (int k = 0; k < w; k++) - { - if (buffer.NoData) - { - // It is very likely that some spectral data was decoded before we've encountered 'end of scan' - // so we need to decode what's left and return (or maybe throw?) - this.spectralConverter.ConvertStrideBaseline(); - return; - } - - this.DecodeBlockBaseline( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); - - this.HandleRestart(); - } - } - - // Convert from spectral to actual pixels via given converter - this.spectralConverter.ConvertStrideBaseline(); + invalid = true; } } - - private void CheckProgressiveData() + else { - // Validate successive scan parameters. - // Logic has been adapted from libjpeg. - // See Table B.3 – Scan header parameter size and values. itu-t81.pdf - bool invalid = false; - if (this.SpectralStart == 0) + // Need not check Ss/Se < 0 since they came from unsigned bytes. + if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) { - if (this.SpectralEnd != 0) - { - invalid = true; - } - } - else - { - // Need not check Ss/Se < 0 since they came from unsigned bytes. - if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63) - { - invalid = true; - } - - // AC scans may have only one component. - if (this.scanComponentCount != 1) - { - invalid = true; - } - } - - if (this.SuccessiveHigh != 0) - { - // Successive approximation refinement scan: must have Al = Ah-1. - if (this.SuccessiveHigh - 1 != this.SuccessiveLow) - { - invalid = true; - } + invalid = true; } - // TODO: How does this affect 12bit jpegs. - // According to libjpeg the range covers 8bit only? - if (this.SuccessiveLow > 13) + // AC scans may have only one component. + if (this.scanComponentCount != 1) { invalid = true; } + } - if (invalid) + if (this.SuccessiveHigh != 0) + { + // Successive approximation refinement scan: must have Al = Ah-1. + if (this.SuccessiveHigh - 1 != this.SuccessiveLow) { - JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); + invalid = true; } } - private void ParseProgressiveData() + // TODO: How does this affect 12bit jpegs. + // According to libjpeg the range covers 8bit only? + if (this.SuccessiveLow > 13) { - this.CheckProgressiveData(); + invalid = true; + } - if (this.scanComponentCount == 1) - { - this.ParseProgressiveDataNonInterleaved(); - } - else - { - this.ParseProgressiveDataInterleaved(); - } + if (invalid) + { + JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow); } + } - private void ParseProgressiveDataInterleaved() + private void ParseProgressiveData() + { + this.CheckProgressiveData(); + + if (this.scanComponentCount == 1) { - // Interleaved - int mcu = 0; - int mcusPerColumn = this.frame.McusPerColumn; - int mcusPerLine = this.frame.McusPerLine; - ref JpegBitReader buffer = ref this.scanBuffer; + this.ParseProgressiveDataNonInterleaved(); + } + else + { + this.ParseProgressiveDataInterleaved(); + } + } + + private void ParseProgressiveDataInterleaved() + { + // Interleaved + int mcu = 0; + int mcusPerColumn = this.frame.McusPerColumn; + int mcusPerLine = this.frame.McusPerLine; + ref JpegBitReader buffer = ref this.scanBuffer; - for (int j = 0; j < mcusPerColumn; j++) + for (int j = 0; j < mcusPerColumn; j++) + { + for (int i = 0; i < mcusPerLine; i++) { - for (int i = 0; i < mcusPerLine; i++) + // Scan an interleaved mcu... process components in order + int mcuRow = mcu / mcusPerLine; + int mcuCol = mcu % mcusPerLine; + for (int k = 0; k < this.scanComponentCount; k++) { - // Scan an interleaved mcu... process components in order - int mcuRow = mcu / mcusPerLine; - int mcuCol = mcu % mcusPerLine; - for (int k = 0; k < this.scanComponentCount; k++) - { - int order = this.frame.ComponentOrder[k]; - var component = this.components[order] as JpegComponent; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + int order = this.frame.ComponentOrder[k]; + var component = this.components[order] as JpegComponent; + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - int h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; + int h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) - { - int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) + { + int blockRow = (mcuRow * v) + y; + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int x = 0; x < h; x++) + for (int x = 0; x < h; x++) + { + if (buffer.NoData) { - if (buffer.NoData) - { - return; - } + return; + } - int blockCol = (mcuCol * h) + x; + int blockCol = (mcuCol * h) + x; - this.DecodeBlockProgressiveDC( - component, - ref Unsafe.Add(ref blockRef, blockCol), - ref dcHuffmanTable); - } + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable); } } - - // After all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - mcu++; - this.HandleRestart(); } + + // After all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + mcu++; + this.HandleRestart(); } } + } - private void ParseProgressiveDataNonInterleaved() - { - var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; - ref JpegBitReader buffer = ref this.scanBuffer; - - int w = component.WidthInBlocks; - int h = component.HeightInBlocks; - - if (this.SpectralStart == 0) - { - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); - - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + private void ParseProgressiveDataNonInterleaved() + { + var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent; + ref JpegBitReader buffer = ref this.scanBuffer; - for (int i = 0; i < w; i++) - { - if (buffer.NoData) - { - return; - } + int w = component.WidthInBlocks; + int h = component.HeightInBlocks; - this.DecodeBlockProgressiveDC( - component, - ref Unsafe.Add(ref blockRef, i), - ref dcHuffmanTable); + if (this.SpectralStart == 0) + { + ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - this.HandleRestart(); - } - } - } - else + for (int j = 0; j < h; j++) { - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - - for (int j = 0; j < h; j++) - { - this.cancellationToken.ThrowIfCancellationRequested(); + this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int i = 0; i < w; i++) + for (int i = 0; i < w; i++) + { + if (buffer.NoData) { - if (buffer.NoData) - { - return; - } + return; + } - this.DecodeBlockProgressiveAC( - ref Unsafe.Add(ref blockRef, i), - ref acHuffmanTable); + this.DecodeBlockProgressiveDC( + component, + ref Unsafe.Add(ref blockRef, i), + ref dcHuffmanTable); - this.HandleRestart(); - } + this.HandleRestart(); } } } - - private void DecodeBlockBaseline( - JpegComponent component, - ref Block8x8 block, - ref HuffmanTable dcTable, - ref HuffmanTable acTable) + else { - ref short blockDataRef = ref Unsafe.As(ref block); - ref JpegBitReader buffer = ref this.scanBuffer; - - // DC - int t = buffer.DecodeHuffman(ref dcTable); - if (t != 0) - { - t = buffer.Receive(t); - } - - t += component.DcPredictor; - component.DcPredictor = t; - blockDataRef = (short)t; + ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - // AC - for (int i = 1; i < 64;) + for (int j = 0; j < h; j++) { - int s = buffer.DecodeHuffman(ref acTable); + this.cancellationToken.ThrowIfCancellationRequested(); - int r = s >> 4; - s &= 15; + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - if (s != 0) - { - i += r; - s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i++]) = (short)s; - } - else + for (int i = 0; i < w; i++) { - if (r == 0) + if (buffer.NoData) { - break; + return; } - i += 16; + this.DecodeBlockProgressiveAC( + ref Unsafe.Add(ref blockRef, i), + ref acHuffmanTable); + + this.HandleRestart(); } } } + } - private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable) + private void DecodeBlockBaseline( + JpegComponent component, + ref Block8x8 block, + ref HuffmanTable dcTable, + ref HuffmanTable acTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + ref JpegBitReader buffer = ref this.scanBuffer; + + // DC + int t = buffer.DecodeHuffman(ref dcTable); + if (t != 0) { - ref short blockDataRef = ref Unsafe.As(ref block); - ref JpegBitReader buffer = ref this.scanBuffer; + t = buffer.Receive(t); + } + + t += component.DcPredictor; + component.DcPredictor = t; + blockDataRef = (short)t; + + // AC + for (int i = 1; i < 64;) + { + int s = buffer.DecodeHuffman(ref acTable); - if (this.SuccessiveHigh == 0) + int r = s >> 4; + s &= 15; + + if (s != 0) { - // First scan for DC coefficient, must be first - int s = buffer.DecodeHuffman(ref dcTable); - if (s != 0) + i += r; + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i++]) = (short)s; + } + else + { + if (r == 0) { - s = buffer.Receive(s); + break; } - s += component.DcPredictor; - component.DcPredictor = s; - blockDataRef = (short)(s << this.SuccessiveLow); + i += 16; } - else + } + } + + private void DecodeBlockProgressiveDC(JpegComponent component, ref Block8x8 block, ref HuffmanTable dcTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + ref JpegBitReader buffer = ref this.scanBuffer; + + if (this.SuccessiveHigh == 0) + { + // First scan for DC coefficient, must be first + int s = buffer.DecodeHuffman(ref dcTable); + if (s != 0) { - // Refinement scan for DC coefficient - buffer.CheckBits(); - blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow); + s = buffer.Receive(s); } + + s += component.DcPredictor; + component.DcPredictor = s; + blockDataRef = (short)(s << this.SuccessiveLow); + } + else + { + // Refinement scan for DC coefficient + buffer.CheckBits(); + blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow); } + } - private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) + private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) + { + ref short blockDataRef = ref Unsafe.As(ref block); + if (this.SuccessiveHigh == 0) { - ref short blockDataRef = ref Unsafe.As(ref block); - if (this.SuccessiveHigh == 0) + // MCU decoding for AC initial scan (either spectral selection, + // or first pass of successive approximation). + if (this.eobrun != 0) { - // MCU decoding for AC initial scan (either spectral selection, - // or first pass of successive approximation). - if (this.eobrun != 0) - { - --this.eobrun; - return; - } + --this.eobrun; + return; + } - ref JpegBitReader buffer = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; - int low = this.SuccessiveLow; + ref JpegBitReader buffer = ref this.scanBuffer; + int start = this.SpectralStart; + int end = this.SpectralEnd; + int low = this.SuccessiveLow; - for (int i = start; i <= end; ++i) - { - int s = buffer.DecodeHuffman(ref acTable); - int r = s >> 4; - s &= 15; + for (int i = start; i <= end; ++i) + { + int s = buffer.DecodeHuffman(ref acTable); + int r = s >> 4; + s &= 15; - i += r; + i += r; - if (s != 0) - { - s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i]) = (short)(s << low); - } - else + if (s != 0) + { + s = buffer.Receive(s); + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i]) = (short)(s << low); + } + else + { + if (r != 15) { - if (r != 15) + this.eobrun = 1 << r; + if (r != 0) { - this.eobrun = 1 << r; - if (r != 0) - { - buffer.CheckBits(); - this.eobrun += buffer.GetBits(r); - } - - --this.eobrun; - break; + buffer.CheckBits(); + this.eobrun += buffer.GetBits(r); } + + --this.eobrun; + break; } } } - else - { - // Refinement scan for these AC coefficients - this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); - } } - - private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) + else { // Refinement scan for these AC coefficients - ref JpegBitReader buffer = ref this.scanBuffer; - int start = this.SpectralStart; - int end = this.SpectralEnd; + this.DecodeBlockProgressiveACRefined(ref blockDataRef, ref acTable); + } + } + + private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref HuffmanTable acTable) + { + // Refinement scan for these AC coefficients + ref JpegBitReader buffer = ref this.scanBuffer; + int start = this.SpectralStart; + int end = this.SpectralEnd; - int p1 = 1 << this.SuccessiveLow; - int m1 = (-1) << this.SuccessiveLow; + int p1 = 1 << this.SuccessiveLow; + int m1 = (-1) << this.SuccessiveLow; - int k = start; + int k = start; - if (this.eobrun == 0) + if (this.eobrun == 0) + { + for (; k <= end; k++) { - for (; k <= end; k++) - { - int s = buffer.DecodeHuffman(ref acTable); - int r = s >> 4; - s &= 15; + int s = buffer.DecodeHuffman(ref acTable); + int r = s >> 4; + s &= 15; - if (s != 0) + if (s != 0) + { + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) { - buffer.CheckBits(); - if (buffer.GetBits(1) != 0) - { - s = p1; - } - else - { - s = m1; - } + s = p1; } else { - if (r != 15) - { - this.eobrun = 1 << r; - - if (r != 0) - { - buffer.CheckBits(); - this.eobrun += buffer.GetBits(r); - } - - break; - } + s = m1; } - - do + } + else + { + if (r != 15) { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - if (coef != 0) + this.eobrun = 1 << r; + + if (r != 0) { buffer.CheckBits(); - if (buffer.GetBits(1) != 0) - { - if ((coef & p1) == 0) - { - coef += (short)(coef >= 0 ? p1 : m1); - } - } - } - else - { - if (--r < 0) - { - break; - } + this.eobrun += buffer.GetBits(r); } - k++; - } - while (k <= end); - - if ((s != 0) && (k < 64)) - { - Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)s; + break; } } - } - if (this.eobrun > 0) - { - for (; k <= end; k++) + do { ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - if (coef != 0) { buffer.CheckBits(); @@ -720,69 +682,104 @@ private void DecodeBlockProgressiveACRefined(ref short blockDataRef, ref Huffman } } } + else + { + if (--r < 0) + { + break; + } + } + + k++; } + while (k <= end); - --this.eobrun; + if ((s != 0) && (k < 64)) + { + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)s; + } } } - [MethodImpl(InliningOptions.ShortMethod)] - private void Reset() + if (this.eobrun > 0) { - for (int i = 0; i < this.components.Length; i++) + for (; k <= end; k++) { - this.components[i].DcPredictor = 0; - } - - this.eobrun = 0; - this.scanBuffer.Reset(); - } + ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); - [MethodImpl(InliningOptions.ShortMethod)] - private bool HandleRestart() - { - if (this.restartInterval > 0 && (--this.todo) == 0) - { - if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) + if (coef != 0) { - if (!this.scanBuffer.FindNextMarker()) + buffer.CheckBits(); + if (buffer.GetBits(1) != 0) { - return false; + if ((coef & p1) == 0) + { + coef += (short)(coef >= 0 ? p1 : m1); + } } } + } - this.todo = this.restartInterval; + --this.eobrun; + } + } - if (this.scanBuffer.HasRestartMarker()) - { - this.Reset(); - return true; - } + [MethodImpl(InliningOptions.ShortMethod)] + private void Reset() + { + for (int i = 0; i < this.components.Length; i++) + { + this.components[i].DcPredictor = 0; + } + + this.eobrun = 0; + this.scanBuffer.Reset(); + } - if (this.scanBuffer.HasBadMarker()) + [MethodImpl(InliningOptions.ShortMethod)] + private bool HandleRestart() + { + if (this.restartInterval > 0 && (--this.todo) == 0) + { + if (this.scanBuffer.Marker == JpegConstants.Markers.XFF) + { + if (!this.scanBuffer.FindNextMarker()) { - this.stream.Position = this.scanBuffer.MarkerPosition; - this.Reset(); - return true; + return false; } } - return false; - } + this.todo = this.restartInterval; - /// - /// Build the huffman table using code lengths and code values. - /// - /// Table type. - /// Table index. - /// Code lengths. - /// Code values. - /// The provided spare workspace memory, can be dirty. - [MethodImpl(InliningOptions.ShortMethod)] - public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) - { - HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[index] = new HuffmanTable(codeLengths, values, workspace); + if (this.scanBuffer.HasRestartMarker()) + { + this.Reset(); + return true; + } + + if (this.scanBuffer.HasBadMarker()) + { + this.stream.Position = this.scanBuffer.MarkerPosition; + this.Reset(); + return true; + } } + + return false; + } + + /// + /// Build the huffman table using code lengths and code values. + /// + /// Table type. + /// Table index. + /// Code lengths. + /// Code values. + /// The provided spare workspace memory, can be dirty. + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) + { + HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[index] = new HuffmanTable(codeLengths, values, workspace); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 5ef9f760c9..7cf90bc235 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -1,140 +1,138 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation. +/// +[StructLayout(LayoutKind.Sequential)] +internal unsafe struct HuffmanTable { /// - /// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation. + /// Memory workspace buffer size used in ctor. + /// + public const int WorkspaceByteSize = 256 * sizeof(uint); + + /// + /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. + /// + public fixed byte Values[256]; + + /// + /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to + /// ensure terminates. + /// + public fixed ulong MaxCode[18]; + + /// + /// Values[] offset for codes of length k ValOffset[k] = Values[] index of 1st symbol of code length + /// k, less the smallest code of length k; so given a code of length k, the corresponding symbol is + /// Values[code + ValOffset[k]]. + /// + public fixed int ValOffset[19]; + + /// + /// Contains the length of bits for the given k value. + /// + public fixed byte LookaheadSize[JpegConstants.Huffman.LookupSize]; + + /// + /// Lookahead table: indexed by the next bits of + /// the input data stream. If the next Huffman code is no more + /// than bits long, we can obtain its length and + /// the corresponding symbol directly from this tables. + /// + /// The lower 8 bits of each table entry contain the number of + /// bits in the corresponding Huffman code, or + 1 + /// if too long. The next 8 bits of each entry contain the symbol. + /// + public fixed byte LookaheadValue[JpegConstants.Huffman.LookupSize]; + + /// + /// Initializes a new instance of the struct. /// - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct HuffmanTable + /// The code lengths. + /// The huffman values. + /// The provided spare workspace memory, can be dirty. + public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { - /// - /// Memory workspace buffer size used in ctor. - /// - public const int WorkspaceByteSize = 256 * sizeof(uint); - - /// - /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. - /// - public fixed byte Values[256]; - - /// - /// Contains the largest code of length k (0 if none). MaxCode[17] is a sentinel to - /// ensure terminates. - /// - public fixed ulong MaxCode[18]; - - /// - /// Values[] offset for codes of length k ValOffset[k] = Values[] index of 1st symbol of code length - /// k, less the smallest code of length k; so given a code of length k, the corresponding symbol is - /// Values[code + ValOffset[k]]. - /// - public fixed int ValOffset[19]; - - /// - /// Contains the length of bits for the given k value. - /// - public fixed byte LookaheadSize[JpegConstants.Huffman.LookupSize]; - - /// - /// Lookahead table: indexed by the next bits of - /// the input data stream. If the next Huffman code is no more - /// than bits long, we can obtain its length and - /// the corresponding symbol directly from this tables. - /// - /// The lower 8 bits of each table entry contain the number of - /// bits in the corresponding Huffman code, or + 1 - /// if too long. The next 8 bits of each entry contain the symbol. - /// - public fixed byte LookaheadValue[JpegConstants.Huffman.LookupSize]; - - /// - /// Initializes a new instance of the struct. - /// - /// The code lengths. - /// The huffman values. - /// The provided spare workspace memory, can be dirty. - public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) + Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); + + // Generate codes + uint code = 0; + int si = 1; + int p = 0; + for (int i = 1; i <= 16; i++) { - Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); + int count = codeLengths[i]; + for (int j = 0; j < count; j++) + { + workspace[p++] = code; + code++; + } - // Generate codes - uint code = 0; - int si = 1; - int p = 0; - for (int i = 1; i <= 16; i++) + // 'code' is now 1 more than the last code used for codelength 'si' + // in the valid worst possible case 'code' would have the least + // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) + // but it must still fit in 'si' bits since no huffman code can be equal to all 1s + // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield + // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) + if (code >= (1 << si)) { - int count = codeLengths[i]; - for (int j = 0; j < count; j++) - { - workspace[p++] = code; - code++; - } + JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); + } - // 'code' is now 1 more than the last code used for codelength 'si' - // in the valid worst possible case 'code' would have the least - // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) - // but it must still fit in 'si' bits since no huffman code can be equal to all 1s - // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield - // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) - if (code >= (1 << si)) - { - JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); - } + code <<= 1; + si++; + } - code <<= 1; - si++; + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) + { + if (codeLengths[j] != 0) + { + this.ValOffset[j] = p - (int)workspace[p]; + p += codeLengths[j]; + this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l + this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; } - - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) + else { - if (codeLengths[j] != 0) - { - this.ValOffset[j] = p - (int)workspace[p]; - p += codeLengths[j]; - this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l - this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; - } - else - { - this.MaxCode[j] = 0; - } + this.MaxCode[j] = 0; } + } - this.ValOffset[18] = 0; - this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates + this.ValOffset[18] = 0; + this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref this.LookaheadSize[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref this.LookaheadSize[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + { + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= codeLengths[length]; i++, p++) { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(workspace[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(workspace[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) - { - this.LookaheadSize[lookBits] = (byte)length; - this.LookaheadValue[lookBits] = this.Values[p]; - lookBits++; - } + this.LookaheadSize[lookBits] = (byte)length; + this.LookaheadValue[lookBits] = this.Values[p]; + lookBits++; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs index 2e4215325d..168e351bdf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegComponent.cs @@ -3,94 +3,93 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Common interface to represent raw Jpeg components. +/// +internal interface IJpegComponent { /// - /// Common interface to represent raw Jpeg components. + /// Gets the component id. + /// + byte Id { get; } + + /// + /// Gets the component's position in the components array. + /// + int Index { get; } + + /// + /// Gets the number of blocks in this component as + /// + Size SizeInBlocks { get; } + + /// + /// Gets the horizontal and the vertical sampling factor as + /// + Size SamplingFactors { get; } + + /// + /// Gets the horizontal sampling factor. + /// + int HorizontalSamplingFactor { get; } + + /// + /// Gets the vertical sampling factor. + /// + int VerticalSamplingFactor { get; } + + /// + /// Gets the divisors needed to apply when calculating colors. + /// + /// https://en.wikipedia.org/wiki/Chroma_subsampling + /// + /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2) + /// + Size SubSamplingDivisors { get; } + + /// + /// Gets the index of the quantization table for this block. + /// + int QuantizationTableIndex { get; } + + /// + /// Gets the storing the "raw" frequency-domain decoded + unzigged blocks. + /// We need to apply IDCT and dequantization to transform them into color-space blocks. + /// + Buffer2D SpectralBlocks { get; } + + /// + /// Gets or sets DC coefficient predictor. + /// + int DcPredictor { get; set; } + + /// + /// Gets or sets the index for the DC table. + /// + int DcTableId { get; set; } + + /// + /// Gets or sets the index for the AC table. + /// + int AcTableId { get; set; } + + /// + /// Initializes component for future buffers initialization. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + void Init(int maxSubFactorH, int maxSubFactorV); + + /// + /// Allocates the spectral blocks. + /// + /// if set to true, use the full height of a block, otherwise use the vertical sampling factor. + void AllocateSpectral(bool fullScan); + + /// + /// Releases resources. /// - internal interface IJpegComponent - { - /// - /// Gets the component id. - /// - byte Id { get; } - - /// - /// Gets the component's position in the components array. - /// - int Index { get; } - - /// - /// Gets the number of blocks in this component as - /// - Size SizeInBlocks { get; } - - /// - /// Gets the horizontal and the vertical sampling factor as - /// - Size SamplingFactors { get; } - - /// - /// Gets the horizontal sampling factor. - /// - int HorizontalSamplingFactor { get; } - - /// - /// Gets the vertical sampling factor. - /// - int VerticalSamplingFactor { get; } - - /// - /// Gets the divisors needed to apply when calculating colors. - /// - /// https://en.wikipedia.org/wiki/Chroma_subsampling - /// - /// In case of 4:2:0 subsampling the values are: Luma.SubSamplingDivisors = (1,1) Chroma.SubSamplingDivisors = (2,2) - /// - Size SubSamplingDivisors { get; } - - /// - /// Gets the index of the quantization table for this block. - /// - int QuantizationTableIndex { get; } - - /// - /// Gets the storing the "raw" frequency-domain decoded + unzigged blocks. - /// We need to apply IDCT and dequantization to transform them into color-space blocks. - /// - Buffer2D SpectralBlocks { get; } - - /// - /// Gets or sets DC coefficient predictor. - /// - int DcPredictor { get; set; } - - /// - /// Gets or sets the index for the DC table. - /// - int DcTableId { get; set; } - - /// - /// Gets or sets the index for the AC table. - /// - int AcTableId { get; set; } - - /// - /// Initializes component for future buffers initialization. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - void Init(int maxSubFactorH, int maxSubFactorV); - - /// - /// Allocates the spectral blocks. - /// - /// if set to true, use the full height of a block, otherwise use the vertical sampling factor. - void AllocateSpectral(bool fullScan); - - /// - /// Releases resources. - /// - void Dispose(); - } + void Dispose(); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs index e270198b7c..ecec723a98 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs @@ -1,49 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Interface for a JPEG scan decoder. +/// +internal interface IJpegScanDecoder { /// - /// Interface for a JPEG scan decoder. + /// Sets the reset interval. + /// + int ResetInterval { set; } + + /// + /// Gets or sets the spectral selection start. + /// + int SpectralStart { get; set; } + + /// + /// Gets or sets the spectral selection end. + /// + int SpectralEnd { get; set; } + + /// + /// Gets or sets the successive approximation high bit end. + /// + int SuccessiveHigh { get; set; } + + /// + /// Gets or sets the successive approximation low bit end. + /// + int SuccessiveLow { get; set; } + + /// + /// Decodes the entropy coded data. + /// + /// Component count in the current scan. + void ParseEntropyCodedData(int scanComponentCount); + + /// + /// Sets the JpegFrame and its components and injects the frame data into the spectral converter. /// - internal interface IJpegScanDecoder - { - /// - /// Sets the reset interval. - /// - int ResetInterval { set; } - - /// - /// Gets or sets the spectral selection start. - /// - int SpectralStart { get; set; } - - /// - /// Gets or sets the spectral selection end. - /// - int SpectralEnd { get; set; } - - /// - /// Gets or sets the successive approximation high bit end. - /// - int SuccessiveHigh { get; set; } - - /// - /// Gets or sets the successive approximation low bit end. - /// - int SuccessiveLow { get; set; } - - /// - /// Decodes the entropy coded data. - /// - /// Component count in the current scan. - void ParseEntropyCodedData(int scanComponentCount); - - /// - /// Sets the JpegFrame and its components and injects the frame data into the spectral converter. - /// - /// The frame. - /// The raw JPEG data. - void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - } + /// The frame. + /// The raw JPEG data. + void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index cbbb44ffbc..fcb98b41b3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -1,28 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +/// +/// Represents decompressed, unprocessed jpeg data with spectral space -s. +/// +internal interface IRawJpegData : IDisposable { /// - /// Represents decompressed, unprocessed jpeg data with spectral space -s. + /// Gets the color space /// - internal interface IRawJpegData : IDisposable - { - /// - /// Gets the color space - /// - JpegColorSpace ColorSpace { get; } + JpegColorSpace ColorSpace { get; } - /// - /// Gets the components. - /// - JpegComponent[] Components { get; } + /// + /// Gets the components. + /// + JpegComponent[] Components { get; } - /// - /// Gets the quantization tables, in natural order. - /// - Block8x8F[] QuantizationTables { get; } - } + /// + /// Gets the quantization tables, in natural order. + /// + Block8x8F[] QuantizationTables { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index 89d3d412d0..3d015f4d94 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -1,124 +1,122 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Provides information about the JFIF marker segment. +/// TODO: Thumbnail? +/// +internal readonly struct JFifMarker : IEquatable { /// - /// Provides information about the JFIF marker segment. - /// TODO: Thumbnail? + /// Gets the length of an JFIF marker segment. /// - internal readonly struct JFifMarker : IEquatable + public const int Length = 13; + + /// + /// Initializes a new instance of the struct. + /// + /// The major version. + /// The minor version. + /// The units for the density values. + /// The horizontal pixel density. + /// The vertical pixel density. + private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, short xDensity, short yDensity) { - /// - /// Gets the length of an JFIF marker segment. - /// - public const int Length = 13; - - /// - /// Initializes a new instance of the struct. - /// - /// The major version. - /// The minor version. - /// The units for the density values. - /// The horizontal pixel density. - /// The vertical pixel density. - private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, short xDensity, short yDensity) + if (xDensity <= 0) { - if (xDensity <= 0) - { - JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0."); - } + JpegThrowHelper.ThrowInvalidImageContentException($"X-Density {xDensity} must be greater than 0."); + } - if (yDensity <= 0) - { - JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0."); - } + if (yDensity <= 0) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Y-Density {yDensity} must be greater than 0."); + } - this.MajorVersion = majorVersion; - this.MinorVersion = minorVersion; + this.MajorVersion = majorVersion; + this.MinorVersion = minorVersion; - // LibJpeg and co will simply cast and not try to enforce a range. - this.DensityUnits = (PixelResolutionUnit)densityUnits; - this.XDensity = xDensity; - this.YDensity = yDensity; - } + // LibJpeg and co will simply cast and not try to enforce a range. + this.DensityUnits = (PixelResolutionUnit)densityUnits; + this.XDensity = xDensity; + this.YDensity = yDensity; + } + + /// + /// Gets the major version. + /// + public byte MajorVersion { get; } + + /// + /// Gets the minor version. + /// + public byte MinorVersion { get; } + + /// + /// Gets the units for the following pixel density fields + /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// 01 : Pixels per inch (2.54 cm) + /// 02 : Pixels per centimeter + /// + public PixelResolutionUnit DensityUnits { get; } - /// - /// Gets the major version. - /// - public byte MajorVersion { get; } - - /// - /// Gets the minor version. - /// - public byte MinorVersion { get; } - - /// - /// Gets the units for the following pixel density fields - /// 00 : No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// 01 : Pixels per inch (2.54 cm) - /// 02 : Pixels per centimeter - /// - public PixelResolutionUnit DensityUnits { get; } - - /// - /// Gets the horizontal pixel density. Must not be zero. - /// - public short XDensity { get; } - - /// - /// Gets the vertical pixel density. Must not be zero. - /// - public short YDensity { get; } - - /// - /// Converts the specified byte array representation of an JFIF marker to its equivalent and - /// returns a value that indicates whether the conversion succeeded. - /// - /// The byte array containing metadata to parse. - /// The marker to return. - public static bool TryParse(byte[] bytes, out JFifMarker marker) + /// + /// Gets the horizontal pixel density. Must not be zero. + /// + public short XDensity { get; } + + /// + /// Gets the vertical pixel density. Must not be zero. + /// + public short YDensity { get; } + + /// + /// Converts the specified byte array representation of an JFIF marker to its equivalent and + /// returns a value that indicates whether the conversion succeeded. + /// + /// The byte array containing metadata to parse. + /// The marker to return. + public static bool TryParse(byte[] bytes, out JFifMarker marker) + { + if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker)) { - if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker)) + byte majorVersion = bytes[5]; + byte minorVersion = bytes[6]; + byte densityUnits = bytes[7]; + short xDensity = (short)((bytes[8] << 8) | bytes[9]); + short yDensity = (short)((bytes[10] << 8) | bytes[11]); + + if (xDensity > 0 && yDensity > 0) { - byte majorVersion = bytes[5]; - byte minorVersion = bytes[6]; - byte densityUnits = bytes[7]; - short xDensity = (short)((bytes[8] << 8) | bytes[9]); - short yDensity = (short)((bytes[10] << 8) | bytes[11]); - - if (xDensity > 0 && yDensity > 0) - { - marker = new JFifMarker(majorVersion, minorVersion, densityUnits, xDensity, yDensity); - return true; - } + marker = new JFifMarker(majorVersion, minorVersion, densityUnits, xDensity, yDensity); + return true; } - - marker = default; - return false; } - /// - public bool Equals(JFifMarker other) - => this.MajorVersion == other.MajorVersion - && this.MinorVersion == other.MinorVersion - && this.DensityUnits == other.DensityUnits - && this.XDensity == other.XDensity - && this.YDensity == other.YDensity; - - /// - public override bool Equals(object obj) => obj is JFifMarker other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.MajorVersion, - this.MinorVersion, - this.DensityUnits, - this.XDensity, - this.YDensity); + marker = default; + return false; } + + /// + public bool Equals(JFifMarker other) + => this.MajorVersion == other.MajorVersion + && this.MinorVersion == other.MinorVersion + && this.DensityUnits == other.DensityUnits + && this.XDensity == other.XDensity + && this.YDensity == other.YDensity; + + /// + public override bool Equals(object obj) => obj is JFifMarker other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.MajorVersion, + this.MinorVersion, + this.DensityUnits, + this.XDensity, + this.YDensity); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs index b5e9009ec9..d80679db69 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs @@ -4,224 +4,223 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Used to buffer and track the bits read from the Huffman entropy encoded data. - /// - internal struct JpegBitReader - { - private readonly BufferedReadStream stream; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; - // The entropy encoded code buffer. - private ulong data; +/// +/// Used to buffer and track the bits read from the Huffman entropy encoded data. +/// +internal struct JpegBitReader +{ + private readonly BufferedReadStream stream; - // The number of valid bits left to read in the buffer. - private int remainingBits; + // The entropy encoded code buffer. + private ulong data; - // Whether there is no more good data to pull from the stream for the current mcu. - private bool badData; + // The number of valid bits left to read in the buffer. + private int remainingBits; - public JpegBitReader(BufferedReadStream stream) - { - this.stream = stream; - this.data = 0ul; - this.remainingBits = 0; - this.Marker = JpegConstants.Markers.XFF; - this.MarkerPosition = 0; - this.badData = false; - this.NoData = false; - } + // Whether there is no more good data to pull from the stream for the current mcu. + private bool badData; - /// - /// Gets the current, if any, marker in the input stream. - /// - public byte Marker { get; private set; } + public JpegBitReader(BufferedReadStream stream) + { + this.stream = stream; + this.data = 0ul; + this.remainingBits = 0; + this.Marker = JpegConstants.Markers.XFF; + this.MarkerPosition = 0; + this.badData = false; + this.NoData = false; + } - /// - /// Gets the opening position of an identified marker. - /// - public long MarkerPosition { get; private set; } + /// + /// Gets the current, if any, marker in the input stream. + /// + public byte Marker { get; private set; } - /// - /// Gets a value indicating whether to continue reading the input stream. - /// - public bool NoData { get; private set; } + /// + /// Gets the opening position of an identified marker. + /// + public long MarkerPosition { get; private set; } - [MethodImpl(InliningOptions.ShortMethod)] - public void CheckBits() - { - if (this.remainingBits < JpegConstants.Huffman.MinBits) - { - this.FillBuffer(); - } - } + /// + /// Gets a value indicating whether to continue reading the input stream. + /// + public bool NoData { get; private set; } - [MethodImpl(InliningOptions.ShortMethod)] - public void Reset() + [MethodImpl(InliningOptions.ShortMethod)] + public void CheckBits() + { + if (this.remainingBits < JpegConstants.Huffman.MinBits) { - this.data = 0ul; - this.remainingBits = 0; - this.Marker = JpegConstants.Markers.XFF; - this.MarkerPosition = 0; - this.badData = false; - this.NoData = false; + this.FillBuffer(); } + } - /// - /// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool HasRestartMarker() => HasRestart(this.Marker); - - /// - /// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7 - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); + [MethodImpl(InliningOptions.ShortMethod)] + public void Reset() + { + this.data = 0ul; + this.remainingBits = 0; + this.Marker = JpegConstants.Markers.XFF; + this.MarkerPosition = 0; + this.badData = false; + this.NoData = false; + } - [MethodImpl(InliningOptions.AlwaysInline)] - public void FillBuffer() - { - // Attempt to load at least the minimum number of required bits into the buffer. - // We fail to do so only if we hit a marker or reach the end of the input stream. - this.remainingBits += JpegConstants.Huffman.FetchBits; - this.data = (this.data << JpegConstants.Huffman.FetchBits) | this.GetBytes(); - } + /// + /// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool HasRestartMarker() => HasRestart(this.Marker); - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe int DecodeHuffman(ref HuffmanTable h) - { - this.CheckBits(); - int index = this.PeekBits(JpegConstants.Huffman.LookupBits); - int size = h.LookaheadSize[index]; + /// + /// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7 + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); - if (size < JpegConstants.Huffman.SlowBits) - { - this.remainingBits -= size; - return h.LookaheadValue[index]; - } + [MethodImpl(InliningOptions.AlwaysInline)] + public void FillBuffer() + { + // Attempt to load at least the minimum number of required bits into the buffer. + // We fail to do so only if we hit a marker or reach the end of the input stream. + this.remainingBits += JpegConstants.Huffman.FetchBits; + this.data = (this.data << JpegConstants.Huffman.FetchBits) | this.GetBytes(); + } - ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits); - while (x > h.MaxCode[size]) - { - size++; - } + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe int DecodeHuffman(ref HuffmanTable h) + { + this.CheckBits(); + int index = this.PeekBits(JpegConstants.Huffman.LookupBits); + int size = h.LookaheadSize[index]; + if (size < JpegConstants.Huffman.SlowBits) + { this.remainingBits -= size; - - return h.Values[(h.ValOffset[size] + (int)(x >> (JpegConstants.Huffman.RegisterSize - size))) & 0xFF]; + return h.LookaheadValue[index]; } - [MethodImpl(InliningOptions.ShortMethod)] - public int Receive(int nbits) + ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits); + while (x > h.MaxCode[size]) { - this.CheckBits(); - return Extend(this.GetBits(nbits), nbits); + size++; } - [MethodImpl(InliningOptions.ShortMethod)] - private static bool HasRestart(byte marker) - => marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7; + this.remainingBits -= size; + + return h.Values[(h.ValOffset[size] + (int)(x >> (JpegConstants.Huffman.RegisterSize - size))) & 0xFF]; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public int Receive(int nbits) + { + this.CheckBits(); + return Extend(this.GetBits(nbits), nbits); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool HasRestart(byte marker) + => marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7; - [MethodImpl(InliningOptions.ShortMethod)] - public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits); + [MethodImpl(InliningOptions.ShortMethod)] + public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits); - [MethodImpl(InliningOptions.ShortMethod)] - public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits); + [MethodImpl(InliningOptions.ShortMethod)] + public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits); - [MethodImpl(InliningOptions.AlwaysInline)] - private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); + [MethodImpl(InliningOptions.AlwaysInline)] + private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); - [MethodImpl(InliningOptions.ShortMethod)] - private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1)); + [MethodImpl(InliningOptions.ShortMethod)] + private static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1)); - [MethodImpl(InliningOptions.ShortMethod)] - private ulong GetBytes() + [MethodImpl(InliningOptions.ShortMethod)] + private ulong GetBytes() + { + ulong temp = 0; + for (int i = 0; i < JpegConstants.Huffman.FetchLoop; i++) { - ulong temp = 0; - for (int i = 0; i < JpegConstants.Huffman.FetchLoop; i++) - { - int b = this.ReadStream(); + int b = this.ReadStream(); - // Found a marker. - if (b == JpegConstants.Markers.XFF) + // Found a marker. + if (b == JpegConstants.Markers.XFF) + { + int c = this.ReadStream(); + while (c == JpegConstants.Markers.XFF) { - int c = this.ReadStream(); - while (c == JpegConstants.Markers.XFF) - { - // Loop here to discard any padding FF bytes on terminating marker, - // so that we can save a valid marker value. - c = this.ReadStream(); - } - - // Found a marker - // We accept multiple FF bytes followed by a 0 as meaning a single FF data byte. - // even though it's considered 'invalid' according to the specs. - if (c != 0) - { - // It's a trick so we won't read past actual marker - this.badData = true; - this.Marker = (byte)c; - this.MarkerPosition = this.stream.Position - 2; - } + // Loop here to discard any padding FF bytes on terminating marker, + // so that we can save a valid marker value. + c = this.ReadStream(); } - temp = (temp << 8) | (ulong)(long)b; + // Found a marker + // We accept multiple FF bytes followed by a 0 as meaning a single FF data byte. + // even though it's considered 'invalid' according to the specs. + if (c != 0) + { + // It's a trick so we won't read past actual marker + this.badData = true; + this.Marker = (byte)c; + this.MarkerPosition = this.stream.Position - 2; + } } - return temp; + temp = (temp << 8) | (ulong)(long)b; } - [MethodImpl(InliningOptions.ShortMethod)] - public bool FindNextMarker() + return temp; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public bool FindNextMarker() + { + while (true) { - while (true) + int b = this.stream.ReadByte(); + if (b == -1) { - int b = this.stream.ReadByte(); - if (b == -1) - { - return false; - } + return false; + } - // Found a marker. - if (b == JpegConstants.Markers.XFF) + // Found a marker. + if (b == JpegConstants.Markers.XFF) + { + while (b == JpegConstants.Markers.XFF) { - while (b == JpegConstants.Markers.XFF) + // Loop here to discard any padding FF bytes on terminating marker. + b = this.stream.ReadByte(); + if (b == -1) { - // Loop here to discard any padding FF bytes on terminating marker. - b = this.stream.ReadByte(); - if (b == -1) - { - return false; - } + return false; } + } - // Found a valid marker. Exit loop - if (b != 0) - { - this.Marker = (byte)b; - this.MarkerPosition = this.stream.Position - 2; - return true; - } + // Found a valid marker. Exit loop + if (b != 0) + { + this.Marker = (byte)b; + this.MarkerPosition = this.stream.Position - 2; + return true; } } } + } - [MethodImpl(InliningOptions.AlwaysInline)] - private int ReadStream() + [MethodImpl(InliningOptions.AlwaysInline)] + private int ReadStream() + { + int value = this.badData ? 0 : this.stream.ReadByte(); + if (value == -1) { - int value = this.badData ? 0 : this.stream.ReadByte(); - if (value == -1) - { - // We've encountered the end of the file stream which means there's no EOI marker - // in the image or the SOS marker has the wrong dimensions set. - this.badData = true; - this.NoData = true; - value = 0; - } - - return value; + // We've encountered the end of the file stream which means there's no EOI marker + // in the image or the SOS marker has the wrong dimensions set. + this.badData = true; + this.NoData = true; + value = 0; } + + return value; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 82f6be7548..5741c9536f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -1,137 +1,135 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Represents a single frame component. +/// +internal class JpegComponent : IDisposable, IJpegComponent { - /// - /// Represents a single frame component. - /// - internal class JpegComponent : IDisposable, IJpegComponent - { - private readonly MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; - public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) - { - this.memoryAllocator = memoryAllocator; - this.Frame = frame; - this.Id = id; + public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + { + this.memoryAllocator = memoryAllocator; + this.Frame = frame; + this.Id = id; - this.HorizontalSamplingFactor = horizontalFactor; - this.VerticalSamplingFactor = verticalFactor; - this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); - this.QuantizationTableIndex = quantizationTableIndex; - this.Index = index; - } + this.QuantizationTableIndex = quantizationTableIndex; + this.Index = index; + } - /// - /// Gets the component id. - /// - public byte Id { get; } + /// + /// Gets the component id. + /// + public byte Id { get; } - /// - /// Gets or sets DC coefficient predictor. - /// - public int DcPredictor { get; set; } + /// + /// Gets or sets DC coefficient predictor. + /// + public int DcPredictor { get; set; } - /// - /// Gets the horizontal sampling factor. - /// - public int HorizontalSamplingFactor { get; } + /// + /// Gets the horizontal sampling factor. + /// + public int HorizontalSamplingFactor { get; } - /// - /// Gets the vertical sampling factor. - /// - public int VerticalSamplingFactor { get; } + /// + /// Gets the vertical sampling factor. + /// + public int VerticalSamplingFactor { get; } - /// - public Buffer2D SpectralBlocks { get; private set; } + /// + public Buffer2D SpectralBlocks { get; private set; } - /// - public Size SubSamplingDivisors { get; private set; } + /// + public Size SubSamplingDivisors { get; private set; } - /// - public int QuantizationTableIndex { get; } + /// + public int QuantizationTableIndex { get; } - /// - public int Index { get; } + /// + public int Index { get; } - /// - public Size SizeInBlocks { get; private set; } + /// + public Size SizeInBlocks { get; private set; } - /// - public Size SamplingFactors { get; set; } + /// + public Size SamplingFactors { get; set; } - /// - /// Gets the number of blocks per line. - /// - public int WidthInBlocks { get; private set; } + /// + /// Gets the number of blocks per line. + /// + public int WidthInBlocks { get; private set; } - /// - /// Gets the number of blocks per column. - /// - public int HeightInBlocks { get; private set; } + /// + /// Gets the number of blocks per column. + /// + public int HeightInBlocks { get; private set; } - /// - /// Gets or sets the index for the DC Huffman table. - /// - public int DcTableId { get; set; } + /// + /// Gets or sets the index for the DC Huffman table. + /// + public int DcTableId { get; set; } - /// - /// Gets or sets the index for the AC Huffman table. - /// - public int AcTableId { get; set; } + /// + /// Gets or sets the index for the AC Huffman table. + /// + public int AcTableId { get; set; } - public JpegFrame Frame { get; } + public JpegFrame Frame { get; } - /// - public void Dispose() - { - this.SpectralBlocks?.Dispose(); - this.SpectralBlocks = null; - } + /// + public void Dispose() + { + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; + } - /// - /// Initializes component for future buffers initialization. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(int maxSubFactorH, int maxSubFactorV) - { - this.WidthInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); + /// + /// Initializes component for future buffers initialization. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(int maxSubFactorH, int maxSubFactorV) + { + this.WidthInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH); - this.HeightInBlocks = (int)MathF.Ceiling( - MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); + this.HeightInBlocks = (int)MathF.Ceiling( + MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV); - int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; - int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; - this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); + int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; + this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); + this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); - if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) - { - JpegThrowHelper.ThrowBadSampling(); - } + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) + { + JpegThrowHelper.ThrowBadSampling(); } + } - /// - public void AllocateSpectral(bool fullScan) + /// + public void AllocateSpectral(bool fullScan) + { + if (this.SpectralBlocks != null) { - if (this.SpectralBlocks != null) - { - // This method will be called each scan marker so we need to allocate only once. - return; - } + // This method will be called each scan marker so we need to allocate only once. + return; + } - int spectralAllocWidth = this.SizeInBlocks.Width; - int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; + int spectralAllocWidth = this.SizeInBlocks.Width; + int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); - } + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs index 185e5b06b0..a372ff496f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFileMarker.cs @@ -4,65 +4,64 @@ using System.Globalization; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Represents a jpeg file marker. +/// +internal readonly struct JpegFileMarker { /// - /// Represents a jpeg file marker. + /// Initializes a new instance of the struct. /// - internal readonly struct JpegFileMarker + /// The marker + /// The position within the stream + public JpegFileMarker(byte marker, long position) + : this(marker, position, false) { - /// - /// Initializes a new instance of the struct. - /// - /// The marker - /// The position within the stream - public JpegFileMarker(byte marker, long position) - : this(marker, position, false) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The marker - /// The position within the stream - /// Whether the current marker is invalid - public JpegFileMarker(byte marker, long position, bool invalid) - { - this.Marker = marker; - this.Position = position; - this.Invalid = invalid; - } + } - /// - /// Gets a value indicating whether the current marker is invalid - /// - public bool Invalid - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Initializes a new instance of the struct. + /// + /// The marker + /// The position within the stream + /// Whether the current marker is invalid + public JpegFileMarker(byte marker, long position, bool invalid) + { + this.Marker = marker; + this.Position = position; + this.Invalid = invalid; + } - /// - /// Gets the position of the marker within a stream - /// - public byte Marker - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Gets a value indicating whether the current marker is invalid + /// + public bool Invalid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - /// Gets the position of the marker within a stream - /// - public long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + /// + /// Gets the position of the marker within a stream + /// + public byte Marker + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - public override string ToString() - => this.Marker.ToString("X", CultureInfo.InvariantCulture); + /// + /// Gets the position of the marker within a stream + /// + public long Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; } + + /// + public override string ToString() + => this.Marker.ToString("X", CultureInfo.InvariantCulture); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index 5c6535bd37..9937cc8e20 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -1,152 +1,149 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +/// +/// Represent a single jpeg frame. +/// +internal sealed class JpegFrame : IDisposable { + public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) + { + this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; + this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10; + + this.Precision = precision; + this.MaxColorChannelValue = MathF.Pow(2, precision) - 1; + + this.PixelWidth = width; + this.PixelHeight = height; + + this.ComponentCount = componentCount; + } + /// - /// Represent a single jpeg frame. + /// Gets a value indicating whether the frame uses the extended specification. /// - internal sealed class JpegFrame : IDisposable - { - public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount) - { - this.IsExtended = sofMarker.Marker is JpegConstants.Markers.SOF1 or JpegConstants.Markers.SOF9; - this.Progressive = sofMarker.Marker is JpegConstants.Markers.SOF2 or JpegConstants.Markers.SOF10; + public bool IsExtended { get; private set; } - this.Precision = precision; - this.MaxColorChannelValue = MathF.Pow(2, precision) - 1; + /// + /// Gets a value indicating whether the frame uses the progressive specification. + /// + public bool Progressive { get; private set; } - this.PixelWidth = width; - this.PixelHeight = height; + /// + /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). + /// + /// + /// This is true for progressive and baseline non-interleaved images. + /// + public bool Interleaved { get; set; } - this.ComponentCount = componentCount; - } + /// + /// Gets the precision. + /// + public byte Precision { get; private set; } + + /// + /// Gets the maximum color value derived from . + /// + public float MaxColorChannelValue { get; private set; } + + /// + /// Gets the number of pixel per row. + /// + public int PixelHeight { get; private set; } + + /// + /// Gets the number of pixels per line. + /// + public int PixelWidth { get; private set; } + + /// + /// Gets the pixel size of the image. + /// + public Size PixelSize => new(this.PixelWidth, this.PixelHeight); + + /// + /// Gets the number of components within a frame. + /// + public byte ComponentCount { get; private set; } + + /// + /// Gets or sets the component id collection. + /// + public byte[] ComponentIds { get; set; } - /// - /// Gets a value indicating whether the frame uses the extended specification. - /// - public bool IsExtended { get; private set; } - - /// - /// Gets a value indicating whether the frame uses the progressive specification. - /// - public bool Progressive { get; private set; } - - /// - /// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers). - /// - /// - /// This is true for progressive and baseline non-interleaved images. - /// - public bool Interleaved { get; set; } - - /// - /// Gets the precision. - /// - public byte Precision { get; private set; } - - /// - /// Gets the maximum color value derived from . - /// - public float MaxColorChannelValue { get; private set; } - - /// - /// Gets the number of pixel per row. - /// - public int PixelHeight { get; private set; } - - /// - /// Gets the number of pixels per line. - /// - public int PixelWidth { get; private set; } - - /// - /// Gets the pixel size of the image. - /// - public Size PixelSize => new(this.PixelWidth, this.PixelHeight); - - /// - /// Gets the number of components within a frame. - /// - public byte ComponentCount { get; private set; } - - /// - /// Gets or sets the component id collection. - /// - public byte[] ComponentIds { get; set; } - - /// - /// Gets or sets the order in which to process the components. - /// in interleaved mode. - /// - public byte[] ComponentOrder { get; set; } - - /// - /// Gets or sets the frame component collection. - /// - public JpegComponent[] Components { get; set; } - - /// - /// Gets or sets the number of MCU's per line. - /// - public int McusPerLine { get; set; } - - /// - /// Gets or sets the number of MCU's per column. - /// - public int McusPerColumn { get; set; } - - /// - /// Gets the mcu size of the image. - /// - public Size McuSize => new(this.McusPerLine, this.McusPerColumn); - - /// - /// Gets the color depth, in number of bits per pixel. - /// - public int BitsPerPixel => this.ComponentCount * this.Precision; - - /// - public void Dispose() + /// + /// Gets or sets the order in which to process the components. + /// in interleaved mode. + /// + public byte[] ComponentOrder { get; set; } + + /// + /// Gets or sets the frame component collection. + /// + public JpegComponent[] Components { get; set; } + + /// + /// Gets or sets the number of MCU's per line. + /// + public int McusPerLine { get; set; } + + /// + /// Gets or sets the number of MCU's per column. + /// + public int McusPerColumn { get; set; } + + /// + /// Gets the mcu size of the image. + /// + public Size McuSize => new(this.McusPerLine, this.McusPerColumn); + + /// + /// Gets the color depth, in number of bits per pixel. + /// + public int BitsPerPixel => this.ComponentCount * this.Precision; + + /// + public void Dispose() + { + if (this.Components != null) { - if (this.Components != null) + for (int i = 0; i < this.Components.Length; i++) { - for (int i = 0; i < this.Components.Length; i++) - { - this.Components[i]?.Dispose(); - } - - this.Components = null; + this.Components[i]?.Dispose(); } + + this.Components = null; } + } - /// - /// Allocates the frame component blocks. - /// - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(int maxSubFactorH, int maxSubFactorV) - { - this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); + /// + /// Allocates the frame component blocks. + /// + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(int maxSubFactorH, int maxSubFactorV) + { + this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8); - for (int i = 0; i < this.ComponentCount; i++) - { - IJpegComponent component = this.Components[i]; - component.Init(maxSubFactorH, maxSubFactorV); - } + for (int i = 0; i < this.ComponentCount; i++) + { + IJpegComponent component = this.Components[i]; + component.Init(maxSubFactorH, maxSubFactorV); } + } - public void AllocateComponents() + public void AllocateComponents() + { + bool fullScan = this.Progressive || !this.Interleaved; + for (int i = 0; i < this.ComponentCount; i++) { - bool fullScan = this.Progressive || !this.Interleaved; - for (int i = 0; i < this.ComponentCount; i++) - { - IJpegComponent component = this.Components[i]; - component.AllocateSpectral(fullScan); - } + IJpegComponent component = this.Components[i]; + component.AllocateSpectral(fullScan); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index ed678bd2b2..795f21a57f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,95 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +/// +/// Provides methods for identifying metadata and color profiles within jpeg images. +/// +internal static class ProfileResolver { /// - /// Provides methods for identifying metadata and color profiles within jpeg images. + /// Gets the JFIF specific markers. /// - internal static class ProfileResolver + public static ReadOnlySpan JFifMarker => new[] { - /// - /// Gets the JFIF specific markers. - /// - public static ReadOnlySpan JFifMarker => new[] - { - (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' - }; + (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' + }; - /// - /// Gets the ICC specific markers. - /// - 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' - }; + /// + /// Gets the ICC specific markers. + /// + 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' + }; - /// - /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data. - /// - public static ReadOnlySpan AdobePhotoshopApp13Marker => new[] - { - (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0' - }; + /// + /// Gets the adobe photoshop APP13 marker which can contain IPTC meta data. + /// + public static ReadOnlySpan AdobePhotoshopApp13Marker => new[] + { + (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', (byte)'\0' + }; - /// - /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block. - /// - public static ReadOnlySpan AdobeImageResourceBlockMarker => new[] - { - (byte)'8', (byte)'B', (byte)'I', (byte)'M' - }; + /// + /// Gets the 8BIM marker, which signals the start of a adobe specific image resource block. + /// + public static ReadOnlySpan AdobeImageResourceBlockMarker => new[] + { + (byte)'8', (byte)'B', (byte)'I', (byte)'M' + }; - /// - /// Gets a IPTC Image resource ID. - /// - public static ReadOnlySpan AdobeIptcMarker => new[] - { - (byte)4, (byte)4 - }; + /// + /// Gets a IPTC Image resource ID. + /// + public static ReadOnlySpan AdobeIptcMarker => new[] + { + (byte)4, (byte)4 + }; - /// - /// Gets the EXIF specific markers. - /// - public static ReadOnlySpan ExifMarker => new[] - { - (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' - }; + /// + /// Gets the EXIF specific markers. + /// + public static ReadOnlySpan ExifMarker => new[] + { + (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' + }; - /// - /// Gets the XMP specific markers. - /// - public static ReadOnlySpan XmpMarker => new[] - { - (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)':', (byte)'/', (byte)'/', - (byte)'n', (byte)'s', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b', - (byte)'e', (byte)'.', (byte)'c', (byte)'o', (byte)'m', (byte)'/', (byte)'x', - (byte)'a', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'0', (byte)'/', - (byte)0 - }; + /// + /// Gets the XMP specific markers. + /// + public static ReadOnlySpan XmpMarker => new[] + { + (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)':', (byte)'/', (byte)'/', + (byte)'n', (byte)'s', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b', + (byte)'e', (byte)'.', (byte)'c', (byte)'o', (byte)'m', (byte)'/', (byte)'x', + (byte)'a', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'0', (byte)'/', + (byte)0 + }; - /// - /// Gets the Adobe specific markers . - /// - public static ReadOnlySpan AdobeMarker => new[] - { - (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e' - }; + /// + /// Gets the Adobe specific markers . + /// + 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. - /// - /// The bytes to check. - /// The profile identifier. - /// The . - public static bool IsProfile(ReadOnlySpan bytesToCheck, ReadOnlySpan profileIdentifier) - { - return bytesToCheck.Length >= profileIdentifier.Length - && bytesToCheck[..profileIdentifier.Length].SequenceEqual(profileIdentifier); - } + /// + /// Returns a value indicating whether the passed bytes are a match to the profile identifier. + /// + /// The bytes to check. + /// The profile identifier. + /// The . + public static bool IsProfile(ReadOnlySpan bytesToCheck, ReadOnlySpan profileIdentifier) + { + return bytesToCheck.Length >= profileIdentifier.Length + && bytesToCheck[..profileIdentifier.Length].SequenceEqual(profileIdentifier); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index be59e90fab..0eb484b94a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -1,125 +1,124 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// Converter used to convert jpeg spectral data to pixels. +/// +internal abstract class SpectralConverter { /// - /// Converter used to convert jpeg spectral data to pixels. + /// Supported scaled spectral block sizes for scaled IDCT decoding. /// - internal abstract class SpectralConverter + private static readonly int[] ScaledBlockSizes = new int[] { - /// - /// Supported scaled spectral block sizes for scaled IDCT decoding. - /// - private static readonly int[] ScaledBlockSizes = new int[] - { - // 8 => 1, 1/8 of the original size - 1, - - // 8 => 2, 1/4 of the original size - 2, - - // 8 => 4, 1/2 of the original size - 4, - }; - - /// - /// Gets a value indicating whether this converter has converted spectral - /// data of the current image or not. - /// - protected bool Converted { get; private set; } - - /// - /// Injects jpeg image decoding metadata. - /// - /// - /// This should be called exactly once during SOF (Start Of Frame) marker. - /// - /// Instance containing decoder-specific parameters. - /// Instance containing decoder-specific parameters. - public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); - - /// - /// Initializes this spectral decoder instance for decoding. - /// This should be called exactly once after all markers which can alter - /// spectral decoding parameters. - /// - public abstract void PrepareForDecoding(); - - /// - /// Converts single spectral jpeg stride to color stride in baseline - /// decoding mode. - /// - /// - /// Called once per decoded spectral stride in - /// only for baseline interleaved jpeg images. - /// Spectral 'stride' doesn't particularly mean 'single stride'. - /// Actual stride height depends on the subsampling factor of the given image. - /// - public abstract void ConvertStrideBaseline(); - - /// - /// Marks current converter state as 'converted'. - /// - /// - /// This must be called only for baseline interleaved jpeg's. - /// - public void CommitConversion() - { - DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConversion)} must be called only once"); + // 8 => 1, 1/8 of the original size + 1, - this.Converted = true; - } + // 8 => 2, 1/4 of the original size + 2, + + // 8 => 4, 1/2 of the original size + 4, + }; + + /// + /// Gets a value indicating whether this converter has converted spectral + /// data of the current image or not. + /// + protected bool Converted { get; private set; } + + /// + /// Injects jpeg image decoding metadata. + /// + /// + /// This should be called exactly once during SOF (Start Of Frame) marker. + /// + /// Instance containing decoder-specific parameters. + /// Instance containing decoder-specific parameters. + public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); + + /// + /// Initializes this spectral decoder instance for decoding. + /// This should be called exactly once after all markers which can alter + /// spectral decoding parameters. + /// + public abstract void PrepareForDecoding(); + + /// + /// Converts single spectral jpeg stride to color stride in baseline + /// decoding mode. + /// + /// + /// Called once per decoded spectral stride in + /// only for baseline interleaved jpeg images. + /// Spectral 'stride' doesn't particularly mean 'single stride'. + /// Actual stride height depends on the subsampling factor of the given image. + /// + public abstract void ConvertStrideBaseline(); - /// - /// Gets the color converter. - /// - /// The jpeg frame with the color space to convert to. - /// The raw JPEG data. - /// The color converter. - protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision); - - /// - /// Calculates image size with optional scaling. - /// - /// - /// Does not apply scalling if is null. - /// - /// Size of the image. - /// Target size of the image. - /// Spectral block size, equals to 8 if scaling is not applied. - /// Resulting image size, equals to if scaling is not applied. - public static Size CalculateResultingImageSize(Size size, Size? targetSize, out int blockPixelSize) + /// + /// Marks current converter state as 'converted'. + /// + /// + /// This must be called only for baseline interleaved jpeg's. + /// + public void CommitConversion() + { + DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConversion)} must be called only once"); + + this.Converted = true; + } + + /// + /// Gets the color converter. + /// + /// The jpeg frame with the color space to convert to. + /// The raw JPEG data. + /// The color converter. + protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision); + + /// + /// Calculates image size with optional scaling. + /// + /// + /// Does not apply scalling if is null. + /// + /// Size of the image. + /// Target size of the image. + /// Spectral block size, equals to 8 if scaling is not applied. + /// Resulting image size, equals to if scaling is not applied. + public static Size CalculateResultingImageSize(Size size, Size? targetSize, out int blockPixelSize) + { + const int blockNativePixelSize = 8; + + blockPixelSize = blockNativePixelSize; + if (targetSize != null) { - const int blockNativePixelSize = 8; + Size tSize = targetSize.Value; - blockPixelSize = blockNativePixelSize; - if (targetSize != null) - { - Size tSize = targetSize.Value; + int fullBlocksWidth = (int)((uint)size.Width / blockNativePixelSize); + int fullBlocksHeight = (int)((uint)size.Height / blockNativePixelSize); - int fullBlocksWidth = (int)((uint)size.Width / blockNativePixelSize); - int fullBlocksHeight = (int)((uint)size.Height / blockNativePixelSize); + // & (blockNativePixelSize - 1) is Numerics.Modulo8(), basically + int blockWidthRemainder = size.Width & (blockNativePixelSize - 1); + int blockHeightRemainder = size.Height & (blockNativePixelSize - 1); - // & (blockNativePixelSize - 1) is Numerics.Modulo8(), basically - int blockWidthRemainder = size.Width & (blockNativePixelSize - 1); - int blockHeightRemainder = size.Height & (blockNativePixelSize - 1); + for (int i = 0; i < ScaledBlockSizes.Length; i++) + { + int blockSize = ScaledBlockSizes[i]; + int scaledWidth = (fullBlocksWidth * blockSize) + (int)Numerics.DivideCeil((uint)(blockWidthRemainder * blockSize), blockNativePixelSize); + int scaledHeight = (fullBlocksHeight * blockSize) + (int)Numerics.DivideCeil((uint)(blockHeightRemainder * blockSize), blockNativePixelSize); - for (int i = 0; i < ScaledBlockSizes.Length; i++) + if (scaledWidth >= tSize.Width && scaledHeight >= tSize.Height) { - int blockSize = ScaledBlockSizes[i]; - int scaledWidth = (fullBlocksWidth * blockSize) + (int)Numerics.DivideCeil((uint)(blockWidthRemainder * blockSize), blockNativePixelSize); - int scaledHeight = (fullBlocksHeight * blockSize) + (int)Numerics.DivideCeil((uint)(blockHeightRemainder * blockSize), blockNativePixelSize); - - if (scaledWidth >= tSize.Width && scaledHeight >= tSize.Height) - { - blockPixelSize = blockSize; - return new Size(scaledWidth, scaledHeight); - } + blockPixelSize = blockSize; + return new Size(scaledWidth, scaledHeight); } } - - return size; } + + return size; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 938815daea..8240a74587 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -1,263 +1,259 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Linq; -using System.Threading; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; + +/// +/// +/// Color decoding scheme: +/// +/// +/// Decode spectral data to Jpeg color space +/// Convert from Jpeg color space to RGB +/// Convert from RGB to target pixel space +/// +/// +/// +internal class SpectralConverter : SpectralConverter, IDisposable + where TPixel : unmanaged, IPixel { - /// + /// + /// instance associated with current + /// decoding routine. + /// + private readonly Configuration configuration; + + private JpegFrame frame; + + private IRawJpegData jpegData; + + /// + /// Jpeg component converters from decompressed spectral to color data. + /// + private ComponentProcessor[] componentProcessors; + + /// + /// Color converter from jpeg color space to target pixel color space. + /// + private JpegColorConverterBase colorConverter; + + /// + /// Intermediate buffer of RGB components used in color conversion. + /// + private IMemoryOwner rgbBuffer; + + /// + /// Proxy buffer used in packing from RGB to target TPixel pixels. + /// + private IMemoryOwner paddedProxyPixelRow; + + /// + /// Resulting 2D pixel buffer. + /// + private Buffer2D pixelBuffer; + + /// + /// How many pixel rows are processed in one 'stride'. + /// + private int pixelRowsPerStep; + + /// + /// How many pixel rows were processed. + /// + private int pixelRowCounter; + + /// + /// Represent target size after decoding for scaling decoding mode. + /// /// - /// Color decoding scheme: - /// - /// - /// Decode spectral data to Jpeg color space - /// Convert from Jpeg color space to RGB - /// Convert from RGB to target pixel space - /// - /// + /// Null if no scaling is required. /// - internal class SpectralConverter : SpectralConverter, IDisposable - where TPixel : unmanaged, IPixel + private Size? targetSize; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// Optional target size for decoded image. + public SpectralConverter(Configuration configuration, Size? targetSize = null) { - /// - /// instance associated with current - /// decoding routine. - /// - private readonly Configuration configuration; - - private JpegFrame frame; - - private IRawJpegData jpegData; - - /// - /// Jpeg component converters from decompressed spectral to color data. - /// - private ComponentProcessor[] componentProcessors; - - /// - /// Color converter from jpeg color space to target pixel color space. - /// - private JpegColorConverterBase colorConverter; - - /// - /// Intermediate buffer of RGB components used in color conversion. - /// - private IMemoryOwner rgbBuffer; - - /// - /// Proxy buffer used in packing from RGB to target TPixel pixels. - /// - private IMemoryOwner paddedProxyPixelRow; - - /// - /// Resulting 2D pixel buffer. - /// - private Buffer2D pixelBuffer; - - /// - /// How many pixel rows are processed in one 'stride'. - /// - private int pixelRowsPerStep; - - /// - /// How many pixel rows were processed. - /// - private int pixelRowCounter; - - /// - /// Represent target size after decoding for scaling decoding mode. - /// - /// - /// Null if no scaling is required. - /// - private Size? targetSize; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// Optional target size for decoded image. - public SpectralConverter(Configuration configuration, Size? targetSize = null) - { - this.configuration = configuration; + this.configuration = configuration; - this.targetSize = targetSize; - } + this.targetSize = targetSize; + } - /// - /// Gets converted pixel buffer. - /// - /// - /// For non-baseline interleaved jpeg this method does a 'lazy' spectral - /// conversion from spectral to color. - /// - /// Cancellation token. - /// Pixel buffer. - public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) + /// + /// Gets converted pixel buffer. + /// + /// + /// For non-baseline interleaved jpeg this method does a 'lazy' spectral + /// conversion from spectral to color. + /// + /// Cancellation token. + /// Pixel buffer. + public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) + { + if (!this.Converted) { - if (!this.Converted) - { - this.PrepareForDecoding(); + this.PrepareForDecoding(); - int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); + int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep); - for (int step = 0; step < steps; step++) - { - cancellationToken.ThrowIfCancellationRequested(); - this.ConvertStride(step); - } + for (int step = 0; step < steps; step++) + { + cancellationToken.ThrowIfCancellationRequested(); + this.ConvertStride(step); } + } + + Buffer2D buffer = this.pixelBuffer; + this.pixelBuffer = null; + return buffer; + } + + /// + /// Converts single spectral jpeg stride to color stride. + /// + /// Spectral stride index. + private void ConvertStride(int spectralStep) + { + int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - Buffer2D buffer = this.pixelBuffer; - this.pixelBuffer = null; - return buffer; + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); } - /// - /// Converts single spectral jpeg stride to color stride. - /// - /// Spectral stride index. - private void ConvertStride(int spectralStep) + int width = this.pixelBuffer.Width; + + for (int yy = this.pixelRowCounter; yy < maxY; yy++) { - int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); + int y = yy - this.pixelRowCounter; - for (int i = 0; i < this.componentProcessors.Length; i++) - { - this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - } + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - int width = this.pixelBuffer.Width; + this.colorConverter.ConvertToRgbInplace(values); + values = values.Slice(0, width); // slice away Jpeg padding - for (int yy = this.pixelRowCounter; yy < maxY; yy++) + Span r = this.rgbBuffer.Slice(0, width); + Span g = this.rgbBuffer.Slice(width, width); + Span b = this.rgbBuffer.Slice(width * 2, width); + + SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); + SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); + SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); + + // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. + // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, + // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. + if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) { - int y = yy - this.pixelRowCounter; - - var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - - this.colorConverter.ConvertToRgbInplace(values); - values = values.Slice(0, width); // slice away Jpeg padding - - Span r = this.rgbBuffer.Slice(0, width); - Span g = this.rgbBuffer.Slice(width, width); - Span b = this.rgbBuffer.Slice(width * 2, width); - - SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r); - SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g); - SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b); - - // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. - // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, - // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. - if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) - { - PixelOperations.Instance.PackFromRgbPlanes(r, g, b, destRow); - } - else - { - Span proxyRow = this.paddedProxyPixelRow.GetSpan(); - PixelOperations.Instance.PackFromRgbPlanes(r, g, b, proxyRow); - proxyRow[..width].CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); - } + PixelOperations.Instance.PackFromRgbPlanes(r, g, b, destRow); + } + else + { + Span proxyRow = this.paddedProxyPixelRow.GetSpan(); + PixelOperations.Instance.PackFromRgbPlanes(r, g, b, proxyRow); + proxyRow[..width].CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); } - - this.pixelRowCounter += this.pixelRowsPerStep; } - /// - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.jpegData = jpegData; - } + this.pixelRowCounter += this.pixelRowsPerStep; + } - /// - public override void PrepareForDecoding() - { - DebugGuard.IsTrue(this.colorConverter == null, "SpectralConverter.PrepareForDecoding() must be called once."); + /// + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.frame = frame; + this.jpegData = jpegData; + } - MemoryAllocator allocator = this.configuration.MemoryAllocator; + /// + public override void PrepareForDecoding() + { + DebugGuard.IsTrue(this.colorConverter == null, "SpectralConverter.PrepareForDecoding() must be called once."); - // color converter from RGB to TPixel - JpegColorConverterBase converter = this.GetColorConverter(this.frame, this.jpegData); - this.colorConverter = converter; + MemoryAllocator allocator = this.configuration.MemoryAllocator; - // resulting image size - Size pixelSize = CalculateResultingImageSize(this.frame.PixelSize, this.targetSize, out int blockPixelSize); + // color converter from RGB to TPixel + JpegColorConverterBase converter = this.GetColorConverter(this.frame, this.jpegData); + this.colorConverter = converter; - // iteration data - int majorBlockWidth = this.frame.Components.Max((component) => component.SizeInBlocks.Width); - int majorVerticalSamplingFactor = this.frame.Components.Max((component) => component.SamplingFactors.Height); + // resulting image size + Size pixelSize = CalculateResultingImageSize(this.frame.PixelSize, this.targetSize, out int blockPixelSize); - this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; + // iteration data + int majorBlockWidth = this.frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = this.frame.Components.Max((component) => component.SamplingFactors.Height); - // pixel buffer for resulting image - this.pixelBuffer = allocator.Allocate2D( - pixelSize.Width, - pixelSize.Height, - this.configuration.PreferContiguousImageBuffers); - this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3); + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelSize; - // component processors from spectral to RGB - int bufferWidth = majorBlockWidth * blockPixelSize; - int batchSize = converter.ElementsPerBatch; - int batchRemainder = bufferWidth & (batchSize - 1); - var postProcessorBufferSize = new Size(bufferWidth + (batchSize - batchRemainder), this.pixelRowsPerStep); - this.componentProcessors = this.CreateComponentProcessors(this.frame, this.jpegData, blockPixelSize, postProcessorBufferSize); + // pixel buffer for resulting image + this.pixelBuffer = allocator.Allocate2D( + pixelSize.Width, + pixelSize.Height, + this.configuration.PreferContiguousImageBuffers); + this.paddedProxyPixelRow = allocator.Allocate(pixelSize.Width + 3); - // single 'stride' rgba32 buffer for conversion between spectral and TPixel - this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3); - } + // component processors from spectral to RGB + int bufferWidth = majorBlockWidth * blockPixelSize; + int batchSize = converter.ElementsPerBatch; + int batchRemainder = bufferWidth & (batchSize - 1); + var postProcessorBufferSize = new Size(bufferWidth + (batchSize - batchRemainder), this.pixelRowsPerStep); + this.componentProcessors = this.CreateComponentProcessors(this.frame, this.jpegData, blockPixelSize, postProcessorBufferSize); - /// - public override void ConvertStrideBaseline() - { - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates extra virtual call - this.ConvertStride(spectralStep: 0); + // single 'stride' rgba32 buffer for conversion between spectral and TPixel + this.rgbBuffer = allocator.Allocate(pixelSize.Width * 3); + } - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.ClearSpectralBuffers(); - } + /// + public override void ConvertStrideBaseline() + { + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates extra virtual call + this.ConvertStride(spectralStep: 0); + + foreach (ComponentProcessor cpp in this.componentProcessors) + { + cpp.ClearSpectralBuffers(); } + } - protected ComponentProcessor[] CreateComponentProcessors(JpegFrame frame, IRawJpegData jpegData, int blockPixelSize, Size processorBufferSize) + protected ComponentProcessor[] CreateComponentProcessors(JpegFrame frame, IRawJpegData jpegData, int blockPixelSize, Size processorBufferSize) + { + MemoryAllocator allocator = this.configuration.MemoryAllocator; + var componentProcessors = new ComponentProcessor[frame.Components.Length]; + for (int i = 0; i < componentProcessors.Length; i++) { - MemoryAllocator allocator = this.configuration.MemoryAllocator; - var componentProcessors = new ComponentProcessor[frame.Components.Length]; - for (int i = 0; i < componentProcessors.Length; i++) + componentProcessors[i] = blockPixelSize switch { - componentProcessors[i] = blockPixelSize switch - { - 4 => new DownScalingComponentProcessor2(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - 2 => new DownScalingComponentProcessor4(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - 1 => new DownScalingComponentProcessor8(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - _ => new DirectComponentProcessor(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), - }; - } - - return componentProcessors; + 4 => new DownScalingComponentProcessor2(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), + 2 => new DownScalingComponentProcessor4(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), + 1 => new DownScalingComponentProcessor8(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), + _ => new DirectComponentProcessor(allocator, frame, jpegData, processorBufferSize, frame.Components[i]), + }; } - /// - public void Dispose() + return componentProcessors; + } + + /// + public void Dispose() + { + if (this.componentProcessors != null) { - if (this.componentProcessors != null) + foreach (ComponentProcessor cpp in this.componentProcessors) { - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } + cpp.Dispose(); } - - this.rgbBuffer?.Dispose(); - this.paddedProxyPixelRow?.Dispose(); - this.pixelBuffer?.Dispose(); } + + this.rgbBuffer?.Dispose(); + this.paddedProxyPixelRow?.Dispose(); + this.pixelBuffer?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs index c489520abf..3049ab2c40 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/Component.cs @@ -1,116 +1,114 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +/// Represents a single frame component. +/// +internal class Component : IDisposable { - /// - /// Represents a single frame component. - /// - internal class Component : IDisposable - { - private readonly MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; - public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) - { - this.memoryAllocator = memoryAllocator; + public Component(MemoryAllocator memoryAllocator, int horizontalFactor, int verticalFactor, int quantizationTableIndex) + { + this.memoryAllocator = memoryAllocator; - this.HorizontalSamplingFactor = horizontalFactor; - this.VerticalSamplingFactor = verticalFactor; - this.SamplingFactors = new Size(horizontalFactor, verticalFactor); + this.HorizontalSamplingFactor = horizontalFactor; + this.VerticalSamplingFactor = verticalFactor; + this.SamplingFactors = new Size(horizontalFactor, verticalFactor); - this.QuantizationTableIndex = quantizationTableIndex; - } + this.QuantizationTableIndex = quantizationTableIndex; + } - /// - /// Gets or sets DC coefficient predictor. - /// - public int DcPredictor { get; set; } + /// + /// Gets or sets DC coefficient predictor. + /// + public int DcPredictor { get; set; } - /// - /// Gets the horizontal sampling factor. - /// - public int HorizontalSamplingFactor { get; } + /// + /// Gets the horizontal sampling factor. + /// + public int HorizontalSamplingFactor { get; } - /// - /// Gets the vertical sampling factor. - /// - public int VerticalSamplingFactor { get; } + /// + /// Gets the vertical sampling factor. + /// + public int VerticalSamplingFactor { get; } - public Buffer2D SpectralBlocks { get; private set; } + public Buffer2D SpectralBlocks { get; private set; } - public Size SubSamplingDivisors { get; private set; } + public Size SubSamplingDivisors { get; private set; } - public int QuantizationTableIndex { get; } + public int QuantizationTableIndex { get; } - public Size SizeInBlocks { get; private set; } + public Size SizeInBlocks { get; private set; } - public Size SamplingFactors { get; set; } + public Size SamplingFactors { get; set; } - /// - /// Gets the number of blocks per line. - /// - public int WidthInBlocks { get; private set; } + /// + /// Gets the number of blocks per line. + /// + public int WidthInBlocks { get; private set; } - /// - /// Gets the number of blocks per column. - /// - public int HeightInBlocks { get; private set; } + /// + /// Gets the number of blocks per column. + /// + public int HeightInBlocks { get; private set; } - /// - /// Gets or sets the index for the DC Huffman table. - /// - public int DcTableId { get; set; } + /// + /// Gets or sets the index for the DC Huffman table. + /// + public int DcTableId { get; set; } - /// - /// Gets or sets the index for the AC Huffman table. - /// - public int AcTableId { get; set; } + /// + /// Gets or sets the index for the AC Huffman table. + /// + public int AcTableId { get; set; } - /// - public void Dispose() - { - this.SpectralBlocks?.Dispose(); - this.SpectralBlocks = null; - } + /// + public void Dispose() + { + this.SpectralBlocks?.Dispose(); + this.SpectralBlocks = null; + } - /// - /// Initializes component for future buffers initialization. - /// - /// asdfasdf. - /// Maximal horizontal subsampling factor among all the components. - /// Maximal vertical subsampling factor among all the components. - public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) - { - uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8; - uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8; + /// + /// Initializes component for future buffers initialization. + /// + /// asdfasdf. + /// Maximal horizontal subsampling factor among all the components. + /// Maximal vertical subsampling factor among all the components. + public void Init(JpegFrame frame, int maxSubFactorH, int maxSubFactorV) + { + uint widthInBlocks = ((uint)frame.PixelWidth + 7) / 8; + uint heightInBlocks = ((uint)frame.PixelHeight + 7) / 8; - this.WidthInBlocks = (int)MathF.Ceiling( - (float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH); + this.WidthInBlocks = (int)MathF.Ceiling( + (float)widthInBlocks * this.HorizontalSamplingFactor / maxSubFactorH); - this.HeightInBlocks = (int)MathF.Ceiling( - (float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV); + this.HeightInBlocks = (int)MathF.Ceiling( + (float)heightInBlocks * this.VerticalSamplingFactor / maxSubFactorV); - int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; - int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; - this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); + int blocksPerLineForMcu = frame.McusPerLine * this.HorizontalSamplingFactor; + int blocksPerColumnForMcu = frame.McusPerColumn * this.VerticalSamplingFactor; + this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); - this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); + this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors); - if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) - { - JpegThrowHelper.ThrowBadSampling(); - } + if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) + { + JpegThrowHelper.ThrowBadSampling(); } + } - public void AllocateSpectral(bool fullScan) - { - int spectralAllocWidth = this.SizeInBlocks.Width; - int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; + public void AllocateSpectral(bool fullScan) + { + int spectralAllocWidth = this.SizeInBlocks.Width; + int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor; - this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight); - } + this.SpectralBlocks = this.memoryAllocator.Allocate2D(spectralAllocWidth, spectralAllocHeight); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs index f13eedb813..2bc1405509 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/ComponentProcessor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,221 +8,220 @@ using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +internal class ComponentProcessor : IDisposable { - internal class ComponentProcessor : IDisposable + private readonly Size blockAreaSize; + + private readonly Component component; + + private Block8x8F quantTable; + + public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable) { - private readonly Size blockAreaSize; + this.component = component; + this.quantTable = quantTable; + + this.component = component; + this.blockAreaSize = component.SubSamplingDivisors * 8; + + // alignment of 8 so each block stride can be sampled from a single 'ref pointer' + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( + postProcessorBufferSize.Width, + postProcessorBufferSize.Height, + 8, + AllocationOptions.Clean); + } + + /// + /// Gets the temporary working buffer of color values. + /// + public Buffer2D ColorBuffer { get; } - private readonly Component component; + public void CopyColorBufferToBlocks(int spectralStep) + { + Buffer2D spectralBuffer = this.component.SpectralBlocks; + int destAreaStride = this.ColorBuffer.Width; + int yBlockStart = spectralStep * this.component.SamplingFactors.Height; - private Block8x8F quantTable; + Block8x8F workspaceBlock = default; - public ComponentProcessor(MemoryAllocator memoryAllocator, Component component, Size postProcessorBufferSize, Block8x8F quantTable) + // handle subsampling + Size subsamplingFactors = this.component.SubSamplingDivisors; + if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1) { - this.component = component; - this.quantTable = quantTable; - - this.component = component; - this.blockAreaSize = component.SubSamplingDivisors * 8; - - // alignment of 8 so each block stride can be sampled from a single 'ref pointer' - this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( - postProcessorBufferSize.Width, - postProcessorBufferSize.Height, - 8, - AllocationOptions.Clean); + this.PackColorBuffer(); } - /// - /// Gets the temporary working buffer of color values. - /// - public Buffer2D ColorBuffer { get; } + int blocksRowsPerStep = this.component.SamplingFactors.Height; - public void CopyColorBufferToBlocks(int spectralStep) + for (int y = 0; y < blocksRowsPerStep; y++) { - Buffer2D spectralBuffer = this.component.SpectralBlocks; - int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.component.SamplingFactors.Height; + int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) + { + // load 8x8 block from 8 pixel strides + int xColorBufferStart = xBlock * 8; + workspaceBlock.ScaledCopyFrom( + ref colorBufferRow[xColorBufferStart], + destAreaStride); - Block8x8F workspaceBlock = default; + // level shift via -128f + workspaceBlock.AddInPlace(-128f); - // handle subsampling - Size subsamplingFactors = this.component.SubSamplingDivisors; - if (subsamplingFactors.Width != 1 || subsamplingFactors.Height != 1) - { - this.PackColorBuffer(); + // FDCT + FloatingPointDCT.TransformFDCT(ref workspaceBlock); + + // Quantize and save to spectral blocks + Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); } + } + } - int blocksRowsPerStep = this.component.SamplingFactors.Height; + public Span GetColorBufferRowSpan(int row) + => this.ColorBuffer.DangerousGetRowSpan(row); - for (int y = 0; y < blocksRowsPerStep; y++) - { - int yBuffer = y * this.blockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); - Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlockStart + y); - for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) - { - // load 8x8 block from 8 pixel strides - int xColorBufferStart = xBlock * 8; - workspaceBlock.ScaledCopyFrom( - ref colorBufferRow[xColorBufferStart], - destAreaStride); + public void Dispose() + => this.ColorBuffer.Dispose(); - // level shift via -128f - workspaceBlock.AddInPlace(-128f); + private void PackColorBuffer() + { + Size factors = this.component.SubSamplingDivisors; - // FDCT - FloatingPointDCT.TransformFDCT(ref workspaceBlock); + int packedWidth = this.ColorBuffer.Width / factors.Width; - // Quantize and save to spectral blocks - Block8x8F.Quantize(ref workspaceBlock, ref blockRow[xBlock], ref this.quantTable); - } - } - } + float averageMultiplier = 1f / (factors.Width * factors.Height); + for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) + { + Span sourceRow = this.ColorBuffer.DangerousGetRowSpan(i); - public Span GetColorBufferRowSpan(int row) - => this.ColorBuffer.DangerousGetRowSpan(row); + // vertical sum + for (int j = 1; j < factors.Height; j++) + { + SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); + } - public void Dispose() - => this.ColorBuffer.Dispose(); + // horizontal sum + SumHorizontal(sourceRow, factors.Width); - private void PackColorBuffer() - { - Size factors = this.component.SubSamplingDivisors; + // calculate average + MultiplyToAverage(sourceRow, averageMultiplier); - int packedWidth = this.ColorBuffer.Width / factors.Width; + // copy to the first 8 slots + sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height)); + } - float averageMultiplier = 1f / (factors.Width * factors.Height); - for (int i = 0; i < this.ColorBuffer.Height; i += factors.Height) + static void SumVertical(Span target, Span source) + { + if (Avx.IsSupported) { - Span sourceRow = this.ColorBuffer.DangerousGetRowSpan(i); + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - // vertical sum - for (int j = 1; j < factors.Height; j++) + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = source.Length / Vector256.Count; + for (nint i = 0; i < count; i++) { - SumVertical(sourceRow, this.ColorBuffer.DangerousGetRowSpan(i + j)); + Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); } - - // horizontal sum - SumHorizontal(sourceRow, factors.Width); - - // calculate average - MultiplyToAverage(sourceRow, averageMultiplier); - - // copy to the first 8 slots - sourceRow.Slice(0, packedWidth).CopyTo(this.ColorBuffer.DangerousGetRowSpan(i / factors.Height)); } - - static void SumVertical(Span target, Span source) + else { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector256 sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - nint count = source.Length / Vector256.Count; - for (nint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Add(Unsafe.Add(ref targetVectorRef, i), Unsafe.Add(ref sourceVectorRef, i)); - } - } - else + nint count = source.Length / Vector.Count; + for (nint i = 0; i < count; i++) { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector sourceVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - - nint count = source.Length / Vector.Count; - for (nint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); - } + Unsafe.Add(ref targetVectorRef, i) += Unsafe.Add(ref sourceVectorRef, i); + } - ref float targetRef = ref MemoryMarshal.GetReference(target); - ref float sourceRef = ref MemoryMarshal.GetReference(source); - for (nint i = count * Vector.Count; i < source.Length; i++) - { - Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); - } + ref float targetRef = ref MemoryMarshal.GetReference(target); + ref float sourceRef = ref MemoryMarshal.GetReference(source); + for (nint i = count * Vector.Count; i < source.Length; i++) + { + Unsafe.Add(ref targetRef, i) += Unsafe.Add(ref sourceRef, i); } } + } - static void SumHorizontal(Span target, int factor) + static void SumHorizontal(Span target, int factor) + { + Span source = target; + if (Avx2.IsSupported) { - Span source = target; - if (Avx2.IsSupported) - { - ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 targetRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - // Ideally we need to use log2: Numerics.Log2((uint)factor) - // but division by 2 works just fine in this case - int haddIterationsCount = (int)((uint)factor / 2); + // Ideally we need to use log2: Numerics.Log2((uint)factor) + // but division by 2 works just fine in this case + int haddIterationsCount = (int)((uint)factor / 2); - // Transform spans so that it only contains 'remainder' - // values for the scalar fallback code - int scalarRemainder = target.Length % (Vector.Count * factor); - int touchedCount = target.Length - scalarRemainder; - source = source.Slice(touchedCount); - target = target.Slice(touchedCount / factor); + // Transform spans so that it only contains 'remainder' + // values for the scalar fallback code + int scalarRemainder = target.Length % (Vector.Count * factor); + int touchedCount = target.Length - scalarRemainder; + source = source.Slice(touchedCount); + target = target.Slice(touchedCount / factor); - uint length = (uint)touchedCount / (uint)Vector256.Count; + uint length = (uint)touchedCount / (uint)Vector256.Count; + + for (int i = 0; i < haddIterationsCount; i++) + { + length /= 2; - for (int i = 0; i < haddIterationsCount; i++) + for (nuint j = 0; j < length; j++) { - length /= 2; - - for (nuint j = 0; j < length; j++) - { - nuint indexLeft = j * 2; - nuint indexRight = indexLeft + 1; - Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); - Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); - } + nuint indexLeft = j * 2; + nuint indexRight = indexLeft + 1; + Vector256 sum = Avx.HorizontalAdd(Unsafe.Add(ref targetRef, indexLeft), Unsafe.Add(ref targetRef, indexRight)); + Unsafe.Add(ref targetRef, j) = Avx2.Permute4x64(sum.AsDouble(), 0b11_01_10_00).AsSingle(); } } + } - // scalar remainder - for (int i = 0; i < source.Length / factor; i++) + // scalar remainder + for (int i = 0; i < source.Length / factor; i++) + { + target[i] = source[i * factor]; + for (int j = 1; j < factor; j++) { - target[i] = source[i * factor]; - for (int j = 1; j < factor; j++) - { - target[i] += source[(i * factor) + j]; - } + target[i] += source[(i * factor) + j]; } } + } - static void MultiplyToAverage(Span target, float multiplier) + static void MultiplyToAverage(Span target, float multiplier) + { + if (Avx.IsSupported) { - if (Avx.IsSupported) - { - ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + ref Vector256 targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed - nint count = target.Length / Vector256.Count; - var multiplierVector = Vector256.Create(multiplier); - for (nint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); - } - } - else + // Spans are guaranteed to be multiple of 8 so no extra 'remainder' steps are needed + nint count = target.Length / Vector256.Count; + var multiplierVector = Vector256.Create(multiplier); + for (nint i = 0; i < count; i++) { - ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); + Unsafe.Add(ref targetVectorRef, i) = Avx.Multiply(Unsafe.Add(ref targetVectorRef, i), multiplierVector); + } + } + else + { + ref Vector targetVectorRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - nint count = target.Length / Vector.Count; - var multiplierVector = new Vector(multiplier); - for (nint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; - } + nint count = target.Length / Vector.Count; + var multiplierVector = new Vector(multiplier); + for (nint i = 0; i < count; i++) + { + Unsafe.Add(ref targetVectorRef, i) *= multiplierVector; + } - ref float targetRef = ref MemoryMarshal.GetReference(target); - for (nint i = count * Vector.Count; i < target.Length; i++) - { - Unsafe.Add(ref targetRef, i) *= multiplier; - } + ref float targetRef = ref MemoryMarshal.GetReference(target); + for (nint i = count * Vector.Count; i < target.Length; i++) + { + Unsafe.Add(ref targetRef, i) *= multiplier; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs index 519b8c0b8b..c2086ce7dd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegComponentConfig.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +internal class JpegComponentConfig { - internal class JpegComponentConfig + public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) { - public JpegComponentConfig(byte id, int hsf, int vsf, int quantIndex, int dcIndex, int acIndex) - { - this.Id = id; - this.HorizontalSampleFactor = hsf; - this.VerticalSampleFactor = vsf; - this.QuantizatioTableIndex = quantIndex; - this.DcTableSelector = dcIndex; - this.AcTableSelector = acIndex; - } + this.Id = id; + this.HorizontalSampleFactor = hsf; + this.VerticalSampleFactor = vsf; + this.QuantizatioTableIndex = quantIndex; + this.DcTableSelector = dcIndex; + this.AcTableSelector = acIndex; + } - public byte Id { get; } + public byte Id { get; } - public int HorizontalSampleFactor { get; } + public int HorizontalSampleFactor { get; } - public int VerticalSampleFactor { get; } + public int VerticalSampleFactor { get; } - public int QuantizatioTableIndex { get; } + public int QuantizatioTableIndex { get; } - public int DcTableSelector { get; } + public int DcTableSelector { get; } - public int AcTableSelector { get; } - } + public int AcTableSelector { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs index feb1ae8eef..5a59837e58 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegFrameConfig.cs @@ -1,44 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +internal class JpegFrameConfig { - internal class JpegFrameConfig + public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) { - public JpegFrameConfig(JpegColorSpace colorType, JpegEncodingColor encodingColor, JpegComponentConfig[] components, JpegHuffmanTableConfig[] huffmanTables, JpegQuantizationTableConfig[] quantTables) + this.ColorType = colorType; + this.EncodingColor = encodingColor; + this.Components = components; + this.HuffmanTables = huffmanTables; + this.QuantizationTables = quantTables; + + this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; + this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; + for (int i = 1; i < components.Length; i++) { - this.ColorType = colorType; - this.EncodingColor = encodingColor; - this.Components = components; - this.HuffmanTables = huffmanTables; - this.QuantizationTables = quantTables; - - this.MaxHorizontalSamplingFactor = components[0].HorizontalSampleFactor; - this.MaxVerticalSamplingFactor = components[0].VerticalSampleFactor; - for (int i = 1; i < components.Length; i++) - { - JpegComponentConfig component = components[i]; - this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); - this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); - } + JpegComponentConfig component = components[i]; + this.MaxHorizontalSamplingFactor = Math.Max(this.MaxHorizontalSamplingFactor, component.HorizontalSampleFactor); + this.MaxVerticalSamplingFactor = Math.Max(this.MaxVerticalSamplingFactor, component.VerticalSampleFactor); } + } - public JpegColorSpace ColorType { get; } + public JpegColorSpace ColorType { get; } - public JpegEncodingColor EncodingColor { get; } + public JpegEncodingColor EncodingColor { get; } - public JpegComponentConfig[] Components { get; } + public JpegComponentConfig[] Components { get; } - public JpegHuffmanTableConfig[] HuffmanTables { get; } + public JpegHuffmanTableConfig[] HuffmanTables { get; } - public JpegQuantizationTableConfig[] QuantizationTables { get; } + public JpegQuantizationTableConfig[] QuantizationTables { get; } - public int MaxHorizontalSamplingFactor { get; } + public int MaxHorizontalSamplingFactor { get; } - public int MaxVerticalSamplingFactor { get; } + public int MaxVerticalSamplingFactor { get; } - public byte? AdobeColorTransformMarkerFlag { get; set; } - } + public byte? AdobeColorTransformMarkerFlag { get; set; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs index cf7c152cb0..5b2d105a29 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegHuffmanTableConfig.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +internal class JpegHuffmanTableConfig { - internal class JpegHuffmanTableConfig + public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) { - public JpegHuffmanTableConfig(int @class, int destIndex, HuffmanSpec table) - { - this.Class = @class; - this.DestinationIndex = destIndex; - this.Table = table; - } + this.Class = @class; + this.DestinationIndex = destIndex; + this.Table = table; + } - public int Class { get; } + public int Class { get; } - public int DestinationIndex { get; } + public int DestinationIndex { get; } - public HuffmanSpec Table { get; } - } + public HuffmanSpec Table { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs index ada2c464a6..157f728e25 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/EncodingConfigs/JpegQuantizationTableConfig.cs @@ -1,20 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +internal class JpegQuantizationTableConfig { - internal class JpegQuantizationTableConfig + public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan quantizationTable) { - public JpegQuantizationTableConfig(int destIndex, ReadOnlySpan quantizationTable) - { - this.DestinationIndex = destIndex; - this.Table = Block8x8.Load(quantizationTable); - } + this.DestinationIndex = destIndex; + this.Table = Block8x8.Load(quantizationTable); + } - public int DestinationIndex { get; } + public int DestinationIndex { get; } - public Block8x8 Table { get; } - } + public Block8x8 Table { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs index bd4cc5545f..fbdf98915f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs @@ -1,69 +1,68 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +/// A compiled look-up table representation of a huffmanSpec. +/// The maximum codeword size is 16 bits. +/// +/// +/// +/// Each value maps to a int32 of which the 24 most significant bits hold the +/// codeword in bits and the 8 least significant bits hold the codeword size. +/// +/// +/// Code value occupies 24 most significant bits as integer value. +/// This value is shifted to the MSB position for performance reasons. +/// For example, decimal value 10 is stored like this: +/// +/// MSB LSB +/// 1010 0000 00000000 00000000 | 00000100 +/// +/// This was done to eliminate extra binary shifts in the encoder. +/// While code length is represented as 8 bit integer value +/// +/// +internal readonly struct HuffmanLut { /// - /// A compiled look-up table representation of a huffmanSpec. - /// The maximum codeword size is 16 bits. + /// Initializes a new instance of the struct. /// - /// - /// - /// Each value maps to a int32 of which the 24 most significant bits hold the - /// codeword in bits and the 8 least significant bits hold the codeword size. - /// - /// - /// Code value occupies 24 most significant bits as integer value. - /// This value is shifted to the MSB position for performance reasons. - /// For example, decimal value 10 is stored like this: - /// - /// MSB LSB - /// 1010 0000 00000000 00000000 | 00000100 - /// - /// This was done to eliminate extra binary shifts in the encoder. - /// While code length is represented as 8 bit integer value - /// - /// - internal readonly struct HuffmanLut + /// dasd + public HuffmanLut(HuffmanSpec spec) { - /// - /// Initializes a new instance of the struct. - /// - /// dasd - public HuffmanLut(HuffmanSpec spec) - { - int maxValue = 0; + int maxValue = 0; - foreach (byte v in spec.Values) + foreach (byte v in spec.Values) + { + if (v > maxValue) { - if (v > maxValue) - { - maxValue = v; - } + maxValue = v; } + } - this.Values = new int[maxValue + 1]; + this.Values = new int[maxValue + 1]; - int code = 0; - int k = 0; + int code = 0; + int k = 0; - for (int i = 0; i < spec.Count.Length; i++) + for (int i = 0; i < spec.Count.Length; i++) + { + int len = i + 1; + for (int j = 0; j < spec.Count[i]; j++) { - int len = i + 1; - for (int j = 0; j < spec.Count[i]; j++) - { - this.Values[spec.Values[k]] = len | (code << (32 - len)); - code++; - k++; - } - - code <<= 1; + this.Values[spec.Values[k]] = len | (code << (32 - len)); + code++; + k++; } - } - /// - /// Gets the collection of huffman values. - /// - public int[] Values { get; } + code <<= 1; + } } + + /// + /// Gets the collection of huffman values. + /// + public int[] Values { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index fe61df5e3a..8edbc3c407 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -1,587 +1,610 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +internal class HuffmanScanEncoder { - internal class HuffmanScanEncoder + /// + /// Maximum number of bytes encoded jpeg 8x8 block can occupy. + /// It's highly unlikely for block to occupy this much space - it's a theoretical limit. + /// + /// + /// Where 16 is maximum huffman code binary length according to itu + /// specs. 10 is maximum value binary length, value comes from discrete + /// cosine tranform with value range: [-1024..1023]. Block stores + /// 8x8 = 64 values thus multiplication by 64. Then divided by 8 to get + /// the number of bytes. This value is then multiplied by + /// for performance reasons. + /// + private const int MaxBytesPerBlock = (16 + 10) * 64 / 8 * MaxBytesPerBlockMultiplier; + + /// + /// Multiplier used within cache buffers size calculation. + /// + /// + /// + /// Theoretically, bytes buffer can fit + /// exactly one minimal coding unit. In reality, coding blocks occupy much + /// less space than the theoretical maximum - this can be exploited. + /// If temporal buffer size is multiplied by at least 2, second half of + /// the resulting buffer will be used as an overflow 'guard' if next + /// block would occupy maximum number of bytes. While first half may fit + /// many blocks before needing to flush. + /// + /// + /// This is subject to change. This can be equal to 1 but recomended + /// value is 2 or even greater - futher benchmarking needed. + /// + /// + private const int MaxBytesPerBlockMultiplier = 2; + + /// + /// size multiplier. + /// + /// + /// Jpeg specification requiers to insert 'stuff' bytes after each + /// 0xff byte value. Worst case scenarion is when all bytes are 0xff. + /// While it's highly unlikely (if not impossible) to get such + /// combination, it's theoretically possible so buffer size must be guarded. + /// + private const int OutputBufferLengthMultiplier = 2; + + /// + /// The DC Huffman tables. + /// + private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4]; + + /// + /// The AC Huffman tables. + /// + private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4]; + + /// + /// Emitted bits 'micro buffer' before being transferred to the . + /// + private uint accumulatedBits; + + /// + /// Buffer for temporal storage of huffman rle encoding bit data. + /// + /// + /// Encoding bits are assembled to 4 byte unsigned integers and then copied to this buffer. + /// This process does NOT include inserting stuff bytes. + /// + private readonly uint[] emitBuffer; + + /// + /// Buffer for temporal storage which is then written to the output stream. + /// + /// + /// Encoding bits from are copied to this byte buffer including stuff bytes. + /// + private readonly byte[] streamWriteBuffer; + + /// + /// Number of jagged bits stored in + /// + private int bitCount; + + private int emitWriteIndex; + + /// + /// The output stream. All attempted writes after the first error become no-ops. + /// + private readonly Stream target; + + /// + /// Initializes a new instance of the class. + /// + /// Amount of encoded 8x8 blocks per single jpeg macroblock. + /// Output stream for saving encoded data. + public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream) { - /// - /// Maximum number of bytes encoded jpeg 8x8 block can occupy. - /// It's highly unlikely for block to occupy this much space - it's a theoretical limit. - /// - /// - /// Where 16 is maximum huffman code binary length according to itu - /// specs. 10 is maximum value binary length, value comes from discrete - /// cosine tranform with value range: [-1024..1023]. Block stores - /// 8x8 = 64 values thus multiplication by 64. Then divided by 8 to get - /// the number of bytes. This value is then multiplied by - /// for performance reasons. - /// - private const int MaxBytesPerBlock = (16 + 10) * 64 / 8 * MaxBytesPerBlockMultiplier; - - /// - /// Multiplier used within cache buffers size calculation. - /// - /// - /// - /// Theoretically, bytes buffer can fit - /// exactly one minimal coding unit. In reality, coding blocks occupy much - /// less space than the theoretical maximum - this can be exploited. - /// If temporal buffer size is multiplied by at least 2, second half of - /// the resulting buffer will be used as an overflow 'guard' if next - /// block would occupy maximum number of bytes. While first half may fit - /// many blocks before needing to flush. - /// - /// - /// This is subject to change. This can be equal to 1 but recomended - /// value is 2 or even greater - futher benchmarking needed. - /// - /// - private const int MaxBytesPerBlockMultiplier = 2; - - /// - /// size multiplier. - /// - /// - /// Jpeg specification requiers to insert 'stuff' bytes after each - /// 0xff byte value. Worst case scenarion is when all bytes are 0xff. - /// While it's highly unlikely (if not impossible) to get such - /// combination, it's theoretically possible so buffer size must be guarded. - /// - private const int OutputBufferLengthMultiplier = 2; - - /// - /// The DC Huffman tables. - /// - private readonly HuffmanLut[] dcHuffmanTables = new HuffmanLut[4]; - - /// - /// The AC Huffman tables. - /// - private readonly HuffmanLut[] acHuffmanTables = new HuffmanLut[4]; - - /// - /// Emitted bits 'micro buffer' before being transferred to the . - /// - private uint accumulatedBits; - - /// - /// Buffer for temporal storage of huffman rle encoding bit data. - /// - /// - /// Encoding bits are assembled to 4 byte unsigned integers and then copied to this buffer. - /// This process does NOT include inserting stuff bytes. - /// - private readonly uint[] emitBuffer; - - /// - /// Buffer for temporal storage which is then written to the output stream. - /// - /// - /// Encoding bits from are copied to this byte buffer including stuff bytes. - /// - private readonly byte[] streamWriteBuffer; - - /// - /// Number of jagged bits stored in - /// - private int bitCount; - - private int emitWriteIndex; - - /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private readonly Stream target; - - /// - /// Initializes a new instance of the class. - /// - /// Amount of encoded 8x8 blocks per single jpeg macroblock. - /// Output stream for saving encoded data. - public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream) - { - int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit; - this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)]; - this.emitWriteIndex = this.emitBuffer.Length; + int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit; + this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)]; + this.emitWriteIndex = this.emitBuffer.Length; - this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; + this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier]; - this.target = outputStream; - } + this.target = outputStream; + } - /// - /// Gets a value indicating whether is full - /// and must be flushed using - /// before encoding next 8x8 coding block. - /// - private bool IsStreamFlushNeeded - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; - } + /// + /// Gets a value indicating whether is full + /// and must be flushed using + /// before encoding next 8x8 coding block. + /// + private bool IsStreamFlushNeeded + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2; + } - public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) - { - HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); - } + public void BuildHuffmanTable(JpegHuffmanTableConfig tableConfig) + { + HuffmanLut[] tables = tableConfig.Class == 0 ? this.dcHuffmanTables : this.acHuffmanTables; + tables[tableConfig.DestinationIndex] = new HuffmanLut(tableConfig.Table); + } - /// - /// Encodes scan in baseline interleaved mode. - /// - /// Output color space. - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + /// Encodes scan in baseline interleaved mode. + /// + /// Output color space. + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + public void EncodeScanBaselineInterleaved(JpegEncodingColor color, JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + switch (color) { - switch (color) - { - case JpegEncodingColor.YCbCrRatio444: - case JpegEncodingColor.Rgb: - this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken); - break; - default: - this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); - break; - } + case JpegEncodingColor.YCbCrRatio444: + case JpegEncodingColor.Rgb: + this.EncodeThreeComponentBaselineInterleavedScanNoSubsampling(frame, converter, cancellationToken); + break; + default: + this.EncodeScanBaselineInterleaved(frame, converter, cancellationToken); + break; } + } - /// - /// Encodes grayscale scan in baseline interleaved mode. - /// - /// Component with grayscale data. - /// Converter from color to spectral. - /// The token to request cancellation. - public void EncodeScanBaselineSingleComponent(Component component, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; + /// + /// Encodes grayscale scan in baseline interleaved mode. + /// + /// Component with grayscale data. + /// Converter from color to spectral. + /// The token to request cancellation. + public void EncodeScanBaselineSingleComponent(Component component, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - for (int i = 0; i < h; i++) - { - cancellationToken.ThrowIfCancellationRequested(); + for (int i = 0; i < h; i++) + { + cancellationToken.ThrowIfCancellationRequested(); - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); - // Encode spectral to binary - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: 0); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (nint k = 0; k < w; k++) - { - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); + for (nint k = 0; k < w; k++) + { + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); } } - - this.FlushRemainingBytes(); } - /// - /// Encodes scan with a single component in baseline non-interleaved mode. - /// - /// Component with grayscale data. - /// The token to request cancellation. - public void EncodeScanBaseline(Component component, CancellationToken cancellationToken) + this.FlushRemainingBytes(); + } + + /// + /// Encodes scan with a single component in baseline non-interleaved mode. + /// + /// Component with grayscale data. + /// The token to request cancellation. + public void EncodeScanBaseline(Component component, CancellationToken cancellationToken) + { + int h = component.HeightInBlocks; + int w = component.WidthInBlocks; + + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + + for (int i = 0; i < h; i++) { - int h = component.HeightInBlocks; - int w = component.WidthInBlocks; + cancellationToken.ThrowIfCancellationRequested(); - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + // Encode spectral to binary + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - for (int i = 0; i < h; i++) + for (nint k = 0; k < w; k++) { - cancellationToken.ThrowIfCancellationRequested(); - - // Encode spectral to binary - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y: i); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, k), + ref dcHuffmanTable, + ref acHuffmanTable); - for (nint k = 0; k < w; k++) + if (this.IsStreamFlushNeeded) { - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, k), - ref dcHuffmanTable, - ref acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } + this.FlushToStream(); } } - - this.FlushRemainingBytes(); } - /// - /// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors. - /// - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - nint mcu = 0; - nint mcusPerColumn = frame.McusPerColumn; - nint mcusPerLine = frame.McusPerLine; + this.FlushRemainingBytes(); + } - for (int j = 0; j < mcusPerColumn; j++) - { - cancellationToken.ThrowIfCancellationRequested(); + /// + /// Encodes scan in baseline interleaved mode for any amount of component with arbitrary sampling factors. + /// + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeScanBaselineInterleaved(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + nint mcu = 0; + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); + for (int j = 0; j < mcusPerColumn; j++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); - // Encode spectral to binary - for (nint i = 0; i < mcusPerLine; i++) + // Encode spectral to binary + for (nint i = 0; i < mcusPerLine; i++) + { + // Scan an interleaved mcu... process components in order + nint mcuCol = mcu % mcusPerLine; + for (nint k = 0; k < frame.Components.Length; k++) { - // Scan an interleaved mcu... process components in order - nint mcuCol = mcu % mcusPerLine; - for (nint k = 0; k < frame.Components.Length; k++) - { - Component component = frame.Components[k]; + Component component = frame.Components[k]; - ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; - ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; + ref HuffmanLut dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId]; + ref HuffmanLut acHuffmanTable = ref this.acHuffmanTables[component.AcTableId]; - nint h = component.HorizontalSamplingFactor; - int v = component.VerticalSamplingFactor; + nint h = component.HorizontalSamplingFactor; + int v = component.VerticalSamplingFactor; - nint blockColBase = mcuCol * h; + nint blockColBase = mcuCol * h; - // Scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (int y = 0; y < v; y++) + // Scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (int y = 0; y < v; y++) + { + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); + ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); + + for (nint x = 0; x < h; x++) { - Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); - ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); - - for (nint x = 0; x < h; x++) - { - nint blockCol = blockColBase + x; - - this.WriteBlock( - component, - ref Unsafe.Add(ref blockRef, blockCol), - ref dcHuffmanTable, - ref acHuffmanTable); - } + nint blockCol = blockColBase + x; + + this.WriteBlock( + component, + ref Unsafe.Add(ref blockRef, blockCol), + ref dcHuffmanTable, + ref acHuffmanTable); } } + } - // After all interleaved components, that's an interleaved MCU - mcu++; - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } + // After all interleaved components, that's an interleaved MCU + mcu++; + if (this.IsStreamFlushNeeded) + { + this.FlushToStream(); } } - - this.FlushRemainingBytes(); } - /// - /// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling. - /// - /// Frame to encode. - /// Converter from color to spectral. - /// The token to request cancellation. - private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - nint mcusPerColumn = frame.McusPerColumn; - nint mcusPerLine = frame.McusPerLine; + this.FlushRemainingBytes(); + } - Component c2 = frame.Components[2]; - Component c1 = frame.Components[1]; - Component c0 = frame.Components[0]; + /// + /// Encodes scan in baseline interleaved mode with exactly 3 components with no subsampling. + /// + /// Frame to encode. + /// Converter from color to spectral. + /// The token to request cancellation. + private void EncodeThreeComponentBaselineInterleavedScanNoSubsampling(JpegFrame frame, SpectralConverter converter, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + nint mcusPerColumn = frame.McusPerColumn; + nint mcusPerLine = frame.McusPerLine; - ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; - ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; - ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId]; - ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId]; - ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId]; - ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId]; + Component c2 = frame.Components[2]; + Component c1 = frame.Components[1]; + Component c0 = frame.Components[0]; - ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0)); - ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); - ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref HuffmanLut c0dcHuffmanTable = ref this.dcHuffmanTables[c0.DcTableId]; + ref HuffmanLut c0acHuffmanTable = ref this.acHuffmanTables[c0.AcTableId]; + ref HuffmanLut c1dcHuffmanTable = ref this.dcHuffmanTables[c1.DcTableId]; + ref HuffmanLut c1acHuffmanTable = ref this.acHuffmanTables[c1.AcTableId]; + ref HuffmanLut c2dcHuffmanTable = ref this.dcHuffmanTables[c2.DcTableId]; + ref HuffmanLut c2acHuffmanTable = ref this.acHuffmanTables[c2.AcTableId]; - for (nint j = 0; j < mcusPerColumn; j++) - { - cancellationToken.ThrowIfCancellationRequested(); + ref Block8x8 c0BlockRef = ref MemoryMarshal.GetReference(c0.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c1BlockRef = ref MemoryMarshal.GetReference(c1.SpectralBlocks.DangerousGetRowSpan(y: 0)); + ref Block8x8 c2BlockRef = ref MemoryMarshal.GetReference(c2.SpectralBlocks.DangerousGetRowSpan(y: 0)); - // Convert from pixels to spectral via given converter - converter.ConvertStrideBaseline(); + for (nint j = 0; j < mcusPerColumn; j++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Convert from pixels to spectral via given converter + converter.ConvertStrideBaseline(); - // Encode spectral to binary - for (nint i = 0; i < mcusPerLine; i++) + // Encode spectral to binary + for (nint i = 0; i < mcusPerLine; i++) + { + this.WriteBlock( + c0, + ref Unsafe.Add(ref c0BlockRef, i), + ref c0dcHuffmanTable, + ref c0acHuffmanTable); + + this.WriteBlock( + c1, + ref Unsafe.Add(ref c1BlockRef, i), + ref c1dcHuffmanTable, + ref c1acHuffmanTable); + + this.WriteBlock( + c2, + ref Unsafe.Add(ref c2BlockRef, i), + ref c2dcHuffmanTable, + ref c2acHuffmanTable); + + if (this.IsStreamFlushNeeded) { - this.WriteBlock( - c0, - ref Unsafe.Add(ref c0BlockRef, i), - ref c0dcHuffmanTable, - ref c0acHuffmanTable); - - this.WriteBlock( - c1, - ref Unsafe.Add(ref c1BlockRef, i), - ref c1dcHuffmanTable, - ref c1acHuffmanTable); - - this.WriteBlock( - c2, - ref Unsafe.Add(ref c2BlockRef, i), - ref c2dcHuffmanTable, - ref c2acHuffmanTable); - - if (this.IsStreamFlushNeeded) - { - this.FlushToStream(); - } + this.FlushToStream(); } } - - this.FlushRemainingBytes(); } - private void WriteBlock( - Component component, - ref Block8x8 block, - ref HuffmanLut dcTable, - ref HuffmanLut acTable) - { - // Emit the DC delta. - int dc = block[0]; - this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); - component.DcPredictor = dc; + this.FlushRemainingBytes(); + } - // Emit the AC components. - int[] acHuffTable = acTable.Values; + private void WriteBlock( + Component component, + ref Block8x8 block, + ref HuffmanLut dcTable, + ref HuffmanLut acTable) + { + // Emit the DC delta. + int dc = block[0]; + this.EmitHuffRLE(dcTable.Values, 0, dc - component.DcPredictor); + component.DcPredictor = dc; - nint lastValuableIndex = block.GetLastNonZeroIndex(); + // Emit the AC components. + int[] acHuffTable = acTable.Values; - int runLength = 0; - ref short blockRef = ref Unsafe.As(ref block); - for (nint zig = 1; zig <= lastValuableIndex; zig++) - { - const int zeroRun1 = 1 << 4; - const int zeroRun16 = 16 << 4; + nint lastValuableIndex = block.GetLastNonZeroIndex(); - int ac = Unsafe.Add(ref blockRef, zig); - if (ac == 0) - { - runLength += zeroRun1; - } - else - { - while (runLength >= zeroRun16) - { - this.EmitHuff(acHuffTable, 0xf0); - runLength -= zeroRun16; - } + int runLength = 0; + ref short blockRef = ref Unsafe.As(ref block); + for (nint zig = 1; zig <= lastValuableIndex; zig++) + { + const int zeroRun1 = 1 << 4; + const int zeroRun16 = 16 << 4; - this.EmitHuffRLE(acHuffTable, runLength, ac); - runLength = 0; - } + int ac = Unsafe.Add(ref blockRef, zig); + if (ac == 0) + { + runLength += zeroRun1; } - - // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over - // this can be done for any number of trailing zeros, even when all 63 ac values are zero - // (Block8x8F.Size - 1) == 63 - last index of the mcu elements - if (lastValuableIndex != Block8x8F.Size - 1) + else { - this.EmitHuff(acHuffTable, 0x00); + while (runLength >= zeroRun16) + { + this.EmitHuff(acHuffTable, 0xf0); + runLength -= zeroRun16; + } + + this.EmitHuffRLE(acHuffTable, runLength, ac); + runLength = 0; } } - /// - /// Emits the most significant count of bits to the buffer. - /// - /// - /// - /// Supports up to 32 count of bits but, generally speaking, jpeg - /// standard assures that there won't be more than 16 bits per single - /// value. - /// - /// - /// Emitting algorithm uses 3 intermediate buffers for caching before - /// writing to the stream: - /// - /// - /// uint32 - /// - /// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits - /// are assembled to whole bytes via this intermediate buffer. - /// - /// - /// - /// uint32[] - /// - /// Assembled bytes from uint32 buffer are saved into this buffer. - /// uint32 buffer values are saved using indices from the last to the first. - /// As bytes are saved to the memory as 4-byte packages endianness matters: - /// Jpeg stream is big-endian, indexing buffer bytes from the last index to the - /// first eliminates all operations to extract separate bytes. This only works for - /// little-endian machines (there are no known examples of big-endian users atm). - /// For big-endians this approach is slower due to the separate byte extraction. - /// - /// - /// - /// byte[] - /// - /// Byte buffer used only during method. - /// - /// - /// - /// - /// - /// Bits to emit, must be shifted to the left. - /// Bits count stored in the bits parameter. - [MethodImpl(InliningOptions.ShortMethod)] - private void Emit(uint bits, int count) + // if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over + // this can be done for any number of trailing zeros, even when all 63 ac values are zero + // (Block8x8F.Size - 1) == 63 - last index of the mcu elements + if (lastValuableIndex != Block8x8F.Size - 1) { - this.accumulatedBits |= bits >> this.bitCount; + this.EmitHuff(acHuffTable, 0x00); + } + } - count += this.bitCount; + /// + /// Emits the most significant count of bits to the buffer. + /// + /// + /// + /// Supports up to 32 count of bits but, generally speaking, jpeg + /// standard assures that there won't be more than 16 bits per single + /// value. + /// + /// + /// Emitting algorithm uses 3 intermediate buffers for caching before + /// writing to the stream: + /// + /// + /// uint32 + /// + /// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits + /// are assembled to whole bytes via this intermediate buffer. + /// + /// + /// + /// uint32[] + /// + /// Assembled bytes from uint32 buffer are saved into this buffer. + /// uint32 buffer values are saved using indices from the last to the first. + /// As bytes are saved to the memory as 4-byte packages endianness matters: + /// Jpeg stream is big-endian, indexing buffer bytes from the last index to the + /// first eliminates all operations to extract separate bytes. This only works for + /// little-endian machines (there are no known examples of big-endian users atm). + /// For big-endians this approach is slower due to the separate byte extraction. + /// + /// + /// + /// byte[] + /// + /// Byte buffer used only during method. + /// + /// + /// + /// + /// + /// Bits to emit, must be shifted to the left. + /// Bits count stored in the bits parameter. + [MethodImpl(InliningOptions.ShortMethod)] + private void Emit(uint bits, int count) + { + this.accumulatedBits |= bits >> this.bitCount; - if (count >= 32) - { - this.emitBuffer[--this.emitWriteIndex] = this.accumulatedBits; - this.accumulatedBits = bits << (32 - this.bitCount); + count += this.bitCount; - count -= 32; - } + if (count >= 32) + { + this.emitBuffer[--this.emitWriteIndex] = this.accumulatedBits; + this.accumulatedBits = bits << (32 - this.bitCount); - this.bitCount = count; + count -= 32; } - /// - /// Emits the given value with the given Huffman table. - /// - /// Huffman table. - /// Value to encode. - [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuff(int[] table, int value) + this.bitCount = count; + } + + /// + /// Emits the given value with the given Huffman table. + /// + /// Huffman table. + /// Value to encode. + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitHuff(int[] table, int value) + { + int x = table[value]; + this.Emit((uint)x & 0xffff_ff00u, x & 0xff); + } + + /// + /// Emits given value via huffman rle encoding. + /// + /// Huffman table. + /// The number of preceding zeroes, preshifted by 4 to the left. + /// Value to encode. + [MethodImpl(InliningOptions.ShortMethod)] + private void EmitHuffRLE(int[] table, int runLength, int value) + { + DebugGuard.IsTrue((runLength & 0xf) == 0, $"{nameof(runLength)} parameter must be shifted to the left by 4 bits"); + + int a = value; + int b = value; + if (a < 0) { - int x = table[value]; - this.Emit((uint)x & 0xffff_ff00u, x & 0xff); + a = -value; + b = value - 1; } - /// - /// Emits given value via huffman rle encoding. - /// - /// Huffman table. - /// The number of preceding zeroes, preshifted by 4 to the left. - /// Value to encode. - [MethodImpl(InliningOptions.ShortMethod)] - private void EmitHuffRLE(int[] table, int runLength, int value) - { - DebugGuard.IsTrue((runLength & 0xf) == 0, $"{nameof(runLength)} parameter must be shifted to the left by 4 bits"); + int valueLen = GetHuffmanEncodingLength((uint)a); - int a = value; - int b = value; - if (a < 0) - { - a = -value; - b = value - 1; - } + // Huffman prefix code + int huffPackage = table[runLength | valueLen]; + int prefixLen = huffPackage & 0xff; + uint prefix = (uint)huffPackage & 0xffff_0000u; - int valueLen = GetHuffmanEncodingLength((uint)a); + // Actual encoded value + uint encodedValue = (uint)b << (32 - valueLen); - // Huffman prefix code - int huffPackage = table[runLength | valueLen]; - int prefixLen = huffPackage & 0xff; - uint prefix = (uint)huffPackage & 0xffff_0000u; + // Doing two binary shifts to get rid of leading 1's in negative value case + this.Emit(prefix | (encodedValue >> prefixLen), prefixLen + valueLen); + } - // Actual encoded value - uint encodedValue = (uint)b << (32 - valueLen); + /// + /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. + /// + /// + /// This is an internal operation supposed to be used only in class for jpeg encoding. + /// + /// The value. + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetHuffmanEncodingLength(uint value) + { + DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); - // Doing two binary shifts to get rid of leading 1's in negative value case - this.Emit(prefix | (encodedValue >> prefixLen), prefixLen + valueLen); - } + // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation + // But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) - /// - /// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding. - /// - /// - /// This is an internal operation supposed to be used only in class for jpeg encoding. - /// - /// The value. - [MethodImpl(InliningOptions.ShortMethod)] - internal static int GetHuffmanEncodingLength(uint value) - { - DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); + // BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0 + // Lzcnt would return 32 for input value of 0 - no need to check that with branching + // Fallback code if Lzcnt is not supported still use if-check + // But most modern CPUs support this instruction so this should not be a problem + return 32 - BitOperations.LeadingZeroCount(value); + } - // This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation - // But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value)) + /// + /// General method for flushing cached spectral data bytes to + /// the ouput stream respecting stuff bytes. + /// + /// + /// Bytes cached via are stored in 4-bytes blocks + /// which makes this method endianness dependent. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void FlushToStream(int endIndex) + { + Span emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan()); - // BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0 - // Lzcnt would return 32 for input value of 0 - no need to check that with branching - // Fallback code if Lzcnt is not supported still use if-check - // But most modern CPUs support this instruction so this should not be a problem - return 32 - BitOperations.LeadingZeroCount(value); - } + int writeIdx = 0; + int startIndex = emitBytes.Length - 1; - /// - /// General method for flushing cached spectral data bytes to - /// the ouput stream respecting stuff bytes. - /// - /// - /// Bytes cached via are stored in 4-bytes blocks - /// which makes this method endianness dependent. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void FlushToStream(int endIndex) + // Some platforms may fail to eliminate this if-else branching + // Even if it happens - buffer is flushed in big packs, + // branching overhead shouldn't be noticeable + if (BitConverter.IsLittleEndian) { - Span emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan()); - - int writeIdx = 0; - int startIndex = emitBytes.Length - 1; + // For little endian case bytes are ordered and can be + // safely written to the stream with stuff bytes + // First byte is cached on the most significant index + // so we are going from the end of the array to its beginning: + // ... [ double word #1 ] [ double word #0 ] + // ... [idx3|idx2|idx1|idx0] [idx3|idx2|idx1|idx0] + for (int i = startIndex; i >= endIndex; i--) + { + byte value = emitBytes[i]; + this.streamWriteBuffer[writeIdx++] = value; - // Some platforms may fail to eliminate this if-else branching - // Even if it happens - buffer is flushed in big packs, - // branching overhead shouldn't be noticeable - if (BitConverter.IsLittleEndian) + // Inserting stuff byte + if (value == 0xff) + { + this.streamWriteBuffer[writeIdx++] = 0x00; + } + } + } + else + { + // For big endian case bytes are ordered in 4-byte packs + // which are ordered like bytes in the little endian case by in 4-byte packs: + // ... [ double word #1 ] [ double word #0 ] + // ... [idx0|idx1|idx2|idx3] [idx0|idx1|idx2|idx3] + // So we must write each 4-bytes in 'natural order' + for (int i = startIndex; i >= endIndex; i -= 4) { - // For little endian case bytes are ordered and can be - // safely written to the stream with stuff bytes - // First byte is cached on the most significant index - // so we are going from the end of the array to its beginning: - // ... [ double word #1 ] [ double word #0 ] - // ... [idx3|idx2|idx1|idx0] [idx3|idx2|idx1|idx0] - for (int i = startIndex; i >= endIndex; i--) + // This loop is caused by the nature of underlying byte buffer + // implementation and indeed causes performace by somewhat 5% + // compared to little endian scenario + // Even with this performance drop this cached buffer implementation + // is faster than individually writing bytes using binary shifts and binary and(s) + for (int j = i - 3; j <= i; j++) { - byte value = emitBytes[i]; + byte value = emitBytes[j]; this.streamWriteBuffer[writeIdx++] = value; // Inserting stuff byte @@ -591,74 +614,47 @@ private void FlushToStream(int endIndex) } } } - else - { - // For big endian case bytes are ordered in 4-byte packs - // which are ordered like bytes in the little endian case by in 4-byte packs: - // ... [ double word #1 ] [ double word #0 ] - // ... [idx0|idx1|idx2|idx3] [idx0|idx1|idx2|idx3] - // So we must write each 4-bytes in 'natural order' - for (int i = startIndex; i >= endIndex; i -= 4) - { - // This loop is caused by the nature of underlying byte buffer - // implementation and indeed causes performace by somewhat 5% - // compared to little endian scenario - // Even with this performance drop this cached buffer implementation - // is faster than individually writing bytes using binary shifts and binary and(s) - for (int j = i - 3; j <= i; j++) - { - byte value = emitBytes[j]; - this.streamWriteBuffer[writeIdx++] = value; - - // Inserting stuff byte - if (value == 0xff) - { - this.streamWriteBuffer[writeIdx++] = 0x00; - } - } - } - } - - this.target.Write(this.streamWriteBuffer, 0, writeIdx); - this.emitWriteIndex = this.emitBuffer.Length; } - /// - /// Flushes spectral data bytes after encoding all channel blocks - /// in a single jpeg macroblock using . - /// - /// - /// This must be called only if is true - /// only during the macroblocks encoding routine. - /// - private void FlushToStream() => - this.FlushToStream(this.emitWriteIndex * 4); - - /// - /// Flushes final cached bits to the stream padding 1's to - /// complement full bytes. - /// - /// - /// This must be called only once at the end of the encoding routine. - /// check is not needed. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void FlushRemainingBytes() - { - // Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits - // And writing only valuable count of bytes count we want to write to the output stream - int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8); - uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount); - this.emitBuffer[this.emitWriteIndex - 1] = packedBytes; - - // Flush cached bytes to the output stream with padding bits - int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; - this.FlushToStream(lastByteIndex); - - // Clear huffman register - // This is needed for for images with multiples scans - this.bitCount = 0; - this.accumulatedBits = 0; - } + this.target.Write(this.streamWriteBuffer, 0, writeIdx); + this.emitWriteIndex = this.emitBuffer.Length; + } + + /// + /// Flushes spectral data bytes after encoding all channel blocks + /// in a single jpeg macroblock using . + /// + /// + /// This must be called only if is true + /// only during the macroblocks encoding routine. + /// + private void FlushToStream() => + this.FlushToStream(this.emitWriteIndex * 4); + + /// + /// Flushes final cached bits to the stream padding 1's to + /// complement full bytes. + /// + /// + /// This must be called only once at the end of the encoding routine. + /// check is not needed. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void FlushRemainingBytes() + { + // Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits + // And writing only valuable count of bytes count we want to write to the output stream + int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8); + uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount); + this.emitBuffer[this.emitWriteIndex - 1] = packedBytes; + + // Flush cached bytes to the output stream with padding bits + int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; + this.FlushToStream(lastByteIndex); + + // Clear huffman register + // This is needed for for images with multiples scans + this.bitCount = 0; + this.accumulatedBits = 0; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs index 1a262f480d..faba3d5afc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs @@ -1,150 +1,149 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +/// The Huffman encoding specifications. +/// +public readonly struct HuffmanSpec { /// - /// The Huffman encoding specifications. + /// Huffman talbe specification for luminance DC. /// - public readonly struct HuffmanSpec - { - /// - /// Huffman talbe specification for luminance DC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec LuminanceDC = new( - new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }); - - /// - /// Huffman talbe specification for luminance AC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec LuminanceAC = new( - new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, - 0, 1, 125 - }, - new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, - 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, - 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, - 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, - 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, - 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, - 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, - 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, - 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, - 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, - 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, - 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }); - - /// - /// Huffman talbe specification for chrominance DC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec ChrominanceDC = new( - new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0 - }, - new byte[] - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 - }); + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceDC = new( + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0 + }, + new byte[] + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); - /// - /// Huffman talbe specification for chrominance DC. - /// - /// - /// This is an example specification taken from the jpeg specification paper. - /// - public static readonly HuffmanSpec ChrominanceAC = new( - new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, - 1, 2, 119 - }, - new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, - 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, - 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, - 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, - 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, - 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, - 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, - 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, - 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, - 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, - 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, - 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, - 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, - 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, - 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa - }); + /// + /// Huffman talbe specification for luminance AC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec LuminanceAC = new( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, + 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, + 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - /// - /// Initializes a new instance of the struct. - /// - /// - /// The number of codes. - /// - /// - /// The decoded values. - /// - public HuffmanSpec(byte[] count, byte[] values) + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceDC = new( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0 + }, + new byte[] { - this.Count = count; - this.Values = values; - } + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + }); - /// - /// Gets the count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count { get; } + /// + /// Huffman talbe specification for chrominance DC. + /// + /// + /// This is an example specification taken from the jpeg specification paper. + /// + public static readonly HuffmanSpec ChrominanceAC = new( + new byte[] + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, + 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, + 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, + 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, + 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, + 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, + 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, + 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa + }); - /// - /// Gets the value[i] - The decoded value of the codeword at the given index. - /// - public readonly byte[] Values { get; } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The number of codes. + /// + /// + /// The decoded values. + /// + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; } + + /// + /// Gets the count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count { get; } + + /// + /// Gets the value[i] - The decoded value of the codeword at the given index. + /// + public readonly byte[] Values { get; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs index 990c218a8c..97a4a2dc0e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/JpegFrame.cs @@ -1,85 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +/// Represent a single jpeg frame. +/// +internal sealed class JpegFrame : IDisposable { - /// - /// Represent a single jpeg frame. - /// - internal sealed class JpegFrame : IDisposable + public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved) { - public JpegFrame(Image image, JpegFrameConfig frameConfig, bool interleaved) - { - this.ColorSpace = frameConfig.ColorType; + this.ColorSpace = frameConfig.ColorType; - this.Interleaved = interleaved; + this.Interleaved = interleaved; - this.PixelWidth = image.Width; - this.PixelHeight = image.Height; + this.PixelWidth = image.Width; + this.PixelHeight = image.Height; - MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; - JpegComponentConfig[] componentConfigs = frameConfig.Components; - this.Components = new Component[componentConfigs.Length]; - for (int i = 0; i < this.Components.Length; i++) - { - JpegComponentConfig componentConfig = componentConfigs[i]; - this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) - { - DcTableId = componentConfig.DcTableSelector, - AcTableId = componentConfig.AcTableSelector, - }; - - this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; - } - - int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor; - int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor; - this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); - this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); - - for (int i = 0; i < this.Components.Length; i++) + JpegComponentConfig[] componentConfigs = frameConfig.Components; + this.Components = new Component[componentConfigs.Length]; + for (int i = 0; i < this.Components.Length; i++) + { + JpegComponentConfig componentConfig = componentConfigs[i]; + this.Components[i] = new Component(allocator, componentConfig.HorizontalSampleFactor, componentConfig.VerticalSampleFactor, componentConfig.QuantizatioTableIndex) { - Component component = this.Components[i]; - component.Init(this, maxSubFactorH, maxSubFactorV); - } + DcTableId = componentConfig.DcTableSelector, + AcTableId = componentConfig.AcTableSelector, + }; + + this.BlocksPerMcu += componentConfig.HorizontalSampleFactor * componentConfig.VerticalSampleFactor; } - public JpegColorSpace ColorSpace { get; } + int maxSubFactorH = frameConfig.MaxHorizontalSamplingFactor; + int maxSubFactorV = frameConfig.MaxVerticalSamplingFactor; + this.McusPerLine = (int)Numerics.DivideCeil((uint)image.Width, (uint)maxSubFactorH * 8); + this.McusPerColumn = (int)Numerics.DivideCeil((uint)image.Height, (uint)maxSubFactorV * 8); - public bool Interleaved { get; } + for (int i = 0; i < this.Components.Length; i++) + { + Component component = this.Components[i]; + component.Init(this, maxSubFactorH, maxSubFactorV); + } + } - public int PixelHeight { get; } + public JpegColorSpace ColorSpace { get; } - public int PixelWidth { get; } + public bool Interleaved { get; } - public Component[] Components { get; } + public int PixelHeight { get; } - public int McusPerLine { get; } + public int PixelWidth { get; } - public int McusPerColumn { get; } + public Component[] Components { get; } - public int BlocksPerMcu { get; } + public int McusPerLine { get; } - public void Dispose() + public int McusPerColumn { get; } + + public int BlocksPerMcu { get; } + + public void Dispose() + { + for (int i = 0; i < this.Components.Length; i++) { - for (int i = 0; i < this.Components.Length; i++) - { - this.Components[i].Dispose(); - } + this.Components[i].Dispose(); } + } - public void AllocateComponents(bool fullScan) + public void AllocateComponents(bool fullScan) + { + for (int i = 0; i < this.Components.Length; i++) { - for (int i = 0; i < this.Components.Length; i++) - { - Component component = this.Components[i]; - component.AllocateSpectral(fullScan); - } + Component component = this.Components[i]; + component.AllocateSpectral(fullScan); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs index 5cc8a0e34f..e1f6c186e2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +/// Converter used to convert pixel data to jpeg spectral data. +/// +internal abstract class SpectralConverter { - /// - /// Converter used to convert pixel data to jpeg spectral data. - /// - internal abstract class SpectralConverter - { - } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs index 07f9e2e49f..47a6029065 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/SpectralConverter{TPixel}.cs @@ -1,149 +1,146 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; + +/// +internal class SpectralConverter : SpectralConverter, IDisposable + where TPixel : unmanaged, IPixel { - /// - internal class SpectralConverter : SpectralConverter, IDisposable - where TPixel : unmanaged, IPixel - { - private readonly ComponentProcessor[] componentProcessors; + private readonly ComponentProcessor[] componentProcessors; + + private readonly int pixelRowsPerStep; + + private int pixelRowCounter; + + private readonly Buffer2D pixelBuffer; + + private readonly IMemoryOwner redLane; - private readonly int pixelRowsPerStep; + private readonly IMemoryOwner greenLane; - private int pixelRowCounter; + private readonly IMemoryOwner blueLane; - private readonly Buffer2D pixelBuffer; + private readonly int alignedPixelWidth; - private readonly IMemoryOwner redLane; + private readonly JpegColorConverterBase colorConverter; - private readonly IMemoryOwner greenLane; + public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables) + { + MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; - private readonly IMemoryOwner blueLane; + // iteration data + int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); - private readonly int alignedPixelWidth; + const int blockPixelHeight = 8; + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; - private readonly JpegColorConverterBase colorConverter; + // pixel buffer of the image + this.pixelBuffer = image.GetRootFramePixelBuffer(); - public SpectralConverter(JpegFrame frame, Image image, Block8x8F[] dequantTables) + // component processors from spectral to Rgb24 + const int blockPixelWidth = 8; + this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; + var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); + this.componentProcessors = new ComponentProcessor[frame.Components.Length]; + for (int i = 0; i < this.componentProcessors.Length; i++) { - MemoryAllocator allocator = image.GetConfiguration().MemoryAllocator; - - // iteration data - int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); - int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); - - const int blockPixelHeight = 8; - this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; - - // pixel buffer of the image - this.pixelBuffer = image.GetRootFramePixelBuffer(); - - // component processors from spectral to Rgb24 - const int blockPixelWidth = 8; - this.alignedPixelWidth = majorBlockWidth * blockPixelWidth; - var postProcessorBufferSize = new Size(this.alignedPixelWidth, this.pixelRowsPerStep); - this.componentProcessors = new ComponentProcessor[frame.Components.Length]; - for (int i = 0; i < this.componentProcessors.Length; i++) - { - Component component = frame.Components[i]; - this.componentProcessors[i] = new ComponentProcessor( - allocator, - component, - postProcessorBufferSize, - dequantTables[component.QuantizationTableIndex]); - } - - this.redLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - this.greenLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - this.blueLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); - - // color converter from Rgb24 to YCbCr - this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); + Component component = frame.Components[i]; + this.componentProcessors[i] = new ComponentProcessor( + allocator, + component, + postProcessorBufferSize, + dequantTables[component.QuantizationTableIndex]); } - public void ConvertStrideBaseline() - { - // Codestyle suggests expression body but it - // also requires empty line before comments - // which looks ugly with expression bodies thus this warning disable + this.redLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.greenLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + this.blueLane = allocator.Allocate(this.alignedPixelWidth, AllocationOptions.Clean); + + // color converter from Rgb24 to YCbCr + this.colorConverter = JpegColorConverterBase.GetConverter(colorSpace: frame.ColorSpace, precision: 8); + } + + public void ConvertStrideBaseline() + { + // Codestyle suggests expression body but it + // also requires empty line before comments + // which looks ugly with expression bodies thus this warning disable #pragma warning disable IDE0022 - // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates the need of virtual call - // from JpegComponentPostProcessor - this.ConvertStride(spectralStep: 0); + // Convert next pixel stride using single spectral `stride' + // Note that zero passing eliminates the need of virtual call + // from JpegComponentPostProcessor + this.ConvertStride(spectralStep: 0); #pragma warning restore IDE0022 - } + } - public void ConvertFull() + public void ConvertFull() + { + int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep); + for (int i = 0; i < steps; i++) { - int steps = (int)Numerics.DivideCeil((uint)this.pixelBuffer.Height, (uint)this.pixelRowsPerStep); - for (int i = 0; i < steps; i++) - { - this.ConvertStride(i); - } + this.ConvertStride(i); } + } - private void ConvertStride(int spectralStep) - { - int start = this.pixelRowCounter; - int end = start + this.pixelRowsPerStep; - - int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; + private void ConvertStride(int spectralStep) + { + int start = this.pixelRowCounter; + int end = start + this.pixelRowsPerStep; - // Pixel strides must be padded with the last pixel of the stride - int paddingStartIndex = this.pixelBuffer.Width; - int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width; + int pixelBufferLastVerticalIndex = this.pixelBuffer.Height - 1; - Span rLane = this.redLane.GetSpan(); - Span gLane = this.greenLane.GetSpan(); - Span bLane = this.blueLane.GetSpan(); + // Pixel strides must be padded with the last pixel of the stride + int paddingStartIndex = this.pixelBuffer.Width; + int paddedPixelsCount = this.alignedPixelWidth - this.pixelBuffer.Width; - for (int yy = start; yy < end; yy++) - { - int y = yy - this.pixelRowCounter; + Span rLane = this.redLane.GetSpan(); + Span gLane = this.greenLane.GetSpan(); + Span bLane = this.blueLane.GetSpan(); - // Unpack TPixel to r/g/b planes - int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex); - Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); - PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); + for (int yy = start; yy < end; yy++) + { + int y = yy - this.pixelRowCounter; - rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); - gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); - bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); + // Unpack TPixel to r/g/b planes + int srcIndex = Math.Min(yy, pixelBufferLastVerticalIndex); + Span sourceRow = this.pixelBuffer.DangerousGetRowSpan(srcIndex); + PixelOperations.Instance.UnpackIntoRgbPlanes(rLane, gLane, bLane, sourceRow); - // Convert from rgb24 to target pixel type - var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); - this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); - } + rLane.Slice(paddingStartIndex).Fill(rLane[paddingStartIndex - 1]); + gLane.Slice(paddingStartIndex).Fill(gLane[paddingStartIndex - 1]); + bLane.Slice(paddingStartIndex).Fill(bLane[paddingStartIndex - 1]); - // Convert pixels to spectral - for (int i = 0; i < this.componentProcessors.Length; i++) - { - this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); - } + // Convert from rgb24 to target pixel type + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); + this.colorConverter.ConvertFromRgb(values, rLane, gLane, bLane); + } - this.pixelRowCounter = end; + // Convert pixels to spectral + for (int i = 0; i < this.componentProcessors.Length; i++) + { + this.componentProcessors[i].CopyColorBufferToBlocks(spectralStep); } - /// - public void Dispose() + this.pixelRowCounter = end; + } + + /// + public void Dispose() + { + foreach (ComponentProcessor cpp in this.componentProcessors) { - foreach (ComponentProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - - this.redLane.Dispose(); - this.greenLane.Dispose(); - this.blueLane.Dispose(); + cpp.Dispose(); } + + this.redLane.Dispose(); + this.greenLane.Dispose(); + this.blueLane.Dispose(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs index 4bc4181ff6..cae89fc3cf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.Intrinsic.cs @@ -4,140 +4,139 @@ using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal static partial class FloatingPointDCT { - internal static partial class FloatingPointDCT + /// + /// Apply floating point FDCT inplace using simd operations. + /// + /// Input block. + private static void FDCT8x8_Avx(ref Block8x8F block) { - /// - /// Apply floating point FDCT inplace using simd operations. - /// - /// Input block. - private static void FDCT8x8_Avx(ref Block8x8F block) + DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); + + // First pass - process columns + FDCT8x8_1D_Avx(ref block); + + // Second pass - process rows + block.TransposeInplace(); + FDCT8x8_1D_Avx(ref block); + + // Applies 1D floating point FDCT inplace + static void FDCT8x8_1D_Avx(ref Block8x8F block) { - DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); - - // First pass - process columns - FDCT8x8_1D_Avx(ref block); - - // Second pass - process rows - block.TransposeInplace(); - FDCT8x8_1D_Avx(ref block); - - // Applies 1D floating point FDCT inplace - static void FDCT8x8_1D_Avx(ref Block8x8F block) - { - Vector256 tmp0 = Avx.Add(block.V0, block.V7); - Vector256 tmp7 = Avx.Subtract(block.V0, block.V7); - Vector256 tmp1 = Avx.Add(block.V1, block.V6); - Vector256 tmp6 = Avx.Subtract(block.V1, block.V6); - Vector256 tmp2 = Avx.Add(block.V2, block.V5); - Vector256 tmp5 = Avx.Subtract(block.V2, block.V5); - Vector256 tmp3 = Avx.Add(block.V3, block.V4); - Vector256 tmp4 = Avx.Subtract(block.V3, block.V4); - - // Even part - Vector256 tmp10 = Avx.Add(tmp0, tmp3); - Vector256 tmp13 = Avx.Subtract(tmp0, tmp3); - Vector256 tmp11 = Avx.Add(tmp1, tmp2); - Vector256 tmp12 = Avx.Subtract(tmp1, tmp2); - - block.V0 = Avx.Add(tmp10, tmp11); - block.V4 = Avx.Subtract(tmp10, tmp11); - - var mm256_F_0_7071 = Vector256.Create(0.707106781f); - Vector256 z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071); - block.V2 = Avx.Add(tmp13, z1); - block.V6 = Avx.Subtract(tmp13, z1); - - // Odd part - tmp10 = Avx.Add(tmp4, tmp5); - tmp11 = Avx.Add(tmp5, tmp6); - tmp12 = Avx.Add(tmp6, tmp7); - - Vector256 z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), Vector256.Create(0.382683433f)); // mm256_F_0_3826 - Vector256 z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411 - Vector256 z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(1.306562965f), tmp12); // mm256_F_1_3065 - Vector256 z3 = Avx.Multiply(tmp11, mm256_F_0_7071); - - Vector256 z11 = Avx.Add(tmp7, z3); - Vector256 z13 = Avx.Subtract(tmp7, z3); - - block.V5 = Avx.Add(z13, z2); - block.V3 = Avx.Subtract(z13, z2); - block.V1 = Avx.Add(z11, z4); - block.V7 = Avx.Subtract(z11, z4); - } + Vector256 tmp0 = Avx.Add(block.V0, block.V7); + Vector256 tmp7 = Avx.Subtract(block.V0, block.V7); + Vector256 tmp1 = Avx.Add(block.V1, block.V6); + Vector256 tmp6 = Avx.Subtract(block.V1, block.V6); + Vector256 tmp2 = Avx.Add(block.V2, block.V5); + Vector256 tmp5 = Avx.Subtract(block.V2, block.V5); + Vector256 tmp3 = Avx.Add(block.V3, block.V4); + Vector256 tmp4 = Avx.Subtract(block.V3, block.V4); + + // Even part + Vector256 tmp10 = Avx.Add(tmp0, tmp3); + Vector256 tmp13 = Avx.Subtract(tmp0, tmp3); + Vector256 tmp11 = Avx.Add(tmp1, tmp2); + Vector256 tmp12 = Avx.Subtract(tmp1, tmp2); + + block.V0 = Avx.Add(tmp10, tmp11); + block.V4 = Avx.Subtract(tmp10, tmp11); + + var mm256_F_0_7071 = Vector256.Create(0.707106781f); + Vector256 z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071); + block.V2 = Avx.Add(tmp13, z1); + block.V6 = Avx.Subtract(tmp13, z1); + + // Odd part + tmp10 = Avx.Add(tmp4, tmp5); + tmp11 = Avx.Add(tmp5, tmp6); + tmp12 = Avx.Add(tmp6, tmp7); + + Vector256 z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), Vector256.Create(0.382683433f)); // mm256_F_0_3826 + Vector256 z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(0.541196100f), tmp10); // mm256_F_0_5411 + Vector256 z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, Vector256.Create(1.306562965f), tmp12); // mm256_F_1_3065 + Vector256 z3 = Avx.Multiply(tmp11, mm256_F_0_7071); + + Vector256 z11 = Avx.Add(tmp7, z3); + Vector256 z13 = Avx.Subtract(tmp7, z3); + + block.V5 = Avx.Add(z13, z2); + block.V3 = Avx.Subtract(z13, z2); + block.V1 = Avx.Add(z11, z4); + block.V7 = Avx.Subtract(z11, z4); } + } + + /// + /// Apply floating point IDCT inplace using simd operations. + /// + /// Transposed input block. + private static void IDCT8x8_Avx(ref Block8x8F transposedBlock) + { + DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); + + // First pass - process columns + IDCT8x8_1D_Avx(ref transposedBlock); + + // Second pass - process rows + transposedBlock.TransposeInplace(); + IDCT8x8_1D_Avx(ref transposedBlock); - /// - /// Apply floating point IDCT inplace using simd operations. - /// - /// Transposed input block. - private static void IDCT8x8_Avx(ref Block8x8F transposedBlock) + // Applies 1D floating point FDCT inplace + static void IDCT8x8_1D_Avx(ref Block8x8F block) { - DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); - - // First pass - process columns - IDCT8x8_1D_Avx(ref transposedBlock); - - // Second pass - process rows - transposedBlock.TransposeInplace(); - IDCT8x8_1D_Avx(ref transposedBlock); - - // Applies 1D floating point FDCT inplace - static void IDCT8x8_1D_Avx(ref Block8x8F block) - { - // Even part - Vector256 tmp0 = block.V0; - Vector256 tmp1 = block.V2; - Vector256 tmp2 = block.V4; - Vector256 tmp3 = block.V6; - - Vector256 z5 = tmp0; - Vector256 tmp10 = Avx.Add(z5, tmp2); - Vector256 tmp11 = Avx.Subtract(z5, tmp2); - - var mm256_F_1_4142 = Vector256.Create(1.414213562f); - Vector256 tmp13 = Avx.Add(tmp1, tmp3); - Vector256 tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142); - - tmp0 = Avx.Add(tmp10, tmp13); - tmp3 = Avx.Subtract(tmp10, tmp13); - tmp1 = Avx.Add(tmp11, tmp12); - tmp2 = Avx.Subtract(tmp11, tmp12); - - // Odd part - Vector256 tmp4 = block.V1; - Vector256 tmp5 = block.V3; - Vector256 tmp6 = block.V5; - Vector256 tmp7 = block.V7; - - Vector256 z13 = Avx.Add(tmp6, tmp5); - Vector256 z10 = Avx.Subtract(tmp6, tmp5); - Vector256 z11 = Avx.Add(tmp4, tmp7); - Vector256 z12 = Avx.Subtract(tmp4, tmp7); - - tmp7 = Avx.Add(z11, z13); - tmp11 = Avx.Multiply(Avx.Subtract(z11, z13), mm256_F_1_4142); - - z5 = Avx.Multiply(Avx.Add(z10, z12), Vector256.Create(1.847759065f)); // mm256_F_1_8477 - - tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823 - tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131 - - tmp6 = Avx.Subtract(tmp12, tmp7); - tmp5 = Avx.Subtract(tmp11, tmp6); - tmp4 = Avx.Subtract(tmp10, tmp5); - - block.V0 = Avx.Add(tmp0, tmp7); - block.V7 = Avx.Subtract(tmp0, tmp7); - block.V1 = Avx.Add(tmp1, tmp6); - block.V6 = Avx.Subtract(tmp1, tmp6); - block.V2 = Avx.Add(tmp2, tmp5); - block.V5 = Avx.Subtract(tmp2, tmp5); - block.V3 = Avx.Add(tmp3, tmp4); - block.V4 = Avx.Subtract(tmp3, tmp4); - } + // Even part + Vector256 tmp0 = block.V0; + Vector256 tmp1 = block.V2; + Vector256 tmp2 = block.V4; + Vector256 tmp3 = block.V6; + + Vector256 z5 = tmp0; + Vector256 tmp10 = Avx.Add(z5, tmp2); + Vector256 tmp11 = Avx.Subtract(z5, tmp2); + + var mm256_F_1_4142 = Vector256.Create(1.414213562f); + Vector256 tmp13 = Avx.Add(tmp1, tmp3); + Vector256 tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142); + + tmp0 = Avx.Add(tmp10, tmp13); + tmp3 = Avx.Subtract(tmp10, tmp13); + tmp1 = Avx.Add(tmp11, tmp12); + tmp2 = Avx.Subtract(tmp11, tmp12); + + // Odd part + Vector256 tmp4 = block.V1; + Vector256 tmp5 = block.V3; + Vector256 tmp6 = block.V5; + Vector256 tmp7 = block.V7; + + Vector256 z13 = Avx.Add(tmp6, tmp5); + Vector256 z10 = Avx.Subtract(tmp6, tmp5); + Vector256 z11 = Avx.Add(tmp4, tmp7); + Vector256 z12 = Avx.Subtract(tmp4, tmp7); + + tmp7 = Avx.Add(z11, z13); + tmp11 = Avx.Multiply(Avx.Subtract(z11, z13), mm256_F_1_4142); + + z5 = Avx.Multiply(Avx.Add(z10, z12), Vector256.Create(1.847759065f)); // mm256_F_1_8477 + + tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, Vector256.Create(-1.082392200f)); // mm256_F_n1_0823 + tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, Vector256.Create(-2.613125930f)); // mm256_F_n2_6131 + + tmp6 = Avx.Subtract(tmp12, tmp7); + tmp5 = Avx.Subtract(tmp11, tmp6); + tmp4 = Avx.Subtract(tmp10, tmp5); + + block.V0 = Avx.Add(tmp0, tmp7); + block.V7 = Avx.Subtract(tmp0, tmp7); + block.V1 = Avx.Add(tmp1, tmp6); + block.V6 = Avx.Subtract(tmp1, tmp6); + block.V2 = Avx.Add(tmp2, tmp5); + block.V5 = Avx.Subtract(tmp2, tmp5); + block.V3 = Avx.Add(tmp3, tmp4); + block.V4 = Avx.Subtract(tmp3, tmp4); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs index cf0d69ef48..15348d4474 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FloatingPointDCT.cs @@ -7,271 +7,270 @@ using System.Runtime.Intrinsics.X86; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Contains floating point forward and inverse DCT implementations +/// +/// +/// Based on "Arai, Agui and Nakajima" algorithm. +/// +internal static partial class FloatingPointDCT { +#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings + private static readonly Vector4 mm128_F_0_7071 = new(0.707106781f); + private static readonly Vector4 mm128_F_0_3826 = new(0.382683433f); + private static readonly Vector4 mm128_F_0_5411 = new(0.541196100f); + private static readonly Vector4 mm128_F_1_3065 = new(1.306562965f); + + private static readonly Vector4 mm128_F_1_4142 = new(1.414213562f); + private static readonly Vector4 mm128_F_1_8477 = new(1.847759065f); + private static readonly Vector4 mm128_F_n1_0823 = new(-1.082392200f); + private static readonly Vector4 mm128_F_n2_6131 = new(-2.613125930f); +#pragma warning restore SA1310, SA1311, IDE1006 + /// - /// Contains floating point forward and inverse DCT implementations + /// Gets adjustment table for quantization tables. /// /// - /// Based on "Arai, Agui and Nakajima" algorithm. + /// + /// Current IDCT and FDCT implementations are based on Arai, Agui, + /// and Nakajima's algorithm. Both DCT methods does not + /// produce finished DCT output, final step is fused into the + /// quantization step. Quantization and de-quantization coefficients + /// must be multiplied by these values. + /// + /// + /// Given values were generated by formula: + /// + /// scalefactor[row] * scalefactor[col], where + /// scalefactor[0] = 1 + /// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + /// + /// /// - internal static partial class FloatingPointDCT + private static readonly float[] AdjustmentCoefficients = new float[] { -#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings - private static readonly Vector4 mm128_F_0_7071 = new(0.707106781f); - private static readonly Vector4 mm128_F_0_3826 = new(0.382683433f); - private static readonly Vector4 mm128_F_0_5411 = new(0.541196100f); - private static readonly Vector4 mm128_F_1_3065 = new(1.306562965f); - - private static readonly Vector4 mm128_F_1_4142 = new(1.414213562f); - private static readonly Vector4 mm128_F_1_8477 = new(1.847759065f); - private static readonly Vector4 mm128_F_n1_0823 = new(-1.082392200f); - private static readonly Vector4 mm128_F_n2_6131 = new(-2.613125930f); -#pragma warning restore SA1310, SA1311, IDE1006 + 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, + 1.3870399f, 1.9238797f, 1.812255f, 1.6309863f, 1.3870399f, 1.0897902f, 0.7506606f, 0.38268346f, + 1.306563f, 1.812255f, 1.707107f, 1.5363555f, 1.306563f, 1.02656f, 0.7071068f, 0.36047992f, + 1.1758755f, 1.6309863f, 1.5363555f, 1.3826833f, 1.1758755f, 0.9238795f, 0.63637924f, 0.32442334f, + 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, + 0.78569496f, 1.0897902f, 1.02656f, 0.9238795f, 0.78569496f, 0.61731654f, 0.42521507f, 0.21677275f, + 0.5411961f, 0.7506606f, 0.7071068f, 0.63637924f, 0.5411961f, 0.42521507f, 0.29289323f, 0.14931567f, + 0.27589938f, 0.38268346f, 0.36047992f, 0.32442334f, 0.27589938f, 0.21677275f, 0.14931567f, 0.076120466f, + }; - /// - /// Gets adjustment table for quantization tables. - /// - /// - /// - /// Current IDCT and FDCT implementations are based on Arai, Agui, - /// and Nakajima's algorithm. Both DCT methods does not - /// produce finished DCT output, final step is fused into the - /// quantization step. Quantization and de-quantization coefficients - /// must be multiplied by these values. - /// - /// - /// Given values were generated by formula: - /// - /// scalefactor[row] * scalefactor[col], where - /// scalefactor[0] = 1 - /// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 - /// - /// - /// - private static readonly float[] AdjustmentCoefficients = new float[] - { - 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, - 1.3870399f, 1.9238797f, 1.812255f, 1.6309863f, 1.3870399f, 1.0897902f, 0.7506606f, 0.38268346f, - 1.306563f, 1.812255f, 1.707107f, 1.5363555f, 1.306563f, 1.02656f, 0.7071068f, 0.36047992f, - 1.1758755f, 1.6309863f, 1.5363555f, 1.3826833f, 1.1758755f, 0.9238795f, 0.63637924f, 0.32442334f, - 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, - 0.78569496f, 1.0897902f, 1.02656f, 0.9238795f, 0.78569496f, 0.61731654f, 0.42521507f, 0.21677275f, - 0.5411961f, 0.7506606f, 0.7071068f, 0.63637924f, 0.5411961f, 0.42521507f, 0.29289323f, 0.14931567f, - 0.27589938f, 0.38268346f, 0.36047992f, 0.32442334f, 0.27589938f, 0.21677275f, 0.14931567f, 0.076120466f, - }; - - /// - /// Adjusts given quantization table for usage with . - /// - /// Quantization table to adjust. - public static void AdjustToIDCT(ref Block8x8F quantTable) + /// + /// Adjusts given quantization table for usage with . + /// + /// Quantization table to adjust. + public static void AdjustToIDCT(ref Block8x8F quantTable) + { + ref float tableRef = ref Unsafe.As(ref quantTable); + ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); + for (nint i = 0; i < Block8x8F.Size; i++) { - ref float tableRef = ref Unsafe.As(ref quantTable); - ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); - for (nint i = 0; i < Block8x8F.Size; i++) - { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f * elemRef * Unsafe.Add(ref multipliersRef, i); - } - - // Spectral macroblocks are transposed before quantization - // so we must transpose quantization table - quantTable.TransposeInplace(); + ref float elemRef = ref Unsafe.Add(ref tableRef, i); + elemRef = 0.125f * elemRef * Unsafe.Add(ref multipliersRef, i); } - /// - /// Adjusts given quantization table for usage with . - /// - /// Quantization table to adjust. - public static void AdjustToFDCT(ref Block8x8F quantTable) + // Spectral macroblocks are transposed before quantization + // so we must transpose quantization table + quantTable.TransposeInplace(); + } + + /// + /// Adjusts given quantization table for usage with . + /// + /// Quantization table to adjust. + public static void AdjustToFDCT(ref Block8x8F quantTable) + { + ref float tableRef = ref Unsafe.As(ref quantTable); + ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); + for (nint i = 0; i < Block8x8F.Size; i++) { - ref float tableRef = ref Unsafe.As(ref quantTable); - ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); - for (nint i = 0; i < Block8x8F.Size; i++) - { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f / (elemRef * Unsafe.Add(ref multipliersRef, i)); - } - - // Spectral macroblocks are not transposed before quantization - // Transpose is done after quantization at zig-zag stage - // so we must transpose quantization table - quantTable.TransposeInplace(); + ref float elemRef = ref Unsafe.Add(ref tableRef, i); + elemRef = 0.125f / (elemRef * Unsafe.Add(ref multipliersRef, i)); } - /// - /// Apply 2D floating point IDCT inplace. - /// - /// - /// Input block must be dequantized with quantization table - /// adjusted by . - /// - /// Input block. - public static void TransformIDCT(ref Block8x8F block) + // Spectral macroblocks are not transposed before quantization + // Transpose is done after quantization at zig-zag stage + // so we must transpose quantization table + quantTable.TransposeInplace(); + } + + /// + /// Apply 2D floating point IDCT inplace. + /// + /// + /// Input block must be dequantized with quantization table + /// adjusted by . + /// + /// Input block. + public static void TransformIDCT(ref Block8x8F block) + { + if (Avx.IsSupported) + { + IDCT8x8_Avx(ref block); + } + else { - if (Avx.IsSupported) - { - IDCT8x8_Avx(ref block); - } - else - { - IDCT_Vector4(ref block); - } + IDCT_Vector4(ref block); } + } - /// - /// Apply 2D floating point IDCT inplace. - /// - /// - /// Input block must be quantized after this method with quantization - /// table adjusted by . - /// - /// Input block. - public static void TransformFDCT(ref Block8x8F block) + /// + /// Apply 2D floating point IDCT inplace. + /// + /// + /// Input block must be quantized after this method with quantization + /// table adjusted by . + /// + /// Input block. + public static void TransformFDCT(ref Block8x8F block) + { + if (Avx.IsSupported) { - if (Avx.IsSupported) - { - FDCT8x8_Avx(ref block); - } - else - { - FDCT_Vector4(ref block); - } + FDCT8x8_Avx(ref block); } + else + { + FDCT_Vector4(ref block); + } + } + + /// + /// Apply floating point IDCT inplace using API. + /// + /// + /// This method can be used even if there's no SIMD intrinsics available + /// as can be compiled to scalar instructions. + /// + /// Input block. + private static void IDCT_Vector4(ref Block8x8F transposedBlock) + { + // First pass - process columns + IDCT8x4_Vector4(ref transposedBlock.V0L); + IDCT8x4_Vector4(ref transposedBlock.V0R); + + // Second pass - process rows + transposedBlock.TransposeInplace(); + IDCT8x4_Vector4(ref transposedBlock.V0L); + IDCT8x4_Vector4(ref transposedBlock.V0R); - /// - /// Apply floating point IDCT inplace using API. - /// - /// - /// This method can be used even if there's no SIMD intrinsics available - /// as can be compiled to scalar instructions. - /// - /// Input block. - private static void IDCT_Vector4(ref Block8x8F transposedBlock) + // Applies 1D floating point IDCT inplace on 8x4 part of 8x8 block + static void IDCT8x4_Vector4(ref Vector4 vecRef) { - // First pass - process columns - IDCT8x4_Vector4(ref transposedBlock.V0L); - IDCT8x4_Vector4(ref transposedBlock.V0R); - - // Second pass - process rows - transposedBlock.TransposeInplace(); - IDCT8x4_Vector4(ref transposedBlock.V0L); - IDCT8x4_Vector4(ref transposedBlock.V0R); - - // Applies 1D floating point IDCT inplace on 8x4 part of 8x8 block - static void IDCT8x4_Vector4(ref Vector4 vecRef) - { - // Even part - Vector4 tmp0 = Unsafe.Add(ref vecRef, 0 * 2); - Vector4 tmp1 = Unsafe.Add(ref vecRef, 2 * 2); - Vector4 tmp2 = Unsafe.Add(ref vecRef, 4 * 2); - Vector4 tmp3 = Unsafe.Add(ref vecRef, 6 * 2); - - Vector4 z5 = tmp0; - Vector4 tmp10 = z5 + tmp2; - Vector4 tmp11 = z5 - tmp2; - - Vector4 tmp13 = tmp1 + tmp3; - Vector4 tmp12 = ((tmp1 - tmp3) * mm128_F_1_4142) - tmp13; - - tmp0 = tmp10 + tmp13; - tmp3 = tmp10 - tmp13; - tmp1 = tmp11 + tmp12; - tmp2 = tmp11 - tmp12; - - // Odd part - Vector4 tmp4 = Unsafe.Add(ref vecRef, 1 * 2); - Vector4 tmp5 = Unsafe.Add(ref vecRef, 3 * 2); - Vector4 tmp6 = Unsafe.Add(ref vecRef, 5 * 2); - Vector4 tmp7 = Unsafe.Add(ref vecRef, 7 * 2); - - Vector4 z13 = tmp6 + tmp5; - Vector4 z10 = tmp6 - tmp5; - Vector4 z11 = tmp4 + tmp7; - Vector4 z12 = tmp4 - tmp7; - - tmp7 = z11 + z13; - tmp11 = (z11 - z13) * mm128_F_1_4142; - - z5 = (z10 + z12) * mm128_F_1_8477; - - tmp10 = (z12 * mm128_F_n1_0823) + z5; - tmp12 = (z10 * mm128_F_n2_6131) + z5; - - tmp6 = tmp12 - tmp7; - tmp5 = tmp11 - tmp6; - tmp4 = tmp10 - tmp5; - - Unsafe.Add(ref vecRef, 0 * 2) = tmp0 + tmp7; - Unsafe.Add(ref vecRef, 7 * 2) = tmp0 - tmp7; - Unsafe.Add(ref vecRef, 1 * 2) = tmp1 + tmp6; - Unsafe.Add(ref vecRef, 6 * 2) = tmp1 - tmp6; - Unsafe.Add(ref vecRef, 2 * 2) = tmp2 + tmp5; - Unsafe.Add(ref vecRef, 5 * 2) = tmp2 - tmp5; - Unsafe.Add(ref vecRef, 3 * 2) = tmp3 + tmp4; - Unsafe.Add(ref vecRef, 4 * 2) = tmp3 - tmp4; - } + // Even part + Vector4 tmp0 = Unsafe.Add(ref vecRef, 0 * 2); + Vector4 tmp1 = Unsafe.Add(ref vecRef, 2 * 2); + Vector4 tmp2 = Unsafe.Add(ref vecRef, 4 * 2); + Vector4 tmp3 = Unsafe.Add(ref vecRef, 6 * 2); + + Vector4 z5 = tmp0; + Vector4 tmp10 = z5 + tmp2; + Vector4 tmp11 = z5 - tmp2; + + Vector4 tmp13 = tmp1 + tmp3; + Vector4 tmp12 = ((tmp1 - tmp3) * mm128_F_1_4142) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + // Odd part + Vector4 tmp4 = Unsafe.Add(ref vecRef, 1 * 2); + Vector4 tmp5 = Unsafe.Add(ref vecRef, 3 * 2); + Vector4 tmp6 = Unsafe.Add(ref vecRef, 5 * 2); + Vector4 tmp7 = Unsafe.Add(ref vecRef, 7 * 2); + + Vector4 z13 = tmp6 + tmp5; + Vector4 z10 = tmp6 - tmp5; + Vector4 z11 = tmp4 + tmp7; + Vector4 z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * mm128_F_1_4142; + + z5 = (z10 + z12) * mm128_F_1_8477; + + tmp10 = (z12 * mm128_F_n1_0823) + z5; + tmp12 = (z10 * mm128_F_n2_6131) + z5; + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + Unsafe.Add(ref vecRef, 0 * 2) = tmp0 + tmp7; + Unsafe.Add(ref vecRef, 7 * 2) = tmp0 - tmp7; + Unsafe.Add(ref vecRef, 1 * 2) = tmp1 + tmp6; + Unsafe.Add(ref vecRef, 6 * 2) = tmp1 - tmp6; + Unsafe.Add(ref vecRef, 2 * 2) = tmp2 + tmp5; + Unsafe.Add(ref vecRef, 5 * 2) = tmp2 - tmp5; + Unsafe.Add(ref vecRef, 3 * 2) = tmp3 + tmp4; + Unsafe.Add(ref vecRef, 4 * 2) = tmp3 - tmp4; } + } + + /// + /// Apply floating point FDCT inplace using API. + /// + /// Input block. + private static void FDCT_Vector4(ref Block8x8F block) + { + // First pass - process columns + FDCT8x4_Vector4(ref block.V0L); + FDCT8x4_Vector4(ref block.V0R); + + // Second pass - process rows + block.TransposeInplace(); + FDCT8x4_Vector4(ref block.V0L); + FDCT8x4_Vector4(ref block.V0R); - /// - /// Apply floating point FDCT inplace using API. - /// - /// Input block. - private static void FDCT_Vector4(ref Block8x8F block) + // Applies 1D floating point FDCT inplace on 8x4 part of 8x8 block + static void FDCT8x4_Vector4(ref Vector4 vecRef) { - // First pass - process columns - FDCT8x4_Vector4(ref block.V0L); - FDCT8x4_Vector4(ref block.V0R); - - // Second pass - process rows - block.TransposeInplace(); - FDCT8x4_Vector4(ref block.V0L); - FDCT8x4_Vector4(ref block.V0R); - - // Applies 1D floating point FDCT inplace on 8x4 part of 8x8 block - static void FDCT8x4_Vector4(ref Vector4 vecRef) - { - Vector4 tmp0 = Unsafe.Add(ref vecRef, 0) + Unsafe.Add(ref vecRef, 14); - Vector4 tmp7 = Unsafe.Add(ref vecRef, 0) - Unsafe.Add(ref vecRef, 14); - Vector4 tmp1 = Unsafe.Add(ref vecRef, 2) + Unsafe.Add(ref vecRef, 12); - Vector4 tmp6 = Unsafe.Add(ref vecRef, 2) - Unsafe.Add(ref vecRef, 12); - Vector4 tmp2 = Unsafe.Add(ref vecRef, 4) + Unsafe.Add(ref vecRef, 10); - Vector4 tmp5 = Unsafe.Add(ref vecRef, 4) - Unsafe.Add(ref vecRef, 10); - Vector4 tmp3 = Unsafe.Add(ref vecRef, 6) + Unsafe.Add(ref vecRef, 8); - Vector4 tmp4 = Unsafe.Add(ref vecRef, 6) - Unsafe.Add(ref vecRef, 8); - - // Even part - Vector4 tmp10 = tmp0 + tmp3; - Vector4 tmp13 = tmp0 - tmp3; - Vector4 tmp11 = tmp1 + tmp2; - Vector4 tmp12 = tmp1 - tmp2; - - Unsafe.Add(ref vecRef, 0) = tmp10 + tmp11; - Unsafe.Add(ref vecRef, 8) = tmp10 - tmp11; - - Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071; - Unsafe.Add(ref vecRef, 4) = tmp13 + z1; - Unsafe.Add(ref vecRef, 12) = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826; - Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5; - Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5; - Vector4 z3 = tmp11 * mm128_F_0_7071; - - Vector4 z11 = tmp7 + z3; - Vector4 z13 = tmp7 - z3; - - Unsafe.Add(ref vecRef, 10) = z13 + z2; - Unsafe.Add(ref vecRef, 6) = z13 - z2; - Unsafe.Add(ref vecRef, 2) = z11 + z4; - Unsafe.Add(ref vecRef, 14) = z11 - z4; - } + Vector4 tmp0 = Unsafe.Add(ref vecRef, 0) + Unsafe.Add(ref vecRef, 14); + Vector4 tmp7 = Unsafe.Add(ref vecRef, 0) - Unsafe.Add(ref vecRef, 14); + Vector4 tmp1 = Unsafe.Add(ref vecRef, 2) + Unsafe.Add(ref vecRef, 12); + Vector4 tmp6 = Unsafe.Add(ref vecRef, 2) - Unsafe.Add(ref vecRef, 12); + Vector4 tmp2 = Unsafe.Add(ref vecRef, 4) + Unsafe.Add(ref vecRef, 10); + Vector4 tmp5 = Unsafe.Add(ref vecRef, 4) - Unsafe.Add(ref vecRef, 10); + Vector4 tmp3 = Unsafe.Add(ref vecRef, 6) + Unsafe.Add(ref vecRef, 8); + Vector4 tmp4 = Unsafe.Add(ref vecRef, 6) - Unsafe.Add(ref vecRef, 8); + + // Even part + Vector4 tmp10 = tmp0 + tmp3; + Vector4 tmp13 = tmp0 - tmp3; + Vector4 tmp11 = tmp1 + tmp2; + Vector4 tmp12 = tmp1 - tmp2; + + Unsafe.Add(ref vecRef, 0) = tmp10 + tmp11; + Unsafe.Add(ref vecRef, 8) = tmp10 - tmp11; + + Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071; + Unsafe.Add(ref vecRef, 4) = tmp13 + z1; + Unsafe.Add(ref vecRef, 12) = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826; + Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5; + Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5; + Vector4 z3 = tmp11 * mm128_F_0_7071; + + Vector4 z11 = tmp7 + z3; + Vector4 z13 = tmp7 - z3; + + Unsafe.Add(ref vecRef, 10) = z13 + z2; + Unsafe.Add(ref vecRef, 6) = z13 - z2; + Unsafe.Add(ref vecRef, 2) = z11 + z4; + Unsafe.Add(ref vecRef, 14) = z11 - z4; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs index 05494f68a8..a2ec0666b0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/JpegColorSpace.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Identifies the colorspace of a Jpeg image. +/// +internal enum JpegColorSpace { /// - /// Identifies the colorspace of a Jpeg image. + /// Color space with 1 component. /// - internal enum JpegColorSpace - { - /// - /// Color space with 1 component. - /// - Grayscale, + Grayscale, - /// - /// Color space with 4 components. - /// - Ycck, + /// + /// Color space with 4 components. + /// + Ycck, - /// - /// Color space with 4 components. - /// - Cmyk, + /// + /// Color space with 4 components. + /// + Cmyk, - /// - /// Color space with 3 components. - /// - RGB, + /// + /// Color space with 3 components. + /// + RGB, - /// - /// Color space with 3 components. - /// - YCbCr - } + /// + /// Color space with 3 components. + /// + YCbCr } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index 5fd7da35e5..3d53d5bfe8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -1,212 +1,210 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Provides methods and properties related to jpeg quantization. +/// +internal static class Quantization { /// - /// Provides methods and properties related to jpeg quantization. + /// Upper bound (inclusive) for jpeg quality setting. + /// + public const int MaxQualityFactor = 100; + + /// + /// Lower bound (inclusive) for jpeg quality setting. + /// + public const int MinQualityFactor = 1; + + /// + /// Default JPEG quality for both luminance and chominance tables. + /// + public const int DefaultQualityFactor = 75; + + /// + /// Represents lowest quality setting which can be estimated with enough confidence. + /// Any quality below it results in a highly compressed jpeg image + /// which shouldn't use standard itu quantization tables for re-encoding. + /// + public const int QualityEstimationConfidenceLowerThreshold = 25; + + /// + /// Represents highest quality setting which can be estimated with enough confidence. + /// + public const int QualityEstimationConfidenceUpperThreshold = 98; + + /// + /// Gets unscaled luminance quantization table. /// - internal static class Quantization + /// + /// The values are derived from ITU section K.1. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + public static ReadOnlySpan LuminanceTable => new byte[] { - /// - /// Upper bound (inclusive) for jpeg quality setting. - /// - public const int MaxQualityFactor = 100; - - /// - /// Lower bound (inclusive) for jpeg quality setting. - /// - public const int MinQualityFactor = 1; - - /// - /// Default JPEG quality for both luminance and chominance tables. - /// - public const int DefaultQualityFactor = 75; - - /// - /// Represents lowest quality setting which can be estimated with enough confidence. - /// Any quality below it results in a highly compressed jpeg image - /// which shouldn't use standard itu quantization tables for re-encoding. - /// - public const int QualityEstimationConfidenceLowerThreshold = 25; - - /// - /// Represents highest quality setting which can be estimated with enough confidence. - /// - public const int QualityEstimationConfidenceUpperThreshold = 98; - - /// - /// Gets unscaled luminance quantization table. - /// - /// - /// The values are derived from ITU section K.1. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - public static ReadOnlySpan LuminanceTable => new byte[] - { - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68, 109, 103, 77, - 24, 35, 55, 64, 81, 104, 113, 92, - 49, 64, 78, 87, 103, 121, 120, 101, - 72, 92, 95, 98, 112, 100, 103, 99, - }; - - /// - /// Gets unscaled chrominance quantization table. - /// - /// - /// The values are derived from ITU section K.1. - /// - // The C# compiler emits this as a compile-time constant embedded in the PE file. - // This is effectively compiled down to: return new ReadOnlySpan(&data, length) - // More details can be found: https://github.com/dotnet/roslyn/pull/24621 - public static ReadOnlySpan ChrominanceTable => new byte[] - { - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - }; - - /// Ported from JPEGsnoop: - /// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694 - /// - /// Estimates jpeg quality based on standard quantization table. - /// - /// - /// Technically, this can be used with any given table but internal decoder code uses ITU spec tables: - /// and . - /// - /// Input quantization table. - /// Natural order quantization table to estimate against. - /// Estimated quality. - public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan target) - { - // This method can be SIMD'ified if standard table is injected as Block8x8F. - // Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8. - double comparePercent; - double sumPercent = 0; - - // Corner case - all 1's => 100 quality - // It would fail to deduce using algorithm below without this check - if (table.EqualsToScalar(1)) - { - // While this is a 100% to be 100 quality, any given table can be scaled to all 1's. - // According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically. - // Quality=100 shouldn't be used in usual use case. - return 100; - } + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99, + }; - int quality; - for (int i = 0; i < Block8x8F.Size; i++) - { - int coeff = (int)table[i]; - - // Coefficients are actually int16 casted to float numbers so there's no truncating error. - if (coeff != 0) - { - comparePercent = 100.0 * (table[i] / target[i]); - } - else - { - // No 'valid' quantization table should contain zero at any position - // while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage. - // Not sure what to do here, we can't throw as this technically correct - // but this will screw up the encoder. - comparePercent = 999.99; - } - - sumPercent += comparePercent; - } + /// + /// Gets unscaled chrominance quantization table. + /// + /// + /// The values are derived from ITU section K.1. + /// + // The C# compiler emits this as a compile-time constant embedded in the PE file. + // This is effectively compiled down to: return new ReadOnlySpan(&data, length) + // More details can be found: https://github.com/dotnet/roslyn/pull/24621 + public static ReadOnlySpan ChrominanceTable => new byte[] + { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }; + + /// Ported from JPEGsnoop: + /// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694 + /// + /// Estimates jpeg quality based on standard quantization table. + /// + /// + /// Technically, this can be used with any given table but internal decoder code uses ITU spec tables: + /// and . + /// + /// Input quantization table. + /// Natural order quantization table to estimate against. + /// Estimated quality. + public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan target) + { + // This method can be SIMD'ified if standard table is injected as Block8x8F. + // Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8. + double comparePercent; + double sumPercent = 0; + + // Corner case - all 1's => 100 quality + // It would fail to deduce using algorithm below without this check + if (table.EqualsToScalar(1)) + { + // While this is a 100% to be 100 quality, any given table can be scaled to all 1's. + // According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically. + // Quality=100 shouldn't be used in usual use case. + return 100; + } - // Perform some statistical analysis of the quality factor - // to determine the likelihood of the current quantization - // table being a scaled version of the "standard" tables. - // If the variance is high, it is unlikely to be the case. - sumPercent /= 64.0; + int quality; + for (int i = 0; i < Block8x8F.Size; i++) + { + int coeff = (int)table[i]; - // Generate the equivalent IJQ "quality" factor - if (sumPercent <= 100.0) + // Coefficients are actually int16 casted to float numbers so there's no truncating error. + if (coeff != 0) { - quality = (int)Math.Round((200 - sumPercent) / 2); + comparePercent = 100.0 * (table[i] / target[i]); } else { - quality = (int)Math.Round(5000.0 / sumPercent); + // No 'valid' quantization table should contain zero at any position + // while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage. + // Not sure what to do here, we can't throw as this technically correct + // but this will screw up the encoder. + comparePercent = 999.99; } - return quality; + sumPercent += comparePercent; } - /// - /// Estimates jpeg quality based on quantization table in zig-zag order. - /// - /// Luminance quantization table. - /// Estimated quality - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable) - => EstimateQuality(ref luminanceTable, LuminanceTable); - - /// - /// Estimates jpeg quality based on quantization table in zig-zag order. - /// - /// Chrominance quantization table. - /// Estimated quality - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable) - => EstimateQuality(ref chrominanceTable, ChrominanceTable); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int QualityToScale(int quality) - { - DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality)); + // Perform some statistical analysis of the quality factor + // to determine the likelihood of the current quantization + // table being a scaled version of the "standard" tables. + // If the variance is high, it is unlikely to be the case. + sumPercent /= 64.0; - return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); + // Generate the equivalent IJQ "quality" factor + if (sumPercent <= 100.0) + { + quality = (int)Math.Round((200 - sumPercent) / 2); } - - public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) + else { - Block8x8F table = default; - for (int j = 0; j < Block8x8F.Size; j++) - { - int x = ((unscaledTable[j] * scale) + 50) / 100; - table[j] = Numerics.Clamp(x, 1, 255); - } - - return table; + quality = (int)Math.Round(5000.0 / sumPercent); } - public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable) - { - int scale = QualityToScale(quality); - Block8x8 table = default; - for (int j = 0; j < Block8x8.Size; j++) - { - int x = ((unscaledTable[j] * scale) + 50) / 100; - table[j] = (short)(uint)Numerics.Clamp(x, 1, 255); - } + return quality; + } + + /// + /// Estimates jpeg quality based on quantization table in zig-zag order. + /// + /// Luminance quantization table. + /// Estimated quality + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable) + => EstimateQuality(ref luminanceTable, LuminanceTable); - return table; + /// + /// Estimates jpeg quality based on quantization table in zig-zag order. + /// + /// Chrominance quantization table. + /// Estimated quality + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable) + => EstimateQuality(ref chrominanceTable, ChrominanceTable); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int QualityToScale(int quality) + { + DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality)); + + return quality < 50 ? (5000 / quality) : (200 - (quality * 2)); + } + + public static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan unscaledTable) + { + Block8x8F table = default; + for (int j = 0; j < Block8x8F.Size; j++) + { + int x = ((unscaledTable[j] * scale) + 50) / 100; + table[j] = Numerics.Clamp(x, 1, 255); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Block8x8F ScaleLuminanceTable(int quality) - => ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable); + return table; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Block8x8F ScaleChrominanceTable(int quality) - => ScaleQuantizationTable(scale: QualityToScale(quality), ChrominanceTable); + public static Block8x8 ScaleQuantizationTable(int quality, Block8x8 unscaledTable) + { + int scale = QualityToScale(quality); + Block8x8 table = default; + for (int j = 0; j < Block8x8.Size; j++) + { + int x = ((unscaledTable[j] * scale) + 50) / 100; + table[j] = (short)(uint)Numerics.Clamp(x, 1, 255); + } + + return table; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Block8x8F ScaleLuminanceTable(int quality) + => ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Block8x8F ScaleChrominanceTable(int quality) + => ScaleQuantizationTable(scale: QualityToScale(quality), ChrominanceTable); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 5872d47678..56e09dbb0f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -1,100 +1,98 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Cache 8 pixel rows on the stack, which may originate from different buffers of a . +/// +/// The type of element in each row. +[StructLayout(LayoutKind.Sequential)] +internal ref struct RowOctet + where T : struct { - /// - /// Cache 8 pixel rows on the stack, which may originate from different buffers of a . - /// - /// The type of element in each row. - [StructLayout(LayoutKind.Sequential)] - internal ref struct RowOctet - where T : struct + private Span row0; + private Span row1; + private Span row2; + private Span row3; + private Span row4; + private Span row5; + private Span row6; + private Span row7; + + // No unsafe tricks, since Span can't be used as a generic argument + public Span this[int y] { - private Span row0; - private Span row1; - private Span row2; - private Span row3; - private Span row4; - private Span row5; - private Span row6; - private Span row7; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => + 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() + }; - // No unsafe tricks, since Span can't be used as a generic argument - public Span this[int y] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => - 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(MethodImplOptions.AggressiveInlining)] - private set + switch (y) { - switch (y) - { - case 0: - this.row0 = value; - break; - case 1: - this.row1 = value; - break; - case 2: - this.row2 = value; - break; - case 3: - this.row3 = value; - break; - case 4: - this.row4 = value; - break; - case 5: - this.row5 = value; - break; - case 6: - this.row6 = value; - break; - default: - this.row7 = value; - break; - } + case 0: + this.row0 = value; + break; + case 1: + this.row1 = value; + break; + case 2: + this.row2 = value; + break; + case 3: + this.row3 = value; + break; + case 4: + this.row4 = value; + break; + case 5: + this.row5 = value; + break; + case 6: + this.row6 = value; + break; + default: + this.row7 = value; + break; } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Update(Buffer2D buffer, int startY) - { - // We don't actually have to assign values outside of the - // frame pixel buffer since they are never requested. - int y = startY; - int yEnd = Math.Min(y + 8, buffer.Height); + [MethodImpl(InliningOptions.ShortMethod)] + public void Update(Buffer2D buffer, int startY) + { + // We don't actually have to assign values outside of the + // frame pixel buffer since they are never requested. + int y = startY; + int yEnd = Math.Min(y + 8, buffer.Height); - int i = 0; - while (y < yEnd) - { - this[i++] = buffer.DangerousGetRowSpan(y++); - } + int i = 0; + while (y < yEnd) + { + this[i++] = buffer.DangerousGetRowSpan(y++); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static Span ThrowIndexOutOfRangeException() + [MethodImpl(MethodImplOptions.NoInlining)] + private static Span ThrowIndexOutOfRangeException() #pragma warning disable CA2201 // Do not raise reserved exception types - => throw new IndexOutOfRangeException(); + => throw new IndexOutOfRangeException(); #pragma warning restore CA2201 // Do not raise reserved exception types - } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs index 037995ea13..369626a96b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ScaledFloatingPointDCT.cs @@ -1,220 +1,218 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; #pragma warning disable IDE0078 -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Contains floating point forward DCT implementations with built-in scaling. +/// +/// +/// Based on "Loeffler, Ligtenberg, and Moschytz" algorithm. +/// +internal static class ScaledFloatingPointDCT { +#pragma warning disable SA1310 + private const float FP32_0_541196100 = 0.541196100f; + private const float FP32_0_765366865 = 0.765366865f; + private const float FP32_1_847759065 = 1.847759065f; + private const float FP32_0_211164243 = 0.211164243f; + private const float FP32_1_451774981 = 1.451774981f; + private const float FP32_2_172734803 = 2.172734803f; + private const float FP32_1_061594337 = 1.061594337f; + private const float FP32_0_509795579 = 0.509795579f; + private const float FP32_0_601344887 = 0.601344887f; + private const float FP32_0_899976223 = 0.899976223f; + private const float FP32_2_562915447 = 2.562915447f; + private const float FP32_0_720959822 = 0.720959822f; + private const float FP32_0_850430095 = 0.850430095f; + private const float FP32_1_272758580 = 1.272758580f; + private const float FP32_3_624509785 = 3.624509785f; +#pragma warning restore SA1310 + + /// + /// Adjusts given quantization table for usage with IDCT algorithms + /// from . + /// + /// Quantization table to adjust. + public static void AdjustToIDCT(ref Block8x8F quantTable) + { + ref float tableRef = ref Unsafe.As(ref quantTable); + for (nint i = 0; i < Block8x8F.Size; i++) + { + ref float elemRef = ref Unsafe.Add(ref tableRef, i); + elemRef = 0.125f * elemRef; + } + + // Spectral macroblocks are transposed before quantization + // so we must transpose quantization table + quantTable.TransposeInplace(); + } + /// - /// Contains floating point forward DCT implementations with built-in scaling. + /// Apply 2D floating point 'donwscaling' IDCT inplace producing + /// 8x8 -> 4x4 result. /// /// - /// Based on "Loeffler, Ligtenberg, and Moschytz" algorithm. + /// Resulting matrix is stored in the top left 4x4 part of the + /// . /// - internal static class ScaledFloatingPointDCT + /// Input block. + /// Dequantization table adjusted by . + /// Output range normalization value, 1/2 of the . + /// Maximum value of the output range. + public static void TransformIDCT_4x4(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) { -#pragma warning disable SA1310 - private const float FP32_0_541196100 = 0.541196100f; - private const float FP32_0_765366865 = 0.765366865f; - private const float FP32_1_847759065 = 1.847759065f; - private const float FP32_0_211164243 = 0.211164243f; - private const float FP32_1_451774981 = 1.451774981f; - private const float FP32_2_172734803 = 2.172734803f; - private const float FP32_1_061594337 = 1.061594337f; - private const float FP32_0_509795579 = 0.509795579f; - private const float FP32_0_601344887 = 0.601344887f; - private const float FP32_0_899976223 = 0.899976223f; - private const float FP32_2_562915447 = 2.562915447f; - private const float FP32_0_720959822 = 0.720959822f; - private const float FP32_0_850430095 = 0.850430095f; - private const float FP32_1_272758580 = 1.272758580f; - private const float FP32_3_624509785 = 3.624509785f; -#pragma warning restore SA1310 - - /// - /// Adjusts given quantization table for usage with IDCT algorithms - /// from . - /// - /// Quantization table to adjust. - public static void AdjustToIDCT(ref Block8x8F quantTable) + for (int ctr = 0; ctr < 8; ctr++) { - ref float tableRef = ref Unsafe.As(ref quantTable); - for (nint i = 0; i < Block8x8F.Size; i++) + // Don't process row 4, second pass doesn't use it + if (ctr == 4) { - ref float elemRef = ref Unsafe.Add(ref tableRef, i); - elemRef = 0.125f * elemRef; + continue; } - // Spectral macroblocks are transposed before quantization - // so we must transpose quantization table - quantTable.TransposeInplace(); + // Even part + float tmp0 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0] * 2; + + float z2 = block[(ctr * 8) + 2] * dequantTable[(ctr * 8) + 2]; + float z3 = block[(ctr * 8) + 6] * dequantTable[(ctr * 8) + 6]; + + float tmp2 = (z2 * FP32_1_847759065) + (z3 * -FP32_0_765366865); + + float tmp10 = tmp0 + tmp2; + float tmp12 = tmp0 - tmp2; + + // Odd part + float z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; + z2 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; + z3 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; + float z4 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; + + tmp0 = (z1 * -FP32_0_211164243) + + (z2 * FP32_1_451774981) + + (z3 * -FP32_2_172734803) + + (z4 * FP32_1_061594337); + + tmp2 = (z1 * -FP32_0_509795579) + + (z2 * -FP32_0_601344887) + + (z3 * FP32_0_899976223) + + (z4 * FP32_2_562915447); + + // temporal result is saved to +4 shifted indices + // because result is saved into the top left 2x2 region of the + // input block + block[(ctr * 8) + 0 + 4] = (tmp10 + tmp2) / 2; + block[(ctr * 8) + 3 + 4] = (tmp10 - tmp2) / 2; + block[(ctr * 8) + 1 + 4] = (tmp12 + tmp0) / 2; + block[(ctr * 8) + 2 + 4] = (tmp12 - tmp0) / 2; } - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 4x4 result. - /// - /// - /// Resulting matrix is stored in the top left 4x4 part of the - /// . - /// - /// Input block. - /// Dequantization table adjusted by . - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static void TransformIDCT_4x4(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) + for (int ctr = 0; ctr < 4; ctr++) { - for (int ctr = 0; ctr < 8; ctr++) - { - // Don't process row 4, second pass doesn't use it - if (ctr == 4) - { - continue; - } - - // Even part - float tmp0 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0] * 2; - - float z2 = block[(ctr * 8) + 2] * dequantTable[(ctr * 8) + 2]; - float z3 = block[(ctr * 8) + 6] * dequantTable[(ctr * 8) + 6]; - - float tmp2 = (z2 * FP32_1_847759065) + (z3 * -FP32_0_765366865); - - float tmp10 = tmp0 + tmp2; - float tmp12 = tmp0 - tmp2; - - // Odd part - float z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; - z2 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; - z3 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; - float z4 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; - - tmp0 = (z1 * -FP32_0_211164243) + - (z2 * FP32_1_451774981) + - (z3 * -FP32_2_172734803) + - (z4 * FP32_1_061594337); - - tmp2 = (z1 * -FP32_0_509795579) + - (z2 * -FP32_0_601344887) + - (z3 * FP32_0_899976223) + - (z4 * FP32_2_562915447); - - // temporal result is saved to +4 shifted indices - // because result is saved into the top left 2x2 region of the - // input block - block[(ctr * 8) + 0 + 4] = (tmp10 + tmp2) / 2; - block[(ctr * 8) + 3 + 4] = (tmp10 - tmp2) / 2; - block[(ctr * 8) + 1 + 4] = (tmp12 + tmp0) / 2; - block[(ctr * 8) + 2 + 4] = (tmp12 - tmp0) / 2; - } - - for (int ctr = 0; ctr < 4; ctr++) - { - // Even part - float tmp0 = block[ctr + (8 * 0) + 4] * 2; - - float tmp2 = (block[ctr + (8 * 2) + 4] * FP32_1_847759065) + (block[ctr + (8 * 6) + 4] * -FP32_0_765366865); - - float tmp10 = tmp0 + tmp2; - float tmp12 = tmp0 - tmp2; - - // Odd part - float z1 = block[ctr + (8 * 7) + 4]; - float z2 = block[ctr + (8 * 5) + 4]; - float z3 = block[ctr + (8 * 3) + 4]; - float z4 = block[ctr + (8 * 1) + 4]; - - tmp0 = (z1 * -FP32_0_211164243) + - (z2 * FP32_1_451774981) + - (z3 * -FP32_2_172734803) + - (z4 * FP32_1_061594337); - - tmp2 = (z1 * -FP32_0_509795579) + - (z2 * -FP32_0_601344887) + - (z3 * FP32_0_899976223) + - (z4 * FP32_2_562915447); - - // Save results to the top left 4x4 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp2) / 2) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 3] = MathF.Round(Numerics.Clamp(((tmp10 - tmp2) / 2) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp12 + tmp0) / 2) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 2] = MathF.Round(Numerics.Clamp(((tmp12 - tmp0) / 2) + normalizationValue, 0, maxValue)); - } + // Even part + float tmp0 = block[ctr + (8 * 0) + 4] * 2; + + float tmp2 = (block[ctr + (8 * 2) + 4] * FP32_1_847759065) + (block[ctr + (8 * 6) + 4] * -FP32_0_765366865); + + float tmp10 = tmp0 + tmp2; + float tmp12 = tmp0 - tmp2; + + // Odd part + float z1 = block[ctr + (8 * 7) + 4]; + float z2 = block[ctr + (8 * 5) + 4]; + float z3 = block[ctr + (8 * 3) + 4]; + float z4 = block[ctr + (8 * 1) + 4]; + + tmp0 = (z1 * -FP32_0_211164243) + + (z2 * FP32_1_451774981) + + (z3 * -FP32_2_172734803) + + (z4 * FP32_1_061594337); + + tmp2 = (z1 * -FP32_0_509795579) + + (z2 * -FP32_0_601344887) + + (z3 * FP32_0_899976223) + + (z4 * FP32_2_562915447); + + // Save results to the top left 4x4 subregion + block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp2) / 2) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 3] = MathF.Round(Numerics.Clamp(((tmp10 - tmp2) / 2) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp12 + tmp0) / 2) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 2] = MathF.Round(Numerics.Clamp(((tmp12 - tmp0) / 2) + normalizationValue, 0, maxValue)); } + } - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 2x2 result. - /// - /// - /// Resulting matrix is stored in the top left 2x2 part of the - /// . - /// - /// Input block. - /// Dequantization table adjusted by . - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static void TransformIDCT_2x2(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) + /// + /// Apply 2D floating point 'donwscaling' IDCT inplace producing + /// 8x8 -> 2x2 result. + /// + /// + /// Resulting matrix is stored in the top left 2x2 part of the + /// . + /// + /// Input block. + /// Dequantization table adjusted by . + /// Output range normalization value, 1/2 of the . + /// Maximum value of the output range. + public static void TransformIDCT_2x2(ref Block8x8F block, ref Block8x8F dequantTable, float normalizationValue, float maxValue) + { + for (int ctr = 0; ctr < 8; ctr++) { - for (int ctr = 0; ctr < 8; ctr++) + // Don't process rows 2/4/6, second pass doesn't use it + if (ctr == 2 || ctr == 4 || ctr == 6) { - // Don't process rows 2/4/6, second pass doesn't use it - if (ctr == 2 || ctr == 4 || ctr == 6) - { - continue; - } - - // Even part - float tmp0; - float z1 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0]; - float tmp10 = z1 * 4; - - // Odd part - z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; - tmp0 = z1 * -FP32_0_720959822; - z1 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; - tmp0 += z1 * FP32_0_850430095; - z1 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; - tmp0 += z1 * -FP32_1_272758580; - z1 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; - tmp0 += z1 * FP32_3_624509785; - - // temporal result is saved to +2 shifted indices - // because result is saved into the top left 2x2 region of the - // input block - block[(ctr * 8) + 2] = (tmp10 + tmp0) / 4; - block[(ctr * 8) + 3] = (tmp10 - tmp0) / 4; + continue; } - for (int ctr = 0; ctr < 2; ctr++) - { - // Even part - float tmp10 = block[ctr + (8 * 0) + 2] * 4; - - // Odd part - float tmp0 = (block[ctr + (8 * 7) + 2] * -FP32_0_720959822) + - (block[ctr + (8 * 5) + 2] * FP32_0_850430095) + - (block[ctr + (8 * 3) + 2] * -FP32_1_272758580) + - (block[ctr + (8 * 1) + 2] * FP32_3_624509785); - - // Save results to the top left 2x2 subregion - block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp0) / 4) + normalizationValue, 0, maxValue)); - block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp10 - tmp0) / 4) + normalizationValue, 0, maxValue)); - } + // Even part + float tmp0; + float z1 = block[(ctr * 8) + 0] * dequantTable[(ctr * 8) + 0]; + float tmp10 = z1 * 4; + + // Odd part + z1 = block[(ctr * 8) + 7] * dequantTable[(ctr * 8) + 7]; + tmp0 = z1 * -FP32_0_720959822; + z1 = block[(ctr * 8) + 5] * dequantTable[(ctr * 8) + 5]; + tmp0 += z1 * FP32_0_850430095; + z1 = block[(ctr * 8) + 3] * dequantTable[(ctr * 8) + 3]; + tmp0 += z1 * -FP32_1_272758580; + z1 = block[(ctr * 8) + 1] * dequantTable[(ctr * 8) + 1]; + tmp0 += z1 * FP32_3_624509785; + + // temporal result is saved to +2 shifted indices + // because result is saved into the top left 2x2 region of the + // input block + block[(ctr * 8) + 2] = (tmp10 + tmp0) / 4; + block[(ctr * 8) + 3] = (tmp10 - tmp0) / 4; } - /// - /// Apply 2D floating point 'donwscaling' IDCT inplace producing - /// 8x8 -> 1x1 result. - /// - /// Direct current term value from input block. - /// Dequantization value. - /// Output range normalization value, 1/2 of the . - /// Maximum value of the output range. - public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) - => MathF.Round(Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue)); + for (int ctr = 0; ctr < 2; ctr++) + { + // Even part + float tmp10 = block[ctr + (8 * 0) + 2] * 4; + + // Odd part + float tmp0 = (block[ctr + (8 * 7) + 2] * -FP32_0_720959822) + + (block[ctr + (8 * 5) + 2] * FP32_0_850430095) + + (block[ctr + (8 * 3) + 2] * -FP32_1_272758580) + + (block[ctr + (8 * 1) + 2] * FP32_3_624509785); + + // Save results to the top left 2x2 subregion + block[(ctr * 8) + 0] = MathF.Round(Numerics.Clamp(((tmp10 + tmp0) / 4) + normalizationValue, 0, maxValue)); + block[(ctr * 8) + 1] = MathF.Round(Numerics.Clamp(((tmp10 - tmp0) / 4) + normalizationValue, 0, maxValue)); + } } + + /// + /// Apply 2D floating point 'donwscaling' IDCT inplace producing + /// 8x8 -> 1x1 result. + /// + /// Direct current term value from input block. + /// Dequantization value. + /// Output range normalization value, 1/2 of the . + /// Maximum value of the output range. + public static float TransformIDCT_1x1(float dc, float dequantizer, float normalizationValue, float maxValue) + => MathF.Round(Numerics.Clamp((dc * dequantizer) + normalizationValue, 0, maxValue)); } #pragma warning restore IDE0078 diff --git a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs index fa7778cbae..c7212fc2df 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/SizeExtensions.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +/// +/// Extension methods for +/// +internal static class SizeExtensions { /// - /// Extension methods for + /// Multiplies 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height); + + /// + /// Divides 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. + /// TODO: Shouldn't we expose this as operator in SixLabors.Core? + /// + public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. /// - internal static class SizeExtensions + public static Size DivideRoundUp(this Size originalSize, int divX, int divY) { - /// - /// Multiplies 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. - /// TODO: Shouldn't we expose this as operator in SixLabors.Core? - /// - public static Size MultiplyBy(this Size a, Size b) => new Size(a.Width * b.Width, a.Height * b.Height); - - /// - /// Divides 'a.Width' with 'b.Width' and 'a.Height' with 'b.Height'. - /// TODO: Shouldn't we expose this as operator in SixLabors.Core? - /// - public static Size DivideBy(this Size a, Size b) => new Size(a.Width / b.Width, a.Height / b.Height); - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, int divX, int divY) - { - var sizeVect = (Vector2)(SizeF)originalSize; - sizeVect /= new Vector2(divX, divY); - sizeVect.X = MathF.Ceiling(sizeVect.X); - sizeVect.Y = MathF.Ceiling(sizeVect.Y); - - return new Size((int)sizeVect.X, (int)sizeVect.Y); - } - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, int divisor) => - DivideRoundUp(originalSize, divisor, divisor); - - /// - /// Divide Width and Height as real numbers and return the Ceiling. - /// - public static Size DivideRoundUp(this Size originalSize, Size divisor) => - DivideRoundUp(originalSize, divisor.Width, divisor.Height); + var sizeVect = (Vector2)(SizeF)originalSize; + sizeVect /= new Vector2(divX, divY); + sizeVect.X = MathF.Ceiling(sizeVect.X); + sizeVect.Y = MathF.Ceiling(sizeVect.Y); + + return new Size((int)sizeVect.X, (int)sizeVect.Y); } + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, int divisor) => + DivideRoundUp(originalSize, divisor, divisor); + + /// + /// Divide Width and Height as real numbers and return the Ceiling. + /// + public static Size DivideRoundUp(this Size originalSize, Size divisor) => + DivideRoundUp(originalSize, divisor.Width, divisor.Height); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs index 2fa1d835e7..f6239ad1e0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs @@ -1,305 +1,303 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal static partial class ZigZag { - internal static partial class ZigZag - { #pragma warning disable SA1309 // naming rules violation warnings - /// - /// Special byte value to zero out elements during Sse/Avx shuffle intrinsics. - /// - private const byte _ = 0xff; + /// + /// Special byte value to zero out elements during Sse/Avx shuffle intrinsics. + /// + private const byte _ = 0xff; #pragma warning restore SA1309 - /// - /// Gets shuffle vectors for - /// zig zag implementation. - /// - private static ReadOnlySpan SseShuffleMasks => new byte[] - { + /// + /// Gets shuffle vectors for + /// zig zag implementation. + /// + private static ReadOnlySpan SseShuffleMasks => new byte[] + { #pragma warning disable SA1515 - /* row0 - A0 B0 A1 A2 B1 C0 D0 C1 */ - // A - 0, 1, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, - // B - _, _, 0, 1, _, _, _, _, 2, 3, _, _, _, _, _, _, - // C - _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, - - /* row1 - B2 A3 A4 B3 C2 D1 E0 F0 */ - // A - _, _, 6, 7, 8, 9, _, _, _, _, _, _, _, _, _, _, - // B - 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, - - /* row2 - E1 D2 C3 B4 A5 A6 B5 C4 */ - // A - _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, _, _, - // B - _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, _, _, - // C - _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, 8, 9, - - /* row3 - D3 E2 F1 G0 H0 G1 F2 E3 */ - // E - _, _, 4, 5, _, _, _, _, _, _, _, _, _, _, 6, 7, - // F - _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5, _, _, - // G - _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, - - /* row4 - D4 C5 B6 A7 B7 C6 D5 E4 */ - // B - _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, - // C - _, _, 10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, - // D - 8, 9, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, - - /* row5 - F3 G2 H1 H2 G3 F4 E5 D6 */ - // F - 6, 7, _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, - // G - _, _, 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, - // H - _, _, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, - - /* row6 - C7 D7 E6 F5 G4 H3 H4 G5 */ - // G - _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, - // H - _, _, _, _, _, _, _, _, _, _, 6, 7, 8, 9, _, _, - - /* row7 - F6 E7 F7 G6 H5 H6 G7 H7 */ - // F - 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _, _, _, - // G - _, _, _, _, _, _, 12, 13, _, _, _, _, 14, 15, _, _, - // H - _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, 14, 15, + /* row0 - A0 B0 A1 A2 B1 C0 D0 C1 */ + // A + 0, 1, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, + // B + _, _, 0, 1, _, _, _, _, 2, 3, _, _, _, _, _, _, + // C + _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, + + /* row1 - B2 A3 A4 B3 C2 D1 E0 F0 */ + // A + _, _, 6, 7, 8, 9, _, _, _, _, _, _, _, _, _, _, + // B + 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, + + /* row2 - E1 D2 C3 B4 A5 A6 B5 C4 */ + // A + _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, _, _, + // B + _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, _, _, + // C + _, _, _, _, 6, 7, _, _, _, _, _, _, _, _, 8, 9, + + /* row3 - D3 E2 F1 G0 H0 G1 F2 E3 */ + // E + _, _, 4, 5, _, _, _, _, _, _, _, _, _, _, 6, 7, + // F + _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5, _, _, + // G + _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, + + /* row4 - D4 C5 B6 A7 B7 C6 D5 E4 */ + // B + _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, + // C + _, _, 10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, + // D + 8, 9, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, + + /* row5 - F3 G2 H1 H2 G3 F4 E5 D6 */ + // F + 6, 7, _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, + // G + _, _, 4, 5, _, _, _, _, 6, 7, _, _, _, _, _, _, + // H + _, _, _, _, 2, 3, 4, 5, _, _, _, _, _, _, _, _, + + /* row6 - C7 D7 E6 F5 G4 H3 H4 G5 */ + // G + _, _, _, _, _, _, _, _, 8, 9, _, _, _, _, 10, 11, + // H + _, _, _, _, _, _, _, _, _, _, 6, 7, 8, 9, _, _, + + /* row7 - F6 E7 F7 G6 H5 H6 G7 H7 */ + // F + 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _, _, _, + // G + _, _, _, _, _, _, 12, 13, _, _, _, _, 14, 15, _, _, + // H + _, _, _, _, _, _, _, _, 10, 11, 12, 13, _, _, 14, 15, #pragma warning restore SA1515 - }; + }; - /// - /// Gets shuffle vectors for - /// zig zag implementation. - /// - private static ReadOnlySpan AvxShuffleMasks => new byte[] - { + /// + /// Gets shuffle vectors for + /// zig zag implementation. + /// + private static ReadOnlySpan AvxShuffleMasks => new byte[] + { #pragma warning disable SA1515 - /* 01 */ - // [cr] crln_01_AB_CD - 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) AB - 0, 1, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 2, 3, 4, 5, 14, 15, _, _, _, _, _, _, _, _, - // (in) CD - _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, _, _, _, _, 0, 1, 10, 11, _, _, _, _, - // [cr] crln_01_23_EF_23_CD - 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) EF - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, - - /* 23 */ - // [cr] crln_23_AB_23_45_GH - 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, - // (in) AB - _, _, _, _, _, _, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // (in) CDe - _, _, 12, 13, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // (in) EF - 2, 3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 6, 7, - // (in) GH - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, - - /* 45 */ - // (in) AB - _, _, _, _, 12, 13, 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // [cr] crln_45_67_CD_45_EF - 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, - // (in) CD - 8, 9, 2, 3, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 12, 13, - // (in) EF - _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 2, 3, _, _, - // (in) GH - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, 12, 13, 6, 7, _, _, _, _, _, _, - - /* 67 */ - // (in) CD - 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - // [cr] crln_67_EF_67_GH - 2, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, - // (in) EF - _, _, _, _, 4, 5, 14, 15, _, _, _, _, _, _, _, _, 8, 9, 2, 3, 10, 11, _, _, _, _, _, _, _, _, _, _, - // (in) GH - _, _, _, _, _, _, _, _, 0, 1, 10, 11, 12, 13, 2, 3, _, _, _, _, _, _, 0, 1, 6, 7, 8, 9, 2, 3, 10, 11, + /* 01 */ + // [cr] crln_01_AB_CD + 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, + // (in) AB + 0, 1, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 2, 3, 4, 5, 14, 15, _, _, _, _, _, _, _, _, + // (in) CD + _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, _, _, _, _, 0, 1, 10, 11, _, _, _, _, + // [cr] crln_01_23_EF_23_CD + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, + // (in) EF + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, + + /* 23 */ + // [cr] crln_23_AB_23_45_GH + 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, + // (in) AB + _, _, _, _, _, _, 8, 9, 2, 3, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + // (in) CDe + _, _, 12, 13, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + // (in) EF + 2, 3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, 12, 13, 6, 7, + // (in) GH + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 8, 9, 2, 3, _, _, _, _, + + /* 45 */ + // (in) AB + _, _, _, _, 12, 13, 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + // [cr] crln_45_67_CD_45_EF + 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, + // (in) CD + 8, 9, 2, 3, _, _, _, _, _, _, 4, 5, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 12, 13, + // (in) EF + _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, 6, 7, _, _, _, _, _, _, _, _, 8, 9, 2, 3, _, _, + // (in) GH + _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 4, 5, 10, 11, 12, 13, 6, 7, _, _, _, _, _, _, + + /* 67 */ + // (in) CD + 6, 7, 14, 15, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, + // [cr] crln_67_EF_67_GH + 2, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, + // (in) EF + _, _, _, _, 4, 5, 14, 15, _, _, _, _, _, _, _, _, 8, 9, 2, 3, 10, 11, _, _, _, _, _, _, _, _, _, _, + // (in) GH + _, _, _, _, _, _, _, _, 0, 1, 10, 11, 12, 13, 2, 3, _, _, _, _, _, _, 0, 1, 6, 7, 8, 9, 2, 3, 10, 11, #pragma warning restore SA1515 - }; + }; - /// - /// Applies zig zag ordering for given 8x8 matrix using SSE cpu intrinsics. - /// - /// Input matrix. - public static unsafe void ApplyTransposingZigZagOrderingSsse3(ref Block8x8 block) + /// + /// Applies zig zag ordering for given 8x8 matrix using SSE cpu intrinsics. + /// + /// Input matrix. + public static unsafe void ApplyTransposingZigZagOrderingSsse3(ref Block8x8 block) + { + DebugGuard.IsTrue(Ssse3.IsSupported, "Ssse3 support is required to run this operation!"); + + fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(SseShuffleMasks)) { - DebugGuard.IsTrue(Ssse3.IsSupported, "Ssse3 support is required to run this operation!"); - - fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(SseShuffleMasks)) - { - Vector128 rowA = block.V0.AsByte(); - Vector128 rowB = block.V1.AsByte(); - Vector128 rowC = block.V2.AsByte(); - Vector128 rowD = block.V3.AsByte(); - Vector128 rowE = block.V4.AsByte(); - Vector128 rowF = block.V5.AsByte(); - Vector128 rowG = block.V6.AsByte(); - Vector128 rowH = block.V7.AsByte(); - - // row0 - A0 B0 A1 A2 B1 C0 D0 C1 - Vector128 row0_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 0))).AsInt16(); - Vector128 row0_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 1))).AsInt16(); - Vector128 row0_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 2))).AsInt16(); - Vector128 row0 = Sse2.Or(Sse2.Or(row0_A, row0_B), row0_C); - row0 = Sse2.Insert(row0.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 0), 6).AsInt16(); - - // row1 - B2 A3 A4 B3 C2 D1 E0 F0 - Vector128 row1_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 3))).AsInt16(); - Vector128 row1_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 4))).AsInt16(); - Vector128 row1 = Sse2.Or(row1_A, row1_B); - row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 2), 4).AsInt16(); - row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 1), 5).AsInt16(); - row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 0), 6).AsInt16(); - row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 0), 7).AsInt16(); - - // row2 - E1 D2 C3 B4 A5 A6 B5 C4 - Vector128 row2_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 5))).AsInt16(); - Vector128 row2_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 6))).AsInt16(); - Vector128 row2_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 7))).AsInt16(); - Vector128 row2 = Sse2.Or(Sse2.Or(row2_A, row2_B), row2_C); - row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 2), 1).AsInt16(); - row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 1), 0).AsInt16(); - - // row3 - D3 E2 F1 G0 H0 G1 F2 E3 - Vector128 row3_E = Ssse3.Shuffle(rowE, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 8))).AsInt16(); - Vector128 row3_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 9))).AsInt16(); - Vector128 row3_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 10))).AsInt16(); - Vector128 row3 = Sse2.Or(Sse2.Or(row3_E, row3_F), row3_G); - row3 = Sse2.Insert(row3.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 3), 0).AsInt16(); - row3 = Sse2.Insert(row3.AsUInt16(), Sse2.Extract(rowH.AsUInt16(), 0), 4).AsInt16(); - - // row4 - D4 C5 B6 A7 B7 C6 D5 E4 - Vector128 row4_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 11))).AsInt16(); - Vector128 row4_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 12))).AsInt16(); - Vector128 row4_D = Ssse3.Shuffle(rowD, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 13))).AsInt16(); - Vector128 row4 = Sse2.Or(Sse2.Or(row4_B, row4_C), row4_D); - row4 = Sse2.Insert(row4.AsUInt16(), Sse2.Extract(rowA.AsUInt16(), 7), 3).AsInt16(); - row4 = Sse2.Insert(row4.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 4), 7).AsInt16(); - - // row5 - F3 G2 H1 H2 G3 F4 E5 D6 - Vector128 row5_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 14))).AsInt16(); - Vector128 row5_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 15))).AsInt16(); - Vector128 row5_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 16))).AsInt16(); - Vector128 row5 = Sse2.Or(Sse2.Or(row5_F, row5_G), row5_H); - row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 6), 7).AsInt16(); - row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 5), 6).AsInt16(); - - // row6 - C7 D7 E6 F5 G4 H3 H4 G5 - Vector128 row6_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 17))).AsInt16(); - Vector128 row6_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 18))).AsInt16(); - Vector128 row6 = Sse2.Or(row6_G, row6_H); - row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 7), 0).AsInt16(); - row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 7), 1).AsInt16(); - row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 6), 2).AsInt16(); - row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 5), 3).AsInt16(); - - // row7 - F6 E7 F7 G6 H5 H6 G7 H7 - Vector128 row7_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 19))).AsInt16(); - Vector128 row7_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 20))).AsInt16(); - Vector128 row7_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 21))).AsInt16(); - Vector128 row7 = Sse2.Or(Sse2.Or(row7_F, row7_G), row7_H); - row7 = Sse2.Insert(row7.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 7), 1).AsInt16(); - - block.V0 = row0; - block.V1 = row1; - block.V2 = row2; - block.V3 = row3; - block.V4 = row4; - block.V5 = row5; - block.V6 = row6; - block.V7 = row7; - } + Vector128 rowA = block.V0.AsByte(); + Vector128 rowB = block.V1.AsByte(); + Vector128 rowC = block.V2.AsByte(); + Vector128 rowD = block.V3.AsByte(); + Vector128 rowE = block.V4.AsByte(); + Vector128 rowF = block.V5.AsByte(); + Vector128 rowG = block.V6.AsByte(); + Vector128 rowH = block.V7.AsByte(); + + // row0 - A0 B0 A1 A2 B1 C0 D0 C1 + Vector128 row0_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 0))).AsInt16(); + Vector128 row0_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 1))).AsInt16(); + Vector128 row0_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 2))).AsInt16(); + Vector128 row0 = Sse2.Or(Sse2.Or(row0_A, row0_B), row0_C); + row0 = Sse2.Insert(row0.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 0), 6).AsInt16(); + + // row1 - B2 A3 A4 B3 C2 D1 E0 F0 + Vector128 row1_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 3))).AsInt16(); + Vector128 row1_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 4))).AsInt16(); + Vector128 row1 = Sse2.Or(row1_A, row1_B); + row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 2), 4).AsInt16(); + row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 1), 5).AsInt16(); + row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 0), 6).AsInt16(); + row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 0), 7).AsInt16(); + + // row2 - E1 D2 C3 B4 A5 A6 B5 C4 + Vector128 row2_A = Ssse3.Shuffle(rowA, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 5))).AsInt16(); + Vector128 row2_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 6))).AsInt16(); + Vector128 row2_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 7))).AsInt16(); + Vector128 row2 = Sse2.Or(Sse2.Or(row2_A, row2_B), row2_C); + row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 2), 1).AsInt16(); + row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 1), 0).AsInt16(); + + // row3 - D3 E2 F1 G0 H0 G1 F2 E3 + Vector128 row3_E = Ssse3.Shuffle(rowE, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 8))).AsInt16(); + Vector128 row3_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 9))).AsInt16(); + Vector128 row3_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 10))).AsInt16(); + Vector128 row3 = Sse2.Or(Sse2.Or(row3_E, row3_F), row3_G); + row3 = Sse2.Insert(row3.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 3), 0).AsInt16(); + row3 = Sse2.Insert(row3.AsUInt16(), Sse2.Extract(rowH.AsUInt16(), 0), 4).AsInt16(); + + // row4 - D4 C5 B6 A7 B7 C6 D5 E4 + Vector128 row4_B = Ssse3.Shuffle(rowB, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 11))).AsInt16(); + Vector128 row4_C = Ssse3.Shuffle(rowC, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 12))).AsInt16(); + Vector128 row4_D = Ssse3.Shuffle(rowD, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 13))).AsInt16(); + Vector128 row4 = Sse2.Or(Sse2.Or(row4_B, row4_C), row4_D); + row4 = Sse2.Insert(row4.AsUInt16(), Sse2.Extract(rowA.AsUInt16(), 7), 3).AsInt16(); + row4 = Sse2.Insert(row4.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 4), 7).AsInt16(); + + // row5 - F3 G2 H1 H2 G3 F4 E5 D6 + Vector128 row5_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 14))).AsInt16(); + Vector128 row5_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 15))).AsInt16(); + Vector128 row5_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 16))).AsInt16(); + Vector128 row5 = Sse2.Or(Sse2.Or(row5_F, row5_G), row5_H); + row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 6), 7).AsInt16(); + row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 5), 6).AsInt16(); + + // row6 - C7 D7 E6 F5 G4 H3 H4 G5 + Vector128 row6_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 17))).AsInt16(); + Vector128 row6_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 18))).AsInt16(); + Vector128 row6 = Sse2.Or(row6_G, row6_H); + row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 7), 0).AsInt16(); + row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 7), 1).AsInt16(); + row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 6), 2).AsInt16(); + row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 5), 3).AsInt16(); + + // row7 - F6 E7 F7 G6 H5 H6 G7 H7 + Vector128 row7_F = Ssse3.Shuffle(rowF, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 19))).AsInt16(); + Vector128 row7_G = Ssse3.Shuffle(rowG, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 20))).AsInt16(); + Vector128 row7_H = Ssse3.Shuffle(rowH, Sse2.LoadVector128(shuffleVectorsPtr + (16 * 21))).AsInt16(); + Vector128 row7 = Sse2.Or(Sse2.Or(row7_F, row7_G), row7_H); + row7 = Sse2.Insert(row7.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 7), 1).AsInt16(); + + block.V0 = row0; + block.V1 = row1; + block.V2 = row2; + block.V3 = row3; + block.V4 = row4; + block.V5 = row5; + block.V6 = row6; + block.V7 = row7; } + } + + /// + /// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics. + /// + /// Input matrix. + public static unsafe void ApplyTransposingZigZagOrderingAvx2(ref Block8x8 block) + { + DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); - /// - /// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics. - /// - /// Input matrix. - public static unsafe void ApplyTransposingZigZagOrderingAvx2(ref Block8x8 block) + fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(AvxShuffleMasks)) { - DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!"); - - fixed (byte* shuffleVectorsPtr = &MemoryMarshal.GetReference(AvxShuffleMasks)) - { - Vector256 rowAB = block.V01.AsByte(); - Vector256 rowCD = block.V23.AsByte(); - Vector256 rowEF = block.V45.AsByte(); - Vector256 rowGH = block.V67.AsByte(); - - /* row01 - A0 B0 A1 A2 B1 C0 D0 C1 | B2 A3 A4 B3 C2 D1 E0 F0 */ - Vector256 crln_01_AB_CD = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32(); - Vector256 row01_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_01_AB_CD).AsByte(); - row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte(); - Vector256 row01_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_AB_CD).AsByte(); - row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (2 * 32))).AsByte(); - Vector256 crln_01_23_EF_23_CD = Avx.LoadVector256(shuffleVectorsPtr + (3 * 32)).AsInt32(); - Vector256 row01_23_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_01_23_EF_23_CD).AsByte(); - Vector256 row01_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte(); - - Vector256 row01 = Avx2.Or(row01_AB, Avx2.Or(row01_CD, row01_EF)); - - /* row23 - E1 D2 C3 B4 A5 A6 B5 C4 | D3 E2 F1 G0 H0 G1 F2 E3 */ - Vector256 crln_23_AB_23_45_GH = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32(); - Vector256 row23_45_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_23_AB_23_45_GH).AsByte(); - Vector256 row23_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte(); - Vector256 row23_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_23_EF_23_CD).AsByte(); - row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte(); - Vector256 row23_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte(); - Vector256 row23_45_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_23_AB_23_45_GH).AsByte(); - Vector256 row23_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32))).AsByte(); - - Vector256 row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH)); - - /* row45 - D4 C5 B6 A7 B7 C6 D5 E4 | F3 G2 H1 H2 G3 F4 E5 D6 */ - Vector256 row45_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32))).AsByte(); - Vector256 crln_45_67_CD_45_EF = Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsInt32(); - Vector256 row45_67_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_45_67_CD_45_EF).AsByte(); - Vector256 row45_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (12 * 32))).AsByte(); - Vector256 row45_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_45_67_CD_45_EF).AsByte(); - row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32))).AsByte(); - Vector256 row45_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32))).AsByte(); - - Vector256 row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH)); - - /* row67 - C7 D7 E6 F5 G4 H3 H4 G5 | F6 E7 F7 G6 H5 H6 G7 H7 */ - Vector256 row67_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32))).AsByte(); - Vector256 crln_67_EF_67_GH = Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsInt32(); - Vector256 row67_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_67_EF_67_GH).AsByte(); - row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32))).AsByte(); - Vector256 row67_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_67_EF_67_GH).AsByte(); - row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (18 * 32))).AsByte(); - - Vector256 row67 = Avx2.Or(row67_CD, Avx2.Or(row67_EF, row67_GH)); - - block.V01 = row01.AsInt16(); - block.V23 = row23.AsInt16(); - block.V45 = row45.AsInt16(); - block.V67 = row67.AsInt16(); - } + Vector256 rowAB = block.V01.AsByte(); + Vector256 rowCD = block.V23.AsByte(); + Vector256 rowEF = block.V45.AsByte(); + Vector256 rowGH = block.V67.AsByte(); + + /* row01 - A0 B0 A1 A2 B1 C0 D0 C1 | B2 A3 A4 B3 C2 D1 E0 F0 */ + Vector256 crln_01_AB_CD = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32(); + Vector256 row01_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_01_AB_CD).AsByte(); + row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte(); + Vector256 row01_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_AB_CD).AsByte(); + row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (2 * 32))).AsByte(); + Vector256 crln_01_23_EF_23_CD = Avx.LoadVector256(shuffleVectorsPtr + (3 * 32)).AsInt32(); + Vector256 row01_23_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_01_23_EF_23_CD).AsByte(); + Vector256 row01_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte(); + + Vector256 row01 = Avx2.Or(row01_AB, Avx2.Or(row01_CD, row01_EF)); + + /* row23 - E1 D2 C3 B4 A5 A6 B5 C4 | D3 E2 F1 G0 H0 G1 F2 E3 */ + Vector256 crln_23_AB_23_45_GH = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32(); + Vector256 row23_45_AB = Avx2.PermuteVar8x32(rowAB.AsInt32(), crln_23_AB_23_45_GH).AsByte(); + Vector256 row23_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte(); + Vector256 row23_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_01_23_EF_23_CD).AsByte(); + row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte(); + Vector256 row23_EF = Avx2.Shuffle(row01_23_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte(); + Vector256 row23_45_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_23_AB_23_45_GH).AsByte(); + Vector256 row23_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32))).AsByte(); + + Vector256 row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH)); + + /* row45 - D4 C5 B6 A7 B7 C6 D5 E4 | F3 G2 H1 H2 G3 F4 E5 D6 */ + Vector256 row45_AB = Avx2.Shuffle(row23_45_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32))).AsByte(); + Vector256 crln_45_67_CD_45_EF = Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsInt32(); + Vector256 row45_67_CD = Avx2.PermuteVar8x32(rowCD.AsInt32(), crln_45_67_CD_45_EF).AsByte(); + Vector256 row45_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (12 * 32))).AsByte(); + Vector256 row45_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_45_67_CD_45_EF).AsByte(); + row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32))).AsByte(); + Vector256 row45_GH = Avx2.Shuffle(row23_45_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32))).AsByte(); + + Vector256 row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH)); + + /* row67 - C7 D7 E6 F5 G4 H3 H4 G5 | F6 E7 F7 G6 H5 H6 G7 H7 */ + Vector256 row67_CD = Avx2.Shuffle(row45_67_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32))).AsByte(); + Vector256 crln_67_EF_67_GH = Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsInt32(); + Vector256 row67_EF = Avx2.PermuteVar8x32(rowEF.AsInt32(), crln_67_EF_67_GH).AsByte(); + row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32))).AsByte(); + Vector256 row67_GH = Avx2.PermuteVar8x32(rowGH.AsInt32(), crln_67_EF_67_GH).AsByte(); + row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (18 * 32))).AsByte(); + + Vector256 row67 = Avx2.Or(row67_CD, Avx2.Or(row67_EF, row67_GH)); + + block.V01 = row01.AsInt16(); + block.V23 = row23.AsInt16(); + block.V45 = row45.AsInt16(); + block.V67 = row67.AsInt16(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index c18333a920..7b0e096e2a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -1,68 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg.Components +internal static partial class ZigZag { - internal static partial class ZigZag + /// + /// Gets span of zig-zag ordering indices. + /// + /// + /// When reading corrupted data, the Huffman decoders could attempt + /// to reference an entry beyond the end of this array (if the decoded + /// zero run length reaches past the end of the block). To prevent + /// wild stores without adding an inner-loop test, we put some extra + /// "63"s after the real entries. This will cause the extra coefficient + /// to be stored in location 63 of the block, not somewhere random. + /// The worst case would be a run-length of 15, which means we need 16 + /// fake entries. + /// + public static ReadOnlySpan ZigZagOrder => new byte[] { - /// - /// Gets span of zig-zag ordering indices. - /// - /// - /// When reading corrupted data, the Huffman decoders could attempt - /// to reference an entry beyond the end of this array (if the decoded - /// zero run length reaches past the end of the block). To prevent - /// wild stores without adding an inner-loop test, we put some extra - /// "63"s after the real entries. This will cause the extra coefficient - /// to be stored in location 63 of the block, not somewhere random. - /// The worst case would be a run-length of 15, which means we need 16 - /// fake entries. - /// - public static ReadOnlySpan ZigZagOrder => new byte[] - { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, - // Extra entries for safety in decoder - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63 - }; + // Extra entries for safety in decoder + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63 + }; - /// - /// Gets span of zig-zag with fused transpose step ordering indices. - /// - /// - /// When reading corrupted data, the Huffman decoders could attempt - /// to reference an entry beyond the end of this array (if the decoded - /// zero run length reaches past the end of the block). To prevent - /// wild stores without adding an inner-loop test, we put some extra - /// "63"s after the real entries. This will cause the extra coefficient - /// to be stored in location 63 of the block, not somewhere random. - /// The worst case would be a run-length of 15, which means we need 16 - /// fake entries. - /// - public static ReadOnlySpan TransposingOrder => new byte[] - { - 0, 8, 1, 2, 9, 16, 24, 17, - 10, 3, 4, 11, 18, 25, 32, 40, - 33, 26, 19, 12, 5, 6, 13, 20, - 27, 34, 41, 48, 56, 49, 42, 35, - 28, 21, 14, 7, 15, 22, 29, 36, - 43, 50, 57, 58, 51, 44, 37, 30, - 23, 31, 38, 45, 52, 59, 60, 53, - 46, 39, 47, 54, 61, 62, 55, 63, + /// + /// Gets span of zig-zag with fused transpose step ordering indices. + /// + /// + /// When reading corrupted data, the Huffman decoders could attempt + /// to reference an entry beyond the end of this array (if the decoded + /// zero run length reaches past the end of the block). To prevent + /// wild stores without adding an inner-loop test, we put some extra + /// "63"s after the real entries. This will cause the extra coefficient + /// to be stored in location 63 of the block, not somewhere random. + /// The worst case would be a run-length of 15, which means we need 16 + /// fake entries. + /// + public static ReadOnlySpan TransposingOrder => new byte[] + { + 0, 8, 1, 2, 9, 16, 24, 17, + 10, 3, 4, 11, 18, 25, 32, 40, + 33, 26, 19, 12, 5, 6, 13, 20, + 27, 34, 41, 48, 56, 49, 42, 35, + 28, 21, 14, 7, 15, 22, 29, 36, + 43, 50, 57, 58, 51, 44, 37, 30, + 23, 31, 38, 45, 52, 59, 60, 53, + 46, 39, 47, 54, 61, 62, 55, 63, - // Extra entries for safety in decoder - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63 - }; - } + // Extra entries for safety in decoder + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63 + }; } diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index f9c7828caf..b7e6d5d614 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Encoder for writing the data image to a stream in jpeg format. +/// +internal interface IJpegEncoderOptions { /// - /// Encoder for writing the data image to a stream in jpeg format. + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// Defaults to 75. /// - internal interface IJpegEncoderOptions - { - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// Defaults to 75. - /// - public int? Quality { get; set; } + public int? Quality { get; set; } - /// - /// Gets or sets the component encoding mode. - /// - /// - /// Interleaved encoding mode encodes all color components in a single scan. - /// Non-interleaved encoding mode encodes each color component in a separate scan. - /// - public bool? Interleaved { get; set; } + /// + /// Gets or sets the component encoding mode. + /// + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; set; } - /// - /// Gets or sets jpeg color for encoding. - /// - public JpegEncodingColor? ColorType { get; set; } - } + /// + /// Gets or sets jpeg color for encoding. + /// + public JpegEncodingColor? ColorType { get; set; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index e98f146a20..963b8eb299 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Registers the image encoders, decoders and mime type detectors for the jpeg format. +/// +public sealed class JpegConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. - /// - public sealed class JpegConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()); - configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()); + configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs index a4ba8725e8..7a627b7b84 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConstants.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConstants.cs @@ -1,340 +1,338 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Contains jpeg constant values defined in the specification. +/// +internal static class JpegConstants { /// - /// Contains jpeg constant values defined in the specification. + /// The maximum allowable length in each dimension of a jpeg image. + /// + public const ushort MaxLength = 65535; + + /// + /// The list of mimetypes that equate to a jpeg. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; + + /// + /// The list of file extensions that equate to a jpeg. + /// + public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; + + /// + /// Contains marker specific constants. /// - internal static class JpegConstants + // ReSharper disable InconsistentNaming + internal static class Markers { /// - /// The maximum allowable length in each dimension of a jpeg image. + /// The prefix used for all markers. + /// + public const byte XFF = 0xFF; + + /// + /// Same as but of type + /// + public const int XFFInt = XFF; + + /// + /// The Start of Image marker + /// + public const byte SOI = 0xD8; + + /// + /// The End of Image marker + /// + public const byte EOI = 0xD9; + + /// + /// Application specific marker for marking the jpeg format. + /// + /// + public const byte APP0 = 0xE0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const byte APP1 = 0xE1; + + /// + /// Application specific marker for marking where to store ICC profile information. + /// + public const byte APP2 = 0xE2; + + /// + /// Application specific marker. + /// + public const byte APP3 = 0xE3; + + /// + /// Application specific marker. + /// + public const byte APP4 = 0xE4; + + /// + /// Application specific marker. /// - public const ushort MaxLength = 65535; + public const byte APP5 = 0xE5; /// - /// The list of mimetypes that equate to a jpeg. + /// Application specific marker. /// - public static readonly IEnumerable MimeTypes = new[] { "image/jpeg", "image/pjpeg" }; + public const byte APP6 = 0xE6; /// - /// The list of file extensions that equate to a jpeg. + /// Application specific marker. /// - public static readonly IEnumerable FileExtensions = new[] { "jpg", "jpeg", "jfif" }; + public const byte APP7 = 0xE7; /// - /// Contains marker specific constants. + /// Application specific marker. /// - // ReSharper disable InconsistentNaming - internal static class Markers - { - /// - /// The prefix used for all markers. - /// - public const byte XFF = 0xFF; + public const byte APP8 = 0xE8; - /// - /// Same as but of type - /// - public const int XFFInt = XFF; + /// + /// Application specific marker. + /// + public const byte APP9 = 0xE9; - /// - /// The Start of Image marker - /// - public const byte SOI = 0xD8; - - /// - /// The End of Image marker - /// - public const byte EOI = 0xD9; - - /// - /// Application specific marker for marking the jpeg format. - /// - /// - public const byte APP0 = 0xE0; - - /// - /// Application specific marker for marking where to store metadata. - /// - public const byte APP1 = 0xE1; - - /// - /// Application specific marker for marking where to store ICC profile information. - /// - public const byte APP2 = 0xE2; - - /// - /// Application specific marker. - /// - public const byte APP3 = 0xE3; - - /// - /// Application specific marker. - /// - public const byte APP4 = 0xE4; - - /// - /// Application specific marker. - /// - public const byte APP5 = 0xE5; - - /// - /// Application specific marker. - /// - public const byte APP6 = 0xE6; - - /// - /// Application specific marker. - /// - public const byte APP7 = 0xE7; - - /// - /// Application specific marker. - /// - public const byte APP8 = 0xE8; - - /// - /// Application specific marker. - /// - public const byte APP9 = 0xE9; - - /// - /// Application specific marker. - /// - public const byte APP10 = 0xEA; - - /// - /// Application specific marker. - /// - public const byte APP11 = 0xEB; - - /// - /// Application specific marker. - /// - public const byte APP12 = 0xEC; - - /// - /// Application specific marker. - /// - public const byte APP13 = 0xED; - - /// - /// Application specific marker used by Adobe for storing encoding information for DCT filters. - /// - public const byte APP14 = 0xEE; - - /// - /// Application specific marker used by GraphicConverter to store JPEG quality. - /// - public const byte APP15 = 0xEF; - - /// - /// Define arithmetic coding conditioning marker. - /// - public const byte DAC = 0xCC; - - /// - /// The text comment marker - /// - public const byte COM = 0xFE; - - /// - /// Define Quantization Table(s) marker - /// - /// Specifies one or more quantization tables. - /// - /// - public const byte DQT = 0xDB; - - /// - /// Start of Frame (baseline DCT) - /// - /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF0 = 0xC0; - - /// - /// Start Of Frame (Extended Sequential DCT) - /// - /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF1 = 0xC1; - - /// - /// Start Of Frame (progressive DCT) - /// - /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, - /// and component subsampling (e.g., 4:2:0). - /// - /// - public const byte SOF2 = 0xC2; - - /// - /// Start of Frame marker, non differential lossless, Huffman coding. - /// - public const byte SOF3 = 0xC3; - - /// - /// Start of Frame marker, differential, Huffman coding, Differential sequential DCT. - /// - public const byte SOF5 = 0xC5; - - /// - /// Start of Frame marker, differential, Huffman coding, Differential progressive DCT. - /// - public const byte SOF6 = 0xC6; - - /// - /// Start of Frame marker, differential lossless, Huffman coding. - /// - public const byte SOF7 = 0xC7; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Extended sequential DCT. - /// - public const byte SOF9 = 0xC9; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Progressive DCT. - /// - public const byte SOF10 = 0xCA; - - /// - /// Start of Frame marker, non-differential, arithmetic coding, Lossless (sequential). - /// - public const byte SOF11 = 0xCB; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential sequential DCT. - /// - public const byte SOF13 = 0xCD; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential progressive DCT. - /// - public const byte SOF14 = 0xCE; - - /// - /// Start of Frame marker, differential, arithmetic coding, Differential lossless (sequential). - /// - public const byte SOF15 = 0xCF; - - /// - /// Define Huffman Table(s) - /// - /// Specifies one or more Huffman tables. - /// - /// - public const byte DHT = 0xC4; - - /// - /// Define Restart Interval - /// - /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so - /// it can be treated like any other variable size segment. - /// - /// - public const byte DRI = 0xDD; - - /// - /// Start of Scan - /// - /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. - /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it - /// will contain, and is immediately followed by entropy-coded data. - /// - /// - public const byte SOS = 0xDA; - - /// - /// Define First Restart - /// - /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. - /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. - /// - /// - public const byte RST0 = 0xD0; - - /// - /// Define Eigth Restart - /// - /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. - /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. - /// - /// - public const byte RST7 = 0xD7; - } - - /// - /// Contains Adobe specific constants. - /// - internal static class Adobe - { - /// - /// The color transform is unknown.(RGB or CMYK) - /// - public const byte ColorTransformUnknown = 0; - - /// - /// The color transform is YCbCr (luminance, red chroma, blue chroma) - /// - public const byte ColorTransformYCbCr = 1; - - /// - /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) - /// - public const byte ColorTransformYcck = 2; - } - - /// - /// Contains Huffman specific constants. - /// - internal static class Huffman - { - /// - /// The size of the huffman decoder register. - /// - public const int RegisterSize = 64; - - /// - /// The number of bits to fetch when filling the buffer. - /// - public const int FetchBits = 48; - - /// - /// The number of times to read the input stream when filling the buffer. - /// - public const int FetchLoop = FetchBits / 8; - - /// - /// The minimum number of bits allowed before by the before fetching. - /// - public const int MinBits = RegisterSize - FetchBits; - - /// - /// If the next Huffman code is no more than this number of bits, we can obtain its length - /// and the corresponding symbol directly from this tables. - /// - public const int LookupBits = 8; - - /// - /// If a Huffman code is this number of bits we cannot use the lookup table to determine its value. - /// - public const int SlowBits = LookupBits + 1; - - /// - /// The size of the lookup table. - /// - public const int LookupSize = 1 << LookupBits; - } + /// + /// Application specific marker. + /// + public const byte APP10 = 0xEA; + + /// + /// Application specific marker. + /// + public const byte APP11 = 0xEB; + + /// + /// Application specific marker. + /// + public const byte APP12 = 0xEC; + + /// + /// Application specific marker. + /// + public const byte APP13 = 0xED; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const byte APP14 = 0xEE; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const byte APP15 = 0xEF; + + /// + /// Define arithmetic coding conditioning marker. + /// + public const byte DAC = 0xCC; + + /// + /// The text comment marker + /// + public const byte COM = 0xFE; + + /// + /// Define Quantization Table(s) marker + /// + /// Specifies one or more quantization tables. + /// + /// + public const byte DQT = 0xDB; + + /// + /// Start of Frame (baseline DCT) + /// + /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF0 = 0xC0; + + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF1 = 0xC1; + + /// + /// Start Of Frame (progressive DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF2 = 0xC2; + + /// + /// Start of Frame marker, non differential lossless, Huffman coding. + /// + public const byte SOF3 = 0xC3; + + /// + /// Start of Frame marker, differential, Huffman coding, Differential sequential DCT. + /// + public const byte SOF5 = 0xC5; + + /// + /// Start of Frame marker, differential, Huffman coding, Differential progressive DCT. + /// + public const byte SOF6 = 0xC6; + + /// + /// Start of Frame marker, differential lossless, Huffman coding. + /// + public const byte SOF7 = 0xC7; + + /// + /// Start of Frame marker, non-differential, arithmetic coding, Extended sequential DCT. + /// + public const byte SOF9 = 0xC9; + + /// + /// Start of Frame marker, non-differential, arithmetic coding, Progressive DCT. + /// + public const byte SOF10 = 0xCA; + + /// + /// Start of Frame marker, non-differential, arithmetic coding, Lossless (sequential). + /// + public const byte SOF11 = 0xCB; + + /// + /// Start of Frame marker, differential, arithmetic coding, Differential sequential DCT. + /// + public const byte SOF13 = 0xCD; + + /// + /// Start of Frame marker, differential, arithmetic coding, Differential progressive DCT. + /// + public const byte SOF14 = 0xCE; + + /// + /// Start of Frame marker, differential, arithmetic coding, Differential lossless (sequential). + /// + public const byte SOF15 = 0xCF; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const byte DHT = 0xC4; + + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks.This marker is followed by two bytes indicating the fixed size so + /// it can be treated like any other variable size segment. + /// + /// + public const byte DRI = 0xDD; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const byte SOS = 0xDA; + + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST0 = 0xD0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST7 = 0xD7; + } + + /// + /// Contains Adobe specific constants. + /// + internal static class Adobe + { + /// + /// The color transform is unknown.(RGB or CMYK) + /// + public const byte ColorTransformUnknown = 0; + + /// + /// The color transform is YCbCr (luminance, red chroma, blue chroma) + /// + public const byte ColorTransformYCbCr = 1; + + /// + /// The color transform is YCCK (luminance, red chroma, blue chroma, keyline) + /// + public const byte ColorTransformYcck = 2; + } + + /// + /// Contains Huffman specific constants. + /// + internal static class Huffman + { + /// + /// The size of the huffman decoder register. + /// + public const int RegisterSize = 64; + + /// + /// The number of bits to fetch when filling the buffer. + /// + public const int FetchBits = 48; + + /// + /// The number of times to read the input stream when filling the buffer. + /// + public const int FetchLoop = FetchBits / 8; + + /// + /// The minimum number of bits allowed before by the before fetching. + /// + public const int MinBits = RegisterSize - FetchBits; + + /// + /// If the next Huffman code is no more than this number of bits, we can obtain its length + /// and the corresponding symbol directly from this tables. + /// + public const int LookupBits = 8; + + /// + /// If a Huffman code is this number of bits we cannot use the lookup table to determine its value. + /// + public const int SlowBits = LookupBits + 1; + + /// + /// The size of the lookup table. + /// + public const int LookupSize = 1 << LookupBits; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 6267fd099a..7e85ddad5c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Decoder for generating an image out of a jpeg encoded stream. +/// +public sealed class JpegDecoder : IImageDecoderSpecialized { - /// - /// Decoder for generating an image out of a jpeg encoded stream. - /// - public sealed class JpegDecoder : IImageDecoderSpecialized + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - using JpegDecoderCore decoder = new(new() { GeneralOptions = options }); - return decoder.Identify(options.Configuration, stream, cancellationToken); - } + using JpegDecoderCore decoder = new(new() { GeneralOptions = options }); + return decoder.Identify(options.Configuration, stream, cancellationToken); + } - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - /// - Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - using JpegDecoderCore decoder = new(options); - Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + /// + Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly) - { - ImageDecoderUtilities.Resize(options.GeneralOptions, image); - } + using JpegDecoderCore decoder = new(options); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); - return image; + if (options.ResizeMode != JpegDecoderResizeMode.IdctOnly) + { + ImageDecoderUtilities.Resize(options.GeneralOptions, image); } - /// - Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoderSpecialized.Decode(JpegDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 50c08eb771..11a9bc5578 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1,14 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -21,1485 +17,1484 @@ using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Performs the jpeg decoding operation. +/// Originally ported from +/// with additional fixes for both performance and common encoding errors. +/// +internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals { /// - /// Performs the jpeg decoding operation. - /// Originally ported from - /// with additional fixes for both performance and common encoding errors. + /// The only supported precision + /// + private readonly byte[] supportedPrecisions = { 8, 12 }; + + /// + /// The buffer used to temporarily store bytes read from the stream. + /// + private readonly byte[] temp = new byte[2 * 16 * 4]; + + /// + /// The buffer used to read markers from the stream. + /// + private readonly byte[] markerBuffer = new byte[2]; + + /// + /// Whether the image has an EXIF marker. + /// + private bool hasExif; + + /// + /// Contains exif data. + /// + private byte[] exifData; + + /// + /// Whether the image has an ICC marker. + /// + private bool hasIcc; + + /// + /// Contains ICC data. + /// + private byte[] iccData; + + /// + /// Whether the image has a IPTC data. + /// + private bool hasIptc; + + /// + /// Contains IPTC data. /// - internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals + private byte[] iptcData; + + /// + /// Whether the image has a XMP data. + /// + private bool hasXmp; + + /// + /// Contains XMP data. + /// + private byte[] xmpData; + + /// + /// Whether the image has a APP14 adobe marker. This is needed to determine image encoded colorspace. + /// + private bool hasAdobeMarker; + + /// + /// Contains information about the JFIF marker. + /// + private JFifMarker jFif; + + /// + /// Contains information about the Adobe marker. + /// + private AdobeMarker adobe; + + /// + /// Scan decoder. + /// + private IJpegScanDecoder scanDecoder; + + /// + /// The arithmetic decoding tables. + /// + private List arithmeticDecodingTables; + + /// + /// The restart interval. + /// + private int? resetInterval; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Whether to skip metadata during decode. + /// + private readonly bool skipMetadata; + + /// + /// The jpeg specific resize options. + /// + private readonly JpegDecoderResizeMode resizeMode; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public JpegDecoderCore(JpegDecoderOptions options) { - /// - /// The only supported precision - /// - private readonly byte[] supportedPrecisions = { 8, 12 }; - - /// - /// The buffer used to temporarily store bytes read from the stream. - /// - private readonly byte[] temp = new byte[2 * 16 * 4]; - - /// - /// The buffer used to read markers from the stream. - /// - private readonly byte[] markerBuffer = new byte[2]; - - /// - /// Whether the image has an EXIF marker. - /// - private bool hasExif; - - /// - /// Contains exif data. - /// - private byte[] exifData; - - /// - /// Whether the image has an ICC marker. - /// - private bool hasIcc; - - /// - /// Contains ICC data. - /// - private byte[] iccData; - - /// - /// Whether the image has a IPTC data. - /// - private bool hasIptc; - - /// - /// Contains IPTC data. - /// - private byte[] iptcData; - - /// - /// Whether the image has a XMP data. - /// - private bool hasXmp; - - /// - /// Contains XMP data. - /// - private byte[] xmpData; - - /// - /// Whether the image has a APP14 adobe marker. This is needed to determine image encoded colorspace. - /// - private bool hasAdobeMarker; - - /// - /// Contains information about the JFIF marker. - /// - private JFifMarker jFif; - - /// - /// Contains information about the Adobe marker. - /// - private AdobeMarker adobe; - - /// - /// Scan decoder. - /// - private IJpegScanDecoder scanDecoder; - - /// - /// The arithmetic decoding tables. - /// - private List arithmeticDecodingTables; - - /// - /// The restart interval. - /// - private int? resetInterval; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Whether to skip metadata during decode. - /// - private readonly bool skipMetadata; - - /// - /// The jpeg specific resize options. - /// - private readonly JpegDecoderResizeMode resizeMode; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public JpegDecoderCore(JpegDecoderOptions options) - { - this.Options = options.GeneralOptions; - this.resizeMode = options.ResizeMode; - this.configuration = options.GeneralOptions.Configuration; - this.skipMetadata = options.GeneralOptions.SkipMetadata; - } + this.Options = options.GeneralOptions; + this.resizeMode = options.ResizeMode; + this.configuration = options.GeneralOptions.Configuration; + this.skipMetadata = options.GeneralOptions.SkipMetadata; + } - /// - public DecoderOptions Options { get; } + /// + public DecoderOptions Options { get; } - /// - public Size Dimensions => this.Frame.PixelSize; + /// + public Size Dimensions => this.Frame.PixelSize; - /// - /// Gets the frame - /// - public JpegFrame Frame { get; private set; } + /// + /// Gets the frame + /// + public JpegFrame Frame { get; private set; } - /// - /// Gets the decoded by this decoder instance. - /// - public ImageMetadata Metadata { get; private set; } + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetadata Metadata { get; private set; } - /// - public JpegColorSpace ColorSpace { get; private set; } + /// + public JpegColorSpace ColorSpace { get; private set; } - /// - /// Gets the components. - /// - public JpegComponent[] Components => this.Frame.Components; + /// + /// Gets the components. + /// + public JpegComponent[] Components => this.Frame.Components; - /// - JpegComponent[] IRawJpegData.Components => this.Components; + /// + JpegComponent[] IRawJpegData.Components => this.Components; - /// - public Block8x8F[] QuantizationTables { get; private set; } + /// + public Block8x8F[] QuantizationTables { get; private set; } - /// - /// Finds the next file marker within the byte stream. - /// - /// The input stream. - /// The . - public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream) + /// + /// Finds the next file marker within the byte stream. + /// + /// The input stream. + /// The . + public static JpegFileMarker FindNextFileMarker(BufferedReadStream stream) + { + while (true) { - while (true) + int b = stream.ReadByte(); + if (b == -1) { - int b = stream.ReadByte(); - if (b == -1) - { - return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); - } + return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); + } - // Found a marker. - if (b == JpegConstants.Markers.XFF) + // Found a marker. + if (b == JpegConstants.Markers.XFF) + { + while (b == JpegConstants.Markers.XFF) { - while (b == JpegConstants.Markers.XFF) + // Loop here to discard any padding FF bytes on terminating marker. + b = stream.ReadByte(); + if (b == -1) { - // Loop here to discard any padding FF bytes on terminating marker. - b = stream.ReadByte(); - if (b == -1) - { - return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); - } + return new JpegFileMarker(JpegConstants.Markers.EOI, stream.Length - 2); } + } - // Found a valid marker. Exit loop - if (b is not 0 and (< JpegConstants.Markers.RST0 or > JpegConstants.Markers.RST7)) - { - return new JpegFileMarker((byte)(uint)b, stream.Position - 2); - } + // Found a valid marker. Exit loop + if (b is not 0 and (< JpegConstants.Markers.RST0 or > JpegConstants.Markers.RST7)) + { + return new JpegFileMarker((byte)(uint)b, stream.Position - 2); } } } + } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using SpectralConverter spectralConverter = new(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize); + this.ParseStream(stream, spectralConverter, cancellationToken); + this.InitExifProfile(); + this.InitIccProfile(); + this.InitIptcProfile(); + this.InitXmpProfile(); + this.InitDerivedMetadataProperties(); + + return new Image( + this.configuration, + spectralConverter.GetPixelBuffer(cancellationToken), + this.Metadata); + } - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ParseStream(stream, spectralConverter: null, cancellationToken); + this.InitExifProfile(); + this.InitIccProfile(); + this.InitIptcProfile(); + this.InitXmpProfile(); + this.InitDerivedMetadataProperties(); + + Size pixelSize = this.Frame.PixelSize; + return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); + } + + /// + /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, + /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). + /// + /// The table bytes. + /// The scan decoder. + public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder) + { + this.Metadata = new ImageMetadata(); + this.QuantizationTables = new Block8x8F[4]; + this.scanDecoder = scanDecoder; + if (tableBytes.Length < 4) { - using SpectralConverter spectralConverter = new(this.configuration, this.resizeMode == JpegDecoderResizeMode.ScaleOnly ? null : this.Options.TargetSize); - this.ParseStream(stream, spectralConverter, cancellationToken); - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); - - return new Image( - this.configuration, - spectralConverter.GetPixelBuffer(cancellationToken), - this.Metadata); + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + using MemoryStream ms = new(tableBytes); + using BufferedReadStream stream = new(this.configuration, ms); + + // Check for the Start Of Image marker. + int bytesRead = stream.Read(this.markerBuffer, 0, 2); + JpegFileMarker fileMarker = new(this.markerBuffer[1], 0); + if (fileMarker.Marker != JpegConstants.Markers.SOI) { - this.ParseStream(stream, spectralConverter: null, cancellationToken); - this.InitExifProfile(); - this.InitIccProfile(); - this.InitIptcProfile(); - this.InitXmpProfile(); - this.InitDerivedMetadataProperties(); - - Size pixelSize = this.Frame.PixelSize; - return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); + JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); } - /// - /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's, - /// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips). - /// - /// The table bytes. - /// The scan decoder. - public void LoadTables(byte[] tableBytes, IJpegScanDecoder scanDecoder) + // Read next marker. + bytesRead = stream.Read(this.markerBuffer, 0, 2); + fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2); + + while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { - this.Metadata = new ImageMetadata(); - this.QuantizationTables = new Block8x8F[4]; - this.scanDecoder = scanDecoder; - if (tableBytes.Length < 4) + if (!fileMarker.Invalid) { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); - } + // Get the marker length. + int markerContentByteSize = this.ReadUint16(stream) - 2; - using MemoryStream ms = new(tableBytes); - using BufferedReadStream stream = new(this.configuration, ms); + // Check whether stream actually has enought bytes to read + // markerContentByteSize is always positive so we cast + // to uint to avoid sign extension + if (stream.RemainingBytes < (uint)markerContentByteSize) + { + JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); + } - // Check for the Start Of Image marker. - int bytesRead = stream.Read(this.markerBuffer, 0, 2); - JpegFileMarker fileMarker = new(this.markerBuffer[1], 0); - if (fileMarker.Marker != JpegConstants.Markers.SOI) - { - JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); + switch (fileMarker.Marker) + { + case JpegConstants.Markers.SOI: + case JpegConstants.Markers.RST0: + case JpegConstants.Markers.RST7: + break; + case JpegConstants.Markers.DHT: + this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); + break; + case JpegConstants.Markers.DQT: + this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); + break; + case JpegConstants.Markers.DRI: + this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); + break; + case JpegConstants.Markers.EOI: + return; + } } // Read next marker. bytesRead = stream.Read(this.markerBuffer, 0, 2); - fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2); - - while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) + if (bytesRead != 2) { - if (!fileMarker.Invalid) - { - // Get the marker length. - int markerContentByteSize = this.ReadUint16(stream) - 2; + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); + } - // Check whether stream actually has enought bytes to read - // markerContentByteSize is always positive so we cast - // to uint to avoid sign extension - if (stream.RemainingBytes < (uint)markerContentByteSize) - { - JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); - } + fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); + } + } - switch (fileMarker.Marker) - { - case JpegConstants.Markers.SOI: - case JpegConstants.Markers.RST0: - case JpegConstants.Markers.RST7: - break; - case JpegConstants.Markers.DHT: - this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); - break; - case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); - break; - case JpegConstants.Markers.DRI: - this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); - break; - case JpegConstants.Markers.EOI: - return; - } - } + /// + /// Parses the input stream for file markers. + /// + /// The input stream. + /// The spectral converter to use. + /// The token to monitor cancellation. + internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken) + { + bool metadataOnly = spectralConverter == null; - // Read next marker. - bytesRead = stream.Read(this.markerBuffer, 0, 2); - if (bytesRead != 2) - { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker"); - } + this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); - fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); - } - } + this.Metadata = new ImageMetadata(); - /// - /// Parses the input stream for file markers. - /// - /// The input stream. - /// The spectral converter to use. - /// The token to monitor cancellation. - internal void ParseStream(BufferedReadStream stream, SpectralConverter spectralConverter, CancellationToken cancellationToken) + // Check for the Start Of Image marker. + stream.Read(this.markerBuffer, 0, 2); + JpegFileMarker fileMarker = new(this.markerBuffer[1], 0); + if (fileMarker.Marker != JpegConstants.Markers.SOI) { - bool metadataOnly = spectralConverter == null; + JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); + } - this.scanDecoder ??= new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); + fileMarker = FindNextFileMarker(stream); + this.QuantizationTables ??= new Block8x8F[4]; - this.Metadata = new ImageMetadata(); + // Break only when we discover a valid EOI marker. + // https://github.com/SixLabors/ImageSharp/issues/695 + while (fileMarker.Marker != JpegConstants.Markers.EOI) + { + cancellationToken.ThrowIfCancellationRequested(); - // Check for the Start Of Image marker. - stream.Read(this.markerBuffer, 0, 2); - JpegFileMarker fileMarker = new(this.markerBuffer[1], 0); - if (fileMarker.Marker != JpegConstants.Markers.SOI) + if (!fileMarker.Invalid) { - JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); - } - - fileMarker = FindNextFileMarker(stream); - this.QuantizationTables ??= new Block8x8F[4]; + // Get the marker length. + int markerContentByteSize = this.ReadUint16(stream) - 2; - // Break only when we discover a valid EOI marker. - // https://github.com/SixLabors/ImageSharp/issues/695 - while (fileMarker.Marker != JpegConstants.Markers.EOI) - { - cancellationToken.ThrowIfCancellationRequested(); + // Check whether stream actually has enough bytes to read + // markerContentByteSize is always positive so we cast + // to uint to avoid sign extension. + if (stream.RemainingBytes < (uint)markerContentByteSize) + { + JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); + } - if (!fileMarker.Invalid) + switch (fileMarker.Marker) { - // Get the marker length. - int markerContentByteSize = this.ReadUint16(stream) - 2; + case JpegConstants.Markers.SOF0: + case JpegConstants.Markers.SOF1: + case JpegConstants.Markers.SOF2: - // Check whether stream actually has enough bytes to read - // markerContentByteSize is always positive so we cast - // to uint to avoid sign extension. - if (stream.RemainingBytes < (uint)markerContentByteSize) - { - JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker); - } + this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly); + break; - switch (fileMarker.Marker) - { - case JpegConstants.Markers.SOF0: - case JpegConstants.Markers.SOF1: - case JpegConstants.Markers.SOF2: + case JpegConstants.Markers.SOF9: + case JpegConstants.Markers.SOF10: + case JpegConstants.Markers.SOF13: + case JpegConstants.Markers.SOF14: + this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken); + if (this.resetInterval.HasValue) + { + this.scanDecoder.ResetInterval = this.resetInterval.Value; + } - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Huffman, metadataOnly); - break; + this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly); + break; - case JpegConstants.Markers.SOF9: - case JpegConstants.Markers.SOF10: - case JpegConstants.Markers.SOF13: - case JpegConstants.Markers.SOF14: - this.scanDecoder = new ArithmeticScanDecoder(stream, spectralConverter, cancellationToken); - if (this.resetInterval.HasValue) - { - this.scanDecoder.ResetInterval = this.resetInterval.Value; - } - - this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, ComponentType.Arithmetic, metadataOnly); - break; + case JpegConstants.Markers.SOF5: + JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported."); + break; - case JpegConstants.Markers.SOF5: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported."); - break; + case JpegConstants.Markers.SOF6: + JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported."); + break; - case JpegConstants.Markers.SOF6: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported."); - break; + case JpegConstants.Markers.SOF3: + case JpegConstants.Markers.SOF7: + JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); + break; - case JpegConstants.Markers.SOF3: - case JpegConstants.Markers.SOF7: - JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported."); - break; + case JpegConstants.Markers.SOF11: + case JpegConstants.Markers.SOF15: + JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported."); + break; - case JpegConstants.Markers.SOF11: - case JpegConstants.Markers.SOF15: - JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with lossless arithmetic coding is not supported."); + case JpegConstants.Markers.SOS: + if (!metadataOnly) + { + this.ProcessStartOfScanMarker(stream, markerContentByteSize); break; + } - case JpegConstants.Markers.SOS: - if (!metadataOnly) - { - this.ProcessStartOfScanMarker(stream, markerContentByteSize); - break; - } + // It's highly unlikely that APPn related data will be found after the SOS marker + // We should have gathered everything we need by now. + return; - // It's highly unlikely that APPn related data will be found after the SOS marker - // We should have gathered everything we need by now. - return; + case JpegConstants.Markers.DHT: - case JpegConstants.Markers.DHT: + if (metadataOnly) + { + stream.Skip(markerContentByteSize); + } + else + { + this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); + } - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize); - } + break; - break; + case JpegConstants.Markers.DQT: + this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.DQT: - this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.DRI: + if (metadataOnly) + { + stream.Skip(markerContentByteSize); + } + else + { + this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); + } - case JpegConstants.Markers.DRI: - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize); - } + break; - break; + case JpegConstants.Markers.APP0: + this.ProcessApplicationHeaderMarker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.APP0: - this.ProcessApplicationHeaderMarker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.APP1: + this.ProcessApp1Marker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.APP2: + this.ProcessApp2Marker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.APP2: - this.ProcessApp2Marker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.APP3: + case JpegConstants.Markers.APP4: + case JpegConstants.Markers.APP5: + case JpegConstants.Markers.APP6: + case JpegConstants.Markers.APP7: + case JpegConstants.Markers.APP8: + case JpegConstants.Markers.APP9: + case JpegConstants.Markers.APP10: + case JpegConstants.Markers.APP11: + case JpegConstants.Markers.APP12: + stream.Skip(markerContentByteSize); + break; - case JpegConstants.Markers.APP3: - case JpegConstants.Markers.APP4: - case JpegConstants.Markers.APP5: - case JpegConstants.Markers.APP6: - case JpegConstants.Markers.APP7: - case JpegConstants.Markers.APP8: - case JpegConstants.Markers.APP9: - case JpegConstants.Markers.APP10: - case JpegConstants.Markers.APP11: - case JpegConstants.Markers.APP12: - stream.Skip(markerContentByteSize); - break; + case JpegConstants.Markers.APP13: + this.ProcessApp13Marker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.APP13: - this.ProcessApp13Marker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.APP14: + this.ProcessApp14Marker(stream, markerContentByteSize); + break; - case JpegConstants.Markers.APP14: - this.ProcessApp14Marker(stream, markerContentByteSize); - break; + case JpegConstants.Markers.APP15: + case JpegConstants.Markers.COM: + stream.Skip(markerContentByteSize); + break; - case JpegConstants.Markers.APP15: - case JpegConstants.Markers.COM: + case JpegConstants.Markers.DAC: + if (metadataOnly) + { stream.Skip(markerContentByteSize); - break; - - case JpegConstants.Markers.DAC: - if (metadataOnly) - { - stream.Skip(markerContentByteSize); - } - else - { - this.ProcessArithmeticTable(stream, markerContentByteSize); - } + } + else + { + this.ProcessArithmeticTable(stream, markerContentByteSize); + } - break; - } + break; } - - // Read on. - fileMarker = FindNextFileMarker(stream); } - this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; + // Read on. + fileMarker = FindNextFileMarker(stream); } - /// - public void Dispose() - { - this.Frame?.Dispose(); - - // Set large fields to null. - this.Frame = null; - this.scanDecoder = null; - } + this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved; + } - /// - /// Returns encoded colorspace based on the adobe APP14 marker. - /// - /// Number of components. - /// Parsed adobe APP14 marker. - /// The - internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) - { - if (componentCount == 1) - { - return JpegColorSpace.Grayscale; - } + /// + public void Dispose() + { + this.Frame?.Dispose(); - if (componentCount == 3) - { - if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) - { - return JpegColorSpace.RGB; - } + // Set large fields to null. + this.Frame = null; + this.scanDecoder = null; + } - return JpegColorSpace.YCbCr; - } + /// + /// Returns encoded colorspace based on the adobe APP14 marker. + /// + /// Number of components. + /// Parsed adobe APP14 marker. + /// The + internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount, ref AdobeMarker adobeMarker) + { + if (componentCount == 1) + { + return JpegColorSpace.Grayscale; + } - if (componentCount == 4) + if (componentCount == 3) + { + if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) { - if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) - { - return JpegColorSpace.Ycck; - } - - return JpegColorSpace.Cmyk; + return JpegColorSpace.RGB; } - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - return default; + return JpegColorSpace.YCbCr; } - /// - /// Returns encoded colorspace based on the component count. - /// - /// Number of components. - /// The - internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) + if (componentCount == 4) { - if (componentCount == 1) + if (adobeMarker.ColorTransform == JpegConstants.Adobe.ColorTransformYcck) { - return JpegColorSpace.Grayscale; + return JpegColorSpace.Ycck; } - if (componentCount == 3) - { - return JpegColorSpace.YCbCr; - } + return JpegColorSpace.Cmyk; + } - if (componentCount == 4) - { - return JpegColorSpace.Cmyk; - } + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); + return default; + } - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - return default; + /// + /// Returns encoded colorspace based on the component count. + /// + /// Number of components. + /// The + internal static JpegColorSpace DeduceJpegColorSpace(byte componentCount) + { + if (componentCount == 1) + { + return JpegColorSpace.Grayscale; } - /// - /// Returns the jpeg color type based on the colorspace and subsampling used. - /// - /// Jpeg color type. - private JpegEncodingColor DeduceJpegColorType() + if (componentCount == 3) { - switch (this.ColorSpace) - { - case JpegColorSpace.Grayscale: - return JpegEncodingColor.Luminance; + return JpegColorSpace.YCbCr; + } - case JpegColorSpace.RGB: - return JpegEncodingColor.Rgb; + if (componentCount == 4) + { + return JpegColorSpace.Cmyk; + } - case JpegColorSpace.YCbCr: - if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegEncodingColor.YCbCrRatio444; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegEncodingColor.YCbCrRatio422; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegEncodingColor.YCbCrRatio420; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegEncodingColor.YCbCrRatio411; - } - else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && - this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && - this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) - { - return JpegEncodingColor.YCbCrRatio410; - } - else - { - return JpegEncodingColor.YCbCrRatio420; - } + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); + return default; + } - case JpegColorSpace.Cmyk: - return JpegEncodingColor.Cmyk; - case JpegColorSpace.Ycck: - return JpegEncodingColor.Ycck; - default: + /// + /// Returns the jpeg color type based on the colorspace and subsampling used. + /// + /// Jpeg color type. + private JpegEncodingColor DeduceJpegColorType() + { + switch (this.ColorSpace) + { + case JpegColorSpace.Grayscale: + return JpegEncodingColor.Luminance; + + case JpegColorSpace.RGB: + return JpegEncodingColor.Rgb; + + case JpegColorSpace.YCbCr: + if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) + { + return JpegEncodingColor.YCbCrRatio444; + } + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 1 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) + { + return JpegEncodingColor.YCbCrRatio422; + } + else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) + { return JpegEncodingColor.YCbCrRatio420; - } + } + else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) + { + return JpegEncodingColor.YCbCrRatio411; + } + else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 && + this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 && + this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1) + { + return JpegEncodingColor.YCbCrRatio410; + } + else + { + return JpegEncodingColor.YCbCrRatio420; + } + + case JpegColorSpace.Cmyk: + return JpegEncodingColor.Cmyk; + case JpegColorSpace.Ycck: + return JpegEncodingColor.Ycck; + default: + return JpegEncodingColor.YCbCrRatio420; } + } - /// - /// Initializes the EXIF profile. - /// - private void InitExifProfile() + /// + /// Initializes the EXIF profile. + /// + private void InitExifProfile() + { + if (this.hasExif) { - if (this.hasExif) - { - this.Metadata.ExifProfile = new ExifProfile(this.exifData); - } + this.Metadata.ExifProfile = new ExifProfile(this.exifData); } + } - /// - /// Initializes the ICC profile. - /// - private void InitIccProfile() + /// + /// Initializes the ICC profile. + /// + private void InitIccProfile() + { + if (this.hasIcc) { - if (this.hasIcc) + IccProfile profile = new(this.iccData); + if (profile.CheckIsValid()) { - IccProfile profile = new(this.iccData); - if (profile.CheckIsValid()) - { - this.Metadata.IccProfile = profile; - } + this.Metadata.IccProfile = profile; } } + } - /// - /// Initializes the IPTC profile. - /// - private void InitIptcProfile() + /// + /// Initializes the IPTC profile. + /// + private void InitIptcProfile() + { + if (this.hasIptc) { - if (this.hasIptc) - { - this.Metadata.IptcProfile = new IptcProfile(this.iptcData); - } + this.Metadata.IptcProfile = new IptcProfile(this.iptcData); + } + } + + /// + /// Initializes the XMP profile. + /// + private void InitXmpProfile() + { + if (this.hasXmp) + { + this.Metadata.XmpProfile = new XmpProfile(this.xmpData); } + } - /// - /// Initializes the XMP profile. - /// - private void InitXmpProfile() + /// + /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. + /// + private void InitDerivedMetadataProperties() + { + if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) + { + this.Metadata.HorizontalResolution = this.jFif.XDensity; + this.Metadata.VerticalResolution = this.jFif.YDensity; + this.Metadata.ResolutionUnits = this.jFif.DensityUnits; + } + else if (this.hasExif) { - if (this.hasXmp) + double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); + double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); + + if (horizontalValue > 0 && verticalValue > 0) { - this.Metadata.XmpProfile = new XmpProfile(this.xmpData); + this.Metadata.HorizontalResolution = horizontalValue; + this.Metadata.VerticalResolution = verticalValue; + this.Metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.Metadata.ExifProfile); } } + } + + private double GetExifResolutionValue(ExifTag tag) + { + IExifValue resolution = this.Metadata.ExifProfile.GetValue(tag); + + return resolution is null ? 0 : resolution.Value.ToDouble(); + } + + /// + /// Extends the profile with additional data. + /// + /// The profile data array. + /// The array containing addition profile data. + private static void ExtendProfile(ref byte[] profile, byte[] extension) + { + int currentLength = profile.Length; + + Array.Resize(ref profile, currentLength + extension.Length); + Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length); + } + + /// + /// Processes the application header containing the JFIF identifier plus extra data. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) + { + // We can only decode JFif identifiers. + // Some images contain multiple JFIF markers (Issue 1932) so we check to see + // if it's already been read. + if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) + { + // Skip the application header length + stream.Skip(remaining); + return; + } - /// - /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. - /// - private void InitDerivedMetadataProperties() + stream.Read(this.temp, 0, JFifMarker.Length); + if (!JFifMarker.TryParse(this.temp, out this.jFif)) { - if (this.jFif.XDensity > 0 && this.jFif.YDensity > 0) + JpegThrowHelper.ThrowNotSupportedException("Unknown App0 Marker - Expected JFIF."); + } + + remaining -= JFifMarker.Length; + + // TODO: thumbnail + if (remaining > 0) + { + if (stream.Position + remaining >= stream.Length) { - this.Metadata.HorizontalResolution = this.jFif.XDensity; - this.Metadata.VerticalResolution = this.jFif.YDensity; - this.Metadata.ResolutionUnits = this.jFif.DensityUnits; + JpegThrowHelper.ThrowInvalidImageContentException("Bad App0 Marker length."); } - else if (this.hasExif) - { - double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); - double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); - if (horizontalValue > 0 && verticalValue > 0) - { - this.Metadata.HorizontalResolution = horizontalValue; - this.Metadata.VerticalResolution = verticalValue; - this.Metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(this.Metadata.ExifProfile); - } - } + stream.Skip(remaining); } + } - private double GetExifResolutionValue(ExifTag tag) + /// + /// Processes the App1 marker retrieving any stored metadata. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessApp1Marker(BufferedReadStream stream, int remaining) + { + const int exifMarkerLength = 6; + const int xmpMarkerLength = 29; + if (remaining < exifMarkerLength || this.skipMetadata) { - IExifValue resolution = this.Metadata.ExifProfile.GetValue(tag); - - return resolution is null ? 0 : resolution.Value.ToDouble(); + // Skip the application header length. + stream.Skip(remaining); + return; } - /// - /// Extends the profile with additional data. - /// - /// The profile data array. - /// The array containing addition profile data. - private static void ExtendProfile(ref byte[] profile, byte[] extension) + if (stream.Position + remaining >= stream.Length) { - int currentLength = profile.Length; - - Array.Resize(ref profile, currentLength + extension.Length); - Buffer.BlockCopy(extension, 0, profile, currentLength, extension.Length); + JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - /// - /// Processes the application header containing the JFIF identifier plus extra data. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) + // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes. + stream.Read(this.temp, 0, exifMarkerLength); + remaining -= exifMarkerLength; + + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { - // We can only decode JFif identifiers. - // Some images contain multiple JFIF markers (Issue 1932) so we check to see - // if it's already been read. - if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) - { - // Skip the application header length - stream.Skip(remaining); - return; - } + this.hasExif = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); - stream.Read(this.temp, 0, JFifMarker.Length); - if (!JFifMarker.TryParse(this.temp, out this.jFif)) + if (this.exifData is null) { - JpegThrowHelper.ThrowNotSupportedException("Unknown App0 Marker - Expected JFIF."); + this.exifData = profile; } - - remaining -= JFifMarker.Length; - - // TODO: thumbnail - if (remaining > 0) + else { - if (stream.Position + remaining >= stream.Length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App0 Marker length."); - } - - stream.Skip(remaining); + // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers. + ExtendProfile(ref this.exifData, profile); } + + remaining = 0; } - /// - /// Processes the App1 marker retrieving any stored metadata. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp1Marker(BufferedReadStream stream, int remaining) + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker[..exifMarkerLength])) { - const int exifMarkerLength = 6; - const int xmpMarkerLength = 29; - if (remaining < exifMarkerLength || this.skipMetadata) + const int remainingXmpMarkerBytes = xmpMarkerLength - exifMarkerLength; + if (remaining < remainingXmpMarkerBytes || this.skipMetadata) { // Skip the application header length. stream.Skip(remaining); return; } - if (stream.Position + remaining >= stream.Length) + stream.Read(this.temp, exifMarkerLength, remainingXmpMarkerBytes); + remaining -= remainingXmpMarkerBytes; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) { - JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); - } - - // XMP marker is the longer then the EXIF marker, so first try read the EXIF marker bytes. - stream.Read(this.temp, 0, exifMarkerLength); - remaining -= exifMarkerLength; - - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) - { - this.hasExif = true; + this.hasXmp = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); - if (this.exifData is null) + if (this.xmpData is null) { - this.exifData = profile; + this.xmpData = profile; } else { - // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers. - ExtendProfile(ref this.exifData, profile); + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers. + ExtendProfile(ref this.xmpData, profile); } remaining = 0; } + } + + // Skip over any remaining bytes of this header. + stream.Skip(remaining); + } + + /// + /// Processes the App2 marker retrieving any stored ICC profile information + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessApp2Marker(BufferedReadStream stream, int remaining) + { + // Length is 14 though we only need to check 12. + const int icclength = 14; + if (remaining < icclength || this.skipMetadata) + { + stream.Skip(remaining); + return; + } + + byte[] identifier = new byte[icclength]; + stream.Read(identifier, 0, icclength); + remaining -= icclength; // We have read it by this point - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker[..exifMarkerLength])) + if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) + { + this.hasIcc = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); + + if (this.iccData is null) + { + this.iccData = profile; + } + else + { + // If the ICC information exceeds 64K, it will be split over multiple APP2 markers + ExtendProfile(ref this.iccData, profile); + } + } + else + { + // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. + stream.Skip(remaining); + } + } + + /// + /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. + /// The tableBytes of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessApp13Marker(BufferedReadStream stream, int remaining) + { + if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata) + { + stream.Skip(remaining); + return; + } + + stream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); + remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) + { + byte[] resourceBlockData = new byte[remaining]; + stream.Read(resourceBlockData, 0, remaining); + Span blockDataSpan = resourceBlockData.AsSpan(); + + while (blockDataSpan.Length > 12) { - const int remainingXmpMarkerBytes = xmpMarkerLength - exifMarkerLength; - if (remaining < remainingXmpMarkerBytes || this.skipMetadata) + if (!ProfileResolver.IsProfile(blockDataSpan[..4], ProfileResolver.AdobeImageResourceBlockMarker)) { - // Skip the application header length. - stream.Skip(remaining); return; } - stream.Read(this.temp, exifMarkerLength, remainingXmpMarkerBytes); - remaining -= remainingXmpMarkerBytes; - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) + blockDataSpan = blockDataSpan[4..]; + Span imageResourceBlockId = blockDataSpan[..2]; + if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) { - this.hasXmp = true; - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); - - if (this.xmpData is null) + int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int dataStartIdx = 2 + resourceBlockNameLength + 4; + if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) { - this.xmpData = profile; + this.hasIptc = true; + this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); + break; } - else + } + else + { + int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); + int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); + int dataStartIdx = 2 + resourceBlockNameLength + 4; + if (blockDataSpan.Length < dataStartIdx + resourceDataSize) { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers. - ExtendProfile(ref this.xmpData, profile); + // Not enough data or the resource data size is wrong. + break; } - remaining = 0; + blockDataSpan = blockDataSpan[(dataStartIdx + resourceDataSize)..]; } } - - // Skip over any remaining bytes of this header. + } + else + { + // If the profile is unknown skip over the rest of it. stream.Skip(remaining); } + } - /// - /// Processes the App2 marker retrieving any stored ICC profile information - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp2Marker(BufferedReadStream stream, int remaining) + /// + /// Processes a DAC marker, decoding the arithmetic tables. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessArithmeticTable(BufferedReadStream stream, int remaining) + { + this.arithmeticDecodingTables ??= new List(4); + + while (remaining > 0) { - // Length is 14 though we only need to check 12. - const int icclength = 14; - if (remaining < icclength || this.skipMetadata) - { - stream.Skip(remaining); - return; - } + int tableClassAndIdentifier = stream.ReadByte(); + remaining--; + byte tableClass = (byte)(tableClassAndIdentifier >> 4); + byte identifier = (byte)(tableClassAndIdentifier & 0xF); - byte[] identifier = new byte[icclength]; - stream.Read(identifier, 0, icclength); - remaining -= icclength; // We have read it by this point + byte conditioningTableValue = (byte)stream.ReadByte(); + remaining--; - if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) - { - this.hasIcc = true; - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); + ArithmeticDecodingTable arithmeticTable = new(tableClass, identifier); + arithmeticTable.Configure(conditioningTableValue); - if (this.iccData is null) - { - this.iccData = profile; - } - else + bool tableEntryReplaced = false; + for (int i = 0; i < this.arithmeticDecodingTables.Count; i++) + { + ArithmeticDecodingTable item = this.arithmeticDecodingTables[i]; + if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier) { - // If the ICC information exceeds 64K, it will be split over multiple APP2 markers - ExtendProfile(ref this.iccData, profile); + this.arithmeticDecodingTables[i] = arithmeticTable; + tableEntryReplaced = true; + break; } } - else + + if (!tableEntryReplaced) { - // Not an ICC profile we can handle. Skip the remaining bytes so we can carry on and ignore this. - stream.Skip(remaining); + this.arithmeticDecodingTables.Add(arithmeticTable); } } + } + + /// + /// Reads the adobe image resource block name: a Pascal string (padded to make size even). + /// + /// The span holding the block resource data. + /// The length of the name. + [MethodImpl(InliningOptions.ShortMethod)] + private static int ReadImageResourceNameLength(Span blockDataSpan) + { + byte nameLength = blockDataSpan[2]; + int nameDataSize = nameLength == 0 ? 2 : nameLength; + if (nameDataSize % 2 != 0) + { + nameDataSize++; + } + + return nameDataSize; + } + + /// + /// Reads the length of a adobe image resource data block. + /// + /// The span holding the block resource data. + /// The length of the block name. + /// The block length. + [MethodImpl(InliningOptions.ShortMethod)] + private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength) + => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); + + /// + /// Processes the application header containing the Adobe identifier + /// which stores image encoding information for DCT filters. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessApp14Marker(BufferedReadStream stream, int remaining) + { + const int markerLength = AdobeMarker.Length; + if (remaining < markerLength) + { + // Skip the application header length + stream.Skip(remaining); + return; + } + + stream.Read(this.temp, 0, markerLength); + remaining -= markerLength; + + if (AdobeMarker.TryParse(this.temp, out this.adobe)) + { + this.hasAdobeMarker = true; + } + + if (remaining > 0) + { + stream.Skip(remaining); + } + } + + /// + /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. + /// + /// The input stream. + /// The remaining bytes in the segment block. + /// + /// Thrown if the tables do not match the header. + /// + private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) + { + JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); - /// - /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. - /// The tableBytes of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp13Marker(BufferedReadStream stream, int remaining) + while (remaining > 0) { - if (remaining < ProfileResolver.AdobePhotoshopApp13Marker.Length || this.skipMetadata) + // 1 byte: quantization table spec + // bit 0..3: table index (0..3) + // bit 4..7: table precision (0 = 8 bit, 1 = 16 bit) + int quantizationTableSpec = stream.ReadByte(); + int tableIndex = quantizationTableSpec & 15; + int tablePrecision = quantizationTableSpec >> 4; + + // Validate: + if (tableIndex > 3) { - stream.Skip(remaining); - return; + JpegThrowHelper.ThrowBadQuantizationTableIndex(tableIndex); } - stream.Read(this.temp, 0, ProfileResolver.AdobePhotoshopApp13Marker.Length); - remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; - if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) - { - byte[] resourceBlockData = new byte[remaining]; - stream.Read(resourceBlockData, 0, remaining); - Span blockDataSpan = resourceBlockData.AsSpan(); + remaining--; - while (blockDataSpan.Length > 12) - { - if (!ProfileResolver.IsProfile(blockDataSpan[..4], ProfileResolver.AdobeImageResourceBlockMarker)) + // Decoding single 8x8 table + ref Block8x8F table = ref this.QuantizationTables[tableIndex]; + switch (tablePrecision) + { + // 8 bit values + case 0: + // Validate: 8 bit table needs exactly 64 bytes + if (remaining < 64) { - return; + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); } - blockDataSpan = blockDataSpan[4..]; - Span imageResourceBlockId = blockDataSpan[..2]; - if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) - { - int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); - int dataStartIdx = 2 + resourceBlockNameLength + 4; - if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) - { - this.hasIptc = true; - this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); - break; - } - } - else - { - int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); - int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); - int dataStartIdx = 2 + resourceBlockNameLength + 4; - if (blockDataSpan.Length < dataStartIdx + resourceDataSize) - { - // Not enough data or the resource data size is wrong. - break; - } + stream.Read(this.temp, 0, 64); + remaining -= 64; - blockDataSpan = blockDataSpan[(dataStartIdx + resourceDataSize)..]; + // Parsing quantization table & saving it in natural order + for (int j = 0; j < 64; j++) + { + table[ZigZag.ZigZagOrder[j]] = this.temp[j]; } - } - } - else - { - // If the profile is unknown skip over the rest of it. - stream.Skip(remaining); - } - } - - /// - /// Processes a DAC marker, decoding the arithmetic tables. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessArithmeticTable(BufferedReadStream stream, int remaining) - { - this.arithmeticDecodingTables ??= new List(4); - while (remaining > 0) - { - int tableClassAndIdentifier = stream.ReadByte(); - remaining--; - byte tableClass = (byte)(tableClassAndIdentifier >> 4); - byte identifier = (byte)(tableClassAndIdentifier & 0xF); + break; - byte conditioningTableValue = (byte)stream.ReadByte(); - remaining--; + // 16 bit values + case 1: + // Validate: 16 bit table needs exactly 128 bytes + if (remaining < 128) + { + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); + } - ArithmeticDecodingTable arithmeticTable = new(tableClass, identifier); - arithmeticTable.Configure(conditioningTableValue); + stream.Read(this.temp, 0, 128); + remaining -= 128; - bool tableEntryReplaced = false; - for (int i = 0; i < this.arithmeticDecodingTables.Count; i++) - { - ArithmeticDecodingTable item = this.arithmeticDecodingTables[i]; - if (item.TableClass == arithmeticTable.TableClass && item.Identifier == arithmeticTable.Identifier) + // Parsing quantization table & saving it in natural order + for (int j = 0; j < 64; j++) { - this.arithmeticDecodingTables[i] = arithmeticTable; - tableEntryReplaced = true; - break; + table[ZigZag.ZigZagOrder[j]] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; } - } - if (!tableEntryReplaced) - { - this.arithmeticDecodingTables.Add(arithmeticTable); - } + break; + + // Unknown precision - error + default: + JpegThrowHelper.ThrowBadQuantizationTablePrecision(tablePrecision); + break; } - } - /// - /// Reads the adobe image resource block name: a Pascal string (padded to make size even). - /// - /// The span holding the block resource data. - /// The length of the name. - [MethodImpl(InliningOptions.ShortMethod)] - private static int ReadImageResourceNameLength(Span blockDataSpan) - { - byte nameLength = blockDataSpan[2]; - int nameDataSize = nameLength == 0 ? 2 : nameLength; - if (nameDataSize % 2 != 0) + // Estimating quality + switch (tableIndex) { - nameDataSize++; + // luminance table + case 0: + jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table); + break; + + // chrominance table + case 1: + jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table); + break; } - - return nameDataSize; } + } - /// - /// Reads the length of a adobe image resource data block. - /// - /// The span holding the block resource data. - /// The length of the block name. - /// The block length. - [MethodImpl(InliningOptions.ShortMethod)] - private static int ReadResourceDataLength(Span blockDataSpan, int resourceBlockNameLength) - => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4)); - - /// - /// Processes the application header containing the Adobe identifier - /// which stores image encoding information for DCT filters. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessApp14Marker(BufferedReadStream stream, int remaining) + /// + /// Processes the Start of Frame marker. Specified in section B.2.2. + /// + /// The input stream. + /// The remaining bytes in the segment block. + /// The current frame marker. + /// The jpeg decoding component type. + /// Whether to parse metadata only. + private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) + { + if (this.Frame != null) { - const int markerLength = AdobeMarker.Length; - if (remaining < markerLength) + if (metadataOnly) { - // Skip the application header length - stream.Skip(remaining); return; } - stream.Read(this.temp, 0, markerLength); - remaining -= markerLength; - - if (AdobeMarker.TryParse(this.temp, out this.adobe)) - { - this.hasAdobeMarker = true; - } + JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); + } - if (remaining > 0) - { - stream.Skip(remaining); - } + // Read initial marker definitions. + const int length = 6; + int bytesRead = stream.Read(this.temp, 0, length); + if (bytesRead != length) + { + JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data."); } - /// - /// Processes the Define Quantization Marker and tables. Specified in section B.2.4.1. - /// - /// The input stream. - /// The remaining bytes in the segment block. - /// - /// Thrown if the tables do not match the header. - /// - private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) + // 1 byte: Bits/sample precision. + byte precision = this.temp[0]; + + // Validate: only 8-bit and 12-bit precisions are supported. + if (Array.IndexOf(this.supportedPrecisions, precision) == -1) { - JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance); + JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported."); + } - while (remaining > 0) - { - // 1 byte: quantization table spec - // bit 0..3: table index (0..3) - // bit 4..7: table precision (0 = 8 bit, 1 = 16 bit) - int quantizationTableSpec = stream.ReadByte(); - int tableIndex = quantizationTableSpec & 15; - int tablePrecision = quantizationTableSpec >> 4; - - // Validate: - if (tableIndex > 3) - { - JpegThrowHelper.ThrowBadQuantizationTableIndex(tableIndex); - } + // 2 byte: Height + int frameHeight = (this.temp[1] << 8) | this.temp[2]; - remaining--; + // 2 byte: Width + int frameWidth = (this.temp[3] << 8) | this.temp[4]; - // Decoding single 8x8 table - ref Block8x8F table = ref this.QuantizationTables[tableIndex]; - switch (tablePrecision) - { - // 8 bit values - case 0: - // Validate: 8 bit table needs exactly 64 bytes - if (remaining < 64) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); - } + // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that). + if (frameHeight == 0 || frameWidth == 0) + { + JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); + } - stream.Read(this.temp, 0, 64); - remaining -= 64; + // 1 byte: Number of components. + byte componentCount = this.temp[5]; - // Parsing quantization table & saving it in natural order - for (int j = 0; j < 64; j++) - { - table[ZigZag.ZigZagOrder[j]] = this.temp[j]; - } + // Validate: componentCount more than 4 can lead to a buffer overflow during stream + // reading so we must limit it to 4. + // We do not support jpeg images with more than 4 components anyway. + if (componentCount > 4) + { + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); + } - break; + this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); + this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; - // 16 bit values - case 1: - // Validate: 16 bit table needs exactly 128 bytes - if (remaining < 128) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); - } + remaining -= length; - stream.Read(this.temp, 0, 128); - remaining -= 128; + // Validate: remaining part must be equal to components * 3 + const int componentBytes = 3; + if (remaining != componentCount * componentBytes) + { + JpegThrowHelper.ThrowBadMarker("SOFn", remaining); + } - // Parsing quantization table & saving it in natural order - for (int j = 0; j < 64; j++) - { - table[ZigZag.ZigZagOrder[j]] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; - } + // components*3 bytes: component data + stream.Read(this.temp, 0, remaining); - break; + // No need to pool this. They max out at 4 + this.Frame.ComponentIds = new byte[componentCount]; + this.Frame.ComponentOrder = new byte[componentCount]; + this.Frame.Components = new JpegComponent[componentCount]; - // Unknown precision - error - default: - JpegThrowHelper.ThrowBadQuantizationTablePrecision(tablePrecision); - break; - } + int maxH = 0; + int maxV = 0; + int index = 0; + for (int i = 0; i < this.Frame.Components.Length; i++) + { + // 1 byte: component identifier + byte componentId = this.temp[index]; - // Estimating quality - switch (tableIndex) - { - // luminance table - case 0: - jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table); - break; + // 1 byte: component sampling factors + byte hv = this.temp[index + 1]; + int h = (hv >> 4) & 15; + int v = hv & 15; - // chrominance table - case 1: - jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table); - break; - } + // Validate: 1-4 range + if (Numerics.IsOutOfRange(h, 1, 4)) + { + JpegThrowHelper.ThrowBadSampling(h); } - } - /// - /// Processes the Start of Frame marker. Specified in section B.2.2. - /// - /// The input stream. - /// The remaining bytes in the segment block. - /// The current frame marker. - /// The jpeg decoding component type. - /// Whether to parse metadata only. - private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining, in JpegFileMarker frameMarker, ComponentType decodingComponentType, bool metadataOnly) - { - if (this.Frame != null) + // Validate: 1-4 range + if (Numerics.IsOutOfRange(v, 1, 4)) { - if (metadataOnly) - { - return; - } - - JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); + JpegThrowHelper.ThrowBadSampling(v); } - // Read initial marker definitions. - const int length = 6; - int bytesRead = stream.Read(this.temp, 0, length); - if (bytesRead != length) + if (maxH < h) { - JpegThrowHelper.ThrowInvalidImageContentException("SOF marker does not contain enough data."); + maxH = h; } - // 1 byte: Bits/sample precision. - byte precision = this.temp[0]; - - // Validate: only 8-bit and 12-bit precisions are supported. - if (Array.IndexOf(this.supportedPrecisions, precision) == -1) + if (maxV < v) { - JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision is supported."); + maxV = v; } - // 2 byte: Height - int frameHeight = (this.temp[1] << 8) | this.temp[2]; + // 1 byte: quantization table destination selector + byte quantTableIndex = this.temp[index + 2]; - // 2 byte: Width - int frameWidth = (this.temp[3] << 8) | this.temp[4]; - - // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that). - if (frameHeight == 0 || frameWidth == 0) + // Validate: 0-3 range + if (quantTableIndex > 3) { - JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight); + JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex); } - // 1 byte: Number of components. - byte componentCount = this.temp[5]; + IJpegComponent component = decodingComponentType is ComponentType.Huffman ? + new JpegComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : + new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); - // Validate: componentCount more than 4 can lead to a buffer overflow during stream - // reading so we must limit it to 4. - // We do not support jpeg images with more than 4 components anyway. - if (componentCount > 4) - { - JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); - } + this.Frame.Components[i] = (JpegComponent)component; + this.Frame.ComponentIds[i] = componentId; + + index += componentBytes; + } - this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); - this.Metadata.GetJpegMetadata().Progressive = this.Frame.Progressive; + this.ColorSpace = this.hasAdobeMarker + ? DeduceJpegColorSpace(componentCount, ref this.adobe) + : DeduceJpegColorSpace(componentCount); + this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType(); - remaining -= length; + if (!metadataOnly) + { + this.Frame.Init(maxH, maxV); + this.scanDecoder.InjectFrameData(this.Frame, this); + } + } - // Validate: remaining part must be equal to components * 3 - const int componentBytes = 3; - if (remaining != componentCount * componentBytes) - { - JpegThrowHelper.ThrowBadMarker("SOFn", remaining); - } + /// + /// Processes a Define Huffman Table marker, and initializes a huffman + /// struct from its contents. Specified in section B.2.4.2. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) + { + const int codeLengthsByteSize = 17; + const int codeValuesMaxByteSize = 256; + const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; - // components*3 bytes: component data - stream.Read(this.temp, 0, remaining); + HuffmanScanDecoder huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder; + if (huffmanScanDecoder is null) + { + JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data"); + } - // No need to pool this. They max out at 4 - this.Frame.ComponentIds = new byte[componentCount]; - this.Frame.ComponentOrder = new byte[componentCount]; - this.Frame.Components = new JpegComponent[componentCount]; + int length = remaining; + using (IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(totalBufferSize)) + { + Span bufferSpan = buffer.GetSpan(); + Span huffmanLengthsSpan = bufferSpan[..codeLengthsByteSize]; + Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); + Span tableWorkspace = MemoryMarshal.Cast(bufferSpan[(codeLengthsByteSize + codeValuesMaxByteSize)..]); - int maxH = 0; - int maxV = 0; - int index = 0; - for (int i = 0; i < this.Frame.Components.Length; i++) + for (int i = 2; i < remaining;) { - // 1 byte: component identifier - byte componentId = this.temp[index]; + byte huffmanTableSpec = (byte)stream.ReadByte(); + int tableType = huffmanTableSpec >> 4; + int tableIndex = huffmanTableSpec & 15; - // 1 byte: component sampling factors - byte hv = this.temp[index + 1]; - int h = (hv >> 4) & 15; - int v = hv & 15; - - // Validate: 1-4 range - if (Numerics.IsOutOfRange(h, 1, 4)) + // Types 0..1 DC..AC + if (tableType > 1) { - JpegThrowHelper.ThrowBadSampling(h); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}."); } - // Validate: 1-4 range - if (Numerics.IsOutOfRange(v, 1, 4)) + // Max tables of each type + if (tableIndex > 3) { - JpegThrowHelper.ThrowBadSampling(v); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } - if (maxH < h) - { - maxH = h; - } + stream.Read(huffmanLengthsSpan, 1, 16); - if (maxV < v) + int codeLengthSum = 0; + for (int j = 1; j < 17; j++) { - maxV = v; + codeLengthSum += huffmanLengthsSpan[j]; } - // 1 byte: quantization table destination selector - byte quantTableIndex = this.temp[index + 2]; + length -= 17; - // Validate: 0-3 range - if (quantTableIndex > 3) + if (codeLengthSum > 256 || codeLengthSum > length) { - JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex); + JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } - IJpegComponent component = decodingComponentType is ComponentType.Huffman ? - new JpegComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i) : - new ArithmeticDecodingComponent(this.configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); - this.Frame.Components[i] = (JpegComponent)component; - this.Frame.ComponentIds[i] = componentId; + i += 17 + codeLengthSum; - index += componentBytes; - } - - this.ColorSpace = this.hasAdobeMarker - ? DeduceJpegColorSpace(componentCount, ref this.adobe) - : DeduceJpegColorSpace(componentCount); - this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType(); - - if (!metadataOnly) - { - this.Frame.Init(maxH, maxV); - this.scanDecoder.InjectFrameData(this.Frame, this); + huffmanScanDecoder!.BuildHuffmanTable( + tableType, + tableIndex, + huffmanLengthsSpan, + huffmanValuesSpan[..codeLengthSum], + tableWorkspace); } } + } - /// - /// Processes a Define Huffman Table marker, and initializes a huffman - /// struct from its contents. Specified in section B.2.4.2. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) + /// + /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, + /// in macroblocks. + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining) + { + if (remaining != 2) { - const int codeLengthsByteSize = 17; - const int codeValuesMaxByteSize = 256; - const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; - - HuffmanScanDecoder huffmanScanDecoder = this.scanDecoder as HuffmanScanDecoder; - if (huffmanScanDecoder is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("missing huffman table data"); - } - - int length = remaining; - using (IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(totalBufferSize)) - { - Span bufferSpan = buffer.GetSpan(); - Span huffmanLengthsSpan = bufferSpan[..codeLengthsByteSize]; - Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); - Span tableWorkspace = MemoryMarshal.Cast(bufferSpan[(codeLengthsByteSize + codeValuesMaxByteSize)..]); - - for (int i = 2; i < remaining;) - { - byte huffmanTableSpec = (byte)stream.ReadByte(); - int tableType = huffmanTableSpec >> 4; - int tableIndex = huffmanTableSpec & 15; - - // Types 0..1 DC..AC - if (tableType > 1) - { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}."); - } - - // Max tables of each type - if (tableIndex > 3) - { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); - } - - stream.Read(huffmanLengthsSpan, 1, 16); - - int codeLengthSum = 0; - for (int j = 1; j < 17; j++) - { - codeLengthSum += huffmanLengthsSpan[j]; - } - - length -= 17; - - if (codeLengthSum > 256 || codeLengthSum > length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); - } - - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); + } - i += 17 + codeLengthSum; + // Save the reset interval, because it can come before or after the SOF marker. + // If the reset interval comes after the SOF marker, the scanDecoder has not been created. + this.resetInterval = this.ReadUint16(stream); - huffmanScanDecoder!.BuildHuffmanTable( - tableType, - tableIndex, - huffmanLengthsSpan, - huffmanValuesSpan[..codeLengthSum], - tableWorkspace); - } - } + if (this.scanDecoder != null) + { + this.scanDecoder.ResetInterval = this.resetInterval.Value; } + } - /// - /// Processes the DRI (Define Restart Interval Marker) Which specifies the interval between RSTn markers, - /// in macroblocks. - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessDefineRestartIntervalMarker(BufferedReadStream stream, int remaining) + /// + /// Processes the SOS (Start of scan marker). + /// + /// The input stream. + /// The remaining bytes in the segment block. + private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining) + { + if (this.Frame is null) { - if (remaining != 2) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); - } + JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); + } - // Save the reset interval, because it can come before or after the SOF marker. - // If the reset interval comes after the SOF marker, the scanDecoder has not been created. - this.resetInterval = this.ReadUint16(stream); + // 1 byte: Number of components in scan. + int selectorsCount = stream.ReadByte(); - if (this.scanDecoder != null) - { - this.scanDecoder.ResetInterval = this.resetInterval.Value; - } + // Validate: 0 < count <= totalComponents + if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) + { + // TODO: extract as separate method? + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); } - /// - /// Processes the SOS (Start of scan marker). - /// - /// The input stream. - /// The remaining bytes in the segment block. - private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining) + // Validate: Marker must contain exactly (4 + selectorsCount*2) bytes + int selectorsBytes = selectorsCount * 2; + if (remaining != 4 + selectorsBytes) { - if (this.Frame is null) - { - JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); - } - - // 1 byte: Number of components in scan. - int selectorsCount = stream.ReadByte(); - - // Validate: 0 < count <= totalComponents - if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}."); - } + JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining); + } - // Validate: Marker must contain exactly (4 + selectorsCount*2) bytes - int selectorsBytes = selectorsCount * 2; - if (remaining != 4 + selectorsBytes) - { - JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining); - } + // selectorsCount*2 bytes: component index + huffman tables indices + stream.Read(this.temp, 0, selectorsBytes); - // selectorsCount*2 bytes: component index + huffman tables indices - stream.Read(this.temp, 0, selectorsBytes); + this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount; + for (int i = 0; i < selectorsBytes; i += 2) + { + // 1 byte: Component id + int componentSelectorId = this.temp[i]; - this.Frame.Interleaved = this.Frame.ComponentCount == selectorsCount; - for (int i = 0; i < selectorsBytes; i += 2) + int componentIndex = -1; + for (int j = 0; j < this.Frame.ComponentIds.Length; j++) { - // 1 byte: Component id - int componentSelectorId = this.temp[i]; - - int componentIndex = -1; - for (int j = 0; j < this.Frame.ComponentIds.Length; j++) + byte id = this.Frame.ComponentIds[j]; + if (componentSelectorId == id) { - byte id = this.Frame.ComponentIds[j]; - if (componentSelectorId == id) - { - componentIndex = j; - break; - } - } - - // Validate: Must be found among registered components. - if (componentIndex == -1) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}."); - } - - this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; - - IJpegComponent component = this.Frame.Components[componentIndex]; - - // 1 byte: Huffman table selectors. - // 4 bits - dc - // 4 bits - ac - int tableSpec = this.temp[i + 1]; - int dcTableIndex = tableSpec >> 4; - int acTableIndex = tableSpec & 15; - - // Validate: both must be < 4 - if (dcTableIndex >= 4 || acTableIndex >= 4) - { - // TODO: extract as separate method? - JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); + componentIndex = j; + break; } - - component.DcTableId = dcTableIndex; - component.AcTableId = acTableIndex; } - // 3 bytes: Progressive scan decoding data. - int bytesRead = stream.Read(this.temp, 0, 3); - if (bytesRead != 3) + // Validate: Must be found among registered components. + if (componentIndex == -1) { - JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data"); + // TODO: extract as separate method? + JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}."); } - this.scanDecoder.SpectralStart = this.temp[0]; + this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; - this.scanDecoder.SpectralEnd = this.temp[1]; + IJpegComponent component = this.Frame.Components[componentIndex]; - int successiveApproximation = this.temp[2]; - this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; - this.scanDecoder.SuccessiveLow = successiveApproximation & 15; + // 1 byte: Huffman table selectors. + // 4 bits - dc + // 4 bits - ac + int tableSpec = this.temp[i + 1]; + int dcTableIndex = tableSpec >> 4; + int acTableIndex = tableSpec & 15; - if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder) + // Validate: both must be < 4 + if (dcTableIndex >= 4 || acTableIndex >= 4) { - arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables); + // TODO: extract as separate method? + JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}"); } - this.scanDecoder.ParseEntropyCodedData(selectorsCount); + component.DcTableId = dcTableIndex; + component.AcTableId = acTableIndex; } - /// - /// Reads a from the stream advancing it by two bytes. - /// - /// The input stream. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private ushort ReadUint16(BufferedReadStream stream) + // 3 bytes: Progressive scan decoding data. + int bytesRead = stream.Read(this.temp, 0, 3); + if (bytesRead != 3) { - int bytesRead = stream.Read(this.markerBuffer, 0, 2); - if (bytesRead != 2) - { - JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort."); - } + JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data"); + } + + this.scanDecoder.SpectralStart = this.temp[0]; + + this.scanDecoder.SpectralEnd = this.temp[1]; + + int successiveApproximation = this.temp[2]; + this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4; + this.scanDecoder.SuccessiveLow = successiveApproximation & 15; + + if (this.scanDecoder is ArithmeticScanDecoder arithmeticScanDecoder) + { + arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables); + } + + this.scanDecoder.ParseEntropyCodedData(selectorsCount); + } - return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); + /// + /// Reads a from the stream advancing it by two bytes. + /// + /// The input stream. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private ushort ReadUint16(BufferedReadStream stream) + { + int bytesRead = stream.Read(this.markerBuffer, 0, 2); + if (bytesRead != 2) + { + JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort."); } + + return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs index 95d1c81d23..193b2d3a8f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderOptions.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Configuration options for decoding Jpeg images. +/// +public sealed class JpegDecoderOptions : ISpecializedDecoderOptions { /// - /// Configuration options for decoding Jpeg images. + /// Gets or sets the resize mode. /// - public sealed class JpegDecoderOptions : ISpecializedDecoderOptions - { - /// - /// Gets or sets the resize mode. - /// - public JpegDecoderResizeMode ResizeMode { get; set; } + public JpegDecoderResizeMode ResizeMode { get; set; } - /// - public DecoderOptions GeneralOptions { get; set; } = new(); - } + /// + public DecoderOptions GeneralOptions { get; set; } = new(); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs index 5037c85809..7206b5d633 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderResizeMode.cs @@ -3,27 +3,26 @@ using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Provides enumeration for resize modes taken during decoding. +/// Applicable only when has a value. +/// +public enum JpegDecoderResizeMode { /// - /// Provides enumeration for resize modes taken during decoding. - /// Applicable only when has a value. + /// Both and . /// - public enum JpegDecoderResizeMode - { - /// - /// Both and . - /// - Combined, + Combined, - /// - /// IDCT-only to nearest block scale. Similar in output to . - /// - IdctOnly, + /// + /// IDCT-only to nearest block scale. Similar in output to . + /// + IdctOnly, - /// - /// Opt-out the IDCT part and only Resize. Can be useful in case of quality concerns. - /// - ScaleOnly - } + /// + /// Opt-out the IDCT part and only Resize. Can be useful in case of quality concerns. + /// + ScaleOnly } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 7fe6a4990d..28d095eedc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -1,71 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Encoder for writing the data image to a stream in jpeg format. +/// +public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { /// - /// Encoder for writing the data image to a stream in jpeg format. + /// Backing field for . /// - public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions - { - /// - /// Backing field for . - /// - private int? quality; + private int? quality; - /// - public int? Quality + /// + public int? Quality + { + get => this.quality; + set { - get => this.quality; - set + if (value is < 1 or > 100) { - if (value is < 1 or > 100) - { - throw new ArgumentException("Quality factor must be in [1..100] range."); - } - - this.quality = value; + throw new ArgumentException("Quality factor must be in [1..100] range."); } + + this.quality = value; } + } - /// - public bool? Interleaved { get; set; } + /// + public bool? Interleaved { get; set; } - /// - public JpegEncodingColor? ColorType { get; set; } + /// + public JpegEncodingColor? ColorType { get; set; } - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new JpegEncoderCore(this); - encoder.Encode(image, stream); - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new JpegEncoderCore(this); + encoder.Encode(image, stream); + } - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new JpegEncoderCore(this); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new JpegEncoderCore(this); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs index 2397c75639..4aed795825 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.FrameConfig.cs @@ -4,193 +4,192 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Image encoder for writing an image to a stream as a jpeg. +/// +internal sealed unsafe partial class JpegEncoderCore { - /// - /// Image encoder for writing an image to a stream as a jpeg. - /// - internal sealed unsafe partial class JpegEncoderCore + private static JpegFrameConfig[] CreateFrameConfigs() { - private static JpegFrameConfig[] CreateFrameConfigs() - { - var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC); - var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC); - var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC); - var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC); + var defaultLuminanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 0, HuffmanSpec.LuminanceDC); + var defaultLuminanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 0, HuffmanSpec.LuminanceAC); + var defaultChrominanceHuffmanDC = new JpegHuffmanTableConfig(@class: 0, destIndex: 1, HuffmanSpec.ChrominanceDC); + var defaultChrominanceHuffmanAC = new JpegHuffmanTableConfig(@class: 1, destIndex: 1, HuffmanSpec.ChrominanceAC); - var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); - var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); + var defaultLuminanceQuantTable = new JpegQuantizationTableConfig(0, Quantization.LuminanceTable); + var defaultChrominanceQuantTable = new JpegQuantizationTableConfig(1, Quantization.ChrominanceTable); - var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC, - defaultChrominanceHuffmanDC, - defaultChrominanceHuffmanAC, - }; + var yCbCrHuffmanConfigs = new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC, + defaultChrominanceHuffmanDC, + defaultChrominanceHuffmanAC, + }; - var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable, - defaultChrominanceQuantTable, - }; + var yCbCrQuantTableConfigs = new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable, + defaultChrominanceQuantTable, + }; - return new JpegFrameConfig[] - { - // YCbCr 4:4:4 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio444, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), + return new JpegFrameConfig[] + { + // YCbCr 4:4:4 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio444, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), - // YCbCr 4:2:2 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio422, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), + // YCbCr 4:2:2 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio422, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), - // YCbCr 4:2:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio420, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), + // YCbCr 4:2:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio420, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 2, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), - // YCbCr 4:1:1 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio411, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), + // YCbCr 4:1:1 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio411, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), - // YCbCr 4:1:0 - new JpegFrameConfig( - JpegColorSpace.YCbCr, - JpegEncodingColor.YCbCrRatio410, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), - }, - yCbCrHuffmanConfigs, - yCbCrQuantTableConfigs), + // YCbCr 4:1:0 + new JpegFrameConfig( + JpegColorSpace.YCbCr, + JpegEncodingColor.YCbCrRatio410, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 4, vsf: 2, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 1, dcIndex: 1, acIndex: 1), + }, + yCbCrHuffmanConfigs, + yCbCrQuantTableConfigs), - // Luminance - new JpegFrameConfig( - JpegColorSpace.Grayscale, - JpegEncodingColor.Luminance, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }), + // Luminance + new JpegFrameConfig( + JpegColorSpace.Grayscale, + JpegEncodingColor.Luminance, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 0, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }), - // Rgb - new JpegFrameConfig( - JpegColorSpace.RGB, - JpegEncodingColor.Rgb, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + // Rgb + new JpegFrameConfig( + JpegColorSpace.RGB, + JpegEncodingColor.Rgb, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 82, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 71, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 66, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown + }, - // Cmyk - new JpegFrameConfig( - JpegColorSpace.Cmyk, - JpegEncodingColor.Cmyk, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + // Cmyk + new JpegFrameConfig( + JpegColorSpace.Cmyk, + JpegEncodingColor.Cmyk, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), }, + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformUnknown, + }, - // YccK - new JpegFrameConfig( - JpegColorSpace.Ycck, - JpegEncodingColor.Ycck, - new JpegComponentConfig[] - { - new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), - }, - new JpegHuffmanTableConfig[] - { - defaultLuminanceHuffmanDC, - defaultLuminanceHuffmanAC - }, - new JpegQuantizationTableConfig[] - { - defaultLuminanceQuantTable - }) - { - AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + // YccK + new JpegFrameConfig( + JpegColorSpace.Ycck, + JpegEncodingColor.Ycck, + new JpegComponentConfig[] + { + new JpegComponentConfig(id: 1, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 2, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 3, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), + new JpegComponentConfig(id: 4, hsf: 1, vsf: 1, quantIndex: 0, dcIndex: 0, acIndex: 0), }, - }; - } + new JpegHuffmanTableConfig[] + { + defaultLuminanceHuffmanDC, + defaultLuminanceHuffmanAC + }, + new JpegQuantizationTableConfig[] + { + defaultLuminanceQuantTable + }) + { + AdobeColorTransformMarkerFlag = JpegConstants.Adobe.ColorTransformYcck, + }, + }; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index ea29e071ce..83c2e27e91 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; -using System.Linq; -using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; @@ -16,701 +12,700 @@ using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Image encoder for writing an image to a stream as a jpeg. +/// +internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream as a jpeg. + /// The available encodable frame configs. + /// + private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[20]; + + private readonly IJpegEncoderOptions options; + + /// + /// The output stream. All attempted writes after the first error become no-ops. + /// + private Stream outputStream; + + /// + /// Initializes a new instance of the class. /// - internal sealed unsafe partial class JpegEncoderCore : IImageEncoderInternals + /// The options. + public JpegEncoderCore(IJpegEncoderOptions options) + => this.options = options; + + public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; + + /// + /// Encode writes the image to the jpeg baseline format with the given options. + /// + /// The pixel format. + /// The image to write from. + /// The stream to write to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - /// - /// The available encodable frame configs. - /// - private static readonly JpegFrameConfig[] FrameConfigs = CreateFrameConfigs(); - - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] buffer = new byte[20]; - - private readonly IJpegEncoderOptions options; - - /// - /// The output stream. All attempted writes after the first error become no-ops. - /// - private Stream outputStream; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - public JpegEncoderCore(IJpegEncoderOptions options) - => this.options = options; - - public Block8x8F[] QuantizationTables { get; } = new Block8x8F[4]; - - /// - /// Encode writes the image to the jpeg baseline format with the given options. - /// - /// The pixel format. - /// The image to write from. - /// The stream to write to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength) { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); + } - if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength) - { - JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height); - } + cancellationToken.ThrowIfCancellationRequested(); - cancellationToken.ThrowIfCancellationRequested(); + this.outputStream = stream; - this.outputStream = stream; + ImageMetadata metadata = image.Metadata; + JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); + JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata); - ImageMetadata metadata = image.Metadata; - JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); - JpegFrameConfig frameConfig = this.GetFrameConfig(jpegMetadata); + bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true; + using var frame = new JpegFrame(image, frameConfig, interleaved); - bool interleaved = this.options.Interleaved ?? jpegMetadata.Interleaved ?? true; - using var frame = new JpegFrame(image, frameConfig, interleaved); + // Write the Start Of Image marker. + this.WriteStartOfImage(); - // Write the Start Of Image marker. - this.WriteStartOfImage(); + // Write APP0 marker + if (frameConfig.AdobeColorTransformMarkerFlag is null) + { + this.WriteJfifApplicationHeader(metadata); + } - // Write APP0 marker - if (frameConfig.AdobeColorTransformMarkerFlag is null) - { - this.WriteJfifApplicationHeader(metadata); - } + // Write APP14 marker with adobe color extension + else + { + this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value); + } - // Write APP14 marker with adobe color extension - else - { - this.WriteApp14Marker(frameConfig.AdobeColorTransformMarkerFlag.Value); - } + // Write Exif, XMP, ICC and IPTC profiles + this.WriteProfiles(metadata); - // Write Exif, XMP, ICC and IPTC profiles - this.WriteProfiles(metadata); + // Write the image dimensions. + this.WriteStartOfFrame(image.Width, image.Height, frameConfig); - // Write the image dimensions. - this.WriteStartOfFrame(image.Width, image.Height, frameConfig); + // Write the Huffman tables. + var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); + this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder); - // Write the Huffman tables. - var scanEncoder = new HuffmanScanEncoder(frame.BlocksPerMcu, stream); - this.WriteDefineHuffmanTables(frameConfig.HuffmanTables, scanEncoder); + // Write the quantization tables. + this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata); - // Write the quantization tables. - this.WriteDefineQuantizationTables(frameConfig.QuantizationTables, this.options.Quality, jpegMetadata); + // Write scans with actual pixel data + using var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables); + this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken); - // Write scans with actual pixel data - using var spectralConverter = new SpectralConverter(frame, image, this.QuantizationTables); - this.WriteHuffmanScans(frame, frameConfig, spectralConverter, scanEncoder, cancellationToken); + // Write the End Of Image marker. + this.WriteEndOfImageMarker(); - // Write the End Of Image marker. - this.WriteEndOfImageMarker(); + stream.Flush(); + } - stream.Flush(); - } + /// + /// Write the start of image marker. + /// + private void WriteStartOfImage() + { + // Markers are always prefixed with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; - /// - /// Write the start of image marker. - /// - private void WriteStartOfImage() - { - // Markers are always prefixed with 0xff. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.SOI; + this.outputStream.Write(this.buffer, 0, 2); + } - this.outputStream.Write(this.buffer, 0, 2); + /// + /// Writes the application header containing the JFIF identifier plus extra data. + /// + /// The image metadata. + private void WriteJfifApplicationHeader(ImageMetadata meta) + { + // Write the JFIF headers + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[2] = 0x00; + this.buffer[3] = 0x10; + this.buffer[4] = 0x4a; // J + this.buffer[5] = 0x46; // F + this.buffer[6] = 0x49; // I + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x00; // = "JFIF",'\0' + this.buffer[9] = 0x01; // versionhi + this.buffer[10] = 0x01; // versionlo + + // Resolution. Big Endian + Span hResolution = this.buffer.AsSpan(12, 2); + Span vResolution = this.buffer.AsSpan(14, 2); + + if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) + { + // Scale down to PPI + this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); } - - /// - /// Writes the application header containing the JFIF identifier plus extra data. - /// - /// The image metadata. - private void WriteJfifApplicationHeader(ImageMetadata meta) + else { - // Write the JFIF headers - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker - this.buffer[2] = 0x00; - this.buffer[3] = 0x10; - this.buffer[4] = 0x4a; // J - this.buffer[5] = 0x46; // F - this.buffer[6] = 0x49; // I - this.buffer[7] = 0x46; // F - this.buffer[8] = 0x00; // = "JFIF",'\0' - this.buffer[9] = 0x01; // versionhi - this.buffer[10] = 0x01; // versionlo - - // Resolution. Big Endian - Span hResolution = this.buffer.AsSpan(12, 2); - Span vResolution = this.buffer.AsSpan(14, 2); - - if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) - { - // Scale down to PPI - this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits - BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); - BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); - } - else - { - // We can simply pass the value. - this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits - BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); - } + // We can simply pass the value. + this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits + BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); + BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); + } - // No thumbnail - this.buffer[16] = 0x00; // Thumbnail width - this.buffer[17] = 0x00; // Thumbnail height + // No thumbnail + this.buffer[16] = 0x00; // Thumbnail width + this.buffer[17] = 0x00; // Thumbnail height - this.outputStream.Write(this.buffer, 0, 18); - } + this.outputStream.Write(this.buffer, 0, 18); + } - /// - /// Writes the Define Huffman Table marker and tables. - /// - private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder) + /// + /// Writes the Define Huffman Table marker and tables. + /// + private void WriteDefineHuffmanTables(JpegHuffmanTableConfig[] tableConfigs, HuffmanScanEncoder scanEncoder) + { + if (tableConfigs is null) { - if (tableConfigs is null) - { - throw new ArgumentNullException(nameof(tableConfigs)); - } + throw new ArgumentNullException(nameof(tableConfigs)); + } - int markerlen = 2; + int markerlen = 2; - for (int i = 0; i < tableConfigs.Length; i++) - { - markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length; - } + for (int i = 0; i < tableConfigs.Length; i++) + { + markerlen += 1 + 16 + tableConfigs[i].Table.Values.Length; + } - this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); - for (int i = 0; i < tableConfigs.Length; i++) - { - JpegHuffmanTableConfig tableConfig = tableConfigs[i]; + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < tableConfigs.Length; i++) + { + JpegHuffmanTableConfig tableConfig = tableConfigs[i]; - int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex; - this.outputStream.WriteByte((byte)header); - this.outputStream.Write(tableConfig.Table.Count); - this.outputStream.Write(tableConfig.Table.Values); + int header = (tableConfig.Class << 4) | tableConfig.DestinationIndex; + this.outputStream.WriteByte((byte)header); + this.outputStream.Write(tableConfig.Table.Count); + this.outputStream.Write(tableConfig.Table.Values); - scanEncoder.BuildHuffmanTable(tableConfig); - } + scanEncoder.BuildHuffmanTable(tableConfig); } + } - /// - /// Writes the APP14 marker to indicate the image is in RGB color space. - /// - private void WriteApp14Marker(byte colorTransform) - { - this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); + /// + /// Writes the APP14 marker to indicate the image is in RGB color space. + /// + private void WriteApp14Marker(byte colorTransform) + { + this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + Components.Decoder.AdobeMarker.Length); - // Identifier: ASCII "Adobe". - this.buffer[0] = 0x41; - this.buffer[1] = 0x64; - this.buffer[2] = 0x6F; - this.buffer[3] = 0x62; - this.buffer[4] = 0x65; + // Identifier: ASCII "Adobe". + this.buffer[0] = 0x41; + this.buffer[1] = 0x64; + this.buffer[2] = 0x6F; + this.buffer[3] = 0x62; + this.buffer[4] = 0x65; - // Version, currently 100. - BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(5, 2), 100); + // Version, currently 100. + BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(5, 2), 100); - // Flags0 - BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(7, 2), 0); + // Flags0 + BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(7, 2), 0); - // Flags1 - BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0); + // Flags1 + BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0); - // Color transform byte - this.buffer[11] = colorTransform; + // Color transform byte + this.buffer[11] = colorTransform; - this.outputStream.Write(this.buffer.AsSpan(0, 12)); - } + this.outputStream.Write(this.buffer.AsSpan(0, 12)); + } - /// - /// Writes the EXIF profile. - /// - /// The exif profile. - private void WriteExifProfile(ExifProfile exifProfile) + /// + /// Writes the EXIF profile. + /// + /// The exif profile. + private void WriteExifProfile(ExifProfile exifProfile) + { + if (exifProfile is null || exifProfile.Values.Count == 0) { - if (exifProfile is null || exifProfile.Values.Count == 0) - { - return; - } + return; + } - const int maxBytesApp1 = 65533; // 64k - 2 padding bytes - const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. + const int maxBytesApp1 = 65533; // 64k - 2 padding bytes + const int maxBytesWithExifId = 65527; // Max - 6 bytes for EXIF header. - byte[] data = exifProfile.ToByteArray(); + byte[] data = exifProfile.ToByteArray(); - if (data.Length == 0) - { - return; - } + if (data.Length == 0) + { + return; + } - // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. - int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; - int remaining = exifMarkerLength + data.Length; - int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining; - int app1Length = bytesToWrite + 2; + // We can write up to a maximum of 64 data to the initial marker so calculate boundaries. + int exifMarkerLength = Components.Decoder.ProfileResolver.ExifMarker.Length; + int remaining = exifMarkerLength + data.Length; + int bytesToWrite = remaining > maxBytesApp1 ? maxBytesApp1 : remaining; + int app1Length = bytesToWrite + 2; + + // Write the app marker, EXIF marker, and data + this.WriteApp1Header(app1Length); + this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); + this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); + remaining -= bytesToWrite; + + // If the exif data exceeds 64K, write it in multiple APP1 Markers + for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId) + { + bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining; + app1Length = bytesToWrite + 2 + exifMarkerLength; - // Write the app marker, EXIF marker, and data this.WriteApp1Header(app1Length); + + // Write Exif00 marker this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); - this.outputStream.Write(data, 0, bytesToWrite - exifMarkerLength); - remaining -= bytesToWrite; - // If the exif data exceeds 64K, write it in multiple APP1 Markers - for (int idx = maxBytesWithExifId; idx < data.Length; idx += maxBytesWithExifId) - { - bytesToWrite = remaining > maxBytesWithExifId ? maxBytesWithExifId : remaining; - app1Length = bytesToWrite + 2 + exifMarkerLength; + // Write the exif data + this.outputStream.Write(data, idx, bytesToWrite); - this.WriteApp1Header(app1Length); + remaining -= bytesToWrite; + } + } - // Write Exif00 marker - this.outputStream.Write(Components.Decoder.ProfileResolver.ExifMarker); + /// + /// Writes the IPTC metadata. + /// + /// The iptc metadata to write. + /// + /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes. + /// + private void WriteIptcProfile(IptcProfile iptcProfile) + { + const int maxBytes = 65533; + if (iptcProfile is null || !iptcProfile.Values.Any()) + { + return; + } - // Write the exif data - this.outputStream.Write(data, idx, bytesToWrite); + iptcProfile.UpdateData(); + byte[] data = iptcProfile.Data; + if (data.Length == 0) + { + return; + } - remaining -= bytesToWrite; - } + if (data.Length > maxBytes) + { + throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes"); } - /// - /// Writes the IPTC metadata. - /// - /// The iptc metadata to write. - /// - /// Thrown if the IPTC profile size exceeds the limit of 65533 bytes. - /// - private void WriteIptcProfile(IptcProfile iptcProfile) + int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + + Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length + + Components.Decoder.ProfileResolver.AdobeIptcMarker.Length + + 2 + 4 + data.Length; + this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker); + this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker); + this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) + this.outputStream.WriteByte(0); + BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); + this.outputStream.Write(this.buffer, 0, 4); + this.outputStream.Write(data, 0, data.Length); + } + + /// + /// Writes the XMP metadata. + /// + /// The XMP metadata to write. + /// + /// Thrown if the XMP profile size exceeds the limit of 65533 bytes. + /// + private void WriteXmpProfile(XmpProfile xmpProfile) + { + if (xmpProfile is null) { - const int maxBytes = 65533; - if (iptcProfile is null || !iptcProfile.Values.Any()) - { - return; - } + return; + } - iptcProfile.UpdateData(); - byte[] data = iptcProfile.Data; - if (data.Length == 0) - { - return; - } + const int xmpOverheadLength = 29; + const int maxBytes = 65533; + const int maxData = maxBytes - xmpOverheadLength; - if (data.Length > maxBytes) - { - throw new ImageFormatException($"Iptc profile size exceeds limit of {maxBytes} bytes"); - } + byte[] data = xmpProfile.Data; - int app13Length = 2 + Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker.Length + - Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker.Length + - Components.Decoder.ProfileResolver.AdobeIptcMarker.Length + - 2 + 4 + data.Length; - this.WriteAppHeader(app13Length, JpegConstants.Markers.APP13); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobePhotoshopApp13Marker); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeImageResourceBlockMarker); - this.outputStream.Write(Components.Decoder.ProfileResolver.AdobeIptcMarker); - this.outputStream.WriteByte(0); // a empty pascal string (padded to make size even) - this.outputStream.WriteByte(0); - BinaryPrimitives.WriteInt32BigEndian(this.buffer, data.Length); - this.outputStream.Write(this.buffer, 0, 4); - this.outputStream.Write(data, 0, data.Length); + if (data is null || data.Length == 0) + { + return; } - /// - /// Writes the XMP metadata. - /// - /// The XMP metadata to write. - /// - /// Thrown if the XMP profile size exceeds the limit of 65533 bytes. - /// - private void WriteXmpProfile(XmpProfile xmpProfile) + int dataLength = data.Length; + int offset = 0; + + while (dataLength > 0) { - if (xmpProfile is null) + int length = dataLength; // Number of bytes to write. + + if (length > maxData) { - return; + length = maxData; } - const int xmpOverheadLength = 29; - const int maxBytes = 65533; - const int maxData = maxBytes - xmpOverheadLength; + dataLength -= length; - byte[] data = xmpProfile.Data; + int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length; + this.WriteApp1Header(app1Length); + this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker); + this.outputStream.Write(data, offset, length); - if (data is null || data.Length == 0) - { - return; - } + offset += length; + } + } - int dataLength = data.Length; - int offset = 0; + /// + /// Writes the App1 header. + /// + /// The length of the data the app1 marker contains. + private void WriteApp1Header(int app1Length) + => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1); - while (dataLength > 0) - { - int length = dataLength; // Number of bytes to write. + /// + /// Writes a AppX header. + /// + /// The length of the data the app marker contains. + /// The app marker to write. + private void WriteAppHeader(int length, byte appMarker) + { + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = appMarker; + this.buffer[2] = (byte)((length >> 8) & 0xFF); + this.buffer[3] = (byte)(length & 0xFF); - if (length > maxData) - { - length = maxData; - } + this.outputStream.Write(this.buffer, 0, 4); + } - dataLength -= length; + /// + /// Writes the ICC profile. + /// + /// The ICC profile to write. + /// + /// Thrown if any of the ICC profiles size exceeds the limit. + /// + private void WriteIccProfile(IccProfile iccProfile) + { + if (iccProfile is null) + { + return; + } - int app1Length = 2 + Components.Decoder.ProfileResolver.XmpMarker.Length + length; - this.WriteApp1Header(app1Length); - this.outputStream.Write(Components.Decoder.ProfileResolver.XmpMarker); - this.outputStream.Write(data, offset, length); + const int iccOverheadLength = 14; + const int maxBytes = 65533; + const int maxData = maxBytes - iccOverheadLength; - offset += length; - } - } + byte[] data = iccProfile.ToByteArray(); - /// - /// Writes the App1 header. - /// - /// The length of the data the app1 marker contains. - private void WriteApp1Header(int app1Length) - => this.WriteAppHeader(app1Length, JpegConstants.Markers.APP1); - - /// - /// Writes a AppX header. - /// - /// The length of the data the app marker contains. - /// The app marker to write. - private void WriteAppHeader(int length, byte appMarker) + if (data is null || data.Length == 0) { - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = appMarker; - this.buffer[2] = (byte)((length >> 8) & 0xFF); - this.buffer[3] = (byte)(length & 0xFF); - - this.outputStream.Write(this.buffer, 0, 4); + return; } - /// - /// Writes the ICC profile. - /// - /// The ICC profile to write. - /// - /// Thrown if any of the ICC profiles size exceeds the limit. - /// - private void WriteIccProfile(IccProfile iccProfile) + // Calculate the number of markers we'll need, rounding up of course. + int dataLength = data.Length; + int count = dataLength / maxData; + + if (count * maxData != dataLength) { - if (iccProfile is null) - { - return; - } + count++; + } - const int iccOverheadLength = 14; - const int maxBytes = 65533; - const int maxData = maxBytes - iccOverheadLength; + // Per spec, counting starts at 1. + int current = 1; + int offset = 0; - byte[] data = iccProfile.ToByteArray(); + while (dataLength > 0) + { + int length = dataLength; // Number of bytes to write. - if (data is null || data.Length == 0) + if (length > maxData) { - return; + length = maxData; } - // Calculate the number of markers we'll need, rounding up of course. - int dataLength = data.Length; - int count = dataLength / maxData; + dataLength -= length; - if (count * maxData != dataLength) - { - count++; - } + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker + int markerLength = length + 16; + this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); + this.buffer[3] = (byte)(markerLength & 0xFF); - // Per spec, counting starts at 1. - int current = 1; - int offset = 0; + this.outputStream.Write(this.buffer, 0, 4); - while (dataLength > 0) - { - int length = dataLength; // Number of bytes to write. - - if (length > maxData) - { - length = maxData; - } - - dataLength -= length; - - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.APP2; // Application Marker - int markerLength = length + 16; - this.buffer[2] = (byte)((markerLength >> 8) & 0xFF); - this.buffer[3] = (byte)(markerLength & 0xFF); - - this.outputStream.Write(this.buffer, 0, 4); - - this.buffer[0] = (byte)'I'; - this.buffer[1] = (byte)'C'; - this.buffer[2] = (byte)'C'; - this.buffer[3] = (byte)'_'; - this.buffer[4] = (byte)'P'; - this.buffer[5] = (byte)'R'; - this.buffer[6] = (byte)'O'; - this.buffer[7] = (byte)'F'; - this.buffer[8] = (byte)'I'; - this.buffer[9] = (byte)'L'; - this.buffer[10] = (byte)'E'; - this.buffer[11] = 0x00; - this.buffer[12] = (byte)current; // The position within the collection. - this.buffer[13] = (byte)count; // The total number of profiles. - - this.outputStream.Write(this.buffer, 0, iccOverheadLength); - this.outputStream.Write(data, offset, length); - - current++; - offset += length; - } + this.buffer[0] = (byte)'I'; + this.buffer[1] = (byte)'C'; + this.buffer[2] = (byte)'C'; + this.buffer[3] = (byte)'_'; + this.buffer[4] = (byte)'P'; + this.buffer[5] = (byte)'R'; + this.buffer[6] = (byte)'O'; + this.buffer[7] = (byte)'F'; + this.buffer[8] = (byte)'I'; + this.buffer[9] = (byte)'L'; + this.buffer[10] = (byte)'E'; + this.buffer[11] = 0x00; + this.buffer[12] = (byte)current; // The position within the collection. + this.buffer[13] = (byte)count; // The total number of profiles. + + this.outputStream.Write(this.buffer, 0, iccOverheadLength); + this.outputStream.Write(data, offset, length); + + current++; + offset += length; } + } - /// - /// Writes the metadata profiles to the image. - /// - /// The image metadata. - private void WriteProfiles(ImageMetadata metadata) + /// + /// Writes the metadata profiles to the image. + /// + /// The image metadata. + private void WriteProfiles(ImageMetadata metadata) + { + if (metadata is null) { - if (metadata is null) - { - return; - } - - // For compatibility, place the profiles in the following order: - // - APP1 EXIF - // - APP1 XMP - // - APP2 ICC - // - APP13 IPTC - metadata.SyncProfiles(); - this.WriteExifProfile(metadata.ExifProfile); - this.WriteXmpProfile(metadata.XmpProfile); - this.WriteIccProfile(metadata.IccProfile); - this.WriteIptcProfile(metadata.IptcProfile); + return; } - /// - /// Writes the Start Of Frame (Baseline) marker. - /// - private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) - { - JpegComponentConfig[] components = frame.Components; - - // Length (high byte, low byte), 8 + components * 3. - int markerlen = 8 + (3 * components.Length); - this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); - this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported - this.buffer[1] = (byte)(height >> 8); - this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[3] = (byte)(width >> 8); - this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported - this.buffer[5] = (byte)components.Length; - - // Components data - for (int i = 0; i < components.Length; i++) - { - int i3 = 3 * i; - Span bufferSpan = this.buffer.AsSpan(i3 + 6, 3); + // For compatibility, place the profiles in the following order: + // - APP1 EXIF + // - APP1 XMP + // - APP2 ICC + // - APP13 IPTC + metadata.SyncProfiles(); + this.WriteExifProfile(metadata.ExifProfile); + this.WriteXmpProfile(metadata.XmpProfile); + this.WriteIccProfile(metadata.IccProfile); + this.WriteIptcProfile(metadata.IptcProfile); + } - // Quantization table selector - bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; + /// + /// Writes the Start Of Frame (Baseline) marker. + /// + private void WriteStartOfFrame(int width, int height, JpegFrameConfig frame) + { + JpegComponentConfig[] components = frame.Components; + + // Length (high byte, low byte), 8 + components * 3. + int markerlen = 8 + (3 * components.Length); + this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); + this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported + this.buffer[1] = (byte)(height >> 8); + this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[3] = (byte)(width >> 8); + this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[5] = (byte)components.Length; + + // Components data + for (int i = 0; i < components.Length; i++) + { + int i3 = 3 * i; + Span bufferSpan = this.buffer.AsSpan(i3 + 6, 3); - // Sampling factors - // 4 bits - int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor; - bufferSpan[1] = (byte)samplingFactors; + // Quantization table selector + bufferSpan[2] = (byte)components[i].QuantizatioTableIndex; - // Id - bufferSpan[0] = components[i].Id; - } + // Sampling factors + // 4 bits + int samplingFactors = (components[i].HorizontalSampleFactor << 4) | components[i].VerticalSampleFactor; + bufferSpan[1] = (byte)samplingFactors; - this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9); + // Id + bufferSpan[0] = components[i].Id; } - /// - /// Writes the StartOfScan marker. - /// - private void WriteStartOfScan(Span components) - { - // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: - // - the marker length "\x00\x0c", - // - the number of components "\x03", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - component 2 uses DC table 1 and AC table 1 "\x02\x11", - // - component 3 uses DC table 1 and AC table 1 "\x03\x11", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.SOS; + this.outputStream.Write(this.buffer, 0, (3 * (components.Length - 1)) + 9); + } - // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) - int sosSize = 6 + (2 * components.Length); - this.buffer[2] = 0x00; - this.buffer[3] = (byte)sosSize; - this.buffer[4] = (byte)components.Length; // Number of components in a scan + /// + /// Writes the StartOfScan marker. + /// + private void WriteStartOfScan(Span components) + { + // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: + // - the marker length "\x00\x0c", + // - the number of components "\x03", + // - component 1 uses DC table 0 and AC table 0 "\x01\x00", + // - component 2 uses DC table 1 and AC table 1 "\x02\x11", + // - component 3 uses DC table 1 and AC table 1 "\x03\x11", + // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + // should be 0x00, 0x3f, 0x00<<4 | 0x00. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOS; + + // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + int sosSize = 6 + (2 * components.Length); + this.buffer[2] = 0x00; + this.buffer[3] = (byte)sosSize; + this.buffer[4] = (byte)components.Length; // Number of components in a scan + + // Components data + for (int i = 0; i < components.Length; i++) + { + int i2 = 2 * i; - // Components data - for (int i = 0; i < components.Length; i++) - { - int i2 = 2 * i; + // Id + this.buffer[i2 + 5] = components[i].Id; - // Id - this.buffer[i2 + 5] = components[i].Id; + // Table selectors + int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector; + this.buffer[i2 + 6] = (byte)tableSelectors; + } - // Table selectors - int tableSelectors = (components[i].DcTableSelector << 4) | components[i].AcTableSelector; - this.buffer[i2 + 6] = (byte)tableSelectors; - } + this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection. + this.buffer[sosSize] = 0x3f; // Se - End of spectral selection. + this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) + this.outputStream.Write(this.buffer, 0, sosSize + 2); + } - this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection. - this.buffer[sosSize] = 0x3f; // Se - End of spectral selection. - this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) - this.outputStream.Write(this.buffer, 0, sosSize + 2); - } + /// + /// Writes the EndOfImage marker. + /// + private void WriteEndOfImageMarker() + { + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.EOI; + this.outputStream.Write(this.buffer, 0, 2); + } - /// - /// Writes the EndOfImage marker. - /// - private void WriteEndOfImageMarker() + /// + /// Writes scans for given config. + /// + private void WriteHuffmanScans(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + if (frame.Components.Length == 1) { - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = JpegConstants.Markers.EOI; - this.outputStream.Write(this.buffer, 0, 2); - } + frame.AllocateComponents(fullScan: false); - /// - /// Writes scans for given config. - /// - private void WriteHuffmanScans(JpegFrame frame, JpegFrameConfig frameConfig, SpectralConverter spectralConverter, HuffmanScanEncoder encoder, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); + } + else if (frame.Interleaved) { - if (frame.Components.Length == 1) - { - frame.AllocateComponents(fullScan: false); + frame.AllocateComponents(fullScan: false); - this.WriteStartOfScan(frameConfig.Components); - encoder.EncodeScanBaselineSingleComponent(frame.Components[0], spectralConverter, cancellationToken); - } - else if (frame.Interleaved) - { - frame.AllocateComponents(fullScan: false); + this.WriteStartOfScan(frameConfig.Components); + encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); + } + else + { + frame.AllocateComponents(fullScan: true); + spectralConverter.ConvertFull(); - this.WriteStartOfScan(frameConfig.Components); - encoder.EncodeScanBaselineInterleaved(frameConfig.EncodingColor, frame, spectralConverter, cancellationToken); - } - else + Span components = frameConfig.Components; + for (int i = 0; i < frame.Components.Length; i++) { - frame.AllocateComponents(fullScan: true); - spectralConverter.ConvertFull(); - - Span components = frameConfig.Components; - for (int i = 0; i < frame.Components.Length; i++) - { - this.WriteStartOfScan(components.Slice(i, 1)); - encoder.EncodeScanBaseline(frame.Components[i], cancellationToken); - } + this.WriteStartOfScan(components.Slice(i, 1)); + encoder.EncodeScanBaseline(frame.Components[i], cancellationToken); } } + } - /// - /// Writes the header for a marker with the given length. - /// - /// The marker to write. - /// The marker length. - private void WriteMarkerHeader(byte marker, int length) - { - // Markers are always prefixed with 0xff. - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[1] = marker; - this.buffer[2] = (byte)(length >> 8); - this.buffer[3] = (byte)(length & 0xff); - this.outputStream.Write(this.buffer, 0, 4); - } - - /// - /// Writes the Define Quantization Marker and prepares tables for encoding. - /// - /// - /// We take quality values in a hierarchical order: - /// - /// Check if encoder has set quality. - /// Check if metadata has set quality. - /// Take default quality value from - /// - /// - /// Quantization tables configs. - /// Optional quality value from the options. - /// Jpeg metadata instance. - private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata) - { - int dataLen = configs.Length * (1 + Block8x8.Size); - - // Marker + quantization table lengths. - int markerlen = 2 + dataLen; - this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) + { + // Markers are always prefixed with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = marker; + this.buffer[2] = (byte)(length >> 8); + this.buffer[3] = (byte)(length & 0xff); + this.outputStream.Write(this.buffer, 0, 4); + } - byte[] buffer = new byte[dataLen]; - int offset = 0; + /// + /// Writes the Define Quantization Marker and prepares tables for encoding. + /// + /// + /// We take quality values in a hierarchical order: + /// + /// Check if encoder has set quality. + /// Check if metadata has set quality. + /// Take default quality value from + /// + /// + /// Quantization tables configs. + /// Optional quality value from the options. + /// Jpeg metadata instance. + private void WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, int? optionsQuality, JpegMetadata metadata) + { + int dataLen = configs.Length * (1 + Block8x8.Size); - Block8x8F workspaceBlock = default; + // Marker + quantization table lengths. + int markerlen = 2 + dataLen; + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - for (int i = 0; i < configs.Length; i++) - { - JpegQuantizationTableConfig config = configs[i]; + byte[] buffer = new byte[dataLen]; + int offset = 0; - int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata); - Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); + Block8x8F workspaceBlock = default; - // write to the output stream - buffer[offset++] = (byte)config.DestinationIndex; + for (int i = 0; i < configs.Length; i++) + { + JpegQuantizationTableConfig config = configs[i]; - for (int j = 0; j < Block8x8.Size; j++) - { - buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; - } + int quality = GetQualityForTable(config.DestinationIndex, optionsQuality, metadata); + Block8x8 scaledTable = Quantization.ScaleQuantizationTable(quality, config.Table); - // apply FDCT multipliers and inject to the destination index - workspaceBlock.LoadFrom(ref scaledTable); - FloatingPointDCT.AdjustToFDCT(ref workspaceBlock); + // write to the output stream + buffer[offset++] = (byte)config.DestinationIndex; - this.QuantizationTables[config.DestinationIndex] = workspaceBlock; + for (int j = 0; j < Block8x8.Size; j++) + { + buffer[offset++] = (byte)(uint)scaledTable[ZigZag.ZigZagOrder[j]]; } - // write filled buffer to the stream - this.outputStream.Write(buffer); + // apply FDCT multipliers and inject to the destination index + workspaceBlock.LoadFrom(ref scaledTable); + FloatingPointDCT.AdjustToFDCT(ref workspaceBlock); - static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch - { - 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor, - 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, - _ => encoderQuality ?? metadata.Quality, - }; + this.QuantizationTables[config.DestinationIndex] = workspaceBlock; } - private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) + // write filled buffer to the stream + this.outputStream.Write(buffer); + + static int GetQualityForTable(int destIndex, int? encoderQuality, JpegMetadata metadata) => destIndex switch { - JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420; - JpegFrameConfig frameConfig = Array.Find( - FrameConfigs, - cfg => cfg.EncodingColor == color); + 0 => encoderQuality ?? metadata.LuminanceQuality ?? Quantization.DefaultQualityFactor, + 1 => encoderQuality ?? metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, + _ => encoderQuality ?? metadata.Quality, + }; + } - if (frameConfig == null) - { - throw new ArgumentException(nameof(color)); - } + private JpegFrameConfig GetFrameConfig(JpegMetadata metadata) + { + JpegEncodingColor color = this.options.ColorType ?? metadata.ColorType ?? JpegEncodingColor.YCbCrRatio420; + JpegFrameConfig frameConfig = Array.Find( + FrameConfigs, + cfg => cfg.EncodingColor == color); - return frameConfig; + if (frameConfig == null) + { + throw new ArgumentException(nameof(color)); } + + return frameConfig; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs index e6a83b0a5d..779ccf61e1 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncodingColor.cs @@ -1,63 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Provides enumeration of available JPEG color types. +/// +public enum JpegEncodingColor : byte { /// - /// Provides enumeration of available JPEG color types. - /// - public enum JpegEncodingColor : byte - { - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only - /// sampled on each alternate line. - /// - YCbCrRatio420 = 0, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// High Quality - Each of the three Y'CbCr components have the same sample rate, - /// thus there is no chroma subsampling. - /// - YCbCrRatio444 = 1, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution. - /// - YCbCrRatio422 = 2, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered. - /// - YCbCrRatio411 = 3, - - /// - /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. - /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. - /// - YCbCrRatio410 = 4, - - /// - /// Single channel, luminance. - /// - Luminance = 5, - - /// - /// The pixel data will be preserved as RGB without any sub sampling. - /// - Rgb = 6, - - /// - /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. - /// - Cmyk = 7, - - /// - /// YCCK colorspace (Y, Cb, Cr, and key black). - /// - Ycck = 8, - } + /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. + /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only + /// sampled on each alternate line. + /// + YCbCrRatio420 = 0, + + /// + /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. + /// High Quality - Each of the three Y'CbCr components have the same sample rate, + /// thus there is no chroma subsampling. + /// + YCbCrRatio444 = 1, + + /// + /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. + /// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution. + /// + YCbCrRatio422 = 2, + + /// + /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. + /// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered. + /// + YCbCrRatio411 = 3, + + /// + /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. + /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. + /// + YCbCrRatio410 = 4, + + /// + /// Single channel, luminance. + /// + Luminance = 5, + + /// + /// The pixel data will be preserved as RGB without any sub sampling. + /// + Rgb = 6, + + /// + /// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing. + /// + Cmyk = 7, + + /// + /// YCCK colorspace (Y, Cb, Cr, and key black). + /// + Ycck = 8, } diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index dd98849012..b9c126e29d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Jpeg; -namespace SixLabors.ImageSharp.Formats.Jpeg +/// +/// Registers the image encoders, decoders and mime type detectors for the jpeg format. +/// +public sealed class JpegFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. - /// - public sealed class JpegFormat : IImageFormat + private JpegFormat() { - private JpegFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static JpegFormat Instance { get; } = new JpegFormat(); + /// + /// Gets the current instance. + /// + public static JpegFormat Instance { get; } = new JpegFormat(); - /// - public string Name => "JPEG"; + /// + public string Name => "JPEG"; - /// - public string DefaultMimeType => "image/jpeg"; + /// + public string DefaultMimeType => "image/jpeg"; - /// - public IEnumerable MimeTypes => JpegConstants.MimeTypes; + /// + public IEnumerable MimeTypes => JpegConstants.MimeTypes; - /// - public IEnumerable FileExtensions => JpegConstants.FileExtensions; + /// + public IEnumerable FileExtensions => JpegConstants.FileExtensions; - /// - public JpegMetadata CreateDefaultFormatMetadata() => new JpegMetadata(); - } + /// + public JpegMetadata CreateDefaultFormatMetadata() => new JpegMetadata(); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index 61b4a36ad7..f2cc631c53 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -1,58 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Jpeg; -namespace SixLabors.ImageSharp.Formats.Jpeg +/// +/// Detects Jpeg file headers +/// +public sealed class JpegImageFormatDetector : IImageFormatDetector { - /// - /// Detects Jpeg file headers - /// - public sealed class JpegImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 11; + /// + public int HeaderSize => 11; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) - => this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + => this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) - => header.Length >= this.HeaderSize - && (IsJfif(header) || IsExif(header) || IsJpeg(header)); + private bool IsSupportedFileFormat(ReadOnlySpan header) + => header.Length >= this.HeaderSize + && (IsJfif(header) || IsExif(header) || IsJpeg(header)); - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(ReadOnlySpan header) => - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private static bool IsJfif(ReadOnlySpan header) => + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; - /// - /// Returns a value indicating whether the given bytes identify EXIF data. - /// - /// The bytes representing the file header. - /// The - private static bool IsExif(ReadOnlySpan header) => - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private static bool IsExif(ReadOnlySpan header) => + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(ReadOnlySpan header) => - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 - } + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private static bool IsJpeg(ReadOnlySpan header) => + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 63127f1eca..59fc2f9cba 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -1,108 +1,106 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +/// +/// Provides Jpeg specific metadata information for the image. +/// +public class JpegMetadata : IDeepCloneable { /// - /// Provides Jpeg specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class JpegMetadata : IDeepCloneable + public JpegMetadata() { - /// - /// Initializes a new instance of the class. - /// - public JpegMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private JpegMetadata(JpegMetadata other) - { - this.ColorType = other.ColorType; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private JpegMetadata(JpegMetadata other) + { + this.ColorType = other.ColorType; - this.LuminanceQuality = other.LuminanceQuality; - this.ChrominanceQuality = other.ChrominanceQuality; - } + this.LuminanceQuality = other.LuminanceQuality; + this.ChrominanceQuality = other.ChrominanceQuality; + } - /// - /// Gets or sets the jpeg luminance quality. - /// - /// - /// This value might not be accurate if it was calculated during jpeg decoding - /// with non-complient ITU quantization tables. - /// - internal int? LuminanceQuality { get; set; } + /// + /// Gets or sets the jpeg luminance quality. + /// + /// + /// This value might not be accurate if it was calculated during jpeg decoding + /// with non-complient ITU quantization tables. + /// + internal int? LuminanceQuality { get; set; } - /// - /// Gets or sets the jpeg chrominance quality. - /// - /// - /// This value might not be accurate if it was calculated during jpeg decoding - /// with non-complient ITU quantization tables. - /// - internal int? ChrominanceQuality { get; set; } + /// + /// Gets or sets the jpeg chrominance quality. + /// + /// + /// This value might not be accurate if it was calculated during jpeg decoding + /// with non-complient ITU quantization tables. + /// + internal int? ChrominanceQuality { get; set; } - /// - /// Gets the encoded quality. - /// - /// - /// Note that jpeg image can have different quality for luminance and chrominance components. - /// This property returns maximum value of luma/chroma qualities if both are present. - /// - public int Quality + /// + /// Gets the encoded quality. + /// + /// + /// Note that jpeg image can have different quality for luminance and chrominance components. + /// This property returns maximum value of luma/chroma qualities if both are present. + /// + public int Quality + { + get { - get + if (this.LuminanceQuality.HasValue) { - if (this.LuminanceQuality.HasValue) + if (this.ChrominanceQuality.HasValue) { - if (this.ChrominanceQuality.HasValue) - { - return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value); - } - - return this.LuminanceQuality.Value; + return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value); } - else - { - if (this.ChrominanceQuality.HasValue) - { - return this.ChrominanceQuality.Value; - } - return Quantization.DefaultQualityFactor; + return this.LuminanceQuality.Value; + } + else + { + if (this.ChrominanceQuality.HasValue) + { + return this.ChrominanceQuality.Value; } + + return Quantization.DefaultQualityFactor; } } + } - /// - /// Gets the color type. - /// - public JpegEncodingColor? ColorType { get; internal set; } + /// + /// Gets the color type. + /// + public JpegEncodingColor? ColorType { get; internal set; } - /// - /// Gets the component encoding mode. - /// - /// - /// Interleaved encoding mode encodes all color components in a single scan. - /// Non-interleaved encoding mode encodes each color component in a separate scan. - /// - public bool? Interleaved { get; internal set; } + /// + /// Gets the component encoding mode. + /// + /// + /// Interleaved encoding mode encodes all color components in a single scan. + /// Non-interleaved encoding mode encodes each color component in a separate scan. + /// + public bool? Interleaved { get; internal set; } - /// - /// Gets the scan encoding mode. - /// - /// - /// Progressive jpeg images encode component data across multiple scans. - /// - public bool? Progressive { get; internal set; } + /// + /// Gets the scan encoding mode. + /// + /// + /// Progressive jpeg images encode component data across multiple scans. + /// + public bool? Progressive { get; internal set; } - /// - public IDeepCloneable DeepClone() => new JpegMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new JpegMetadata(this); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index cbaafbc4e5..19eedf9f1d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Jpeg +namespace SixLabors.ImageSharp.Formats.Jpeg; + +internal static class JpegThrowHelper { - internal static class JpegThrowHelper - { - /// - /// Cold path optimization for throwing 's. - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); - /// - /// Cold path optimization for throwing 's. - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadQuantizationTablePrecision(int precision) => throw new InvalidImageContentException($"Unknown Quantization Table precision {precision}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadQuantizationTablePrecision(int precision) => throw new InvalidImageContentException($"Unknown Quantization Table precision {precision}."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}"); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}"); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedColorSpace() => throw new NotSupportedException("Image color space could not be deduced."); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedColorSpace() => throw new NotSupportedException("Image color space could not be deduced."); } diff --git a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs index cc89685a31..753dfdb60e 100644 --- a/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/MetadataExtensions.cs @@ -4,18 +4,17 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the jpeg format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the jpeg format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static JpegMetadata GetJpegMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(JpegFormat.Instance); } diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index c5d28002a0..d49633575d 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -1,194 +1,192 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Pixel decoding methods for the PBM binary encoding. +/// +internal class BinaryDecoder { + private static L8 white = new(255); + private static L8 black = new(0); + /// - /// Pixel decoding methods for the PBM binary encoding. + /// Decode the specified pixels. /// - internal class BinaryDecoder + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// Data type of the pixles components. + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel { - private static L8 white = new(255); - private static L8 black = new(0); - - /// - /// Decode the specified pixels. - /// - /// The type of pixel to encode to. - /// The configuration. - /// The pixel array to encode into. - /// The stream to read the data from. - /// The ColorType to decode. - /// Data type of the pixles components. - /// - /// Thrown if an invalid combination of setting is requested. - /// - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel + if (colorType == PbmColorType.Grayscale) { - if (colorType == PbmColorType.Grayscale) + if (componentType == PbmComponentType.Byte) { - if (componentType == PbmComponentType.Byte) - { - ProcessGrayscale(configuration, pixels, stream); - } - else - { - ProcessWideGrayscale(configuration, pixels, stream); - } + ProcessGrayscale(configuration, pixels, stream); } - else if (colorType == PbmColorType.Rgb) + else { - if (componentType == PbmComponentType.Byte) - { - ProcessRgb(configuration, pixels, stream); - } - else - { - ProcessWideRgb(configuration, pixels, stream); - } + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + ProcessRgb(configuration, pixels, stream); } else { - ProcessBlackAndWhite(configuration, pixels, stream); + ProcessWideRgb(configuration, pixels, stream); } } + else + { + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 1; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); - private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 1; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - stream.Read(rowSpan); - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8Bytes( + configuration, + rowSpan, + pixelSpan, + width); } + } + + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 2; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); - private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 2; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - stream.Read(rowSpan); - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL16Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL16Bytes( + configuration, + rowSpan, + pixelSpan, + width); } + } - private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 3; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 3; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - stream.Read(rowSpan); - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb24Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb24Bytes( + configuration, + rowSpan, + pixelSpan, + width); } + } - private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 6; + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 6; - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - stream.Read(rowSpan); - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb48Bytes( - configuration, - rowSpan, - pixelSpan, - width); - } + stream.Read(rowSpan); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb48Bytes( + configuration, + rowSpan, + pixelSpan, + width); } + } - private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int startBit = 0; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = pixels.Width; - int height = pixels.Height; - int startBit = 0; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) + for (int x = 0; x < width;) { - for (int x = 0; x < width;) + int raw = stream.ReadByte(); + int bit = startBit; + startBit = 0; + for (; bit < 8; bit++) { - int raw = stream.ReadByte(); - int bit = startBit; - startBit = 0; - for (; bit < 8; bit++) + bool bitValue = (raw & (0x80 >> bit)) != 0; + rowSpan[x] = bitValue ? black : white; + x++; + if (x == width) { - bool bitValue = (raw & (0x80 >> bit)) != 0; - rowSpan[x] = bitValue ? black : white; - x++; - if (x == width) + startBit = (bit + 1) & 7; // Round off to below 8. + if (startBit != 0) { - startBit = (bit + 1) & 7; // Round off to below 8. - if (startBit != 0) - { - stream.Seek(-1, System.IO.SeekOrigin.Current); - } - - break; + stream.Seek(-1, System.IO.SeekOrigin.Current); } + + break; } } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); } } } diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs index 67cbdd5220..b179c775cf 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -1,207 +1,204 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Pixel encoding methods for the PBM binary encoding. +/// +internal class BinaryEncoder { /// - /// Pixel encoding methods for the PBM binary encoding. + /// Decode pixels into the PBM binary encoding. /// - internal class BinaryEncoder + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// Data type of the pixles components. + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel { - /// - /// Decode pixels into the PBM binary encoding. - /// - /// The type of input pixel. - /// The configuration. - /// The bytestream to write to. - /// The input image. - /// The ColorType to use. - /// Data type of the pixles components. - /// - /// Thrown if an invalid combination of setting is requested. - /// - public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel + if (colorType == PbmColorType.Grayscale) { - if (colorType == PbmColorType.Grayscale) + if (componentType == PbmComponentType.Byte) { - if (componentType == PbmComponentType.Byte) - { - WriteGrayscale(configuration, stream, image); - } - else - { - WriteWideGrayscale(configuration, stream, image); - } + WriteGrayscale(configuration, stream, image); } - else if (colorType == PbmColorType.Rgb) + else { - if (componentType == PbmComponentType.Byte) - { - WriteRgb(configuration, stream, image); - } - else - { - WriteWideRgb(configuration, stream, image); - } + WriteWideGrayscale(configuration, stream, image); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + WriteRgb(configuration, stream, image); } else { - WriteBlackAndWhite(configuration, stream, image); + WriteWideRgb(configuration, stream, image); } } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + } - private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8Bytes( - configuration, - pixelSpan, - rowSpan, - width); + PixelOperations.Instance.ToL8Bytes( + configuration, + pixelSpan, + rowSpan, + width); - stream.Write(rowSpan); - } + stream.Write(rowSpan); } + } - private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 2; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 2; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL16Bytes( - configuration, - pixelSpan, - rowSpan, - width); + PixelOperations.Instance.ToL16Bytes( + configuration, + pixelSpan, + rowSpan, + width); - stream.Write(rowSpan); - } + stream.Write(rowSpan); } + } - private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 3; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 3; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb24Bytes( - configuration, - pixelSpan, - rowSpan, - width); + PixelOperations.Instance.ToRgb24Bytes( + configuration, + pixelSpan, + rowSpan, + width); - stream.Write(rowSpan); - } + stream.Write(rowSpan); } + } - private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + const int bytesPerPixel = 6; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) { - const int bytesPerPixel = 6; - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); - Span rowSpan = row.GetSpan(); - - for (int y = 0; y < height; y++) - { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb48Bytes( - configuration, - pixelSpan, - rowSpan, - width); + PixelOperations.Instance.ToRgb48Bytes( + configuration, + pixelSpan, + rowSpan, + width); - stream.Write(rowSpan); - } + stream.Write(rowSpan); } + } - private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + int previousValue = 0; + int startBit = 0; + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - - int previousValue = 0; - int startBit = 0; - for (int y = 0; y < height; y++) - { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); - for (int x = 0; x < width;) + for (int x = 0; x < width;) + { + int value = previousValue; + for (int i = startBit; i < 8; i++) { - int value = previousValue; - for (int i = startBit; i < 8; i++) + if (rowSpan[x].PackedValue < 128) { - if (rowSpan[x].PackedValue < 128) - { - value |= 0x80 >> i; - } - - x++; - if (x == width) - { - previousValue = value; - startBit = (i + 1) & 7; // Round off to below 8. - break; - } + value |= 0x80 >> i; } - if (startBit == 0) + x++; + if (x == width) { - stream.WriteByte((byte)value); - previousValue = 0; + previousValue = value; + startBit = (i + 1) & 7; // Round off to below 8. + break; } } + + if (startBit == 0) + { + stream.WriteByte((byte)value); + previousValue = 0; + } } } } diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index 4e740ce8e5..d62ca32807 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -1,65 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Extensions methods for . +/// +internal static class BufferedReadStreamExtensions { /// - /// Extensions methods for . + /// Skip over any whitespace or any comments. /// - internal static class BufferedReadStreamExtensions + public static void SkipWhitespaceAndComments(this BufferedReadStream stream) { - /// - /// Skip over any whitespace or any comments. - /// - public static void SkipWhitespaceAndComments(this BufferedReadStream stream) + bool isWhitespace; + do { - bool isWhitespace; - do - { - int val = stream.ReadByte(); + int val = stream.ReadByte(); - // Comments start with '#' and end at the next new-line. - if (val == 0x23) + // Comments start with '#' and end at the next new-line. + if (val == 0x23) + { + int innerValue; + do { - int innerValue; - do - { - innerValue = stream.ReadByte(); - } - while (innerValue != 0x0a); - - // Continue searching for whitespace. - val = innerValue; + innerValue = stream.ReadByte(); } + while (innerValue != 0x0a); - isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; + // Continue searching for whitespace. + val = innerValue; } - while (isWhitespace); - stream.Seek(-1, SeekOrigin.Current); + + isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; } + while (isWhitespace); + stream.Seek(-1, SeekOrigin.Current); + } - /// - /// Read a decimal text value. - /// - /// The integer value of the decimal. - public static int ReadDecimal(this BufferedReadStream stream) + /// + /// Read a decimal text value. + /// + /// The integer value of the decimal. + public static int ReadDecimal(this BufferedReadStream stream) + { + int value = 0; + while (true) { - int value = 0; - while (true) + int current = stream.ReadByte() - 0x30; + if ((uint)current > 9) { - int current = stream.ReadByte() - 0x30; - if ((uint)current > 9) - { - break; - } - - value = (value * 10) + current; + break; } - return value; + value = (value * 10) + current; } + + return value; } } diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs index fb413c3716..7039ef2620 100644 --- a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs +++ b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Configuration options for use during PBM encoding. +/// +internal interface IPbmEncoderOptions { /// - /// Configuration options for use during PBM encoding. + /// Gets the encoding of the pixels. /// - internal interface IPbmEncoderOptions - { - /// - /// Gets the encoding of the pixels. - /// - PbmEncoding? Encoding { get; } + PbmEncoding? Encoding { get; } - /// - /// Gets the Color type of the resulting image. - /// - PbmColorType? ColorType { get; } + /// + /// Gets the Color type of the resulting image. + /// + PbmColorType? ColorType { get; } - /// - /// Gets the Data Type of the pixel components. - /// - PbmComponentType? ComponentType { get; } - } + /// + /// Gets the Data Type of the pixel components. + /// + PbmComponentType? ComponentType { get; } } diff --git a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs index d63d66b84e..6d44e91a50 100644 --- a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs @@ -4,18 +4,17 @@ using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the pbm format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the pbm format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); } diff --git a/src/ImageSharp/Formats/Pbm/PbmColorType.cs b/src/ImageSharp/Formats/Pbm/PbmColorType.cs index 955654f01e..985fb70c06 100644 --- a/src/ImageSharp/Formats/Pbm/PbmColorType.cs +++ b/src/ImageSharp/Formats/Pbm/PbmColorType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Provides enumeration of available PBM color types. +/// +public enum PbmColorType : byte { /// - /// Provides enumeration of available PBM color types. + /// PBM /// - public enum PbmColorType : byte - { - /// - /// PBM - /// - BlackAndWhite = 0, + BlackAndWhite = 0, - /// - /// PGM - Greyscale. Single component. - /// - Grayscale = 1, + /// + /// PGM - Greyscale. Single component. + /// + Grayscale = 1, - /// - /// PPM - RGB Color. 3 components. - /// - Rgb = 2, - } + /// + /// PPM - RGB Color. 3 components. + /// + Rgb = 2, } diff --git a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs index de5c0ec94e..d350bb2ef6 100644 --- a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs +++ b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// The data type of the components of the pixels. +/// +public enum PbmComponentType : byte { /// - /// The data type of the components of the pixels. + /// Single bit per pixel, exclusively for . /// - public enum PbmComponentType : byte - { - /// - /// Single bit per pixel, exclusively for . - /// - Bit = 0, + Bit = 0, - /// - /// 8 bits unsigned integer per component. - /// - Byte = 1, + /// + /// 8 bits unsigned integer per component. + /// + Byte = 1, - /// - /// 16 bits unsigned integer per component. - /// - Short = 2 - } + /// + /// 16 bits unsigned integer per component. + /// + Short = 2 } diff --git a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs index fa521a3661..cd411a2d75 100644 --- a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs +++ b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Registers the image encoders, decoders and mime type detectors for the Pbm format. +/// +public sealed class PbmConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the Pbm format. - /// - public sealed class PbmConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); - configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); + configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs index 6c805d406c..fe0088c3ce 100644 --- a/src/ImageSharp/Formats/Pbm/PbmConstants.cs +++ b/src/ImageSharp/Formats/Pbm/PbmConstants.cs @@ -1,28 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Pbm; -namespace SixLabors.ImageSharp.Formats.Pbm +/// +/// Contains PBM constant values defined in the specification. +/// +internal static class PbmConstants { /// - /// Contains PBM constant values defined in the specification. + /// The maximum allowable pixel value of a ppm image. /// - internal static class PbmConstants - { - /// - /// The maximum allowable pixel value of a ppm image. - /// - public const ushort MaxLength = 65535; + public const ushort MaxLength = 65535; - /// - /// The list of mimetypes that equate to a ppm. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; + /// + /// The list of mimetypes that equate to a ppm. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; - /// - /// The list of file extensions that equate to a ppm. - /// - public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm" }; - } + /// + /// The list of file extensions that equate to a ppm. + /// + public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm" }; } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 394da0a60f..2caf8ecc1f 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -1,58 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from +/// the family of PNM images. +/// +/// +/// PBM +/// Black and white images. +/// +/// +/// PGM +/// Grayscale images. +/// +/// +/// PPM +/// Color images, with RGB pixels. +/// +/// +/// The specification of these images is found at . +/// +public sealed class PbmDecoder : IImageDecoder { - /// - /// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from - /// the family of PNM images. - /// - /// - /// PBM - /// Black and white images. - /// - /// - /// PGM - /// Grayscale images. - /// - /// - /// PPM - /// Color images, with RGB pixels. - /// - /// - /// The specification of these images is found at . - /// - public sealed class PbmDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } - - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + return new PbmDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } - PbmDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options, image); + PbmDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options, image); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoder)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 05f06deaae..d5ea66482e 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -1,204 +1,201 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Performs the PBM decoding operation. +/// +internal sealed class PbmDecoderCore : IImageDecoderInternals { + private int maxPixelValue; + /// - /// Performs the PBM decoding operation. + /// The general configuration. /// - internal sealed class PbmDecoderCore : IImageDecoderInternals - { - private int maxPixelValue; - - /// - /// The general configuration. - /// - private readonly Configuration configuration; - - /// - /// The colortype to use - /// - private PbmColorType colorType; - - /// - /// The size of the pixel array - /// - private Size pixelSize; - - /// - /// The component data type - /// - private PbmComponentType componentType; - - /// - /// The Encoding of pixels - /// - private PbmEncoding encoding; - - /// - /// The decoded by this decoder instance. - /// - private ImageMetadata metadata; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public PbmDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - } + private readonly Configuration configuration; + + /// + /// The colortype to use + /// + private PbmColorType colorType; - /// - public DecoderOptions Options { get; } + /// + /// The size of the pixel array + /// + private Size pixelSize; + + /// + /// The component data type + /// + private PbmComponentType componentType; - /// - public Size Dimensions => this.pixelSize; + /// + /// The Encoding of pixels + /// + private PbmEncoding encoding; - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - this.ProcessHeader(stream); + /// + /// The decoded by this decoder instance. + /// + private ImageMetadata metadata; - var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public PbmDecoderCore(DecoderOptions options) + { + this.Options = options; + this.configuration = options.Configuration; + } - Buffer2D pixels = image.GetRootFramePixelBuffer(); + /// + public DecoderOptions Options { get; } - this.ProcessPixels(stream, pixels); - if (this.NeedsUpscaling()) - { - this.ProcessUpscaling(image); - } + /// + public Size Dimensions => this.pixelSize; - return image; - } + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ProcessHeader(stream); - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.ProcessHeader(stream); + var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); - // BlackAndWhite pixels are encoded into a byte. - int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8; - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata); - } + Buffer2D pixels = image.GetRootFramePixelBuffer(); - /// - /// Processes the ppm header. - /// - /// The input stream. - private void ProcessHeader(BufferedReadStream stream) + this.ProcessPixels(stream, pixels); + if (this.NeedsUpscaling()) { - Span buffer = stackalloc byte[2]; + this.ProcessUpscaling(image); + } - int bytesRead = stream.Read(buffer); - if (bytesRead != 2 || buffer[0] != 'P') - { - throw new InvalidImageContentException("Empty or not an PPM image."); - } + return image; + } - switch ((char)buffer[1]) - { - case '1': - // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). - this.colorType = PbmColorType.BlackAndWhite; - this.encoding = PbmEncoding.Plain; - break; - case '2': - // Plain PGM format: 1 component per pixel, in decimal text. - this.colorType = PbmColorType.Grayscale; - this.encoding = PbmEncoding.Plain; - break; - case '3': - // Plain PPM format: 3 components per pixel, in decimal text. - this.colorType = PbmColorType.Rgb; - this.encoding = PbmEncoding.Plain; - break; - case '4': - // Binary PBM format: 1 component per pixel, 8 pixels per byte. - this.colorType = PbmColorType.BlackAndWhite; - this.encoding = PbmEncoding.Binary; - break; - case '5': - // Binary PGM format: 1 components per pixel, in binary integers. - this.colorType = PbmColorType.Grayscale; - this.encoding = PbmEncoding.Binary; - break; - case '6': - // Binary PPM format: 3 components per pixel, in binary integers. - this.colorType = PbmColorType.Rgb; - this.encoding = PbmEncoding.Binary; - break; - case '7': - // PAM image: sequence of images. - // Not implemented yet - default: - throw new InvalidImageContentException("Unknown of not implemented image type encountered."); - } + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ProcessHeader(stream); - stream.SkipWhitespaceAndComments(); - int width = stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - int height = stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - if (this.colorType != PbmColorType.BlackAndWhite) - { - this.maxPixelValue = stream.ReadDecimal(); - if (this.maxPixelValue > 255) - { - this.componentType = PbmComponentType.Short; - } - else - { - this.componentType = PbmComponentType.Byte; - } - - stream.SkipWhitespaceAndComments(); - } - else - { - this.componentType = PbmComponentType.Bit; - } + // BlackAndWhite pixels are encoded into a byte. + int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8; + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata); + } + + /// + /// Processes the ppm header. + /// + /// The input stream. + private void ProcessHeader(BufferedReadStream stream) + { + Span buffer = stackalloc byte[2]; - this.pixelSize = new Size(width, height); - this.metadata = new ImageMetadata(); - PbmMetadata meta = this.metadata.GetPbmMetadata(); - meta.Encoding = this.encoding; - meta.ColorType = this.colorType; - meta.ComponentType = this.componentType; + int bytesRead = stream.Read(buffer); + if (bytesRead != 2 || buffer[0] != 'P') + { + throw new InvalidImageContentException("Empty or not an PPM image."); + } + + switch ((char)buffer[1]) + { + case '1': + // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). + this.colorType = PbmColorType.BlackAndWhite; + this.encoding = PbmEncoding.Plain; + break; + case '2': + // Plain PGM format: 1 component per pixel, in decimal text. + this.colorType = PbmColorType.Grayscale; + this.encoding = PbmEncoding.Plain; + break; + case '3': + // Plain PPM format: 3 components per pixel, in decimal text. + this.colorType = PbmColorType.Rgb; + this.encoding = PbmEncoding.Plain; + break; + case '4': + // Binary PBM format: 1 component per pixel, 8 pixels per byte. + this.colorType = PbmColorType.BlackAndWhite; + this.encoding = PbmEncoding.Binary; + break; + case '5': + // Binary PGM format: 1 components per pixel, in binary integers. + this.colorType = PbmColorType.Grayscale; + this.encoding = PbmEncoding.Binary; + break; + case '6': + // Binary PPM format: 3 components per pixel, in binary integers. + this.colorType = PbmColorType.Rgb; + this.encoding = PbmEncoding.Binary; + break; + case '7': + // PAM image: sequence of images. + // Not implemented yet + default: + throw new InvalidImageContentException("Unknown of not implemented image type encountered."); } - private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel + stream.SkipWhitespaceAndComments(); + int width = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + int height = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + if (this.colorType != PbmColorType.BlackAndWhite) { - if (this.encoding == PbmEncoding.Binary) + this.maxPixelValue = stream.ReadDecimal(); + if (this.maxPixelValue > 255) { - BinaryDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); + this.componentType = PbmComponentType.Short; } else { - PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); + this.componentType = PbmComponentType.Byte; } + + stream.SkipWhitespaceAndComments(); + } + else + { + this.componentType = PbmComponentType.Bit; } - private void ProcessUpscaling(Image image) - where TPixel : unmanaged, IPixel + this.pixelSize = new Size(width, height); + this.metadata = new ImageMetadata(); + PbmMetadata meta = this.metadata.GetPbmMetadata(); + meta.Encoding = this.encoding; + meta.ColorType = this.colorType; + meta.ComponentType = this.componentType; + } + + private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + if (this.encoding == PbmEncoding.Binary) + { + BinaryDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); + } + else { - int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255; - float factor = maxAllocationValue / this.maxPixelValue; - image.Mutate(x => x.Brightness(factor)); + PlainDecoder.Process(this.configuration, pixels, stream, this.colorType, this.componentType); } + } - private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; + private void ProcessUpscaling(Image image) + where TPixel : unmanaged, IPixel + { + int maxAllocationValue = this.componentType == PbmComponentType.Short ? 65535 : 255; + float factor = maxAllocationValue / this.maxPixelValue; + image.Mutate(x => x.Brightness(factor)); } + + private bool NeedsUpscaling() => this.colorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs index 9bfd7192d7..e0e93cc11e 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -1,69 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from +/// the family of PNM images. +/// +/// The PNM formats are a fairly simple image format. They share a plain text header, consisting of: +/// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in +/// plain text decimals separated by spaces, or binary encoded. +/// +/// +/// PBM +/// Black and white images, with 1 representing black and 0 representing white. +/// +/// +/// PGM +/// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white. +/// +/// +/// PPM +/// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color. +/// +/// +/// +/// The specification of these images is found at . +/// +public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions { /// - /// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from - /// the family of PNM images. - /// - /// The PNM formats are a fairly simple image format. They share a plain text header, consisting of: - /// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in - /// plain text decimals separated by spaces, or binary encoded. - /// - /// - /// PBM - /// Black and white images, with 1 representing black and 0 representing white. - /// - /// - /// PGM - /// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white. - /// - /// - /// PPM - /// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color. - /// - /// - /// - /// The specification of these images is found at . + /// Gets or sets the Encoding of the pixels. /// - public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions - { - /// - /// Gets or sets the Encoding of the pixels. - /// - public PbmEncoding? Encoding { get; set; } + public PbmEncoding? Encoding { get; set; } - /// - /// Gets or sets the Color type of the resulting image. - /// - public PbmColorType? ColorType { get; set; } + /// + /// Gets or sets the Color type of the resulting image. + /// + public PbmColorType? ColorType { get; set; } - /// - /// Gets or sets the data type of the pixels components. - /// - public PbmComponentType? ComponentType { get; set; } + /// + /// Gets or sets the data type of the pixels components. + /// + public PbmComponentType? ComponentType { get; set; } - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new PbmEncoderCore(image.GetConfiguration(), this); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new PbmEncoderCore(image.GetConfiguration(), this); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs index 431eea4c42..c7d8d183c2 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -1,187 +1,183 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Text; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap. +/// +internal sealed class PbmEncoderCore : IImageEncoderInternals { + private const byte NewLine = (byte)'\n'; + private const byte Space = (byte)' '; + private const byte P = (byte)'P'; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder options. + /// + private readonly IPbmEncoderOptions options; + + /// + /// The encoding for the pixels. + /// + private PbmEncoding encoding; + + /// + /// Gets the Color type of the resulting image. + /// + private PbmColorType colorType; + + /// + /// Gets the maximum pixel value, per component. + /// + private PbmComponentType componentType; + /// - /// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap. + /// Initializes a new instance of the class. /// - internal sealed class PbmEncoderCore : IImageEncoderInternals + /// The configuration. + /// The encoder options. + public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) { - private const byte NewLine = (byte)'\n'; - private const byte Space = (byte)' '; - private const byte P = (byte)'P'; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// The encoder options. - /// - private readonly IPbmEncoderOptions options; - - /// - /// The encoding for the pixels. - /// - private PbmEncoding encoding; - - /// - /// Gets the Color type of the resulting image. - /// - private PbmColorType colorType; - - /// - /// Gets the maximum pixel value, per component. - /// - private PbmComponentType componentType; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The encoder options. - public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) - { - this.configuration = configuration; - this.options = options; - } + this.configuration = configuration; + this.options = options; + } - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - this.DeduceOptions(image); + this.DeduceOptions(image); - byte signature = this.DeduceSignature(); - this.WriteHeader(stream, signature, image.Size()); + byte signature = this.DeduceSignature(); + this.WriteHeader(stream, signature, image.Size()); - this.WritePixels(stream, image.Frames.RootFrame); + this.WritePixels(stream, image.Frames.RootFrame); - stream.Flush(); + stream.Flush(); + } + + private void DeduceOptions(Image image) + where TPixel : unmanaged, IPixel + { + this.configuration = image.GetConfiguration(); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + this.encoding = this.options.Encoding ?? metadata.Encoding; + this.colorType = this.options.ColorType ?? metadata.ColorType; + if (this.colorType != PbmColorType.BlackAndWhite) + { + this.componentType = this.options.ComponentType ?? metadata.ComponentType; } + else + { + this.componentType = PbmComponentType.Bit; + } + } - private void DeduceOptions(Image image) - where TPixel : unmanaged, IPixel + private byte DeduceSignature() + { + byte signature; + if (this.colorType == PbmColorType.BlackAndWhite) { - this.configuration = image.GetConfiguration(); - PbmMetadata metadata = image.Metadata.GetPbmMetadata(); - this.encoding = this.options.Encoding ?? metadata.Encoding; - this.colorType = this.options.ColorType ?? metadata.ColorType; - if (this.colorType != PbmColorType.BlackAndWhite) + if (this.encoding == PbmEncoding.Plain) { - this.componentType = this.options.ComponentType ?? metadata.ComponentType; + signature = (byte)'1'; } else { - this.componentType = PbmComponentType.Bit; + signature = (byte)'4'; } } - - private byte DeduceSignature() + else if (this.colorType == PbmColorType.Grayscale) { - byte signature; - if (this.colorType == PbmColorType.BlackAndWhite) + if (this.encoding == PbmEncoding.Plain) + { + signature = (byte)'2'; + } + else { - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'1'; - } - else - { - signature = (byte)'4'; - } + signature = (byte)'5'; } - else if (this.colorType == PbmColorType.Grayscale) + } + else + { + // RGB ColorType + if (this.encoding == PbmEncoding.Plain) { - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'2'; - } - else - { - signature = (byte)'5'; - } + signature = (byte)'3'; } else { - // RGB ColorType - if (this.encoding == PbmEncoding.Plain) - { - signature = (byte)'3'; - } - else - { - signature = (byte)'6'; - } + signature = (byte)'6'; } - - return signature; } - private void WriteHeader(Stream stream, byte signature, Size pixelSize) - { - Span buffer = stackalloc byte[128]; + return signature; + } - int written = 3; - buffer[0] = P; - buffer[1] = signature; - buffer[2] = NewLine; + private void WriteHeader(Stream stream, byte signature, Size pixelSize) + { + Span buffer = stackalloc byte[128]; - Utf8Formatter.TryFormat(pixelSize.Width, buffer[written..], out int bytesWritten); - written += bytesWritten; - buffer[written++] = Space; - Utf8Formatter.TryFormat(pixelSize.Height, buffer[written..], out bytesWritten); + int written = 3; + buffer[0] = P; + buffer[1] = signature; + buffer[2] = NewLine; + + Utf8Formatter.TryFormat(pixelSize.Width, buffer[written..], out int bytesWritten); + written += bytesWritten; + buffer[written++] = Space; + Utf8Formatter.TryFormat(pixelSize.Height, buffer[written..], out bytesWritten); + written += bytesWritten; + buffer[written++] = NewLine; + + if (this.colorType != PbmColorType.BlackAndWhite) + { + int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; + Utf8Formatter.TryFormat(maxPixelValue, buffer[written..], out bytesWritten); written += bytesWritten; buffer[written++] = NewLine; + } - if (this.colorType != PbmColorType.BlackAndWhite) - { - int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; - Utf8Formatter.TryFormat(maxPixelValue, buffer[written..], out bytesWritten); - written += bytesWritten; - buffer[written++] = NewLine; - } + stream.Write(buffer, 0, written); + } - stream.Write(buffer, 0, written); + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WritePixels(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + if (this.encoding == PbmEncoding.Plain) + { + PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); } - - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The to write to. - /// - /// The containing pixel data. - /// - private void WritePixels(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + else { - if (this.encoding == PbmEncoding.Plain) - { - PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); - } - else - { - BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); - } + BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); } } } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs index aa42e85c28..ed43b1f007 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Provides enumeration of available PBM encodings. +/// +public enum PbmEncoding : byte { /// - /// Provides enumeration of available PBM encodings. + /// Plain text decimal encoding. /// - public enum PbmEncoding : byte - { - /// - /// Plain text decimal encoding. - /// - Plain = 0, + Plain = 0, - /// - /// Binary integer encoding. - /// - Binary = 1, - } + /// + /// Binary integer encoding. + /// + Binary = 1, } diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs index e644250a11..bdf5e785ec 100644 --- a/src/ImageSharp/Formats/Pbm/PbmFormat.cs +++ b/src/ImageSharp/Formats/Pbm/PbmFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Pbm; -namespace SixLabors.ImageSharp.Formats.Pbm +/// +/// Registers the image encoders, decoders and mime type detectors for the PBM format. +/// +public sealed class PbmFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the PBM format. - /// - public sealed class PbmFormat : IImageFormat + private PbmFormat() { - private PbmFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static PbmFormat Instance { get; } = new(); + /// + /// Gets the current instance. + /// + public static PbmFormat Instance { get; } = new(); - /// - public string Name => "PBM"; + /// + public string Name => "PBM"; - /// - public string DefaultMimeType => "image/x-portable-pixmap"; + /// + public string DefaultMimeType => "image/x-portable-pixmap"; - /// - public IEnumerable MimeTypes => PbmConstants.MimeTypes; + /// + public IEnumerable MimeTypes => PbmConstants.MimeTypes; - /// - public IEnumerable FileExtensions => PbmConstants.FileExtensions; + /// + public IEnumerable FileExtensions => PbmConstants.FileExtensions; - /// - public PbmMetadata CreateDefaultFormatMetadata() => new(); - } + /// + public PbmMetadata CreateDefaultFormatMetadata() => new(); } diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs index 4446d4b186..c982042e02 100644 --- a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs @@ -1,34 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Pbm; -namespace SixLabors.ImageSharp.Formats.Pbm +/// +/// Detects Pbm file headers. +/// +public sealed class PbmImageFormatDetector : IImageFormatDetector { - /// - /// Detects Pbm file headers. - /// - public sealed class PbmImageFormatDetector : IImageFormatDetector - { - private const byte P = (byte)'P'; - private const byte Zero = (byte)'0'; - private const byte Seven = (byte)'7'; + private const byte P = (byte)'P'; + private const byte Zero = (byte)'0'; + private const byte Seven = (byte)'7'; - /// - public int HeaderSize => 2; + /// + public int HeaderSize => 2; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) => IsSupportedFileFormat(header) ? PbmFormat.Instance : null; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => IsSupportedFileFormat(header) ? PbmFormat.Instance : null; - private static bool IsSupportedFileFormat(ReadOnlySpan header) + private static bool IsSupportedFileFormat(ReadOnlySpan header) + { + if ((uint)header.Length > 1) { - if ((uint)header.Length > 1) - { - // Signature should be between P1 and P6. - return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); - } - - return false; + // Signature should be between P1 and P6. + return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); } + + return false; } } diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index 6806ffdb93..938e11db16 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -1,46 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Provides PBM specific metadata information for the image. +/// +public class PbmMetadata : IDeepCloneable { /// - /// Provides PBM specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class PbmMetadata : IDeepCloneable - { - /// - /// Initializes a new instance of the class. - /// - public PbmMetadata() => - this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; + public PbmMetadata() => + this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PbmMetadata(PbmMetadata other) - { - this.Encoding = other.Encoding; - this.ColorType = other.ColorType; - this.ComponentType = other.ComponentType; - } + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PbmMetadata(PbmMetadata other) + { + this.Encoding = other.Encoding; + this.ColorType = other.ColorType; + this.ComponentType = other.ComponentType; + } - /// - /// Gets or sets the encoding of the pixels. - /// - public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; + /// + /// Gets or sets the encoding of the pixels. + /// + public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; - /// - /// Gets or sets the color type. - /// - public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; + /// + /// Gets or sets the color type. + /// + public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; - /// - /// Gets or sets the data type of the pixel components. - /// - public PbmComponentType ComponentType { get; set; } + /// + /// Gets or sets the data type of the pixel components. + /// + public PbmComponentType ComponentType { get; set; } - /// - public IDeepCloneable DeepClone() => new PbmMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new PbmMetadata(this); } diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index 203cd18f1e..f6d803684c 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -1,198 +1,196 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Pixel decoding methods for the PBM plain encoding. +/// +internal class PlainDecoder { + private static readonly L8 White = new(255); + private static readonly L8 Black = new(0); + /// - /// Pixel decoding methods for the PBM plain encoding. + /// Decode the specified pixels. /// - internal class PlainDecoder + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// Data type of the pixles components. + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel { - private static readonly L8 White = new(255); - private static readonly L8 Black = new(0); - - /// - /// Decode the specified pixels. - /// - /// The type of pixel to encode to. - /// The configuration. - /// The pixel array to encode into. - /// The stream to read the data from. - /// The ColorType to decode. - /// Data type of the pixles components. - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel + if (colorType == PbmColorType.Grayscale) { - if (colorType == PbmColorType.Grayscale) + if (componentType == PbmComponentType.Byte) { - if (componentType == PbmComponentType.Byte) - { - ProcessGrayscale(configuration, pixels, stream); - } - else - { - ProcessWideGrayscale(configuration, pixels, stream); - } + ProcessGrayscale(configuration, pixels, stream); } - else if (colorType == PbmColorType.Rgb) + else { - if (componentType == PbmComponentType.Byte) - { - ProcessRgb(configuration, pixels, stream); - } - else - { - ProcessWideRgb(configuration, pixels, stream); - } + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) + { + ProcessRgb(configuration, pixels, stream); } else { - ProcessBlackAndWhite(configuration, pixels, stream); + ProcessWideRgb(configuration, pixels, stream); } } - - private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel + else { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - byte value = (byte)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - rowSpan[x] = new L8(value); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); + byte value = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L8(value); } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); } + } - private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - ushort value = (ushort)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - rowSpan[x] = new L16(value); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL16( - configuration, - rowSpan, - pixelSpan); + ushort value = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L16(value); } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL16( + configuration, + rowSpan, + pixelSpan); } + } - private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - byte red = (byte)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - byte green = (byte)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - byte blue = (byte)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - rowSpan[x] = new Rgb24(red, green, blue); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb24( - configuration, - rowSpan, - pixelSpan); + byte red = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte green = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte blue = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb24(red, green, blue); } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb24( + configuration, + rowSpan, + pixelSpan); } + } - private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - ushort red = (ushort)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - ushort green = (ushort)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - ushort blue = (ushort)stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - rowSpan[x] = new Rgb48(red, green, blue); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromRgb48( - configuration, - rowSpan, - pixelSpan); + ushort red = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort green = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort blue = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb48(red, green, blue); } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromRgb48( + configuration, + rowSpan, + pixelSpan); } + } - private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) - where TPixel : unmanaged, IPixel - { - int width = pixels.Width; - int height = pixels.Height; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int value = stream.ReadDecimal(); - stream.SkipWhitespaceAndComments(); - rowSpan[x] = value == 0 ? White : Black; - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8( - configuration, - rowSpan, - pixelSpan); + int value = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = value == 0 ? White : Black; } + + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); } } } diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index d989b6e660..29260f54aa 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -1,251 +1,248 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Text; -using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Pbm +namespace SixLabors.ImageSharp.Formats.Pbm; + +/// +/// Pixel encoding methods for the PBM plain encoding. +/// +internal class PlainEncoder { + private const byte NewLine = 0x0a; + private const byte Space = 0x20; + private const byte Zero = 0x30; + private const byte One = 0x31; + + private const int MaxCharsPerPixelBlackAndWhite = 2; + private const int MaxCharsPerPixelGrayscale = 4; + private const int MaxCharsPerPixelGrayscaleWide = 6; + private const int MaxCharsPerPixelRgb = 4 * 3; + private const int MaxCharsPerPixelRgbWide = 6 * 3; + + private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); + /// - /// Pixel encoding methods for the PBM plain encoding. + /// Decode pixels into the PBM plain encoding. /// - internal class PlainEncoder + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// Data type of the pixles components. + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) + where TPixel : unmanaged, IPixel { - private const byte NewLine = 0x0a; - private const byte Space = 0x20; - private const byte Zero = 0x30; - private const byte One = 0x31; - - private const int MaxCharsPerPixelBlackAndWhite = 2; - private const int MaxCharsPerPixelGrayscale = 4; - private const int MaxCharsPerPixelGrayscaleWide = 6; - private const int MaxCharsPerPixelRgb = 4 * 3; - private const int MaxCharsPerPixelRgbWide = 6 * 3; - - private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); - - /// - /// Decode pixels into the PBM plain encoding. - /// - /// The type of input pixel. - /// The configuration. - /// The bytestream to write to. - /// The input image. - /// The ColorType to use. - /// Data type of the pixles components. - public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) - where TPixel : unmanaged, IPixel + if (colorType == PbmColorType.Grayscale) { - if (colorType == PbmColorType.Grayscale) + if (componentType == PbmComponentType.Byte) + { + WriteGrayscale(configuration, stream, image); + } + else { - if (componentType == PbmComponentType.Byte) - { - WriteGrayscale(configuration, stream, image); - } - else - { - WriteWideGrayscale(configuration, stream, image); - } + WriteWideGrayscale(configuration, stream, image); } - else if (colorType == PbmColorType.Rgb) + } + else if (colorType == PbmColorType.Rgb) + { + if (componentType == PbmComponentType.Byte) { - if (componentType == PbmComponentType.Byte) - { - WriteRgb(configuration, stream, image); - } - else - { - WriteWideRgb(configuration, stream, image); - } + WriteRgb(configuration, stream, image); } else { - WriteBlackAndWhite(configuration, stream, image); + WriteWideRgb(configuration, stream, image); } - - // Write EOF indicator, as some encoders expect it. - stream.WriteByte(Space); } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + + // Write EOF indicator, as some encoders expect it. + stream.WriteByte(Space); + } - private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } + } - private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL16( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL16( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan[written..], out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } + } - private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgb24( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb24( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } + } - private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgb48( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgb48( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); - written += bytesWritten; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan[written..], out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan[written..], out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan[written..], out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } + } - private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); + Span plainSpan = plainMemory.GetSpan(); + + for (int y = 0; y < height; y++) { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.PixelBuffer; - MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner row = allocator.Allocate(width); - Span rowSpan = row.GetSpan(); - using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); - Span plainSpan = plainMemory.GetSpan(); - - for (int y = 0; y < height; y++) + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + int written = 0; + for (int x = 0; x < width; x++) { - Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8( - configuration, - pixelSpan, - rowSpan); - - int written = 0; - for (int x = 0; x < width; x++) - { - byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; - plainSpan[written++] = value; - plainSpan[written++] = Space; - } - - plainSpan[written - 1] = NewLine; - stream.Write(plainSpan, 0, written); + byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; + plainSpan[written++] = value; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } } diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index 0d15eebc73..79ea884ce4 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -4,52 +4,51 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats +namespace SixLabors.ImageSharp.Formats; + +/// +/// Contains information about the pixels that make up an images visual data. +/// +public class PixelTypeInfo { /// - /// Contains information about the pixels that make up an images visual data. + /// Initializes a new instance of the class. + /// + /// Color depth, in number of bits per pixel. + public PixelTypeInfo(int bitsPerPixel) + { + this.BitsPerPixel = bitsPerPixel; + } + + /// + /// Initializes a new instance of the class. + /// + /// Color depth, in number of bits per pixel. + /// The pixel alpha transparency behavior. + public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation alpha) + { + this.BitsPerPixel = bitsPerPixel; + this.AlphaRepresentation = alpha; + } + + /// + /// Gets color depth, in number of bits per pixel. + /// + public int BitsPerPixel { get; } + + /// + /// Gets the pixel alpha transparency behavior. + /// means unknown, unspecified. /// - public class PixelTypeInfo + public PixelAlphaRepresentation? AlphaRepresentation { get; } + + internal static PixelTypeInfo Create() + where TPixel : unmanaged, IPixel => + new PixelTypeInfo(Unsafe.SizeOf() * 8); + + internal static PixelTypeInfo Create(PixelAlphaRepresentation alpha) + where TPixel : unmanaged, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// Color depth, in number of bits per pixel. - public PixelTypeInfo(int bitsPerPixel) - { - this.BitsPerPixel = bitsPerPixel; - } - - /// - /// Initializes a new instance of the class. - /// - /// Color depth, in number of bits per pixel. - /// The pixel alpha transparency behavior. - public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation alpha) - { - this.BitsPerPixel = bitsPerPixel; - this.AlphaRepresentation = alpha; - } - - /// - /// Gets color depth, in number of bits per pixel. - /// - public int BitsPerPixel { get; } - - /// - /// Gets the pixel alpha transparency behavior. - /// means unknown, unspecified. - /// - public PixelAlphaRepresentation? AlphaRepresentation { get; } - - internal static PixelTypeInfo Create() - where TPixel : unmanaged, IPixel => - new PixelTypeInfo(Unsafe.SizeOf() * 8); - - internal static PixelTypeInfo Create(PixelAlphaRepresentation alpha) - where TPixel : unmanaged, IPixel - { - return new PixelTypeInfo(Unsafe.SizeOf() * 8, alpha); - } + return new PixelTypeInfo(Unsafe.SizeOf() * 8, alpha); } } diff --git a/src/ImageSharp/Formats/Png/Adam7.cs b/src/ImageSharp/Formats/Png/Adam7.cs index 788ff30b69..f52a66c265 100644 --- a/src/ImageSharp/Formats/Png/Adam7.cs +++ b/src/ImageSharp/Formats/Png/Adam7.cs @@ -1,84 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Constants and helper methods for the Adam7 interlacing algorithm. +/// +internal static class Adam7 { /// - /// Constants and helper methods for the Adam7 interlacing algorithm. + /// The amount to increment when processing each column per scanline for each interlaced pass. /// - internal static class Adam7 - { - /// - /// The amount to increment when processing each column per scanline for each interlaced pass. - /// - public static readonly int[] ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; + public static readonly int[] ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; - /// - /// The index to start at when processing each column per scanline for each interlaced pass. - /// - public static readonly int[] FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; + /// + /// The index to start at when processing each column per scanline for each interlaced pass. + /// + public static readonly int[] FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; - /// - /// The index to start at when processing each row per scanline for each interlaced pass. - /// - public static readonly int[] FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; + /// + /// The index to start at when processing each row per scanline for each interlaced pass. + /// + public static readonly int[] FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; - /// - /// The amount to increment when processing each row per scanline for each interlaced pass. - /// - public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; + /// + /// The amount to increment when processing each row per scanline for each interlaced pass. + /// + public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; - /// - /// Gets the width of the block. - /// - /// The width. - /// The pass. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeBlockWidth(int width, int pass) - { - return (width + ColumnIncrement[pass] - 1 - FirstColumn[pass]) / ColumnIncrement[pass]; - } + /// + /// Gets the width of the block. + /// + /// The width. + /// The pass. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeBlockWidth(int width, int pass) + { + return (width + ColumnIncrement[pass] - 1 - FirstColumn[pass]) / ColumnIncrement[pass]; + } - /// - /// Gets the height of the block. - /// - /// The height. - /// The pass. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeBlockHeight(int height, int pass) - { - return (height + RowIncrement[pass] - 1 - FirstRow[pass]) / RowIncrement[pass]; - } + /// + /// Gets the height of the block. + /// + /// The height. + /// The pass. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeBlockHeight(int height, int pass) + { + return (height + RowIncrement[pass] - 1 - FirstRow[pass]) / RowIncrement[pass]; + } - /// - /// Returns the correct number of columns for each interlaced pass. - /// - /// The line width. - /// The current pass index. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ComputeColumns(int width, int passIndex) + /// + /// Returns the correct number of columns for each interlaced pass. + /// + /// The line width. + /// The current pass index. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ComputeColumns(int width, int passIndex) + { + switch (passIndex) { - switch (passIndex) - { - case 0: return (width + 7) / 8; - case 1: return (width + 3) / 8; - case 2: return (width + 3) / 4; - case 3: return (width + 1) / 4; - case 4: return (width + 1) / 2; - case 5: return width / 2; - case 6: return width; - default: throw new ArgumentException($"Not a valid pass index: {passIndex}"); - } + case 0: return (width + 7) / 8; + case 1: return (width + 3) / 8; + case 2: return (width + 3) / 4; + case 3: return (width + 1) / 4; + case 4: return (width + 1) / 2; + case 5: return width / 2; + case 6: return width; + default: throw new ArgumentException($"Not a valid pass index: {passIndex}"); } } } diff --git a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs index 31b845e19b..34d53f00eb 100644 --- a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs +++ b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs @@ -1,110 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp.Formats.Png.Chunks +namespace SixLabors.ImageSharp.Formats.Png.Chunks; + +/// +/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. +/// +internal readonly struct PhysicalChunkData { + public const int Size = 9; + + public PhysicalChunkData(uint x, uint y, byte unitSpecifier) + { + this.XAxisPixelsPerUnit = x; + this.YAxisPixelsPerUnit = y; + this.UnitSpecifier = unitSpecifier; + } + + /// + /// Gets the number of pixels per unit on the X axis. + /// + public uint XAxisPixelsPerUnit { get; } + /// - /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// Gets the number of pixels per unit on the Y axis. /// - internal readonly struct PhysicalChunkData + public uint YAxisPixelsPerUnit { get; } + + /// + /// Gets the unit specifier. + /// 0: unit is unknown + /// 1: unit is the meter + /// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. + /// + public byte UnitSpecifier { get; } + + /// + /// Parses the PhysicalChunkData from the given buffer. + /// + /// The data buffer. + /// The parsed PhysicalChunkData. + public static PhysicalChunkData Parse(ReadOnlySpan data) { - public const int Size = 9; + uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]); + uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); + byte unit = data[8]; - public PhysicalChunkData(uint x, uint y, byte unitSpecifier) - { - this.XAxisPixelsPerUnit = x; - this.YAxisPixelsPerUnit = y; - this.UnitSpecifier = unitSpecifier; - } + return new PhysicalChunkData(hResolution, vResolution, unit); + } + + /// + /// Constructs the PngPhysicalChunkData from the provided metadata. + /// If the resolution units are not in meters, they are automatically converted. + /// + /// The metadata. + /// The constructed PngPhysicalChunkData instance. + public static PhysicalChunkData FromMetadata(ImageMetadata meta) + { + byte unitSpecifier = 0; + uint x; + uint y; - /// - /// Gets the number of pixels per unit on the X axis. - /// - public uint XAxisPixelsPerUnit { get; } - - /// - /// Gets the number of pixels per unit on the Y axis. - /// - public uint YAxisPixelsPerUnit { get; } - - /// - /// Gets the unit specifier. - /// 0: unit is unknown - /// 1: unit is the meter - /// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified. - /// - public byte UnitSpecifier { get; } - - /// - /// Parses the PhysicalChunkData from the given buffer. - /// - /// The data buffer. - /// The parsed PhysicalChunkData. - public static PhysicalChunkData Parse(ReadOnlySpan data) + switch (meta.ResolutionUnits) { - uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]); - uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); - byte unit = data[8]; + case PixelResolutionUnit.AspectRatio: + unitSpecifier = 0; // Unspecified + x = (uint)Math.Round(meta.HorizontalResolution); + y = (uint)Math.Round(meta.VerticalResolution); + break; - return new PhysicalChunkData(hResolution, vResolution, unit); - } + case PixelResolutionUnit.PixelsPerInch: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); + y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); + break; - /// - /// Constructs the PngPhysicalChunkData from the provided metadata. - /// If the resolution units are not in meters, they are automatically converted. - /// - /// The metadata. - /// The constructed PngPhysicalChunkData instance. - public static PhysicalChunkData FromMetadata(ImageMetadata meta) - { - byte unitSpecifier = 0; - uint x; - uint y; - - switch (meta.ResolutionUnits) - { - case PixelResolutionUnit.AspectRatio: - unitSpecifier = 0; // Unspecified - x = (uint)Math.Round(meta.HorizontalResolution); - y = (uint)Math.Round(meta.VerticalResolution); - break; - - case PixelResolutionUnit.PixelsPerInch: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); - y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); - break; - - case PixelResolutionUnit.PixelsPerCentimeter: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); - y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); - break; - - default: - unitSpecifier = 1; // Per meter - x = (uint)Math.Round(meta.HorizontalResolution); - y = (uint)Math.Round(meta.VerticalResolution); - break; - } - - return new PhysicalChunkData(x, y, unitSpecifier); - } + case PixelResolutionUnit.PixelsPerCentimeter: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); + y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); + break; - /// - /// Writes the data to the given buffer. - /// - /// The buffer. - public void WriteTo(Span buffer) - { - BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.XAxisPixelsPerUnit); - BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit); - buffer[8] = this.UnitSpecifier; + default: + unitSpecifier = 1; // Per meter + x = (uint)Math.Round(meta.HorizontalResolution); + y = (uint)Math.Round(meta.VerticalResolution); + break; } + + return new PhysicalChunkData(x, y, unitSpecifier); + } + + /// + /// Writes the data to the given buffer. + /// + /// The buffer. + public void WriteTo(Span buffer) + { + BinaryPrimitives.WriteUInt32BigEndian(buffer[..4], this.XAxisPixelsPerUnit); + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit); + buffer[8] = this.UnitSpecifier; } } diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 2cc7697550..7a6ea13ca7 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -1,214 +1,212 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// The Average filter uses the average of the two neighboring pixels (left and above) to predict +/// the value of a pixel. +/// +/// +internal static class AverageFilter { /// - /// The Average filter uses the average of the two neighboring pixels (left and above) to predict - /// the value of a pixel. - /// + /// Decodes a scanline, which was filtered with the average filter. /// - internal static class AverageFilter + /// The scanline to decode. + /// The previous scanline. + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - /// - /// Decodes a scanline, which was filtered with the average filter. - /// - /// The scanline to decode. - /// The previous scanline. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + + // The Avg filter predicts each pixel as the (truncated) average of a and b: + // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) + // With pixels positioned like this: + // prev: c b + // row: a d + if (Sse2.IsSupported && bytesPerPixel is 4) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - - // The Avg filter predicts each pixel as the (truncated) average of a and b: - // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) - // With pixels positioned like this: - // prev: c b - // row: a d - if (Sse2.IsSupported && bytesPerPixel is 4) - { - DecodeSse2(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline, bytesPerPixel); - } + DecodeSse2(scanline, previousScanline); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeSse2(Span scanline, Span previousScanline) + else { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + DecodeScalar(scanline, previousScanline, bytesPerPixel); + } + } - Vector128 d = Vector128.Zero; - var ones = Vector128.Create((byte)1); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeSse2(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - int rb = scanline.Length; - nint offset = 1; - while (rb >= 4) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 a = d; - Vector128 b = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(); - d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); - - // PNG requires a truncating average, so we can't just use _mm_avg_epu8, - // but we can fix it up by subtracting off 1 if it rounded up. - Vector128 avg = Sse2.Average(a, b); - Vector128 xor = Sse2.Xor(a, b); - Vector128 and = Sse2.And(xor, ones); - avg = Sse2.Subtract(avg, and); - d = Sse2.Add(d, avg); - - // Store the result. - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); - - rb -= 4; - offset += 4; - } - } + Vector128 d = Vector128.Zero; + var ones = Vector128.Create((byte)1); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 a = d; + Vector128 b = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(); + d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); + + // PNG requires a truncating average, so we can't just use _mm_avg_epu8, + // but we can fix it up by subtracting off 1 if it rounded up. + Vector128 avg = Sse2.Average(a, b); + Vector128 xor = Sse2.Xor(a, b); + Vector128 and = Sse2.And(xor, ones); + avg = Sse2.Subtract(avg, and); + d = Sse2.Add(d, avg); + + // Store the result. + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); + + rb -= 4; + offset += 4; + } + } - nint x = 1; - for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + (above >> 1)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - for (; x < scanline.Length; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + Average(left, above)); - } + nint x = 1; + for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + (above >> 1)); } - /// - /// Encodes a scanline with the average filter applied. - /// - /// The scanline to encode. - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + for (; x < scanline.Length; ++x) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + Average(left, above)); + } + } - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; + /// + /// Encodes a scanline with the average filter applied. + /// + /// The scanline to encode. + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - resultBaseRef = (byte)FilterType.Average; + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - nint x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) + resultBaseRef = (byte)FilterType.Average; - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; - Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); + nint x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - (above >> 1)); + sum += Numerics.Abs(unchecked((sbyte)res)); + } - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 sumAccumulator = Vector256.Zero; + Vector256 allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); - Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet); - Vector256 res = Avx2.Subtract(scan, avg); + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector256.Count; + Vector256 avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet); + Vector256 res = Avx2.Subtract(scan, avg); - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector256.Count; - sum += Numerics.EvenReduceSum(sumAccumulator); + sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); } - else if (Sse2.IsSupported) + + sum += Numerics.EvenReduceSum(sumAccumulator); + } + else if (Sse2.IsSupported) + { + Vector128 zero = Vector128.Zero; + Vector128 sumAccumulator = Vector128.Zero; + Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); + + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector128.Count; xLeft += Vector128.Count) { - Vector128 zero = Vector128.Zero; - Vector128 sumAccumulator = Vector128.Zero; - Vector128 allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte(); + Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + + Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet); + Vector128 res = Sse2.Subtract(scan, avg); + + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector128.Count; - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector128.Count; xLeft += Vector128.Count) + Vector128 absRes; + if (Ssse3.IsSupported) { - Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector128 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - - Vector128 avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet); - Vector128 res = Sse2.Subtract(scan, avg); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector128.Count; - - Vector128 absRes; - if (Ssse3.IsSupported) - { - absRes = Ssse3.Abs(res.AsSByte()); - } - else - { - Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), res.AsSByte()); - absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask).AsByte(); - } - - sumAccumulator = Sse2.Add(sumAccumulator, Sse2.SumAbsoluteDifferences(absRes, zero).AsInt32()); + absRes = Ssse3.Abs(res.AsSByte()); + } + else + { + Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), res.AsSByte()); + absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask).AsByte(); } - sum += Numerics.EvenReduceSum(sumAccumulator); + sumAccumulator = Sse2.Add(sumAccumulator, Sse2.SumAbsoluteDifferences(absRes, zero).AsInt32()); } - for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - Average(left, above)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + sum += Numerics.EvenReduceSum(sumAccumulator); } - /// - /// Calculates the average value of two bytes - /// - /// The left byte - /// The above byte - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Average(byte left, byte above) => (left + above) >> 1; + for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - Average(left, above)); + sum += Numerics.Abs(unchecked((sbyte)res)); + } } + + /// + /// Calculates the average value of two bytes + /// + /// The left byte + /// The above byte + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Average(byte left, byte above) => (left + above) >> 1; } diff --git a/src/ImageSharp/Formats/Png/Filters/FilterType.cs b/src/ImageSharp/Formats/Png/Filters/FilterType.cs index a3a5b51b67..4d7a974f08 100644 --- a/src/ImageSharp/Formats/Png/Filters/FilterType.cs +++ b/src/ImageSharp/Formats/Png/Filters/FilterType.cs @@ -1,43 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// Provides enumeration of the various PNG filter types. +/// +/// +internal enum FilterType { /// - /// Provides enumeration of the various PNG filter types. - /// + /// With the None filter, the scanline is transmitted unmodified; it is only necessary to + /// insert a filter type byte before the data. /// - internal enum FilterType - { - /// - /// With the None filter, the scanline is transmitted unmodified; it is only necessary to - /// insert a filter type byte before the data. - /// - None = 0, + None = 0, - /// - /// The Sub filter transmits the difference between each byte and the value of the corresponding - /// byte of the prior pixel. - /// - Sub = 1, + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub = 1, - /// - /// The Up filter is just like the Sub filter except that the pixel immediately above the current - /// pixel, rather than just to its left, is used as the predictor. - /// - Up = 2, + /// + /// The Up filter is just like the Sub filter except that the pixel immediately above the current + /// pixel, rather than just to its left, is used as the predictor. + /// + Up = 2, - /// - /// The Average filter uses the average of the two neighboring pixels (left and above) to - /// predict the value of a pixel. - /// - Average = 3, + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to + /// predict the value of a pixel. + /// + Average = 3, - /// - /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), - /// then chooses as predictor the neighboring pixel closest to the computed value. - /// This technique is due to Alan W. Paeth - /// - Paeth = 4 - } + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// This technique is due to Alan W. Paeth + /// + Paeth = 4 } diff --git a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs index a791952cf2..31215ede8e 100644 --- a/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/NoneFilter.cs @@ -1,30 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// The None filter, the scanline is transmitted unmodified; it is only necessary to +/// insert a filter type byte before the data. +/// +/// +internal static class NoneFilter { /// - /// The None filter, the scanline is transmitted unmodified; it is only necessary to - /// insert a filter type byte before the data. - /// + /// Encodes the scanline /// - internal static class NoneFilter + /// The scanline to encode + /// The filtered scanline result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, Span result) { - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The filtered scanline result. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, Span result) - { - // Insert row filter byte before the data. - result[0] = (byte)FilterType.None; - result = result[1..]; - scanline[..Math.Min(scanline.Length, result.Length)].CopyTo(result); - } + // Insert row filter byte before the data. + result[0] = (byte)FilterType.None; + result = result[1..]; + scanline[..Math.Min(scanline.Length, result.Length)].CopyTo(result); } } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index f4de3132c9..aee68487dd 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -1,317 +1,315 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), +/// then chooses as predictor the neighboring pixel closest to the computed value. +/// This technique is due to Alan W. Paeth. +/// +/// +internal static class PaethFilter { /// - /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), - /// then chooses as predictor the neighboring pixel closest to the computed value. - /// This technique is due to Alan W. Paeth. - /// + /// Decodes a scanline, which was filtered with the paeth filter. /// - internal static class PaethFilter + /// The scanline to decode. + /// The previous scanline. + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) { - /// - /// Decodes a scanline, which was filtered with the paeth filter. - /// - /// The scanline to decode. - /// The previous scanline. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline, int bytesPerPixel) + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + + // Paeth tries to predict pixel d using the pixel to the left of it, a, + // and two pixels from the previous row, b and c: + // prev: c b + // row: a d + // The Paeth function predicts d to be whichever of a, b, or c is nearest to + // p = a + b - c. + if (Sse41.IsSupported && bytesPerPixel is 4) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - - // Paeth tries to predict pixel d using the pixel to the left of it, a, - // and two pixels from the previous row, b and c: - // prev: c b - // row: a d - // The Paeth function predicts d to be whichever of a, b, or c is nearest to - // p = a + b - c. - if (Sse41.IsSupported && bytesPerPixel is 4) - { - DecodeSse41(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline, bytesPerPixel); - } + DecodeSse41(scanline, previousScanline); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeSse41(Span scanline, Span previousScanline) + else { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + DecodeScalar(scanline, previousScanline, bytesPerPixel); + } + } - Vector128 b = Vector128.Zero; - Vector128 d = Vector128.Zero; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeSse41(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - int rb = scanline.Length; - nint offset = 1; - while (rb >= 4) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 b = Vector128.Zero; + Vector128 d = Vector128.Zero; - // It's easiest to do this math (particularly, deal with pc) with 16-bit intermediates. - Vector128 c = b; - Vector128 a = d; - b = Sse2.UnpackLow( - Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(), - Vector128.Zero); - d = Sse2.UnpackLow( - Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(), - Vector128.Zero); + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - // (p-a) == (a+b-c - a) == (b-c) - Vector128 pa = Sse2.Subtract(b.AsInt16(), c.AsInt16()); + // It's easiest to do this math (particularly, deal with pc) with 16-bit intermediates. + Vector128 c = b; + Vector128 a = d; + b = Sse2.UnpackLow( + Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref prevBaseRef, offset))).AsByte(), + Vector128.Zero); + d = Sse2.UnpackLow( + Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(), + Vector128.Zero); - // (p-b) == (a+b-c - b) == (a-c) - Vector128 pb = Sse2.Subtract(a.AsInt16(), c.AsInt16()); + // (p-a) == (a+b-c - a) == (b-c) + Vector128 pa = Sse2.Subtract(b.AsInt16(), c.AsInt16()); - // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) - Vector128 pc = Sse2.Add(pa.AsInt16(), pb.AsInt16()); + // (p-b) == (a+b-c - b) == (a-c) + Vector128 pb = Sse2.Subtract(a.AsInt16(), c.AsInt16()); - pa = Ssse3.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */ - pb = Ssse3.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */ - pc = Ssse3.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */ + // (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) + Vector128 pc = Sse2.Add(pa.AsInt16(), pb.AsInt16()); - Vector128 smallest = Sse2.Min(pc, Sse2.Min(pa, pb)); + pa = Ssse3.Abs(pa.AsInt16()).AsInt16(); /* |p-a| */ + pb = Ssse3.Abs(pb.AsInt16()).AsInt16(); /* |p-b| */ + pc = Ssse3.Abs(pc.AsInt16()).AsInt16(); /* |p-c| */ - // Paeth breaks ties favoring a over b over c. - Vector128 mask = Sse41.BlendVariable(c, b, Sse2.CompareEqual(smallest, pb).AsByte()); - Vector128 nearest = Sse41.BlendVariable(mask, a, Sse2.CompareEqual(smallest, pa).AsByte()); + Vector128 smallest = Sse2.Min(pc, Sse2.Min(pa, pb)); - // Note `_epi8`: we need addition to wrap modulo 255. - d = Sse2.Add(d, nearest); + // Paeth breaks ties favoring a over b over c. + Vector128 mask = Sse41.BlendVariable(c, b, Sse2.CompareEqual(smallest, pb).AsByte()); + Vector128 nearest = Sse41.BlendVariable(mask, a, Sse2.CompareEqual(smallest, pa).AsByte()); - // Store the result. - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(Sse2.PackUnsignedSaturate(d.AsInt16(), d.AsInt16()).AsInt32()); + // Note `_epi8`: we need addition to wrap modulo 255. + d = Sse2.Add(d, nearest); - rb -= 4; - offset += 4; - } - } + // Store the result. + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(Sse2.PackUnsignedSaturate(d.AsInt16(), d.AsInt16()).AsInt32()); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + rb -= 4; + offset += 4; + } + } - // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) - int offset = bytesPerPixel + 1; // Add one because x starts at one. - nint x = 1; - for (; x < offset; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + above); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline, int bytesPerPixel) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - for (; x < scanline.Length; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel); - scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); - } + // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) + int offset = bytesPerPixel + 1; // Add one because x starts at one. + nint x = 1; + for (; x < offset; x++) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + above); } - /// - /// Encodes a scanline and applies the paeth filter. - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + for (; x < scanline.Length; x++) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel); + scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); + } + } - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; + /// + /// Encodes a scanline and applies the paeth filter. + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - resultBaseRef = (byte)FilterType.Paeth; + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - nint x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(0, above, 0)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) + resultBaseRef = (byte)FilterType.Paeth; - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; + nint x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(0, above, 0)); + sum += Numerics.Abs(unchecked((sbyte)res)); + } - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 sumAccumulator = Vector256.Zero; - Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector256.Count; + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } + Vector256 res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector256.Count; - sum += Numerics.EvenReduceSum(sumAccumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector.Count; xLeft += Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); - - Vector res = scan - PaethPredictor(left, above, upperLeft); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector.Count; - - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } + sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); } - for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + sum += Numerics.EvenReduceSum(sumAccumulator); } - - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbor pixel. - /// The above neighbor pixel. - /// The upper left neighbor pixel. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) + else if (Vector.IsHardwareAccelerated) { - int p = left + above - upperLeft; - int pa = Numerics.Abs(p - left); - int pb = Numerics.Abs(p - above); - int pc = Numerics.Abs(p - upperLeft); + Vector sumAccumulator = Vector.Zero; - if (pa <= pb && pa <= pc) + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector.Count; xLeft += Vector.Count) { - return left; + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, xLeft)); + + Vector res = scan - PaethPredictor(left, above, upperLeft); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector.Count; + + Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); } - if (pb <= pc) + for (int i = 0; i < Vector.Count; i++) { - return above; + sum += (int)sumAccumulator[i]; } - - return upperLeft; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft) + for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { - Vector256 zero = Vector256.Zero; - - // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left) - // to pa = abs(above - upleft). Same deal for pb. - // Using saturated subtraction, if the result is negative, the output is zero. - // If we subtract in both directions and `or` the results, only one can be - // non-zero, so we end up with the absolute value. - Vector256 sac = Avx2.SubtractSaturate(above, upleft); - Vector256 sbc = Avx2.SubtractSaturate(left, upleft); - Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac); - Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc); - - // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft). - // We've already calculated left - upleft and above - upleft in `sac` and `sbc`. - // If they are both negative or both positive, the absolute value of their - // sum can't possibly be less than `pa` or `pb`, so we'll never use the value. - // We make a mask that sets the value to 255 if they either both got - // saturated to zero or both didn't. Then we calculate the absolute value - // of their difference using saturated subtract and `or`, same as before, - // keeping the value only where the mask isn't set. - Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero)); - Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb))); - - // Finally, blend the values together. We start with `upleft` and overwrite on - // tied values so that the `left`, `above`, `upleft` precedence is preserved. - Vector256 minbc = Avx2.Min(pc, pb); - Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb)); - return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa)); + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += Numerics.Abs(unchecked((sbyte)res)); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) - { - Vector.Widen(left, out Vector a1, out Vector a2); - Vector.Widen(above, out Vector b1, out Vector b2); - Vector.Widen(upperLeft, out Vector c1, out Vector c2); + /// + /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses + /// as predictor the neighboring pixel closest to the computed value. + /// + /// The left neighbor pixel. + /// The above neighbor pixel. + /// The upper left neighbor pixel. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte PaethPredictor(byte left, byte above, byte upperLeft) + { + int p = left + above - upperLeft; + int pa = Numerics.Abs(p - left); + int pb = Numerics.Abs(p - above); + int pc = Numerics.Abs(p - upperLeft); - Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1)); - Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2)); - return Vector.AsVectorByte(Vector.Narrow(p1, p2)); + if (pa <= pb && pa <= pc) + { + return left; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) + if (pb <= pc) { - Vector p = left + above - upperLeft; - Vector pa = Vector.Abs(p - left); - Vector pb = Vector.Abs(p - above); - Vector pc = Vector.Abs(p - upperLeft); - - Vector pa_pb = Vector.LessThanOrEqual(pa, pb); - Vector pa_pc = Vector.LessThanOrEqual(pa, pc); - Vector pb_pc = Vector.LessThanOrEqual(pb, pc); - - return Vector.ConditionalSelect( - condition: Vector.BitwiseAnd(pa_pb, pa_pc), - left: left, - right: Vector.ConditionalSelect( - condition: pb_pc, - left: above, - right: upperLeft)); + return above; } + + return upperLeft; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 PaethPredictor(Vector256 left, Vector256 above, Vector256 upleft) + { + Vector256 zero = Vector256.Zero; + + // Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left) + // to pa = abs(above - upleft). Same deal for pb. + // Using saturated subtraction, if the result is negative, the output is zero. + // If we subtract in both directions and `or` the results, only one can be + // non-zero, so we end up with the absolute value. + Vector256 sac = Avx2.SubtractSaturate(above, upleft); + Vector256 sbc = Avx2.SubtractSaturate(left, upleft); + Vector256 pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac); + Vector256 pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc); + + // pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft). + // We've already calculated left - upleft and above - upleft in `sac` and `sbc`. + // If they are both negative or both positive, the absolute value of their + // sum can't possibly be less than `pa` or `pb`, so we'll never use the value. + // We make a mask that sets the value to 255 if they either both got + // saturated to zero or both didn't. Then we calculate the absolute value + // of their difference using saturated subtract and `or`, same as before, + // keeping the value only where the mask isn't set. + Vector256 pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero)); + Vector256 pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb))); + + // Finally, blend the values together. We start with `upleft` and overwrite on + // tied values so that the `left`, `above`, `upleft` precedence is preserved. + Vector256 minbc = Avx2.Min(pc, pb); + Vector256 resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb)); + return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) + { + Vector.Widen(left, out Vector a1, out Vector a2); + Vector.Widen(above, out Vector b1, out Vector b2); + Vector.Widen(upperLeft, out Vector c1, out Vector c2); + + Vector p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1)); + Vector p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2)); + return Vector.AsVectorByte(Vector.Narrow(p1, p2)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector PaethPredictor(Vector left, Vector above, Vector upperLeft) + { + Vector p = left + above - upperLeft; + Vector pa = Vector.Abs(p - left); + Vector pb = Vector.Abs(p - above); + Vector pc = Vector.Abs(p - upperLeft); + + Vector pa_pb = Vector.LessThanOrEqual(pa, pb); + Vector pa_pc = Vector.LessThanOrEqual(pa, pc); + Vector pb_pc = Vector.LessThanOrEqual(pb, pc); + + return Vector.ConditionalSelect( + condition: Vector.BitwiseAnd(pa_pb, pa_pc), + left: left, + right: Vector.ConditionalSelect( + condition: pb_pc, + left: above, + right: upperLeft)); } } diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index f339e1bbac..dcf55237d3 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -1,158 +1,156 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// The Sub filter transmits the difference between each byte and the value of the corresponding byte +/// of the prior pixel. +/// +/// +internal static class SubFilter { /// - /// The Sub filter transmits the difference between each byte and the value of the corresponding byte - /// of the prior pixel. - /// + /// Decodes a scanline, which was filtered with the sub filter. /// - internal static class SubFilter + /// The scanline to decode. + /// The bytes per pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, int bytesPerPixel) { - /// - /// Decodes a scanline, which was filtered with the sub filter. - /// - /// The scanline to decode. - /// The bytes per pixel. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, int bytesPerPixel) + // The Sub filter predicts each pixel as the previous pixel. + if (Sse2.IsSupported && bytesPerPixel is 4) { - // The Sub filter predicts each pixel as the previous pixel. - if (Sse2.IsSupported && bytesPerPixel is 4) - { - DecodeSse2(scanline); - } - else - { - DecodeScalar(scanline, bytesPerPixel); - } + DecodeSse2(scanline); } - - private static void DecodeSse2(Span scanline) + else { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - - Vector128 d = Vector128.Zero; + DecodeScalar(scanline, bytesPerPixel); + } + } - int rb = scanline.Length; - nint offset = 1; - while (rb >= 4) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 a = d; - d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); + private static void DecodeSse2(Span scanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - d = Sse2.Add(d, a); + Vector128 d = Vector128.Zero; - Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); + int rb = scanline.Length; + nint offset = 1; + while (rb >= 4) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 a = d; + d = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref scanRef)).AsByte(); - rb -= 4; - offset += 4; - } - } + d = Sse2.Add(d, a); - private static void DecodeScalar(Span scanline, int bytesPerPixel) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + Unsafe.As(ref scanRef) = Sse2.ConvertToInt32(d.AsInt32()); - // Sub(x) + Raw(x-bpp) - nint x = bytesPerPixel + 1; - Unsafe.Add(ref scanBaseRef, x); - for (; x < scanline.Length; ++x) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - scan = (byte)(scan + prev); - } + rb -= 4; + offset += 4; } + } + + private static void DecodeScalar(Span scanline, int bytesPerPixel) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - /// - /// Encodes a scanline with the sup filter applied. - /// - /// The scanline to encode. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) + // Sub(x) + Raw(x-bpp) + nint x = bytesPerPixel + 1; + Unsafe.Add(ref scanBaseRef, x); + for (; x < scanline.Length; ++x) { - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + scan = (byte)(scan + prev); + } + } - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; + /// + /// Encodes a scanline with the sup filter applied. + /// + /// The scanline to encode. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan result, int bytesPerPixel, out int sum) + { + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - // Sub(x) = Raw(x) - Raw(x-bpp) - resultBaseRef = (byte)FilterType.Sub; + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - nint x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = scan; - sum += Numerics.Abs(unchecked((sbyte)res)); - } + // Sub(x) = Raw(x) - Raw(x-bpp) + resultBaseRef = (byte)FilterType.Sub; - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; + nint x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = scan; + sum += Numerics.Abs(unchecked((sbyte)res)); + } - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 sumAccumulator = Vector256.Zero; - Vector256 res = Avx2.Subtract(scan, prev); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector256.Count; + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector256.Count; xLeft += Vector256.Count) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } + Vector256 res = Avx2.Subtract(scan, prev); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector256.Count; - sum += Numerics.EvenReduceSum(sumAccumulator); + sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector.Count; xLeft += Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); + sum += Numerics.EvenReduceSum(sumAccumulator); + } + else if (Vector.IsHardwareAccelerated) + { + Vector sumAccumulator = Vector.Zero; - Vector res = scan - prev; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector.Count; + for (nint xLeft = x - bytesPerPixel; x <= scanline.Length - Vector.Count; xLeft += Vector.Count) + { + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector prev = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } + Vector res = scan - prev; + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector.Count; - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } + Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); } - for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) + for (int i = 0; i < Vector.Count; i++) { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - prev); - sum += Numerics.Abs(unchecked((sbyte)res)); + sum += (int)sumAccumulator[i]; } } + + for (nint xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - prev); + sum += Numerics.Abs(unchecked((sbyte)res)); + } } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 9990f4c6fd..833563655c 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -1,196 +1,194 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Formats.Png.Filters +namespace SixLabors.ImageSharp.Formats.Png.Filters; + +/// +/// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, +/// rather than just to its left, is used as the predictor. +/// +/// +internal static class UpFilter { /// - /// The Up filter is just like the Sub filter except that the pixel immediately above the current pixel, - /// rather than just to its left, is used as the predictor. - /// + /// Decodes a scanline, which was filtered with the up filter. /// - internal static class UpFilter + /// The scanline to decode + /// The previous scanline. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Decode(Span scanline, Span previousScanline) { - /// - /// Decodes a scanline, which was filtered with the up filter. - /// - /// The scanline to decode - /// The previous scanline. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(Span scanline, Span previousScanline) - { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - if (Avx2.IsSupported) - { - DecodeAvx2(scanline, previousScanline); - } - else if (Sse2.IsSupported) - { - DecodeSse2(scanline, previousScanline); - } - else - { - DecodeScalar(scanline, previousScanline); - } + if (Avx2.IsSupported) + { + DecodeAvx2(scanline, previousScanline); } - - private static void DecodeAvx2(Span scanline, Span previousScanline) + else if (Sse2.IsSupported) { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + DecodeSse2(scanline, previousScanline); + } + else + { + DecodeScalar(scanline, previousScanline); + } + } - // Up(x) + Prior(x) - int rb = scanline.Length; - nint offset = 1; - while (rb >= Vector256.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector256 current = Unsafe.As>(ref scanRef); - Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); + private static void DecodeAvx2(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - Vector256 sum = Avx2.Add(up, current); - Unsafe.As>(ref scanRef) = sum; + // Up(x) + Prior(x) + int rb = scanline.Length; + nint offset = 1; + while (rb >= Vector256.Count) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector256 current = Unsafe.As>(ref scanRef); + Vector256 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - offset += Vector256.Count; - rb -= Vector256.Count; - } + Vector256 sum = Avx2.Add(up, current); + Unsafe.As>(ref scanRef) = sum; - // Handle left over. - for (nint i = offset; i < scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } + offset += Vector256.Count; + rb -= Vector256.Count; } - private static void DecodeSse2(Span scanline, Span previousScanline) + // Handle left over. + for (nint i = offset; i < scanline.Length; i++) { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); + byte above = Unsafe.Add(ref prevBaseRef, offset); + scan = (byte)(scan + above); + offset++; + } + } - // Up(x) + Prior(x) - int rb = scanline.Length; - nint offset = 1; - while (rb >= Vector128.Count) - { - ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); - Vector128 current = Unsafe.As>(ref scanRef); - Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); + private static void DecodeSse2(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - Vector128 sum = Sse2.Add(up, current); - Unsafe.As>(ref scanRef) = sum; + // Up(x) + Prior(x) + int rb = scanline.Length; + nint offset = 1; + while (rb >= Vector128.Count) + { + ref byte scanRef = ref Unsafe.Add(ref scanBaseRef, offset); + Vector128 current = Unsafe.As>(ref scanRef); + Vector128 up = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, offset)); - offset += Vector128.Count; - rb -= Vector128.Count; - } + Vector128 sum = Sse2.Add(up, current); + Unsafe.As>(ref scanRef) = sum; - // Handle left over. - for (nint i = offset; i < scanline.Length; i++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); - byte above = Unsafe.Add(ref prevBaseRef, offset); - scan = (byte)(scan + above); - offset++; - } + offset += Vector128.Count; + rb -= Vector128.Count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DecodeScalar(Span scanline, Span previousScanline) + // Handle left over. + for (nint i = offset; i < scanline.Length; i++) { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - - // Up(x) + Prior(x) - for (nint x = 1; x < scanline.Length; x++) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)(scan + above); - } + ref byte scan = ref Unsafe.Add(ref scanBaseRef, offset); + byte above = Unsafe.Add(ref prevBaseRef, offset); + scan = (byte)(scan + above); + offset++; } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DecodeScalar(Span scanline, Span previousScanline) + { + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - /// - /// Encodes a scanline with the up filter applied. - /// - /// The scanline to encode. - /// The previous scanline. - /// The filtered scanline result. - /// The sum of the total variance of the filtered row. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) + // Up(x) + Prior(x) + for (nint x = 1; x < scanline.Length; x++) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + above); + } + } - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; + /// + /// Encodes a scanline with the up filter applied. + /// + /// The scanline to encode. + /// The previous scanline. + /// The filtered scanline result. + /// The sum of the total variance of the filtered row. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Encode(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - // Up(x) = Raw(x) - Prior(x) - resultBaseRef = (byte)FilterType.Up; + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; - nint x = 0; + // Up(x) = Raw(x) - Prior(x) + resultBaseRef = (byte)FilterType.Up; - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 sumAccumulator = Vector256.Zero; + nint x = 0; - for (; x <= scanline.Length - Vector256.Count;) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + if (Avx2.IsSupported) + { + Vector256 zero = Vector256.Zero; + Vector256 sumAccumulator = Vector256.Zero; - Vector256 res = Avx2.Subtract(scan, above); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector256.Count; + for (; x <= scanline.Length - Vector256.Count;) + { + Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector256 above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); - } + Vector256 res = Avx2.Subtract(scan, above); + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector256.Count; - sum += Numerics.EvenReduceSum(sumAccumulator); + sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32()); } - else if (Vector.IsHardwareAccelerated) - { - Vector sumAccumulator = Vector.Zero; - for (; x <= scanline.Length - Vector.Count;) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); + sum += Numerics.EvenReduceSum(sumAccumulator); + } + else if (Vector.IsHardwareAccelerated) + { + Vector sumAccumulator = Vector.Zero; - Vector res = scan - above; - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type - x += Vector.Count; + for (; x <= scanline.Length - Vector.Count;) + { + Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); + Vector above = Unsafe.As>(ref Unsafe.Add(ref prevBaseRef, x)); - Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); - } + Vector res = scan - above; + Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type + x += Vector.Count; - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)sumAccumulator[i]; - } + Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res)))); } - for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) + for (int i = 0; i < Vector.Count; i++) { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)res)); + sum += (int)sumAccumulator[i]; } } + + for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - above); + sum += Numerics.Abs(unchecked((sbyte)res)); + } } } diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index 1bc785eed6..27d86f9218 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -3,76 +3,75 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// The options available for manipulating the encoder pipeline. +/// +internal interface IPngEncoderOptions { /// - /// The options available for manipulating the encoder pipeline. + /// Gets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. /// - internal interface IPngEncoderOptions - { - /// - /// Gets the number of bits per sample or per palette index (not per pixel). - /// Not all values are allowed for all values. - /// - PngBitDepth? BitDepth { get; } + PngBitDepth? BitDepth { get; } - /// - /// Gets the color type. - /// - PngColorType? ColorType { get; } + /// + /// Gets the color type. + /// + PngColorType? ColorType { get; } - /// - /// Gets the filter method. - /// - PngFilterMethod? FilterMethod { get; } + /// + /// Gets the filter method. + /// + PngFilterMethod? FilterMethod { get; } - /// - /// Gets the compression level 1-9. - /// Defaults to . - /// - PngCompressionLevel CompressionLevel { get; } + /// + /// Gets the compression level 1-9. + /// Defaults to . + /// + PngCompressionLevel CompressionLevel { get; } - /// - /// Gets the threshold of characters in text metadata, when compression should be used. - /// - int TextCompressionThreshold { get; } + /// + /// Gets the threshold of characters in text metadata, when compression should be used. + /// + int TextCompressionThreshold { get; } - /// - /// Gets the gamma value, that will be written the image. - /// - /// The gamma value of the image. - float? Gamma { get; } + /// + /// Gets the gamma value, that will be written the image. + /// + /// The gamma value of the image. + float? Gamma { get; } - /// - /// Gets the quantizer for reducing the color count. - /// - IQuantizer Quantizer { get; } + /// + /// Gets the quantizer for reducing the color count. + /// + IQuantizer Quantizer { get; } - /// - /// Gets the transparency threshold. - /// - byte Threshold { get; } + /// + /// Gets the transparency threshold. + /// + byte Threshold { get; } - /// - /// Gets a value indicating whether this instance should write an Adam7 interlaced image. - /// - PngInterlaceMode? InterlaceMethod { get; } + /// + /// Gets a value indicating whether this instance should write an Adam7 interlaced image. + /// + PngInterlaceMode? InterlaceMethod { get; } - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. - /// When set to true, all ancillary chunks will be skipped. - /// - bool IgnoreMetadata { get; } + /// + /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. + /// When set to true, all ancillary chunks will be skipped. + /// + bool IgnoreMetadata { get; } - /// - /// Gets the chunk filter method. This allows to filter ancillary chunks. - /// - PngChunkFilter? ChunkFilter { get; } + /// + /// Gets the chunk filter method. This allows to filter ancillary chunks. + /// + PngChunkFilter? ChunkFilter { get; } - /// - /// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0, - /// should be converted to transparent black, which can yield in better compression in some cases. - /// - PngTransparentColorMode TransparentColorMode { get; } - } + /// + /// Gets a value indicating whether fully transparent pixels that may contain R, G, B values which are not 0, + /// should be converted to transparent black, which can yield in better compression in some cases. + /// + PngTransparentColorMode TransparentColorMode { get; } } diff --git a/src/ImageSharp/Formats/Png/MetadataExtensions.cs b/src/ImageSharp/Formats/Png/MetadataExtensions.cs index 24417eb1b3..e05bd5f844 100644 --- a/src/ImageSharp/Formats/Png/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Png/MetadataExtensions.cs @@ -4,18 +4,17 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the png format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the png format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static PngMetadata GetPngMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PngFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static PngMetadata GetPngMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PngFormat.Instance); } diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs index 50e76326e5..452839d1d4 100644 --- a/src/ImageSharp/Formats/Png/PngBitDepth.cs +++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs @@ -2,36 +2,35 @@ // Licensed under the Six Labors Split License. // Note the value assignment, This will allow us to add 1, 2, and 4 bit encoding when we support it. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides enumeration for the available PNG bit depths. +/// +public enum PngBitDepth : byte { /// - /// Provides enumeration for the available PNG bit depths. + /// 1 bit per sample or per palette index (not per pixel). /// - public enum PngBitDepth : byte - { - /// - /// 1 bit per sample or per palette index (not per pixel). - /// - Bit1 = 1, + Bit1 = 1, - /// - /// 2 bits per sample or per palette index (not per pixel). - /// - Bit2 = 2, + /// + /// 2 bits per sample or per palette index (not per pixel). + /// + Bit2 = 2, - /// - /// 4 bits per sample or per palette index (not per pixel). - /// - Bit4 = 4, + /// + /// 4 bits per sample or per palette index (not per pixel). + /// + Bit4 = 4, - /// - /// 8 bits per sample or per palette index (not per pixel). - /// - Bit8 = 8, + /// + /// 8 bits per sample or per palette index (not per pixel). + /// + Bit8 = 8, - /// - /// 16 bits per sample or per palette index (not per pixel). - /// - Bit16 = 16 - } + /// + /// 16 bits per sample or per palette index (not per pixel). + /// + Bit16 = 16 } diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index 09270cd524..945661aa57 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -3,46 +3,45 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Stores header information about a chunk. +/// +internal readonly struct PngChunk { - /// - /// Stores header information about a chunk. - /// - internal readonly struct PngChunk + public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) { - public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) - { - this.Length = length; - this.Type = type; - this.Data = data; - } + this.Length = length; + this.Type = type; + this.Data = data; + } - /// - /// Gets the length. - /// An unsigned integer giving the number of bytes in the chunk's - /// data field. The length counts only the data field, not itself, - /// the chunk type code, or the CRC. Zero is a valid length - /// - public int Length { get; } + /// + /// Gets the length. + /// An unsigned integer giving the number of bytes in the chunk's + /// data field. The length counts only the data field, not itself, + /// the chunk type code, or the CRC. Zero is a valid length + /// + public int Length { get; } - /// - /// Gets the chunk type. - /// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters. - /// - public PngChunkType Type { get; } + /// + /// Gets the chunk type. + /// The value is the equal to the UInt32BigEndian encoding of its 4 ASCII characters. + /// + public PngChunkType Type { get; } - /// - /// Gets the data bytes appropriate to the chunk type, if any. - /// This field can be of zero length or null. - /// - public IMemoryOwner Data { get; } + /// + /// Gets the data bytes appropriate to the chunk type, if any. + /// This field can be of zero length or null. + /// + public IMemoryOwner Data { get; } - /// - /// Gets a value indicating whether the given chunk is critical to decoding - /// - public bool IsCritical => - this.Type == PngChunkType.Header || - this.Type == PngChunkType.Palette || - this.Type == PngChunkType.Data; - } + /// + /// Gets a value indicating whether the given chunk is critical to decoding + /// + public bool IsCritical => + this.Type == PngChunkType.Header || + this.Type == PngChunkType.Palette || + this.Type == PngChunkType.Data; } diff --git a/src/ImageSharp/Formats/Png/PngChunkFilter.cs b/src/ImageSharp/Formats/Png/PngChunkFilter.cs index f0feb9e6a2..8d69c5b1e3 100644 --- a/src/ImageSharp/Formats/Png/PngChunkFilter.cs +++ b/src/ImageSharp/Formats/Png/PngChunkFilter.cs @@ -1,44 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Png; -namespace SixLabors.ImageSharp.Formats.Png +/// +/// Provides enumeration of available PNG optimization methods. +/// +[Flags] +public enum PngChunkFilter { /// - /// Provides enumeration of available PNG optimization methods. + /// With the None filter, all chunks will be written. /// - [Flags] - public enum PngChunkFilter - { - /// - /// With the None filter, all chunks will be written. - /// - None = 0, + None = 0, - /// - /// Excludes the physical dimension information chunk from encoding. - /// - ExcludePhysicalChunk = 1 << 0, + /// + /// Excludes the physical dimension information chunk from encoding. + /// + ExcludePhysicalChunk = 1 << 0, - /// - /// Excludes the gamma information chunk from encoding. - /// - ExcludeGammaChunk = 1 << 1, + /// + /// Excludes the gamma information chunk from encoding. + /// + ExcludeGammaChunk = 1 << 1, - /// - /// Excludes the eXIf chunk from encoding. - /// - ExcludeExifChunk = 1 << 2, + /// + /// Excludes the eXIf chunk from encoding. + /// + ExcludeExifChunk = 1 << 2, - /// - /// Excludes the tTXt, iTXt or zTXt chunk from encoding. - /// - ExcludeTextChunks = 1 << 3, + /// + /// Excludes the tTXt, iTXt or zTXt chunk from encoding. + /// + ExcludeTextChunks = 1 << 3, - /// - /// All ancillary chunks will be excluded. - /// - ExcludeAll = ~None - } + /// + /// All ancillary chunks will be excluded. + /// + ExcludeAll = ~None } diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index 213a4a7937..f47c2e7f86 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -1,130 +1,129 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Contains a list of chunk types. +/// +internal enum PngChunkType : uint { /// - /// Contains a list of chunk types. - /// - internal enum PngChunkType : uint - { - /// - /// The IDAT chunk contains the actual image data. The image can contains more - /// than one chunk of this type. All chunks together are the whole image. - /// - Data = 0x49444154U, - - /// - /// This chunk must appear last. It marks the end of the PNG data stream. - /// The chunk's data field is empty. - /// - End = 0x49454E44U, - - /// - /// The first chunk in a png file. Can only exists once. Contains - /// common information like the width and the height of the image or - /// the used compression method. - /// - Header = 0x49484452U, - - /// - /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte - /// series in the RGB format. - /// - Palette = 0x504C5445U, - - /// - /// The eXIf data chunk which contains the Exif profile. - /// - Exif = 0x65584966U, - - /// - /// This chunk specifies the relationship between the image samples and the desired - /// display output intensity. - /// - Gamma = 0x67414D41U, - - /// - /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. - /// - Physical = 0x70485973U, - - /// - /// Textual information that the encoder wishes to record with the image can be stored in - /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. - /// - Text = 0x74455874U, - - /// - /// Textual information that the encoder wishes to record with the image. The zTXt and tEXt chunks are semantically equivalent, - /// but the zTXt chunk is recommended for storing large blocks of text. Each zTXt chunk contains a (uncompressed) keyword and - /// a compressed text string. - /// - CompressedText = 0x7A545874U, - - /// - /// The iTXt chunk contains International textual data. It contains a keyword, an optional language tag, an optional translated keyword - /// and the actual text string, which can be compressed or uncompressed. - /// - InternationalText = 0x69545874U, - - /// - /// The tRNS chunk specifies that the image uses simple transparency: - /// either alpha values associated with palette entries (for indexed-color images) - /// or a single transparent color (for grayscale and true color images). - /// - Transparency = 0x74524E53U, - - /// - /// The tIME chunk gives the time of the last image modification (not the time of initial image creation). - /// - Time = 0x74494d45, - - /// - /// The bKGD chunk specifies a default background colour to present the image against. - /// If there is any other preferred background, either user-specified or part of a larger page (as in a browser), - /// the bKGD chunk should be ignored. - /// - Background = 0x624b4744, - - /// - /// The iCCP chunk contains a embedded color profile. If the iCCP chunk is present, - /// the image samples conform to the colour space represented by the embedded ICC profile as defined by the International Color Consortium. - /// - EmbeddedColorProfile = 0x69434350, - - /// - /// The sBIT chunk defines the original number of significant bits (which can be less than or equal to the sample depth). - /// This allows PNG decoders to recover the original data losslessly even if the data had a sample depth not directly supported by PNG. - /// - SignificantBits = 0x73424954, - - /// - /// If the sRGB chunk is present, the image samples conform to the sRGB colour space [IEC 61966-2-1] and should be displayed - /// using the specified rendering intent defined by the International Color Consortium. - /// - StandardRgbColourSpace = 0x73524742, - - /// - /// The hIST chunk gives the approximate usage frequency of each colour in the palette. - /// - Histogram = 0x68495354, - - /// - /// The sPLT chunk contains the suggested palette. - /// - SuggestedPalette = 0x73504c54, - - /// - /// The cHRM chunk may be used to specify the 1931 CIE x,y chromaticities of the red, - /// green, and blue display primaries used in the image, and the referenced white point. - /// - Chroma = 0x6348524d, - - /// - /// Malformed chunk named CgBI produced by apple, which is not conform to the specification. - /// Related issue is here https://github.com/SixLabors/ImageSharp/issues/410 - /// - ProprietaryApple = 0x43674249 - } + /// The IDAT chunk contains the actual image data. The image can contains more + /// than one chunk of this type. All chunks together are the whole image. + /// + Data = 0x49444154U, + + /// + /// This chunk must appear last. It marks the end of the PNG data stream. + /// The chunk's data field is empty. + /// + End = 0x49454E44U, + + /// + /// The first chunk in a png file. Can only exists once. Contains + /// common information like the width and the height of the image or + /// the used compression method. + /// + Header = 0x49484452U, + + /// + /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte + /// series in the RGB format. + /// + Palette = 0x504C5445U, + + /// + /// The eXIf data chunk which contains the Exif profile. + /// + Exif = 0x65584966U, + + /// + /// This chunk specifies the relationship between the image samples and the desired + /// display output intensity. + /// + Gamma = 0x67414D41U, + + /// + /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// + Physical = 0x70485973U, + + /// + /// Textual information that the encoder wishes to record with the image can be stored in + /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. + /// + Text = 0x74455874U, + + /// + /// Textual information that the encoder wishes to record with the image. The zTXt and tEXt chunks are semantically equivalent, + /// but the zTXt chunk is recommended for storing large blocks of text. Each zTXt chunk contains a (uncompressed) keyword and + /// a compressed text string. + /// + CompressedText = 0x7A545874U, + + /// + /// The iTXt chunk contains International textual data. It contains a keyword, an optional language tag, an optional translated keyword + /// and the actual text string, which can be compressed or uncompressed. + /// + InternationalText = 0x69545874U, + + /// + /// The tRNS chunk specifies that the image uses simple transparency: + /// either alpha values associated with palette entries (for indexed-color images) + /// or a single transparent color (for grayscale and true color images). + /// + Transparency = 0x74524E53U, + + /// + /// The tIME chunk gives the time of the last image modification (not the time of initial image creation). + /// + Time = 0x74494d45, + + /// + /// The bKGD chunk specifies a default background colour to present the image against. + /// If there is any other preferred background, either user-specified or part of a larger page (as in a browser), + /// the bKGD chunk should be ignored. + /// + Background = 0x624b4744, + + /// + /// The iCCP chunk contains a embedded color profile. If the iCCP chunk is present, + /// the image samples conform to the colour space represented by the embedded ICC profile as defined by the International Color Consortium. + /// + EmbeddedColorProfile = 0x69434350, + + /// + /// The sBIT chunk defines the original number of significant bits (which can be less than or equal to the sample depth). + /// This allows PNG decoders to recover the original data losslessly even if the data had a sample depth not directly supported by PNG. + /// + SignificantBits = 0x73424954, + + /// + /// If the sRGB chunk is present, the image samples conform to the sRGB colour space [IEC 61966-2-1] and should be displayed + /// using the specified rendering intent defined by the International Color Consortium. + /// + StandardRgbColourSpace = 0x73524742, + + /// + /// The hIST chunk gives the approximate usage frequency of each colour in the palette. + /// + Histogram = 0x68495354, + + /// + /// The sPLT chunk contains the suggested palette. + /// + SuggestedPalette = 0x73504c54, + + /// + /// The cHRM chunk may be used to specify the 1931 CIE x,y chromaticities of the red, + /// green, and blue display primaries used in the image, and the referenced white point. + /// + Chroma = 0x6348524d, + + /// + /// Malformed chunk named CgBI produced by apple, which is not conform to the specification. + /// Related issue is here https://github.com/SixLabors/ImageSharp/issues/410 + /// + ProprietaryApple = 0x43674249 } diff --git a/src/ImageSharp/Formats/Png/PngColorType.cs b/src/ImageSharp/Formats/Png/PngColorType.cs index 139554c7cc..3a5b1f8625 100644 --- a/src/ImageSharp/Formats/Png/PngColorType.cs +++ b/src/ImageSharp/Formats/Png/PngColorType.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides enumeration of available PNG color types. +/// +public enum PngColorType : byte { /// - /// Provides enumeration of available PNG color types. + /// Each pixel is a grayscale sample. /// - public enum PngColorType : byte - { - /// - /// Each pixel is a grayscale sample. - /// - Grayscale = 0, + Grayscale = 0, - /// - /// Each pixel is an R,G,B triple. - /// - Rgb = 2, + /// + /// Each pixel is an R,G,B triple. + /// + Rgb = 2, - /// - /// Each pixel is a palette index; a PLTE chunk must appear. - /// - Palette = 3, + /// + /// Each pixel is a palette index; a PLTE chunk must appear. + /// + Palette = 3, - /// - /// Each pixel is a grayscale sample, followed by an alpha sample. - /// - GrayscaleWithAlpha = 4, + /// + /// Each pixel is a grayscale sample, followed by an alpha sample. + /// + GrayscaleWithAlpha = 4, - /// - /// Each pixel is an R,G,B triple, followed by an alpha sample. - /// - RgbWithAlpha = 6 - } + /// + /// Each pixel is an R,G,B triple, followed by an alpha sample. + /// + RgbWithAlpha = 6 } diff --git a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs index df098b7beb..6098032073 100644 --- a/src/ImageSharp/Formats/Png/PngCompressionLevel.cs +++ b/src/ImageSharp/Formats/Png/PngCompressionLevel.cs @@ -3,82 +3,81 @@ using System.ComponentModel; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides enumeration of available PNG compression levels. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public enum PngCompressionLevel { /// - /// Provides enumeration of available PNG compression levels. + /// Level 0. Equivalent to . + /// + Level0 = 0, + + /// + /// No compression. Equivalent to . + /// + NoCompression = Level0, + + /// + /// Level 1. Equivalent to . + /// + Level1 = 1, + + /// + /// Best speed compression level. + /// + BestSpeed = Level1, + + /// + /// Level 2. + /// + Level2 = 2, + + /// + /// Level 3. + /// + Level3 = 3, + + /// + /// Level 4. + /// + Level4 = 4, + + /// + /// Level 5. + /// + Level5 = 5, + + /// + /// Level 6. Equivalent to . + /// + Level6 = 6, + + /// + /// The default compression level. Equivalent to . + /// + DefaultCompression = Level6, + + /// + /// Level 7. + /// + Level7 = 7, + + /// + /// Level 8. + /// + Level8 = 8, + + /// + /// Level 9. Equivalent to . + /// + Level9 = 9, + + /// + /// Best compression level. Equivalent to . /// - [EditorBrowsable(EditorBrowsableState.Never)] - public enum PngCompressionLevel - { - /// - /// Level 0. Equivalent to . - /// - Level0 = 0, - - /// - /// No compression. Equivalent to . - /// - NoCompression = Level0, - - /// - /// Level 1. Equivalent to . - /// - Level1 = 1, - - /// - /// Best speed compression level. - /// - BestSpeed = Level1, - - /// - /// Level 2. - /// - Level2 = 2, - - /// - /// Level 3. - /// - Level3 = 3, - - /// - /// Level 4. - /// - Level4 = 4, - - /// - /// Level 5. - /// - Level5 = 5, - - /// - /// Level 6. Equivalent to . - /// - Level6 = 6, - - /// - /// The default compression level. Equivalent to . - /// - DefaultCompression = Level6, - - /// - /// Level 7. - /// - Level7 = 7, - - /// - /// Level 8. - /// - Level8 = 8, - - /// - /// Level 9. Equivalent to . - /// - Level9 = 9, - - /// - /// Best compression level. Equivalent to . - /// - BestCompression = Level9, - } + BestCompression = Level9, } diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index bbf2f4cb8f..caee817191 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Registers the image encoders, decoders and mime type detectors for the png format. +/// +public sealed class PngConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the png format. - /// - public sealed class PngConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()); - configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, new PngDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder()); + configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, new PngDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 69fae6af91..b76c73b9f2 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,106 +1,103 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Text; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Defines Png constants defined in the specification. +/// +internal static class PngConstants { /// - /// Defines Png constants defined in the specification. + /// The character encoding to use when reading and writing textual data keywords and text - (Latin-1 ISO-8859-1). /// - internal static class PngConstants - { - /// - /// The character encoding to use when reading and writing textual data keywords and text - (Latin-1 ISO-8859-1). - /// - public static readonly Encoding Encoding = Encoding.GetEncoding("ISO-8859-1"); + public static readonly Encoding Encoding = Encoding.GetEncoding("ISO-8859-1"); - /// - /// The character encoding to use when reading and writing language tags within iTXt chunks - (ASCII 7bit). - /// - public static readonly Encoding LanguageEncoding = Encoding.ASCII; + /// + /// The character encoding to use when reading and writing language tags within iTXt chunks - (ASCII 7bit). + /// + public static readonly Encoding LanguageEncoding = Encoding.ASCII; - /// - /// The character encoding to use when reading and writing translated textual data keywords and text - (UTF8). - /// - public static readonly Encoding TranslatedEncoding = Encoding.UTF8; + /// + /// The character encoding to use when reading and writing translated textual data keywords and text - (UTF8). + /// + public static readonly Encoding TranslatedEncoding = Encoding.UTF8; - /// - /// The list of mimetypes that equate to a Png. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/png" }; + /// + /// The list of mimetypes that equate to a Png. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/png" }; - /// - /// The list of file extensions that equate to a Png. - /// - public static readonly IEnumerable FileExtensions = new[] { "png" }; + /// + /// The list of file extensions that equate to a Png. + /// + public static readonly IEnumerable FileExtensions = new[] { "png" }; - /// - /// The header bytes as a big-endian coded ulong. - /// - public const ulong HeaderValue = 0x89504E470D0A1A0AUL; + /// + /// The header bytes as a big-endian coded ulong. + /// + public const ulong HeaderValue = 0x89504E470D0A1A0AUL; - /// - /// The dictionary of available color types. - /// - public static readonly Dictionary ColorTypes = new Dictionary - { - [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, - [PngColorType.Rgb] = new byte[] { 8, 16 }, - [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, - [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, - [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } - }; + /// + /// The dictionary of available color types. + /// + public static readonly Dictionary ColorTypes = new Dictionary + { + [PngColorType.Grayscale] = new byte[] { 1, 2, 4, 8, 16 }, + [PngColorType.Rgb] = new byte[] { 8, 16 }, + [PngColorType.Palette] = new byte[] { 1, 2, 4, 8 }, + [PngColorType.GrayscaleWithAlpha] = new byte[] { 8, 16 }, + [PngColorType.RgbWithAlpha] = new byte[] { 8, 16 } + }; - /// - /// The maximum length of keyword in a text chunk is 79 bytes. - /// - public const int MaxTextKeywordLength = 79; + /// + /// The maximum length of keyword in a text chunk is 79 bytes. + /// + public const int MaxTextKeywordLength = 79; - /// - /// The minimum length of a keyword in a text chunk is 1 byte. - /// - public const int MinTextKeywordLength = 1; + /// + /// 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 - }; + /// + /// 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 + }; - /// - /// Gets the keyword of the XMP metadata, encoded in an iTXT chunk. - /// - public static ReadOnlySpan XmpKeyword => new byte[] - { - (byte)'X', - (byte)'M', - (byte)'L', - (byte)':', - (byte)'c', - (byte)'o', - (byte)'m', - (byte)'.', - (byte)'a', - (byte)'d', - (byte)'o', - (byte)'b', - (byte)'e', - (byte)'.', - (byte)'x', - (byte)'m', - (byte)'p' - }; - } + /// + /// Gets the keyword of the XMP metadata, encoded in an iTXT chunk. + /// + public static ReadOnlySpan XmpKeyword => new byte[] + { + (byte)'X', + (byte)'M', + (byte)'L', + (byte)':', + (byte)'c', + (byte)'o', + (byte)'m', + (byte)'.', + (byte)'a', + (byte)'d', + (byte)'o', + (byte)'b', + (byte)'e', + (byte)'.', + (byte)'x', + (byte)'m', + (byte)'p' + }; } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 2aa5634358..bcc193f0b8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -1,97 +1,94 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Decoder for generating an image out of a png encoded stream. +/// +public sealed class PngDecoder : IImageDecoder { - /// - /// Decoder for generating an image out of a png encoded stream. - /// - public sealed class PngDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return new PngDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } + return new PngDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - PngDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + PngDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - ImageDecoderUtilities.Resize(options, image); + ImageDecoderUtilities.Resize(options, image); - return image; - } + return image; + } - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - PngDecoderCore decoder = new(options, true); - IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken); - stream.Position = 0; - - PngMetadata meta = info.Metadata.GetPngMetadata(); - PngColorType color = meta.ColorType.GetValueOrDefault(); - PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); - - var imageDecoder = (IImageDecoder)this; - switch (color) - { - case PngColorType.Grayscale: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? imageDecoder.Decode(options, stream, cancellationToken) - : imageDecoder.Decode(options, stream, cancellationToken); - } + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return !meta.HasTransparency - ? imageDecoder.Decode(options, stream, cancellationToken) - : imageDecoder.Decode(options, stream, cancellationToken); + PngDecoderCore decoder = new(options, true); + IImageInfo info = decoder.Identify(options.Configuration, stream, cancellationToken); + stream.Position = 0; - case PngColorType.Rgb: - if (bits == PngBitDepth.Bit16) - { - return !meta.HasTransparency - ? imageDecoder.Decode(options, stream, cancellationToken) - : imageDecoder.Decode(options, stream, cancellationToken); - } + PngMetadata meta = info.Metadata.GetPngMetadata(); + PngColorType color = meta.ColorType.GetValueOrDefault(); + PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + var imageDecoder = (IImageDecoder)this; + switch (color) + { + case PngColorType.Grayscale: + if (bits == PngBitDepth.Bit16) + { return !meta.HasTransparency - ? imageDecoder.Decode(options, stream, cancellationToken) - : imageDecoder.Decode(options, stream, cancellationToken); + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); + } - case PngColorType.Palette: - return imageDecoder.Decode(options, stream, cancellationToken); - - case PngColorType.GrayscaleWithAlpha: - return (bits == PngBitDepth.Bit16) - ? imageDecoder.Decode(options, stream, cancellationToken) + return !meta.HasTransparency + ? imageDecoder.Decode(options, stream, cancellationToken) : imageDecoder.Decode(options, stream, cancellationToken); - case PngColorType.RgbWithAlpha: - return (bits == PngBitDepth.Bit16) - ? imageDecoder.Decode(options, stream, cancellationToken) + case PngColorType.Rgb: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); + } + + return !meta.HasTransparency + ? imageDecoder.Decode(options, stream, cancellationToken) : imageDecoder.Decode(options, stream, cancellationToken); - default: - return imageDecoder.Decode(options, stream, cancellationToken); - } + case PngColorType.Palette: + return imageDecoder.Decode(options, stream, cancellationToken); + + case PngColorType.GrayscaleWithAlpha: + return (bits == PngBitDepth.Bit16) + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); + + case PngColorType.RgbWithAlpha: + return (bits == PngBitDepth.Bit16) + ? imageDecoder.Decode(options, stream, cancellationToken) + : imageDecoder.Decode(options, stream, cancellationToken); + + default: + return imageDecoder.Decode(options, stream, cancellationToken); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 3d9fc68fa0..2247cd6b7b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1,16 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; using System.Globalization; -using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; @@ -23,574 +20,650 @@ using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Performs the png decoding operation. +/// +internal sealed class PngDecoderCore : IImageDecoderInternals { /// - /// Performs the png decoding operation. + /// Reusable buffer. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// The general decoder options. + /// + private readonly Configuration configuration; + + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool skipMetadata; + + /// + /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. + /// + private readonly bool colorMetadataOnly; + + /// + /// Used the manage memory allocations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The stream to decode from. + /// + private BufferedReadStream currentStream; + + /// + /// The png header. + /// + private PngHeader header; + + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; + + /// + /// The number of bytes per sample. + /// + private int bytesPerSample; + + /// + /// The number of bytes per scanline. + /// + private int bytesPerScanline; + + /// + /// The palette containing color information for indexed png's. + /// + private byte[] palette; + + /// + /// The palette containing alpha channel color information for indexed png's. + /// + private byte[] paletteAlpha; + + /// + /// Previous scanline processed. + /// + private IMemoryOwner previousScanline; + + /// + /// The current scanline that is being processed. + /// + private IMemoryOwner scanline; + + /// + /// The index of the current scanline being processed. + /// + private int currentRow = Adam7.FirstRow[0]; + + /// + /// The current number of bytes read in the current scanline. + /// + private int currentRowBytesRead; + + /// + /// Gets or sets the png color type. + /// + private PngColorType pngColorType; + + /// + /// The next chunk of data to return. /// - internal sealed class PngDecoderCore : IImageDecoderInternals + private PngChunk? nextChunk; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public PngDecoderCore(DecoderOptions options) { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// The general decoder options. - /// - private readonly Configuration configuration; - - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. - /// - private readonly bool colorMetadataOnly; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream; - - /// - /// The png header. - /// - private PngHeader header; - - /// - /// The number of bytes per pixel. - /// - private int bytesPerPixel; - - /// - /// The number of bytes per sample. - /// - private int bytesPerSample; - - /// - /// The number of bytes per scanline. - /// - private int bytesPerScanline; - - /// - /// The palette containing color information for indexed png's. - /// - private byte[] palette; - - /// - /// The palette containing alpha channel color information for indexed png's. - /// - private byte[] paletteAlpha; - - /// - /// Previous scanline processed. - /// - private IMemoryOwner previousScanline; - - /// - /// The current scanline that is being processed. - /// - private IMemoryOwner scanline; - - /// - /// The index of the current scanline being processed. - /// - private int currentRow = Adam7.FirstRow[0]; - - /// - /// The current number of bytes read in the current scanline. - /// - private int currentRowBytesRead; - - /// - /// Gets or sets the png color type. - /// - private PngColorType pngColorType; - - /// - /// The next chunk of data to return. - /// - private PngChunk? nextChunk; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public PngDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - internal PngDecoderCore(DecoderOptions options, bool colorMetadataOnly) - { - this.Options = options; - this.colorMetadataOnly = colorMetadataOnly; - this.skipMetadata = true; - this.configuration = options.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - } - - /// - public DecoderOptions Options { get; } - - /// - public Size Dimensions => new(this.header.Width, this.header.Height); - - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - ImageMetadata metadata = new(); - PngMetadata pngMetadata = metadata.GetPngMetadata(); - this.currentStream = stream; - this.currentStream.Skip(8); - Image image = null; - try + this.Options = options; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.memoryAllocator = this.configuration.MemoryAllocator; + } + + internal PngDecoderCore(DecoderOptions options, bool colorMetadataOnly) + { + this.Options = options; + this.colorMetadataOnly = colorMetadataOnly; + this.skipMetadata = true; + this.configuration = options.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + } + + /// + public DecoderOptions Options { get; } + + /// + public Size Dimensions => new(this.header.Width, this.header.Height); + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + ImageMetadata metadata = new(); + PngMetadata pngMetadata = metadata.GetPngMetadata(); + this.currentStream = stream; + this.currentStream.Skip(8); + Image image = null; + try + { + while (this.TryReadChunk(out PngChunk chunk)) { - while (this.TryReadChunk(out PngChunk chunk)) + try { - try + switch (chunk.Type) { - switch (chunk.Type) - { - case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Physical: - ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Gamma: - ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Data: - if (image is null) - { - this.InitializeImage(metadata, out image); - } + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Physical: + ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Data: + if (image is null) + { + this.InitializeImage(metadata, out image); + } - this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata); + this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata); - break; - case PngChunkType.Palette: - byte[] pal = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(pal); - this.palette = pal; - break; - case PngChunkType.Transparency: - byte[] alpha = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(alpha); - this.paletteAlpha = alpha; - this.AssignTransparentMarkers(alpha, pngMetadata); - break; - case PngChunkType.Text: - this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Exif: - if (!this.skipMetadata) - { - byte[] exifData = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(exifData); - MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); - } + break; + case PngChunkType.Palette: + byte[] pal = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(pal); + this.palette = pal; + break; + case PngChunkType.Transparency: + byte[] alpha = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(alpha); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha, pngMetadata); + break; + case PngChunkType.Text: + this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.CompressedText: + this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.InternationalText: + this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Exif: + if (!this.skipMetadata) + { + byte[] exifData = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(exifData); + MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); + } - break; - case PngChunkType.EmbeddedColorProfile: - this.ReadColorProfileChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.End: - goto EOF; - case PngChunkType.ProprietaryApple: - PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); - break; - } - } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + break; + case PngChunkType.EmbeddedColorProfile: + this.ReadColorProfileChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.End: + goto EOF; + case PngChunkType.ProprietaryApple: + PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); + break; } } - - EOF: - if (image is null) + finally { - PngThrowHelper.ThrowNoData(); + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() } - - return image; - } - catch - { - image?.Dispose(); - throw; } - finally + + EOF: + if (image is null) { - this.scanline?.Dispose(); - this.previousScanline?.Dispose(); - this.nextChunk?.Data?.Dispose(); + PngThrowHelper.ThrowNoData(); } + + return image; } + catch + { + image?.Dispose(); + throw; + } + finally + { + this.scanline?.Dispose(); + this.previousScanline?.Dispose(); + this.nextChunk?.Data?.Dispose(); + } + } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + ImageMetadata metadata = new(); + PngMetadata pngMetadata = metadata.GetPngMetadata(); + this.currentStream = stream; + this.currentStream.Skip(8); + try { - ImageMetadata metadata = new(); - PngMetadata pngMetadata = metadata.GetPngMetadata(); - this.currentStream = stream; - this.currentStream.Skip(8); - try + while (this.TryReadChunk(out PngChunk chunk)) { - while (this.TryReadChunk(out PngChunk chunk)) + try { - try + switch (chunk.Type) { - switch (chunk.Type) - { - case PngChunkType.Header: - this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Physical: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Gamma: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Physical: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.Data: - - // Spec says tRNS must be before IDAT so safe to exit. - if (this.colorMetadataOnly) - { - goto EOF; - } + } + ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + if (this.colorMetadataOnly) + { this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.Transparency: - byte[] alpha = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(alpha); - this.paletteAlpha = alpha; - this.AssignTransparentMarkers(alpha, pngMetadata); + } - if (this.colorMetadataOnly) - { - goto EOF; - } + ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Data: + // Spec says tRNS must be before IDAT so safe to exit. + if (this.colorMetadataOnly) + { + goto EOF; + } + + this.SkipChunkDataAndCrc(chunk); + break; + case PngChunkType.Transparency: + byte[] alpha = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(alpha); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha, pngMetadata); + + if (this.colorMetadataOnly) + { + goto EOF; + } + + break; + case PngChunkType.Text: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.Text: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); - break; - case PngChunkType.CompressedText: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); + } + + this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.CompressedText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.InternationalText: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); + } + + this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); + break; + case PngChunkType.InternationalText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.Exif: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - break; - } - - if (!this.skipMetadata) - { - byte[] exifData = new byte[chunk.Length]; - chunk.Data.GetSpan().CopyTo(exifData); - MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); - } + } + this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); + break; + case PngChunkType.Exif: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); break; - case PngChunkType.End: - goto EOF; + } - default: - if (this.colorMetadataOnly) - { - this.SkipChunkDataAndCrc(chunk); - } + if (!this.skipMetadata) + { + byte[] exifData = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(exifData); + MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); + } - break; - } - } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + break; + case PngChunkType.End: + goto EOF; + + default: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + } + + break; } } - - EOF: - if (this.header.Width == 0 && this.header.Height == 0) + finally { - PngThrowHelper.ThrowNoHeader(); + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() } - - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } - finally + + EOF: + if (this.header.Width == 0 && this.header.Height == 0) { - this.scanline?.Dispose(); - this.previousScanline?.Dispose(); + PngThrowHelper.ThrowNoHeader(); } + + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); + } + finally + { + this.scanline?.Dispose(); + this.previousScanline?.Dispose(); } + } - /// - /// Reads the least significant bits from the byte pair with the others set to 0. - /// - /// The source buffer. - /// THe offset. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) - => (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); - - /// - /// Attempts to convert a byte array to a new array where each value in the original array is represented by the - /// specified number of bits. - /// - /// The bytes to convert from. Cannot be empty. - /// The number of bytes per scanline. - /// The number of bits per value. - /// The new array. - /// The resulting array. - private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IMemoryOwner buffer) - { - if (bits >= 8) - { - buffer = null; - return false; - } + /// + /// Reads the least significant bits from the byte pair with the others set to 0. + /// + /// The source buffer. + /// THe offset. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) + => (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); - buffer = this.memoryAllocator.Allocate(bytesPerScanline * 8 / bits, AllocationOptions.Clean); - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref buffer.GetReference(); - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; + /// + /// Attempts to convert a byte array to a new array where each value in the original array is represented by the + /// specified number of bits. + /// + /// The bytes to convert from. Cannot be empty. + /// The number of bytes per scanline. + /// The number of bits per value. + /// The new array. + /// The resulting array. + private bool TryScaleUpTo8BitArray(ReadOnlySpan source, int bytesPerScanline, int bits, out IMemoryOwner buffer) + { + if (bits >= 8) + { + buffer = null; + return false; + } - for (int i = 0; i < bytesPerScanline; i++) + buffer = this.memoryAllocator.Allocate(bytesPerScanline * 8 / bits, AllocationOptions.Clean); + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref buffer.GetReference(); + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + for (int i = 0; i < bytesPerScanline; i++) + { + byte b = Unsafe.Add(ref sourceRef, i); + for (int shift = 0; shift < 8; shift += bits) { - byte b = Unsafe.Add(ref sourceRef, i); - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; - Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex; - resultOffset++; - } + int colorIndex = (b >> (8 - bits - shift)) & mask; + Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex; + resultOffset++; } + } - return true; + return true; + } + + /// + /// Reads the data chunk containing physical dimension data. + /// + /// The metadata to read to. + /// The data containing physical data. + private static void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data) + { + PhysicalChunkData physicalChunk = PhysicalChunkData.Parse(data); + + metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue + ? PixelResolutionUnit.AspectRatio + : PixelResolutionUnit.PixelsPerMeter; + + metadata.HorizontalResolution = physicalChunk.XAxisPixelsPerUnit; + metadata.VerticalResolution = physicalChunk.YAxisPixelsPerUnit; + } + + /// + /// Reads the data chunk containing gamma data. + /// + /// The metadata to read to. + /// The data containing physical data. + private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) + { + if (data.Length < 4) + { + // Ignore invalid gamma chunks. + return; } - /// - /// Reads the data chunk containing physical dimension data. - /// - /// The metadata to read to. - /// The data containing physical data. - private static void ReadPhysicalChunk(ImageMetadata metadata, ReadOnlySpan data) + // For example, a gamma of 1/2.2 would be stored as 45455. + // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. + pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F; + } + + /// + /// Initializes the image and various buffers needed for processing + /// + /// The type the pixels will be + /// The metadata information for the image + /// The image that we will populate + private void InitializeImage(ImageMetadata metadata, out Image image) + where TPixel : unmanaged, IPixel + { + image = Image.CreateUninitialized( + this.configuration, + this.header.Width, + this.header.Height, + metadata); + + this.bytesPerPixel = this.CalculateBytesPerPixel(); + this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; + this.bytesPerSample = 1; + if (this.header.BitDepth >= 8) { - PhysicalChunkData physicalChunk = PhysicalChunkData.Parse(data); + this.bytesPerSample = this.header.BitDepth / 8; + } - metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue - ? PixelResolutionUnit.AspectRatio - : PixelResolutionUnit.PixelsPerMeter; + this.previousScanline?.Dispose(); + this.scanline?.Dispose(); + this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + } - metadata.HorizontalResolution = physicalChunk.XAxisPixelsPerUnit; - metadata.VerticalResolution = physicalChunk.YAxisPixelsPerUnit; + /// + /// Calculates the correct number of bits per pixel for the given color type. + /// + /// The + private int CalculateBitsPerPixel() + { + switch (this.pngColorType) + { + case PngColorType.Grayscale: + case PngColorType.Palette: + return this.header.BitDepth; + case PngColorType.GrayscaleWithAlpha: + return this.header.BitDepth * 2; + case PngColorType.Rgb: + return this.header.BitDepth * 3; + case PngColorType.RgbWithAlpha: + return this.header.BitDepth * 4; + default: + PngThrowHelper.ThrowNotSupportedColor(); + return -1; } + } - /// - /// Reads the data chunk containing gamma data. - /// - /// The metadata to read to. - /// The data containing physical data. - private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// The + private int CalculateBytesPerPixel() + => this.pngColorType + switch { - if (data.Length < 4) - { - // Ignore invalid gamma chunks. - return; - } + PngColorType.Grayscale => this.header.BitDepth == 16 ? 2 : 1, + PngColorType.GrayscaleWithAlpha => this.header.BitDepth == 16 ? 4 : 2, + PngColorType.Palette => 1, + PngColorType.Rgb => this.header.BitDepth == 16 ? 6 : 3, + _ => this.header.BitDepth == 16 ? 8 : 4, + }; - // For example, a gamma of 1/2.2 would be stored as 45455. - // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. - pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F; - } - - /// - /// Initializes the image and various buffers needed for processing - /// - /// The type the pixels will be - /// The metadata information for the image - /// The image that we will populate - private void InitializeImage(ImageMetadata metadata, out Image image) - where TPixel : unmanaged, IPixel - { - image = Image.CreateUninitialized( - this.configuration, - this.header.Width, - this.header.Height, - metadata); - - this.bytesPerPixel = this.CalculateBytesPerPixel(); - this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; - this.bytesPerSample = 1; - if (this.header.BitDepth >= 8) - { - this.bytesPerSample = this.header.BitDepth / 8; - } + /// + /// Calculates the scanline length. + /// + /// The width of the row. + /// + /// The representing the length. + /// + private int CalculateScanlineLength(int width) + { + int mod = this.header.BitDepth == 16 ? 16 : 8; + int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; - this.previousScanline?.Dispose(); - this.scanline?.Dispose(); - this.previousScanline = this.memoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); - this.scanline = this.configuration.MemoryAllocator.Allocate(this.bytesPerScanline, AllocationOptions.Clean); + int amount = scanlineLength % mod; + if (amount != 0) + { + scanlineLength += mod - amount; } - /// - /// Calculates the correct number of bits per pixel for the given color type. - /// - /// The - private int CalculateBitsPerPixel() + return scanlineLength / mod; + } + + /// + /// Reads the scanlines within the image. + /// + /// The pixel format. + /// The png chunk containing the compressed scanline data. + /// The pixel data. + /// The png metadata + private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel + { + using ZlibInflateStream deframeStream = new(this.currentStream, this.ReadNextDataChunk); + deframeStream.AllocateNewBytes(chunk.Length, true); + DeflateStream dataStream = deframeStream.CompressedStream; + + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) { - switch (this.pngColorType) - { - case PngColorType.Grayscale: - case PngColorType.Palette: - return this.header.BitDepth; - case PngColorType.GrayscaleWithAlpha: - return this.header.BitDepth * 2; - case PngColorType.Rgb: - return this.header.BitDepth * 3; - case PngColorType.RgbWithAlpha: - return this.header.BitDepth * 4; - default: - PngThrowHelper.ThrowNotSupportedColor(); - return -1; - } + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); + } + else + { + this.DecodePixelData(dataStream, image, pngMetadata); } + } - /// - /// Calculates the correct number of bytes per pixel for the given color type. - /// - /// The - private int CalculateBytesPerPixel() - => this.pngColorType - switch + /// + /// Decodes the raw pixel data row by row + /// + /// The pixel format. + /// The compressed pixel data stream. + /// The image to decode to. + /// The png metadata + private void DecodePixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel + { + while (this.currentRow < this.header.Height) + { + Span scanlineSpan = this.scanline.GetSpan(); + while (this.currentRowBytesRead < this.bytesPerScanline) { - PngColorType.Grayscale => this.header.BitDepth == 16 ? 2 : 1, - PngColorType.GrayscaleWithAlpha => this.header.BitDepth == 16 ? 4 : 2, - PngColorType.Palette => 1, - PngColorType.Rgb => this.header.BitDepth == 16 ? 6 : 3, - _ => this.header.BitDepth == 16 ? 8 : 4, - }; - - /// - /// Calculates the scanline length. - /// - /// The width of the row. - /// - /// The representing the length. - /// - private int CalculateScanlineLength(int width) - { - int mod = this.header.BitDepth == 16 ? 16 : 8; - int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; - - int amount = scanlineLength % mod; - if (amount != 0) + int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + if (bytesRead <= 0) + { + return; + } + + this.currentRowBytesRead += bytesRead; + } + + this.currentRowBytesRead = 0; + + switch ((FilterType)scanlineSpan[0]) { - scanlineLength += mod - amount; + case FilterType.None: + break; + + case FilterType.Sub: + SubFilter.Decode(scanlineSpan, this.bytesPerPixel); + break; + + case FilterType.Up: + UpFilter.Decode(scanlineSpan, this.previousScanline.GetSpan()); + break; + + case FilterType.Average: + AverageFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + break; + + case FilterType.Paeth: + PaethFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + break; + + default: + PngThrowHelper.ThrowUnknownFilter(); + break; } - return scanlineLength / mod; + this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); + + this.SwapScanlineBuffers(); + this.currentRow++; } + } - /// - /// Reads the scanlines within the image. - /// - /// The pixel format. - /// The png chunk containing the compressed scanline data. - /// The pixel data. - /// The png metadata - private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) - where TPixel : unmanaged, IPixel + /// + /// Decodes the raw interlaced pixel data row by row + /// + /// + /// The pixel format. + /// The compressed pixel data stream. + /// The current image. + /// The png metadata. + private void DecodeInterlacedPixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel + { + int pass = 0; + int width = this.header.Width; + Buffer2D imageBuffer = image.PixelBuffer; + while (true) { - using ZlibInflateStream deframeStream = new(this.currentStream, this.ReadNextDataChunk); - deframeStream.AllocateNewBytes(chunk.Length, true); - DeflateStream dataStream = deframeStream.CompressedStream; + int numColumns = Adam7.ComputeColumns(width, pass); - if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) + if (numColumns == 0) { - this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); - } - else - { - this.DecodePixelData(dataStream, image, pngMetadata); + pass++; + + // This pass contains no data; skip to next pass + continue; } - } - /// - /// Decodes the raw pixel data row by row - /// - /// The pixel format. - /// The compressed pixel data stream. - /// The image to decode to. - /// The png metadata - private void DecodePixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : unmanaged, IPixel - { + int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + while (this.currentRow < this.header.Height) { - Span scanlineSpan = this.scanline.GetSpan(); - while (this.currentRowBytesRead < this.bytesPerScanline) + while (this.currentRowBytesRead < bytesPerInterlaceScanline) { - int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); + int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); if (bytesRead <= 0) { return; @@ -601,25 +674,28 @@ private void DecodePixelData(DeflateStream compressedStream, ImageFrame< this.currentRowBytesRead = 0; - switch ((FilterType)scanlineSpan[0]) + Span scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); + Span prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); + + switch ((FilterType)scanSpan[0]) { case FilterType.None: break; case FilterType.Sub: - SubFilter.Decode(scanlineSpan, this.bytesPerPixel); + SubFilter.Decode(scanSpan, this.bytesPerPixel); break; case FilterType.Up: - UpFilter.Decode(scanlineSpan, this.previousScanline.GetSpan()); + UpFilter.Decode(scanSpan, prevSpan); break; case FilterType.Average: - AverageFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; case FilterType.Paeth: - PaethFilter.Decode(scanlineSpan, this.previousScanline.GetSpan(), this.bytesPerPixel); + PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; default: @@ -627,1000 +703,920 @@ private void DecodePixelData(DeflateStream compressedStream, ImageFrame< break; } - this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); + Span rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow); + this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); this.SwapScanlineBuffers(); - this.currentRow++; - } - } - - /// - /// Decodes the raw interlaced pixel data row by row - /// - /// - /// The pixel format. - /// The compressed pixel data stream. - /// The current image. - /// The png metadata. - private void DecodeInterlacedPixelData(DeflateStream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : unmanaged, IPixel - { - int pass = 0; - int width = this.header.Width; - Buffer2D imageBuffer = image.PixelBuffer; - while (true) - { - int numColumns = Adam7.ComputeColumns(width, pass); - if (numColumns == 0) - { - pass++; - - // This pass contains no data; skip to next pass - continue; - } - - int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; + this.currentRow += Adam7.RowIncrement[pass]; + } - while (this.currentRow < this.header.Height) - { - while (this.currentRowBytesRead < bytesPerInterlaceScanline) - { - int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); - if (bytesRead <= 0) - { - return; - } + pass++; + this.previousScanline.Clear(); - this.currentRowBytesRead += bytesRead; - } + if (pass < 7) + { + this.currentRow = Adam7.FirstRow[pass]; + } + else + { + pass = 0; + break; + } + } + } - this.currentRowBytesRead = 0; + /// + /// Processes the de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The de-filtered scanline + /// The image + /// The png metadata. + private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) + where TPixel : unmanaged, IPixel + { + Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(this.currentRow); - Span scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline); - Span prevSpan = this.previousScanline.Slice(0, bytesPerInterlaceScanline); + // Trim the first marker byte from the buffer + ReadOnlySpan trimmed = defilteredScanline[1..]; - switch ((FilterType)scanSpan[0]) - { - case FilterType.None: - break; + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + IMemoryOwner buffer = null; + try + { + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline - 1, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; - case FilterType.Sub: - SubFilter.Decode(scanSpan, this.bytesPerPixel); - break; + switch (this.pngColorType) + { + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - case FilterType.Up: - UpFilter.Decode(scanSpan, prevSpan); - break; + break; - case FilterType.Average: - AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - case FilterType.Paeth: - PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); - break; + break; - default: - PngThrowHelper.ThrowUnknownFilter(); - break; - } + case PngColorType.Palette: + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); - Span rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow); - this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); + break; - this.SwapScanlineBuffers(); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessRgbScanline( + this.configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - this.currentRow += Adam7.RowIncrement[pass]; - } + break; - pass++; - this.previousScanline.Clear(); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessRgbaScanline( + this.configuration, + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); - if (pass < 7) - { - this.currentRow = Adam7.FirstRow[pass]; - } - else - { - pass = 0; break; - } } } - - /// - /// Processes the de-filtered scanline filling the image pixel data - /// - /// The pixel format. - /// The de-filtered scanline - /// The image - /// The png metadata. - private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) - where TPixel : unmanaged, IPixel + finally { - Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(this.currentRow); + buffer?.Dispose(); + } + } + + /// + /// Processes the interlaced de-filtered scanline filling the image pixel data + /// + /// The pixel format. + /// The de-filtered scanline + /// The current image row. + /// The png metadata. + /// 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 : unmanaged, IPixel + { + // Trim the first marker byte from the buffer + ReadOnlySpan trimmed = defilteredScanline[1..]; - // Trim the first marker byte from the buffer - ReadOnlySpan trimmed = defilteredScanline[1..]; + // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. + IMemoryOwner buffer = null; + try + { + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( + trimmed, + this.bytesPerScanline, + this.header.BitDepth, + out buffer) + ? buffer.GetSpan() + : trimmed; - // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - IMemoryOwner buffer = null; - try + switch (this.pngColorType) { - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( - trimmed, - this.bytesPerScanline - 1, - this.header.BitDepth, - out buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) - { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); + case PngColorType.Grayscale: + PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + pngMetadata.HasTransparency, + pngMetadata.TransparentL16.GetValueOrDefault(), + pngMetadata.TransparentL8.GetValueOrDefault()); - break; + break; - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.GrayscaleWithAlpha: + PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - break; + break; - case PngColorType.Palette: - PngScanlineProcessor.ProcessPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - this.palette, - this.paletteAlpha); + case PngColorType.Palette: + PngScanlineProcessor.ProcessInterlacedPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.palette, + this.paletteAlpha); - break; + break; - case PngColorType.Rgb: - PngScanlineProcessor.ProcessRgbScanline( - this.configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + case PngColorType.Rgb: + PngScanlineProcessor.ProcessInterlacedRgbScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample, + pngMetadata.HasTransparency, + pngMetadata.TransparentRgb48.GetValueOrDefault(), + pngMetadata.TransparentRgb24.GetValueOrDefault()); - break; + break; - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessRgbaScanline( - this.configuration, - this.header, - scanlineSpan, - rowSpan, - this.bytesPerPixel, - this.bytesPerSample); + case PngColorType.RgbWithAlpha: + PngScanlineProcessor.ProcessInterlacedRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); - break; - } - } - finally - { - buffer?.Dispose(); + break; } } + finally + { + buffer?.Dispose(); + } + } - /// - /// Processes the interlaced de-filtered scanline filling the image pixel data - /// - /// The pixel format. - /// The de-filtered scanline - /// The current image row. - /// The png metadata. - /// 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 : unmanaged, IPixel - { - // Trim the first marker byte from the buffer - ReadOnlySpan trimmed = defilteredScanline[1..]; - - // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - IMemoryOwner buffer = null; - try + /// + /// Decodes and assigns marker colors that identify transparent pixels in non indexed images. + /// + /// The alpha tRNS array. + /// The png metadata. + private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngMetadata) + { + if (this.pngColorType == PngColorType.Rgb) + { + if (alpha.Length >= 6) { - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray( - trimmed, - this.bytesPerScanline, - this.header.BitDepth, - out buffer) - ? buffer.GetSpan() - : trimmed; - - switch (this.pngColorType) + if (this.header.BitDepth == 16) { - case PngColorType.Grayscale: - PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - pngMetadata.HasTransparency, - pngMetadata.TransparentL16.GetValueOrDefault(), - pngMetadata.TransparentL8.GetValueOrDefault()); - - break; - - case PngColorType.GrayscaleWithAlpha: - PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); - - break; - - case PngColorType.Palette: - PngScanlineProcessor.ProcessInterlacedPaletteScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.palette, - this.paletteAlpha); - - break; - - case PngColorType.Rgb: - PngScanlineProcessor.ProcessInterlacedRgbScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample, - pngMetadata.HasTransparency, - pngMetadata.TransparentRgb48.GetValueOrDefault(), - pngMetadata.TransparentRgb24.GetValueOrDefault()); + ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2]); + ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); + ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); - break; - - case PngColorType.RgbWithAlpha: - PngScanlineProcessor.ProcessInterlacedRgbaScanline( - this.header, - scanlineSpan, - rowSpan, - pixelOffset, - increment, - this.bytesPerPixel, - this.bytesPerSample); - - break; + pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc); + pngMetadata.HasTransparency = true; + return; } - } - finally - { - buffer?.Dispose(); + + byte r = ReadByteLittleEndian(alpha, 0); + byte g = ReadByteLittleEndian(alpha, 2); + byte b = ReadByteLittleEndian(alpha, 4); + pngMetadata.TransparentRgb24 = new Rgb24(r, g, b); + pngMetadata.HasTransparency = true; } } - - /// - /// Decodes and assigns marker colors that identify transparent pixels in non indexed images. - /// - /// The alpha tRNS array. - /// The png metadata. - private void AssignTransparentMarkers(ReadOnlySpan alpha, PngMetadata pngMetadata) + else if (this.pngColorType == PngColorType.Grayscale) { - if (this.pngColorType == PngColorType.Rgb) + if (alpha.Length >= 2) { - if (alpha.Length >= 6) + if (this.header.BitDepth == 16) { - if (this.header.BitDepth == 16) - { - ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2]); - ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); - ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); - - pngMetadata.TransparentRgb48 = new Rgb48(rc, gc, bc); - pngMetadata.HasTransparency = true; - return; - } - - byte r = ReadByteLittleEndian(alpha, 0); - byte g = ReadByteLittleEndian(alpha, 2); - byte b = ReadByteLittleEndian(alpha, 4); - pngMetadata.TransparentRgb24 = new Rgb24(r, g, b); - pngMetadata.HasTransparency = true; + pngMetadata.TransparentL16 = new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2])); } - } - else if (this.pngColorType == PngColorType.Grayscale) - { - if (alpha.Length >= 2) + else { - if (this.header.BitDepth == 16) - { - pngMetadata.TransparentL16 = new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2])); - } - else - { - pngMetadata.TransparentL8 = new L8(ReadByteLittleEndian(alpha, 0)); - } - - pngMetadata.HasTransparency = true; + pngMetadata.TransparentL8 = new L8(ReadByteLittleEndian(alpha, 0)); } - } - else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0) - { + pngMetadata.HasTransparency = true; } } - - /// - /// Reads a header chunk from the data. - /// - /// The png metadata. - /// The containing data. - private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data) + else if (this.pngColorType == PngColorType.Palette && alpha.Length > 0) { - this.header = PngHeader.Parse(data); + pngMetadata.HasTransparency = true; + } + } - this.header.Validate(); + /// + /// Reads a header chunk from the data. + /// + /// The png metadata. + /// The containing data. + private void ReadHeaderChunk(PngMetadata pngMetadata, ReadOnlySpan data) + { + this.header = PngHeader.Parse(data); + + this.header.Validate(); + + pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth; + pngMetadata.ColorType = this.header.ColorType; + pngMetadata.InterlaceMethod = this.header.InterlaceMethod; - pngMetadata.BitDepth = (PngBitDepth)this.header.BitDepth; - pngMetadata.ColorType = this.header.ColorType; - pngMetadata.InterlaceMethod = this.header.InterlaceMethod; + this.pngColorType = this.header.ColorType; + } - this.pngColorType = this.header.ColorType; + /// + /// Reads a text chunk containing image properties from the data. + /// + /// The object. + /// The metadata to decode to. + /// The containing the data. + private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) + { + if (this.skipMetadata) + { + return; } - /// - /// Reads a text chunk containing image properties from the data. - /// - /// The object. - /// The metadata to decode to. - /// The containing the data. - private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) + int zeroIndex = data.IndexOf((byte)0); + + // Keywords are restricted to 1 to 79 bytes in length. + if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) { - if (this.skipMetadata) - { - return; - } + return; + } - int zeroIndex = data.IndexOf((byte)0); + ReadOnlySpan keywordBytes = data[..zeroIndex]; + if (!TryReadTextKeyword(keywordBytes, out string name)) + { + return; + } - // Keywords are restricted to 1 to 79 bytes in length. - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + string value = PngConstants.Encoding.GetString(data[(zeroIndex + 1)..]); - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) - { - return; - } + if (!TryReadTextChunkMetadata(baseMetadata, name, value)) + { + metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); + } + } + + /// + /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string. + /// + /// The object. + /// The metadata to decode to. + /// The containing the data. + private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) + { + if (this.skipMetadata) + { + return; + } + + int zeroIndex = data.IndexOf((byte)0); + if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + { + return; + } - string value = PngConstants.Encoding.GetString(data[(zeroIndex + 1)..]); + byte compressionMethod = data[zeroIndex + 1]; + if (compressionMethod != 0) + { + // Only compression method 0 is supported (zlib datastream with deflate compression). + return; + } - if (!TryReadTextChunkMetadata(baseMetadata, name, value)) - { - metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); - } + ReadOnlySpan keywordBytes = data[..zeroIndex]; + if (!TryReadTextKeyword(keywordBytes, out string name)) + { + return; } - /// - /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string. - /// - /// The object. - /// The metadata to decode to. - /// The containing the data. - private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) + ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + + if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed) + && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) { - if (this.skipMetadata) - { - return; - } + metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); + } + } - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + /// + /// Checks if the given text chunk is actually storing parsable metadata. + /// + /// The object to store the parsed metadata in. + /// The name of the text chunk. + /// The contents of the text chunk. + /// True if metadata was successfully parsed from the text chunk. False if the + /// text chunk was not identified as metadata, and should be stored in the metadata + /// object unmodified. + private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string chunkName, string chunkText) + { + if (chunkName.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && + TryReadLegacyExifTextChunk(baseMetadata, chunkText)) + { + // Successfully parsed legacy exif data from text + return true; + } - byte compressionMethod = data[zeroIndex + 1]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + // TODO: "Raw profile type iptc", potentially others? - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) - { - return; - } + // No special chunk data identified + return false; + } - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + /// + /// Reads exif data encoded into a text chunk with the name "raw profile type exif". + /// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the + /// 2017 update to png that allowed a true exif chunk. + /// + /// The to store the decoded exif tags into. + /// The contents of the "raw profile type exif" text chunk. + private static bool TryReadLegacyExifTextChunk(ImageMetadata metadata, string data) + { + ReadOnlySpan dataSpan = data.AsSpan(); + dataSpan = dataSpan.TrimStart(); - if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed) - && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) - { - metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); - } + if (!StringEqualsInsensitive(dataSpan[..4], "exif".AsSpan())) + { + // "exif" identifier is missing from the beginning of the text chunk + return false; } - /// - /// Checks if the given text chunk is actually storing parsable metadata. - /// - /// The object to store the parsed metadata in. - /// The name of the text chunk. - /// The contents of the text chunk. - /// True if metadata was successfully parsed from the text chunk. False if the - /// text chunk was not identified as metadata, and should be stored in the metadata - /// object unmodified. - private static bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string chunkName, string chunkText) - { - if (chunkName.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && - TryReadLegacyExifTextChunk(baseMetadata, chunkText)) - { - // Successfully parsed legacy exif data from text - return true; - } + // Skip to the data length + dataSpan = dataSpan[4..].TrimStart(); + int dataLengthEnd = dataSpan.IndexOf('\n'); + int dataLength = ParseInt32(dataSpan[..dataSpan.IndexOf('\n')]); - // TODO: "Raw profile type iptc", potentially others? + // Skip to the hex-encoded data + dataSpan = dataSpan[dataLengthEnd..].Trim(); - // No special chunk data identified + // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). + // This doesn't actually allocate. + ReadOnlySpan exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + + if (dataLength < exifHeader.Length) + { + // Not enough room for the required exif header, this data couldn't possibly be valid return false; } - /// - /// Reads exif data encoded into a text chunk with the name "raw profile type exif". - /// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the - /// 2017 update to png that allowed a true exif chunk. - /// - /// The to store the decoded exif tags into. - /// The contents of the "raw profile type exif" text chunk. - private static bool TryReadLegacyExifTextChunk(ImageMetadata metadata, string data) - { - ReadOnlySpan dataSpan = data.AsSpan(); - dataSpan = dataSpan.TrimStart(); + // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile + byte[] exifBlob = new byte[dataLength - exifHeader.Length]; - if (!StringEqualsInsensitive(dataSpan[..4], "exif".AsSpan())) + try + { + // Check for the presence of the exif header in the hex-encoded binary data + byte[] tempExifBuf = exifBlob; + if (exifBlob.Length < exifHeader.Length) { - // "exif" identifier is missing from the beginning of the text chunk - return false; + // Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case + tempExifBuf = new byte[exifHeader.Length]; } - // Skip to the data length - dataSpan = dataSpan[4..].TrimStart(); - int dataLengthEnd = dataSpan.IndexOf('\n'); - int dataLength = ParseInt32(dataSpan[..dataSpan.IndexOf('\n')]); - - // Skip to the hex-encoded data - dataSpan = dataSpan[dataLengthEnd..].Trim(); - - // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). - // This doesn't actually allocate. - ReadOnlySpan exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; - - if (dataLength < exifHeader.Length) + HexConverter.HexStringToBytes(dataSpan[..(exifHeader.Length * 2)], tempExifBuf); + if (!tempExifBuf.AsSpan()[..exifHeader.Length].SequenceEqual(exifHeader)) { - // Not enough room for the required exif header, this data couldn't possibly be valid + // Exif header in the hex data is not valid return false; } - // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile - byte[] exifBlob = new byte[dataLength - exifHeader.Length]; + // Skip over the exif header we just tested + dataSpan = dataSpan[(exifHeader.Length * 2)..]; + dataLength -= exifHeader.Length; - try + // Load the hex-encoded data, one line at a time + for (int i = 0; i < dataLength;) { - // Check for the presence of the exif header in the hex-encoded binary data - byte[] tempExifBuf = exifBlob; - if (exifBlob.Length < exifHeader.Length) - { - // Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case - tempExifBuf = new byte[exifHeader.Length]; - } + ReadOnlySpan lineSpan = dataSpan; - HexConverter.HexStringToBytes(dataSpan[..(exifHeader.Length * 2)], tempExifBuf); - if (!tempExifBuf.AsSpan()[..exifHeader.Length].SequenceEqual(exifHeader)) + int newlineIndex = dataSpan.IndexOf('\n'); + if (newlineIndex != -1) { - // Exif header in the hex data is not valid - return false; + lineSpan = dataSpan[..newlineIndex]; } - // Skip over the exif header we just tested - dataSpan = dataSpan[(exifHeader.Length * 2)..]; - dataLength -= exifHeader.Length; + i += HexConverter.HexStringToBytes(lineSpan, exifBlob.AsSpan()[i..]); - // Load the hex-encoded data, one line at a time - for (int i = 0; i < dataLength;) - { - ReadOnlySpan lineSpan = dataSpan; + dataSpan = dataSpan[(newlineIndex + 1)..]; + } + } + catch + { + return false; + } - int newlineIndex = dataSpan.IndexOf('\n'); - if (newlineIndex != -1) - { - lineSpan = dataSpan[..newlineIndex]; - } + MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false); + return true; + } - i += HexConverter.HexStringToBytes(lineSpan, exifBlob.AsSpan()[i..]); + /// + /// Reads the color profile chunk. The data is stored similar to the zTXt chunk. + /// + /// The metadata. + /// The bytes containing the profile. + private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan data) + { + int zeroIndex = data.IndexOf((byte)0); + if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + { + return; + } - dataSpan = dataSpan[(newlineIndex + 1)..]; - } - } - catch - { - return false; - } + byte compressionMethod = data[zeroIndex + 1]; + if (compressionMethod != 0) + { + // Only compression method 0 is supported (zlib datastream with deflate compression). + return; + } - MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false); - return true; + ReadOnlySpan keywordBytes = data[..zeroIndex]; + if (!TryReadTextKeyword(keywordBytes, out string name)) + { + return; } - /// - /// Reads the color profile chunk. The data is stored similar to the zTXt chunk. - /// - /// The metadata. - /// The bytes containing the profile. - private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan data) + ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; + + if (this.TryUncompressZlibData(compressedData, out byte[] iccpProfileBytes)) { - int zeroIndex = data.IndexOf((byte)0); - if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + metadata.IccProfile = new IccProfile(iccpProfileBytes); + } + } - byte compressionMethod = data[zeroIndex + 1]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + /// + /// Tries to un-compress zlib compressed data. + /// + /// The compressed data. + /// The uncompressed bytes array. + /// True, if de-compressing was successful. + private unsafe bool TryUncompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray) + { + fixed (byte* compressedDataBase = compressedData) + { + using IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.configuration.StreamProcessingBufferSize); + using MemoryStream memoryStreamOutput = new(compressedData.Length); + using UnmanagedMemoryStream memoryStreamInput = new(compressedDataBase, compressedData.Length); + using BufferedReadStream bufferedStream = new(this.configuration, memoryStreamInput); + using ZlibInflateStream inflateStream = new(bufferedStream); - ReadOnlySpan keywordBytes = data[..zeroIndex]; - if (!TryReadTextKeyword(keywordBytes, out string name)) + Span destUncompressedData = destBuffer.GetSpan(); + if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) { - return; + uncompressedBytesArray = Array.Empty(); + return false; } - ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; - - if (this.TryUncompressZlibData(compressedData, out byte[] iccpProfileBytes)) + int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); + while (bytesRead != 0) { - metadata.IccProfile = new IccProfile(iccpProfileBytes); + memoryStreamOutput.Write(destUncompressedData[..bytesRead]); + bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); } + + uncompressedBytesArray = memoryStreamOutput.ToArray(); + return true; } + } - /// - /// Tries to un-compress zlib compressed data. - /// - /// The compressed data. - /// The uncompressed bytes array. - /// True, if de-compressing was successful. - private unsafe bool TryUncompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray) - { - fixed (byte* compressedDataBase = compressedData) - { - using IMemoryOwner destBuffer = this.memoryAllocator.Allocate(this.configuration.StreamProcessingBufferSize); - using MemoryStream memoryStreamOutput = new(compressedData.Length); - using UnmanagedMemoryStream memoryStreamInput = new(compressedDataBase, compressedData.Length); - using BufferedReadStream bufferedStream = new(this.configuration, memoryStreamInput); - using ZlibInflateStream inflateStream = new(bufferedStream); - - Span destUncompressedData = destBuffer.GetSpan(); - if (!inflateStream.AllocateNewBytes(compressedData.Length, false)) - { - uncompressedBytesArray = Array.Empty(); - return false; - } + /// + /// Compares two ReadOnlySpan<char>s in a case-insensitive method. + /// This is only needed because older frameworks are missing the extension method. + /// + /// The first to compare. + /// The second to compare. + /// True if the spans were identical, false otherwise. + private static bool StringEqualsInsensitive(ReadOnlySpan span1, ReadOnlySpan span2) + => span1.Equals(span2, StringComparison.OrdinalIgnoreCase); - int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); - while (bytesRead != 0) - { - memoryStreamOutput.Write(destUncompressedData[..bytesRead]); - bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); - } + /// + /// int.Parse() a ReadOnlySpan<char>, with a fallback for older frameworks. + /// + /// The to parse. + /// The parsed . + private static int ParseInt32(ReadOnlySpan span) => int.Parse(span, provider: CultureInfo.InvariantCulture); - uncompressedBytesArray = memoryStreamOutput.ToArray(); - return true; - } + /// + /// Sets the in to , + /// or copies exif tags if already contains an . + /// + /// The to store the exif data in. + /// The to copy exif tags from. + /// If already contains an , + /// controls whether existing exif tags in will be overwritten with any conflicting + /// tags from . + private static void MergeOrSetExifProfile(ImageMetadata metadata, ExifProfile newProfile, bool replaceExistingKeys) + { + if (metadata.ExifProfile is null) + { + // No exif metadata was loaded yet, so just assign it + metadata.ExifProfile = newProfile; } - - /// - /// Compares two ReadOnlySpan<char>s in a case-insensitive method. - /// This is only needed because older frameworks are missing the extension method. - /// - /// The first to compare. - /// The second to compare. - /// True if the spans were identical, false otherwise. - private static bool StringEqualsInsensitive(ReadOnlySpan span1, ReadOnlySpan span2) - => span1.Equals(span2, StringComparison.OrdinalIgnoreCase); - - /// - /// int.Parse() a ReadOnlySpan<char>, with a fallback for older frameworks. - /// - /// The to parse. - /// The parsed . - private static int ParseInt32(ReadOnlySpan span) => int.Parse(span, provider: CultureInfo.InvariantCulture); - - /// - /// Sets the in to , - /// or copies exif tags if already contains an . - /// - /// The to store the exif data in. - /// The to copy exif tags from. - /// If already contains an , - /// controls whether existing exif tags in will be overwritten with any conflicting - /// tags from . - private static void MergeOrSetExifProfile(ImageMetadata metadata, ExifProfile newProfile, bool replaceExistingKeys) - { - if (metadata.ExifProfile is null) - { - // No exif metadata was loaded yet, so just assign it - metadata.ExifProfile = newProfile; - } - else + else + { + // Try to merge existing keys with the ones from the new profile + foreach (IExifValue newKey in newProfile.Values) { - // Try to merge existing keys with the ones from the new profile - foreach (IExifValue newKey in newProfile.Values) + if (replaceExistingKeys || metadata.ExifProfile.GetValueInternal(newKey.Tag) is null) { - if (replaceExistingKeys || metadata.ExifProfile.GetValueInternal(newKey.Tag) is null) - { - metadata.ExifProfile.SetValueInternal(newKey.Tag, newKey.GetValue()); - } + metadata.ExifProfile.SetValueInternal(newKey.Tag, newKey.GetValue()); } } } + } - /// - /// Reads a iTXt chunk, which contains international text data. It contains: - /// - A uncompressed keyword. - /// - Compression flag, indicating if a compression is used. - /// - Compression method. - /// - Language tag (optional). - /// - A translated keyword (optional). - /// - Text data, which is either compressed or uncompressed. - /// - /// The metadata to decode to. - /// The containing the data. - private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data) - { - if (this.skipMetadata) - { - return; - } + /// + /// Reads a iTXt chunk, which contains international text data. It contains: + /// - A uncompressed keyword. + /// - Compression flag, indicating if a compression is used. + /// - Compression method. + /// - Language tag (optional). + /// - A translated keyword (optional). + /// - Text data, which is either compressed or uncompressed. + /// + /// The metadata to decode to. + /// The containing the data. + private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data) + { + if (this.skipMetadata) + { + return; + } - PngMetadata pngMetadata = metadata.GetPngMetadata(); - int zeroIndexKeyword = data.IndexOf((byte)0); - if (zeroIndexKeyword is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) - { - return; - } + PngMetadata pngMetadata = metadata.GetPngMetadata(); + int zeroIndexKeyword = data.IndexOf((byte)0); + if (zeroIndexKeyword is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) + { + return; + } - byte compressionFlag = data[zeroIndexKeyword + 1]; - if (compressionFlag is not (0 or 1)) - { - return; - } + byte compressionFlag = data[zeroIndexKeyword + 1]; + if (compressionFlag is not (0 or 1)) + { + return; + } - byte compressionMethod = data[zeroIndexKeyword + 2]; - if (compressionMethod != 0) - { - // Only compression method 0 is supported (zlib datastream with deflate compression). - return; - } + byte compressionMethod = data[zeroIndexKeyword + 2]; + if (compressionMethod != 0) + { + // Only compression method 0 is supported (zlib datastream with deflate compression). + return; + } - int langStartIdx = zeroIndexKeyword + 3; - int languageLength = data[langStartIdx..].IndexOf((byte)0); - if (languageLength < 0) - { - return; - } + int langStartIdx = zeroIndexKeyword + 3; + int languageLength = data[langStartIdx..].IndexOf((byte)0); + if (languageLength < 0) + { + return; + } - string language = PngConstants.LanguageEncoding.GetString(data.Slice(langStartIdx, languageLength)); + string language = PngConstants.LanguageEncoding.GetString(data.Slice(langStartIdx, languageLength)); - int translatedKeywordStartIdx = langStartIdx + languageLength + 1; - int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0); - string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength)); + int translatedKeywordStartIdx = langStartIdx + languageLength + 1; + int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0); + string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength)); - ReadOnlySpan keywordBytes = data[..zeroIndexKeyword]; - if (!TryReadTextKeyword(keywordBytes, out string keyword)) - { - return; - } + ReadOnlySpan keywordBytes = data[..zeroIndexKeyword]; + if (!TryReadTextKeyword(keywordBytes, out string keyword)) + { + return; + } - int dataStartIdx = translatedKeywordStartIdx + translatedKeywordLength + 1; - if (compressionFlag == 1) - { - ReadOnlySpan compressedData = data[dataStartIdx..]; + int dataStartIdx = translatedKeywordStartIdx + translatedKeywordLength + 1; + if (compressionFlag == 1) + { + ReadOnlySpan compressedData = data[dataStartIdx..]; - if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed)) - { - pngMetadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); - } - } - else if (IsXmpTextData(keywordBytes)) + if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed)) { - metadata.XmpProfile = new XmpProfile(data[dataStartIdx..].ToArray()); - } - else - { - string value = PngConstants.TranslatedEncoding.GetString(data[dataStartIdx..]); - pngMetadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword)); + pngMetadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); } } + else if (IsXmpTextData(keywordBytes)) + { + metadata.XmpProfile = new XmpProfile(data[dataStartIdx..].ToArray()); + } + else + { + string value = PngConstants.TranslatedEncoding.GetString(data[dataStartIdx..]); + pngMetadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword)); + } + } - /// - /// Decompresses a byte array with zlib compressed text data. - /// - /// Compressed text data bytes. - /// The string encoding to use. - /// The uncompressed value. - /// The . - private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value) + /// + /// Decompresses a byte array with zlib compressed text data. + /// + /// Compressed text data bytes. + /// The string encoding to use. + /// The uncompressed value. + /// The . + private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, out string value) + { + if (this.TryUncompressZlibData(compressedData, out byte[] uncompressedData)) { - if (this.TryUncompressZlibData(compressedData, out byte[] uncompressedData)) - { - value = encoding.GetString(uncompressedData); - return true; - } + value = encoding.GetString(uncompressedData); + return true; + } - value = null; - return false; + value = null; + return false; + } + + /// + /// Reads the next data chunk. + /// + /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. + private int ReadNextDataChunk() + { + if (this.nextChunk != null) + { + return 0; } - /// - /// Reads the next data chunk. - /// - /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. - private int ReadNextDataChunk() + this.currentStream.Read(this.buffer, 0, 4); + + if (this.TryReadChunk(out PngChunk chunk)) { - if (this.nextChunk != null) + if (chunk.Type == PngChunkType.Data) { - return 0; + chunk.Data?.Dispose(); + return chunk.Length; } - this.currentStream.Read(this.buffer, 0, 4); + this.nextChunk = chunk; + } - if (this.TryReadChunk(out PngChunk chunk)) - { - if (chunk.Type == PngChunkType.Data) - { - chunk.Data?.Dispose(); - return chunk.Length; - } + return 0; + } - this.nextChunk = chunk; - } + /// + /// Reads a chunk from the stream. + /// + /// The image format chunk. + /// + /// The . + /// + private bool TryReadChunk(out PngChunk chunk) + { + if (this.nextChunk != null) + { + chunk = this.nextChunk.Value; - return 0; + this.nextChunk = null; + + return true; } - /// - /// Reads a chunk from the stream. - /// - /// The image format chunk. - /// - /// The . - /// - private bool TryReadChunk(out PngChunk chunk) + if (!this.TryReadChunkLength(out int length)) { - if (this.nextChunk != null) - { - chunk = this.nextChunk.Value; + chunk = default; - this.nextChunk = null; - - return true; - } + // IEND + return false; + } - if (!this.TryReadChunkLength(out int length)) + while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position)) + { + // Not a valid chunk so try again until we reach a known chunk. + if (!this.TryReadChunkLength(out length)) { chunk = default; - // IEND return false; } + } - while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position)) - { - // Not a valid chunk so try again until we reach a known chunk. - if (!this.TryReadChunkLength(out length)) - { - chunk = default; - - return false; - } - } + PngChunkType type = this.ReadChunkType(); - PngChunkType type = this.ReadChunkType(); + // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. + // We can skip all other chunk data in the stream for better performance. + if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency) + { + chunk = new PngChunk(length, type); - // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. - // We can skip all other chunk data in the stream for better performance. - if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency) - { - chunk = new PngChunk(length, type); + return true; + } - return true; - } + long pos = this.currentStream.Position; + chunk = new PngChunk( + length: length, + type: type, + data: this.ReadChunkData(length)); - long pos = this.currentStream.Position; - chunk = new PngChunk( - length: length, - type: type, - data: this.ReadChunkData(length)); + this.ValidateChunk(chunk); - this.ValidateChunk(chunk); + // Restore the stream position for IDAT chunks, because it will be decoded later and + // was only read to verifying the CRC is correct. + if (type == PngChunkType.Data) + { + this.currentStream.Position = pos; + } - // Restore the stream position for IDAT chunks, because it will be decoded later and - // was only read to verifying the CRC is correct. - if (type == PngChunkType.Data) - { - this.currentStream.Position = pos; - } + return true; + } - return true; - } + /// + /// Validates the png chunk. + /// + /// The . + private void ValidateChunk(in PngChunk chunk) + { + uint inputCrc = this.ReadChunkCrc(); - /// - /// Validates the png chunk. - /// - /// The . - private void ValidateChunk(in PngChunk chunk) + if (chunk.IsCritical) { - uint inputCrc = this.ReadChunkCrc(); + Span chunkType = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); - if (chunk.IsCritical) - { - Span chunkType = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); - - uint validCrc = Crc32.Calculate(chunkType); - validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan()); + uint validCrc = Crc32.Calculate(chunkType); + validCrc = Crc32.Calculate(validCrc, chunk.Data.GetSpan()); - if (validCrc != inputCrc) - { - string chunkTypeName = Encoding.ASCII.GetString(chunkType); + if (validCrc != inputCrc) + { + string chunkTypeName = Encoding.ASCII.GetString(chunkType); - // ensure when throwing we dispose the data back to the memory allocator - chunk.Data?.Dispose(); - PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); - } + // ensure when throwing we dispose the data back to the memory allocator + chunk.Data?.Dispose(); + PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); } } + } - /// - /// Reads the cycle redundancy chunk from the data. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private uint ReadChunkCrc() + /// + /// Reads the cycle redundancy chunk from the data. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private uint ReadChunkCrc() + { + uint crc = 0; + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { - uint crc = 0; - if (this.currentStream.Read(this.buffer, 0, 4) == 4) - { - crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer); - } - - return crc; + crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer); } - /// - /// Skips the chunk data and the cycle redundancy chunk read from the data. - /// - /// The image format chunk. - [MethodImpl(InliningOptions.ShortMethod)] - private void SkipChunkDataAndCrc(in PngChunk chunk) - { - this.currentStream.Skip(chunk.Length); - this.currentStream.Skip(4); - } + return crc; + } - /// - /// Reads the chunk data from the stream. - /// - /// The length of the chunk data to read. - [MethodImpl(InliningOptions.ShortMethod)] - private IMemoryOwner ReadChunkData(int length) - { - // We rent the buffer here to return it afterwards in Decode() - IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); + /// + /// Skips the chunk data and the cycle redundancy chunk read from the data. + /// + /// The image format chunk. + [MethodImpl(InliningOptions.ShortMethod)] + private void SkipChunkDataAndCrc(in PngChunk chunk) + { + this.currentStream.Skip(chunk.Length); + this.currentStream.Skip(4); + } - this.currentStream.Read(buffer.GetSpan(), 0, length); + /// + /// Reads the chunk data from the stream. + /// + /// The length of the chunk data to read. + [MethodImpl(InliningOptions.ShortMethod)] + private IMemoryOwner ReadChunkData(int length) + { + // We rent the buffer here to return it afterwards in Decode() + IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate(length, AllocationOptions.Clean); - return buffer; - } + this.currentStream.Read(buffer.GetSpan(), 0, length); - /// - /// Identifies the chunk type from the chunk. - /// - /// - /// Thrown if the input stream is not valid. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private PngChunkType ReadChunkType() + return buffer; + } + + /// + /// Identifies the chunk type from the chunk. + /// + /// + /// Thrown if the input stream is not valid. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private PngChunkType ReadChunkType() + { + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) - { - return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); - } + return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + } - PngThrowHelper.ThrowInvalidChunkType(); + PngThrowHelper.ThrowInvalidChunkType(); - // The IDE cannot detect the throw here. - return default; - } + // The IDE cannot detect the throw here. + return default; + } - /// - /// Attempts to read the length of the next chunk. - /// - /// The result length. If the return type is this parameter is passed uninitialized. - /// - /// Whether the length was read. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private bool TryReadChunkLength(out int result) + /// + /// Attempts to read the length of the next chunk. + /// + /// The result length. If the return type is this parameter is passed uninitialized. + /// + /// Whether the length was read. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private bool TryReadChunkLength(out int result) + { + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) - { - result = BinaryPrimitives.ReadInt32BigEndian(this.buffer); - - return true; - } + result = BinaryPrimitives.ReadInt32BigEndian(this.buffer); - result = default; - return false; + return true; } - /// - /// Tries to reads a text chunk keyword, which have some restrictions to be valid: - /// Keywords shall contain only printable Latin-1 characters and should not have leading or trailing whitespace. - /// See: https://www.w3.org/TR/PNG/#11zTXt - /// - /// The keyword bytes. - /// The name. - /// True, if the keyword could be read and is valid. - private static bool TryReadTextKeyword(ReadOnlySpan keywordBytes, out string name) - { - name = string.Empty; + result = default; + return false; + } + + /// + /// Tries to reads a text chunk keyword, which have some restrictions to be valid: + /// Keywords shall contain only printable Latin-1 characters and should not have leading or trailing whitespace. + /// See: https://www.w3.org/TR/PNG/#11zTXt + /// + /// The keyword bytes. + /// The name. + /// True, if the keyword could be read and is valid. + private static bool TryReadTextKeyword(ReadOnlySpan keywordBytes, out string name) + { + name = string.Empty; - // Keywords shall contain only printable Latin-1. - foreach (byte c in keywordBytes) + // Keywords shall contain only printable Latin-1. + foreach (byte c in keywordBytes) + { + if (c is not ((>= 32 and <= 126) or (>= 161 and <= 255))) { - if (c is not ((>= 32 and <= 126) or (>= 161 and <= 255))) - { - return false; - } + return false; } - - // Keywords should not be empty or have leading or trailing whitespace. - name = PngConstants.Encoding.GetString(keywordBytes); - return !string.IsNullOrWhiteSpace(name) - && !name.StartsWith(" ", StringComparison.Ordinal) - && !name.EndsWith(" ", StringComparison.Ordinal); } - private static bool IsXmpTextData(ReadOnlySpan keywordBytes) - => keywordBytes.SequenceEqual(PngConstants.XmpKeyword); - - private void SwapScanlineBuffers() - => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); + // Keywords should not be empty or have leading or trailing whitespace. + name = PngConstants.Encoding.GetString(keywordBytes); + return !string.IsNullOrWhiteSpace(name) + && !name.StartsWith(" ", StringComparison.Ordinal) + && !name.EndsWith(" ", StringComparison.Ordinal); } + + private static bool IsXmpTextData(ReadOnlySpan keywordBytes) + => keywordBytes.SequenceEqual(PngConstants.XmpKeyword); + + private void SwapScanlineBuffers() + => (this.scanline, this.previousScanline) = (this.previousScanline, this.scanline); } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 31e0c17c54..d769bcbc8d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -1,89 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Image encoder for writing image data to a stream in png format. +/// +public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions { - /// - /// Image encoder for writing image data to a stream in png format. - /// - public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions - { - /// - public PngBitDepth? BitDepth { get; set; } + /// + public PngBitDepth? BitDepth { get; set; } - /// - public PngColorType? ColorType { get; set; } + /// + public PngColorType? ColorType { get; set; } - /// - public PngFilterMethod? FilterMethod { get; set; } + /// + public PngFilterMethod? FilterMethod { get; set; } - /// - public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; + /// + public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; - /// - public int TextCompressionThreshold { get; set; } = 1024; + /// + public int TextCompressionThreshold { get; set; } = 1024; - /// - public float? Gamma { get; set; } + /// + public float? Gamma { get; set; } - /// - public IQuantizer Quantizer { get; set; } + /// + public IQuantizer Quantizer { get; set; } - /// - public byte Threshold { get; set; } = byte.MaxValue; + /// + public byte Threshold { get; set; } = byte.MaxValue; - /// - public PngInterlaceMode? InterlaceMethod { get; set; } + /// + public PngInterlaceMode? InterlaceMethod { get; set; } - /// - public PngChunkFilter? ChunkFilter { get; set; } + /// + public PngChunkFilter? ChunkFilter { get; set; } - /// - public bool IgnoreMetadata { get; set; } + /// + public bool IgnoreMetadata { get; set; } - /// - public PngTransparentColorMode TransparentColorMode { get; set; } + /// + public PngTransparentColorMode TransparentColorMode { get; set; } - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { - using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) - { - encoder.Encode(image, stream); - } + encoder.Encode(image, stream); } + } - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // The introduction of a local variable that refers to an object the implements + // IDisposable means you must use async/await, where the compiler generates the + // state machine and a continuation. + using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { - // The introduction of a local variable that refers to an object the implements - // IDisposable means you must use async/await, where the compiler generates the - // state machine and a continuation. - using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) - { - await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); - } + await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7ba28393db..c45da6a825 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -1,13 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; @@ -16,1184 +13,1183 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Performs the png encoding operation. +/// +internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable { /// - /// Performs the png encoding operation. + /// The maximum block size, defaults at 64k for uncompressed blocks. /// - internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable - { - /// - /// The maximum block size, defaults at 64k for uncompressed blocks. - /// - private const int MaxBlockSize = 65535; - - /// - /// Used the manage memory allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The configuration instance for the decoding operation. - /// - private readonly Configuration configuration; - - /// - /// Reusable buffer for writing general data. - /// - private readonly byte[] buffer = new byte[8]; - - /// - /// Reusable buffer for writing chunk data. - /// - private readonly byte[] chunkDataBuffer = new byte[16]; - - /// - /// The encoder options - /// - private readonly PngEncoderOptions options; - - /// - /// The bit depth. - /// - private byte bitDepth; - - /// - /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. - /// - private bool use16Bit; - - /// - /// The number of bytes per pixel. - /// - private int bytesPerPixel; - - /// - /// The image width. - /// - private int width; - - /// - /// The image height. - /// - private int height; - - /// - /// The raw data of previous scanline. - /// - private IMemoryOwner previousScanline; - - /// - /// The raw data of current scanline. - /// - private IMemoryOwner currentScanline; - - /// - /// The color profile name. - /// - private const string ColorProfileName = "ICC Profile"; - - /// - /// Initializes a new instance of the class. - /// - /// The to use for buffer allocations. - /// The configuration. - /// The options for influencing the encoder - public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoderOptions options) - { - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.options = options; - } + private const int MaxBlockSize = 65535; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + /// + /// Used the manage memory allocations. + /// + private readonly MemoryAllocator memoryAllocator; - this.width = image.Width; - this.height = image.Height; + /// + /// The configuration instance for the decoding operation. + /// + private readonly Configuration configuration; - ImageMetadata metadata = image.Metadata; + /// + /// Reusable buffer for writing general data. + /// + private readonly byte[] buffer = new byte[8]; - PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); - PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - Image clonedImage = null; - bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear; - if (clearTransparency) - { - clonedImage = image.Clone(); - ClearTransparentPixels(clonedImage); - } + /// + /// Reusable buffer for writing chunk data. + /// + private readonly byte[] chunkDataBuffer = new byte[16]; - IndexedImageFrame quantized = this.CreateQuantizedImage(image, clonedImage); + /// + /// The encoder options + /// + private readonly PngEncoderOptions options; - stream.Write(PngConstants.HeaderBytes); + /// + /// The bit depth. + /// + private byte bitDepth; - this.WriteHeaderChunk(stream); - this.WriteGammaChunk(stream); - this.WriteColorProfileChunk(stream, metadata); - this.WritePaletteChunk(stream, quantized); - this.WriteTransparencyChunk(stream, pngMetadata); - this.WritePhysicalChunk(stream, metadata); - this.WriteExifChunk(stream, metadata); - this.WriteXmpChunk(stream, metadata); - this.WriteTextChunks(stream, pngMetadata); - this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream); - this.WriteEndChunk(stream); + /// + /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. + /// + private bool use16Bit; - stream.Flush(); + /// + /// The number of bytes per pixel. + /// + private int bytesPerPixel; - quantized?.Dispose(); - clonedImage?.Dispose(); - } + /// + /// The image width. + /// + private int width; - /// - public void Dispose() + /// + /// The image height. + /// + private int height; + + /// + /// The raw data of previous scanline. + /// + private IMemoryOwner previousScanline; + + /// + /// The raw data of current scanline. + /// + private IMemoryOwner currentScanline; + + /// + /// The color profile name. + /// + private const string ColorProfileName = "ICC Profile"; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for buffer allocations. + /// The configuration. + /// The options for influencing the encoder + public PngEncoderCore(MemoryAllocator memoryAllocator, Configuration configuration, PngEncoderOptions options) + { + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.options = options; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.width = image.Width; + this.height = image.Height; + + ImageMetadata metadata = image.Metadata; + + PngMetadata pngMetadata = metadata.GetFormatMetadata(PngFormat.Instance); + PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); + Image clonedImage = null; + bool clearTransparency = this.options.TransparentColorMode == PngTransparentColorMode.Clear; + if (clearTransparency) { - this.previousScanline?.Dispose(); - this.currentScanline?.Dispose(); - this.previousScanline = null; - this.currentScanline = null; + clonedImage = image.Clone(); + ClearTransparentPixels(clonedImage); } - /// - /// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases. - /// - /// The type of the pixel. - /// The cloned image where the transparent pixels will be changed. - private static void ClearTransparentPixels(Image image) - where TPixel : unmanaged, IPixel => - image.ProcessPixelRows(accessor => + IndexedImageFrame quantized = this.CreateQuantizedImage(image, clonedImage); + + stream.Write(PngConstants.HeaderBytes); + + this.WriteHeaderChunk(stream); + this.WriteGammaChunk(stream); + this.WriteColorProfileChunk(stream, metadata); + this.WritePaletteChunk(stream, quantized); + this.WriteTransparencyChunk(stream, pngMetadata); + this.WritePhysicalChunk(stream, metadata); + this.WriteExifChunk(stream, metadata); + this.WriteXmpChunk(stream, metadata); + this.WriteTextChunks(stream, pngMetadata); + this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream); + this.WriteEndChunk(stream); + + stream.Flush(); + + quantized?.Dispose(); + clonedImage?.Dispose(); + } + + /// + public void Dispose() + { + this.previousScanline?.Dispose(); + this.currentScanline?.Dispose(); + this.previousScanline = null; + this.currentScanline = null; + } + + /// + /// Convert transparent pixels, to transparent black pixels, which can yield to better compression in some cases. + /// + /// The type of the pixel. + /// The cloned image where the transparent pixels will be changed. + private static void ClearTransparentPixels(Image image) + where TPixel : unmanaged, IPixel => + image.ProcessPixelRows(accessor => + { + Rgba32 rgba32 = default; + Rgba32 transparent = Color.Transparent; + for (int y = 0; y < accessor.Height; y++) { - Rgba32 rgba32 = default; - Rgba32 transparent = Color.Transparent; - for (int y = 0; y < accessor.Height; y++) + Span span = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) { - Span span = accessor.GetRowSpan(y); - for (int x = 0; x < accessor.Width; x++) - { - span[x].ToRgba32(ref rgba32); + span[x].ToRgba32(ref rgba32); - if (rgba32.A == 0) - { - span[x].FromRgba32(transparent); - } + if (rgba32.A == 0) + { + span[x].FromRgba32(transparent); } } - }); - - /// - /// Creates the quantized image and sets calculates and sets the bit depth. - /// - /// The type of the pixel. - /// The image to quantize. - /// Cloned image with transparent pixels are changed to black. - /// The quantized image. - private IndexedImageFrame CreateQuantizedImage(Image image, Image clonedImage) - where TPixel : unmanaged, IPixel - { - IndexedImageFrame quantized; - if (this.options.TransparentColorMode == PngTransparentColorMode.Clear) - { - quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage); - this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); - } - else - { - quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); - this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); } + }); - return quantized; + /// + /// Creates the quantized image and sets calculates and sets the bit depth. + /// + /// The type of the pixel. + /// The image to quantize. + /// Cloned image with transparent pixels are changed to black. + /// The quantized image. + private IndexedImageFrame CreateQuantizedImage(Image image, Image clonedImage) + where TPixel : unmanaged, IPixel + { + IndexedImageFrame quantized; + if (this.options.TransparentColorMode == PngTransparentColorMode.Clear) + { + quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, clonedImage); + this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); } - - /// Collects a row of grayscale pixels. - /// The pixel format. - /// The image row span. - private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) - where TPixel : unmanaged, IPixel + else { - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - Span rawScanlineSpan = this.currentScanline.GetSpan(); - ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); + quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, quantized); + } + + return quantized; + } - if (this.options.ColorType == PngColorType.Grayscale) + /// Collects a row of grayscale pixels. + /// The pixel format. + /// The image row span. + private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) + where TPixel : unmanaged, IPixel + { + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + Span rawScanlineSpan = this.currentScanline.GetSpan(); + ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); + + if (this.options.ColorType == PngColorType.Grayscale) + { + if (this.use16Bit) { - if (this.use16Bit) + // 16 bit grayscale + using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) { - // 16 bit grayscale - using (IMemoryOwner luminanceBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + Span luminanceSpan = luminanceBuffer.GetSpan(); + ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); + PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan); + + // Can't map directly to byte array as it's big-endian. + for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2) { - Span luminanceSpan = luminanceBuffer.GetSpan(); - ref L16 luminanceRef = ref MemoryMarshal.GetReference(luminanceSpan); - PixelOperations.Instance.ToL16(this.configuration, rowSpan, luminanceSpan); - - // Can't map directly to byte array as it's big-endian. - for (int x = 0, o = 0; x < luminanceSpan.Length; x++, o += 2) - { - L16 luminance = Unsafe.Add(ref luminanceRef, x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); - } + L16 luminance = Unsafe.Add(ref luminanceRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance.PackedValue); } } - else if (this.bitDepth == 8) - { - // 8 bit grayscale - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - } - else - { - // 1, 2, and 4 bit grayscale - using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); - Span tempSpan = temp.GetSpan(); - - // We need to first create an array of luminance bytes then scale them down to the correct bit depth. - PixelOperations.Instance.ToL8Bytes( - this.configuration, - rowSpan, - tempSpan, - rowSpan.Length); - PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); - } } - else if (this.use16Bit) + else if (this.bitDepth == 8) { - // 16 bit grayscale + alpha - using IMemoryOwner laBuffer = this.memoryAllocator.Allocate(rowSpan.Length); - Span laSpan = laBuffer.GetSpan(); - ref La32 laRef = ref MemoryMarshal.GetReference(laSpan); - PixelOperations.Instance.ToLa32(this.configuration, rowSpan, laSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4) - { - La32 la = Unsafe.Add(ref laRef, x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A); - } + // 8 bit grayscale + PixelOperations.Instance.ToL8Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); } else { - // 8 bit grayscale + alpha - PixelOperations.Instance.ToLa16Bytes( + // 1, 2, and 4 bit grayscale + using IMemoryOwner temp = this.memoryAllocator.Allocate(rowSpan.Length, AllocationOptions.Clean); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + + // We need to first create an array of luminance bytes then scale them down to the correct bit depth. + PixelOperations.Instance.ToL8Bytes( this.configuration, rowSpan, - rawScanlineSpan, + tempSpan, rowSpan.Length); + PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor); } } - - /// - /// Collects a row of true color pixel data. - /// - /// The pixel format. - /// The row span. - private void CollectTPixelBytes(ReadOnlySpan rowSpan) - where TPixel : unmanaged, IPixel + else if (this.use16Bit) { - Span rawScanlineSpan = this.currentScanline.GetSpan(); - - switch (this.bytesPerPixel) + // 16 bit grayscale + alpha + using IMemoryOwner laBuffer = this.memoryAllocator.Allocate(rowSpan.Length); + Span laSpan = laBuffer.GetSpan(); + ref La32 laRef = ref MemoryMarshal.GetReference(laSpan); + PixelOperations.Instance.ToLa32(this.configuration, rowSpan, laSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4) { - case 4: - - // 8 bit Rgba - PixelOperations.Instance.ToRgba32Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - break; + La32 la = Unsafe.Add(ref laRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A); + } + } + else + { + // 8 bit grayscale + alpha + PixelOperations.Instance.ToLa16Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); + } + } - case 3: + /// + /// Collects a row of true color pixel data. + /// + /// The pixel format. + /// The row span. + private void CollectTPixelBytes(ReadOnlySpan rowSpan) + where TPixel : unmanaged, IPixel + { + Span rawScanlineSpan = this.currentScanline.GetSpan(); - // 8 bit Rgb - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - rowSpan, - rawScanlineSpan, - rowSpan.Length); - break; + switch (this.bytesPerPixel) + { + case 4: - case 8: + // 8 bit Rgba + PixelOperations.Instance.ToRgba32Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); + break; - // 16 bit Rgba - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) - { - Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A); - } - } + case 3: - break; + // 8 bit Rgb + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + rowSpan, + rawScanlineSpan, + rowSpan.Length); + break; - default: + case 8: - // 16 bit Rgb - using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + // 16 bit Rgba + using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span rgbaSpan = rgbaBuffer.GetSpan(); + ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan); + PixelOperations.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) { - Span rgbSpan = rgbBuffer.GetSpan(); - ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan); - PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan); - - // Can't map directly to byte array as it's big endian. - for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) - { - Rgb48 rgb = Unsafe.Add(ref rgbRef, x); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G); - BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B); - } + Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgba.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgba.B); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 6, 2), rgba.A); } + } - break; - } - } + break; - /// - /// Encodes the pixel data line by line. - /// Each scanline is encoded in the most optimal manner to improve compression. - /// - /// The pixel format. - /// The row span. - /// The quantized pixels. Can be null. - /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) - where TPixel : unmanaged, IPixel - { - switch (this.options.ColorType) - { - case PngColorType.Palette: + default: - if (this.bitDepth < 8) - { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); - } - else + // 16 bit Rgb + using (IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(rowSpan.Length)) + { + Span rgbSpan = rgbBuffer.GetSpan(); + ref Rgb48 rgbRef = ref MemoryMarshal.GetReference(rgbSpan); + PixelOperations.Instance.ToRgb48(this.configuration, rowSpan, rgbSpan); + + // Can't map directly to byte array as it's big endian. + for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) { - quantized.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan()); + Rgb48 rgb = Unsafe.Add(ref rgbRef, x); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), rgb.R); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgb.G); + BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 4, 2), rgb.B); } + } - break; - case PngColorType.Grayscale: - case PngColorType.GrayscaleWithAlpha: - this.CollectGrayscaleBytes(rowSpan); - break; - default: - this.CollectTPixelBytes(rowSpan); - break; - } + break; } + } - /// - /// Apply the line filter for the raw scanline to enable better compression. - /// - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void FilterPixelBytes(ref Span filter, ref Span attempt) + /// + /// Encodes the pixel data line by line. + /// Each scanline is encoded in the most optimal manner to improve compression. + /// + /// The pixel format. + /// The row span. + /// The quantized pixels. Can be null. + /// The row. + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel + { + switch (this.options.ColorType) { - switch (this.options.FilterMethod) - { - case PngFilterMethod.None: - NoneFilter.Encode(this.currentScanline.GetSpan(), filter); - break; - case PngFilterMethod.Sub: - SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); - break; - - case PngFilterMethod.Up: - UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); - break; + case PngColorType.Palette: - case PngFilterMethod.Average: - AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); - break; + if (this.bitDepth < 8) + { + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + } + else + { + quantized.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan()); + } - case PngFilterMethod.Paeth: - PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); - break; - default: - this.ApplyOptimalFilteredScanline(ref filter, ref attempt); - break; - } + break; + case PngColorType.Grayscale: + case PngColorType.GrayscaleWithAlpha: + this.CollectGrayscaleBytes(rowSpan); + break; + default: + this.CollectTPixelBytes(rowSpan); + break; } + } - /// - /// Collects the pixel data line by line for compressing. - /// Each scanline is filtered in the most optimal manner to improve compression. - /// - /// The pixel format. - /// The row span. - /// The filtered buffer. - /// Used for attempting optimized filtering. - /// The quantized pixels. Can be . - /// The row number. - private void CollectAndFilterPixelRow( - ReadOnlySpan rowSpan, - ref Span filter, - ref Span attempt, - IndexedImageFrame quantized, - int row) - where TPixel : unmanaged, IPixel + /// + /// Apply the line filter for the raw scanline to enable better compression. + /// + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void FilterPixelBytes(ref Span filter, ref Span attempt) + { + switch (this.options.FilterMethod) { - this.CollectPixelBytes(rowSpan, quantized, row); - this.FilterPixelBytes(ref filter, ref attempt); + case PngFilterMethod.None: + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + break; + case PngFilterMethod.Sub: + SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + + case PngFilterMethod.Up: + UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _); + break; + + case PngFilterMethod.Average: + AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + + case PngFilterMethod.Paeth: + PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _); + break; + default: + this.ApplyOptimalFilteredScanline(ref filter, ref attempt); + break; } + } - /// - /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. - /// - /// The row span. - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void EncodeAdam7IndexedPixelRow( - ReadOnlySpan row, - ref Span filter, - ref Span attempt) - { - // CollectPixelBytes - if (this.bitDepth < 8) - { - PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); - } - else - { - row.CopyTo(this.currentScanline.GetSpan()); - } + /// + /// Collects the pixel data line by line for compressing. + /// Each scanline is filtered in the most optimal manner to improve compression. + /// + /// The pixel format. + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + /// The quantized pixels. Can be . + /// The row number. + private void CollectAndFilterPixelRow( + ReadOnlySpan rowSpan, + ref Span filter, + ref Span attempt, + IndexedImageFrame quantized, + int row) + where TPixel : unmanaged, IPixel + { + this.CollectPixelBytes(rowSpan, quantized, row); + this.FilterPixelBytes(ref filter, ref attempt); + } - this.FilterPixelBytes(ref filter, ref attempt); + /// + /// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode. + /// + /// The row span. + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void EncodeAdam7IndexedPixelRow( + ReadOnlySpan row, + ref Span filter, + ref Span attempt) + { + // CollectPixelBytes + if (this.bitDepth < 8) + { + PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth); } - - /// - /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed - /// to be most compressible, using lowest total variation as proxy for compressibility. - /// - /// The filtered buffer. - /// Used for attempting optimized filtering. - private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) + else { - // Palette images don't compress well with adaptive filtering. - // Nor do images comprising a single row. - if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) - { - NoneFilter.Encode(this.currentScanline.GetSpan(), filter); - return; - } - - Span current = this.currentScanline.GetSpan(); - Span previous = this.previousScanline.GetSpan(); + row.CopyTo(this.currentScanline.GetSpan()); + } - int min = int.MaxValue; - SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } + this.FilterPixelBytes(ref filter, ref attempt); + } - UpFilter.Encode(current, previous, attempt, out sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } + /// + /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed + /// to be most compressible, using lowest total variation as proxy for compressibility. + /// + /// The filtered buffer. + /// Used for attempting optimized filtering. + private void ApplyOptimalFilteredScanline(ref Span filter, ref Span attempt) + { + // Palette images don't compress well with adaptive filtering. + // Nor do images comprising a single row. + if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8) + { + NoneFilter.Encode(this.currentScanline.GetSpan(), filter); + return; + } - AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); - if (sum < min) - { - min = sum; - RuntimeUtility.Swap(ref filter, ref attempt); - } + Span current = this.currentScanline.GetSpan(); + Span previous = this.previousScanline.GetSpan(); - PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); - if (sum < min) - { - RuntimeUtility.Swap(ref filter, ref attempt); - } + int min = int.MaxValue; + SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum); + if (sum < min) + { + min = sum; + RuntimeUtility.Swap(ref filter, ref attempt); } - /// - /// Writes the header chunk to the stream. - /// - /// The containing image data. - private void WriteHeaderChunk(Stream stream) + UpFilter.Encode(current, previous, attempt, out sum); + if (sum < min) { - PngHeader header = new( - width: this.width, - height: this.height, - bitDepth: this.bitDepth, - colorType: this.options.ColorType.Value, - compressionMethod: 0, // None - filterMethod: 0, - interlaceMethod: this.options.InterlaceMethod.Value); - - header.WriteTo(this.chunkDataBuffer); - - this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size); + min = sum; + RuntimeUtility.Swap(ref filter, ref attempt); } - /// - /// Writes the palette chunk to the stream. - /// Should be written before the first IDAT chunk. - /// - /// The pixel format. - /// The containing image data. - /// The quantized frame. - private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) - where TPixel : unmanaged, IPixel + AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) { - if (quantized is null) - { - return; - } + min = sum; + RuntimeUtility.Swap(ref filter, ref attempt); + } - // Grab the palette and write it to the stream. - ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = palette.Length; - int colorTableLength = paletteLength * Unsafe.SizeOf(); - bool hasAlpha = false; + PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum); + if (sum < min) + { + RuntimeUtility.Swap(ref filter, ref attempt); + } + } - using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); - using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); + /// + /// Writes the header chunk to the stream. + /// + /// The containing image data. + private void WriteHeaderChunk(Stream stream) + { + PngHeader header = new( + width: this.width, + height: this.height, + bitDepth: this.bitDepth, + colorType: this.options.ColorType.Value, + compressionMethod: 0, // None + filterMethod: 0, + interlaceMethod: this.options.InterlaceMethod.Value); + + header.WriteTo(this.chunkDataBuffer); + + this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size); + } - ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); + /// + /// Writes the palette chunk to the stream. + /// Should be written before the first IDAT chunk. + /// + /// The pixel format. + /// The containing image data. + /// The quantized frame. + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel + { + if (quantized is null) + { + return; + } - // 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); + // Grab the palette and write it to the stream. + ReadOnlySpan palette = quantized.Palette.Span; + int paletteLength = palette.Length; + int colorTableLength = paletteLength * Unsafe.SizeOf(); + bool hasAlpha = false; - // 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; + using IMemoryOwner colorTable = this.memoryAllocator.Allocate(colorTableLength); + using IMemoryOwner alphaTable = this.memoryAllocator.Allocate(paletteLength); - Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; - if (alpha > this.options.Threshold) - { - alpha = byte.MaxValue; - } + ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - hasAlpha = hasAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, i) = alpha; - } + // 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); - this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); + // 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; - // Write the transparency data - if (hasAlpha) + Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; + if (alpha > this.options.Threshold) { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); + alpha = byte.MaxValue; } + + hasAlpha = hasAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; } - /// - /// Writes the physical dimension information to the stream. - /// Should be written before IDAT chunk. - /// - /// The containing image data. - /// The image metadata. - private void WritePhysicalChunk(Stream stream, ImageMetadata meta) + this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength); + + // Write the transparency data + if (hasAlpha) { - if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk) - { - return; - } + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength); + } + } + + /// + /// Writes the physical dimension information to the stream. + /// Should be written before IDAT chunk. + /// + /// The containing image data. + /// The image metadata. + private void WritePhysicalChunk(Stream stream, ImageMetadata meta) + { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludePhysicalChunk) == PngChunkFilter.ExcludePhysicalChunk) + { + return; + } - PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); + PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); - this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size); + this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size); + } + + /// + /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the metadata. + /// + /// The containing image data. + /// The image metadata. + private void WriteExifChunk(Stream stream, ImageMetadata meta) + { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk) + { + return; } - /// - /// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the metadata. - /// - /// The containing image data. - /// The image metadata. - private void WriteExifChunk(Stream stream, ImageMetadata meta) + if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) { - if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeExifChunk) == PngChunkFilter.ExcludeExifChunk) - { - return; - } + return; + } - if (meta.ExifProfile is null || meta.ExifProfile.Values.Count == 0) - { - return; - } + meta.SyncProfiles(); + this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + } - meta.SyncProfiles(); - this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); + /// + /// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata. + /// + /// The containing image data. + /// The image metadata. + private void WriteXmpChunk(Stream stream, ImageMetadata meta) + { + const int iTxtHeaderSize = 5; + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) + { + return; } - /// - /// Writes an iTXT chunk, containing the XMP metadata to the stream, if such profile is present in the metadata. - /// - /// The containing image data. - /// The image metadata. - private void WriteXmpChunk(Stream stream, ImageMetadata meta) + if (meta.XmpProfile is null) { - const int iTxtHeaderSize = 5; - if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) - { - return; - } + return; + } - if (meta.XmpProfile is null) - { - return; - } + byte[] xmpData = meta.XmpProfile.Data; - byte[] xmpData = meta.XmpProfile.Data; + if (xmpData.Length == 0) + { + return; + } - if (xmpData.Length == 0) - { - return; - } + int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + iTxtHeaderSize; + + using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); + Span payload = owner.GetSpan(); + PngConstants.XmpKeyword.CopyTo(payload); + int bytesWritten = PngConstants.XmpKeyword.Length; + + // Write the iTxt header (all zeros in this case). + Span iTxtHeader = payload[bytesWritten..]; + iTxtHeader[4] = 0; + iTxtHeader[3] = 0; + iTxtHeader[2] = 0; + iTxtHeader[1] = 0; + iTxtHeader[0] = 0; + bytesWritten += 5; + + // And the XMP data itself. + xmpData.CopyTo(payload[bytesWritten..]); + this.WriteChunk(stream, PngChunkType.InternationalText, payload); + } - int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + iTxtHeaderSize; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span payload = owner.GetSpan(); - PngConstants.XmpKeyword.CopyTo(payload); - int bytesWritten = PngConstants.XmpKeyword.Length; - - // Write the iTxt header (all zeros in this case). - Span iTxtHeader = payload[bytesWritten..]; - iTxtHeader[4] = 0; - iTxtHeader[3] = 0; - iTxtHeader[2] = 0; - iTxtHeader[1] = 0; - iTxtHeader[0] = 0; - bytesWritten += 5; - - // And the XMP data itself. - xmpData.CopyTo(payload[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.InternationalText, payload); + /// + /// Writes the color profile chunk. + /// + /// The stream to write to. + /// The image meta data. + private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData) + { + if (metaData.IccProfile is null) + { + return; } - /// - /// Writes the color profile chunk. - /// - /// The stream to write to. - /// The image meta data. - private void WriteColorProfileChunk(Stream stream, ImageMetadata metaData) - { - if (metaData.IccProfile is null) - { - return; - } + byte[] iccProfileBytes = metaData.IccProfile.ToByteArray(); - byte[] iccProfileBytes = metaData.IccProfile.ToByteArray(); + byte[] compressedData = this.GetZlibCompressedBytes(iccProfileBytes); + int payloadLength = ColorProfileName.Length + compressedData.Length + 2; - byte[] compressedData = this.GetZlibCompressedBytes(iccProfileBytes); - int payloadLength = ColorProfileName.Length + compressedData.Length + 2; + using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); + Span outputBytes = owner.GetSpan(); + PngConstants.Encoding.GetBytes(ColorProfileName).CopyTo(outputBytes); + int bytesWritten = ColorProfileName.Length; + outputBytes[bytesWritten++] = 0; // Null separator. + outputBytes[bytesWritten++] = 0; // Compression. + compressedData.CopyTo(outputBytes[bytesWritten..]); + this.WriteChunk(stream, PngChunkType.EmbeddedColorProfile, outputBytes); + } - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(ColorProfileName).CopyTo(outputBytes); - int bytesWritten = ColorProfileName.Length; - outputBytes[bytesWritten++] = 0; // Null separator. - outputBytes[bytesWritten++] = 0; // Compression. - compressedData.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.EmbeddedColorProfile, outputBytes); + /// + /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk, + /// depending whether the text contains any latin characters or should be compressed. + /// + /// The containing image data. + /// The image metadata. + private void WriteTextChunks(Stream stream, PngMetadata meta) + { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) + { + return; } - /// - /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk, - /// depending whether the text contains any latin characters or should be compressed. - /// - /// The containing image data. - /// The image metadata. - private void WriteTextChunks(Stream stream, PngMetadata meta) + const int maxLatinCode = 255; + for (int i = 0; i < meta.TextData.Count; i++) { - if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) - { - return; - } - - const int maxLatinCode = 255; - for (int i = 0; i < meta.TextData.Count; i++) + PngTextData textData = meta.TextData[i]; + bool hasUnicodeCharacters = false; + foreach (char c in textData.Value) { - PngTextData textData = meta.TextData[i]; - bool hasUnicodeCharacters = false; - foreach (char c in textData.Value) + if (c > maxLatinCode) { - if (c > maxLatinCode) - { - hasUnicodeCharacters = true; - break; - } + hasUnicodeCharacters = true; + break; } + } - if (hasUnicodeCharacters || !string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)) - { - // Write iTXt chunk. - byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword); - byte[] textBytes = textData.Value.Length > this.options.TextCompressionThreshold - ? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value)) - : PngConstants.TranslatedEncoding.GetBytes(textData.Value); - - byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); - byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); - - int payloadLength = keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - keywordBytes.CopyTo(outputBytes); - int bytesWritten = keywordBytes.Length; - outputBytes[bytesWritten++] = 0; - if (textData.Value.Length > this.options.TextCompressionThreshold) - { - // Indicate that the text is compressed. - outputBytes[bytesWritten++] = 1; - } - else - { - outputBytes[bytesWritten++] = 0; - } - - outputBytes[bytesWritten++] = 0; - languageTag.CopyTo(outputBytes[bytesWritten..]); - bytesWritten += languageTag.Length; - outputBytes[bytesWritten++] = 0; - translatedKeyword.CopyTo(outputBytes[bytesWritten..]); - bytesWritten += translatedKeyword.Length; - outputBytes[bytesWritten++] = 0; - textBytes.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes); - } - else if (textData.Value.Length > this.options.TextCompressionThreshold) + if (hasUnicodeCharacters || !string.IsNullOrWhiteSpace(textData.LanguageTag) || !string.IsNullOrWhiteSpace(textData.TranslatedKeyword)) + { + // Write iTXt chunk. + byte[] keywordBytes = PngConstants.Encoding.GetBytes(textData.Keyword); + byte[] textBytes = textData.Value.Length > this.options.TextCompressionThreshold + ? this.GetZlibCompressedBytes(PngConstants.TranslatedEncoding.GetBytes(textData.Value)) + : PngConstants.TranslatedEncoding.GetBytes(textData.Value); + + byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); + byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); + + int payloadLength = keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5; + + using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); + Span outputBytes = owner.GetSpan(); + keywordBytes.CopyTo(outputBytes); + int bytesWritten = keywordBytes.Length; + outputBytes[bytesWritten++] = 0; + if (textData.Value.Length > this.options.TextCompressionThreshold) { - // Write zTXt chunk. - byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value)); - int payloadLength = textData.Keyword.Length + compressedData.Length + 2; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - int bytesWritten = textData.Keyword.Length; - outputBytes[bytesWritten++] = 0; // Null separator. - outputBytes[bytesWritten++] = 0; // Compression. - compressedData.CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes); + // Indicate that the text is compressed. + outputBytes[bytesWritten++] = 1; } else { - // Write tEXt chunk. - int payloadLength = textData.Keyword.Length + textData.Value.Length + 1; - - using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); - Span outputBytes = owner.GetSpan(); - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - int bytesWritten = textData.Keyword.Length; outputBytes[bytesWritten++] = 0; - PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes[bytesWritten..]); - this.WriteChunk(stream, PngChunkType.Text, outputBytes); } + + outputBytes[bytesWritten++] = 0; + languageTag.CopyTo(outputBytes[bytesWritten..]); + bytesWritten += languageTag.Length; + outputBytes[bytesWritten++] = 0; + translatedKeyword.CopyTo(outputBytes[bytesWritten..]); + bytesWritten += translatedKeyword.Length; + outputBytes[bytesWritten++] = 0; + textBytes.CopyTo(outputBytes[bytesWritten..]); + this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes); + } + else if (textData.Value.Length > this.options.TextCompressionThreshold) + { + // Write zTXt chunk. + byte[] compressedData = this.GetZlibCompressedBytes(PngConstants.Encoding.GetBytes(textData.Value)); + int payloadLength = textData.Keyword.Length + compressedData.Length + 2; + + using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); + Span outputBytes = owner.GetSpan(); + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + int bytesWritten = textData.Keyword.Length; + outputBytes[bytesWritten++] = 0; // Null separator. + outputBytes[bytesWritten++] = 0; // Compression. + compressedData.CopyTo(outputBytes[bytesWritten..]); + this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes); + } + else + { + // Write tEXt chunk. + int payloadLength = textData.Keyword.Length + textData.Value.Length + 1; + + using IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength); + Span outputBytes = owner.GetSpan(); + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + int bytesWritten = textData.Keyword.Length; + outputBytes[bytesWritten++] = 0; + PngConstants.Encoding.GetBytes(textData.Value).CopyTo(outputBytes[bytesWritten..]); + this.WriteChunk(stream, PngChunkType.Text, outputBytes); } } + } - /// - /// Compresses a given text using Zlib compression. - /// - /// The bytes to compress. - /// The compressed byte array. - private byte[] GetZlibCompressedBytes(byte[] dataBytes) + /// + /// Compresses a given text using Zlib compression. + /// + /// The bytes to compress. + /// The compressed byte array. + private byte[] GetZlibCompressedBytes(byte[] dataBytes) + { + using MemoryStream memoryStream = new(); + using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { - using MemoryStream memoryStream = new(); - using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) - { - deflateStream.Write(dataBytes); - } + deflateStream.Write(dataBytes); + } - return memoryStream.ToArray(); + return memoryStream.ToArray(); + } + + /// + /// Writes the gamma information to the stream. + /// Should be written before PLTE and IDAT chunk. + /// + /// The containing image data. + private void WriteGammaChunk(Stream stream) + { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk) + { + return; } - /// - /// Writes the gamma information to the stream. - /// Should be written before PLTE and IDAT chunk. - /// - /// The containing image data. - private void WriteGammaChunk(Stream stream) + if (this.options.Gamma > 0) { - if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeGammaChunk) == PngChunkFilter.ExcludeGammaChunk) - { - return; - } + // 4-byte unsigned integer of gamma * 100,000. + uint gammaValue = (uint)(this.options.Gamma * 100_000F); - if (this.options.Gamma > 0) - { - // 4-byte unsigned integer of gamma * 100,000. - uint gammaValue = (uint)(this.options.Gamma * 100_000F); + BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue); - BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue); + this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4); + } + } - this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4); - } + /// + /// Writes the transparency chunk to the stream. + /// Should be written after PLTE and before IDAT. + /// + /// The containing image data. + /// The image metadata. + private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) + { + if (!pngMetadata.HasTransparency) + { + return; } - /// - /// Writes the transparency chunk to the stream. - /// Should be written after PLTE and before IDAT. - /// - /// The containing image data. - /// The image metadata. - private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata) + Span alpha = this.chunkDataBuffer.AsSpan(); + if (pngMetadata.ColorType == PngColorType.Rgb) { - if (!pngMetadata.HasTransparency) + if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit) { - return; - } + Rgb48 rgb = pngMetadata.TransparentRgb48.Value; + BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R); + BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G); + BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B); - Span alpha = this.chunkDataBuffer.AsSpan(); - if (pngMetadata.ColorType == PngColorType.Rgb) + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); + } + else if (pngMetadata.TransparentRgb24.HasValue) { - if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit) - { - Rgb48 rgb = pngMetadata.TransparentRgb48.Value; - BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R); - BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G); - BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B); - - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); - } - else if (pngMetadata.TransparentRgb24.HasValue) - { - alpha.Clear(); - Rgb24 rgb = pngMetadata.TransparentRgb24.Value; - alpha[1] = rgb.R; - alpha[3] = rgb.G; - alpha[5] = rgb.B; - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); - } + alpha.Clear(); + Rgb24 rgb = pngMetadata.TransparentRgb24.Value; + alpha[1] = rgb.R; + alpha[3] = rgb.G; + alpha[5] = rgb.B; + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6); } - else if (pngMetadata.ColorType == PngColorType.Grayscale) + } + else if (pngMetadata.ColorType == PngColorType.Grayscale) + { + if (pngMetadata.TransparentL16.HasValue && this.use16Bit) { - if (pngMetadata.TransparentL16.HasValue && this.use16Bit) - { - BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue); - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); - } - else if (pngMetadata.TransparentL8.HasValue) - { - alpha.Clear(); - alpha[1] = pngMetadata.TransparentL8.Value.PackedValue; - this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); - } + BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue); + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); + } + else if (pngMetadata.TransparentL8.HasValue) + { + alpha.Clear(); + alpha[1] = pngMetadata.TransparentL8.Value.PackedValue; + this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2); } } + } - /// - /// Writes the pixel information to the stream. - /// - /// The pixel format. - /// The image. - /// The quantized pixel data. Can be null. - /// The stream. - private void WriteDataChunks(Image pixels, IndexedImageFrame quantized, Stream stream) - where TPixel : unmanaged, IPixel - { - byte[] buffer; - int bufferLength; + /// + /// Writes the pixel information to the stream. + /// + /// The pixel format. + /// The image. + /// The quantized pixel data. Can be null. + /// The stream. + private void WriteDataChunks(Image pixels, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel + { + byte[] buffer; + int bufferLength; - using (MemoryStream memoryStream = new()) + using (MemoryStream memoryStream = new()) + { + using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) { - using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.options.CompressionLevel)) + if (this.options.InterlaceMethod == PngInterlaceMode.Adam7) { - if (this.options.InterlaceMethod == PngInterlaceMode.Adam7) + if (quantized != null) { - if (quantized != null) - { - this.EncodeAdam7IndexedPixels(quantized, deflateStream); - } - else - { - this.EncodeAdam7Pixels(pixels, deflateStream); - } + this.EncodeAdam7IndexedPixels(quantized, deflateStream); } else { - this.EncodePixels(pixels, quantized, deflateStream); + this.EncodeAdam7Pixels(pixels, deflateStream); } } - - buffer = memoryStream.ToArray(); - bufferLength = buffer.Length; - } - - // Store the chunks in repeated 64k blocks. - // This reduces the memory load for decoding the image for many decoders. - int numChunks = bufferLength / MaxBlockSize; - - if (bufferLength % MaxBlockSize != 0) - { - numChunks++; - } - - for (int i = 0; i < numChunks; i++) - { - int length = bufferLength - (i * MaxBlockSize); - - if (length > MaxBlockSize) + else { - length = MaxBlockSize; + this.EncodePixels(pixels, quantized, deflateStream); } - - this.WriteChunk(stream, PngChunkType.Data, buffer, i * MaxBlockSize, length); } + + buffer = memoryStream.ToArray(); + bufferLength = buffer.Length; } - /// - /// Allocates the buffers for each scanline. - /// - /// The bytes per scanline. - private void AllocateScanlineBuffers(int bytesPerScanline) + // Store the chunks in repeated 64k blocks. + // This reduces the memory load for decoding the image for many decoders. + int numChunks = bufferLength / MaxBlockSize; + + if (bufferLength % MaxBlockSize != 0) { - // Clean up from any potential previous runs. - this.previousScanline?.Dispose(); - this.currentScanline?.Dispose(); - this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); - this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + numChunks++; } - /// - /// Encodes the pixels. - /// - /// The type of the pixel. - /// The pixels. - /// The quantized pixels span. - /// The deflate stream. - private void EncodePixels(Image pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel + for (int i = 0; i < numChunks; i++) { - int bytesPerScanline = this.CalculateScanlineLength(this.width); - int filterLength = bytesPerScanline + 1; - this.AllocateScanlineBuffers(bytesPerScanline); + int length = bufferLength - (i * MaxBlockSize); - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - - pixels.ProcessPixelRows(accessor => + if (length > MaxBlockSize) { - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); - for (int y = 0; y < this.height; y++) - { - this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); - deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } - }); + length = MaxBlockSize; + } + + this.WriteChunk(stream, PngChunkType.Data, buffer, i * MaxBlockSize, length); } + } - /// - /// Interlaced encoding the pixels. - /// - /// The type of the pixel. - /// The image. - /// The deflate stream. - private void EncodeAdam7Pixels(Image image, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel + /// + /// Allocates the buffers for each scanline. + /// + /// The bytes per scanline. + private void AllocateScanlineBuffers(int bytesPerScanline) + { + // Clean up from any potential previous runs. + this.previousScanline?.Dispose(); + this.currentScanline?.Dispose(); + this.previousScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + this.currentScanline = this.memoryAllocator.Allocate(bytesPerScanline, AllocationOptions.Clean); + } + + /// + /// Encodes the pixels. + /// + /// The type of the pixel. + /// The pixels. + /// The quantized pixels span. + /// The deflate stream. + private void EncodePixels(Image pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel + { + int bytesPerScanline = this.CalculateScanlineLength(this.width); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); + + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + + pixels.ProcessPixelRows(accessor => { - int width = image.Width; - int height = image.Height; - Buffer2D pixelBuffer = image.Frames.RootFrame.PixelBuffer; - for (int pass = 0; pass < 7; pass++) + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + for (int y = 0; y < this.height; y++) { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; - int blockWidth = Adam7.ComputeBlockWidth(width, pass); + this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); + } + }); + } - int bytesPerScanline = this.bytesPerPixel <= 1 - ? ((blockWidth * this.bitDepth) + 7) / 8 - : blockWidth * this.bytesPerPixel; + /// + /// Interlaced encoding the pixels. + /// + /// The type of the pixel. + /// The image. + /// The deflate stream. + private void EncodeAdam7Pixels(Image image, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.Frames.RootFrame.PixelBuffer; + for (int pass = 0; pass < 7; pass++) + { + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(width, pass); + + int bytesPerScanline = this.bytesPerPixel <= 1 + ? ((blockWidth * this.bitDepth) + 7) / 8 + : blockWidth * this.bytesPerPixel; - int filterLength = bytesPerScanline + 1; - this.AllocateScanlineBuffers(bytesPerScanline); + int filterLength = bytesPerScanline + 1; + this.AllocateScanlineBuffers(bytesPerScanline); - using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - Span block = blockBuffer.GetSpan(); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); - for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) + for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) + { + // Collect pixel data + Span srcRow = pixelBuffer.DangerousGetRowSpan(row); + for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { - // Collect pixel data - Span srcRow = pixelBuffer.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) - { - block[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // Encode data - // Note: quantized parameter not used - // Note: row parameter not used - this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); - deflateStream.Write(filter); + // Encode data + // Note: quantized parameter not used + // Note: row parameter not used + this.CollectAndFilterPixelRow(block, ref filter, ref attempt, null, -1); + deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } + this.SwapScanlineBuffers(); } } + } - /// - /// Interlaced encoding the quantized (indexed, with palette) pixels. - /// - /// The type of the pixel. - /// The quantized. - /// The deflate stream. - private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : unmanaged, IPixel + /// + /// Interlaced encoding the quantized (indexed, with palette) pixels. + /// + /// The type of the pixel. + /// The quantized. + /// The deflate stream. + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel + { + int width = quantized.Width; + int height = quantized.Height; + for (int pass = 0; pass < 7; pass++) { - int width = quantized.Width; - int height = quantized.Height; - for (int pass = 0; pass < 7; pass++) - { - int startRow = Adam7.FirstRow[pass]; - int startCol = Adam7.FirstColumn[pass]; - int blockWidth = Adam7.ComputeBlockWidth(width, pass); + int startRow = Adam7.FirstRow[pass]; + int startCol = Adam7.FirstColumn[pass]; + int blockWidth = Adam7.ComputeBlockWidth(width, pass); - int bytesPerScanline = this.bytesPerPixel <= 1 - ? ((blockWidth * this.bitDepth) + 7) / 8 - : blockWidth * this.bytesPerPixel; + int bytesPerScanline = this.bytesPerPixel <= 1 + ? ((blockWidth * this.bitDepth) + 7) / 8 + : blockWidth * this.bytesPerPixel; - int filterLength = bytesPerScanline + 1; + int filterLength = bytesPerScanline + 1; - this.AllocateScanlineBuffers(bytesPerScanline); + this.AllocateScanlineBuffers(bytesPerScanline); - using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); - using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner blockBuffer = this.memoryAllocator.Allocate(blockWidth); + using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); + using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - Span block = blockBuffer.GetSpan(); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); + Span block = blockBuffer.GetSpan(); + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); - for (int row = startRow; - row < height; - row += Adam7.RowIncrement[pass]) + for (int row = startRow; + row < height; + row += Adam7.RowIncrement[pass]) + { + // Collect data + ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); + for (int col = startCol, i = 0; + col < width; + col += Adam7.ColumnIncrement[pass]) { - // Collect data - ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); - for (int col = startCol, i = 0; - col < width; - col += Adam7.ColumnIncrement[pass]) - { - block[i++] = srcRow[col]; - } + block[i++] = srcRow[col]; + } - // Encode data - this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); - deflateStream.Write(filter); + // Encode data + this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt); + deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } + this.SwapScanlineBuffers(); } } + } - /// - /// Writes the chunk end to the stream. - /// - /// The containing image data. - private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null); - - /// - /// Writes a chunk to the stream. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - private void WriteChunk(Stream stream, PngChunkType type, Span data) - => this.WriteChunk(stream, type, data, 0, data.Length); - - /// - /// Writes a chunk of a specified length to the stream at the given offset. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - /// The position to offset the data at. - /// The of the data to write. - private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length) - { - BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); - BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); + /// + /// Writes the chunk end to the stream. + /// + /// The containing image data. + private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null); - stream.Write(this.buffer, 0, 8); + /// + /// Writes a chunk to the stream. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + private void WriteChunk(Stream stream, PngChunkType type, Span data) + => this.WriteChunk(stream, type, data, 0, data.Length); - uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer + /// + /// Writes a chunk of a specified length to the stream at the given offset. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + /// The position to offset the data at. + /// The of the data to write. + private void WriteChunk(Stream stream, PngChunkType type, Span data, int offset, int length) + { + BinaryPrimitives.WriteInt32BigEndian(this.buffer, length); + BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type); - if (data.Length > 0 && length > 0) - { - stream.Write(data, offset, length); + stream.Write(this.buffer, 0, 8); - crc = Crc32.Calculate(crc, data.Slice(offset, length)); - } + uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer - BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); + if (data.Length > 0 && length > 0) + { + stream.Write(data, offset, length); - stream.Write(this.buffer, 0, 4); // write the crc + crc = Crc32.Calculate(crc, data.Slice(offset, length)); } - /// - /// Calculates the scanline length. - /// - /// The width of the row. - /// - /// The representing the length. - /// - private int CalculateScanlineLength(int width) - { - int mod = this.bitDepth == 16 ? 16 : 8; - int scanlineLength = width * this.bitDepth * this.bytesPerPixel; + BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc); - int amount = scanlineLength % mod; - if (amount != 0) - { - scanlineLength += mod - amount; - } + stream.Write(this.buffer, 0, 4); // write the crc + } - return scanlineLength / mod; - } + /// + /// Calculates the scanline length. + /// + /// The width of the row. + /// + /// The representing the length. + /// + private int CalculateScanlineLength(int width) + { + int mod = this.bitDepth == 16 ? 16 : 8; + int scanlineLength = width * this.bitDepth * this.bytesPerPixel; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SwapScanlineBuffers() + int amount = scanlineLength % mod; + if (amount != 0) { - ref IMemoryOwner prev = ref this.previousScanline; - ref IMemoryOwner current = ref this.currentScanline; - RuntimeUtility.Swap(ref prev, ref current); + scanlineLength += mod - amount; } + + return scanlineLength / mod; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SwapScanlineBuffers() + { + ref IMemoryOwner prev = ref this.previousScanline; + ref IMemoryOwner current = ref this.currentScanline; + RuntimeUtility.Swap(ref prev, ref current); } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs index 99f64f2387..5bee1ebd53 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderHelpers.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// The helper methods for class. +/// +internal static class PngEncoderHelpers { /// - /// The helper methods for class. + /// Packs the given 8 bit array into and array of depths. /// - internal static class PngEncoderHelpers + /// The source span in 8 bits. + /// The resultant span in . + /// The bit depth. + /// The scaling factor. + public static void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits, float scale = 1) { - /// - /// Packs the given 8 bit array into and array of depths. - /// - /// The source span in 8 bits. - /// The resultant span in . - /// The bit depth. - /// The scaling factor. - public static void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits, float scale = 1) - { - ref byte sourceRef = ref MemoryMarshal.GetReference(source); - ref byte resultRef = ref MemoryMarshal.GetReference(result); - - int shift = 8 - bits; - byte mask = (byte)(0xFF >> shift); - byte shift0 = (byte)shift; - int v = 0; - int resultOffset = 0; + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref MemoryMarshal.GetReference(result); - for (int i = 0; i < source.Length; i++) - { - int value = ((int)MathF.Round(Unsafe.Add(ref sourceRef, i) / scale)) & mask; - v |= value << shift; + int shift = 8 - bits; + byte mask = (byte)(0xFF >> shift); + byte shift0 = (byte)shift; + int v = 0; + int resultOffset = 0; - if (shift == 0) - { - shift = shift0; - Unsafe.Add(ref resultRef, resultOffset) = (byte)v; - resultOffset++; - v = 0; - } - else - { - shift -= bits; - } - } + for (int i = 0; i < source.Length; i++) + { + int value = ((int)MathF.Round(Unsafe.Add(ref sourceRef, i) / scale)) & mask; + v |= value << shift; - if (shift != shift0) + if (shift == 0) { + shift = shift0; Unsafe.Add(ref resultRef, resultOffset) = (byte)v; + resultOffset++; + v = 0; } + else + { + shift -= bits; + } + } + + if (shift != shift0) + { + Unsafe.Add(ref resultRef, resultOffset) = (byte)v; } } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs index aaf1071cfc..7fe27b78d7 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptions.cs @@ -3,67 +3,66 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// The options structure for the . +/// +internal class PngEncoderOptions : IPngEncoderOptions { /// - /// The options structure for the . + /// Initializes a new instance of the class. /// - internal class PngEncoderOptions : IPngEncoderOptions + /// The source. + public PngEncoderOptions(IPngEncoderOptions source) { - /// - /// Initializes a new instance of the class. - /// - /// The source. - public PngEncoderOptions(IPngEncoderOptions source) - { - this.BitDepth = source.BitDepth; - this.ColorType = source.ColorType; - this.FilterMethod = source.FilterMethod; - this.CompressionLevel = source.CompressionLevel; - this.TextCompressionThreshold = source.TextCompressionThreshold; - this.Gamma = source.Gamma; - this.Quantizer = source.Quantizer; - this.Threshold = source.Threshold; - this.InterlaceMethod = source.InterlaceMethod; - this.ChunkFilter = source.ChunkFilter; - this.IgnoreMetadata = source.IgnoreMetadata; - this.TransparentColorMode = source.TransparentColorMode; - } + this.BitDepth = source.BitDepth; + this.ColorType = source.ColorType; + this.FilterMethod = source.FilterMethod; + this.CompressionLevel = source.CompressionLevel; + this.TextCompressionThreshold = source.TextCompressionThreshold; + this.Gamma = source.Gamma; + this.Quantizer = source.Quantizer; + this.Threshold = source.Threshold; + this.InterlaceMethod = source.InterlaceMethod; + this.ChunkFilter = source.ChunkFilter; + this.IgnoreMetadata = source.IgnoreMetadata; + this.TransparentColorMode = source.TransparentColorMode; + } - /// - public PngBitDepth? BitDepth { get; set; } + /// + public PngBitDepth? BitDepth { get; set; } - /// - public PngColorType? ColorType { get; set; } + /// + public PngColorType? ColorType { get; set; } - /// - public PngFilterMethod? FilterMethod { get; set; } + /// + public PngFilterMethod? FilterMethod { get; set; } - /// - public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; + /// + public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; - /// - public int TextCompressionThreshold { get; } + /// + public int TextCompressionThreshold { get; } - /// - public float? Gamma { get; set; } + /// + public float? Gamma { get; set; } - /// - public IQuantizer Quantizer { get; set; } + /// + public IQuantizer Quantizer { get; set; } - /// - public byte Threshold { get; } + /// + public byte Threshold { get; } - /// - public PngInterlaceMode? InterlaceMethod { get; set; } + /// + public PngInterlaceMode? InterlaceMethod { get; set; } - /// - public PngChunkFilter? ChunkFilter { get; set; } + /// + public PngChunkFilter? ChunkFilter { get; set; } - /// - public bool IgnoreMetadata { get; set; } + /// + public bool IgnoreMetadata { get; set; } - /// - public PngTransparentColorMode TransparentColorMode { get; set; } - } + /// + public PngTransparentColorMode TransparentColorMode { get; set; } } diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index bed20fd415..e1ee61c378 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -1,216 +1,214 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// The helper methods for the PNG encoder options. +/// +internal static class PngEncoderOptionsHelpers { /// - /// The helper methods for the PNG encoder options. + /// Adjusts the options based upon the given metadata. /// - internal static class PngEncoderOptionsHelpers + /// The options. + /// The PNG metadata. + /// if set to true [use16 bit]. + /// The bytes per pixel. + public static void AdjustOptions( + PngEncoderOptions options, + PngMetadata pngMetadata, + out bool use16Bit, + out int bytesPerPixel) + where TPixel : unmanaged, IPixel { - /// - /// Adjusts the options based upon the given metadata. - /// - /// The options. - /// The PNG metadata. - /// if set to true [use16 bit]. - /// The bytes per pixel. - public static void AdjustOptions( - PngEncoderOptions options, - PngMetadata pngMetadata, - out bool use16Bit, - out int bytesPerPixel) - where TPixel : unmanaged, IPixel + // Always take the encoder options over the metadata values. + options.Gamma ??= pngMetadata.Gamma; + + // Use options, then check metadata, if nothing set there then we suggest + // a sensible default based upon the pixel format. + options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType(); + options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth(); + if (!options.FilterMethod.HasValue) { - // Always take the encoder options over the metadata values. - options.Gamma ??= pngMetadata.Gamma; - - // Use options, then check metadata, if nothing set there then we suggest - // a sensible default based upon the pixel format. - options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType(); - options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth(); - if (!options.FilterMethod.HasValue) + // Specification recommends default filter method None for paletted images and Paeth for others. + if (options.ColorType == PngColorType.Palette) { - // Specification recommends default filter method None for paletted images and Paeth for others. - if (options.ColorType == PngColorType.Palette) - { - options.FilterMethod = PngFilterMethod.None; - } - else - { - options.FilterMethod = PngFilterMethod.Paeth; - } + options.FilterMethod = PngFilterMethod.None; } - - // Ensure bit depth and color type are a supported combination. - // Bit8 is the only bit depth supported by all color types. - byte bits = (byte)options.BitDepth; - byte[] validBitDepths = PngConstants.ColorTypes[options.ColorType.Value]; - if (Array.IndexOf(validBitDepths, bits) == -1) + else { - options.BitDepth = PngBitDepth.Bit8; + options.FilterMethod = PngFilterMethod.Paeth; } + } - options.InterlaceMethod ??= pngMetadata.InterlaceMethod; + // Ensure bit depth and color type are a supported combination. + // Bit8 is the only bit depth supported by all color types. + byte bits = (byte)options.BitDepth; + byte[] validBitDepths = PngConstants.ColorTypes[options.ColorType.Value]; + if (Array.IndexOf(validBitDepths, bits) == -1) + { + options.BitDepth = PngBitDepth.Bit8; + } - use16Bit = options.BitDepth == PngBitDepth.Bit16; - bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); + options.InterlaceMethod ??= pngMetadata.InterlaceMethod; - if (options.IgnoreMetadata) - { - options.ChunkFilter = PngChunkFilter.ExcludeAll; - } + use16Bit = options.BitDepth == PngBitDepth.Bit16; + bytesPerPixel = CalculateBytesPerPixel(options.ColorType, use16Bit); + + if (options.IgnoreMetadata) + { + options.ChunkFilter = PngChunkFilter.ExcludeAll; } + } - /// - /// Creates the quantized frame. - /// - /// The type of the pixel. - /// The options. - /// The image. - public static IndexedImageFrame CreateQuantizedFrame( - PngEncoderOptions options, - Image image) - where TPixel : unmanaged, IPixel + /// + /// Creates the quantized frame. + /// + /// The type of the pixel. + /// The options. + /// The image. + public static IndexedImageFrame CreateQuantizedFrame( + PngEncoderOptions options, + Image image) + where TPixel : unmanaged, IPixel + { + if (options.ColorType != PngColorType.Palette) { - if (options.ColorType != PngColorType.Palette) - { - return null; - } + return null; + } - // Use the metadata to determine what quantization depth to use if no quantizer has been set. - if (options.Quantizer is null) - { - byte bits = (byte)options.BitDepth; - var maxColors = ColorNumerics.GetColorCountForBitDepth(bits); - options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); - } + // Use the metadata to determine what quantization depth to use if no quantizer has been set. + if (options.Quantizer is null) + { + byte bits = (byte)options.BitDepth; + var maxColors = ColorNumerics.GetColorCountForBitDepth(bits); + options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); + } - // Create quantized frame returning the palette and set the bit depth. - using (IQuantizer frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration())) - { - ImageFrame frame = image.Frames.RootFrame; - return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - } + // Create quantized frame returning the palette and set the bit depth. + using (IQuantizer frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer(image.GetConfiguration())) + { + ImageFrame frame = image.Frames.RootFrame; + return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } + } - /// - /// Calculates the bit depth value. - /// - /// The type of the pixel. - /// The options. - /// The quantized frame. - public static byte CalculateBitDepth( - PngEncoderOptions options, - IndexedImageFrame quantizedFrame) - where TPixel : unmanaged, IPixel + /// + /// Calculates the bit depth value. + /// + /// The type of the pixel. + /// The options. + /// The quantized frame. + public static byte CalculateBitDepth( + PngEncoderOptions options, + IndexedImageFrame quantizedFrame) + where TPixel : unmanaged, IPixel + { + byte bitDepth; + if (options.ColorType == PngColorType.Palette) { - byte bitDepth; - if (options.ColorType == PngColorType.Palette) - { - byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length), 1, 8); - byte bits = Math.Max((byte)options.BitDepth, quantizedBits); - - // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk - // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not - // be within the acceptable range. - if (bits == 3) - { - bits = 4; - } - else if (bits >= 5 && bits <= 7) - { - bits = 8; - } - - bitDepth = bits; - } - else + byte quantizedBits = (byte)Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length), 1, 8); + byte bits = Math.Max((byte)options.BitDepth, quantizedBits); + + // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk + // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not + // be within the acceptable range. + if (bits == 3) { - bitDepth = (byte)options.BitDepth; + bits = 4; } - - if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1) + else if (bits >= 5 && bits <= 7) { - throw new NotSupportedException("Bit depth is not supported or not valid."); + bits = 8; } - return bitDepth; + bitDepth = bits; } - - /// - /// Calculates the correct number of bytes per pixel for the given color type. - /// - /// Bytes per pixel. - private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) + else { - return pngColorType switch - { - PngColorType.Grayscale => use16Bit ? 2 : 1, - PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, - PngColorType.Palette => 1, - PngColorType.Rgb => use16Bit ? 6 : 3, - - // PngColorType.RgbWithAlpha - _ => use16Bit ? 8 : 4, - }; + bitDepth = (byte)options.BitDepth; } - /// - /// Returns a suggested for the given - /// This is not exhaustive but covers many common pixel formats. - /// - private static PngColorType SuggestColorType() - where TPixel : unmanaged, IPixel + if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1) { - return typeof(TPixel) switch - { - Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha, - Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha, - Type t when t == typeof(Bgr24) => PngColorType.Rgb, - Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha, - Type t when t == typeof(L8) => PngColorType.Grayscale, - Type t when t == typeof(L16) => PngColorType.Grayscale, - Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha, - Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha, - Type t when t == typeof(Rgb24) => PngColorType.Rgb, - Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha, - Type t when t == typeof(Rgb48) => PngColorType.Rgb, - Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha, - Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha, - _ => PngColorType.RgbWithAlpha - }; + throw new NotSupportedException("Bit depth is not supported or not valid."); } - /// - /// Returns a suggested for the given - /// This is not exhaustive but covers many common pixel formats. - /// - private static PngBitDepth SuggestBitDepth() - where TPixel : unmanaged, IPixel + return bitDepth; + } + + /// + /// Calculates the correct number of bytes per pixel for the given color type. + /// + /// Bytes per pixel. + private static int CalculateBytesPerPixel(PngColorType? pngColorType, bool use16Bit) + { + return pngColorType switch { - return typeof(TPixel) switch - { - Type t when t == typeof(A8) => PngBitDepth.Bit8, - Type t when t == typeof(Argb32) => PngBitDepth.Bit8, - Type t when t == typeof(Bgr24) => PngBitDepth.Bit8, - Type t when t == typeof(Bgra32) => PngBitDepth.Bit8, - Type t when t == typeof(L8) => PngBitDepth.Bit8, - Type t when t == typeof(L16) => PngBitDepth.Bit16, - Type t when t == typeof(La16) => PngBitDepth.Bit8, - Type t when t == typeof(La32) => PngBitDepth.Bit16, - Type t when t == typeof(Rgb24) => PngBitDepth.Bit8, - Type t when t == typeof(Rgba32) => PngBitDepth.Bit8, - Type t when t == typeof(Rgb48) => PngBitDepth.Bit16, - Type t when t == typeof(Rgba64) => PngBitDepth.Bit16, - Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16, - _ => PngBitDepth.Bit8 - }; - } + PngColorType.Grayscale => use16Bit ? 2 : 1, + PngColorType.GrayscaleWithAlpha => use16Bit ? 4 : 2, + PngColorType.Palette => 1, + PngColorType.Rgb => use16Bit ? 6 : 3, + + // PngColorType.RgbWithAlpha + _ => use16Bit ? 8 : 4, + }; + } + + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngColorType SuggestColorType() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Argb32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Bgr24) => PngColorType.Rgb, + Type t when t == typeof(Bgra32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(L8) => PngColorType.Grayscale, + Type t when t == typeof(L16) => PngColorType.Grayscale, + Type t when t == typeof(La16) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(La32) => PngColorType.GrayscaleWithAlpha, + Type t when t == typeof(Rgb24) => PngColorType.Rgb, + Type t when t == typeof(Rgba32) => PngColorType.RgbWithAlpha, + Type t when t == typeof(Rgb48) => PngColorType.Rgb, + Type t when t == typeof(Rgba64) => PngColorType.RgbWithAlpha, + Type t when t == typeof(RgbaVector) => PngColorType.RgbWithAlpha, + _ => PngColorType.RgbWithAlpha + }; + } + + /// + /// Returns a suggested for the given + /// This is not exhaustive but covers many common pixel formats. + /// + private static PngBitDepth SuggestBitDepth() + where TPixel : unmanaged, IPixel + { + return typeof(TPixel) switch + { + Type t when t == typeof(A8) => PngBitDepth.Bit8, + Type t when t == typeof(Argb32) => PngBitDepth.Bit8, + Type t when t == typeof(Bgr24) => PngBitDepth.Bit8, + Type t when t == typeof(Bgra32) => PngBitDepth.Bit8, + Type t when t == typeof(L8) => PngBitDepth.Bit8, + Type t when t == typeof(L16) => PngBitDepth.Bit16, + Type t when t == typeof(La16) => PngBitDepth.Bit8, + Type t when t == typeof(La32) => PngBitDepth.Bit16, + Type t when t == typeof(Rgb24) => PngBitDepth.Bit8, + Type t when t == typeof(Rgba32) => PngBitDepth.Bit8, + Type t when t == typeof(Rgb48) => PngBitDepth.Bit16, + Type t when t == typeof(Rgba64) => PngBitDepth.Bit16, + Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16, + _ => PngBitDepth.Bit8 + }; } } diff --git a/src/ImageSharp/Formats/Png/PngFilterMethod.cs b/src/ImageSharp/Formats/Png/PngFilterMethod.cs index 0e76fc91b2..65f50eec3a 100644 --- a/src/ImageSharp/Formats/Png/PngFilterMethod.cs +++ b/src/ImageSharp/Formats/Png/PngFilterMethod.cs @@ -1,46 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides enumeration of available PNG filter methods. +/// +public enum PngFilterMethod { /// - /// Provides enumeration of available PNG filter methods. + /// With the None filter, the scanline is transmitted unmodified. /// - public enum PngFilterMethod - { - /// - /// With the None filter, the scanline is transmitted unmodified. - /// - None, + None, - /// - /// The Sub filter transmits the difference between each byte and the value of the corresponding - /// byte of the prior pixel. - /// - Sub, + /// + /// The Sub filter transmits the difference between each byte and the value of the corresponding + /// byte of the prior pixel. + /// + Sub, - /// - /// The Up filter is just like the filter except that the pixel immediately above the current pixel, - /// rather than just to its left, is used as the predictor. - /// - Up, + /// + /// The Up filter is just like the filter except that the pixel immediately above the current pixel, + /// rather than just to its left, is used as the predictor. + /// + Up, - /// - /// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel. - /// - Average, + /// + /// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel. + /// + Average, - /// - /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), - /// then chooses as predictor the neighboring pixel closest to the computed value. - /// - Paeth, + /// + /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), + /// then chooses as predictor the neighboring pixel closest to the computed value. + /// + Paeth, - /// - /// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of - /// absolute values of outputs. - /// This method usually outperforms any single fixed filter choice. - /// - Adaptive, - } + /// + /// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of + /// absolute values of outputs. + /// This method usually outperforms any single fixed filter choice. + /// + Adaptive, } diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index df222008be..a0cc065004 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -1,37 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Png; -namespace SixLabors.ImageSharp.Formats.Png +/// +/// Registers the image encoders, decoders and mime type detectors for the png format. +/// +public sealed class PngFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the png format. - /// - public sealed class PngFormat : IImageFormat + private PngFormat() { - private PngFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static PngFormat Instance { get; } = new PngFormat(); + /// + /// Gets the current instance. + /// + public static PngFormat Instance { get; } = new PngFormat(); - /// - public string Name => "PNG"; + /// + public string Name => "PNG"; - /// - public string DefaultMimeType => "image/png"; + /// + public string DefaultMimeType => "image/png"; - /// - public IEnumerable MimeTypes => PngConstants.MimeTypes; + /// + public IEnumerable MimeTypes => PngConstants.MimeTypes; - /// - public IEnumerable FileExtensions => PngConstants.FileExtensions; + /// + public IEnumerable FileExtensions => PngConstants.FileExtensions; - /// - public PngMetadata CreateDefaultFormatMetadata() => new PngMetadata(); - } + /// + public PngMetadata CreateDefaultFormatMetadata() => new PngMetadata(); } diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index 3e4609aad3..cee1017f12 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -1,144 +1,142 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Represents the png header chunk. +/// +internal readonly struct PngHeader { + public const int Size = 13; + + public PngHeader( + int width, + int height, + byte bitDepth, + PngColorType colorType, + byte compressionMethod, + byte filterMethod, + PngInterlaceMode interlaceMethod) + { + this.Width = width; + this.Height = height; + this.BitDepth = bitDepth; + this.ColorType = colorType; + this.CompressionMethod = compressionMethod; + this.FilterMethod = filterMethod; + this.InterlaceMethod = interlaceMethod; + } + + /// + /// Gets the dimension in x-direction of the image in pixels. + /// + public int Width { get; } + + /// + /// Gets the dimension in y-direction of the image in pixels. + /// + public int Height { get; } + /// - /// Represents the png header chunk. + /// Gets the bit depth. + /// Bit depth is a single-byte integer giving the number of bits per sample + /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, + /// although not all values are allowed for all color types. /// - internal readonly struct PngHeader + public byte BitDepth { get; } + + /// + /// Gets the color type. + /// Color type is a integer that describes the interpretation of the + /// image data. Color type codes represent sums of the following values: + /// 1 (palette used), 2 (color used), and 4 (alpha channel used). + /// + public PngColorType ColorType { get; } + + /// + /// Gets the compression method. + /// Indicates the method used to compress the image data. At present, + /// only compression method 0 (deflate/inflate compression with a sliding + /// window of at most 32768 bytes) is defined. + /// + public byte CompressionMethod { get; } + + /// + /// Gets the preprocessing method. + /// Indicates the preprocessing method applied to the image + /// data before compression. At present, only filter method 0 + /// (adaptive filtering with five basic filter types) is defined. + /// + public byte FilterMethod { get; } + + /// + /// Gets the transmission order. + /// Indicates the transmission order of the image data. + /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). + /// + public PngInterlaceMode InterlaceMethod { get; } + + /// + /// Validates the png header. + /// + /// + /// Thrown if the image does pass validation. + /// + public void Validate() { - public const int Size = 13; - - public PngHeader( - int width, - int height, - byte bitDepth, - PngColorType colorType, - byte compressionMethod, - byte filterMethod, - PngInterlaceMode interlaceMethod) + if (!PngConstants.ColorTypes.TryGetValue(this.ColorType, out byte[] supportedBitDepths)) { - this.Width = width; - this.Height = height; - this.BitDepth = bitDepth; - this.ColorType = colorType; - this.CompressionMethod = compressionMethod; - this.FilterMethod = filterMethod; - this.InterlaceMethod = interlaceMethod; + throw new NotSupportedException($"Invalid or unsupported color type. Was '{this.ColorType}'."); } - /// - /// Gets the dimension in x-direction of the image in pixels. - /// - public int Width { get; } - - /// - /// Gets the dimension in y-direction of the image in pixels. - /// - public int Height { get; } - - /// - /// Gets the bit depth. - /// Bit depth is a single-byte integer giving the number of bits per sample - /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, - /// although not all values are allowed for all color types. - /// - public byte BitDepth { get; } - - /// - /// Gets the color type. - /// Color type is a integer that describes the interpretation of the - /// image data. Color type codes represent sums of the following values: - /// 1 (palette used), 2 (color used), and 4 (alpha channel used). - /// - public PngColorType ColorType { get; } - - /// - /// Gets the compression method. - /// Indicates the method used to compress the image data. At present, - /// only compression method 0 (deflate/inflate compression with a sliding - /// window of at most 32768 bytes) is defined. - /// - public byte CompressionMethod { get; } - - /// - /// Gets the preprocessing method. - /// Indicates the preprocessing method applied to the image - /// data before compression. At present, only filter method 0 - /// (adaptive filtering with five basic filter types) is defined. - /// - public byte FilterMethod { get; } - - /// - /// Gets the transmission order. - /// Indicates the transmission order of the image data. - /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). - /// - public PngInterlaceMode InterlaceMethod { get; } - - /// - /// Validates the png header. - /// - /// - /// Thrown if the image does pass validation. - /// - public void Validate() + if (supportedBitDepths.AsSpan().IndexOf(this.BitDepth) == -1) { - if (!PngConstants.ColorTypes.TryGetValue(this.ColorType, out byte[] supportedBitDepths)) - { - throw new NotSupportedException($"Invalid or unsupported color type. Was '{this.ColorType}'."); - } - - if (supportedBitDepths.AsSpan().IndexOf(this.BitDepth) == -1) - { - throw new NotSupportedException($"Invalid or unsupported bit depth. Was '{this.BitDepth}'."); - } - - if (this.FilterMethod != 0) - { - throw new NotSupportedException($"Invalid filter method. Expected 0. Was '{this.FilterMethod}'."); - } - - // The png specification only defines 'None' and 'Adam7' as interlaced methods. - if (this.InterlaceMethod is not PngInterlaceMode.None and not PngInterlaceMode.Adam7) - { - throw new NotSupportedException($"Invalid interlace method. Expected 'None' or 'Adam7'. Was '{this.InterlaceMethod}'."); - } + throw new NotSupportedException($"Invalid or unsupported bit depth. Was '{this.BitDepth}'."); } - /// - /// Writes the header to the given buffer. - /// - /// The buffer to write to. - public void WriteTo(Span buffer) + if (this.FilterMethod != 0) { - BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.Width); - BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4, 4), this.Height); - - buffer[8] = this.BitDepth; - buffer[9] = (byte)this.ColorType; - buffer[10] = this.CompressionMethod; - buffer[11] = this.FilterMethod; - buffer[12] = (byte)this.InterlaceMethod; + throw new NotSupportedException($"Invalid filter method. Expected 0. Was '{this.FilterMethod}'."); } - /// - /// Parses the PngHeader from the given data buffer. - /// - /// The data to parse. - /// The parsed PngHeader. - public static PngHeader Parse(ReadOnlySpan data) - => new( - width: BinaryPrimitives.ReadInt32BigEndian(data[..4]), - height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)), - bitDepth: data[8], - colorType: (PngColorType)data[9], - compressionMethod: data[10], - filterMethod: data[11], - interlaceMethod: (PngInterlaceMode)data[12]); + // The png specification only defines 'None' and 'Adam7' as interlaced methods. + if (this.InterlaceMethod is not PngInterlaceMode.None and not PngInterlaceMode.Adam7) + { + throw new NotSupportedException($"Invalid interlace method. Expected 'None' or 'Adam7'. Was '{this.InterlaceMethod}'."); + } } + + /// + /// Writes the header to the given buffer. + /// + /// The buffer to write to. + public void WriteTo(Span buffer) + { + BinaryPrimitives.WriteInt32BigEndian(buffer[..4], this.Width); + BinaryPrimitives.WriteInt32BigEndian(buffer.Slice(4, 4), this.Height); + + buffer[8] = this.BitDepth; + buffer[9] = (byte)this.ColorType; + buffer[10] = this.CompressionMethod; + buffer[11] = this.FilterMethod; + buffer[12] = (byte)this.InterlaceMethod; + } + + /// + /// Parses the PngHeader from the given data buffer. + /// + /// The data to parse. + /// The parsed PngHeader. + public static PngHeader Parse(ReadOnlySpan data) + => new( + width: BinaryPrimitives.ReadInt32BigEndian(data[..4]), + height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)), + bitDepth: data[8], + colorType: (PngColorType)data[9], + compressionMethod: data[10], + filterMethod: data[11], + interlaceMethod: (PngInterlaceMode)data[12]); } diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs index d8bcbac0f2..907898e17f 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -1,28 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Detects png file headers +/// +public sealed class PngImageFormatDetector : IImageFormatDetector { - /// - /// Detects png file headers - /// - public sealed class PngImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 8; + /// + public int HeaderSize => 8; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) - { - return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; - } + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; + } - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - return header.Length >= this.HeaderSize && BinaryPrimitives.ReadUInt64BigEndian(header) == PngConstants.HeaderValue; - } + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + return header.Length >= this.HeaderSize && BinaryPrimitives.ReadUInt64BigEndian(header) == PngConstants.HeaderValue; } } diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index 4e6c2bbd00..859643e149 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides enumeration of available PNG interlace modes. +/// +public enum PngInterlaceMode : byte { /// - /// Provides enumeration of available PNG interlace modes. + /// Non interlaced /// - public enum PngInterlaceMode : byte - { - /// - /// Non interlaced - /// - None = 0, + None = 0, - /// - /// Adam 7 interlacing. - /// - Adam7 = 1 - } + /// + /// Adam 7 interlacing. + /// + Adam7 = 1 } diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs index 52d70906dc..9ff3905fe1 100644 --- a/src/ImageSharp/Formats/Png/PngMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngMetadata.cs @@ -1,102 +1,100 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides Png specific metadata information for the image. +/// +public class PngMetadata : IDeepCloneable { /// - /// Provides Png specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class PngMetadata : IDeepCloneable + public PngMetadata() { - /// - /// Initializes a new instance of the class. - /// - public PngMetadata() - { - } + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PngMetadata(PngMetadata other) + { + this.BitDepth = other.BitDepth; + this.ColorType = other.ColorType; + this.Gamma = other.Gamma; + this.InterlaceMethod = other.InterlaceMethod; + this.HasTransparency = other.HasTransparency; + this.TransparentL8 = other.TransparentL8; + this.TransparentL16 = other.TransparentL16; + this.TransparentRgb24 = other.TransparentRgb24; + this.TransparentRgb48 = other.TransparentRgb48; - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private PngMetadata(PngMetadata other) + for (int i = 0; i < other.TextData.Count; i++) { - this.BitDepth = other.BitDepth; - this.ColorType = other.ColorType; - this.Gamma = other.Gamma; - this.InterlaceMethod = other.InterlaceMethod; - this.HasTransparency = other.HasTransparency; - this.TransparentL8 = other.TransparentL8; - this.TransparentL16 = other.TransparentL16; - this.TransparentRgb24 = other.TransparentRgb24; - this.TransparentRgb48 = other.TransparentRgb48; - - for (int i = 0; i < other.TextData.Count; i++) - { - this.TextData.Add(other.TextData[i]); - } + this.TextData.Add(other.TextData[i]); } - - /// - /// Gets or sets the number of bits per sample or per palette index (not per pixel). - /// Not all values are allowed for all values. - /// - public PngBitDepth? BitDepth { get; set; } - - /// - /// Gets or sets the color type. - /// - public PngColorType? ColorType { get; set; } - - /// - /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image. - /// - public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None; - - /// - /// Gets or sets the gamma value for the image. - /// - public float Gamma { get; set; } - - /// - /// Gets or sets the Rgb24 transparent color. - /// This represents any color in an 8 bit Rgb24 encoded png that should be transparent. - /// - public Rgb24? TransparentRgb24 { get; set; } - - /// - /// Gets or sets the Rgb48 transparent color. - /// This represents any color in a 16 bit Rgb24 encoded png that should be transparent. - /// - public Rgb48? TransparentRgb48 { get; set; } - - /// - /// Gets or sets the 8 bit grayscale transparent color. - /// This represents any color in an 8 bit grayscale encoded png that should be transparent. - /// - public L8? TransparentL8 { get; set; } - - /// - /// Gets or sets the 16 bit grayscale transparent color. - /// This represents any color in a 16 bit grayscale encoded png that should be transparent. - /// - public L16? TransparentL16 { get; set; } - - /// - /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. - /// - public bool HasTransparency { get; set; } - - /// - /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. - /// Used for conveying textual information associated with the image. - /// - public IList TextData { get; set; } = new List(); - - /// - public IDeepCloneable DeepClone() => new PngMetadata(this); } + + /// + /// Gets or sets the number of bits per sample or per palette index (not per pixel). + /// Not all values are allowed for all values. + /// + public PngBitDepth? BitDepth { get; set; } + + /// + /// Gets or sets the color type. + /// + public PngColorType? ColorType { get; set; } + + /// + /// Gets or sets a value indicating whether this instance should write an Adam7 interlaced image. + /// + public PngInterlaceMode? InterlaceMethod { get; set; } = PngInterlaceMode.None; + + /// + /// Gets or sets the gamma value for the image. + /// + public float Gamma { get; set; } + + /// + /// Gets or sets the Rgb24 transparent color. + /// This represents any color in an 8 bit Rgb24 encoded png that should be transparent. + /// + public Rgb24? TransparentRgb24 { get; set; } + + /// + /// Gets or sets the Rgb48 transparent color. + /// This represents any color in a 16 bit Rgb24 encoded png that should be transparent. + /// + public Rgb48? TransparentRgb48 { get; set; } + + /// + /// Gets or sets the 8 bit grayscale transparent color. + /// This represents any color in an 8 bit grayscale encoded png that should be transparent. + /// + public L8? TransparentL8 { get; set; } + + /// + /// Gets or sets the 16 bit grayscale transparent color. + /// This represents any color in a 16 bit grayscale encoded png that should be transparent. + /// + public L16? TransparentL16 { get; set; } + + /// + /// Gets or sets a value indicating whether the image contains a transparency chunk and markers were decoded. + /// + public bool HasTransparency { get; set; } + + /// + /// Gets or sets the collection of text data stored within the iTXt, tEXt, and zTXt chunks. + /// Used for conveying textual information associated with the image. + /// + public IList TextData { get; set; } = new List(); + + /// + public IDeepCloneable DeepClone() => new PngMetadata(this); } diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 41b5c370d7..cf77788bc0 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -1,558 +1,556 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats. +/// TODO: We should make this a stateful class or struct to reduce the number of arguments on methods (most are invariant). +/// +internal static class PngScanlineProcessor { - /// - /// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats. - /// TODO: We should make this a stateful class or struct to reduce the number of arguments on methods (most are invariant). - /// - internal static class PngScanlineProcessor + public static void ProcessGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + bool hasTrans, + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel { - public static void ProcessGrayscaleScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - bool hasTrans, - L16 luminance16Trans, - L8 luminanceTrans) - where TPixel : unmanaged, IPixel - { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); - - if (!hasTrans) - { - if (header.BitDepth == 16) - { - for (int x = 0, o = 0; x < header.Width; x++, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - pixel.FromL16(Unsafe.As(ref luminance)); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - else - { - for (int x = 0; x < header.Width; x++) - { - byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); - pixel.FromL8(Unsafe.As(ref luminance)); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - - return; - } + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); + if (!hasTrans) + { if (header.BitDepth == 16) { - La32 source = default; for (int x = 0, o = 0; x < header.Width; x++, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - source.L = luminance; - source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - - pixel.FromLa32(source); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - La16 source = default; - byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); for (int x = 0; x < header.Width; x++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); - source.L = luminance; - source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - - pixel.FromLa16(source); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } + + return; } - public static void ProcessInterlacedGrayscaleScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int pixelOffset, - int increment, - bool hasTrans, - L16 luminance16Trans, - L8 luminanceTrans) - where TPixel : unmanaged, IPixel + if (header.BitDepth == 16) { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); + La32 source = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - if (!hasTrans) + pixel.FromLa32(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + La16 source = default; + byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); + for (int x = 0; x < header.Width; x++) { - if (header.BitDepth == 16) - { - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - pixel.FromL16(Unsafe.As(ref luminance)); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - else - { - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) - { - byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - pixel.FromL8(Unsafe.As(ref luminance)); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - return; + pixel.FromLa16(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } + } + } + public static void ProcessInterlacedGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + bool hasTrans, + L16 luminance16Trans, + L8 luminanceTrans) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(header.BitDepth) - 1); + + if (!hasTrans) + { if (header.BitDepth == 16) { - La32 source = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) { ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - source.L = luminance; - source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - - pixel.FromLa32(source); + pixel.FromL16(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - La16 source = default; - byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); - source.L = luminance; - source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - - pixel.FromLa16(source); + pixel.FromL8(Unsafe.As(ref luminance)); Unsafe.Add(ref rowSpanRef, x) = pixel; } } + + return; } - public static void ProcessGrayscaleWithAlphaScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel + if (header.BitDepth == 16) { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - - if (header.BitDepth == 16) + La32 source = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) { - La32 source = default; - for (int x = 0, o = 0; x < header.Width; x++, o += 4) - { - source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.L = luminance; + source.A = luminance.Equals(luminance16Trans.PackedValue) ? ushort.MinValue : ushort.MaxValue; - pixel.FromLa32(source); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromLa32(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } - else + } + else + { + La16 source = default; + byte scaledLuminanceTrans = (byte)(luminanceTrans.PackedValue * scaleFactor); + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { - La16 source = default; - for (int x = 0; x < header.Width; x++) - { - int offset = x * bytesPerPixel; - source.L = Unsafe.Add(ref scanlineSpanRef, offset); - source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); + source.L = luminance; + source.A = luminance.Equals(scaledLuminanceTrans) ? byte.MinValue : byte.MaxValue; - pixel.FromLa16(source); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromLa16(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } + } - public static void ProcessInterlacedGrayscaleWithAlphaScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int pixelOffset, - int increment, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel - { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - - if (header.BitDepth == 16) - { - La32 source = default; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4) - { - source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + public static void ProcessGrayscaleWithAlphaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - pixel.FromLa32(source); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - else + if (header.BitDepth == 16) + { + La32 source = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 4) { - int offset = 0; - La16 source = default; - for (int x = pixelOffset; x < header.Width; x += increment) - { - source.L = Unsafe.Add(ref scanlineSpanRef, offset); - source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromLa16(source); - Unsafe.Add(ref rowSpanRef, x) = pixel; - offset += bytesPerPixel; - } + pixel.FromLa32(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } - - public static void ProcessPaletteScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - ReadOnlySpan palette, - byte[] paletteAlpha) - where TPixel : unmanaged, IPixel + else { - if (palette.IsEmpty) + La16 source = default; + for (int x = 0; x < header.Width; x++) { - PngThrowHelper.ThrowMissingPalette(); + int offset = x * bytesPerPixel; + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + + pixel.FromLa16(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } + } + } - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); - ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); + public static void ProcessInterlacedGrayscaleWithAlphaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - if (paletteAlpha?.Length > 0) + if (header.BitDepth == 16) + { + La32 source = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4) { - // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha - // channel and we should try to read it. - Rgba32 rgba = default; - ref byte paletteAlphaRef = ref paletteAlpha[0]; - - for (int x = 0; x < header.Width; x++) - { - int index = Unsafe.Add(ref scanlineSpanRef, x); - rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); - rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; + source.L = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + source.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - pixel.FromRgba32(rgba); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromLa32(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; } - else + } + else + { + int offset = 0; + La16 source = default; + for (int x = pixelOffset; x < header.Width; x += increment) { - for (int x = 0; x < header.Width; x++) - { - int index = Unsafe.Add(ref scanlineSpanRef, x); - Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); + source.L = Unsafe.Add(ref scanlineSpanRef, offset); + source.A = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); - pixel.FromRgb24(rgb); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromLa16(source); + Unsafe.Add(ref rowSpanRef, x) = pixel; + offset += bytesPerPixel; } } + } + + public static void ProcessPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : unmanaged, IPixel + { + if (palette.IsEmpty) + { + PngThrowHelper.ThrowMissingPalette(); + } - public static void ProcessInterlacedPaletteScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int pixelOffset, - int increment, - ReadOnlySpan palette, - byte[] paletteAlpha) - where TPixel : unmanaged, IPixel + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); + ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); + + if (paletteAlpha?.Length > 0) { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); - ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + Rgba32 rgba = default; + ref byte paletteAlphaRef = ref paletteAlpha[0]; - if (paletteAlpha?.Length > 0) + for (int x = 0; x < header.Width; x++) { - // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha - // channel and we should try to read it. - Rgba32 rgba = default; - ref byte paletteAlphaRef = ref paletteAlpha[0]; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) - { - int index = Unsafe.Add(ref scanlineSpanRef, o); - rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; - rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); + int index = Unsafe.Add(ref scanlineSpanRef, x); + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); + rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; - pixel.FromRgba32(rgba); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; } - else + } + else + { + for (int x = 0; x < header.Width; x++) { - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) - { - int index = Unsafe.Add(ref scanlineSpanRef, o); - Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); + int index = Unsafe.Add(ref scanlineSpanRef, x); + Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); - pixel.FromRgb24(rgb); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } + } - public static void ProcessRgbScanline( - Configuration configuration, - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int bytesPerPixel, - int bytesPerSample, - bool hasTrans, - Rgb48 rgb48Trans, - Rgb24 rgb24Trans) - where TPixel : unmanaged, IPixel + public static void ProcessInterlacedPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + ReadOnlySpan palettePixels = MemoryMarshal.Cast(palette); + ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); + + if (paletteAlpha?.Length > 0) { - TPixel pixel = default; - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha + // channel and we should try to read it. + Rgba32 rgba = default; + ref byte paletteAlphaRef = ref paletteAlpha[0]; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + int index = Unsafe.Add(ref scanlineSpanRef, o); + rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); - if (!hasTrans) + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) { - if (header.BitDepth == 16) - { - Rgb48 rgb48 = default; - for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - - pixel.FromRgb48(rgb48); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - else - { - PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width); - } + int index = Unsafe.Add(ref scanlineSpanRef, o); + Rgb24 rgb = Unsafe.Add(ref palettePixelsRef, index); - return; + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; } + } + } + + public static void ProcessRgbScanline( + Configuration configuration, + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample, + bool hasTrans, + Rgb48 rgb48Trans, + Rgb24 rgb24Trans) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + if (!hasTrans) + { if (header.BitDepth == 16) { Rgb48 rgb48 = default; - Rgba64 rgba64 = default; for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) { rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - rgba64.Rgb = rgb48; - rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.FromRgba64(rgba64); + pixel.FromRgb48(rgb48); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgba32 rgba32 = default; - ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); - ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); - for (int x = 0; x < header.Width; x++) - { - ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); - rgba32.Rgb = rgb24; - rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; - - pixel.FromRgba32(rgba32); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + PixelOperations.Instance.FromRgb24Bytes(configuration, scanlineSpan, rowSpan, header.Width); } + + return; } - public static void ProcessInterlacedRgbScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int pixelOffset, - int increment, - int bytesPerPixel, - int bytesPerSample, - bool hasTrans, - Rgb48 rgb48Trans, - Rgb24 rgb24Trans) - where TPixel : unmanaged, IPixel + if (header.BitDepth == 16) { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) + { + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - if (header.BitDepth == 16) + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba32 = default; + ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); + ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); + for (int x = 0; x < header.Width; x++) { - if (hasTrans) - { - Rgb48 rgb48 = default; - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - - rgba64.Rgb = rgb48; - rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.FromRgba64(rgba64); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } - else - { - Rgb48 rgb48 = default; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - - pixel.FromRgb48(rgb48); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } - } + ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); + rgba32.Rgb = rgb24; + rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; - return; + pixel.FromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; } + } + } + + public static void ProcessInterlacedRgbScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample, + bool hasTrans, + Rgb48 rgb48Trans, + Rgb24 rgb24Trans) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + if (header.BitDepth == 16) + { if (hasTrans) { - Rgba32 rgba = default; + Rgb48 rgb48 = default; + Rgba64 rgba64 = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - rgba.R = Unsafe.Add(ref scanlineSpanRef, o); - rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); - rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); - rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - pixel.FromRgba32(rgba); + rgba64.Rgb = rgb48; + rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.FromRgba64(rgba64); Unsafe.Add(ref rowSpanRef, x) = pixel; } } else { - Rgb24 rgb = default; + Rgb48 rgb48 = default; for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - rgb.R = Unsafe.Add(ref scanlineSpanRef, o); - rgb.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); - rgb.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - pixel.FromRgb24(rgb); + pixel.FromRgb48(rgb48); Unsafe.Add(ref rowSpanRef, x) = pixel; } } + + return; } - public static void ProcessRgbaScanline( - Configuration configuration, - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel + if (hasTrans) { - TPixel pixel = default; - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); - - if (header.BitDepth == 16) + Rgba32 rgba = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - Rgba64 rgba64 = default; - for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) - { - rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + rgba.R = Unsafe.Add(ref scanlineSpanRef, o); + rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; - pixel.FromRgba64(rgba64); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; } - else + } + else + { + Rgb24 rgb = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width); + rgb.R = Unsafe.Add(ref scanlineSpanRef, o); + rgb.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgb.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + + pixel.FromRgb24(rgb); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } + } + + public static void ProcessRgbaScanline( + Configuration configuration, + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) + { + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); - public static void ProcessInterlacedRgbaScanline( - in PngHeader header, - ReadOnlySpan scanlineSpan, - Span rowSpan, - int pixelOffset, - int increment, - int bytesPerPixel, - int bytesPerSample) - where TPixel : unmanaged, IPixel + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else { - TPixel pixel = default; - ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); - ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + PixelOperations.Instance.FromRgba32Bytes(configuration, scanlineSpan, rowSpan, header.Width); + } + } - if (header.BitDepth == 16) + public static void ProcessInterlacedRgbaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample) + where TPixel : unmanaged, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) - { - rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); - rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); - rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); - rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); + rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); + rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); + rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); + rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); - pixel.FromRgba64(rgba64); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; } - else + } + else + { + Rgba32 rgba = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) { - Rgba32 rgba = default; - for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) - { - rgba.R = Unsafe.Add(ref scanlineSpanRef, o); - rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); - rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); - rgba.A = Unsafe.Add(ref scanlineSpanRef, o + (3 * bytesPerSample)); + rgba.R = Unsafe.Add(ref scanlineSpanRef, o); + rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); + rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); + rgba.A = Unsafe.Add(ref scanlineSpanRef, o + (3 * bytesPerSample)); - pixel.FromRgba32(rgba); - Unsafe.Add(ref rowSpanRef, x) = pixel; - } + pixel.FromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; } } } diff --git a/src/ImageSharp/Formats/Png/PngTextData.cs b/src/ImageSharp/Formats/Png/PngTextData.cs index 7db26b60be..af2ba4b1db 100644 --- a/src/ImageSharp/Formats/Png/PngTextData.cs +++ b/src/ImageSharp/Formats/Png/PngTextData.cs @@ -1,137 +1,134 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Png; -namespace SixLabors.ImageSharp.Formats.Png +/// +/// Stores text data contained in the iTXt, tEXt, and zTXt chunks. +/// Used for conveying textual information associated with the image, like the name of the author, +/// the copyright information, the date, where the image was created, or some other information. +/// +public readonly struct PngTextData : IEquatable { /// - /// Stores text data contained in the iTXt, tEXt, and zTXt chunks. - /// Used for conveying textual information associated with the image, like the name of the author, - /// the copyright information, the date, where the image was created, or some other information. + /// Initializes a new instance of the struct. /// - public readonly struct PngTextData : IEquatable + /// The keyword of the property. + /// The value of the property. + /// An optional language tag. + /// A optional translated keyword. + public PngTextData(string keyword, string value, string languageTag, string translatedKeyword) { - /// - /// Initializes a new instance of the struct. - /// - /// The keyword of the property. - /// The value of the property. - /// An optional language tag. - /// A optional translated keyword. - public PngTextData(string keyword, string value, string languageTag, string translatedKeyword) - { - Guard.NotNullOrWhiteSpace(keyword, nameof(keyword)); + Guard.NotNullOrWhiteSpace(keyword, nameof(keyword)); - // No leading or trailing whitespace is allowed in keywords. - this.Keyword = keyword.Trim(); - this.Value = value; - this.LanguageTag = languageTag; - this.TranslatedKeyword = translatedKeyword; - } + // No leading or trailing whitespace is allowed in keywords. + this.Keyword = keyword.Trim(); + this.Value = value; + this.LanguageTag = languageTag; + this.TranslatedKeyword = translatedKeyword; + } - /// - /// Gets the keyword of this which indicates - /// the type of information represented by the text string as described in https://www.w3.org/TR/PNG/#11keywords. - /// - /// - /// Typical properties are the author, copyright information or other meta information. - /// - public string Keyword { get; } + /// + /// Gets the keyword of this which indicates + /// the type of information represented by the text string as described in https://www.w3.org/TR/PNG/#11keywords. + /// + /// + /// Typical properties are the author, copyright information or other meta information. + /// + public string Keyword { get; } - /// - /// Gets the value of this . - /// - public string Value { get; } + /// + /// Gets the value of this . + /// + public string Value { get; } - /// - /// Gets an optional language tag defined in https://www.w3.org/TR/PNG/#2-RFC-3066 indicates the human language used by the translated keyword and the text. - /// If the first word is two or three letters long, it is an ISO language code https://www.w3.org/TR/PNG/#2-ISO-639. - /// - /// - /// Examples: cn, en-uk, no-bok, x-klingon, x-KlInGoN. - /// - public string LanguageTag { get; } + /// + /// Gets an optional language tag defined in https://www.w3.org/TR/PNG/#2-RFC-3066 indicates the human language used by the translated keyword and the text. + /// If the first word is two or three letters long, it is an ISO language code https://www.w3.org/TR/PNG/#2-ISO-639. + /// + /// + /// Examples: cn, en-uk, no-bok, x-klingon, x-KlInGoN. + /// + public string LanguageTag { get; } - /// - /// Gets an optional translated keyword, should contain a translation of the keyword into the language indicated by the language tag. - /// - public string TranslatedKeyword { get; } + /// + /// Gets an optional translated keyword, should contain a translation of the keyword into the language indicated by the language tag. + /// + public string TranslatedKeyword { get; } - /// - /// Compares two objects. The result specifies whether the values - /// of the properties of the two objects are equal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(PngTextData left, PngTextData right) - => left.Equals(right); + /// + /// Compares two objects. The result specifies whether the values + /// of the properties of the two objects are equal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(PngTextData left, PngTextData right) + => left.Equals(right); - /// - /// Compares two objects. The result specifies whether the values - /// of the properties of the two objects are unequal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(PngTextData left, PngTextData right) - => !(left == right); + /// + /// Compares two objects. The result specifies whether the values + /// of the properties of the two objects are unequal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(PngTextData left, PngTextData right) + => !(left == right); - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// The object to compare with the current instance. - /// - /// - /// true if and this instance are the same type and represent the - /// same value; otherwise, false. - /// - public override bool Equals(object obj) - => obj is PngTextData other && this.Equals(other); + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// The object to compare with the current instance. + /// + /// + /// true if and this instance are the same type and represent the + /// same value; otherwise, false. + /// + public override bool Equals(object obj) + => obj is PngTextData other && this.Equals(other); - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - public override int GetHashCode() - => HashCode.Combine(this.Keyword, this.Value, this.LanguageTag, this.TranslatedKeyword); + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + => HashCode.Combine(this.Keyword, this.Value, this.LanguageTag, this.TranslatedKeyword); - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// - public override string ToString() - => $"PngTextData [ Name={this.Keyword}, Value={this.Value} ]"; + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() + => $"PngTextData [ Name={this.Keyword}, Value={this.Value} ]"; - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// True if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(PngTextData other) - => this.Keyword.Equals(other.Keyword, StringComparison.OrdinalIgnoreCase) - && this.Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase) - && this.LanguageTag.Equals(other.LanguageTag, StringComparison.OrdinalIgnoreCase) - && this.TranslatedKeyword.Equals(other.TranslatedKeyword, StringComparison.OrdinalIgnoreCase); - } + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(PngTextData other) + => this.Keyword.Equals(other.Keyword, StringComparison.OrdinalIgnoreCase) + && this.Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase) + && this.LanguageTag.Equals(other.LanguageTag, StringComparison.OrdinalIgnoreCase) + && this.TranslatedKeyword.Equals(other.TranslatedKeyword, StringComparison.OrdinalIgnoreCase); } diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs index c62775f87a..b7ca55b2bf 100644 --- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs +++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs @@ -1,52 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Cold path optimizations for throwing png format based exceptions. +/// +internal static class PngThrowHelper { - /// - /// Cold path optimizations for throwing png format based exceptions. - /// - internal static class PngThrowHelper - { - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) - => throw new InvalidImageContentException(errorMessage, innerException); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk"); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk"); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowMissingPalette() => throw new InvalidImageContentException("PNG Image does not contain a palette chunk"); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data."); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type"); - - [DoesNotReturn] - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type."); - } + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoHeader() => throw new InvalidImageContentException("PNG Image does not contain a header chunk"); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNoData() => throw new InvalidImageContentException("PNG Image does not contain a data chunk"); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowMissingPalette() => throw new InvalidImageContentException("PNG Image does not contain a palette chunk"); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType() => throw new InvalidImageContentException("Invalid PNG data."); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkType(string message) => throw new InvalidImageContentException(message); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new InvalidImageContentException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedColor() => throw new NotSupportedException("Unsupported PNG color type"); + + [DoesNotReturn] + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowUnknownFilter() => throw new InvalidImageContentException("Unknown filter type."); } diff --git a/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs b/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs index 4505787cce..76a89608bd 100644 --- a/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs +++ b/src/ImageSharp/Formats/Png/PngTransparentColorMode.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Png +namespace SixLabors.ImageSharp.Formats.Png; + +/// +/// Enum indicating how the transparency should be handled on encoding. +/// +public enum PngTransparentColorMode { /// - /// Enum indicating how the transparency should be handled on encoding. + /// The transparency will be kept as is. /// - public enum PngTransparentColorMode - { - /// - /// The transparency will be kept as is. - /// - Preserve = 0, + Preserve = 0, - /// - /// Converts fully transparent pixels that may contain R, G, B values which are not 0, - /// to transparent black, which can yield in better compression in some cases. - /// - Clear = 1, - } + /// + /// Converts fully transparent pixels that may contain R, G, B values which are not 0, + /// to transparent black, which can yield in better compression in some cases. + /// + Clear = 1, } diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs index a6fd215db5..a42feb7f35 100644 --- a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Configuration options for use during tga encoding. +/// +internal interface ITgaEncoderOptions { /// - /// Configuration options for use during tga encoding. + /// Gets the number of bits per pixel. /// - internal interface ITgaEncoderOptions - { - /// - /// Gets the number of bits per pixel. - /// - TgaBitsPerPixel? BitsPerPixel { get; } + TgaBitsPerPixel? BitsPerPixel { get; } - /// - /// Gets a value indicating whether run length compression should be used. - /// - TgaCompression Compression { get; } - } + /// + /// Gets a value indicating whether run length compression should be used. + /// + TgaCompression Compression { get; } } diff --git a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs index 2f085af73c..8d5e357641 100644 --- a/src/ImageSharp/Formats/Tga/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Tga/MetadataExtensions.cs @@ -4,18 +4,17 @@ using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the tga format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the tga format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance); - } + /// The metadata this method extends. + /// The . + public static TgaMetadata GetTgaMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TgaFormat.Instance); } diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs index 1e5083ce5f..da34e62f7e 100644 --- a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Enumerates the available bits per pixel the tga encoder supports. +/// +public enum TgaBitsPerPixel : byte { /// - /// Enumerates the available bits per pixel the tga encoder supports. + /// 8 bits per pixel. Each pixel consists of 1 byte. /// - public enum TgaBitsPerPixel : byte - { - /// - /// 8 bits per pixel. Each pixel consists of 1 byte. - /// - Pixel8 = 8, + Pixel8 = 8, - /// - /// 16 bits per pixel. Each pixel consists of 2 bytes. - /// - Pixel16 = 16, + /// + /// 16 bits per pixel. Each pixel consists of 2 bytes. + /// + Pixel16 = 16, - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Pixel24 = 24, + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 24, - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes. - /// - Pixel32 = 32 - } + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 32 } diff --git a/src/ImageSharp/Formats/Tga/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs index fc9601a48f..5d4ce3a9d6 100644 --- a/src/ImageSharp/Formats/Tga/TgaCompression.cs +++ b/src/ImageSharp/Formats/Tga/TgaCompression.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Indicates if compression is used. +/// +public enum TgaCompression { /// - /// Indicates if compression is used. + /// No compression is used. /// - public enum TgaCompression - { - /// - /// No compression is used. - /// - None, + None, - /// - /// Run length encoding is used. - /// - RunLength, - } + /// + /// Run length encoding is used. + /// + RunLength, } diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs index 6ea273fb44..06f93d1ce5 100644 --- a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Registers the image encoders, decoders and mime type detectors for the tga format. +/// +public sealed class TgaConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the tga format. - /// - public sealed class TgaConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); - configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); + configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Tga/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs index 2dd8c3e808..ac7d837985 100644 --- a/src/ImageSharp/Formats/Tga/TgaConstants.cs +++ b/src/ImageSharp/Formats/Tga/TgaConstants.cs @@ -1,25 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Tga; -namespace SixLabors.ImageSharp.Formats.Tga +internal static class TgaConstants { - internal static class TgaConstants - { - /// - /// The list of mimetypes that equate to a targa file. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" }; + /// + /// The list of mimetypes that equate to a targa file. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" }; - /// - /// The list of file extensions that equate to a targa file. - /// - public static readonly IEnumerable FileExtensions = new[] { "tga", "vda", "icb", "vst" }; + /// + /// The list of file extensions that equate to a targa file. + /// + public static readonly IEnumerable FileExtensions = new[] { "tga", "vda", "icb", "vst" }; - /// - /// The file header length of a tga image in bytes. - /// - public const int FileHeaderLength = 18; - } + /// + /// The file header length of a tga image in bytes. + /// + public const int FileHeaderLength = 18; } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index ac99271cb9..362daa77d2 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -1,42 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Image decoder for Truevision TGA images. +/// +public sealed class TgaDecoder : IImageDecoder { - /// - /// Image decoder for Truevision TGA images. - /// - public sealed class TgaDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + return new TgaDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } - TgaDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options, image); + TgaDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options, image); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoder)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index d901759d1f..efb61610c2 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -1,947 +1,944 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Performs the tga decoding operation. +/// +internal sealed class TgaDecoderCore : IImageDecoderInternals { /// - /// Performs the tga decoding operation. + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[4]; + + /// + /// General configuration options. + /// + private readonly Configuration configuration; + + /// + /// The metadata. /// - internal sealed class TgaDecoderCore : IImageDecoderInternals + private ImageMetadata metadata; + + /// + /// The tga specific metadata. + /// + private TgaMetadata tgaMetadata; + + /// + /// The file header containing general information about the image. + /// + private TgaFileHeader fileHeader; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The stream to decode from. + /// + private BufferedReadStream currentStream; + + /// + /// Indicates whether there is a alpha channel present. + /// + private bool hasAlpha; + + /// + /// Initializes a new instance of the class. + /// + /// The options. + public TgaDecoderCore(DecoderOptions options) { - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBuffer = new byte[4]; - - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// The metadata. - /// - private ImageMetadata metadata; - - /// - /// The tga specific metadata. - /// - private TgaMetadata tgaMetadata; - - /// - /// The file header containing general information about the image. - /// - private TgaFileHeader fileHeader; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream; - - /// - /// Indicates whether there is a alpha channel present. - /// - private bool hasAlpha; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - public TgaDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - this.memoryAllocator = this.configuration.MemoryAllocator; - } + this.Options = options; + this.configuration = options.Configuration; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - public DecoderOptions Options { get; } + /// + public DecoderOptions Options { get; } - /// - public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); + /// + public Size Dimensions => new(this.fileHeader.Width, this.fileHeader.Height); - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + try { - try + TgaImageOrigin origin = this.ReadFileHeader(stream); + this.currentStream.Skip(this.fileHeader.IdLength); + + // Parse the color map, if present. + if (this.fileHeader.ColorMapType is not 0 and not 1) { - TgaImageOrigin origin = this.ReadFileHeader(stream); - this.currentStream.Skip(this.fileHeader.IdLength); + TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); + } + + if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) + { + throw new UnknownImageFormatException("Width or height cannot be 0"); + } - // Parse the color map, if present. - if (this.fileHeader.ColorMapType is not 0 and not 1) + var image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + if (this.fileHeader.ColorMapType == 1) + { + if (this.fileHeader.CMapLength <= 0) { - TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); + TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length"); } - if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) + if (this.fileHeader.CMapDepth <= 0) { - throw new UnknownImageFormatException("Width or height cannot be 0"); + TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map depth"); } - var image = Image.CreateUninitialized(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - - if (this.fileHeader.ColorMapType == 1) + int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; + int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; + using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) { - if (this.fileHeader.CMapLength <= 0) + Span paletteSpan = palette.GetSpan(); + int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); + if (bytesRead != colorMapSizeInBytes) { - TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map length"); + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map"); } - if (this.fileHeader.CMapDepth <= 0) + if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) { - TgaThrowHelper.ThrowInvalidImageContentException("Missing tga color map depth"); + this.ReadPalettedRle( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + paletteSpan, + colorMapPixelSizeInBytes, + origin); } - - int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; - int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; - using (IMemoryOwner palette = this.memoryAllocator.Allocate(colorMapSizeInBytes, AllocationOptions.Clean)) + else { - Span paletteSpan = palette.GetSpan(); - int bytesRead = this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes); - if (bytesRead != colorMapSizeInBytes) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read the color map"); - } - - if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) - { - this.ReadPalettedRle( - this.fileHeader.Width, - this.fileHeader.Height, - pixels, - paletteSpan, - colorMapPixelSizeInBytes, - origin); - } - else - { - this.ReadPaletted( - this.fileHeader.Width, - this.fileHeader.Height, - pixels, - paletteSpan, - colorMapPixelSizeInBytes, - origin); - } + this.ReadPaletted( + this.fileHeader.Width, + this.fileHeader.Height, + pixels, + paletteSpan, + colorMapPixelSizeInBytes, + origin); } - - return image; } - // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. - if (this.fileHeader.CMapLength > 0) - { - int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; - this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); - } + return image; + } - switch (this.fileHeader.PixelDepth) - { - case 8: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); - } - else - { - this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } + // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. + if (this.fileHeader.CMapLength > 0) + { + int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; + this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); + } - break; + switch (this.fileHeader.PixelDepth) + { + case 8: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); + } + else + { + this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } - case 15: - case 16: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); - } - else - { - this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } + break; - break; + case 15: + case 16: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); + } + else + { + this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } - case 24: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); - } - else - { - this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } + break; - break; + case 24: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); + } + else + { + this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } - case 32: - if (this.fileHeader.ImageType.IsRunLengthEncoded()) - { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); - } - else - { - this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); - } + break; - break; + case 32: + if (this.fileHeader.ImageType.IsRunLengthEncoded()) + { + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); + } + else + { + this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); + } - default: - TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files."); - break; - } + break; - return image; - } - catch (IndexOutOfRangeException e) - { - throw new ImageFormatException("TGA image does not have a valid format.", e); + default: + TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files."); + break; } + + return image; } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("TGA image does not have a valid format.", e); + } + } - /// - /// Reads a uncompressed TGA image with a palette. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The color palette. - /// Color map size of one entry in bytes. - /// The image origin. - private void ReadPaletted(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + /// + /// Reads a uncompressed TGA image with a palette. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The color palette. + /// Color map size of one entry in bytes. + /// The image origin. + private void ReadPaletted(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + + for (int y = 0; y < height; y++) { - TPixel color = default; - bool invertX = InvertX(origin); + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); - for (int y = 0; y < height; y++) + switch (colorMapPixelSizeInBytes) { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - - switch (colorMapPixelSizeInBytes) - { - case 2: - if (invertX) + case 2: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - for (int x = width - 1; x >= 0; x--) - { - this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } - else + } + else + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } + } - break; + break; - case 3: - if (invertX) + case 3: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - for (int x = width - 1; x >= 0; x--) - { - this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } - else + } + else + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } + } - break; + break; - case 4: - if (invertX) + case 4: + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - for (int x = width - 1; x >= 0; x--) - { - this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } - else + } + else + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); - } + this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } + } - break; - } + break; } } + } - /// - /// Reads a run length encoded TGA image with a palette. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The color palette. - /// Color map size of one entry in bytes. - /// The image origin. - private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + /// + /// Reads a run length encoded TGA image with a palette. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The color palette. + /// Color map size of one entry in bytes. + /// The image origin. + private void ReadPalettedRle(int width, int height, Buffer2D pixels, Span palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) { - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) - { - TPixel color = default; - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); + TPixel color = default; + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + int rowStartIdx = y * width; + for (int x = 0; x < width; x++) { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width; - for (int x = 0; x < width; x++) + int idx = rowStartIdx + x; + switch (colorMapPixelSizeInBytes) { - int idx = rowStartIdx + x; - switch (colorMapPixelSizeInBytes) - { - case 1: - color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 2: - this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); - break; - case 3: - color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - case 4: - color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); - break; - } - - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; + case 1: + color.FromL8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 2: + this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); + break; + case 3: + color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; + case 4: + color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); + break; } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } + } - /// - /// Reads a uncompressed monochrome TGA image. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// the image origin. - private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + /// + /// Reads a uncompressed monochrome TGA image. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// the image origin. + private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) { - bool invertX = InvertX(origin); - if (invertX) + TPixel color = default; + for (int y = 0; y < height; y++) { - TPixel color = default; - for (int y = 0; y < height; y++) + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - for (int x = width - 1; x >= 0; x--) - { - this.ReadL8Pixel(color, x, pixelSpan); - } + this.ReadL8Pixel(color, x, pixelSpan); } - - return; } - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); - Span rowSpan = row.GetSpan(); - bool invertY = InvertY(origin); - if (invertY) - { - for (int y = height - 1; y >= 0; y--) - { - this.ReadL8Row(width, pixels, rowSpan, y); - } - } - else + return; + } + + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); + if (invertY) + { + for (int y = height - 1; y >= 0; y--) { - for (int y = 0; y < height; y++) - { - this.ReadL8Row(width, pixels, rowSpan, y); - } + this.ReadL8Row(width, pixels, rowSpan, y); } } - - /// - /// Reads a uncompressed TGA image where each pixels has 16 bit. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + else { - TPixel color = default; - bool invertX = InvertX(origin); - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0); - Span rowSpan = row.GetSpan(); - for (int y = 0; y < height; y++) { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - - if (invertX) - { - for (int x = width - 1; x >= 0; x--) - { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2); - if (bytesRead != 2) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } + this.ReadL8Row(width, pixels, rowSpan, y); + } + } + } - if (!this.hasAlpha) - { - this.scratchBuffer[1] |= 1 << 7; - } + /// + /// Reads a uncompressed TGA image where each pixels has 16 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0); + Span rowSpan = row.GetSpan(); - if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) - { - color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); - } - else - { - color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); - } + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); - pixelSpan[x] = color; - } - } - else + if (invertX) + { + for (int x = width - 1; x >= 0; x--) { - int bytesRead = this.currentStream.Read(rowSpan); - if (bytesRead != rowSpan.Length) + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 2); + if (bytesRead != 2) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); } if (!this.hasAlpha) { - // We need to set the alpha component value to fully opaque. - for (int x = 1; x < rowSpan.Length; x += 2) - { - rowSpan[x] |= 1 << 7; - } + this.scratchBuffer[1] |= 1 << 7; } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { - PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); + color.FromLa16(Unsafe.As(ref this.scratchBuffer[0])); } else { - PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); + color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0])); + } + + pixelSpan[x] = color; + } + } + else + { + int bytesRead = this.currentStream.Read(rowSpan); + if (bytesRead != rowSpan.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); + } + + if (!this.hasAlpha) + { + // We need to set the alpha component value to fully opaque. + for (int x = 1; x < rowSpan.Length; x += 2) + { + rowSpan[x] |= 1 << 7; } } + + if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) + { + PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); + } + else + { + PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); + } } } + } - /// - /// Reads a uncompressed TGA image where each pixels has 24 bit. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + /// + /// Reads a uncompressed TGA image where each pixels has 24 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + bool invertX = InvertX(origin); + if (invertX) { - bool invertX = InvertX(origin); - if (invertX) + TPixel color = default; + for (int y = 0; y < height; y++) { - TPixel color = default; - for (int y = 0; y < height; y++) + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); + for (int x = width - 1; x >= 0; x--) { - int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.DangerousGetRowSpan(newY); - for (int x = width - 1; x >= 0; x--) - { - this.ReadBgr24Pixel(color, x, pixelSpan); - } + this.ReadBgr24Pixel(color, x, pixelSpan); } + } - return; + return; + } + + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0); + Span rowSpan = row.GetSpan(); + bool invertY = InvertY(origin); + + if (invertY) + { + for (int y = height - 1; y >= 0; y--) + { + this.ReadBgr24Row(width, pixels, rowSpan, y); } + } + else + { + for (int y = 0; y < height; y++) + { + this.ReadBgr24Row(width, pixels, rowSpan, y); + } + } + } - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0); + /// + /// Reads a uncompressed TGA image where each pixels has 32 bit. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The image origin. + private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + bool invertX = InvertX(origin); + if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) + { + using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); Span rowSpan = row.GetSpan(); - bool invertY = InvertY(origin); - if (invertY) + if (InvertY(origin)) { for (int y = height - 1; y >= 0; y--) { - this.ReadBgr24Row(width, pixels, rowSpan, y); + this.ReadBgra32Row(width, pixels, rowSpan, y); } } else { for (int y = 0; y < height; y++) { - this.ReadBgr24Row(width, pixels, rowSpan, y); + this.ReadBgra32Row(width, pixels, rowSpan, y); } } + + return; } - /// - /// Reads a uncompressed TGA image where each pixels has 32 bit. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The image origin. - private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + for (int y = 0; y < height; y++) { - TPixel color = default; - bool invertX = InvertX(origin); - if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + if (invertX) { - using IMemoryOwner row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0); - Span rowSpan = row.GetSpan(); - - if (InvertY(origin)) - { - for (int y = height - 1; y >= 0; y--) - { - this.ReadBgra32Row(width, pixels, rowSpan, y); - } - } - else + for (int x = width - 1; x >= 0; x--) { - for (int y = 0; y < height; y++) - { - this.ReadBgra32Row(width, pixels, rowSpan, y); - } + this.ReadBgra32Pixel(x, color, pixelRow); } - - return; } - - for (int y = 0; y < height; y++) + else { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - if (invertX) + for (int x = 0; x < width; x++) { - for (int x = width - 1; x >= 0; x--) - { - this.ReadBgra32Pixel(x, color, pixelRow); - } - } - else - { - for (int x = 0; x < width; x++) - { - this.ReadBgra32Pixel(x, color, pixelRow); - } + this.ReadBgra32Pixel(x, color, pixelRow); } } } + } - /// - /// Reads a run length encoded TGA image. - /// - /// The pixel type. - /// The width of the image. - /// The height of the image. - /// The to assign the palette to. - /// The bytes per pixel. - /// The image origin. - private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) - where TPixel : unmanaged, IPixel + /// + /// Reads a run length encoded TGA image. + /// + /// The pixel type. + /// The width of the image. + /// The height of the image. + /// The to assign the palette to. + /// The bytes per pixel. + /// The image origin. + private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + byte alphaBits = this.tgaMetadata.AlphaChannelBits; + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) { - TPixel color = default; - byte alphaBits = this.tgaMetadata.AlphaChannelBits; - using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) + Span bufferSpan = buffer.GetSpan(); + this.UncompressRle(width, height, bufferSpan, bytesPerPixel); + for (int y = 0; y < height; y++) { - Span bufferSpan = buffer.GetSpan(); - this.UncompressRle(width, height, bufferSpan, bytesPerPixel); - for (int y = 0; y < height; y++) + int newY = InvertY(y, height, origin); + Span pixelRow = pixels.DangerousGetRowSpan(newY); + int rowStartIdx = y * width * bytesPerPixel; + for (int x = 0; x < width; x++) { - int newY = InvertY(y, height, origin); - Span pixelRow = pixels.DangerousGetRowSpan(newY); - int rowStartIdx = y * width * bytesPerPixel; - for (int x = 0; x < width; x++) + int idx = rowStartIdx + (x * bytesPerPixel); + switch (bytesPerPixel) { - int idx = rowStartIdx + (x * bytesPerPixel); - switch (bytesPerPixel) - { - case 1: - color.FromL8(Unsafe.As(ref bufferSpan[idx])); - break; - case 2: - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque for Bgra5551. - bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); - } - - if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) - { - color.FromLa16(Unsafe.As(ref bufferSpan[idx])); - } - else - { - color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); - } - - break; - case 3: - color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); - break; - case 4: - if (this.hasAlpha) - { - color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); - } - else - { - byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; - color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); - } - - break; - } + case 1: + color.FromL8(Unsafe.As(ref bufferSpan[idx])); + break; + case 2: + if (!this.hasAlpha) + { + // Set alpha value to 1, to treat it as opaque for Bgra5551. + bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); + } - int newX = InvertX(x, width, origin); - pixelRow[newX] = color; + if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) + { + color.FromLa16(Unsafe.As(ref bufferSpan[idx])); + } + else + { + color.FromBgra5551(Unsafe.As(ref bufferSpan[idx])); + } + + break; + case 3: + color.FromBgr24(Unsafe.As(ref bufferSpan[idx])); + break; + case 4: + if (this.hasAlpha) + { + color.FromBgra32(Unsafe.As(ref bufferSpan[idx])); + } + else + { + byte alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; + color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], alpha)); + } + + break; } + + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } + } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ReadFileHeader(stream); + return new ImageInfo( + new PixelTypeInfo(this.fileHeader.PixelDepth), + this.fileHeader.Width, + this.fileHeader.Height, + this.metadata); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) + where TPixel : unmanaged, IPixel + { + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) { - this.ReadFileHeader(stream); - return new ImageInfo( - new PixelTypeInfo(this.fileHeader.PixelDepth), - this.fileHeader.Width, - this.fileHeader.Height, - this.metadata); + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Row(int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel - { - int bytesRead = this.currentStream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromL8Bytes(this.configuration, row, pixelSpan, width); + } - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromL8Bytes(this.configuration, row, pixelSpan, width); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + byte pixelValue = (byte)this.currentStream.ReadByte(); + color.FromL8(Unsafe.As(ref pixelValue)); + pixelSpan[x] = color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadL8Pixel(TPixel color, int x, Span pixelSpan) - where TPixel : unmanaged, IPixel + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) + where TPixel : unmanaged, IPixel + { + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3); + if (bytesRead != 3) { - byte pixelValue = (byte)this.currentStream.ReadByte(); - color.FromL8(Unsafe.As(ref pixelValue)); - pixelSpan[x] = color; + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan) - where TPixel : unmanaged, IPixel - { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 3); - if (bytesRead != 3) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgr pixel"); - } - - color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0])); - pixelSpan[x] = color; - } + color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0])); + pixelSpan[x] = color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgr24Row(int width, Buffer2D pixels, Span row, int y) + where TPixel : unmanaged, IPixel + { + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) { - int bytesRead = this.currentStream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } - - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes(this.configuration, row, pixelSpan, width); + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4); - if (bytesRead != 4) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel"); - } + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromBgr24Bytes(this.configuration, row, pixelSpan, width); + } - byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; - color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); - pixelRow[x] = color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int bytesRead = this.currentStream.Read(this.scratchBuffer, 0, 4); + if (bytesRead != 4) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a bgra pixel"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) - where TPixel : unmanaged, IPixel - { - int bytesRead = this.currentStream.Read(row); - if (bytesRead != row.Length) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); - } + byte alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3]; + color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha)); + pixelRow[x] = color; + } - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes(this.configuration, row, pixelSpan, width); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadBgra32Row(int width, Buffer2D pixels, Span row, int y) + where TPixel : unmanaged, IPixel + { + int bytesRead = this.currentStream.Read(row); + if (bytesRead != row.Length) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel row"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = this.currentStream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromBgra32Bytes(this.configuration, row, pixelSpan, width); + } - this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); - pixelRow[x] = color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra16Pixel(Span palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) - where TPixel : unmanaged, IPixel - { - Bgra5551 bgra = default; - bgra.FromBgra5551(Unsafe.As(ref palette[index * colorMapPixelSizeInBytes])); + this.ReadPalettedBgra16Pixel(palette, colorIndex, colorMapPixelSizeInBytes, ref color); + pixelRow[x] = color; + } - if (!this.hasAlpha) - { - // Set alpha value to 1, to treat it as opaque. - bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra16Pixel(Span palette, int index, int colorMapPixelSizeInBytes, ref TPixel color) + where TPixel : unmanaged, IPixel + { + Bgra5551 bgra = default; + bgra.FromBgra5551(Unsafe.As(ref palette[index * colorMapPixelSizeInBytes])); - color.FromBgra5551(bgra); + if (!this.hasAlpha) + { + // Set alpha value to 1, to treat it as opaque. + bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgr24Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = this.currentStream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } + color.FromBgra5551(bgra); + } - color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - pixelRow[x] = color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgr24Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadPalettedBgra32Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) - where TPixel : unmanaged, IPixel - { - int colorIndex = this.currentStream.ReadByte(); - if (colorIndex == -1) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); - } + color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } - color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - pixelRow[x] = color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadPalettedBgra32Pixel(Span palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow) + where TPixel : unmanaged, IPixel + { + int colorIndex = this.currentStream.ReadByte(); + if (colorIndex == -1) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read color index"); } - /// - /// Produce uncompressed tga data from a run length encoded stream. - /// - /// The width of the image. - /// The height of the image. - /// Buffer for uncompressed data. - /// The bytes used per pixel. - private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) + color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); + pixelRow[x] = color; + } + + /// + /// Produce uncompressed tga data from a run length encoded stream. + /// + /// The width of the image. + /// The height of the image. + /// Buffer for uncompressed data. + /// The bytes used per pixel. + private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel) + { + int uncompressedPixels = 0; + Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel); + int totalPixels = width * height; + while (uncompressedPixels < totalPixels) { - int uncompressedPixels = 0; - Span pixel = this.scratchBuffer.AsSpan(0, bytesPerPixel); - int totalPixels = width * height; - while (uncompressedPixels < totalPixels) + byte runLengthByte = (byte)this.currentStream.ReadByte(); + + // The high bit of a run length packet is set to 1. + int highBit = runLengthByte >> 7; + if (highBit == 1) { - byte runLengthByte = (byte)this.currentStream.ReadByte(); + int runLength = runLengthByte & 127; + int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); + if (bytesRead != bytesPerPixel) + { + TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); + } - // The high bit of a run length packet is set to 1. - int highBit = runLengthByte >> 7; - if (highBit == 1) + int bufferIdx = uncompressedPixels * bytesPerPixel; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) + { + pixel.CopyTo(buffer[bufferIdx..]); + bufferIdx += bytesPerPixel; + } + } + else + { + // Non-run-length encoded packet. + int runLength = runLengthByte; + int bufferIdx = uncompressedPixels * bytesPerPixel; + for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) { - int runLength = runLengthByte & 127; int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); if (bytesRead != bytesPerPixel) { TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); } - int bufferIdx = uncompressedPixels * bytesPerPixel; - for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) - { - pixel.CopyTo(buffer[bufferIdx..]); - bufferIdx += bytesPerPixel; - } - } - else - { - // Non-run-length encoded packet. - int runLength = runLengthByte; - int bufferIdx = uncompressedPixels * bytesPerPixel; - for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) - { - int bytesRead = this.currentStream.Read(pixel, 0, bytesPerPixel); - if (bytesRead != bytesPerPixel) - { - TgaThrowHelper.ThrowInvalidImageContentException("Not enough data to read a pixel from the stream"); - } - - pixel.CopyTo(buffer[bufferIdx..]); - bufferIdx += bytesPerPixel; - } + pixel.CopyTo(buffer[bufferIdx..]); + bufferIdx += bytesPerPixel; } } } + } - /// - /// Returns the y- value based on the given height. - /// - /// The y- value representing the current row. - /// The height of the image. - /// The image origin. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int InvertY(int y, int height, TgaImageOrigin origin) + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the image. + /// The image origin. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InvertY(int y, int height, TgaImageOrigin origin) + { + if (InvertY(origin)) { - if (InvertY(origin)) - { - return height - y - 1; - } + return height - y - 1; + } + + return y; + } - return y; + /// + /// Indicates whether the y coordinates needs to be inverted, to keep a top left origin. + /// + /// The image origin. + /// True, if y coordinate needs to be inverted. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool InvertY(TgaImageOrigin origin) => origin switch + { + TgaImageOrigin.BottomLeft => true, + TgaImageOrigin.BottomRight => true, + _ => false + }; + + /// + /// Returns the x- value based on the given width. + /// + /// The x- value representing the current column. + /// The width of the image. + /// The image origin. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InvertX(int x, int width, TgaImageOrigin origin) + { + if (InvertX(origin)) + { + return width - x - 1; } - /// - /// Indicates whether the y coordinates needs to be inverted, to keep a top left origin. - /// - /// The image origin. - /// True, if y coordinate needs to be inverted. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertY(TgaImageOrigin origin) => origin switch + return x; + } + + /// + /// Indicates whether the x coordinates needs to be inverted, to keep a top left origin. + /// + /// The image origin. + /// True, if x coordinate needs to be inverted. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool InvertX(TgaImageOrigin origin) => + origin switch { - TgaImageOrigin.BottomLeft => true, + TgaImageOrigin.TopRight => true, TgaImageOrigin.BottomRight => true, _ => false }; - /// - /// Returns the x- value based on the given width. - /// - /// The x- value representing the current column. - /// The width of the image. - /// The image origin. - /// The representing the inverted value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int InvertX(int x, int width, TgaImageOrigin origin) - { - if (InvertX(origin)) - { - return width - x - 1; - } - - return x; - } - - /// - /// Indicates whether the x coordinates needs to be inverted, to keep a top left origin. - /// - /// The image origin. - /// True, if x coordinate needs to be inverted. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool InvertX(TgaImageOrigin origin) => - origin switch - { - TgaImageOrigin.TopRight => true, - TgaImageOrigin.BottomRight => true, - _ => false - }; - - /// - /// Reads the tga file header from the stream. - /// - /// The containing image data. - /// The image origin. - private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) - { - this.currentStream = stream; + /// + /// Reads the tga file header from the stream. + /// + /// The containing image data. + /// The image origin. + private TgaImageOrigin ReadFileHeader(BufferedReadStream stream) + { + this.currentStream = stream; - Span buffer = stackalloc byte[TgaFileHeader.Size]; + Span buffer = stackalloc byte[TgaFileHeader.Size]; - this.currentStream.Read(buffer, 0, TgaFileHeader.Size); - this.fileHeader = TgaFileHeader.Parse(buffer); - this.metadata = new ImageMetadata(); - this.tgaMetadata = this.metadata.GetTgaMetadata(); - this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; + this.currentStream.Read(buffer, 0, TgaFileHeader.Size); + this.fileHeader = TgaFileHeader.Parse(buffer); + this.metadata = new ImageMetadata(); + this.tgaMetadata = this.metadata.GetTgaMetadata(); + this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; - int alphaBits = this.fileHeader.ImageDescriptor & 0xf; - if (alphaBits is not 0 and not 1 and not 8) - { - TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); - } + int alphaBits = this.fileHeader.ImageDescriptor & 0xf; + if (alphaBits is not 0 and not 1 and not 8) + { + TgaThrowHelper.ThrowInvalidImageContentException("Invalid alpha channel bits"); + } - this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; - this.hasAlpha = alphaBits > 0; + this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; + this.hasAlpha = alphaBits > 0; - // Bits 4 and 5 describe the image origin. - var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); - return origin; - } + // Bits 4 and 5 describe the image origin. + var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); + return origin; } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index e25677ecc5..f437e80a3f 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -1,43 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Image encoder for writing an image to a stream as a targa truevision image. +/// +public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions { /// - /// Image encoder for writing an image to a stream as a targa truevision image. + /// Gets or sets the number of bits per pixel. /// - public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions - { - /// - /// Gets or sets the number of bits per pixel. - /// - public TgaBitsPerPixel? BitsPerPixel { get; set; } + public TgaBitsPerPixel? BitsPerPixel { get; set; } - /// - /// Gets or sets a value indicating whether no compression or run length compression should be used. - /// - public TgaCompression Compression { get; set; } = TgaCompression.RunLength; + /// + /// Gets or sets a value indicating whether no compression or run length compression should be used. + /// + public TgaCompression Compression { get; set; } = TgaCompression.RunLength; - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 5cfdf3022c..016806db03 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -1,429 +1,425 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Numerics; using System.Runtime.CompilerServices; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Image encoder for writing an image to a stream as a truevision targa image. +/// +internal sealed class TgaEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream as a truevision targa image. + /// Used for allocating memory during processing operations. /// - internal sealed class TgaEncoderCore : IImageEncoderInternals - { - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// Reusable buffer for writing data. - /// - private readonly byte[] buffer = new byte[2]; - - /// - /// The color depth, in number of bits per pixel. - /// - private TgaBitsPerPixel? bitsPerPixel; - - /// - /// Indicates if run length compression should be used. - /// - private readonly TgaCompression compression; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options. - /// The memory manager. - public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.bitsPerPixel = options.BitsPerPixel; - this.compression = options.Compression; - } + private readonly MemoryAllocator memoryAllocator; - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + /// + /// The global configuration. + /// + private Configuration configuration; - this.configuration = image.GetConfiguration(); - ImageMetadata metadata = image.Metadata; - TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); - this.bitsPerPixel ??= tgaMetadata.BitsPerPixel; + /// + /// Reusable buffer for writing data. + /// + private readonly byte[] buffer = new byte[2]; - TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; - if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) - { - imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; - } + /// + /// The color depth, in number of bits per pixel. + /// + private TgaBitsPerPixel? bitsPerPixel; - byte imageDescriptor = 0; - if (this.compression is TgaCompression.RunLength) - { - // If compression is used, set bit 5 of the image descriptor to indicate a left top origin. - imageDescriptor |= 0x20; - } + /// + /// Indicates if run length compression should be used. + /// + private readonly TgaCompression compression; - if (this.bitsPerPixel is TgaBitsPerPixel.Pixel32) - { - // Indicate, that 8 bit are used for the alpha channel. - imageDescriptor |= 0x8; - } + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.bitsPerPixel = options.BitsPerPixel; + this.compression = options.Compression; + } - if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16) - { - // Indicate, that 1 bit is used for the alpha channel. - imageDescriptor |= 0x1; - } + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - TgaFileHeader fileHeader = new( - idLength: 0, - colorMapType: 0, - imageType: imageType, - cMapStart: 0, - cMapLength: 0, - cMapDepth: 0, - xOffset: 0, - yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left. - width: (short)image.Width, - height: (short)image.Height, - pixelDepth: (byte)this.bitsPerPixel.Value, - imageDescriptor: imageDescriptor); - - Span buffer = stackalloc byte[TgaFileHeader.Size]; - fileHeader.WriteTo(buffer); - - stream.Write(buffer, 0, TgaFileHeader.Size); - if (this.compression is TgaCompression.RunLength) - { - this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); - } - else - { - this.WriteImage(stream, image.Frames.RootFrame); - } + this.configuration = image.GetConfiguration(); + ImageMetadata metadata = image.Metadata; + TgaMetadata tgaMetadata = metadata.GetTgaMetadata(); + this.bitsPerPixel ??= tgaMetadata.BitsPerPixel; - stream.Flush(); + TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; + if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) + { + imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; } - /// - /// Writes the pixel data to the binary stream. - /// - /// The pixel format. - /// The to write to. - /// - /// The containing pixel data. - /// - private void WriteImage(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + byte imageDescriptor = 0; + if (this.compression is TgaCompression.RunLength) { - Buffer2D pixels = image.PixelBuffer; - switch (this.bitsPerPixel) - { - case TgaBitsPerPixel.Pixel8: - this.Write8Bit(stream, pixels); - break; + // If compression is used, set bit 5 of the image descriptor to indicate a left top origin. + imageDescriptor |= 0x20; + } - case TgaBitsPerPixel.Pixel16: - this.Write16Bit(stream, pixels); - break; + if (this.bitsPerPixel is TgaBitsPerPixel.Pixel32) + { + // Indicate, that 8 bit are used for the alpha channel. + imageDescriptor |= 0x8; + } - case TgaBitsPerPixel.Pixel24: - this.Write24Bit(stream, pixels); - break; + if (this.bitsPerPixel is TgaBitsPerPixel.Pixel16) + { + // Indicate, that 1 bit is used for the alpha channel. + imageDescriptor |= 0x1; + } - case TgaBitsPerPixel.Pixel32: - this.Write32Bit(stream, pixels); - break; - } + TgaFileHeader fileHeader = new( + idLength: 0, + colorMapType: 0, + imageType: imageType, + cMapStart: 0, + cMapLength: 0, + cMapDepth: 0, + xOffset: 0, + yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left. + width: (short)image.Width, + height: (short)image.Height, + pixelDepth: (byte)this.bitsPerPixel.Value, + imageDescriptor: imageDescriptor); + + Span buffer = stackalloc byte[TgaFileHeader.Size]; + fileHeader.WriteTo(buffer); + + stream.Write(buffer, 0, TgaFileHeader.Size); + if (this.compression is TgaCompression.RunLength) + { + this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); + } + else + { + this.WriteImage(stream, image.Frames.RootFrame); } - /// - /// Writes a run length encoded tga image to the stream. - /// - /// The pixel type. - /// The stream to write the image to. - /// The image to encode. - private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) - where TPixel : unmanaged, IPixel + stream.Flush(); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WriteImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Buffer2D pixels = image.PixelBuffer; + switch (this.bitsPerPixel) { - Rgba32 color = default; - Buffer2D pixels = image.PixelBuffer; - for (int y = 0; y < image.Height; y++) + case TgaBitsPerPixel.Pixel8: + this.Write8Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel16: + this.Write16Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel24: + this.Write24Bit(stream, pixels); + break; + + case TgaBitsPerPixel.Pixel32: + this.Write32Bit(stream, pixels); + break; + } + } + + /// + /// Writes a run length encoded tga image to the stream. + /// + /// The pixel type. + /// The stream to write the image to. + /// The image to encode. + private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + Rgba32 color = default; + Buffer2D pixels = image.PixelBuffer; + for (int y = 0; y < image.Height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y); + for (int x = 0; x < image.Width;) { - Span pixelRow = pixels.DangerousGetRowSpan(y); - for (int x = 0; x < image.Width;) - { - TPixel currentPixel = pixelRow[x]; - currentPixel.ToRgba32(ref color); - byte equalPixelCount = FindEqualPixels(pixelRow, x); + TPixel currentPixel = pixelRow[x]; + currentPixel.ToRgba32(ref color); + byte equalPixelCount = FindEqualPixels(pixelRow, x); - if (equalPixelCount > 0) - { - // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. - stream.WriteByte((byte)(equalPixelCount | 128)); - this.WritePixel(stream, currentPixel, color); - x += equalPixelCount + 1; - } - else + if (equalPixelCount > 0) + { + // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. + stream.WriteByte((byte)(equalPixelCount | 128)); + this.WritePixel(stream, currentPixel, color); + x += equalPixelCount + 1; + } + else + { + // Write Raw Packet (i.e., Non-Run-Length Encoded): + byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x); + stream.WriteByte(unEqualPixelCount); + this.WritePixel(stream, currentPixel, color); + x++; + for (int i = 0; i < unEqualPixelCount; i++) { - // Write Raw Packet (i.e., Non-Run-Length Encoded): - byte unEqualPixelCount = FindUnEqualPixels(pixelRow, x); - stream.WriteByte(unEqualPixelCount); + currentPixel = pixelRow[x]; + currentPixel.ToRgba32(ref color); this.WritePixel(stream, currentPixel, color); x++; - for (int i = 0; i < unEqualPixelCount; i++) - { - currentPixel = pixelRow[x]; - currentPixel.ToRgba32(ref color); - this.WritePixel(stream, currentPixel, color); - x++; - } } } } } + } - /// - /// Writes a the pixel to the stream. - /// - /// The type of the pixel. - /// The stream to write to. - /// The current pixel. - /// The color of the pixel to write. - private void WritePixel(Stream stream, TPixel currentPixel, Rgba32 color) - where TPixel : unmanaged, IPixel + /// + /// Writes a the pixel to the stream. + /// + /// The type of the pixel. + /// The stream to write to. + /// The current pixel. + /// The color of the pixel to write. + private void WritePixel(Stream stream, TPixel currentPixel, Rgba32 color) + where TPixel : unmanaged, IPixel + { + switch (this.bitsPerPixel) { - switch (this.bitsPerPixel) - { - case TgaBitsPerPixel.Pixel8: - int luminance = GetLuminance(currentPixel); - stream.WriteByte((byte)luminance); - break; - - case TgaBitsPerPixel.Pixel16: - Bgra5551 bgra5551 = new(color.ToVector4()); - BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); - stream.WriteByte(this.buffer[0]); - stream.WriteByte(this.buffer[1]); - - break; - - case TgaBitsPerPixel.Pixel24: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - break; - - case TgaBitsPerPixel.Pixel32: - stream.WriteByte(color.B); - stream.WriteByte(color.G); - stream.WriteByte(color.R); - stream.WriteByte(color.A); - break; - } + case TgaBitsPerPixel.Pixel8: + int luminance = GetLuminance(currentPixel); + stream.WriteByte((byte)luminance); + break; + + case TgaBitsPerPixel.Pixel16: + Bgra5551 bgra5551 = new(color.ToVector4()); + BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); + stream.WriteByte(this.buffer[0]); + stream.WriteByte(this.buffer[1]); + + break; + + case TgaBitsPerPixel.Pixel24: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + break; + + case TgaBitsPerPixel.Pixel32: + stream.WriteByte(color.B); + stream.WriteByte(color.G); + stream.WriteByte(color.R); + stream.WriteByte(color.A); + break; } + } - /// - /// Finds consecutive pixels which have the same value up to 128 pixels maximum. - /// - /// The pixel type. - /// A pixel row of the image to encode. - /// X coordinate to start searching for the same pixels. - /// The number of equal pixels. - private static byte FindEqualPixels(Span pixelRow, int xStart) - where TPixel : unmanaged, IPixel + /// + /// Finds consecutive pixels which have the same value up to 128 pixels maximum. + /// + /// The pixel type. + /// A pixel row of the image to encode. + /// X coordinate to start searching for the same pixels. + /// The number of equal pixels. + private static byte FindEqualPixels(Span pixelRow, int xStart) + where TPixel : unmanaged, IPixel + { + byte equalPixelCount = 0; + TPixel startPixel = pixelRow[xStart]; + for (int x = xStart + 1; x < pixelRow.Length; x++) { - byte equalPixelCount = 0; - TPixel startPixel = pixelRow[xStart]; - for (int x = xStart + 1; x < pixelRow.Length; x++) + TPixel nextPixel = pixelRow[x]; + if (startPixel.Equals(nextPixel)) { - TPixel nextPixel = pixelRow[x]; - if (startPixel.Equals(nextPixel)) - { - equalPixelCount++; - } - else - { - return equalPixelCount; - } - - if (equalPixelCount >= 127) - { - return equalPixelCount; - } + equalPixelCount++; + } + else + { + return equalPixelCount; } - return equalPixelCount; + if (equalPixelCount >= 127) + { + return equalPixelCount; + } } - /// - /// Finds consecutive pixels which are unequal up to 128 pixels maximum. - /// - /// The pixel type. - /// A pixel row of the image to encode. - /// X coordinate to start searching for the unequal pixels. - /// The number of equal pixels. - private static byte FindUnEqualPixels(Span pixelRow, int xStart) - where TPixel : unmanaged, IPixel + return equalPixelCount; + } + + /// + /// Finds consecutive pixels which are unequal up to 128 pixels maximum. + /// + /// The pixel type. + /// A pixel row of the image to encode. + /// X coordinate to start searching for the unequal pixels. + /// The number of equal pixels. + private static byte FindUnEqualPixels(Span pixelRow, int xStart) + where TPixel : unmanaged, IPixel + { + byte unEqualPixelCount = 0; + TPixel currentPixel = pixelRow[xStart]; + for (int x = xStart + 1; x < pixelRow.Length; x++) { - byte unEqualPixelCount = 0; - TPixel currentPixel = pixelRow[xStart]; - for (int x = xStart + 1; x < pixelRow.Length; x++) + TPixel nextPixel = pixelRow[x]; + if (currentPixel.Equals(nextPixel)) { - TPixel nextPixel = pixelRow[x]; - if (currentPixel.Equals(nextPixel)) - { - return unEqualPixelCount; - } - - unEqualPixelCount++; + return unEqualPixelCount; + } - if (unEqualPixelCount >= 127) - { - return unEqualPixelCount; - } + unEqualPixelCount++; - currentPixel = nextPixel; + if (unEqualPixelCount >= 127) + { + return unEqualPixelCount; } - return unEqualPixelCount; + currentPixel = nextPixel; } - private IMemoryOwner AllocateRow(int width, int bytesPerPixel) - => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); - - /// - /// Writes the 8bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write8Bit(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); - Span rowSpan = row.GetSpan(); + return unEqualPixelCount; + } - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToL8Bytes( - this.configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } - } + private IMemoryOwner AllocateRow(int width, int bytesPerPixel) + => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); - /// - /// Writes the 16bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write16Bit(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); - Span rowSpan = row.GetSpan(); + /// + /// Writes the 8bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write8Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner row = this.AllocateRow(pixels.Width, 1); + Span rowSpan = row.GetSpan(); - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra5551Bytes( - this.configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToL8Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } + } - /// - /// Writes the 24bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write24Bit(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); - Span rowSpan = row.GetSpan(); + /// + /// Writes the 16bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write16Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner row = this.AllocateRow(pixels.Width, 2); + Span rowSpan = row.GetSpan(); - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgr24Bytes( - this.configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToBgra5551Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } + } - /// - /// Writes the 32bit pixels uncompressed to the stream. - /// - /// The pixel format. - /// The to write to. - /// The containing pixel data. - private void Write32Bit(Stream stream, Buffer2D pixels) - where TPixel : unmanaged, IPixel - { - using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); - Span rowSpan = row.GetSpan(); + /// + /// Writes the 24bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write24Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner row = this.AllocateRow(pixels.Width, 3); + Span rowSpan = row.GetSpan(); - for (int y = pixels.Height - 1; y >= 0; y--) - { - Span pixelSpan = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.ToBgra32Bytes( - this.configuration, - pixelSpan, - rowSpan, - pixelSpan.Length); - stream.Write(rowSpan); - } + for (int y = pixels.Height - 1; y >= 0; y--) + { + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } + } - /// - /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. - /// - /// The type of pixel format. - /// The pixel to get the luminance from. - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetLuminance(TPixel sourcePixel) - where TPixel : unmanaged, IPixel + /// + /// Writes the 32bit pixels uncompressed to the stream. + /// + /// The pixel format. + /// The to write to. + /// The containing pixel data. + private void Write32Bit(Stream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + using IMemoryOwner row = this.AllocateRow(pixels.Width, 4); + Span rowSpan = row.GetSpan(); + + for (int y = pixels.Height - 1; y >= 0; y--) { - Vector4 vector = sourcePixel.ToVector4(); - return ColorNumerics.GetBT709Luminance(ref vector, 256); + Span pixelSpan = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes( + this.configuration, + pixelSpan, + rowSpan, + pixelSpan.Length); + stream.Write(rowSpan); } } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The type of pixel format. + /// The pixel to get the luminance from. + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel) + where TPixel : unmanaged, IPixel + { + Vector4 vector = sourcePixel.ToVector4(); + return ColorNumerics.GetBT709Luminance(ref vector, 256); + } } diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs index 591c218cd1..007dc03de1 100644 --- a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs +++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs @@ -1,147 +1,145 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// This block of bytes tells the application detailed information about the targa image. +/// +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +internal readonly struct TgaFileHeader { /// - /// This block of bytes tells the application detailed information about the targa image. - /// + /// Defines the size of the data structure in the targa file. + /// + public const int Size = TgaConstants.FileHeaderLength; + + public TgaFileHeader( + byte idLength, + byte colorMapType, + TgaImageType imageType, + short cMapStart, + short cMapLength, + byte cMapDepth, + short xOffset, + short yOffset, + short width, + short height, + byte pixelDepth, + byte imageDescriptor) + { + this.IdLength = idLength; + this.ColorMapType = colorMapType; + this.ImageType = imageType; + this.CMapStart = cMapStart; + this.CMapLength = cMapLength; + this.CMapDepth = cMapDepth; + this.XOffset = xOffset; + this.YOffset = yOffset; + this.Width = width; + this.Height = height; + this.PixelDepth = pixelDepth; + this.ImageDescriptor = imageDescriptor; + } + + /// + /// Gets the id length. + /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number + /// of characters is 255. A value of zero indicates that no Image ID field is included with the image. + /// + public byte IdLength { get; } + + /// + /// Gets the color map type. + /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined + /// values for this field: + /// 0 - indicates that no color-map data is included with this image. + /// 1 - indicates that a color-map is included with this image. + /// + public byte ColorMapType { get; } + + /// + /// Gets the image type. + /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various + /// pixel depths. + /// + public TgaImageType ImageType { get; } + + /// + /// Gets the start of the color map. + /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field + /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero. + /// + public short CMapStart { get; } + + /// + /// Gets the total number of color map entries included. + /// + public short CMapLength { get; } + + /// + /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. + /// + public byte CMapDepth { get; } + + /// + /// Gets the XOffset. + /// These bytes specify the absolute horizontal coordinate for the lower left + /// corner of the image as it is positioned on a display device having an + /// origin at the lower left of the screen. + /// + public short XOffset { get; } + + /// + /// Gets the YOffset. + /// These bytes specify the absolute vertical coordinate for the lower left + /// corner of the image as it is positioned on a display device having an + /// origin at the lower left of the screen. + /// + public short YOffset { get; } + + /// + /// Gets the width of the image in pixels. + /// + public short Width { get; } + + /// + /// Gets the height of the image in pixels. /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal readonly struct TgaFileHeader + public short Height { get; } + + /// + /// Gets the number of bits per pixel. This number includes + /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and + /// 32 but other pixel depths could be used. + /// + public byte PixelDepth { get; } + + /// + /// Gets the ImageDescriptor. + /// ImageDescriptor contains two pieces of information. + /// Bits 0 through 3 contain the number of attribute bits per pixel. + /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel, + /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image. + /// This position may be any of the four corners of the display screen. + /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen. + /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0. + /// + public byte ImageDescriptor { get; } + + public static TgaFileHeader Parse(Span data) { - /// - /// Defines the size of the data structure in the targa file. - /// - public const int Size = TgaConstants.FileHeaderLength; - - public TgaFileHeader( - byte idLength, - byte colorMapType, - TgaImageType imageType, - short cMapStart, - short cMapLength, - byte cMapDepth, - short xOffset, - short yOffset, - short width, - short height, - byte pixelDepth, - byte imageDescriptor) - { - this.IdLength = idLength; - this.ColorMapType = colorMapType; - this.ImageType = imageType; - this.CMapStart = cMapStart; - this.CMapLength = cMapLength; - this.CMapDepth = cMapDepth; - this.XOffset = xOffset; - this.YOffset = yOffset; - this.Width = width; - this.Height = height; - this.PixelDepth = pixelDepth; - this.ImageDescriptor = imageDescriptor; - } - - /// - /// Gets the id length. - /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number - /// of characters is 255. A value of zero indicates that no Image ID field is included with the image. - /// - public byte IdLength { get; } - - /// - /// Gets the color map type. - /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined - /// values for this field: - /// 0 - indicates that no color-map data is included with this image. - /// 1 - indicates that a color-map is included with this image. - /// - public byte ColorMapType { get; } - - /// - /// Gets the image type. - /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various - /// pixel depths. - /// - public TgaImageType ImageType { get; } - - /// - /// Gets the start of the color map. - /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field - /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero. - /// - public short CMapStart { get; } - - /// - /// Gets the total number of color map entries included. - /// - public short CMapLength { get; } - - /// - /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. - /// - public byte CMapDepth { get; } - - /// - /// Gets the XOffset. - /// These bytes specify the absolute horizontal coordinate for the lower left - /// corner of the image as it is positioned on a display device having an - /// origin at the lower left of the screen. - /// - public short XOffset { get; } - - /// - /// Gets the YOffset. - /// These bytes specify the absolute vertical coordinate for the lower left - /// corner of the image as it is positioned on a display device having an - /// origin at the lower left of the screen. - /// - public short YOffset { get; } - - /// - /// Gets the width of the image in pixels. - /// - public short Width { get; } - - /// - /// Gets the height of the image in pixels. - /// - public short Height { get; } - - /// - /// Gets the number of bits per pixel. This number includes - /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and - /// 32 but other pixel depths could be used. - /// - public byte PixelDepth { get; } - - /// - /// Gets the ImageDescriptor. - /// ImageDescriptor contains two pieces of information. - /// Bits 0 through 3 contain the number of attribute bits per pixel. - /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel, - /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image. - /// This position may be any of the four corners of the display screen. - /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen. - /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0. - /// - public byte ImageDescriptor { get; } - - public static TgaFileHeader Parse(Span data) - { - return MemoryMarshal.Cast(data)[0]; - } - - public void WriteTo(Span buffer) - { - ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); - - dest = this; - } + return MemoryMarshal.Cast(data)[0]; + } + + public void WriteTo(Span buffer) + { + ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); + + dest = this; } } diff --git a/src/ImageSharp/Formats/Tga/TgaFormat.cs b/src/ImageSharp/Formats/Tga/TgaFormat.cs index 6ec70d6555..886d4eea36 100644 --- a/src/ImageSharp/Formats/Tga/TgaFormat.cs +++ b/src/ImageSharp/Formats/Tga/TgaFormat.cs @@ -1,33 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Tga; -namespace SixLabors.ImageSharp.Formats.Tga +/// +/// Registers the image encoders, decoders and mime type detectors for the tga format. +/// +public sealed class TgaFormat : IImageFormat { /// - /// Registers the image encoders, decoders and mime type detectors for the tga format. + /// Gets the current instance. /// - public sealed class TgaFormat : IImageFormat - { - /// - /// Gets the current instance. - /// - public static TgaFormat Instance { get; } = new TgaFormat(); + public static TgaFormat Instance { get; } = new TgaFormat(); - /// - public string Name => "TGA"; + /// + public string Name => "TGA"; - /// - public string DefaultMimeType => "image/tga"; + /// + public string DefaultMimeType => "image/tga"; - /// - public IEnumerable MimeTypes => TgaConstants.MimeTypes; + /// + public IEnumerable MimeTypes => TgaConstants.MimeTypes; - /// - public IEnumerable FileExtensions => TgaConstants.FileExtensions; + /// + public IEnumerable FileExtensions => TgaConstants.FileExtensions; - /// - public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata(); - } + /// + public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata(); } diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs index a3ecfeb170..23f93a8249 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tga; -namespace SixLabors.ImageSharp.Formats.Tga +/// +/// Detects tga file headers. +/// +public sealed class TgaImageFormatDetector : IImageFormatDetector { - /// - /// Detects tga file headers. - /// - public sealed class TgaImageFormatDetector : IImageFormatDetector + /// + public int HeaderSize => 16; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) { - /// - public int HeaderSize => 16; + return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; + } - /// - public IImageFormat DetectFormat(ReadOnlySpan header) + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; - } + // There are no magic bytes in the first few bytes of a tga file, + // so we try to figure out if its a valid tga by checking for valid tga header bytes. - private bool IsSupportedFileFormat(ReadOnlySpan header) - { - if (header.Length >= this.HeaderSize) + // The color map type should be either 0 or 1, other values are not valid. + if (header[1] != 0 && header[1] != 1) { - // There are no magic bytes in the first few bytes of a tga file, - // so we try to figure out if its a valid tga by checking for valid tga header bytes. - - // The color map type should be either 0 or 1, other values are not valid. - if (header[1] != 0 && header[1] != 1) - { - return false; - } - - // The third byte is the image type. - var imageType = (TgaImageType)header[2]; - if (!imageType.IsValid()) - { - return false; - } + return false; + } - // If the color map typ is zero, all bytes of the color map specification should also be zeros. - if (header[1] == 0) - { - if (header[3] != 0 || header[4] != 0 || header[5] != 0 || header[6] != 0 || header[7] != 0) - { - return false; - } - } + // The third byte is the image type. + var imageType = (TgaImageType)header[2]; + if (!imageType.IsValid()) + { + return false; + } - // The height or the width of the image should not be zero. - if ((header[12] == 0 && header[13] == 0) || (header[14] == 0 && header[15] == 0)) + // If the color map typ is zero, all bytes of the color map specification should also be zeros. + if (header[1] == 0) + { + if (header[3] != 0 || header[4] != 0 || header[5] != 0 || header[6] != 0 || header[7] != 0) { return false; } + } - return true; + // The height or the width of the image should not be zero. + if ((header[12] == 0 && header[13] == 0) || (header[14] == 0 && header[15] == 0)) + { + return false; } - return false; + return true; } + + return false; } } diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs index a31a0f23fa..3aa5a6cd38 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +internal enum TgaImageOrigin { - internal enum TgaImageOrigin - { - /// - /// Bottom left origin. - /// - BottomLeft = 0, + /// + /// Bottom left origin. + /// + BottomLeft = 0, - /// - /// Bottom right origin. - /// - BottomRight = 1, + /// + /// Bottom right origin. + /// + BottomRight = 1, - /// - /// Top left origin. - /// - TopLeft = 2, + /// + /// Top left origin. + /// + TopLeft = 2, - /// - /// Top right origin. - /// - TopRight = 3, - } + /// + /// Top right origin. + /// + TopRight = 3, } diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs index af9a68f377..bc06332f21 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageType.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs @@ -2,47 +2,46 @@ // Licensed under the Six Labors Split License. namespace SixLabors. - ImageSharp.Formats.Tga + ImageSharp.Formats.Tga; + +/// +/// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color, +/// True-Color and Direct-Color images of various pixel depths. +/// +public enum TgaImageType : byte { /// - /// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color, - /// True-Color and Direct-Color images of various pixel depths. + /// No image data included. + /// + NoImageData = 0, + + /// + /// Uncompressed, color mapped image. + /// + ColorMapped = 1, + + /// + /// Uncompressed true color image. + /// + TrueColor = 2, + + /// + /// Uncompressed Black and white (grayscale) image. + /// + BlackAndWhite = 3, + + /// + /// Run length encoded, color mapped image. + /// + RleColorMapped = 9, + + /// + /// Run length encoded, true color image. + /// + RleTrueColor = 10, + + /// + /// Run length encoded, black and white (grayscale) image. /// - public enum TgaImageType : byte - { - /// - /// No image data included. - /// - NoImageData = 0, - - /// - /// Uncompressed, color mapped image. - /// - ColorMapped = 1, - - /// - /// Uncompressed true color image. - /// - TrueColor = 2, - - /// - /// Uncompressed Black and white (grayscale) image. - /// - BlackAndWhite = 3, - - /// - /// Run length encoded, color mapped image. - /// - RleColorMapped = 9, - - /// - /// Run length encoded, true color image. - /// - RleTrueColor = 10, - - /// - /// Run length encoded, black and white (grayscale) image. - /// - RleBlackAndWhite = 11, - } + RleBlackAndWhite = 11, } diff --git a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs index 3c0e030c04..f96d7fae94 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs @@ -1,49 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Extension methods for TgaImageType enum. +/// +public static class TgaImageTypeExtensions { /// - /// Extension methods for TgaImageType enum. + /// Checks if this tga image type is run length encoded. /// - public static class TgaImageTypeExtensions + /// The tga image type. + /// True, if this image type is run length encoded, otherwise false. + public static bool IsRunLengthEncoded(this TgaImageType imageType) { - /// - /// Checks if this tga image type is run length encoded. - /// - /// The tga image type. - /// True, if this image type is run length encoded, otherwise false. - public static bool IsRunLengthEncoded(this TgaImageType imageType) + if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) { - if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) - { - return true; - } - - return false; + return true; } - /// - /// Checks, if the image type has valid value. - /// - /// The image type. - /// true, if its a valid tga image type. - public static bool IsValid(this TgaImageType imageType) + return false; + } + + /// + /// Checks, if the image type has valid value. + /// + /// The image type. + /// true, if its a valid tga image type. + public static bool IsValid(this TgaImageType imageType) + { + switch (imageType) { - switch (imageType) - { - case TgaImageType.NoImageData: - case TgaImageType.ColorMapped: - case TgaImageType.TrueColor: - case TgaImageType.BlackAndWhite: - case TgaImageType.RleColorMapped: - case TgaImageType.RleTrueColor: - case TgaImageType.RleBlackAndWhite: - return true; + case TgaImageType.NoImageData: + case TgaImageType.ColorMapped: + case TgaImageType.TrueColor: + case TgaImageType.BlackAndWhite: + case TgaImageType.RleColorMapped: + case TgaImageType.RleTrueColor: + case TgaImageType.RleBlackAndWhite: + return true; - default: - return false; - } + default: + return false; } } } diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs index 61b92d7313..1fb3ab5c5d 100644 --- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs +++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs @@ -1,38 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +/// +/// Provides TGA specific metadata information for the image. +/// +public class TgaMetadata : IDeepCloneable { /// - /// Provides TGA specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class TgaMetadata : IDeepCloneable + public TgaMetadata() { - /// - /// Initializes a new instance of the class. - /// - public TgaMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private TgaMetadata(TgaMetadata other) - => this.BitsPerPixel = other.BitsPerPixel; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private TgaMetadata(TgaMetadata other) + => this.BitsPerPixel = other.BitsPerPixel; - /// - /// Gets or sets the number of bits per pixel. - /// - public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24; + /// + /// Gets or sets the number of bits per pixel. + /// + public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24; - /// - /// Gets or sets the number of alpha bits per pixel. - /// - public byte AlphaChannelBits { get; set; } + /// + /// Gets or sets the number of alpha bits per pixel. + /// + public byte AlphaChannelBits { get; set; } - /// - public IDeepCloneable DeepClone() => new TgaMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new TgaMetadata(this); } diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs index 4d3e4f28a2..e2ab502259 100644 --- a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs +++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs @@ -1,37 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Tga +namespace SixLabors.ImageSharp.Formats.Tga; + +internal static class TgaThrowHelper { - internal static class TgaThrowHelper - { - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage) - => throw new InvalidImageContentException(errorMessage); + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) + => throw new InvalidImageContentException(errorMessage); - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - /// The exception that is the cause of the current exception, or a null reference - /// if no inner exception is specified. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) - => throw new InvalidImageContentException(errorMessage, innerException); + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + /// The exception that is the cause of the current exception, or a null reference + /// if no inner exception is specified. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) + => throw new InvalidImageContentException(errorMessage, innerException); - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupportedException(string errorMessage) - => throw new NotSupportedException(errorMessage); - } + /// + /// Cold path optimization for throwing 's + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedException(string errorMessage) + => throw new NotSupportedException(errorMessage); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs index 1219fa02ed..52b7b63352 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs @@ -1,63 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +internal static class BitWriterUtils { - internal static class BitWriterUtils + public static void WriteBits(Span buffer, nint pos, nint count, byte value) { - public static void WriteBits(Span buffer, nint pos, nint count, byte value) - { - nint bitPos = Numerics.Modulo8(pos); - nint bufferPos = pos / 8; - nint startIdx = bufferPos + bitPos; - nint endIdx = startIdx + count; + nint bitPos = Numerics.Modulo8(pos); + nint bufferPos = pos / 8; + nint startIdx = bufferPos + bitPos; + nint endIdx = startIdx + count; - if (value == 1) + if (value == 1) + { + for (nint i = startIdx; i < endIdx; i++) { - for (nint i = startIdx; i < endIdx; i++) + WriteBit(buffer, bufferPos, bitPos); + + bitPos++; + if (bitPos >= 8) { - WriteBit(buffer, bufferPos, bitPos); - - bitPos++; - if (bitPos >= 8) - { - bitPos = 0; - bufferPos++; - } + bitPos = 0; + bufferPos++; } } - else + } + else + { + for (nint i = startIdx; i < endIdx; i++) { - for (nint i = startIdx; i < endIdx; i++) + WriteZeroBit(buffer, bufferPos, bitPos); + + bitPos++; + if (bitPos >= 8) { - WriteZeroBit(buffer, bufferPos, bitPos); - - bitPos++; - if (bitPos >= 8) - { - bitPos = 0; - bufferPos++; - } + bitPos = 0; + bufferPos++; } } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void WriteBit(Span buffer, nint bufferPos, nint bitPos) - { - ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); - b |= (byte)(1 << (int)(7 - bitPos)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void WriteBit(Span buffer, nint bufferPos, nint bitPos) + { + ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); + b |= (byte)(1 << (int)(7 - bitPos)); + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void WriteZeroBit(Span buffer, nint bufferPos, nint bitPos) - { - ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); - b = (byte)(b & ~(1 << (int)(7 - bitPos))); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void WriteZeroBit(Span buffer, nint bufferPos, nint bitPos) + { + ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos); + b = (byte)(b & ~(1 << (int)(7 - bitPos))); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs index 2001228390..6881e3a7b3 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs @@ -1,55 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +internal sealed class DeflateCompressor : TiffBaseCompressor { - internal sealed class DeflateCompressor : TiffBaseCompressor - { - private readonly DeflateCompressionLevel compressionLevel; + private readonly DeflateCompressionLevel compressionLevel; - private readonly MemoryStream memoryStream = new MemoryStream(); + private readonly MemoryStream memoryStream = new MemoryStream(); - public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) - : base(output, allocator, width, bitsPerPixel, predictor) - => this.compressionLevel = compressionLevel; + public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel) + : base(output, allocator, width, bitsPerPixel, predictor) + => this.compressionLevel = compressionLevel; - /// - public override TiffCompression Method => TiffCompression.Deflate; + /// + public override TiffCompression Method => TiffCompression.Deflate; - /// - public override void Initialize(int rowsPerStrip) - { - } + /// + public override void Initialize(int rowsPerStrip) + { + } - /// - public override void CompressStrip(Span rows, int height) + /// + public override void CompressStrip(Span rows, int height) + { + this.memoryStream.Seek(0, SeekOrigin.Begin); + using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel)) { - this.memoryStream.Seek(0, SeekOrigin.Begin); - using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel)) + if (this.Predictor == TiffPredictor.Horizontal) { - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); - } - - stream.Write(rows); - stream.Flush(); + HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); } - int size = (int)this.memoryStream.Position; - byte[] buffer = this.memoryStream.GetBuffer(); - this.Output.Write(buffer, 0, size); + stream.Write(rows); + stream.Flush(); } - /// - protected override void Dispose(bool disposing) - { - } + int size = (int)this.memoryStream.Position; + byte[] buffer = this.memoryStream.GetBuffer(); + this.Output.Write(buffer, 0, size); + } + + /// + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs index 0d76187844..ee034f7eee 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs @@ -1,40 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +internal sealed class LzwCompressor : TiffBaseCompressor { - internal sealed class LzwCompressor : TiffBaseCompressor - { - private TiffLzwEncoder lzwEncoder; + private TiffLzwEncoder lzwEncoder; - public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) - : base(output, allocator, width, bitsPerPixel, predictor) - { - } + public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor) + : base(output, allocator, width, bitsPerPixel, predictor) + { + } - /// - public override TiffCompression Method => TiffCompression.Lzw; + /// + public override TiffCompression Method => TiffCompression.Lzw; - /// - public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); + /// + public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator); - /// - public override void CompressStrip(Span rows, int height) + /// + public override void CompressStrip(Span rows, int height) + { + if (this.Predictor == TiffPredictor.Horizontal) { - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); - } - - this.lzwEncoder.Encode(rows, this.Output); + HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel); } - /// - protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose(); + this.lzwEncoder.Encode(rows, this.Output); } + + /// + protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose(); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs index 4d2d74ad50..34dce4ce9d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs @@ -1,34 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +internal sealed class NoCompressor : TiffBaseCompressor { - internal sealed class NoCompressor : TiffBaseCompressor + public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(output, memoryAllocator, width, bitsPerPixel) { - public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(output, memoryAllocator, width, bitsPerPixel) - { - } + } - /// - public override TiffCompression Method => TiffCompression.None; + /// + public override TiffCompression Method => TiffCompression.None; - /// - public override void Initialize(int rowsPerStrip) - { - } + /// + public override void Initialize(int rowsPerStrip) + { + } - /// - public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); + /// + public override void CompressStrip(Span rows, int height) => this.Output.Write(rows); - /// - protected override void Dispose(bool disposing) - { - } + /// + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs index 472ceb21e4..180d28de9b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs @@ -1,49 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +internal sealed class PackBitsCompressor : TiffBaseCompressor { - internal sealed class PackBitsCompressor : TiffBaseCompressor + private IMemoryOwner pixelData; + + public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) { - private IMemoryOwner pixelData; + } - public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) - { - } + /// + public override TiffCompression Method => TiffCompression.PackBits; - /// - public override TiffCompression Method => TiffCompression.PackBits; + /// + public override void Initialize(int rowsPerStrip) + { + int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; + this.pixelData = this.Allocator.Allocate(this.BytesPerRow + additionalBytes); + } - /// - public override void Initialize(int rowsPerStrip) - { - int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1; - this.pixelData = this.Allocator.Allocate(this.BytesPerRow + additionalBytes); - } + /// + public override void CompressStrip(Span rows, int height) + { + DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); + DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); - /// - public override void CompressStrip(Span rows, int height) + Span span = this.pixelData.GetSpan(); + for (int i = 0; i < height; i++) { - DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height"); - DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match"); - - Span span = this.pixelData.GetSpan(); - for (int i = 0; i < height; i++) - { - Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); - int size = PackBitsWriter.PackBits(row, span); - this.Output.Write(span[..size]); - } + Span row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow); + int size = PackBitsWriter.PackBits(row, span); + this.Output.Write(span[..size]); } - - /// - protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); } + + /// + protected override void Dispose(bool disposing) => this.pixelData?.Dispose(); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs index 5c9a707291..1c0a473659 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs @@ -1,128 +1,125 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +/// +/// Pack Bits compression for tiff images. See Tiff Spec v6, section 9. +/// +internal static class PackBitsWriter { - /// - /// Pack Bits compression for tiff images. See Tiff Spec v6, section 9. - /// - internal static class PackBitsWriter + public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan) { - public static int PackBits(ReadOnlySpan rowSpan, Span compressedRowSpan) - { - int maxRunLength = 127; - int posInRowSpan = 0; - int bytesWritten = 0; - int literalRunLength = 0; + int maxRunLength = 127; + int posInRowSpan = 0; + int bytesWritten = 0; + int literalRunLength = 0; - while (posInRowSpan < rowSpan.Length) + while (posInRowSpan < rowSpan.Length) + { + bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan); + if (useReplicateRun) { - bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan); - if (useReplicateRun) - { - if (literalRunLength > 0) - { - WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); - bytesWritten += literalRunLength + 1; - } - - // Write a run with the same bytes. - int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength); - WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten); - - bytesWritten += 2; - literalRunLength = 0; - posInRowSpan += runLength; - continue; - } - - literalRunLength++; - posInRowSpan++; - - if (literalRunLength >= maxRunLength) + if (literalRunLength > 0) { WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); bytesWritten += literalRunLength + 1; - literalRunLength = 0; } + + // Write a run with the same bytes. + int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength); + WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten); + + bytesWritten += 2; + literalRunLength = 0; + posInRowSpan += runLength; + continue; } - if (literalRunLength > 0) + literalRunLength++; + posInRowSpan++; + + if (literalRunLength >= maxRunLength) { WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); bytesWritten += literalRunLength + 1; + literalRunLength = 0; } - - return bytesWritten; } - private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) + if (literalRunLength > 0) { - DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); - - int literalRunStart = end - literalRunLength; - sbyte runLength = (sbyte)(literalRunLength - 1); - compressedRowSpan[compressedRowPos] = (byte)runLength; - rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan[(compressedRowPos + 1)..]); + WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten); + bytesWritten += literalRunLength + 1; } - private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) - { - DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); + return bytesWritten; + } - sbyte headerByte = (sbyte)(-runLength + 1); - compressedRowSpan[compressedRowPos] = (byte)headerByte; - compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; - } + private static void WriteLiteralRun(ReadOnlySpan rowSpan, int end, int literalRunLength, Span compressedRowSpan, int compressedRowPos) + { + DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength)); + + int literalRunStart = end - literalRunLength; + sbyte runLength = (sbyte)(literalRunLength - 1); + compressedRowSpan[compressedRowPos] = (byte)runLength; + rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan[(compressedRowPos + 1)..]); + } + + private static void WriteRun(ReadOnlySpan rowSpan, int start, int runLength, Span compressedRowSpan, int compressedRowPos) + { + DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength)); + + sbyte headerByte = (sbyte)(-runLength + 1); + compressedRowSpan[compressedRowPos] = (byte)headerByte; + compressedRowSpan[compressedRowPos + 1] = rowSpan[start]; + } - private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos) + private static bool IsReplicateRun(ReadOnlySpan rowSpan, int startPos) + { + // We consider run which has at least 3 same consecutive bytes a candidate for a run. + byte startByte = rowSpan[startPos]; + int count = 0; + for (int i = startPos + 1; i < rowSpan.Length; i++) { - // We consider run which has at least 3 same consecutive bytes a candidate for a run. - byte startByte = rowSpan[startPos]; - int count = 0; - for (int i = startPos + 1; i < rowSpan.Length; i++) + if (rowSpan[i] == startByte) { - if (rowSpan[i] == startByte) - { - count++; - if (count >= 2) - { - return true; - } - } - else + count++; + if (count >= 2) { - break; + return true; } } - - return false; + else + { + break; + } } - private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength) + return false; + } + + private static int FindRunLength(ReadOnlySpan rowSpan, int startPos, int maxRunLength) + { + var startByte = rowSpan[startPos]; + int count = 1; + for (int i = startPos + 1; i < rowSpan.Length; i++) { - var startByte = rowSpan[startPos]; - int count = 1; - for (int i = startPos + 1; i < rowSpan.Length; i++) + if (rowSpan[i] == startByte) { - if (rowSpan[i] == startByte) - { - count++; - } - else - { - break; - } - - if (count == maxRunLength) - { - break; - } + count++; + } + else + { + break; } - return count; + if (count == maxRunLength) + { + break; + } } + + return count; } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs index 7c3ecc643e..dc9e1e7296 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs @@ -1,144 +1,141 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +/// +/// Bitwriter for writing compressed CCITT T4 1D data. +/// +internal sealed class T4BitCompressor : TiffCcittCompressor { /// - /// Bitwriter for writing compressed CCITT T4 1D data. + /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. + /// + private readonly bool useModifiedHuffman; + + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed data. + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + /// Indicates if the modified huffman RLE should be used. + public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) + : base(output, allocator, width, bitsPerPixel) => this.useModifiedHuffman = useModifiedHuffman; + + /// + public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; + + /// + /// Writes a image compressed with CCITT T4 to the output buffer. /// - internal sealed class T4BitCompressor : TiffCcittCompressor + /// The pixels as 8-bit gray array. + /// The strip height. + /// The destination for the compressed data. + protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) { - /// - /// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows. - /// - private readonly bool useModifiedHuffman; - - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed data. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// Indicates if the modified huffman RLE should be used. - public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false) - : base(output, allocator, width, bitsPerPixel) => this.useModifiedHuffman = useModifiedHuffman; - - /// - public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax; - - /// - /// Writes a image compressed with CCITT T4 to the output buffer. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) + if (!this.useModifiedHuffman) { - if (!this.useModifiedHuffman) - { - // An EOL code is expected at the start of the data. - this.WriteCode(12, 1, compressedData); - } + // An EOL code is expected at the start of the data. + this.WriteCode(12, 1, compressedData); + } - for (int y = 0; y < height; y++) - { - bool isWhiteRun = true; - bool isStartOrRow = true; - int x = 0; + for (int y = 0; y < height; y++) + { + bool isWhiteRun = true; + bool isStartOrRow = true; + int x = 0; - Span row = pixelsAsGray.Slice(y * this.Width, this.Width); - while (x < this.Width) + Span row = pixelsAsGray.Slice(y * this.Width, this.Width); + while (x < this.Width) + { + uint runLength = 0; + for (int i = x; i < this.Width; i++) { - uint runLength = 0; - for (int i = x; i < this.Width; i++) + if (isWhiteRun && row[i] != 255) { - if (isWhiteRun && row[i] != 255) - { - break; - } - - if (isWhiteRun && row[i] == 255) - { - runLength++; - continue; - } - - if (!isWhiteRun && row[i] != 0) - { - break; - } - - if (!isWhiteRun && row[i] == 0) - { - runLength++; - } + break; } - if (isStartOrRow && runLength == 0) + if (isWhiteRun && row[i] == 255) { - this.WriteCode(8, WhiteZeroRunTermCode, compressedData); - - isWhiteRun = false; - isStartOrRow = false; + runLength++; continue; } - uint code; - uint codeLength; - if (runLength <= 63) + if (!isWhiteRun && row[i] != 0) { - code = GetTermCode(runLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - x += (int)runLength; + break; } - else + + if (!isWhiteRun && row[i] == 0) { - runLength = GetBestFittingMakeupRunLength(runLength); - code = GetMakeupCode(runLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - x += (int)runLength; + runLength++; + } + } + + if (isStartOrRow && runLength == 0) + { + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); + + isWhiteRun = false; + isStartOrRow = false; + continue; + } - // If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero. - if (x == this.Width) + uint code; + uint codeLength; + if (runLength <= 63) + { + code = GetTermCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + } + else + { + runLength = GetBestFittingMakeupRunLength(runLength); + code = GetMakeupCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); + x += (int)runLength; + + // If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero. + if (x == this.Width) + { + if (isWhiteRun) { - if (isWhiteRun) - { - this.WriteCode(8, WhiteZeroRunTermCode, compressedData); - } - else - { - this.WriteCode(10, BlackZeroRunTermCode, compressedData); - } + this.WriteCode(8, WhiteZeroRunTermCode, compressedData); + } + else + { + this.WriteCode(10, BlackZeroRunTermCode, compressedData); } - - continue; } - isStartOrRow = false; - isWhiteRun = !isWhiteRun; + continue; } - this.WriteEndOfLine(compressedData); + isStartOrRow = false; + isWhiteRun = !isWhiteRun; } + + this.WriteEndOfLine(compressedData); } + } - private void WriteEndOfLine(Span compressedData) + private void WriteEndOfLine(Span compressedData) + { + if (this.useModifiedHuffman) { - if (this.useModifiedHuffman) - { - this.PadByte(); - } - else - { - // Write EOL. - this.WriteCode(12, 1, compressedData); - } + this.PadByte(); + } + else + { + // Write EOL. + this.WriteCode(12, 1, compressedData); } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs index 99b4a9fccc..d6bb1e22d9 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs @@ -1,201 +1,198 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +/// +/// Bitwriter for writing compressed CCITT T6 2D data. +/// +internal sealed class T6BitCompressor : TiffCcittCompressor { /// - /// Bitwriter for writing compressed CCITT T6 2D data. + /// Vertical codes from -3 to +3. /// - internal sealed class T6BitCompressor : TiffCcittCompressor + private static readonly (uint Length, uint Code)[] VerticalCodes = { - /// - /// Vertical codes from -3 to +3. - /// - private static readonly (uint Length, uint Code)[] VerticalCodes = - { - (7u, 3u), - (6u, 3u), - (3u, 3u), - (1u, 1u), - (3u, 2u), - (6u, 2u), - (7u, 2u) - }; - - private IMemoryOwner referenceLineBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed data. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) - { - } + (7u, 3u), + (6u, 3u), + (3u, 3u), + (1u, 1u), + (3u, 2u), + (6u, 2u), + (7u, 2u) + }; - /// - public override TiffCompression Method => TiffCompression.CcittGroup4Fax; + private IMemoryOwner referenceLineBuffer; - /// - /// Writes a image compressed with CCITT T6 to the output buffer. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed data. + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + public T6BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + } + + /// + public override TiffCompression Method => TiffCompression.CcittGroup4Fax; + + /// + /// Writes a image compressed with CCITT T6 to the output buffer. + /// + /// The pixels as 8-bit gray array. + /// The strip height. + /// The destination for the compressed data. + protected override void CompressStrip(Span pixelsAsGray, int height, Span compressedData) + { + // Initial reference line is all white. + Span referenceLine = this.referenceLineBuffer.GetSpan(); + referenceLine.Fill(0xff); + + for (int y = 0; y < height; y++) { - // Initial reference line is all white. - Span referenceLine = this.referenceLineBuffer.GetSpan(); - referenceLine.Fill(0xff); + Span row = pixelsAsGray.Slice(y * this.Width, this.Width); + uint a0 = 0; + uint a1 = row[0] == 0 ? 0 : FindRunEnd(row, 0); + uint b1 = referenceLine[0] == 0 ? 0 : FindRunEnd(referenceLine, 0); - for (int y = 0; y < height; y++) + while (true) { - Span row = pixelsAsGray.Slice(y * this.Width, this.Width); - uint a0 = 0; - uint a1 = row[0] == 0 ? 0 : FindRunEnd(row, 0); - uint b1 = referenceLine[0] == 0 ? 0 : FindRunEnd(referenceLine, 0); - - while (true) + uint b2 = FindRunEnd(referenceLine, b1); + if (b2 < a1) { - uint b2 = FindRunEnd(referenceLine, b1); - if (b2 < a1) + // Pass mode. + this.WriteCode(4, 1, compressedData); + a0 = b2; + } + else + { + int d = int.MaxValue; + if ((b1 >= a1) && (b1 - a1 <= 3)) + { + d = (int)(b1 - a1); + } + else if ((b1 < a1) && (a1 - b1 <= 3)) { - // Pass mode. - this.WriteCode(4, 1, compressedData); - a0 = b2; + d = -(int)(a1 - b1); + } + + if (d is >= -3 and <= 3) + { + // Vertical mode. + (uint length, uint code) = VerticalCodes[d + 3]; + this.WriteCode(length, code, compressedData); + a0 = a1; } else { - int d = int.MaxValue; - if ((b1 >= a1) && (b1 - a1 <= 3)) - { - d = (int)(b1 - a1); - } - else if ((b1 < a1) && (a1 - b1 <= 3)) - { - d = -(int)(a1 - b1); - } + // Horizontal mode. + this.WriteCode(3, 1, compressedData); - if (d is >= -3 and <= 3) + uint a2 = FindRunEnd(row, a1); + if ((a0 + a1 == 0) || (row[(int)a0] != 0)) { - // Vertical mode. - (uint length, uint code) = VerticalCodes[d + 3]; - this.WriteCode(length, code, compressedData); - a0 = a1; + this.WriteRun(a1 - a0, true, compressedData); + this.WriteRun(a2 - a1, false, compressedData); } else { - // Horizontal mode. - this.WriteCode(3, 1, compressedData); - - uint a2 = FindRunEnd(row, a1); - if ((a0 + a1 == 0) || (row[(int)a0] != 0)) - { - this.WriteRun(a1 - a0, true, compressedData); - this.WriteRun(a2 - a1, false, compressedData); - } - else - { - this.WriteRun(a1 - a0, false, compressedData); - this.WriteRun(a2 - a1, true, compressedData); - } - - a0 = a2; + this.WriteRun(a1 - a0, false, compressedData); + this.WriteRun(a2 - a1, true, compressedData); } - } - if (a0 >= row.Length) - { - break; + a0 = a2; } + } - byte thisPixel = row[(int)a0]; - a1 = FindRunEnd(row, a0, thisPixel); - b1 = FindRunEnd(referenceLine, a0, (byte)~thisPixel); - b1 = FindRunEnd(referenceLine, b1, thisPixel); + if (a0 >= row.Length) + { + break; } - // This row is now the reference line. - row.CopyTo(referenceLine); + byte thisPixel = row[(int)a0]; + a1 = FindRunEnd(row, a0, thisPixel); + b1 = FindRunEnd(referenceLine, a0, (byte)~thisPixel); + b1 = FindRunEnd(referenceLine, b1, thisPixel); } - this.WriteCode(12, 1, compressedData); - this.WriteCode(12, 1, compressedData); + // This row is now the reference line. + row.CopyTo(referenceLine); } - /// - protected override void Dispose(bool disposing) + this.WriteCode(12, 1, compressedData); + this.WriteCode(12, 1, compressedData); + } + + /// + protected override void Dispose(bool disposing) + { + this.referenceLineBuffer?.Dispose(); + base.Dispose(disposing); + } + + /// + /// Finds the end of a pixel run. + /// + /// The row of pixels to examine. + /// The index of the first pixel in to examine. + /// Color of pixels in the run. If not specified, the color at + /// will be used. + /// The index of the first pixel at or after + /// that does not match , or the length of , + /// whichever comes first. + private static uint FindRunEnd(Span row, uint startIndex, byte? color = null) + { + if (startIndex >= row.Length) { - this.referenceLineBuffer?.Dispose(); - base.Dispose(disposing); + return (uint)row.Length; } - /// - /// Finds the end of a pixel run. - /// - /// The row of pixels to examine. - /// The index of the first pixel in to examine. - /// Color of pixels in the run. If not specified, the color at - /// will be used. - /// The index of the first pixel at or after - /// that does not match , or the length of , - /// whichever comes first. - private static uint FindRunEnd(Span row, uint startIndex, byte? color = null) + byte colorValue = color ?? row[(int)startIndex]; + for (int i = (int)startIndex; i < row.Length; i++) { - if (startIndex >= row.Length) + if (row[i] != colorValue) { - return (uint)row.Length; + return (uint)i; } - - byte colorValue = color ?? row[(int)startIndex]; - for (int i = (int)startIndex; i < row.Length; i++) - { - if (row[i] != colorValue) - { - return (uint)i; - } - } - - return (uint)row.Length; } - /// - public override void Initialize(int rowsPerStrip) - { - base.Initialize(rowsPerStrip); - this.referenceLineBuffer = this.Allocator.Allocate(this.Width); - } + return (uint)row.Length; + } - /// - /// Writes a run to the output buffer. - /// - /// The length of the run. - /// If true the run is white pixels, - /// if false the run is black pixels. - /// The destination to write the run to. - private void WriteRun(uint runLength, bool isWhiteRun, Span compressedData) - { - uint code; - uint codeLength; - while (runLength > 63) - { - uint makeupLength = GetBestFittingMakeupRunLength(runLength); - code = GetMakeupCode(makeupLength, out codeLength, isWhiteRun); - this.WriteCode(codeLength, code, compressedData); - runLength -= makeupLength; - } + /// + public override void Initialize(int rowsPerStrip) + { + base.Initialize(rowsPerStrip); + this.referenceLineBuffer = this.Allocator.Allocate(this.Width); + } - code = GetTermCode(runLength, out codeLength, isWhiteRun); + /// + /// Writes a run to the output buffer. + /// + /// The length of the run. + /// If true the run is white pixels, + /// if false the run is black pixels. + /// The destination to write the run to. + private void WriteRun(uint runLength, bool isWhiteRun, Span compressedData) + { + uint code; + uint codeLength; + while (runLength > 63) + { + uint makeupLength = GetBestFittingMakeupRunLength(runLength); + code = GetMakeupCode(makeupLength, out codeLength, isWhiteRun); this.WriteCode(codeLength, code, compressedData); + runLength -= makeupLength; } + + code = GetTermCode(runLength, out codeLength, isWhiteRun); + this.WriteCode(codeLength, code, compressedData); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs index c43f24774b..0c43f92555 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs @@ -1,536 +1,532 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +/// +/// Common functionality for CCITT T4 and T6 Compression +/// +internal abstract class TiffCcittCompressor : TiffBaseCompressor { - /// - /// Common functionality for CCITT T4 and T6 Compression - /// - internal abstract class TiffCcittCompressor : TiffBaseCompressor + protected const uint WhiteZeroRunTermCode = 0x35; + + protected const uint BlackZeroRunTermCode = 0x37; + + private static readonly uint[] MakeupRunLength = { - protected const uint WhiteZeroRunTermCode = 0x35; + 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 + }; - protected const uint BlackZeroRunTermCode = 0x37; + private static readonly Dictionary WhiteLen4TermCodes = new() + { + { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } + }; - private static readonly uint[] MakeupRunLength = - { - 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560 - }; + private static readonly Dictionary WhiteLen5TermCodes = new() + { + { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } + }; - private static readonly Dictionary WhiteLen4TermCodes = new() - { - { 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF } - }; + private static readonly Dictionary WhiteLen6TermCodes = new() + { + { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } + }; - private static readonly Dictionary WhiteLen5TermCodes = new() - { - { 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 } - }; + private static readonly Dictionary WhiteLen7TermCodes = new() + { + { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, + { 27, 0x24 }, { 28, 0x18 } + }; - private static readonly Dictionary WhiteLen6TermCodes = new() - { - { 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B } - }; + private static readonly Dictionary WhiteLen8TermCodes = new() + { + { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, + { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, + { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, + { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, + { 63, 0x34 } + }; + + private static readonly Dictionary BlackLen2TermCodes = new() + { + { 2, 0x3 }, { 3, 0x2 } + }; - private static readonly Dictionary WhiteLen7TermCodes = new() - { - { 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 }, - { 27, 0x24 }, { 28, 0x18 } - }; + private static readonly Dictionary BlackLen3TermCodes = new() + { + { 1, 0x2 }, { 4, 0x3 } + }; - private static readonly Dictionary WhiteLen8TermCodes = new() - { - { 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 }, - { 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D }, - { 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 }, - { 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 }, - { 63, 0x34 } - }; + private static readonly Dictionary BlackLen4TermCodes = new() + { + { 5, 0x3 }, { 6, 0x2 } + }; - private static readonly Dictionary BlackLen2TermCodes = new() - { - { 2, 0x3 }, { 3, 0x2 } - }; + private static readonly Dictionary BlackLen5TermCodes = new() + { + { 7, 0x3 } + }; - private static readonly Dictionary BlackLen3TermCodes = new() - { - { 1, 0x2 }, { 4, 0x3 } - }; + private static readonly Dictionary BlackLen6TermCodes = new() + { + { 8, 0x5 }, { 9, 0x4 } + }; - private static readonly Dictionary BlackLen4TermCodes = new() - { - { 5, 0x3 }, { 6, 0x2 } - }; + private static readonly Dictionary BlackLen7TermCodes = new() + { + { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } + }; - private static readonly Dictionary BlackLen5TermCodes = new() - { - { 7, 0x3 } - }; + private static readonly Dictionary BlackLen8TermCodes = new() + { + { 13, 0x4 }, { 14, 0x7 } + }; - private static readonly Dictionary BlackLen6TermCodes = new() - { - { 8, 0x5 }, { 9, 0x4 } - }; + private static readonly Dictionary BlackLen9TermCodes = new() + { + { 15, 0x18 } + }; - private static readonly Dictionary BlackLen7TermCodes = new() - { - { 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 } - }; + private static readonly Dictionary BlackLen10TermCodes = new() + { + { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } + }; - private static readonly Dictionary BlackLen8TermCodes = new() - { - { 13, 0x4 }, { 14, 0x7 } - }; + private static readonly Dictionary BlackLen11TermCodes = new() + { + { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } + }; - private static readonly Dictionary BlackLen9TermCodes = new() - { - { 15, 0x18 } - }; + private static readonly Dictionary BlackLen12TermCodes = new() + { + { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, + { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, + { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, + { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, + { 62, 0x66 }, { 63, 0x67 } + }; + + private static readonly Dictionary WhiteLen5MakeupCodes = new() + { + { 64, 0x1B }, { 128, 0x12 } + }; - private static readonly Dictionary BlackLen10TermCodes = new() - { - { 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 } - }; + private static readonly Dictionary WhiteLen6MakeupCodes = new() + { + { 192, 0x17 }, { 1664, 0x18 } + }; - private static readonly Dictionary BlackLen11TermCodes = new() - { - { 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 } - }; + private static readonly Dictionary WhiteLen8MakeupCodes = new() + { + { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } + }; - private static readonly Dictionary BlackLen12TermCodes = new() - { - { 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 }, - { 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB }, - { 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 }, - { 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A }, - { 62, 0x66 }, { 63, 0x67 } - }; + private static readonly Dictionary WhiteLen7MakeupCodes = new() + { + { 256, 0x37 } + }; - private static readonly Dictionary WhiteLen5MakeupCodes = new() - { - { 64, 0x1B }, { 128, 0x12 } - }; + private static readonly Dictionary WhiteLen9MakeupCodes = new() + { + { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, + { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, + { 1600, 0x9A }, { 1728, 0x9B } + }; - private static readonly Dictionary WhiteLen6MakeupCodes = new() - { - { 192, 0x17 }, { 1664, 0x18 } - }; + private static readonly Dictionary WhiteLen11MakeupCodes = new() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; - private static readonly Dictionary WhiteLen8MakeupCodes = new() - { - { 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 } - }; + private static readonly Dictionary WhiteLen12MakeupCodes = new() + { + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; - private static readonly Dictionary WhiteLen7MakeupCodes = new() - { - { 256, 0x37 } - }; + private static readonly Dictionary BlackLen10MakeupCodes = new() + { + { 64, 0xF } + }; - private static readonly Dictionary WhiteLen9MakeupCodes = new() - { - { 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 }, - { 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 }, - { 1600, 0x9A }, { 1728, 0x9B } - }; + private static readonly Dictionary BlackLen11MakeupCodes = new() + { + { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } + }; - private static readonly Dictionary WhiteLen11MakeupCodes = new() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; + private static readonly Dictionary BlackLen12MakeupCodes = new() + { + { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, + { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, + { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } + }; - private static readonly Dictionary WhiteLen12MakeupCodes = new() - { - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; + private static readonly Dictionary BlackLen13MakeupCodes = new() + { + { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, + { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, + { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } + }; - private static readonly Dictionary BlackLen10MakeupCodes = new() - { - { 64, 0xF } - }; + private int bytePosition; - private static readonly Dictionary BlackLen11MakeupCodes = new() - { - { 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD } - }; + private byte bitPosition; - private static readonly Dictionary BlackLen12MakeupCodes = new() - { - { 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 }, - { 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C }, - { 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F } - }; + private IMemoryOwner compressedDataBuffer; - private static readonly Dictionary BlackLen13MakeupCodes = new() - { - { 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 }, - { 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 }, - { 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 } - }; + /// + /// Initializes a new instance of the class. + /// + /// The output. + /// The allocator. + /// The width. + /// The bits per pixel. + protected TiffCcittCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) + : base(output, allocator, width, bitsPerPixel) + { + DebugGuard.IsTrue(bitsPerPixel == 1, nameof(bitsPerPixel), "CCITT compression requires one bit per pixel"); + this.bytePosition = 0; + this.bitPosition = 0; + } - private int bytePosition; + private static uint GetWhiteMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; - private byte bitPosition; + if (WhiteLen5MakeupCodes.TryGetValue(runLength, out uint value)) + { + codeLength = 5; + return value; + } - private IMemoryOwner compressedDataBuffer; + if (WhiteLen6MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 6; + return value; + } - /// - /// Initializes a new instance of the class. - /// - /// The output. - /// The allocator. - /// The width. - /// The bits per pixel. - protected TiffCcittCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel) - : base(output, allocator, width, bitsPerPixel) + if (WhiteLen7MakeupCodes.TryGetValue(runLength, out value)) { - DebugGuard.IsTrue(bitsPerPixel == 1, nameof(bitsPerPixel), "CCITT compression requires one bit per pixel"); - this.bytePosition = 0; - this.bitPosition = 0; + codeLength = 7; + return value; } - private static uint GetWhiteMakeupCode(uint runLength, out uint codeLength) + if (WhiteLen8MakeupCodes.TryGetValue(runLength, out value)) { - codeLength = 0; + codeLength = 8; + return value; + } - if (WhiteLen5MakeupCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 5; - return value; - } + if (WhiteLen9MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 9; + return value; + } - if (WhiteLen6MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } + if (WhiteLen11MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 11; + return value; + } - if (WhiteLen7MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } + if (WhiteLen12MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 12; + return value; + } - if (WhiteLen8MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } + return 0; + } - if (WhiteLen9MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 9; - return value; - } + private static uint GetBlackMakeupCode(uint runLength, out uint codeLength) + { + codeLength = 0; - if (WhiteLen11MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 11; - return value; - } + if (BlackLen10MakeupCodes.TryGetValue(runLength, out uint value)) + { + codeLength = 10; + return value; + } - if (WhiteLen12MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } + if (BlackLen11MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 11; + return value; + } - return 0; + if (BlackLen12MakeupCodes.TryGetValue(runLength, out value)) + { + codeLength = 12; + return value; } - private static uint GetBlackMakeupCode(uint runLength, out uint codeLength) + if (BlackLen13MakeupCodes.TryGetValue(runLength, out value)) { - codeLength = 0; + codeLength = 13; + return value; + } - if (BlackLen10MakeupCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 10; - return value; - } + return 0; + } - if (BlackLen11MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 11; - return value; - } + private static uint GetWhiteTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; - if (BlackLen12MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } + if (WhiteLen4TermCodes.TryGetValue(runLength, out uint value)) + { + codeLength = 4; + return value; + } - if (BlackLen13MakeupCodes.TryGetValue(runLength, out value)) - { - codeLength = 13; - return value; - } + if (WhiteLen5TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 5; + return value; + } - return 0; + if (WhiteLen6TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 6; + return value; } - private static uint GetWhiteTermCode(uint runLength, out uint codeLength) + if (WhiteLen7TermCodes.TryGetValue(runLength, out value)) { - codeLength = 0; + codeLength = 7; + return value; + } - if (WhiteLen4TermCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 4; - return value; - } + if (WhiteLen8TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 8; + return value; + } - if (WhiteLen5TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 5; - return value; - } + return 0; + } - if (WhiteLen6TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } + private static uint GetBlackTermCode(uint runLength, out uint codeLength) + { + codeLength = 0; - if (WhiteLen7TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } + if (BlackLen2TermCodes.TryGetValue(runLength, out uint value)) + { + codeLength = 2; + return value; + } - if (WhiteLen8TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } + if (BlackLen3TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 3; + return value; + } - return 0; + if (BlackLen4TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 4; + return value; } - private static uint GetBlackTermCode(uint runLength, out uint codeLength) + if (BlackLen5TermCodes.TryGetValue(runLength, out value)) { - codeLength = 0; + codeLength = 5; + return value; + } - if (BlackLen2TermCodes.TryGetValue(runLength, out uint value)) - { - codeLength = 2; - return value; - } + if (BlackLen6TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 6; + return value; + } - if (BlackLen3TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 3; - return value; - } + if (BlackLen7TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 7; + return value; + } - if (BlackLen4TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 4; - return value; - } + if (BlackLen8TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 8; + return value; + } - if (BlackLen5TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 5; - return value; - } + if (BlackLen9TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 9; + return value; + } - if (BlackLen6TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 6; - return value; - } + if (BlackLen10TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 10; + return value; + } - if (BlackLen7TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 7; - return value; - } + if (BlackLen11TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 11; + return value; + } - if (BlackLen8TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 8; - return value; - } + if (BlackLen12TermCodes.TryGetValue(runLength, out value)) + { + codeLength = 12; + return value; + } - if (BlackLen9TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 9; - return value; - } + return 0; + } - if (BlackLen10TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 10; - return value; - } + /// + /// Gets the best makeup run length for a given run length + /// + /// A run length needing a makeup code + /// The makeup length for . + protected static uint GetBestFittingMakeupRunLength(uint runLength) + { + DebugGuard.MustBeGreaterThanOrEqualTo(runLength, MakeupRunLength[0], nameof(runLength)); - if (BlackLen11TermCodes.TryGetValue(runLength, out value)) + for (int i = 0; i < MakeupRunLength.Length - 1; i++) + { + if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) { - codeLength = 11; - return value; + return MakeupRunLength[i]; } + } - if (BlackLen12TermCodes.TryGetValue(runLength, out value)) - { - codeLength = 12; - return value; - } + return MakeupRunLength[^1]; + } - return 0; + /// + /// Gets the terminating code for a run length. + /// + /// The run length to get the terminating code for. + /// The length of the terminating code. + /// If true, the run is of white pixels. + /// If false the run is of black pixels + /// The terminating code for a run of length + protected static uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) + { + return GetWhiteTermCode(runLength, out codeLength); } - /// - /// Gets the best makeup run length for a given run length - /// - /// A run length needing a makeup code - /// The makeup length for . - protected static uint GetBestFittingMakeupRunLength(uint runLength) + return GetBlackTermCode(runLength, out codeLength); + } + + /// + /// Gets the makeup code for a run length. + /// + /// The run length to get the makeup code for. + /// The length of the makeup code. + /// If true, the run is of white pixels. + /// If false the run is of black pixels + /// The makeup code for a run of length + protected static uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) + { + if (isWhiteRun) { - DebugGuard.MustBeGreaterThanOrEqualTo(runLength, MakeupRunLength[0], nameof(runLength)); + return GetWhiteMakeupCode(runLength, out codeLength); + } - for (int i = 0; i < MakeupRunLength.Length - 1; i++) - { - if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength) - { - return MakeupRunLength[i]; - } - } + return GetBlackMakeupCode(runLength, out codeLength); + } - return MakeupRunLength[^1]; + /// + /// Pads output to the next byte. + /// + /// + /// If the output is not currently on a byte boundary, + /// zero-pad it to the next byte. + /// + protected void PadByte() + { + // Check if padding is necessary. + if (Numerics.Modulo8(this.bitPosition) != 0) + { + // Skip padding bits, move to next byte. + this.bytePosition++; + this.bitPosition = 0; } + } - /// - /// Gets the terminating code for a run length. - /// - /// The run length to get the terminating code for. - /// The length of the terminating code. - /// If true, the run is of white pixels. - /// If false the run is of black pixels - /// The terminating code for a run of length - protected static uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun) + /// + /// Writes a code to the output. + /// + /// The length of the code to write. + /// The code to be written. + /// The destination buffer to write the code to. + protected void WriteCode(uint codeLength, uint code, Span compressedData) + { + while (codeLength > 0) { - if (isWhiteRun) + int bitNumber = (int)codeLength; + bool bit = (code & (1 << (bitNumber - 1))) != 0; + if (bit) { - return GetWhiteTermCode(runLength, out codeLength); + BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); } - - return GetBlackTermCode(runLength, out codeLength); - } - - /// - /// Gets the makeup code for a run length. - /// - /// The run length to get the makeup code for. - /// The length of the makeup code. - /// If true, the run is of white pixels. - /// If false the run is of black pixels - /// The makeup code for a run of length - protected static uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun) - { - if (isWhiteRun) + else { - return GetWhiteMakeupCode(runLength, out codeLength); + BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); } - return GetBlackMakeupCode(runLength, out codeLength); - } - - /// - /// Pads output to the next byte. - /// - /// - /// If the output is not currently on a byte boundary, - /// zero-pad it to the next byte. - /// - protected void PadByte() - { - // Check if padding is necessary. - if (Numerics.Modulo8(this.bitPosition) != 0) + this.bitPosition++; + if (this.bitPosition == 8) { - // Skip padding bits, move to next byte. this.bytePosition++; this.bitPosition = 0; } - } - /// - /// Writes a code to the output. - /// - /// The length of the code to write. - /// The code to be written. - /// The destination buffer to write the code to. - protected void WriteCode(uint codeLength, uint code, Span compressedData) - { - while (codeLength > 0) - { - int bitNumber = (int)codeLength; - bool bit = (code & (1 << (bitNumber - 1))) != 0; - if (bit) - { - BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition); - } - else - { - BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition); - } - - this.bitPosition++; - if (this.bitPosition == 8) - { - this.bytePosition++; - this.bitPosition = 0; - } - - codeLength--; - } + codeLength--; } + } - /// - /// Writes a image compressed with CCITT T6 to the stream. - /// - /// The pixels as 8-bit gray array. - /// The strip height. - public override void CompressStrip(Span rows, int height) - { - DebugGuard.IsTrue(rows.Length / height == this.Width, "Values must be equals"); - DebugGuard.IsTrue(rows.Length % height == 0, "Values must be equals"); + /// + /// Writes a image compressed with CCITT T6 to the stream. + /// + /// The pixels as 8-bit gray array. + /// The strip height. + public override void CompressStrip(Span rows, int height) + { + DebugGuard.IsTrue(rows.Length / height == this.Width, "Values must be equals"); + DebugGuard.IsTrue(rows.Length % height == 0, "Values must be equals"); - this.compressedDataBuffer.Clear(); - Span compressedData = this.compressedDataBuffer.GetSpan(); + this.compressedDataBuffer.Clear(); + Span compressedData = this.compressedDataBuffer.GetSpan(); - this.bytePosition = 0; - this.bitPosition = 0; + this.bytePosition = 0; + this.bitPosition = 0; - this.CompressStrip(rows, height, compressedData); + this.CompressStrip(rows, height, compressedData); - // Write the compressed data to the stream. - int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; - this.Output.Write(compressedData[..bytesToWrite]); - } + // Write the compressed data to the stream. + int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition; + this.Output.Write(compressedData[..bytesToWrite]); + } - /// - /// Compress a data strip - /// - /// The pixels as 8-bit gray array. - /// The strip height. - /// The destination for the compressed data. - protected abstract void CompressStrip(Span pixelsAsGray, int height, Span compressedData); + /// + /// Compress a data strip + /// + /// The pixels as 8-bit gray array. + /// The strip height. + /// The destination for the compressed data. + protected abstract void CompressStrip(Span pixelsAsGray, int height, Span compressedData); - /// - protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); + /// + protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose(); - /// - public override void Initialize(int rowsPerStrip) - { - // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. - int maxNeededBytes = this.Width * rowsPerStrip; - this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); - } + /// + public override void Initialize(int rowsPerStrip) + { + // This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. + int maxNeededBytes = this.Width * rowsPerStrip; + this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes); } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs index 2d20553b3c..9096271fe5 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs @@ -1,49 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +internal class TiffJpegCompressor : TiffBaseCompressor { - internal class TiffJpegCompressor : TiffBaseCompressor + public TiffJpegCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(output, memoryAllocator, width, bitsPerPixel, predictor) { - public TiffJpegCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(output, memoryAllocator, width, bitsPerPixel, predictor) - { - } + } - /// - public override TiffCompression Method => TiffCompression.Jpeg; + /// + public override TiffCompression Method => TiffCompression.Jpeg; - /// - public override void Initialize(int rowsPerStrip) - { - } + /// + public override void Initialize(int rowsPerStrip) + { + } - /// - public override void CompressStrip(Span rows, int height) - { - int pixelCount = rows.Length / 3; - int width = pixelCount / height; - - using var memoryStream = new MemoryStream(); - var image = Image.LoadPixelData(rows, width, height); - image.Save(memoryStream, new JpegEncoder() - { - ColorType = JpegEncodingColor.Rgb - }); - memoryStream.Position = 0; - memoryStream.WriteTo(this.Output); - } - - /// - protected override void Dispose(bool disposing) + /// + public override void CompressStrip(Span rows, int height) + { + int pixelCount = rows.Length / 3; + int width = pixelCount / height; + + using var memoryStream = new MemoryStream(); + var image = Image.LoadPixelData(rows, width, height); + image.Save(memoryStream, new JpegEncoder() { - } + ColorType = JpegEncodingColor.Rgb + }); + memoryStream.Position = 0; + memoryStream.WriteTo(this.Output); + } + + /// + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs index 69c1f01d3c..a5b90e659d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs @@ -1,270 +1,267 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; + +/* + This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/// +/// Encodes and compresses the image data using dynamic Lempel-Ziv compression. +/// +/// +/// +/// This code is based on the used for GIF encoding. There is potential +/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW +/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is +/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial +/// byte indicating the length of the sub-block. In TIFF the data is written as a single block +/// with no length indicator (this can be determined from the 'StripByteCounts' entry). +/// +/// +internal sealed class TiffLzwEncoder : IDisposable { - /* - This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys - - Original licence: - - BSD 3-Clause License - - * Copyright (c) 2015, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - ** Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + // Clear: Re-initialize tables. + private static readonly int ClearCode = 256; - /// - /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. - /// - /// - /// - /// This code is based on the used for GIF encoding. There is potential - /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW - /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is - /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial - /// byte indicating the length of the sub-block. In TIFF the data is written as a single block - /// with no length indicator (this can be determined from the 'StripByteCounts' entry). - /// - /// - internal sealed class TiffLzwEncoder : IDisposable - { - // Clear: Re-initialize tables. - private static readonly int ClearCode = 256; + // End of Information. + private static readonly int EoiCode = 257; + + private static readonly int MinBits = 9; + private static readonly int MaxBits = 12; + + private static readonly int TableSize = 1 << MaxBits; - // End of Information. - private static readonly int EoiCode = 257; + // A child is made up of a parent (or prefix) code plus a suffix byte + // and siblings are strings with a common parent(or prefix) and different suffix bytes. + private readonly IMemoryOwner children; - private static readonly int MinBits = 9; - private static readonly int MaxBits = 12; + private readonly IMemoryOwner siblings; - private static readonly int TableSize = 1 << MaxBits; + private readonly IMemoryOwner suffixes; - // A child is made up of a parent (or prefix) code plus a suffix byte - // and siblings are strings with a common parent(or prefix) and different suffix bytes. - private readonly IMemoryOwner children; + // Initial setup + private int parent; + private int bitsPerCode; + private int nextValidCode; + private int maxCode; - private readonly IMemoryOwner siblings; + // Buffer for partial codes + private int bits; + private int bitPos; + private int bufferPosition; - private readonly IMemoryOwner suffixes; + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + public TiffLzwEncoder(MemoryAllocator memoryAllocator) + { + this.children = memoryAllocator.Allocate(TableSize); + this.siblings = memoryAllocator.Allocate(TableSize); + this.suffixes = memoryAllocator.Allocate(TableSize); + } - // Initial setup - private int parent; - private int bitsPerCode; - private int nextValidCode; - private int maxCode; + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The data to compress. + /// The stream to write to. + public void Encode(Span data, Stream stream) + { + this.Reset(); - // Buffer for partial codes - private int bits; - private int bitPos; - private int bufferPosition; + Span childrenSpan = this.children.GetSpan(); + Span suffixesSpan = this.suffixes.GetSpan(); + Span siblingsSpan = this.siblings.GetSpan(); + int length = data.Length; - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - public TiffLzwEncoder(MemoryAllocator memoryAllocator) + if (length == 0) { - this.children = memoryAllocator.Allocate(TableSize); - this.siblings = memoryAllocator.Allocate(TableSize); - this.suffixes = memoryAllocator.Allocate(TableSize); + return; } - /// - /// Encodes and compresses the indexed pixels to the stream. - /// - /// The data to compress. - /// The stream to write to. - public void Encode(Span data, Stream stream) + if (this.parent == -1) { - this.Reset(); - - Span childrenSpan = this.children.GetSpan(); - Span suffixesSpan = this.suffixes.GetSpan(); - Span siblingsSpan = this.siblings.GetSpan(); - int length = data.Length; - - if (length == 0) - { - return; - } + // Init stream. + this.WriteCode(stream, ClearCode); + this.parent = this.ReadNextByte(data); + } - if (this.parent == -1) - { - // Init stream. - this.WriteCode(stream, ClearCode); - this.parent = this.ReadNextByte(data); - } + while (this.bufferPosition < data.Length) + { + int value = this.ReadNextByte(data); + int child = childrenSpan[this.parent]; - while (this.bufferPosition < data.Length) + if (child > 0) { - int value = this.ReadNextByte(data); - int child = childrenSpan[this.parent]; - - if (child > 0) + if (suffixesSpan[child] == value) { - if (suffixesSpan[child] == value) - { - this.parent = child; - } - else - { - int sibling = child; + this.parent = child; + } + else + { + int sibling = child; - while (true) + while (true) + { + if (siblingsSpan[sibling] > 0) { - if (siblingsSpan[sibling] > 0) - { - sibling = siblingsSpan[sibling]; + sibling = siblingsSpan[sibling]; - if (suffixesSpan[sibling] == value) - { - this.parent = sibling; - break; - } - } - else + if (suffixesSpan[sibling] == value) { - siblingsSpan[sibling] = (short)this.nextValidCode; - suffixesSpan[this.nextValidCode] = (short)value; - this.WriteCode(stream, this.parent); - this.parent = value; - this.nextValidCode++; - - this.IncreaseCodeSizeOrResetIfNeeded(stream); - + this.parent = sibling; break; } } - } - } - else - { - childrenSpan[this.parent] = (short)this.nextValidCode; - suffixesSpan[this.nextValidCode] = (short)value; - this.WriteCode(stream, this.parent); - this.parent = value; - this.nextValidCode++; + else + { + siblingsSpan[sibling] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); - this.IncreaseCodeSizeOrResetIfNeeded(stream); + break; + } + } } } - - // Write EOI when we are done. - this.WriteCode(stream, this.parent); - this.WriteCode(stream, EoiCode); - - // Flush partial codes by writing 0 pad. - if (this.bitPos > 0) + else { - this.WriteCode(stream, 0); + childrenSpan[this.parent] = (short)this.nextValidCode; + suffixesSpan[this.nextValidCode] = (short)value; + this.WriteCode(stream, this.parent); + this.parent = value; + this.nextValidCode++; + + this.IncreaseCodeSizeOrResetIfNeeded(stream); } } - /// - public void Dispose() - { - this.children.Dispose(); - this.siblings.Dispose(); - this.suffixes.Dispose(); - } + // Write EOI when we are done. + this.WriteCode(stream, this.parent); + this.WriteCode(stream, EoiCode); - private void Reset() + // Flush partial codes by writing 0 pad. + if (this.bitPos > 0) { - this.children.Clear(); - this.siblings.Clear(); - this.suffixes.Clear(); - - this.parent = -1; - this.bitsPerCode = MinBits; - this.nextValidCode = EoiCode + 1; - this.maxCode = (1 << this.bitsPerCode) - 1; - - this.bits = 0; - this.bitPos = 0; - this.bufferPosition = 0; + this.WriteCode(stream, 0); } + } - private byte ReadNextByte(Span data) => data[this.bufferPosition++]; + /// + public void Dispose() + { + this.children.Dispose(); + this.siblings.Dispose(); + this.suffixes.Dispose(); + } - private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) - { - if (this.nextValidCode > this.maxCode) - { - if (this.bitsPerCode == MaxBits) - { - // Reset stream by writing Clear code. - this.WriteCode(stream, ClearCode); + private void Reset() + { + this.children.Clear(); + this.siblings.Clear(); + this.suffixes.Clear(); + + this.parent = -1; + this.bitsPerCode = MinBits; + this.nextValidCode = EoiCode + 1; + this.maxCode = (1 << this.bitsPerCode) - 1; + + this.bits = 0; + this.bitPos = 0; + this.bufferPosition = 0; + } - // Reset tables. - this.ResetTables(); - } - else - { - // Increase code size. - this.bitsPerCode++; - this.maxCode = MaxValue(this.bitsPerCode); - } - } - } + private byte ReadNextByte(Span data) => data[this.bufferPosition++]; - private void WriteCode(Stream stream, int code) + private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) + { + if (this.nextValidCode > this.maxCode) { - this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); - this.bitPos += this.bitsPerCode; + if (this.bitsPerCode == MaxBits) + { + // Reset stream by writing Clear code. + this.WriteCode(stream, ClearCode); - while (this.bitPos >= 8) + // Reset tables. + this.ResetTables(); + } + else { - int b = (this.bits >> (this.bitPos - 8)) & 0xff; - stream.WriteByte((byte)b); - this.bitPos -= 8; + // Increase code size. + this.bitsPerCode++; + this.maxCode = MaxValue(this.bitsPerCode); } - - this.bits &= BitmaskFor(this.bitPos); } + } + + private void WriteCode(Stream stream, int code) + { + this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); + this.bitPos += this.bitsPerCode; - private void ResetTables() + while (this.bitPos >= 8) { - this.children.GetSpan().Clear(); - this.siblings.GetSpan().Clear(); - this.bitsPerCode = MinBits; - this.maxCode = MaxValue(this.bitsPerCode); - this.nextValidCode = EoiCode + 1; + int b = (this.bits >> (this.bitPos - 8)) & 0xff; + stream.WriteByte((byte)b); + this.bitPos -= 8; } - private static int MaxValue(int codeLen) => (1 << codeLen) - 1; + this.bits &= BitmaskFor(this.bitPos); + } - private static int BitmaskFor(int bits) => MaxValue(bits); + private void ResetTables() + { + this.children.GetSpan().Clear(); + this.siblings.GetSpan().Clear(); + this.bitsPerCode = MinBits; + this.maxCode = MaxValue(this.bitsPerCode); + this.nextValidCode = EoiCode + 1; } + + private static int MaxValue(int codeLen) => (1 << codeLen) - 1; + + private static int BitmaskFor(int bits) => MaxValue(bits); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs index 1b66cbf80b..a15c0c09d3 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs @@ -1,156 +1,154 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Represents a reference scan line for CCITT 2D decoding. +/// +internal readonly ref struct CcittReferenceScanline { + private readonly ReadOnlySpan scanLine; + private readonly int width; + private readonly byte whiteByte; + + /// + /// Initializes a new instance of the struct. + /// + /// Indicates, if white is zero, otherwise black is zero. + /// The scan line. + public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan scanLine) + { + this.scanLine = scanLine; + this.width = scanLine.Length; + this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + } + /// - /// Represents a reference scan line for CCITT 2D decoding. + /// Initializes a new instance of the struct. /// - internal readonly ref struct CcittReferenceScanline + /// Indicates, if white is zero, otherwise black is zero. + /// The width of the scanline. + public CcittReferenceScanline(bool whiteIsZero, int width) { - private readonly ReadOnlySpan scanLine; - private readonly int width; - private readonly byte whiteByte; - - /// - /// Initializes a new instance of the struct. - /// - /// Indicates, if white is zero, otherwise black is zero. - /// The scan line. - public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan scanLine) + this.scanLine = default; + this.width = width; + this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + } + + public bool IsEmpty => this.scanLine.IsEmpty; + + /// + /// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0. + /// + /// The reference or starting element om the coding line. + /// Fill byte. + /// Position of b1. + public int FindB1(int a0, byte a0Byte) + { + if (this.IsEmpty) { - this.scanLine = scanLine; - this.width = scanLine.Length; - this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); } - /// - /// Initializes a new instance of the struct. - /// - /// Indicates, if white is zero, otherwise black is zero. - /// The width of the scanline. - public CcittReferenceScanline(bool whiteIsZero, int width) + return this.FindB1ForNormalLine(a0, a0Byte); + } + + /// + /// Finds b2: The next changing element to the right of b1 on the reference line. + /// + /// The first changing element on the reference line to the right of a0 and opposite of color to a0. + /// Position of b1. + public int FindB2(int b1) + { + if (this.IsEmpty) { - this.scanLine = default; - this.width = width; - this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; + return this.FindB2ForImaginaryWhiteLine(); } - public bool IsEmpty => this.scanLine.IsEmpty; + return this.FindB2ForNormalLine(b1); + } - /// - /// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0. - /// - /// The reference or starting element om the coding line. - /// Fill byte. - /// Position of b1. - public int FindB1(int a0, byte a0Byte) + private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) + { + if (a0 < 0) { - if (this.IsEmpty) + if (a0Byte != this.whiteByte) { - return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); + return 0; } - - return this.FindB1ForNormalLine(a0, a0Byte); } - /// - /// Finds b2: The next changing element to the right of b1 on the reference line. - /// - /// The first changing element on the reference line to the right of a0 and opposite of color to a0. - /// Position of b1. - public int FindB2(int b1) + return this.width; + } + + private int FindB1ForNormalLine(int a0, byte a0Byte) + { + int offset = 0; + if (a0 < 0) { - if (this.IsEmpty) + if (a0Byte != this.scanLine[0]) { - return this.FindB2ForImaginaryWhiteLine(); + return 0; } - - return this.FindB2ForNormalLine(b1); } - - private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) + else { - if (a0 < 0) - { - if (a0Byte != this.whiteByte) - { - return 0; - } - } + offset = a0; + } - return this.width; + ReadOnlySpan searchSpace = this.scanLine[offset..]; + byte searchByte = (byte)~a0Byte; + int index = searchSpace.IndexOf(searchByte); + if (index < 0) + { + return this.scanLine.Length; } - private int FindB1ForNormalLine(int a0, byte a0Byte) + if (index != 0) { - int offset = 0; - if (a0 < 0) - { - if (a0Byte != this.scanLine[0]) - { - return 0; - } - } - else - { - offset = a0; - } + return offset + index; + } - ReadOnlySpan searchSpace = this.scanLine[offset..]; - byte searchByte = (byte)~a0Byte; - int index = searchSpace.IndexOf(searchByte); - if (index < 0) - { - return this.scanLine.Length; - } + searchByte = (byte)~searchSpace[0]; + index = searchSpace.IndexOf(searchByte); + if (index < 0) + { + return this.scanLine.Length; + } - if (index != 0) - { - return offset + index; - } + searchSpace = searchSpace[index..]; + offset += index; + index = searchSpace.IndexOf((byte)~searchByte); + if (index < 0) + { + return this.scanLine.Length; + } - searchByte = (byte)~searchSpace[0]; - index = searchSpace.IndexOf(searchByte); - if (index < 0) - { - return this.scanLine.Length; - } + return index + offset; + } - searchSpace = searchSpace[index..]; - offset += index; - index = searchSpace.IndexOf((byte)~searchByte); - if (index < 0) - { - return this.scanLine.Length; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindB2ForImaginaryWhiteLine() => this.width; - return index + offset; + private int FindB2ForNormalLine(int b1) + { + if (b1 >= this.scanLine.Length) + { + return this.scanLine.Length; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int FindB2ForImaginaryWhiteLine() => this.width; - - private int FindB2ForNormalLine(int b1) + byte searchByte = (byte)~this.scanLine[b1]; + int offset = b1 + 1; + ReadOnlySpan searchSpace = this.scanLine[offset..]; + int index = searchSpace.IndexOf(searchByte); + if (index == -1) { - if (b1 >= this.scanLine.Length) - { - return this.scanLine.Length; - } - - byte searchByte = (byte)~this.scanLine[b1]; - int offset = b1 + 1; - ReadOnlySpan searchSpace = this.scanLine[offset..]; - int index = searchSpace.IndexOf(searchByte); - if (index == -1) - { - return this.scanLine.Length; - } - - return offset + index; + return this.scanLine.Length; } + + return offset + index; } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs index 540b537170..4e0d90b12d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs @@ -3,31 +3,30 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +[DebuggerDisplay("Type = {Type}")] +internal readonly struct CcittTwoDimensionalCode { - [DebuggerDisplay("Type = {Type}")] - internal readonly struct CcittTwoDimensionalCode - { - private readonly ushort value; + private readonly ushort value; - /// - /// Initializes a new instance of the struct. - /// - /// The code word. - /// The type of the code. - /// The bits required. - /// The extension bits. - public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) - { - this.Code = code; - this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); - } + /// + /// Initializes a new instance of the struct. + /// + /// The code word. + /// The type of the code. + /// The bits required. + /// The extension bits. + public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) + { + this.Code = code; + this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); + } - /// - /// Gets the code type. - /// - public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); + /// + /// Gets the code type. + /// + public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); - public int Code { get; } - } + public int Code { get; } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs index 815415d6b7..187206c677 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs @@ -1,73 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Enum for the different two dimensional code words for the ccitt fax compression. +/// +internal enum CcittTwoDimensionalCodeType { /// - /// Enum for the different two dimensional code words for the ccitt fax compression. + /// No valid code word was read. /// - internal enum CcittTwoDimensionalCodeType - { - /// - /// No valid code word was read. - /// - None = 0, + None = 0, - /// - /// Pass mode: This mode is identified when the position of b2 lies to the left of a1. - /// - Pass = 1, + /// + /// Pass mode: This mode is identified when the position of b2 lies to the left of a1. + /// + Pass = 1, - /// - /// Indicates horizontal mode. - /// - Horizontal = 2, + /// + /// Indicates horizontal mode. + /// + Horizontal = 2, - /// - /// Vertical 0 code word: relative distance between a1 and b1 is 0. - /// - Vertical0 = 3, + /// + /// Vertical 0 code word: relative distance between a1 and b1 is 0. + /// + Vertical0 = 3, - /// - /// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1. - /// - VerticalR1 = 4, + /// + /// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1. + /// + VerticalR1 = 4, - /// - /// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1. - /// - VerticalR2 = 5, + /// + /// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1. + /// + VerticalR2 = 5, - /// - /// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1. - /// - VerticalR3 = 6, + /// + /// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1. + /// + VerticalR3 = 6, - /// - /// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1. - /// - VerticalL1 = 7, + /// + /// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1. + /// + VerticalL1 = 7, - /// - /// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1. - /// - VerticalL2 = 8, + /// + /// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1. + /// + VerticalL2 = 8, - /// - /// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1. - /// - VerticalL3 = 9, + /// + /// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1. + /// + VerticalL3 = 9, - /// - /// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. - /// Not supported. - /// - Extensions1D = 10, + /// + /// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. + /// Not supported. + /// + Extensions1D = 10, - /// - /// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. - /// Not supported. - /// - Extensions2D = 11, - } + /// + /// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. + /// Not supported. + /// + Extensions2D = 11, } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs index ae89fce0ee..4c377c8783 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs @@ -1,82 +1,79 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.IO.Compression; -using System.Threading; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using Deflate compression. +/// +/// +/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. +/// +internal sealed class DeflateTiffCompression : TiffBaseDecompressor { + private readonly bool isBigEndian; + + private readonly TiffColorType colorType; + /// - /// Class to handle cases where TIFF image data is compressed using Deflate compression. + /// Initializes a new instance of the class. /// - /// - /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. - /// - internal sealed class DeflateTiffCompression : TiffBaseDecompressor + /// The memoryAllocator to use for buffer allocations. + /// The image width. + /// The bits used per pixel. + /// The color type of the pixel data. + /// The tiff predictor used. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) { - private readonly bool isBigEndian; - - private readonly TiffColorType colorType; + this.colorType = colorType; + this.isBigEndian = isBigEndian; + } - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits used per pixel. - /// The color type of the pixel data. - /// The tiff predictor used. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) - : base(memoryAllocator, width, bitsPerPixel, predictor) + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + long pos = stream.Position; + using (var deframeStream = new ZlibInflateStream( + stream, + () => + { + int left = (int)(byteCount - (stream.Position - pos)); + return left > 0 ? left : 0; + })) { - this.colorType = colorType; - this.isBigEndian = isBigEndian; - } + deframeStream.AllocateNewBytes(byteCount, true); + DeflateStream dataStream = deframeStream.CompressedStream; - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - long pos = stream.Position; - using (var deframeStream = new ZlibInflateStream( - stream, - () => - { - int left = (int)(byteCount - (stream.Position - pos)); - return left > 0 ? left : 0; - })) + int totalRead = 0; + while (totalRead < buffer.Length) { - deframeStream.AllocateNewBytes(byteCount, true); - DeflateStream dataStream = deframeStream.CompressedStream; - - int totalRead = 0; - while (totalRead < buffer.Length) + int bytesRead = dataStream.Read(buffer, totalRead, buffer.Length - totalRead); + if (bytesRead <= 0) { - int bytesRead = dataStream.Read(buffer, totalRead, buffer.Length - totalRead); - if (bytesRead <= 0) - { - break; - } - - totalRead += bytesRead; + break; } - } - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); + totalRead += bytesRead; } } - /// - protected override void Dispose(bool disposing) + if (this.Predictor == TiffPredictor.Horizontal) { + HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs index 2a0b8dfc2c..6d57f1ff50 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/GrayJpegSpectralConverter.cs @@ -5,25 +5,24 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Spectral converter for gray TIFF's which use the JPEG compression. +/// +/// The type of the pixel. +internal sealed class GrayJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel { /// - /// Spectral converter for gray TIFF's which use the JPEG compression. + /// Initializes a new instance of the class. /// - /// The type of the pixel. - internal sealed class GrayJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel + /// The configuration. + public GrayJpegSpectralConverter(Configuration configuration) + : base(configuration) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - public GrayJpegSpectralConverter(Configuration configuration) - : base(configuration) - { - } - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, frame.Precision); } + + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, frame.Precision); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index d2247e2d22..d67a61355d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Tiff.Constants; @@ -11,117 +9,116 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed as a jpeg stream. +/// +internal sealed class JpegTiffCompression : TiffBaseDecompressor { - /// - /// Class to handle cases where TIFF image data is compressed as a jpeg stream. - /// - internal sealed class JpegTiffCompression : TiffBaseDecompressor - { - private readonly JpegDecoderOptions options; + private readonly JpegDecoderOptions options; - private readonly byte[] jpegTables; + private readonly byte[] jpegTables; - private readonly TiffPhotometricInterpretation photometricInterpretation; + private readonly TiffPhotometricInterpretation photometricInterpretation; - /// - /// Initializes a new instance of the class. - /// - /// The specialized jpeg decoder options. - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits per pixel. - /// The JPEG tables containing the quantization and/or Huffman tables. - /// The photometric interpretation. - public JpegTiffCompression( - JpegDecoderOptions options, - MemoryAllocator memoryAllocator, - int width, - int bitsPerPixel, - byte[] jpegTables, - TiffPhotometricInterpretation photometricInterpretation) - : base(memoryAllocator, width, bitsPerPixel) - { - this.options = options; - this.jpegTables = jpegTables; - this.photometricInterpretation = photometricInterpretation; - } + /// + /// Initializes a new instance of the class. + /// + /// The specialized jpeg decoder options. + /// The memoryAllocator to use for buffer allocations. + /// The image width. + /// The bits per pixel. + /// The JPEG tables containing the quantization and/or Huffman tables. + /// The photometric interpretation. + public JpegTiffCompression( + JpegDecoderOptions options, + MemoryAllocator memoryAllocator, + int width, + int bitsPerPixel, + byte[] jpegTables, + TiffPhotometricInterpretation photometricInterpretation) + : base(memoryAllocator, width, bitsPerPixel) + { + this.options = options; + this.jpegTables = jpegTables; + this.photometricInterpretation = photometricInterpretation; + } - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + if (this.jpegTables != null) { - if (this.jpegTables != null) + using var jpegDecoder = new JpegDecoderCore(this.options); + Configuration configuration = this.options.GeneralOptions.Configuration; + switch (this.photometricInterpretation) { - using var jpegDecoder = new JpegDecoderCore(this.options); - Configuration configuration = this.options.GeneralOptions.Configuration; - switch (this.photometricInterpretation) + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: { - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - { - using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); - var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, cancellationToken); - jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); - jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken); - - using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken); - CopyImageBytesToBuffer(buffer, decompressedBuffer); - break; - } + using SpectralConverter spectralConverterGray = new GrayJpegSpectralConverter(configuration); + var scanDecoderGray = new HuffmanScanDecoder(stream, spectralConverterGray, cancellationToken); + jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray); + jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken); - case TiffPhotometricInterpretation.YCbCr: - case TiffPhotometricInterpretation.Rgb: - { - using SpectralConverter spectralConverter = - new TiffJpegSpectralConverter(configuration, this.photometricInterpretation); - var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); - jpegDecoder.LoadTables(this.jpegTables, scanDecoder); - jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken); + using Buffer2D decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken); + CopyImageBytesToBuffer(buffer, decompressedBuffer); + break; + } - using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken); - CopyImageBytesToBuffer(buffer, decompressedBuffer); - break; - } + case TiffPhotometricInterpretation.YCbCr: + case TiffPhotometricInterpretation.Rgb: + { + using SpectralConverter spectralConverter = + new TiffJpegSpectralConverter(configuration, this.photometricInterpretation); + var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); + jpegDecoder.LoadTables(this.jpegTables, scanDecoder); + jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken); - default: - TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported"); - break; + using Buffer2D decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken); + CopyImageBytesToBuffer(buffer, decompressedBuffer); + break; } - } - else - { - using var image = Image.Load(stream); - CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); + + default: + TiffThrowHelper.ThrowNotSupported($"Jpeg compressed tiff with photometric interpretation {this.photometricInterpretation} is not supported"); + break; } } - - private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + else { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); - rgbBytes.CopyTo(buffer[offset..]); - offset += rgbBytes.Length; - } + using var image = Image.Load(stream); + CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); } + } - private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + { + int offset = 0; + for (int y = 0; y < pixelBuffer.Height; y++) { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); - rgbBytes.CopyTo(buffer[offset..]); - offset += rgbBytes.Length; - } + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); + Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); + rgbBytes.CopyTo(buffer[offset..]); + offset += rgbBytes.Length; } + } - /// - protected override void Dispose(bool disposing) + private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + { + int offset = 0; + for (int y = 0; y < pixelBuffer.Height; y++) { + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); + Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); + rgbBytes.CopyTo(buffer[offset..]); + offset += rgbBytes.Length; } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs index 8537c57e16..94cbe0b00c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs @@ -1,95 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +/// +/// Represents a lzw string with a code word and a code length. +/// +public class LzwString { + private static readonly LzwString Empty = new LzwString(0, 0, 0, null); + + private readonly LzwString previous; + private readonly byte value; + /// - /// Represents a lzw string with a code word and a code length. + /// Initializes a new instance of the class. /// - public class LzwString + /// The code word. + public LzwString(byte code) + : this(code, code, 1, null) { - private static readonly LzwString Empty = new LzwString(0, 0, 0, null); + } - private readonly LzwString previous; - private readonly byte value; + private LzwString(byte value, byte firstChar, int length, LzwString previous) + { + this.value = value; + this.FirstChar = firstChar; + this.Length = length; + this.previous = previous; + } - /// - /// Initializes a new instance of the class. - /// - /// The code word. - public LzwString(byte code) - : this(code, code, 1, null) - { - } + /// + /// Gets the code length; + /// + public int Length { get; } - private LzwString(byte value, byte firstChar, int length, LzwString previous) + /// + /// Gets the first character of the codeword. + /// + public byte FirstChar { get; } + + /// + /// Concatenates two code words. + /// + /// The code word to concatenate. + /// A concatenated lzw string. + public LzwString Concatenate(byte other) + { + if (this == Empty) { - this.value = value; - this.FirstChar = firstChar; - this.Length = length; - this.previous = previous; + return new LzwString(other); } - /// - /// Gets the code length; - /// - public int Length { get; } - - /// - /// Gets the first character of the codeword. - /// - public byte FirstChar { get; } + return new LzwString(other, this.FirstChar, this.Length + 1, this); + } - /// - /// Concatenates two code words. - /// - /// The code word to concatenate. - /// A concatenated lzw string. - public LzwString Concatenate(byte other) + /// + /// Writes decoded pixel to buffer at a given position. + /// + /// The buffer to write to. + /// The position to write to. + /// The number of bytes written. + public int WriteTo(Span buffer, int offset) + { + if (this.Length == 0) { - if (this == Empty) - { - return new LzwString(other); - } - - return new LzwString(other, this.FirstChar, this.Length + 1, this); + return 0; } - /// - /// Writes decoded pixel to buffer at a given position. - /// - /// The buffer to write to. - /// The position to write to. - /// The number of bytes written. - public int WriteTo(Span buffer, int offset) + if (this.Length == 1) { - if (this.Length == 0) - { - return 0; - } - - if (this.Length == 1) - { - buffer[offset] = this.value; - return 1; - } - - LzwString e = this; - int endIdx = this.Length - 1; - if (endIdx >= buffer.Length) - { - TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); - } + buffer[offset] = this.value; + return 1; + } - for (int i = endIdx; i >= 0; i--) - { - buffer[offset + i] = e.value; - e = e.previous; - } + LzwString e = this; + int endIdx = this.Length - 1; + if (endIdx >= buffer.Length) + { + TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!"); + } - return this.Length; + for (int i = endIdx; i >= 0; i--) + { + buffer[offset + i] = e.value; + e = e.previous; } + + return this.Length; } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs index a53704abf6..01591e138b 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs @@ -1,55 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using LZW compression. +/// +internal sealed class LzwTiffCompression : TiffBaseDecompressor { + private readonly bool isBigEndian; + + private readonly TiffColorType colorType; + /// - /// Class to handle cases where TIFF image data is compressed using LZW compression. + /// Initializes a new instance of the class. /// - internal sealed class LzwTiffCompression : TiffBaseDecompressor + /// The memoryAllocator to use for buffer allocations. + /// The image width. + /// The bits used per pixel. + /// The color type of the pixel data. + /// The tiff predictor used. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) + : base(memoryAllocator, width, bitsPerPixel, predictor) { - private readonly bool isBigEndian; - - private readonly TiffColorType colorType; - - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The image width. - /// The bits used per pixel. - /// The color type of the pixel data. - /// The tiff predictor used. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - this.colorType = colorType; - this.isBigEndian = isBigEndian; - } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - var decoder = new TiffLzwDecoder(stream); - decoder.DecodePixels(buffer); + this.colorType = colorType; + this.isBigEndian = isBigEndian; + } - if (this.Predictor == TiffPredictor.Horizontal) - { - HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); - } - } + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + var decoder = new TiffLzwDecoder(stream); + decoder.DecodePixels(buffer); - /// - protected override void Dispose(bool disposing) + if (this.Predictor == TiffPredictor.Horizontal) { + HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian); } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs index 1f27ff1cef..5560128dd0 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs @@ -4,67 +4,66 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Bit reader for data encoded with the modified huffman rle method. +/// See TIFF 6.0 specification, section 10. +/// +internal sealed class ModifiedHuffmanBitReader : T4BitReader { /// - /// Bit reader for data encoded with the modified huffman rle method. - /// See TIFF 6.0 specification, section 10. + /// Initializes a new instance of the class. /// - internal sealed class ModifiedHuffmanBitReader : T4BitReader + /// The compressed input stream. + /// The logical order of bits within a byte. + /// The number of bytes to read from the stream. + public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) + : base(input, fillOrder, bytesToRead) { - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) - : base(input, fillOrder, bytesToRead) - { - } + } - /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6; + /// + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6; - /// - public override bool IsEndOfScanLine + /// + public override bool IsEndOfScanLine + { + get { - get + if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) { - if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) - { - return true; - } - - if (this.CurValueBitsRead == 11 && this.Value == 0) - { - // black run. - return true; - } - - return false; + return true; } - } - - /// - public override void StartNewRow() - { - base.StartNewRow(); - int remainder = Numerics.Modulo8(this.BitsRead); - if (remainder != 0) + if (this.CurValueBitsRead == 11 && this.Value == 0) { - // Skip padding bits, move to next byte. - this.AdvancePosition(); + // black run. + return true; } + + return false; } + } - /// - /// No EOL is expected at the start of a run for the modified huffman encoding. - /// - protected override void ReadEolBeforeFirstData() + /// + public override void StartNewRow() + { + base.StartNewRow(); + + int remainder = Numerics.Modulo8(this.BitsRead); + if (remainder != 0) { - // Nothing to do here. + // Skip padding bits, move to next byte. + this.AdvancePosition(); } } + + /// + /// No EOL is expected at the start of a run for the modified huffman encoding. + /// + protected override void ReadEolBeforeFirstData() + { + // Nothing to do here. + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs index c95048b686..d2dbedc9cb 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs @@ -1,104 +1,101 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. +/// +internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor { + private readonly byte whiteValue; + + private readonly byte blackValue; + /// - /// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. + /// Initializes a new instance of the class. /// - internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor + /// The memory allocator. + /// The logical order of bits within a byte. + /// The image width. + /// The number of bits per pixel. + /// The photometric interpretation. + public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) { - private readonly byte whiteValue; - - private readonly byte blackValue; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// The photometric interpretation. - public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) - { - this.FillOrder = fillOrder; - bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.whiteValue = (byte)(isWhiteZero ? 0 : 1); - this.blackValue = (byte)(isWhiteZero ? 1 : 0); - } + this.FillOrder = fillOrder; + bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.whiteValue = (byte)(isWhiteZero ? 0 : 1); + this.blackValue = (byte)(isWhiteZero ? 1 : 0); + } - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } + /// + /// Gets the logical order of bits within a byte. + /// + private TiffFillOrder FillOrder { get; } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount); - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + buffer.Clear(); + nint bitsWritten = 0; + nuint pixelsWritten = 0; + nint rowsWritten = 0; + while (bitReader.HasMoreData) { - var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount); + bitReader.ReadNextRun(); - buffer.Clear(); - nint bitsWritten = 0; - nuint pixelsWritten = 0; - nint rowsWritten = 0; - while (bitReader.HasMoreData) + if (bitReader.RunLength > 0) { - bitReader.ReadNextRun(); - - if (bitReader.RunLength > 0) + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); + } + else { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); - } - else - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); - } - - bitsWritten += (int)bitReader.RunLength; - pixelsWritten += bitReader.RunLength; + BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); } - if (pixelsWritten == (ulong)this.Width) + bitsWritten += (int)bitReader.RunLength; + pixelsWritten += bitReader.RunLength; + } + + if (pixelsWritten == (ulong)this.Width) + { + rowsWritten++; + pixelsWritten = 0; + + // Write padding bits, if necessary. + nint pad = 8 - Numerics.Modulo8(bitsWritten); + if (pad != 8) { - rowsWritten++; - pixelsWritten = 0; - - // Write padding bits, if necessary. - nint pad = 8 - Numerics.Modulo8(bitsWritten); - if (pad != 8) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); - bitsWritten += pad; - } - - if (rowsWritten >= stripHeight) - { - break; - } - - bitReader.StartNewRow(); + BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); + bitsWritten += pad; } - if (pixelsWritten > (ulong)this.Width) + if (rowsWritten >= stripHeight) { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); + break; } + + bitReader.StartNewRow(); } - } - /// - protected override void Dispose(bool disposing) - { + if (pixelsWritten > (ulong)this.Width) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width"); + } } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs index 6bec4e2ce5..0c601e9ddb 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs @@ -1,36 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is not compressed. +/// +internal sealed class NoneTiffCompression : TiffBaseDecompressor { /// - /// Class to handle cases where TIFF image data is not compressed. + /// Initializes a new instance of the class. /// - internal sealed class NoneTiffCompression : TiffBaseDecompressor + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) { - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(memoryAllocator, width, bitsPerPixel) - { - } + } - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); - /// - protected override void Dispose(bool disposing) - { - } + /// + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs index 80abe0e857..0999f934b2 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs @@ -1,88 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using PackBits compression. +/// +internal sealed class PackBitsTiffCompression : TiffBaseDecompressor { + private IMemoryOwner compressedDataMemory; + /// - /// Class to handle cases where TIFF image data is compressed using PackBits compression. + /// Initializes a new instance of the class. /// - internal sealed class PackBitsTiffCompression : TiffBaseDecompressor + /// The memoryAllocator to use for buffer allocations. + /// The width of the image. + /// The number of bits per pixel. + public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) + : base(memoryAllocator, width, bitsPerPixel) { - private IMemoryOwner compressedDataMemory; + } - /// - /// Initializes a new instance of the class. - /// - /// The memoryAllocator to use for buffer allocations. - /// The width of the image. - /// The number of bits per pixel. - public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel) - : base(memoryAllocator, width, bitsPerPixel) + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + if (this.compressedDataMemory == null) { + this.compressedDataMemory = this.Allocator.Allocate(byteCount); } - - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + else if (this.compressedDataMemory.Length() < byteCount) { - if (this.compressedDataMemory == null) - { - this.compressedDataMemory = this.Allocator.Allocate(byteCount); - } - else if (this.compressedDataMemory.Length() < byteCount) - { - this.compressedDataMemory.Dispose(); - this.compressedDataMemory = this.Allocator.Allocate(byteCount); - } + this.compressedDataMemory.Dispose(); + this.compressedDataMemory = this.Allocator.Allocate(byteCount); + } - Span compressedData = this.compressedDataMemory.GetSpan(); + Span compressedData = this.compressedDataMemory.GetSpan(); - stream.Read(compressedData, 0, byteCount); - int compressedOffset = 0; - int decompressedOffset = 0; + stream.Read(compressedData, 0, byteCount); + int compressedOffset = 0; + int decompressedOffset = 0; - while (compressedOffset < byteCount) + while (compressedOffset < byteCount) + { + byte headerByte = compressedData[compressedOffset]; + + if (headerByte <= 127) { - byte headerByte = compressedData[compressedOffset]; + int literalOffset = compressedOffset + 1; + int literalLength = compressedData[compressedOffset] + 1; - if (headerByte <= 127) + if ((literalOffset + literalLength) > compressedData.Length) { - int literalOffset = compressedOffset + 1; - int literalLength = compressedData[compressedOffset] + 1; - - if ((literalOffset + literalLength) > compressedData.Length) - { - TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); - } + TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data."); + } - compressedData.Slice(literalOffset, literalLength).CopyTo(buffer[decompressedOffset..]); + compressedData.Slice(literalOffset, literalLength).CopyTo(buffer[decompressedOffset..]); - compressedOffset += literalLength + 1; - decompressedOffset += literalLength; - } - else if (headerByte == 0x80) - { - compressedOffset += 1; - } - else - { - byte repeatData = compressedData[compressedOffset + 1]; - int repeatLength = 257 - headerByte; + compressedOffset += literalLength + 1; + decompressedOffset += literalLength; + } + else if (headerByte == 0x80) + { + compressedOffset += 1; + } + else + { + byte repeatData = compressedData[compressedOffset + 1]; + int repeatLength = 257 - headerByte; - buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData); + buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData); - compressedOffset += 2; - decompressedOffset += repeatLength; - } + compressedOffset += 2; + decompressedOffset += repeatLength; } } - - /// - protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); } + + /// + protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose(); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index 10ded4db65..d35748a717 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -5,27 +5,26 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Spectral converter for YCbCr TIFF's which use the JPEG compression. +/// The jpeg data should be always treated as RGB color space. +/// +/// The type of the pixel. +internal sealed class RgbJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel { /// - /// Spectral converter for YCbCr TIFF's which use the JPEG compression. - /// The jpeg data should be always treated as RGB color space. + /// Initializes a new instance of the class. + /// This Spectral converter will always convert the pixel data to RGB color. /// - /// The type of the pixel. - internal sealed class RgbJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel + /// The configuration. + public RgbJpegSpectralConverter(Configuration configuration) + : base(configuration) { - /// - /// Initializes a new instance of the class. - /// This Spectral converter will always convert the pixel data to RGB color. - /// - /// The configuration. - public RgbJpegSpectralConverter(Configuration configuration) - : base(configuration) - { - } - - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); } + + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs index 02756d807b..bfc4f9beed 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs @@ -1,872 +1,870 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Bitreader for reading compressed CCITT T4 1D data. +/// +internal class T4BitReader { /// - /// Bitreader for reading compressed CCITT T4 1D data. + /// The logical order of bits within a byte. /// - internal class T4BitReader - { - /// - /// The logical order of bits within a byte. - /// - private readonly TiffFillOrder fillOrder; - - /// - /// Indicates whether its the first line of data which is read from the image. - /// - private bool isFirstScanLine; - - /// - /// Indicates whether we have found a termination code which signals the end of a run. - /// - private bool terminationCodeFound; - - /// - /// We keep track if its the start of the row, because each run is expected to start with a white run. - /// If the image row itself starts with black, a white run of zero is expected. - /// - private bool isStartOfRow; - - /// - /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. - /// - private readonly bool eolPadding; - - /// - /// The minimum code length in bits. - /// - private const int MinCodeLength = 2; - - /// - /// The maximum code length in bits. - /// - private readonly int maxCodeLength = 13; - - private static readonly Dictionary WhiteLen4TermCodes = new() - { - { 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 } - }; + private readonly TiffFillOrder fillOrder; - private static readonly Dictionary WhiteLen5TermCodes = new() - { - { 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 } - }; + /// + /// Indicates whether its the first line of data which is read from the image. + /// + private bool isFirstScanLine; - private static readonly Dictionary WhiteLen6TermCodes = new() - { - { 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 } - }; + /// + /// Indicates whether we have found a termination code which signals the end of a run. + /// + private bool terminationCodeFound; - private static readonly Dictionary WhiteLen7TermCodes = new() - { - { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, - { 0x24, 27 }, { 0x18, 28 } - }; + /// + /// We keep track if its the start of the row, because each run is expected to start with a white run. + /// If the image row itself starts with black, a white run of zero is expected. + /// + private bool isStartOfRow; - private static readonly Dictionary WhiteLen8TermCodes = new() - { - { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, - { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, - { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, - { 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } - }; + /// + /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. + /// + private readonly bool eolPadding; - private static readonly Dictionary BlackLen2TermCodes = new() - { - { 0x3, 2 }, { 0x2, 3 } - }; + /// + /// The minimum code length in bits. + /// + private const int MinCodeLength = 2; - private static readonly Dictionary BlackLen3TermCodes = new() - { - { 0x2, 1 }, { 0x3, 4 } - }; + /// + /// The maximum code length in bits. + /// + private readonly int maxCodeLength = 13; - private static readonly Dictionary BlackLen4TermCodes = new() - { - { 0x3, 5 }, { 0x2, 6 } - }; + private static readonly Dictionary WhiteLen4TermCodes = new() + { + { 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 } + }; - private static readonly Dictionary BlackLen5TermCodes = new() - { - { 0x3, 7 } - }; + private static readonly Dictionary WhiteLen5TermCodes = new() + { + { 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 } + }; - private static readonly Dictionary BlackLen6TermCodes = new() - { - { 0x5, 8 }, { 0x4, 9 } - }; + private static readonly Dictionary WhiteLen6TermCodes = new() + { + { 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 } + }; - private static readonly Dictionary BlackLen7TermCodes = new() - { - { 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 } - }; + private static readonly Dictionary WhiteLen7TermCodes = new() + { + { 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, + { 0x24, 27 }, { 0x18, 28 } + }; - private static readonly Dictionary BlackLen8TermCodes = new() - { - { 0x4, 13 }, { 0x7, 14 } - }; + private static readonly Dictionary WhiteLen8TermCodes = new() + { + { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, + { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, + { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, + { 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } + }; - private static readonly Dictionary BlackLen9TermCodes = new() - { - { 0x18, 15 } - }; + private static readonly Dictionary BlackLen2TermCodes = new() + { + { 0x3, 2 }, { 0x2, 3 } + }; - private static readonly Dictionary BlackLen10TermCodes = new() - { - { 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 } - }; + private static readonly Dictionary BlackLen3TermCodes = new() + { + { 0x2, 1 }, { 0x3, 4 } + }; - private static readonly Dictionary BlackLen11TermCodes = new() - { - { 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 } - }; + private static readonly Dictionary BlackLen4TermCodes = new() + { + { 0x3, 5 }, { 0x2, 6 } + }; - private static readonly Dictionary BlackLen12TermCodes = new() - { - { 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 }, - { 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 }, - { 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 }, - { 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 }, - { 0x66, 62 }, { 0x67, 63 } - }; - - private static readonly Dictionary WhiteLen5MakeupCodes = new() - { - { 0x1B, 64 }, { 0x12, 128 } - }; + private static readonly Dictionary BlackLen5TermCodes = new() + { + { 0x3, 7 } + }; - private static readonly Dictionary WhiteLen6MakeupCodes = new() - { - { 0x17, 192 }, { 0x18, 1664 } - }; + private static readonly Dictionary BlackLen6TermCodes = new() + { + { 0x5, 8 }, { 0x4, 9 } + }; - private static readonly Dictionary WhiteLen8MakeupCodes = new() - { - { 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } - }; + private static readonly Dictionary BlackLen7TermCodes = new() + { + { 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 } + }; - private static readonly Dictionary WhiteLen7MakeupCodes = new() - { - { 0x37, 256 } - }; + private static readonly Dictionary BlackLen8TermCodes = new() + { + { 0x4, 13 }, { 0x7, 14 } + }; - private static readonly Dictionary WhiteLen9MakeupCodes = new() - { - { 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 }, - { 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 }, - { 0x9A, 1600 }, { 0x9B, 1728 } - }; + private static readonly Dictionary BlackLen9TermCodes = new() + { + { 0x18, 15 } + }; - private static readonly Dictionary WhiteLen11MakeupCodes = new() - { - { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } - }; + private static readonly Dictionary BlackLen10TermCodes = new() + { + { 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 } + }; - private static readonly Dictionary WhiteLen12MakeupCodes = new() - { - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, - { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } - }; + private static readonly Dictionary BlackLen11TermCodes = new() + { + { 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 } + }; - private static readonly Dictionary BlackLen10MakeupCodes = new() - { - { 0xF, 64 } - }; + private static readonly Dictionary BlackLen12TermCodes = new() + { + { 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 }, + { 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 }, + { 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 }, + { 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 }, + { 0x66, 62 }, { 0x67, 63 } + }; + + private static readonly Dictionary WhiteLen5MakeupCodes = new() + { + { 0x1B, 64 }, { 0x12, 128 } + }; - private static readonly Dictionary BlackLen11MakeupCodes = new() - { - { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } - }; + private static readonly Dictionary WhiteLen6MakeupCodes = new() + { + { 0x17, 192 }, { 0x18, 1664 } + }; - private static readonly Dictionary BlackLen12MakeupCodes = new() - { - { 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, - { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, - { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } - }; + private static readonly Dictionary WhiteLen8MakeupCodes = new() + { + { 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 } + }; - private static readonly Dictionary BlackLen13MakeupCodes = new() - { - { 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 }, - { 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 }, - { 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 } - }; - - /// - /// The compressed input stream. - /// - private readonly BufferedReadStream stream; - - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. - public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false) - { - this.stream = input; - this.fillOrder = fillOrder; - this.DataLength = bytesToRead; - this.BitsRead = 0; - this.Value = 0; - this.CurValueBitsRead = 0; - this.Position = 0; - this.IsWhiteRun = true; - this.isFirstScanLine = true; - this.isStartOfRow = true; - this.terminationCodeFound = false; - this.RunLength = 0; - this.eolPadding = eolPadding; + private static readonly Dictionary WhiteLen7MakeupCodes = new() + { + { 0x37, 256 } + }; - this.ReadNextByte(); + private static readonly Dictionary WhiteLen9MakeupCodes = new() + { + { 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 }, + { 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 }, + { 0x9A, 1600 }, { 0x9B, 1728 } + }; - if (this.eolPadding) - { - this.maxCodeLength = 24; - } - } + private static readonly Dictionary WhiteLen11MakeupCodes = new() + { + { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } + }; - /// - /// Gets or sets the byte at the given position. - /// - private byte DataAtPosition { get; set; } - - /// - /// Gets the current value. - /// - protected uint Value { get; private set; } - - /// - /// Gets the number of bits read for the current run value. - /// - protected int CurValueBitsRead { get; private set; } - - /// - /// Gets the number of bits read. - /// - protected int BitsRead { get; private set; } - - /// - /// Gets the available data in bytes. - /// - protected int DataLength { get; } - - /// - /// Gets or sets the byte position in the buffer. - /// - protected ulong Position { get; set; } - - /// - /// Gets a value indicating whether there is more data to read left. - /// - public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1; - - /// - /// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. - /// - public bool IsWhiteRun { get; protected set; } - - /// - /// Gets the number of pixels in the current run. - /// - public uint RunLength { get; private set; } - - /// - /// Gets a value indicating whether the end of a pixel row has been reached. - /// - public virtual bool IsEndOfScanLine - { - get - { - if (this.eolPadding) - { - return this.CurValueBitsRead >= 12 && this.Value == 1; - } + private static readonly Dictionary WhiteLen12MakeupCodes = new() + { + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + }; - return this.CurValueBitsRead == 12 && this.Value == 1; - } - } + private static readonly Dictionary BlackLen10MakeupCodes = new() + { + { 0xF, 64 } + }; - /// - /// Read the next run of pixels. - /// - public void ReadNextRun() - { - if (this.terminationCodeFound) - { - this.IsWhiteRun = !this.IsWhiteRun; - this.terminationCodeFound = false; - } + private static readonly Dictionary BlackLen11MakeupCodes = new() + { + { 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 } + }; - // Initialize for next run. - this.Reset(); + private static readonly Dictionary BlackLen12MakeupCodes = new() + { + { 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, + { 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 }, + { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 } + }; - // We expect an EOL before the first data. - this.ReadEolBeforeFirstData(); + private static readonly Dictionary BlackLen13MakeupCodes = new() + { + { 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 }, + { 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 }, + { 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 } + }; - // A code word must have at least 2 bits. - this.Value = this.ReadValue(MinCodeLength); + /// + /// The compressed input stream. + /// + private readonly BufferedReadStream stream; - do - { - if (this.CurValueBitsRead > this.maxCodeLength) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); - } + /// + /// Initializes a new instance of the class. + /// + /// The compressed input stream. + /// The logical order of bits within a byte. + /// The number of bytes to read from the stream. + /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. + public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false) + { + this.stream = input; + this.fillOrder = fillOrder; + this.DataLength = bytesToRead; + this.BitsRead = 0; + this.Value = 0; + this.CurValueBitsRead = 0; + this.Position = 0; + this.IsWhiteRun = true; + this.isFirstScanLine = true; + this.isStartOfRow = true; + this.terminationCodeFound = false; + this.RunLength = 0; + this.eolPadding = eolPadding; + + this.ReadNextByte(); + + if (this.eolPadding) + { + this.maxCodeLength = 24; + } + } - bool isMakeupCode = this.IsMakeupCode(); - if (isMakeupCode) - { - if (this.IsWhiteRun) - { - this.RunLength += this.WhiteMakeupCodeRunLength(); - } - else - { - this.RunLength += this.BlackMakeupCodeRunLength(); - } + /// + /// Gets or sets the byte at the given position. + /// + private byte DataAtPosition { get; set; } - this.isStartOfRow = false; - this.Reset(resetRunLength: false); - continue; - } + /// + /// Gets the current value. + /// + protected uint Value { get; private set; } - bool isTerminatingCode = this.IsTerminatingCode(); - if (isTerminatingCode) - { - // Each line starts with a white run. If the image starts with black, a white run with length zero is written. - if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) - { - this.Reset(); - this.isStartOfRow = false; - this.terminationCodeFound = true; - this.RunLength = 0; - break; - } - - if (this.IsWhiteRun) - { - this.RunLength += this.WhiteTerminatingCodeRunLength(); - } - else - { - this.RunLength += this.BlackTerminatingCodeRunLength(); - } + /// + /// Gets the number of bits read for the current run value. + /// + protected int CurValueBitsRead { get; private set; } - this.terminationCodeFound = true; - this.isStartOfRow = false; - break; - } + /// + /// Gets the number of bits read. + /// + protected int BitsRead { get; private set; } - uint currBit = this.ReadValue(1); - this.Value = (this.Value << 1) | currBit; + /// + /// Gets the available data in bytes. + /// + protected int DataLength { get; } - if (this.IsEndOfScanLine) - { - this.StartNewRow(); - } - } - while (!this.IsEndOfScanLine); + /// + /// Gets or sets the byte position in the buffer. + /// + protected ulong Position { get; set; } - this.isFirstScanLine = false; - } + /// + /// Gets a value indicating whether there is more data to read left. + /// + public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1; - /// - /// Initialization for a new row. - /// - public virtual void StartNewRow() - { - // Each new row starts with a white run. - this.IsWhiteRun = true; - this.isStartOfRow = true; - this.terminationCodeFound = false; - } + /// + /// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. + /// + public bool IsWhiteRun { get; protected set; } - /// - /// An EOL is expected before the first data. - /// - protected virtual void ReadEolBeforeFirstData() + /// + /// Gets the number of pixels in the current run. + /// + public uint RunLength { get; private set; } + + /// + /// Gets a value indicating whether the end of a pixel row has been reached. + /// + public virtual bool IsEndOfScanLine + { + get { - if (this.isFirstScanLine) + if (this.eolPadding) { - this.Value = this.ReadValue(this.eolPadding ? 16 : 12); - - if (!this.IsEndOfScanLine) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); - } - - this.Reset(); + return this.CurValueBitsRead >= 12 && this.Value == 1; } + + return this.CurValueBitsRead == 12 && this.Value == 1; } + } - /// - /// Resets the current value read and the number of bits read. - /// - /// if set to true resets also the run length. - protected void Reset(bool resetRunLength = true) + /// + /// Read the next run of pixels. + /// + public void ReadNextRun() + { + if (this.terminationCodeFound) { - this.Value = 0; - this.CurValueBitsRead = 0; - - if (resetRunLength) - { - this.RunLength = 0; - } + this.IsWhiteRun = !this.IsWhiteRun; + this.terminationCodeFound = false; } - /// - /// Resets the bits read to 0. - /// - protected void ResetBitsRead() => this.BitsRead = 0; - - /// - /// Reads the next value. - /// - /// The number of bits to read. - /// The value read. - [MethodImpl(InliningOptions.ShortMethod)] - protected uint ReadValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + // Initialize for next run. + this.Reset(); - uint v = 0; - int shift = nBits; - while (shift-- > 0) - { - uint bit = this.GetBit(); - v |= bit << shift; - this.CurValueBitsRead++; - } + // We expect an EOL before the first data. + this.ReadEolBeforeFirstData(); - return v; - } + // A code word must have at least 2 bits. + this.Value = this.ReadValue(MinCodeLength); - /// - /// Advances the position by one byte. - /// - /// True, if data could be advanced by one byte, otherwise false. - protected bool AdvancePosition() + do { - if (this.LoadNewByte()) + if (this.CurValueBitsRead > this.maxCodeLength) { - return true; + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); } - return false; - } - - private uint WhiteTerminatingCodeRunLength() - { - switch (this.CurValueBitsRead) + bool isMakeupCode = this.IsMakeupCode(); + if (isMakeupCode) { - case 4: + if (this.IsWhiteRun) { - return WhiteLen4TermCodes[this.Value]; + this.RunLength += this.WhiteMakeupCodeRunLength(); } - - case 5: + else { - return WhiteLen5TermCodes[this.Value]; + this.RunLength += this.BlackMakeupCodeRunLength(); } - case 6: + this.isStartOfRow = false; + this.Reset(resetRunLength: false); + continue; + } + + bool isTerminatingCode = this.IsTerminatingCode(); + if (isTerminatingCode) + { + // Each line starts with a white run. If the image starts with black, a white run with length zero is written. + if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) { - return WhiteLen6TermCodes[this.Value]; + this.Reset(); + this.isStartOfRow = false; + this.terminationCodeFound = true; + this.RunLength = 0; + break; } - case 7: + if (this.IsWhiteRun) { - return WhiteLen7TermCodes[this.Value]; + this.RunLength += this.WhiteTerminatingCodeRunLength(); } - - case 8: + else { - return WhiteLen8TermCodes[this.Value]; + this.RunLength += this.BlackTerminatingCodeRunLength(); } + + this.terminationCodeFound = true; + this.isStartOfRow = false; + break; } - return 0; + uint currBit = this.ReadValue(1); + this.Value = (this.Value << 1) | currBit; + + if (this.IsEndOfScanLine) + { + this.StartNewRow(); + } } + while (!this.IsEndOfScanLine); + + this.isFirstScanLine = false; + } - private uint BlackTerminatingCodeRunLength() + /// + /// Initialization for a new row. + /// + public virtual void StartNewRow() + { + // Each new row starts with a white run. + this.IsWhiteRun = true; + this.isStartOfRow = true; + this.terminationCodeFound = false; + } + + /// + /// An EOL is expected before the first data. + /// + protected virtual void ReadEolBeforeFirstData() + { + if (this.isFirstScanLine) { - switch (this.CurValueBitsRead) + this.Value = this.ReadValue(this.eolPadding ? 16 : 12); + + if (!this.IsEndOfScanLine) { - case 2: - { - return BlackLen2TermCodes[this.Value]; - } + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found"); + } - case 3: - { - return BlackLen3TermCodes[this.Value]; - } + this.Reset(); + } + } - case 4: - { - return BlackLen4TermCodes[this.Value]; - } + /// + /// Resets the current value read and the number of bits read. + /// + /// if set to true resets also the run length. + protected void Reset(bool resetRunLength = true) + { + this.Value = 0; + this.CurValueBitsRead = 0; - case 5: - { - return BlackLen5TermCodes[this.Value]; - } + if (resetRunLength) + { + this.RunLength = 0; + } + } - case 6: - { - return BlackLen6TermCodes[this.Value]; - } + /// + /// Resets the bits read to 0. + /// + protected void ResetBitsRead() => this.BitsRead = 0; - case 7: - { - return BlackLen7TermCodes[this.Value]; - } + /// + /// Reads the next value. + /// + /// The number of bits to read. + /// The value read. + [MethodImpl(InliningOptions.ShortMethod)] + protected uint ReadValue(int nBits) + { + DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - case 8: - { - return BlackLen8TermCodes[this.Value]; - } + uint v = 0; + int shift = nBits; + while (shift-- > 0) + { + uint bit = this.GetBit(); + v |= bit << shift; + this.CurValueBitsRead++; + } - case 9: - { - return BlackLen9TermCodes[this.Value]; - } + return v; + } - case 10: - { - return BlackLen10TermCodes[this.Value]; - } + /// + /// Advances the position by one byte. + /// + /// True, if data could be advanced by one byte, otherwise false. + protected bool AdvancePosition() + { + if (this.LoadNewByte()) + { + return true; + } - case 11: - { - return BlackLen11TermCodes[this.Value]; - } + return false; + } - case 12: - { - return BlackLen12TermCodes[this.Value]; - } + private uint WhiteTerminatingCodeRunLength() + { + switch (this.CurValueBitsRead) + { + case 4: + { + return WhiteLen4TermCodes[this.Value]; } - return 0; - } + case 5: + { + return WhiteLen5TermCodes[this.Value]; + } - private uint WhiteMakeupCodeRunLength() - { - switch (this.CurValueBitsRead) + case 6: { - case 5: - { - return WhiteLen5MakeupCodes[this.Value]; - } + return WhiteLen6TermCodes[this.Value]; + } - case 6: - { - return WhiteLen6MakeupCodes[this.Value]; - } + case 7: + { + return WhiteLen7TermCodes[this.Value]; + } - case 7: - { - return WhiteLen7MakeupCodes[this.Value]; - } + case 8: + { + return WhiteLen8TermCodes[this.Value]; + } + } - case 8: - { - return WhiteLen8MakeupCodes[this.Value]; - } + return 0; + } - case 9: - { - return WhiteLen9MakeupCodes[this.Value]; - } + private uint BlackTerminatingCodeRunLength() + { + switch (this.CurValueBitsRead) + { + case 2: + { + return BlackLen2TermCodes[this.Value]; + } - case 11: - { - return WhiteLen11MakeupCodes[this.Value]; - } + case 3: + { + return BlackLen3TermCodes[this.Value]; + } - case 12: - { - return WhiteLen12MakeupCodes[this.Value]; - } + case 4: + { + return BlackLen4TermCodes[this.Value]; } - return 0; - } + case 5: + { + return BlackLen5TermCodes[this.Value]; + } - private uint BlackMakeupCodeRunLength() - { - switch (this.CurValueBitsRead) + case 6: { - case 10: - { - return BlackLen10MakeupCodes[this.Value]; - } + return BlackLen6TermCodes[this.Value]; + } - case 11: - { - return BlackLen11MakeupCodes[this.Value]; - } + case 7: + { + return BlackLen7TermCodes[this.Value]; + } - case 12: - { - return BlackLen12MakeupCodes[this.Value]; - } + case 8: + { + return BlackLen8TermCodes[this.Value]; + } - case 13: - { - return BlackLen13MakeupCodes[this.Value]; - } + case 9: + { + return BlackLen9TermCodes[this.Value]; } - return 0; - } + case 10: + { + return BlackLen10TermCodes[this.Value]; + } - private bool IsMakeupCode() - { - if (this.IsWhiteRun) + case 11: { - return this.IsWhiteMakeupCode(); + return BlackLen11TermCodes[this.Value]; } - return this.IsBlackMakeupCode(); + case 12: + { + return BlackLen12TermCodes[this.Value]; + } } - private bool IsWhiteMakeupCode() + return 0; + } + + private uint WhiteMakeupCodeRunLength() + { + switch (this.CurValueBitsRead) { - switch (this.CurValueBitsRead) + case 5: { - case 5: - { - return WhiteLen5MakeupCodes.ContainsKey(this.Value); - } - - case 6: - { - return WhiteLen6MakeupCodes.ContainsKey(this.Value); - } + return WhiteLen5MakeupCodes[this.Value]; + } - case 7: - { - return WhiteLen7MakeupCodes.ContainsKey(this.Value); - } + case 6: + { + return WhiteLen6MakeupCodes[this.Value]; + } - case 8: - { - return WhiteLen8MakeupCodes.ContainsKey(this.Value); - } + case 7: + { + return WhiteLen7MakeupCodes[this.Value]; + } - case 9: - { - return WhiteLen9MakeupCodes.ContainsKey(this.Value); - } + case 8: + { + return WhiteLen8MakeupCodes[this.Value]; + } - case 11: - { - return WhiteLen11MakeupCodes.ContainsKey(this.Value); - } + case 9: + { + return WhiteLen9MakeupCodes[this.Value]; + } - case 12: - { - return WhiteLen12MakeupCodes.ContainsKey(this.Value); - } + case 11: + { + return WhiteLen11MakeupCodes[this.Value]; } - return false; + case 12: + { + return WhiteLen12MakeupCodes[this.Value]; + } } - private bool IsBlackMakeupCode() + return 0; + } + + private uint BlackMakeupCodeRunLength() + { + switch (this.CurValueBitsRead) { - switch (this.CurValueBitsRead) + case 10: { - case 10: - { - return BlackLen10MakeupCodes.ContainsKey(this.Value); - } + return BlackLen10MakeupCodes[this.Value]; + } - case 11: - { - return BlackLen11MakeupCodes.ContainsKey(this.Value); - } + case 11: + { + return BlackLen11MakeupCodes[this.Value]; + } - case 12: - { - return BlackLen12MakeupCodes.ContainsKey(this.Value); - } + case 12: + { + return BlackLen12MakeupCodes[this.Value]; + } - case 13: - { - return BlackLen13MakeupCodes.ContainsKey(this.Value); - } + case 13: + { + return BlackLen13MakeupCodes[this.Value]; } + } + + return 0; + } - return false; + private bool IsMakeupCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteMakeupCode(); } - private bool IsTerminatingCode() + return this.IsBlackMakeupCode(); + } + + private bool IsWhiteMakeupCode() + { + switch (this.CurValueBitsRead) { - if (this.IsWhiteRun) + case 5: { - return this.IsWhiteTerminatingCode(); + return WhiteLen5MakeupCodes.ContainsKey(this.Value); } - return this.IsBlackTerminatingCode(); - } - - private bool IsWhiteTerminatingCode() - { - switch (this.CurValueBitsRead) + case 6: { - case 4: - { - return WhiteLen4TermCodes.ContainsKey(this.Value); - } + return WhiteLen6MakeupCodes.ContainsKey(this.Value); + } - case 5: - { - return WhiteLen5TermCodes.ContainsKey(this.Value); - } + case 7: + { + return WhiteLen7MakeupCodes.ContainsKey(this.Value); + } - case 6: - { - return WhiteLen6TermCodes.ContainsKey(this.Value); - } + case 8: + { + return WhiteLen8MakeupCodes.ContainsKey(this.Value); + } - case 7: - { - return WhiteLen7TermCodes.ContainsKey(this.Value); - } + case 9: + { + return WhiteLen9MakeupCodes.ContainsKey(this.Value); + } - case 8: - { - return WhiteLen8TermCodes.ContainsKey(this.Value); - } + case 11: + { + return WhiteLen11MakeupCodes.ContainsKey(this.Value); } - return false; + case 12: + { + return WhiteLen12MakeupCodes.ContainsKey(this.Value); + } } - private bool IsBlackTerminatingCode() + return false; + } + + private bool IsBlackMakeupCode() + { + switch (this.CurValueBitsRead) { - switch (this.CurValueBitsRead) + case 10: { - case 2: - { - return BlackLen2TermCodes.ContainsKey(this.Value); - } + return BlackLen10MakeupCodes.ContainsKey(this.Value); + } - case 3: - { - return BlackLen3TermCodes.ContainsKey(this.Value); - } + case 11: + { + return BlackLen11MakeupCodes.ContainsKey(this.Value); + } - case 4: - { - return BlackLen4TermCodes.ContainsKey(this.Value); - } + case 12: + { + return BlackLen12MakeupCodes.ContainsKey(this.Value); + } - case 5: - { - return BlackLen5TermCodes.ContainsKey(this.Value); - } + case 13: + { + return BlackLen13MakeupCodes.ContainsKey(this.Value); + } + } - case 6: - { - return BlackLen6TermCodes.ContainsKey(this.Value); - } + return false; + } - case 7: - { - return BlackLen7TermCodes.ContainsKey(this.Value); - } + private bool IsTerminatingCode() + { + if (this.IsWhiteRun) + { + return this.IsWhiteTerminatingCode(); + } - case 8: - { - return BlackLen8TermCodes.ContainsKey(this.Value); - } + return this.IsBlackTerminatingCode(); + } - case 9: - { - return BlackLen9TermCodes.ContainsKey(this.Value); - } + private bool IsWhiteTerminatingCode() + { + switch (this.CurValueBitsRead) + { + case 4: + { + return WhiteLen4TermCodes.ContainsKey(this.Value); + } - case 10: - { - return BlackLen10TermCodes.ContainsKey(this.Value); - } + case 5: + { + return WhiteLen5TermCodes.ContainsKey(this.Value); + } - case 11: - { - return BlackLen11TermCodes.ContainsKey(this.Value); - } + case 6: + { + return WhiteLen6TermCodes.ContainsKey(this.Value); + } - case 12: - { - return BlackLen12TermCodes.ContainsKey(this.Value); - } + case 7: + { + return WhiteLen7TermCodes.ContainsKey(this.Value); } - return false; + case 8: + { + return WhiteLen8TermCodes.ContainsKey(this.Value); + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint GetBit() + return false; + } + + private bool IsBlackTerminatingCode() + { + switch (this.CurValueBitsRead) { - if (this.BitsRead >= 8) + case 2: { - this.AdvancePosition(); + return BlackLen2TermCodes.ContainsKey(this.Value); } - int shift = 8 - this.BitsRead - 1; - uint bit = (uint)((this.DataAtPosition & (1 << shift)) != 0 ? 1 : 0); - this.BitsRead++; + case 3: + { + return BlackLen3TermCodes.ContainsKey(this.Value); + } - return bit; - } + case 4: + { + return BlackLen4TermCodes.ContainsKey(this.Value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool LoadNewByte() - { - if (this.Position < (ulong)this.DataLength) + case 5: { - this.ReadNextByte(); - this.Position++; - return true; + return BlackLen5TermCodes.ContainsKey(this.Value); } - this.Position++; - this.DataAtPosition = 0; - return false; - } + case 6: + { + return BlackLen6TermCodes.ContainsKey(this.Value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadNextByte() - { - int nextByte = this.stream.ReadByte(); - if (nextByte == -1) + case 7: + { + return BlackLen7TermCodes.ContainsKey(this.Value); + } + + case 8: { - TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data."); + return BlackLen8TermCodes.ContainsKey(this.Value); } - this.ResetBitsRead(); - this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst - ? ReverseBits((byte)nextByte) - : (byte)nextByte; + case 9: + { + return BlackLen9TermCodes.ContainsKey(this.Value); + } + + case 10: + { + return BlackLen10TermCodes.ContainsKey(this.Value); + } + + case 11: + { + return BlackLen11TermCodes.ContainsKey(this.Value); + } + + case 12: + { + return BlackLen12TermCodes.ContainsKey(this.Value); + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint GetBit() + { + if (this.BitsRead >= 8) + { + this.AdvancePosition(); + } + + int shift = 8 - this.BitsRead - 1; + uint bit = (uint)((this.DataAtPosition & (1 << shift)) != 0 ? 1 : 0); + this.BitsRead++; + + return bit; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool LoadNewByte() + { + if (this.Position < (ulong)this.DataLength) + { + this.ReadNextByte(); + this.Position++; + return true; } - // http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte ReverseBits(byte b) => - (byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32); + this.Position++; + this.DataAtPosition = 0; + return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ReadNextByte() + { + int nextByte = this.stream.ReadByte(); + if (nextByte == -1) + { + TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data."); + } + + this.ResetBitsRead(); + this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst + ? ReverseBits((byte)nextByte) + : (byte)nextByte; + } + + // http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ReverseBits(byte b) => + (byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs index e8513c474b..6bdcad2b81 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs @@ -1,128 +1,125 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. +/// +internal sealed class T4TiffCompression : TiffBaseDecompressor { + private readonly FaxCompressionOptions faxCompressionOptions; + + private readonly byte whiteValue; + + private readonly byte blackValue; + + private readonly int width; + /// - /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. + /// Initializes a new instance of the class. /// - internal sealed class T4TiffCompression : TiffBaseDecompressor + /// The memory allocator. + /// The logical order of bits within a byte. + /// The image width. + /// The number of bits per pixel. + /// Fax compression options. + /// The photometric interpretation. + public T4TiffCompression( + MemoryAllocator allocator, + TiffFillOrder fillOrder, + int width, + int bitsPerPixel, + FaxCompressionOptions faxOptions, + TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) { - private readonly FaxCompressionOptions faxCompressionOptions; - - private readonly byte whiteValue; - - private readonly byte blackValue; - - private readonly int width; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// Fax compression options. - /// The photometric interpretation. - public T4TiffCompression( - MemoryAllocator allocator, - TiffFillOrder fillOrder, - int width, - int bitsPerPixel, - FaxCompressionOptions faxOptions, - TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) + this.faxCompressionOptions = faxOptions; + this.FillOrder = fillOrder; + this.width = width; + bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.whiteValue = (byte)(isWhiteZero ? 0 : 1); + this.blackValue = (byte)(isWhiteZero ? 1 : 0); + } + + /// + /// Gets the logical order of bits within a byte. + /// + private TiffFillOrder FillOrder { get; } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) { - this.faxCompressionOptions = faxOptions; - this.FillOrder = fillOrder; - this.width = width; - bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.whiteValue = (byte)(isWhiteZero ? 0 : 1); - this.blackValue = (byte)(isWhiteZero ? 1 : 0); + TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); } - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } + bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); + var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, eolPadding); - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + buffer.Clear(); + nint bitsWritten = 0; + nuint pixelsWritten = 0; + nint rowsWritten = 0; + while (bitReader.HasMoreData) { - if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) + bitReader.ReadNextRun(); + + if (bitReader.RunLength > 0) { - TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); - } + this.WritePixelRun(buffer, bitReader, bitsWritten); - bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); - var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, eolPadding); + bitsWritten += (int)bitReader.RunLength; + pixelsWritten += bitReader.RunLength; + } - buffer.Clear(); - nint bitsWritten = 0; - nuint pixelsWritten = 0; - nint rowsWritten = 0; - while (bitReader.HasMoreData) + if (bitReader.IsEndOfScanLine) { - bitReader.ReadNextRun(); - - if (bitReader.RunLength > 0) + // Write padding bytes, if necessary. + nint pad = 8 - Numerics.Modulo8(bitsWritten); + if (pad != 8) { - this.WritePixelRun(buffer, bitReader, bitsWritten); - - bitsWritten += (int)bitReader.RunLength; - pixelsWritten += bitReader.RunLength; + BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); + bitsWritten += pad; } - if (bitReader.IsEndOfScanLine) + pixelsWritten = 0; + rowsWritten++; + + if (rowsWritten >= stripHeight) { - // Write padding bytes, if necessary. - nint pad = 8 - Numerics.Modulo8(bitsWritten); - if (pad != 8) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0); - bitsWritten += pad; - } - - pixelsWritten = 0; - rowsWritten++; - - if (rowsWritten >= stripHeight) - { - break; - } + break; } } - - // Edge case for when we are at the last byte, but there are still some unwritten pixels left. - if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width) - { - bitReader.ReadNextRun(); - this.WritePixelRun(buffer, bitReader, bitsWritten); - } } - private void WritePixelRun(Span buffer, T4BitReader bitReader, nint bitsWritten) + // Edge case for when we are at the last byte, but there are still some unwritten pixels left. + if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width) { - if (bitReader.IsWhiteRun) - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); - } - else - { - BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); - } + bitReader.ReadNextRun(); + this.WritePixelRun(buffer, bitReader, bitsWritten); } + } - /// - protected override void Dispose(bool disposing) + private void WritePixelRun(Span buffer, T4BitReader bitReader, nint bitsWritten) + { + if (bitReader.IsWhiteRun) + { + BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue); + } + else { + BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue); } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs index a1127b0209..8be0939d3a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs @@ -5,175 +5,174 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Bit reader for reading CCITT T6 compressed fax data. +/// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6 +/// +internal sealed class T6BitReader : T4BitReader { - /// - /// Bit reader for reading CCITT T6 compressed fax data. - /// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6 - /// - internal sealed class T6BitReader : T4BitReader - { - private readonly int maxCodeLength = 12; + private readonly int maxCodeLength = 12; - private static readonly CcittTwoDimensionalCode None = new(0, CcittTwoDimensionalCodeType.None, 0); + private static readonly CcittTwoDimensionalCode None = new(0, CcittTwoDimensionalCodeType.None, 0); - private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1); + private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1); - private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3); - private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3); - private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3); + private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3); + private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3); + private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3); - private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4); + private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4); - private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6); - private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6); + private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6); + private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6); - private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7); - private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7); + private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7); + private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7); + private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7); + private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7); - /// - /// Initializes a new instance of the class. - /// - /// The compressed input stream. - /// The logical order of bits within a byte. - /// The number of bytes to read from the stream. - public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) - : base(input, fillOrder, bytesToRead) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The compressed input stream. + /// The logical order of bits within a byte. + /// The number of bytes to read from the stream. + public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead) + : base(input, fillOrder, bytesToRead) + { + } - /// - public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < (7 - 1); + /// + public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < (7 - 1); - /// - /// Gets or sets the two dimensional code. - /// - public CcittTwoDimensionalCode Code { get; internal set; } + /// + /// Gets or sets the two dimensional code. + /// + public CcittTwoDimensionalCode Code { get; internal set; } - public bool ReadNextCodeWord() - { - this.Code = None; - this.Reset(); - uint value = this.ReadValue(1); + public bool ReadNextCodeWord() + { + this.Code = None; + this.Reset(); + uint value = this.ReadValue(1); - do + do + { + if (this.CurValueBitsRead > this.maxCodeLength) { - if (this.CurValueBitsRead > this.maxCodeLength) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); - } - - switch (this.CurValueBitsRead) - { - case 1: - if (value == Len1Code1.Code) - { - this.Code = Len1Code1; - return false; - } - - break; - - case 3: - if (value == Len3Code001.Code) - { - this.Code = Len3Code001; - return false; - } - - if (value == Len3Code010.Code) - { - this.Code = Len3Code010; - return false; - } - - if (value == Len3Code011.Code) - { - this.Code = Len3Code011; - return false; - } - - break; - - case 4: - if (value == Len4Code0001.Code) - { - this.Code = Len4Code0001; - return false; - } - - break; - - case 6: - if (value == Len6Code000010.Code) - { - this.Code = Len6Code000010; - return false; - } - - if (value == Len6Code000011.Code) - { - this.Code = Len6Code000011; - return false; - } - - break; - - case 7: - if (value == Len7Code0000000.Code) - { - this.Code = Len7Code0000000; - return false; - } - - if (value == Len7Code0000001.Code) - { - this.Code = Len7Code0000001; - return false; - } - - if (value == Len7Code0000011.Code) - { - this.Code = Len7Code0000011; - return false; - } - - if (value == Len7Code0000010.Code) - { - this.Code = Len7Code0000010; - return false; - } - - break; - } - - uint currBit = this.ReadValue(1); - value = (value << 1) | currBit; + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read"); } - while (!this.IsEndOfScanLine); - if (this.IsEndOfScanLine) + switch (this.CurValueBitsRead) { - return true; + case 1: + if (value == Len1Code1.Code) + { + this.Code = Len1Code1; + return false; + } + + break; + + case 3: + if (value == Len3Code001.Code) + { + this.Code = Len3Code001; + return false; + } + + if (value == Len3Code010.Code) + { + this.Code = Len3Code010; + return false; + } + + if (value == Len3Code011.Code) + { + this.Code = Len3Code011; + return false; + } + + break; + + case 4: + if (value == Len4Code0001.Code) + { + this.Code = Len4Code0001; + return false; + } + + break; + + case 6: + if (value == Len6Code000010.Code) + { + this.Code = Len6Code000010; + return false; + } + + if (value == Len6Code000011.Code) + { + this.Code = Len6Code000011; + return false; + } + + break; + + case 7: + if (value == Len7Code0000000.Code) + { + this.Code = Len7Code0000000; + return false; + } + + if (value == Len7Code0000001.Code) + { + this.Code = Len7Code0000001; + return false; + } + + if (value == Len7Code0000011.Code) + { + this.Code = Len7Code0000011; + return false; + } + + if (value == Len7Code0000010.Code) + { + this.Code = Len7Code0000010; + return false; + } + + break; } - return false; + uint currBit = this.ReadValue(1); + value = (value << 1) | currBit; } + while (!this.IsEndOfScanLine); - /// - /// No EOL is expected at the start of a run. - /// - protected override void ReadEolBeforeFirstData() + if (this.IsEndOfScanLine) { - // Nothing to do here. + return true; } - /// - /// Swaps the white run to black run an vise versa. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; + return false; } + + /// + /// No EOL is expected at the start of a run. + /// + protected override void ReadEolBeforeFirstData() + { + // Nothing to do here. + } + + /// + /// Swaps the white run to black run an vise versa. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index 2c10edbea5..eb97272cb8 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -1,276 +1,273 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed using CCITT T6 compression. +/// +internal sealed class T6TiffCompression : TiffBaseDecompressor { + private readonly bool isWhiteZero; + + private readonly int width; + + private readonly byte white; + /// - /// Class to handle cases where TIFF image data is compressed using CCITT T6 compression. + /// Initializes a new instance of the class. /// - internal sealed class T6TiffCompression : TiffBaseDecompressor + /// The memory allocator. + /// The logical order of bits within a byte. + /// The image width. + /// The number of bits per pixel. + /// The photometric interpretation. + public T6TiffCompression( + MemoryAllocator allocator, + TiffFillOrder fillOrder, + int width, + int bitsPerPixel, + TiffPhotometricInterpretation photometricInterpretation) + : base(allocator, width, bitsPerPixel) { - private readonly bool isWhiteZero; - - private readonly int width; - - private readonly byte white; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The logical order of bits within a byte. - /// The image width. - /// The number of bits per pixel. - /// The photometric interpretation. - public T6TiffCompression( - MemoryAllocator allocator, - TiffFillOrder fillOrder, - int width, - int bitsPerPixel, - TiffPhotometricInterpretation photometricInterpretation) - : base(allocator, width, bitsPerPixel) - { - this.FillOrder = fillOrder; - this.width = width; - this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; - this.white = (byte)(this.isWhiteZero ? 0 : 255); - } + this.FillOrder = fillOrder; + this.width = width; + this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; + this.white = (byte)(this.isWhiteZero ? 0 : 255); + } + + /// + /// Gets the logical order of bits within a byte. + /// + private TiffFillOrder FillOrder { get; } + + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + int height = stripHeight; + buffer.Clear(); - /// - /// Gets the logical order of bits within a byte. - /// - private TiffFillOrder FillOrder { get; } + using System.Buffers.IMemoryOwner scanLineBuffer = this.Allocator.Allocate(this.width * 2); + Span scanLine = scanLineBuffer.GetSpan()[..this.width]; + Span referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width); - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + var bitReader = new T6BitReader(stream, this.FillOrder, byteCount); + + var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width); + nint bitsWritten = 0; + for (int y = 0; y < height; y++) { - int height = stripHeight; - buffer.Clear(); + scanLine.Clear(); + Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); - using System.Buffers.IMemoryOwner scanLineBuffer = this.Allocator.Allocate(this.width * 2); - Span scanLine = scanLineBuffer.GetSpan()[..this.width]; - Span referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width); + bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); - var bitReader = new T6BitReader(stream, this.FillOrder, byteCount); + scanLine.CopyTo(referenceScanLineSpan); + referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan); + } + } - var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width); - nint bitsWritten = 0; - for (int y = 0; y < height; y++) + private nint WriteScanLine(Span buffer, Span scanLine, nint bitsWritten) + { + nint bitPos = Numerics.Modulo8(bitsWritten); + nint bufferPos = bitsWritten / 8; + ref byte scanLineRef = ref MemoryMarshal.GetReference(scanLine); + for (nint i = 0; i < scanLine.Length; i++) + { + if (Unsafe.Add(ref scanLineRef, i) != this.white) { - scanLine.Clear(); - Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); + BitWriterUtils.WriteBit(buffer, bufferPos, bitPos); + } - bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); + bitPos++; + bitsWritten++; - scanLine.CopyTo(referenceScanLineSpan); - referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan); + if (bitPos >= 8) + { + bitPos = 0; + bufferPos++; } } - private nint WriteScanLine(Span buffer, Span scanLine, nint bitsWritten) + // Write padding bytes, if necessary. + nint remainder = Numerics.Modulo8(bitsWritten); + if (remainder != 0) + { + nint padding = 8 - remainder; + BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0); + bitsWritten += padding; + } + + return bitsWritten; + } + + private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span scanline) + { + int width = scanline.Length; + bitReader.StartNewRow(); + + // 2D Encoding variables. + int a0 = -1; + byte fillByte = whiteIsZero ? (byte)0 : (byte)255; + + // Process every code word in this scanline. + int unpacked = 0; + while (true) { - nint bitPos = Numerics.Modulo8(bitsWritten); - nint bufferPos = bitsWritten / 8; - ref byte scanLineRef = ref MemoryMarshal.GetReference(scanLine); - for (nint i = 0; i < scanLine.Length; i++) + // Read next code word and advance pass it. + bool isEol = bitReader.ReadNextCodeWord(); + + // Special case handling for EOL. + if (isEol) { - if (Unsafe.Add(ref scanLineRef, i) != this.white) + // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, + // it is appropriate to assume that the missing rows consist entirely of white pixels. + if (whiteIsZero) { - BitWriterUtils.WriteBit(buffer, bufferPos, bitPos); + scanline.Clear(); } - - bitPos++; - bitsWritten++; - - if (bitPos >= 8) + else { - bitPos = 0; - bufferPos++; + scanline.Fill(255); } - } - // Write padding bytes, if necessary. - nint remainder = Numerics.Modulo8(bitsWritten); - if (remainder != 0) - { - nint padding = 8 - remainder; - BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0); - bitsWritten += padding; + break; } - return bitsWritten; - } - - private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span scanline) - { - int width = scanline.Length; - bitReader.StartNewRow(); + // Update 2D Encoding variables. + int b1 = referenceScanline.FindB1(a0, fillByte); - // 2D Encoding variables. - int a0 = -1; - byte fillByte = whiteIsZero ? (byte)0 : (byte)255; - - // Process every code word in this scanline. - int unpacked = 0; - while (true) + // Switch on the code word. + int a1; + switch (bitReader.Code.Type) { - // Read next code word and advance pass it. - bool isEol = bitReader.ReadNextCodeWord(); + case CcittTwoDimensionalCodeType.None: + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); + break; - // Special case handling for EOL. - if (isEol) - { - // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, - // it is appropriate to assume that the missing rows consist entirely of white pixels. - if (whiteIsZero) + case CcittTwoDimensionalCodeType.Pass: + int b2 = referenceScanline.FindB2(b1); + scanline[unpacked..b2].Fill(fillByte); + unpacked = b2; + a0 = b2; + break; + case CcittTwoDimensionalCodeType.Horizontal: + // Decode M(a0a1) + bitReader.ReadNextRun(); + int runLength = (int)bitReader.RunLength; + if (runLength > (uint)(scanline.Length - unpacked)) { - scanline.Clear(); + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); } - else + + scanline.Slice(unpacked, runLength).Fill(fillByte); + unpacked += runLength; + fillByte = (byte)~fillByte; + + // Decode M(a1a2) + bitReader.ReadNextRun(); + runLength = (int)bitReader.RunLength; + if (runLength > (uint)(scanline.Length - unpacked)) { - scanline.Fill(255); + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); } + scanline.Slice(unpacked, runLength).Fill(fillByte); + unpacked += runLength; + fillByte = (byte)~fillByte; + + // Prepare next a0 + a0 = unpacked; break; - } - // Update 2D Encoding variables. - int b1 = referenceScanline.FindB1(a0, fillByte); + case CcittTwoDimensionalCodeType.Vertical0: + a1 = b1; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; - // Switch on the code word. - int a1; - switch (bitReader.Code.Type) - { - case CcittTwoDimensionalCodeType.None: - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word."); - break; - - case CcittTwoDimensionalCodeType.Pass: - int b2 = referenceScanline.FindB2(b1); - scanline[unpacked..b2].Fill(fillByte); - unpacked = b2; - a0 = b2; - break; - case CcittTwoDimensionalCodeType.Horizontal: - // Decode M(a0a1) - bitReader.ReadNextRun(); - int runLength = (int)bitReader.RunLength; - if (runLength > (uint)(scanline.Length - unpacked)) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); - } - - scanline.Slice(unpacked, runLength).Fill(fillByte); - unpacked += runLength; - fillByte = (byte)~fillByte; - - // Decode M(a1a2) - bitReader.ReadNextRun(); - runLength = (int)bitReader.RunLength; - if (runLength > (uint)(scanline.Length - unpacked)) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error"); - } - - scanline.Slice(unpacked, runLength).Fill(fillByte); - unpacked += runLength; - fillByte = (byte)~fillByte; - - // Prepare next a0 - a0 = unpacked; - break; - - case CcittTwoDimensionalCodeType.Vertical0: - a1 = b1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR1: - a1 = b1 + 1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR2: - a1 = b1 + 2; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalR3: - a1 = b1 + 3; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL1: - a1 = b1 - 1; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL2: - a1 = b1 - 2; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - case CcittTwoDimensionalCodeType.VerticalL3: - a1 = b1 - 3; - scanline[unpacked..a1].Fill(fillByte); - unpacked = a1; - a0 = a1; - fillByte = (byte)~fillByte; - bitReader.SwapColor(); - break; - - default: - throw new NotSupportedException("ccitt extensions are not supported."); - } + case CcittTwoDimensionalCodeType.VerticalR1: + a1 = b1 + 1; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; - // This line is fully unpacked. Should exit and process next line. - if (unpacked == width) - { + case CcittTwoDimensionalCodeType.VerticalR2: + a1 = b1 + 2; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); break; - } - if (unpacked > width) - { - TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); - } + case CcittTwoDimensionalCodeType.VerticalR3: + a1 = b1 + 3; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL1: + a1 = b1 - 1; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL2: + a1 = b1 - 2; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + case CcittTwoDimensionalCodeType.VerticalL3: + a1 = b1 - 3; + scanline[unpacked..a1].Fill(fillByte); + unpacked = a1; + a0 = a1; + fillByte = (byte)~fillByte; + bitReader.SwapColor(); + break; + + default: + throw new NotSupportedException("ccitt extensions are not supported."); } - } - /// - protected override void Dispose(bool disposing) - { + // This line is fully unpacked. Should exit and process next line. + if (unpacked == width) + { + break; + } + + if (unpacked > width) + { + TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width"); + } } } + + /// + protected override void Dispose(bool disposing) + { + } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs index 8b892757da..f051aaea1d 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffJpegSpectralConverter{TPixel}.cs @@ -6,45 +6,44 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Spectral converter for YCbCr TIFF's which use the JPEG compression. +/// The jpeg data should be always treated as RGB color space. +/// +/// The type of the pixel. +internal sealed class TiffJpegSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel { + private readonly TiffPhotometricInterpretation photometricInterpretation; + /// - /// Spectral converter for YCbCr TIFF's which use the JPEG compression. - /// The jpeg data should be always treated as RGB color space. + /// Initializes a new instance of the class. + /// This Spectral converter will always convert the pixel data to RGB color. /// - /// The type of the pixel. - internal sealed class TiffJpegSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel - { - private readonly TiffPhotometricInterpretation photometricInterpretation; + /// The configuration. + /// Tiff photometric interpretation. + public TiffJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) + : base(configuration) + => this.photometricInterpretation = photometricInterpretation; - /// - /// Initializes a new instance of the class. - /// This Spectral converter will always convert the pixel data to RGB color. - /// - /// The configuration. - /// Tiff photometric interpretation. - public TiffJpegSpectralConverter(Configuration configuration, TiffPhotometricInterpretation photometricInterpretation) - : base(configuration) - => this.photometricInterpretation = photometricInterpretation; + /// + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) + { + JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation); + return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); + } - /// - protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) + /// + /// This converter must be used only for RGB and YCbCr color spaces for performance reasons. + /// For grayscale images must be used. + /// + private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) + => interpretation switch { - JpegColorSpace colorSpace = GetJpegColorSpaceFromPhotometricInterpretation(this.photometricInterpretation); - return JpegColorConverterBase.GetConverter(colorSpace, frame.Precision); - } - - /// - /// This converter must be used only for RGB and YCbCr color spaces for performance reasons. - /// For grayscale images must be used. - /// - private static JpegColorSpace GetJpegColorSpaceFromPhotometricInterpretation(TiffPhotometricInterpretation interpretation) - => interpretation switch - { - TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB, - TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, - _ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"), - }; - } + TiffPhotometricInterpretation.Rgb => JpegColorSpace.RGB, + TiffPhotometricInterpretation.YCbCr => JpegColorSpace.RGB, + _ => throw new InvalidImageContentException($"Invalid tiff photometric interpretation for jpeg encoding: {interpretation}"), + }; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs index 5c7b0234d6..a53e1bc74c 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs @@ -1,225 +1,234 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; - -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/* + This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys + + Original licence: + + BSD 3-Clause License + + * Copyright (c) 2015, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + ** Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/// +/// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13. +/// +internal sealed class TiffLzwDecoder { - /* - This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys - - Original licence: - - BSD 3-Clause License - - * Copyright (c) 2015, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - ** Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ + /// + /// The stream to decode. + /// + private readonly Stream stream; /// - /// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13. + /// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode. + /// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again. /// - internal sealed class TiffLzwDecoder - { - /// - /// The stream to decode. - /// - private readonly Stream stream; - - /// - /// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode. - /// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again. - /// - private const int ClearCode = 256; - - /// - /// End of Information. - /// - private const int EoiCode = 257; - - /// - /// Minimum code length of 9 bits. - /// - private const int MinBits = 9; - - /// - /// Maximum code length of 12 bits. - /// - private const int MaxBits = 12; - - /// - /// Maximum table size of 4096. - /// - private const int TableSize = 1 << MaxBits; - - private readonly LzwString[] table; - - private int tableLength; - private int bitsPerCode; - private int oldCode = ClearCode; - private int maxCode; - private int bitMask; - private int maxString; - private bool eofReached; - private int nextData; - private int nextBits; - - /// - /// Initializes a new instance of the class - /// and sets the stream, where the compressed data should be read from. - /// - /// The stream to read from. - /// is null. - public TiffLzwDecoder(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); + private const int ClearCode = 256; - this.stream = stream; + /// + /// End of Information. + /// + private const int EoiCode = 257; - // TODO: Investigate a manner by which we can avoid this allocation. - this.table = new LzwString[TableSize]; - for (int i = 0; i < 256; i++) - { - this.table[i] = new LzwString((byte)i); - } + /// + /// Minimum code length of 9 bits. + /// + private const int MinBits = 9; - this.Init(); - } + /// + /// Maximum code length of 12 bits. + /// + private const int MaxBits = 12; + + /// + /// Maximum table size of 4096. + /// + private const int TableSize = 1 << MaxBits; + + private readonly LzwString[] table; + + private int tableLength; + private int bitsPerCode; + private int oldCode = ClearCode; + private int maxCode; + private int bitMask; + private int maxString; + private bool eofReached; + private int nextData; + private int nextBits; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream to read from. + /// is null. + public TiffLzwDecoder(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); - private void Init() + this.stream = stream; + + // TODO: Investigate a manner by which we can avoid this allocation. + this.table = new LzwString[TableSize]; + for (int i = 0; i < 256; i++) { - // Table length is 256 + 2, because of special clear code and end of information code. - this.tableLength = 258; - this.bitsPerCode = MinBits; - this.bitMask = BitmaskFor(this.bitsPerCode); - this.maxCode = this.MaxCode(); - this.maxString = 1; + this.table[i] = new LzwString((byte)i); } - /// - /// Decodes and decompresses all pixel indices from the stream. - /// - /// The pixel array to decode to. - public void DecodePixels(Span pixels) - { - // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. - // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ - int code; - int offset = 0; + this.Init(); + } + + private void Init() + { + // Table length is 256 + 2, because of special clear code and end of information code. + this.tableLength = 258; + this.bitsPerCode = MinBits; + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + this.maxString = 1; + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// The pixel array to decode to. + public void DecodePixels(Span pixels) + { + // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. + // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ + int code; + int offset = 0; - while ((code = this.GetNextCode()) != EoiCode) + while ((code = this.GetNextCode()) != EoiCode) + { + if (code == ClearCode) { - if (code == ClearCode) + this.Init(); + code = this.GetNextCode(); + + if (code == EoiCode) { - this.Init(); - code = this.GetNextCode(); + break; + } - if (code == EoiCode) - { - break; - } + if (this.table[code] == null) + { + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); + } - if (this.table[code] == null) - { - TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})"); - } + offset += this.table[code].WriteTo(pixels, offset); + } + else + { + if (this.table[this.oldCode] == null) + { + TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); + } + if (this.IsInTable(code)) + { offset += this.table[code].WriteTo(pixels, offset); + + this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); } else { - if (this.table[this.oldCode] == null) - { - TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})"); - } - - if (this.IsInTable(code)) - { - offset += this.table[code].WriteTo(pixels, offset); - - this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar)); - } - else - { - LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); - - offset += outString.WriteTo(pixels, offset); - this.AddStringToTable(outString); - } + LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar); + + offset += outString.WriteTo(pixels, offset); + this.AddStringToTable(outString); } + } - this.oldCode = code; + this.oldCode = code; - if (offset >= pixels.Length) - { - break; - } + if (offset >= pixels.Length) + { + break; } } + } - private void AddStringToTable(LzwString lzwString) + private void AddStringToTable(LzwString lzwString) + { + if (this.tableLength > this.table.Length) { - if (this.tableLength > this.table.Length) - { - TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); - } + TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)"); + } + + this.table[this.tableLength++] = lzwString; - this.table[this.tableLength++] = lzwString; + if (this.tableLength > this.maxCode) + { + this.bitsPerCode++; - if (this.tableLength > this.maxCode) + if (this.bitsPerCode > MaxBits) { - this.bitsPerCode++; + // Continue reading MaxBits (12 bit) length codes. + this.bitsPerCode = MaxBits; + } - if (this.bitsPerCode > MaxBits) - { - // Continue reading MaxBits (12 bit) length codes. - this.bitsPerCode = MaxBits; - } + this.bitMask = BitmaskFor(this.bitsPerCode); + this.maxCode = this.MaxCode(); + } - this.bitMask = BitmaskFor(this.bitsPerCode); - this.maxCode = this.MaxCode(); - } + if (lzwString.Length > this.maxString) + { + this.maxString = lzwString.Length; + } + } - if (lzwString.Length > this.maxString) - { - this.maxString = lzwString.Length; - } + private int GetNextCode() + { + if (this.eofReached) + { + return EoiCode; } - private int GetNextCode() + int read = this.stream.ReadByte(); + if (read < 0) { - if (this.eofReached) - { - return EoiCode; - } + this.eofReached = true; + return EoiCode; + } + + this.nextData = (this.nextData << 8) | read; + this.nextBits += 8; - int read = this.stream.ReadByte(); + if (this.nextBits < this.bitsPerCode) + { + read = this.stream.ReadByte(); if (read < 0) { this.eofReached = true; @@ -228,30 +237,17 @@ private int GetNextCode() this.nextData = (this.nextData << 8) | read; this.nextBits += 8; + } - if (this.nextBits < this.bitsPerCode) - { - read = this.stream.ReadByte(); - if (read < 0) - { - this.eofReached = true; - return EoiCode; - } - - this.nextData = (this.nextData << 8) | read; - this.nextBits += 8; - } - - int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; - this.nextBits -= this.bitsPerCode; + int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask; + this.nextBits -= this.bitsPerCode; - return code; - } + return code; + } - private bool IsInTable(int code) => code < this.tableLength; + private bool IsInTable(int code) => code < this.tableLength; - private int MaxCode() => this.bitMask - 1; + private int MaxCode() => this.bitMask - 1; - private static int BitmaskFor(int bits) => (1 << bits) - 1; - } + private static int BitmaskFor(int bits) => (1 << bits) - 1; } diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs index 587d302c64..4e1c9c2f84 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/WebpTiffCompression.cs @@ -1,58 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors +namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; + +/// +/// Class to handle cases where TIFF image data is compressed as a webp stream. +/// +internal class WebpTiffCompression : TiffBaseDecompressor { + private readonly DecoderOptions options; + /// - /// Class to handle cases where TIFF image data is compressed as a webp stream. + /// Initializes a new instance of the class. /// - internal class WebpTiffCompression : TiffBaseDecompressor - { - private readonly DecoderOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The general decoder options. - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// The predictor. - public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(memoryAllocator, width, bitsPerPixel, predictor) - => this.options = options; + /// The general decoder options. + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + /// The predictor. + public WebpTiffCompression(DecoderOptions options, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(memoryAllocator, width, bitsPerPixel, predictor) + => this.options = options; - /// - protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - using Image image = ((IImageDecoder)new WebpDecoder()).Decode(this.options, stream, cancellationToken); - CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); - } + /// + protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + using Image image = ((IImageDecoder)new WebpDecoder()).Decode(this.options, stream, cancellationToken); + CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer); + } - private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + private static void CopyImageBytesToBuffer(Span buffer, Buffer2D pixelBuffer) + { + int offset = 0; + for (int y = 0; y < pixelBuffer.Height; y++) { - int offset = 0; - for (int y = 0; y < pixelBuffer.Height; y++) - { - Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); - Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); - rgbBytes.CopyTo(buffer[offset..]); - offset += rgbBytes.Length; - } + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); + Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); + rgbBytes.CopyTo(buffer[offset..]); + offset += rgbBytes.Length; } + } - /// - protected override void Dispose(bool disposing) - { - } + /// + protected override void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs index 29237cda8c..55784efed2 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs @@ -1,36 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +/// +/// Fax compression options, see TIFF spec page 51f (T4Options). +/// +[Flags] +public enum FaxCompressionOptions : uint { /// - /// Fax compression options, see TIFF spec page 51f (T4Options). + /// No options. /// - [Flags] - public enum FaxCompressionOptions : uint - { - /// - /// No options. - /// - None = 0, + None = 0, - /// - /// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed). - /// - TwoDimensionalCoding = 1, + /// + /// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed). + /// + TwoDimensionalCoding = 1, - /// - /// If set, uncompressed mode is used. - /// - UncompressedMode = 2, + /// + /// If set, uncompressed mode is used. + /// + UncompressedMode = 2, - /// - /// If set, fill bits have been added as necessary before EOL codes such that - /// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte - /// preceded by a zero nibble: xxxx-0000 0000-0001. - /// - EolPadding = 4 - } + /// + /// If set, fill bits have been added as necessary before EOL codes such that + /// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte + /// preceded by a zero nibble: xxxx-0000 0000-0001. + /// + EolPadding = 4 } diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs index 406d6ef743..be1d00e7ed 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,609 +8,608 @@ using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +/// +/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images. +/// +internal static class HorizontalPredictor { /// - /// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images. + /// Inverts the horizontal prediction. /// - internal static class HorizontalPredictor + /// Buffer with decompressed pixel data. + /// The width of the image or strip. + /// The color type of the pixel data. + /// If set to true decodes the pixel data as big endian, otherwise as little endian. + public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian) { - /// - /// Inverts the horizontal prediction. - /// - /// Buffer with decompressed pixel data. - /// The width of the image or strip. - /// The color type of the pixel data. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public static void Undo(Span pixelBytes, int width, TiffColorType colorType, bool isBigEndian) + switch (colorType) { - switch (colorType) - { - case TiffColorType.BlackIsZero8: - case TiffColorType.WhiteIsZero8: - case TiffColorType.PaletteColor: - UndoGray8Bit(pixelBytes, width); - break; - case TiffColorType.BlackIsZero16: - case TiffColorType.WhiteIsZero16: - UndoGray16Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.BlackIsZero32: - case TiffColorType.WhiteIsZero32: - UndoGray32Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgb888: - case TiffColorType.CieLab: - UndoRgb24Bit(pixelBytes, width); - break; - case TiffColorType.Rgba8888: - UndoRgba32Bit(pixelBytes, width); - break; - case TiffColorType.Rgb161616: - UndoRgb48Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgba16161616: - UndoRgba64Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgb323232: - UndoRgb96Bit(pixelBytes, width, isBigEndian); - break; - case TiffColorType.Rgba32323232: - UndoRgba128Bit(pixelBytes, width, isBigEndian); - break; - } + case TiffColorType.BlackIsZero8: + case TiffColorType.WhiteIsZero8: + case TiffColorType.PaletteColor: + UndoGray8Bit(pixelBytes, width); + break; + case TiffColorType.BlackIsZero16: + case TiffColorType.WhiteIsZero16: + UndoGray16Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.BlackIsZero32: + case TiffColorType.WhiteIsZero32: + UndoGray32Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.Rgb888: + case TiffColorType.CieLab: + UndoRgb24Bit(pixelBytes, width); + break; + case TiffColorType.Rgba8888: + UndoRgba32Bit(pixelBytes, width); + break; + case TiffColorType.Rgb161616: + UndoRgb48Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.Rgba16161616: + UndoRgba64Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.Rgb323232: + UndoRgb96Bit(pixelBytes, width, isBigEndian); + break; + case TiffColorType.Rgba32323232: + UndoRgba128Bit(pixelBytes, width, isBigEndian); + break; } + } - public static void ApplyHorizontalPrediction(Span rows, int width, int bitsPerPixel) + public static void ApplyHorizontalPrediction(Span rows, int width, int bitsPerPixel) + { + if (bitsPerPixel == 8) { - if (bitsPerPixel == 8) - { - ApplyHorizontalPrediction8Bit(rows, width); - } - else if (bitsPerPixel == 24) + ApplyHorizontalPrediction8Bit(rows, width); + } + else if (bitsPerPixel == 24) + { + ApplyHorizontalPrediction24Bit(rows, width); + } + } + + /// + /// Applies a horizontal predictor to the rgb row. + /// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next. + /// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus + /// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly. + /// + /// The rgb pixel rows. + /// The width. + [MethodImpl(InliningOptions.ShortMethod)] + private static void ApplyHorizontalPrediction24Bit(Span rows, int width) + { + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) + { + Span rowSpan = rows.Slice(y * width, width); + Span rowRgb = MemoryMarshal.Cast(rowSpan); + + for (int x = rowRgb.Length - 1; x >= 1; x--) { - ApplyHorizontalPrediction24Bit(rows, width); + byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); + byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); + byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); + var rgb = new Rgb24(r, g, b); + rowRgb[x].FromRgb24(rgb); } } + } - /// - /// Applies a horizontal predictor to the rgb row. - /// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next. - /// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus - /// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly. - /// - /// The rgb pixel rows. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - private static void ApplyHorizontalPrediction24Bit(Span rows, int width) + /// + /// Applies a horizontal predictor to a gray pixel row. + /// + /// The gray pixel rows. + /// The width. + [MethodImpl(InliningOptions.ShortMethod)] + private static void ApplyHorizontalPrediction8Bit(Span rows, int width) + { + DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); + int height = rows.Length / width; + for (int y = 0; y < height; y++) { - DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); - int height = rows.Length / width; - for (int y = 0; y < height; y++) + Span rowSpan = rows.Slice(y * width, width); + for (int x = rowSpan.Length - 1; x >= 1; x--) { - Span rowSpan = rows.Slice(y * width, width); - Span rowRgb = MemoryMarshal.Cast(rowSpan); - - for (int x = rowRgb.Length - 1; x >= 1; x--) - { - byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R); - byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G); - byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B); - var rgb = new Rgb24(r, g, b); - rowRgb[x].FromRgb24(rgb); - } + rowSpan[x] -= rowSpan[x - 1]; } } + } - /// - /// Applies a horizontal predictor to a gray pixel row. - /// - /// The gray pixel rows. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - private static void ApplyHorizontalPrediction8Bit(Span rows, int width) + private static void UndoGray8Bit(Span pixelBytes, int width) + { + int rowBytesCount = width; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) { - DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals"); - int height = rows.Length / width; - for (int y = 0; y < height; y++) + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + + byte pixelValue = rowBytes[0]; + for (int x = 1; x < width; x++) { - Span rowSpan = rows.Slice(y * width, width); - for (int x = rowSpan.Length - 1; x >= 1; x--) - { - rowSpan[x] -= rowSpan[x - 1]; - } + pixelValue += rowBytes[x]; + rowBytes[x] = pixelValue; } } + } - private static void UndoGray8Bit(Span pixelBytes, int width) + private static void UndoGray16Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 2; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width; - int height = pixelBytes.Length / rowBytesCount; for (int y = 0; y < height; y++) { + int offset = 0; Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; - byte pixelValue = rowBytes[0]; for (int x = 1; x < width; x++) { - pixelValue += rowBytes[x]; - rowBytes[x] = pixelValue; + Span rowSpan = rowBytes.Slice(offset, 2); + ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue); + offset += 2; } } } - - private static void UndoGray16Bit(Span pixelBytes, int width, bool isBigEndian) + else { - int rowBytesCount = width * 2; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue); - offset += 2; - } - } - } - else - { - for (int y = 0; y < height; y++) + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + Span rowSpan = rowBytes.Slice(offset, 2); + ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue); offset += 2; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue); - offset += 2; - } } } } + } - private static void UndoGray32Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoGray32Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 4; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) - { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue); - offset += 4; - } - } - } - else - { - for (int y = 0; y < height; y++) + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + Span rowSpan = rowBytes.Slice(offset, 4); + uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue); offset += 4; - - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - pixelValue += diff; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue); - offset += 4; - } } } } - - private static void UndoRgb24Bit(Span pixelBytes, int width) + else { - int rowBytesCount = width * 3; - int height = pixelBytes.Length / rowBytesCount; for (int y = 0; y < height; y++) { + int offset = 0; Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; - ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); - byte r = rowRgbBase.R; - byte g = rowRgbBase.G; - byte b = rowRgbBase.B; + uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; - for (int x = 1; x < rowRgb.Length; x++) + for (int x = 1; x < width; x++) { - ref Rgb24 pixel = ref rowRgb[x]; - r += pixel.R; - g += pixel.G; - b += pixel.B; - var rgb = new Rgb24(r, g, b); - pixel.FromRgb24(rgb); + Span rowSpan = rowBytes.Slice(offset, 4); + uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + pixelValue += diff; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue); + offset += 4; } } } + } - private static void UndoRgba32Bit(Span pixelBytes, int width) + private static void UndoRgb24Bit(Span pixelBytes, int width) + { + int rowBytesCount = width * 3; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) { - int rowBytesCount = width * 4; - int height = pixelBytes.Length / rowBytesCount; - for (int y = 0; y < height; y++) + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; + ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); + byte r = rowRgbBase.R; + byte g = rowRgbBase.G; + byte b = rowRgbBase.B; + + for (int x = 1; x < rowRgb.Length; x++) { - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; - ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); - byte r = rowRgbBase.R; - byte g = rowRgbBase.G; - byte b = rowRgbBase.B; - byte a = rowRgbBase.A; - - for (int x = 1; x < rowRgb.Length; x++) - { - ref Rgba32 pixel = ref rowRgb[x]; - r += pixel.R; - g += pixel.G; - b += pixel.B; - a += pixel.A; - var rgb = new Rgba32(r, g, b, a); - pixel.FromRgba32(rgb); - } + ref Rgb24 pixel = ref rowRgb[x]; + r += pixel.R; + g += pixel.G; + b += pixel.B; + var rgb = new Rgb24(r, g, b); + pixel.FromRgb24(rgb); + } + } + } + + private static void UndoRgba32Bit(Span pixelBytes, int width) + { + int rowBytesCount = width * 4; + int height = pixelBytes.Length / rowBytesCount; + for (int y = 0; y < height; y++) + { + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + Span rowRgb = MemoryMarshal.Cast(rowBytes)[..width]; + ref Rgba32 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb); + byte r = rowRgbBase.R; + byte g = rowRgbBase.G; + byte b = rowRgbBase.B; + byte a = rowRgbBase.A; + + for (int x = 1; x < rowRgb.Length; x++) + { + ref Rgba32 pixel = ref rowRgb[x]; + r += pixel.R; + g += pixel.G; + b += pixel.B; + a += pixel.A; + var rgb = new Rgba32(r, g, b, a); + pixel.FromRgba32(rgb); } } + } - private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoRgb48Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 6; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width * 6; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); offset += 2; - ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); offset += 2; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); - offset += 2; - } + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); + offset += 2; } } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); offset += 2; - ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); offset += 2; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); - offset += 2; - } + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); + offset += 2; } } } + } - private static void UndoRgba64Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoRgba64Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 8; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width * 8; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); offset += 2; - ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); offset += 2; - ushort a = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); offset += 2; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a); - offset += 2; - } + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaA = TiffUtils.ConvertToUShortBigEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt16BigEndian(rowSpan, a); + offset += 2; } } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + offset += 2; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + Span rowSpan = rowBytes.Slice(offset, 2); + ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); offset += 2; - ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); - offset += 2; - ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); offset += 2; - ushort a = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2)); + + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); offset += 2; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 2); - ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b); - offset += 2; - - rowSpan = rowBytes.Slice(offset, 2); - ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a); - offset += 2; - } + rowSpan = rowBytes.Slice(offset, 2); + ushort deltaA = TiffUtils.ConvertToUShortLittleEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, a); + offset += 2; } } } + } - private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoRgb96Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 12; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width * 12; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); offset += 4; - uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); offset += 4; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); - offset += 4; - } + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); + offset += 4; } } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); offset += 4; - uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); offset += 4; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); - offset += 4; - } + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); + offset += 4; } } } + } - private static void UndoRgba128Bit(Span pixelBytes, int width, bool isBigEndian) + private static void UndoRgba128Bit(Span pixelBytes, int width, bool isBigEndian) + { + int rowBytesCount = width * 16; + int height = pixelBytes.Length / rowBytesCount; + if (isBigEndian) { - int rowBytesCount = width * 16; - int height = pixelBytes.Length / rowBytesCount; - if (isBigEndian) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); offset += 4; - uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); offset += 4; - uint a = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); offset += 4; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a); - offset += 4; - } + rowSpan = rowBytes.Slice(offset, 4); + uint deltaA = TiffUtils.ConvertToUIntBigEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt32BigEndian(rowSpan, a); + offset += 4; } } - else + } + else + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + int offset = 0; + Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); + uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + offset += 4; + + for (int x = 1; x < width; x++) { - int offset = 0; - Span rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount); - uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + Span rowSpan = rowBytes.Slice(offset, 4); + uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + r += deltaR; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); offset += 4; - uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); - offset += 4; - uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + g += deltaG; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); offset += 4; - uint a = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4)); + + rowSpan = rowBytes.Slice(offset, 4); + uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + b += deltaB; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); offset += 4; - for (int x = 1; x < width; x++) - { - Span rowSpan = rowBytes.Slice(offset, 4); - uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - r += deltaR; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - g += deltaG; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - b += deltaB; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b); - offset += 4; - - rowSpan = rowBytes.Slice(offset, 4); - uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan); - a += deltaA; - BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a); - offset += 4; - } + rowSpan = rowBytes.Slice(offset, 4); + uint deltaA = TiffUtils.ConvertToUIntLittleEndian(rowSpan); + a += deltaA; + BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, a); + offset += 4; } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs index f7c94cfb0b..36f8c20d72 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs @@ -1,62 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +internal abstract class TiffBaseCompression : IDisposable { - internal abstract class TiffBaseCompression : IDisposable - { - private bool isDisposed; + private bool isDisposed; - protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - { - this.Allocator = allocator; - this.Width = width; - this.BitsPerPixel = bitsPerPixel; - this.Predictor = predictor; - this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; - } + protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + { + this.Allocator = allocator; + this.Width = width; + this.BitsPerPixel = bitsPerPixel; + this.Predictor = predictor; + this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8; + } - /// - /// Gets the image width. - /// - public int Width { get; } - - /// - /// Gets the bits per pixel. - /// - public int BitsPerPixel { get; } - - /// - /// Gets the bytes per row. - /// - public int BytesPerRow { get; } - - /// - /// Gets the predictor to use. Should only be used with deflate or lzw compression. - /// - public TiffPredictor Predictor { get; } - - /// - /// Gets the memory allocator. - /// - protected MemoryAllocator Allocator { get; } - - /// - public void Dispose() + /// + /// Gets the image width. + /// + public int Width { get; } + + /// + /// Gets the bits per pixel. + /// + public int BitsPerPixel { get; } + + /// + /// Gets the bytes per row. + /// + public int BytesPerRow { get; } + + /// + /// Gets the predictor to use. Should only be used with deflate or lzw compression. + /// + public TiffPredictor Predictor { get; } + + /// + /// Gets the memory allocator. + /// + protected MemoryAllocator Allocator { get; } + + /// + public void Dispose() + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - this.Dispose(true); + return; } - protected abstract void Dispose(bool disposing); + this.isDisposed = true; + this.Dispose(true); } + + protected abstract void Dispose(bool disposing); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs index f773416592..4680e23173 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs @@ -1,48 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +internal abstract class TiffBaseCompressor : TiffBaseCompression { - internal abstract class TiffBaseCompressor : TiffBaseCompression - { - /// - /// Initializes a new instance of the class. - /// - /// The output stream to write the compressed image to. - /// The memory allocator. - /// The image width. - /// Bits per pixel. - /// The predictor to use (should only be used with deflate or lzw compression). Defaults to none. - protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(allocator, width, bitsPerPixel, predictor) - => this.Output = output; + /// + /// Initializes a new instance of the class. + /// + /// The output stream to write the compressed image to. + /// The memory allocator. + /// The image width. + /// Bits per pixel. + /// The predictor to use (should only be used with deflate or lzw compression). Defaults to none. + protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(allocator, width, bitsPerPixel, predictor) + => this.Output = output; - /// - /// Gets the compression method to use. - /// - public abstract TiffCompression Method { get; } + /// + /// Gets the compression method to use. + /// + public abstract TiffCompression Method { get; } - /// - /// Gets the output stream to write the compressed image to. - /// - public Stream Output { get; } + /// + /// Gets the output stream to write the compressed image to. + /// + public Stream Output { get; } - /// - /// Does any initialization required for the compression. - /// - /// The number of rows per strip. - public abstract void Initialize(int rowsPerStrip); + /// + /// Does any initialization required for the compression. + /// + /// The number of rows per strip. + public abstract void Initialize(int rowsPerStrip); - /// - /// Compresses a strip of the image. - /// - /// Image rows to compress. - /// Image height. - public abstract void CompressStrip(Span rows, int height); - } + /// + /// Compresses a strip of the image. + /// + /// Image rows to compress. + /// Image height. + public abstract void CompressStrip(Span rows, int height); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index aaa3e94993..03cd639ad3 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -1,63 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +/// +/// The base tiff decompressor class. +/// +internal abstract class TiffBaseDecompressor : TiffBaseCompression { /// - /// The base tiff decompressor class. + /// Initializes a new instance of the class. /// - internal abstract class TiffBaseDecompressor : TiffBaseCompression + /// The memory allocator. + /// The width of the image. + /// The bits per pixel. + /// The predictor. + protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) + : base(memoryAllocator, width, bitsPerPixel, predictor) { - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The width of the image. - /// The bits per pixel. - /// The predictor. - protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) - : base(memoryAllocator, width, bitsPerPixel, predictor) - { - } + } - /// - /// Decompresses image data into the supplied buffer. - /// - /// The to read image data from. - /// The strip offset of stream. - /// The number of bytes to read from the input stream. - /// The height of the strip. - /// The output buffer for uncompressed data. - /// The token to monitor cancellation. - public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) - { - DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); - DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The strip offset of stream. + /// The number of bytes to read from the input stream. + /// The height of the strip. + /// The output buffer for uncompressed data. + /// The token to monitor cancellation. + public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + { + DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); + DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); - stream.Seek((long)stripOffset, SeekOrigin.Begin); - this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken); + stream.Seek((long)stripOffset, SeekOrigin.Begin); + this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken); - if ((long)stripOffset + (long)stripByteCount < stream.Position) - { - TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); - } + if ((long)stripOffset + (long)stripByteCount < stream.Position) + { + TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); } - - /// - /// Decompresses image data into the supplied buffer. - /// - /// The to read image data from. - /// The number of bytes to read from the input stream. - /// The height of the strip. - /// The output buffer for uncompressed data. - /// The token to monitor cancellation. - protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken); } + + /// + /// Decompresses image data into the supplied buffer. + /// + /// The to read image data from. + /// The number of bytes to read from the input stream. + /// The height of the strip. + /// The output buffer for uncompressed data. + /// The token to monitor cancellation. + protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span buffer, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs index 2dff9628b9..1b98066925 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs @@ -1,73 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +internal static class TiffCompressorFactory { - internal static class TiffCompressorFactory + public static TiffBaseCompressor Create( + TiffCompression method, + Stream output, + MemoryAllocator allocator, + int width, + int bitsPerPixel, + DeflateCompressionLevel compressionLevel, + TiffPredictor predictor) { - public static TiffBaseCompressor Create( - TiffCompression method, - Stream output, - MemoryAllocator allocator, - int width, - int bitsPerPixel, - DeflateCompressionLevel compressionLevel, - TiffPredictor predictor) + switch (method) { - switch (method) - { - // The following compression types are not implemented in the encoder and will default to no compression instead. - case TiffCompression.ItuTRecT43: - case TiffCompression.ItuTRecT82: - case TiffCompression.OldJpeg: - case TiffCompression.OldDeflate: - case TiffCompression.None: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + // The following compression types are not implemented in the encoder and will default to no compression instead. + case TiffCompression.ItuTRecT43: + case TiffCompression.ItuTRecT82: + case TiffCompression.OldJpeg: + case TiffCompression.OldDeflate: + case TiffCompression.None: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new NoCompressor(output, allocator, width, bitsPerPixel); + return new NoCompressor(output, allocator, width, bitsPerPixel); - case TiffCompression.Jpeg: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new TiffJpegCompressor(output, allocator, width, bitsPerPixel); + case TiffCompression.Jpeg: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new TiffJpegCompressor(output, allocator, width, bitsPerPixel); - case TiffCompression.PackBits: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new PackBitsCompressor(output, allocator, width, bitsPerPixel); + case TiffCompression.PackBits: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new PackBitsCompressor(output, allocator, width, bitsPerPixel); - case TiffCompression.Deflate: - return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); + case TiffCompression.Deflate: + return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel); - case TiffCompression.Lzw: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); + case TiffCompression.Lzw: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor); - case TiffCompression.CcittGroup3Fax: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); + case TiffCompression.CcittGroup3Fax: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, false); - case TiffCompression.CcittGroup4Fax: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T6BitCompressor(output, allocator, width, bitsPerPixel); + case TiffCompression.CcittGroup4Fax: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T6BitCompressor(output, allocator, width, bitsPerPixel); - case TiffCompression.Ccitt1D: - DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); + case TiffCompression.Ccitt1D: + DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4BitCompressor(output, allocator, width, bitsPerPixel, true); - default: - throw TiffThrowHelper.NotSupportedCompressor(method.ToString()); - } + default: + throw TiffThrowHelper.NotSupportedCompressor(method.ToString()); } } } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs index 3188b9e9a1..34f0ed2dbd 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs @@ -1,56 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +/// +/// Provides enumeration of the various TIFF compression types the decoder can handle. +/// +internal enum TiffDecoderCompressionType { /// - /// Provides enumeration of the various TIFF compression types the decoder can handle. - /// - internal enum TiffDecoderCompressionType - { - /// - /// Image data is stored uncompressed in the TIFF file. - /// - None = 0, - - /// - /// Image data is compressed using PackBits compression. - /// - PackBits = 1, - - /// - /// Image data is compressed using Deflate compression. - /// - Deflate = 2, - - /// - /// Image data is compressed using LZW compression. - /// - Lzw = 3, - - /// - /// Image data is compressed using CCITT T.4 fax compression. - /// - T4 = 4, - - /// - /// Image data is compressed using CCITT T.6 fax compression. - /// - T6 = 5, - - /// - /// Image data is compressed using modified huffman compression. - /// - HuffmanRle = 6, - - /// - /// The image data is compressed as a JPEG stream. - /// - Jpeg = 7, - - /// - /// The image data is compressed as a WEBP stream. - /// - Webp = 8, - } + /// Image data is stored uncompressed in the TIFF file. + /// + None = 0, + + /// + /// Image data is compressed using PackBits compression. + /// + PackBits = 1, + + /// + /// Image data is compressed using Deflate compression. + /// + Deflate = 2, + + /// + /// Image data is compressed using LZW compression. + /// + Lzw = 3, + + /// + /// Image data is compressed using CCITT T.4 fax compression. + /// + T4 = 4, + + /// + /// Image data is compressed using CCITT T.6 fax compression. + /// + T6 = 5, + + /// + /// Image data is compressed using modified huffman compression. + /// + HuffmanRle = 6, + + /// + /// The image data is compressed as a JPEG stream. + /// + Jpeg = 7, + + /// + /// The image data is compressed as a WEBP stream. + /// + Webp = 8, } diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs index 213746d7de..e09b93c02a 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs @@ -6,67 +6,66 @@ using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Formats.Tiff.Compression; + +internal static class TiffDecompressorsFactory { - internal static class TiffDecompressorsFactory + public static TiffBaseDecompressor Create( + DecoderOptions options, + TiffDecoderCompressionType method, + MemoryAllocator allocator, + TiffPhotometricInterpretation photometricInterpretation, + int width, + int bitsPerPixel, + TiffColorType colorType, + TiffPredictor predictor, + FaxCompressionOptions faxOptions, + byte[] jpegTables, + TiffFillOrder fillOrder, + ByteOrder byteOrder) { - public static TiffBaseDecompressor Create( - DecoderOptions options, - TiffDecoderCompressionType method, - MemoryAllocator allocator, - TiffPhotometricInterpretation photometricInterpretation, - int width, - int bitsPerPixel, - TiffColorType colorType, - TiffPredictor predictor, - FaxCompressionOptions faxOptions, - byte[] jpegTables, - TiffFillOrder fillOrder, - ByteOrder byteOrder) + switch (method) { - switch (method) - { - case TiffDecoderCompressionType.None: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new NoneTiffCompression(allocator, width, bitsPerPixel); + case TiffDecoderCompressionType.None: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new NoneTiffCompression(allocator, width, bitsPerPixel); - case TiffDecoderCompressionType.PackBits: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new PackBitsTiffCompression(allocator, width, bitsPerPixel); + case TiffDecoderCompressionType.PackBits: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new PackBitsTiffCompression(allocator, width, bitsPerPixel); - case TiffDecoderCompressionType.Deflate: - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian); + case TiffDecoderCompressionType.Deflate: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new DeflateTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian); - case TiffDecoderCompressionType.Lzw: - DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); - return new LzwTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian); + case TiffDecoderCompressionType.Lzw: + DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected"); + return new LzwTiffCompression(allocator, width, bitsPerPixel, colorType, predictor, byteOrder == ByteOrder.BigEndian); - case TiffDecoderCompressionType.T4: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); + case TiffDecoderCompressionType.T4: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); - case TiffDecoderCompressionType.T6: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); + case TiffDecoderCompressionType.T6: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); - case TiffDecoderCompressionType.HuffmanRle: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); + case TiffDecoderCompressionType.HuffmanRle: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); - case TiffDecoderCompressionType.Jpeg: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation); + case TiffDecoderCompressionType.Jpeg: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new JpegTiffCompression(new() { GeneralOptions = options }, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation); - case TiffDecoderCompressionType.Webp: - DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); - return new WebpTiffCompression(options, allocator, width, bitsPerPixel); + case TiffDecoderCompressionType.Webp: + DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); + return new WebpTiffCompression(options, allocator, width, bitsPerPixel); - default: - throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); - } + default: + throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); } } } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs index fdb1d6aa16..edc7915115 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -1,115 +1,114 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the compression formats defined by the Tiff file-format. +/// +public enum TiffCompression : ushort { /// - /// Enumeration representing the compression formats defined by the Tiff file-format. - /// - public enum TiffCompression : ushort - { - /// - /// A invalid compression value. - /// - Invalid = 0, - - /// - /// No compression. - /// - None = 1, - - /// - /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding. - /// - Ccitt1D = 2, - - /// - /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). - /// - CcittGroup3Fax = 3, - - /// - /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). - /// - CcittGroup4Fax = 4, - - /// - /// LZW compression (see Section 13 of the TIFF 6.0 specification). - /// - Lzw = 5, - - /// - /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification). - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - OldJpeg = 6, - - /// - /// JPEG compression (see TIFF Specification, supplement 2). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - Jpeg = 7, - - /// - /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2). - /// - Deflate = 8, - - /// - /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ItuTRecT82 = 9, - - /// - /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). - /// - /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ItuTRecT43 = 10, - - /// - /// NeXT 2-bit Grey Scale compression algorithm. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - NeXT = 32766, - - /// - /// PackBits compression. - /// - PackBits = 32773, - - /// - /// ThunderScan 4-bit compression. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - ThunderScan = 32809, - - /// - /// Deflate compression - old. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - OldDeflate = 32946, - - /// - /// Pixel data is compressed with webp encoder. - /// - /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, - /// if this is chosen. - /// - Webp = 50001, - } + /// A invalid compression value. + /// + Invalid = 0, + + /// + /// No compression. + /// + None = 1, + + /// + /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding. + /// + Ccitt1D = 2, + + /// + /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + CcittGroup3Fax = 3, + + /// + /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification). + /// + CcittGroup4Fax = 4, + + /// + /// LZW compression (see Section 13 of the TIFF 6.0 specification). + /// + Lzw = 5, + + /// + /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification). + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + OldJpeg = 6, + + /// + /// JPEG compression (see TIFF Specification, supplement 2). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + Jpeg = 7, + + /// + /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2). + /// + Deflate = 8, + + /// + /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + ItuTRecT82 = 9, + + /// + /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). + /// + /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, + /// if this is chosen. + /// + ItuTRecT43 = 10, + + /// + /// NeXT 2-bit Grey Scale compression algorithm. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + NeXT = 32766, + + /// + /// PackBits compression. + /// + PackBits = 32773, + + /// + /// ThunderScan 4-bit compression. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + ThunderScan = 32809, + + /// + /// Deflate compression - old. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + OldDeflate = 32946, + + /// + /// Pixel data is compressed with webp encoder. + /// + /// Note: The TIFF encoder does not support this compression and will default to use no compression instead, + /// if this is chosen. + /// + Webp = 50001, } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index a87cc0765c..05dacfef65 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -1,93 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +/// +/// Defines constants defined in the TIFF specification. +/// +internal static class TiffConstants { /// - /// Defines constants defined in the TIFF specification. - /// - internal static class TiffConstants - { - /// - /// Byte order markers for indicating little endian encoding. - /// - public const byte ByteOrderLittleEndian = 0x49; - - /// - /// Byte order markers for indicating big endian encoding. - /// - public const byte ByteOrderBigEndian = 0x4D; - - /// - /// Byte order markers for indicating little endian encoding. - /// - public const ushort ByteOrderLittleEndianShort = 0x4949; - - /// - /// Byte order markers for indicating big endian encoding. - /// - public const ushort ByteOrderBigEndianShort = 0x4D4D; - - /// - /// Magic number used within the image file header to identify a TIFF format file. - /// - public const ushort HeaderMagicNumber = 42; - - /// - /// The big tiff header magic number - /// - public const ushort BigTiffHeaderMagicNumber = 43; - - /// - /// The big tiff bytesize of offsets value. - /// - public const ushort BigTiffBytesize = 8; - - /// - /// RowsPerStrip default value, which is effectively infinity. - /// - public const int RowsPerStripInfinity = 2147483647; - - /// - /// Size (in bytes) of the Rational and SRational data types - /// - public const int SizeOfRational = 8; - - /// - /// The default strip size is 8k. - /// - public const int DefaultStripSize = 8 * 1024; - - /// - /// The bits per sample for 1 bit bicolor images. - /// - public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0); - - /// - /// The bits per sample for images with a 4 color palette. - /// - public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0); - - /// - /// The bits per sample for 8 bit images. - /// - public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0); - - /// - /// The bits per sample for color images with 8 bits for each color channel. - /// - public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8); - - /// - /// The list of mimetypes that equate to a tiff. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; - - /// - /// The list of file extensions that equate to a tiff. - /// - public static readonly IEnumerable FileExtensions = new[] { "tiff", "tif" }; - } + /// Byte order markers for indicating little endian encoding. + /// + public const byte ByteOrderLittleEndian = 0x49; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const byte ByteOrderBigEndian = 0x4D; + + /// + /// Byte order markers for indicating little endian encoding. + /// + public const ushort ByteOrderLittleEndianShort = 0x4949; + + /// + /// Byte order markers for indicating big endian encoding. + /// + public const ushort ByteOrderBigEndianShort = 0x4D4D; + + /// + /// Magic number used within the image file header to identify a TIFF format file. + /// + public const ushort HeaderMagicNumber = 42; + + /// + /// The big tiff header magic number + /// + public const ushort BigTiffHeaderMagicNumber = 43; + + /// + /// The big tiff bytesize of offsets value. + /// + public const ushort BigTiffBytesize = 8; + + /// + /// RowsPerStrip default value, which is effectively infinity. + /// + public const int RowsPerStripInfinity = 2147483647; + + /// + /// Size (in bytes) of the Rational and SRational data types + /// + public const int SizeOfRational = 8; + + /// + /// The default strip size is 8k. + /// + public const int DefaultStripSize = 8 * 1024; + + /// + /// The bits per sample for 1 bit bicolor images. + /// + public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0); + + /// + /// The bits per sample for images with a 4 color palette. + /// + public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0); + + /// + /// The bits per sample for 8 bit images. + /// + public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0); + + /// + /// The bits per sample for color images with 8 bits for each color channel. + /// + public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8); + + /// + /// The list of mimetypes that equate to a tiff. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/tiff", "image/tiff-fx" }; + + /// + /// The list of file extensions that equate to a tiff. + /// + public static readonly IEnumerable FileExtensions = new[] { "tiff", "tif" }; } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs index f9f528ca56..10323304fc 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the possible uses of extra components in TIFF format files. +/// +internal enum TiffExtraSamples { /// - /// Enumeration representing the possible uses of extra components in TIFF format files. + /// Unspecified data. /// - internal enum TiffExtraSamples - { - /// - /// Unspecified data. - /// - Unspecified = 0, + Unspecified = 0, - /// - /// Associated alpha data (with pre-multiplied color). - /// - AssociatedAlpha = 1, + /// + /// Associated alpha data (with pre-multiplied color). + /// + AssociatedAlpha = 1, - /// - /// Unassociated alpha data. - /// - UnassociatedAlpha = 2 - } + /// + /// Unassociated alpha data. + /// + UnassociatedAlpha = 2 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs index e8a1795aa5..38445298e9 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the fill orders defined by the Tiff file-format. +/// +internal enum TiffFillOrder : ushort { /// - /// Enumeration representing the fill orders defined by the Tiff file-format. + /// Pixels with lower column values are stored in the higher-order bits of the byte. /// - internal enum TiffFillOrder : ushort - { - /// - /// Pixels with lower column values are stored in the higher-order bits of the byte. - /// - MostSignificantBitFirst = 1, + MostSignificantBitFirst = 1, - /// - /// Pixels with lower column values are stored in the lower-order bits of the byte. - /// - LeastSignificantBitFirst = 2 - } + /// + /// Pixels with lower column values are stored in the lower-order bits of the byte. + /// + LeastSignificantBitFirst = 2 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs index bf92f91bac..94af2d91ff 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs @@ -1,44 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +/// +/// Enumeration representing the sub-file types defined by the Tiff file-format. +/// +[Flags] +public enum TiffNewSubfileType : uint { /// - /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// A full-resolution image. /// - [Flags] - public enum TiffNewSubfileType : uint - { - /// - /// A full-resolution image. - /// - FullImage = 0, + FullImage = 0, - /// - /// Reduced-resolution version of another image in this TIFF file. - /// - Preview = 1, + /// + /// Reduced-resolution version of another image in this TIFF file. + /// + Preview = 1, - /// - /// A single page of a multi-page image. - /// - SinglePage = 2, + /// + /// A single page of a multi-page image. + /// + SinglePage = 2, - /// - /// A transparency mask for another image in this TIFF file. - /// - TransparencyMask = 4, + /// + /// A transparency mask for another image in this TIFF file. + /// + TransparencyMask = 4, - /// - /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification). - /// - AlternativePreview = 65536, + /// + /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification). + /// + AlternativePreview = 65536, - /// - /// Mixed raster content (see RFC2301). - /// - MixedRasterContent = 8 - } + /// + /// Mixed raster content (see RFC2301). + /// + MixedRasterContent = 8 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs index 88ab87b044..4f1be32a1a 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs @@ -1,51 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the image orientations defined by the Tiff file-format. +/// +internal enum TiffOrientation { /// - /// Enumeration representing the image orientations defined by the Tiff file-format. - /// - internal enum TiffOrientation - { - /// - /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively. - /// - TopLeft = 1, - - /// - /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively. - /// - TopRight = 2, - - /// - /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively. - /// - BottomRight = 3, - - /// - /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively. - /// - BottomLeft = 4, - - /// - /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively. - /// - LeftTop = 5, - - /// - /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively. - /// - RightTop = 6, - - /// - /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively. - /// - RightBottom = 7, - - /// - /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively. - /// - LeftBottom = 8 - } + /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively. + /// + TopLeft = 1, + + /// + /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively. + /// + TopRight = 2, + + /// + /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively. + /// + BottomRight = 3, + + /// + /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively. + /// + BottomLeft = 4, + + /// + /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively. + /// + LeftTop = 5, + + /// + /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively. + /// + RightTop = 6, + + /// + /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively. + /// + RightBottom = 7, + + /// + /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively. + /// + LeftBottom = 8 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs index d382b04535..cb537dda3c 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs @@ -1,89 +1,88 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format. +/// +public enum TiffPhotometricInterpretation : ushort { /// - /// Enumeration representing the photometric interpretation formats defined by the Tiff file-format. + /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + /// Not supported by the TiffEncoder. /// - public enum TiffPhotometricInterpretation : ushort - { - /// - /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. - /// - /// Not supported by the TiffEncoder. - /// - WhiteIsZero = 0, + WhiteIsZero = 0, - /// - /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white. - /// - BlackIsZero = 1, + /// + /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero = 1, - /// - /// RGB image. - /// - Rgb = 2, + /// + /// RGB image. + /// + Rgb = 2, - /// - /// Palette Color. - /// - PaletteColor = 3, + /// + /// Palette Color. + /// + PaletteColor = 3, - /// - /// A transparency mask. - /// - /// Not supported by the TiffEncoder. - /// - TransparencyMask = 4, + /// + /// A transparency mask. + /// + /// Not supported by the TiffEncoder. + /// + TransparencyMask = 4, - /// - /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. - /// - Separated = 5, + /// + /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + Separated = 5, - /// - /// YCbCr (see Section 21 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. - /// - YCbCr = 6, + /// + /// YCbCr (see Section 21 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + YCbCr = 6, - /// - /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. - /// - CieLab = 8, + /// + /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). + /// + /// Not supported by the TiffEncoder. + /// + CieLab = 8, - /// - /// ICC L*a*b* (see TIFF Specification, supplement 1). - /// - /// Not supported by the TiffEncoder. - /// - IccLab = 9, + /// + /// ICC L*a*b* (see TIFF Specification, supplement 1). + /// + /// Not supported by the TiffEncoder. + /// + IccLab = 9, - /// - /// ITU L*a*b* (see RFC2301). - /// - /// Not supported by the TiffEncoder. - /// - ItuLab = 10, + /// + /// ITU L*a*b* (see RFC2301). + /// + /// Not supported by the TiffEncoder. + /// + ItuLab = 10, - /// - /// Color Filter Array (see the DNG specification). - /// - /// Not supported by the TiffEncoder. - /// - ColorFilterArray = 32803, + /// + /// Color Filter Array (see the DNG specification). + /// + /// Not supported by the TiffEncoder. + /// + ColorFilterArray = 32803, - /// - /// Linear Raw (see the DNG specification). - /// - /// Not supported by the TiffEncoder. - /// - LinearRaw = 34892 - } + /// + /// Linear Raw (see the DNG specification). + /// + /// Not supported by the TiffEncoder. + /// + LinearRaw = 34892 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs index 5ecd8d99aa..b4d107e5f0 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing how the components of each pixel are stored the Tiff file-format. +/// +public enum TiffPlanarConfiguration : ushort { /// - /// Enumeration representing how the components of each pixel are stored the Tiff file-format. + /// Chunky format. + /// The component values for each pixel are stored contiguously. + /// The order of the components within the pixel is specified by + /// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB. /// - public enum TiffPlanarConfiguration : ushort - { - /// - /// Chunky format. - /// The component values for each pixel are stored contiguously. - /// The order of the components within the pixel is specified by - /// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB. - /// - Chunky = 1, + Chunky = 1, - /// - /// Planar format. - /// The components are stored in separate “component planes.” The - /// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional - /// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns - /// for row 0 are stored first, followed by the columns of row 1, and so on.) - /// PhotometricInterpretation describes the type of data stored in each component - /// plane. For example, RGB data is stored with the Red components in one component - /// plane, the Green in another, and the Blue in another. - /// - Planar = 2 - } + /// + /// Planar format. + /// The components are stored in separate “component planes.” The + /// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional + /// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns + /// for row 0 are stored first, followed by the columns of row 1, and so on.) + /// PhotometricInterpretation describes the type of data stored in each component + /// plane. For example, RGB data is stored with the Red components in one component + /// plane, the Green in another, and the Blue in another. + /// + Planar = 2 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs index bc35e5ab14..cf0d12dba8 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// A mathematical operator that is applied to the image data before an encoding scheme is applied. +/// +public enum TiffPredictor : ushort { /// - /// A mathematical operator that is applied to the image data before an encoding scheme is applied. + /// No prediction. /// - public enum TiffPredictor : ushort - { - /// - /// No prediction. - /// - None = 1, + None = 1, - /// - /// Horizontal differencing. - /// - Horizontal = 2, + /// + /// Horizontal differencing. + /// + Horizontal = 2, - /// - /// Floating point horizontal differencing. - /// - /// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none. - /// - FloatingPoint = 3 - } + /// + /// Floating point horizontal differencing. + /// + /// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none. + /// + FloatingPoint = 3 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs index 4377636623..9f30c7a050 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs @@ -1,41 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Specifies how to interpret each data sample in a pixel. +/// +public enum TiffSampleFormat : ushort { /// - /// Specifies how to interpret each data sample in a pixel. + /// Unsigned integer data. Default value. /// - public enum TiffSampleFormat : ushort - { - /// - /// Unsigned integer data. Default value. - /// - UnsignedInteger = 1, + UnsignedInteger = 1, - /// - /// Signed integer data. - /// - SignedInteger = 2, + /// + /// Signed integer data. + /// + SignedInteger = 2, - /// - /// IEEE floating point data. - /// - Float = 3, + /// + /// IEEE floating point data. + /// + Float = 3, - /// - /// Undefined data format. - /// - Undefined = 4, + /// + /// Undefined data format. + /// + Undefined = 4, - /// - /// The complex int. - /// - ComplexInt = 5, + /// + /// The complex int. + /// + ComplexInt = 5, - /// - /// The complex float. - /// - ComplexFloat = 6 - } + /// + /// The complex float. + /// + ComplexFloat = 6 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs index b8788f537e..cc2a2f8ad3 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the sub-file types defined by the Tiff file-format. +/// +public enum TiffSubfileType : ushort { /// - /// Enumeration representing the sub-file types defined by the Tiff file-format. + /// Full-resolution image data. /// - public enum TiffSubfileType : ushort - { - /// - /// Full-resolution image data. - /// - FullImage = 1, + FullImage = 1, - /// - /// Reduced-resolution image data. - /// - Preview = 2, + /// + /// Reduced-resolution image data. + /// + Preview = 2, - /// - /// A single page of a multi-page image. - /// - SinglePage = 3 - } + /// + /// A single page of a multi-page image. + /// + SinglePage = 3 } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs index c3b9913efe..1577371122 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.Constants +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the thresholding applied to image data defined by the Tiff file-format. +/// +internal enum TiffThresholding { /// - /// Enumeration representing the thresholding applied to image data defined by the Tiff file-format. + /// No dithering or halftoning. /// - internal enum TiffThresholding - { - /// - /// No dithering or halftoning. - /// - None = 1, + None = 1, - /// - /// An ordered dither or halftone technique. - /// - Ordered = 2, + /// + /// An ordered dither or halftone technique. + /// + Ordered = 2, - /// - /// A randomized process such as error diffusion. - /// - Random = 3 - } + /// + /// A randomized process such as error diffusion. + /// + Random = 3 } diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs index 9f73751e1e..1e74e630ce 100644 --- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs +++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs @@ -5,43 +5,42 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Encapsulates the options for the . +/// +internal interface ITiffEncoderOptions { /// - /// Encapsulates the options for the . + /// Gets the number of bits per pixel. + /// + TiffBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets the compression type to use. + /// + TiffCompression? Compression { get; } + + /// + /// Gets the compression level 1-9 for the deflate compression mode. + /// Defaults to . + /// + DeflateCompressionLevel? CompressionLevel { get; } + + /// + /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. + /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. + /// + TiffPhotometricInterpretation? PhotometricInterpretation { get; } + + /// + /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. + /// + TiffPredictor? HorizontalPredictor { get; } + + /// + /// Gets the quantizer for creating a color palette image. /// - internal interface ITiffEncoderOptions - { - /// - /// Gets the number of bits per pixel. - /// - TiffBitsPerPixel? BitsPerPixel { get; } - - /// - /// Gets the compression type to use. - /// - TiffCompression? Compression { get; } - - /// - /// Gets the compression level 1-9 for the deflate compression mode. - /// Defaults to . - /// - DeflateCompressionLevel? CompressionLevel { get; } - - /// - /// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor. - /// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used. - /// - TiffPhotometricInterpretation? PhotometricInterpretation { get; } - - /// - /// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression. - /// - TiffPredictor? HorizontalPredictor { get; } - - /// - /// Gets the quantizer for creating a color palette image. - /// - IQuantizer Quantizer { get; } - } + IQuantizer Quantizer { get; } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 4cd5cd1ad4..54d5c4ce86 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -1,111 +1,107 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// The TIFF IFD reader class. +/// +internal class DirectoryReader { - /// - /// The TIFF IFD reader class. - /// - internal class DirectoryReader - { - private const int DirectoryMax = 65534; + private const int DirectoryMax = 65534; - private readonly Stream stream; + private readonly Stream stream; - private readonly MemoryAllocator allocator; + private readonly MemoryAllocator allocator; - private ulong nextIfdOffset; + private ulong nextIfdOffset; - public DirectoryReader(Stream stream, MemoryAllocator allocator) - { - this.stream = stream; - this.allocator = allocator; - } + public DirectoryReader(Stream stream, MemoryAllocator allocator) + { + this.stream = stream; + this.allocator = allocator; + } - /// - /// Gets the byte order. - /// - public ByteOrder ByteOrder { get; private set; } + /// + /// Gets the byte order. + /// + public ByteOrder ByteOrder { get; private set; } - public bool IsBigTiff { get; private set; } + public bool IsBigTiff { get; private set; } - /// - /// Reads image file directories. - /// - /// Image file directories. - public IEnumerable Read() - { - this.ByteOrder = ReadByteOrder(this.stream); - var headerReader = new HeaderReader(this.stream, this.ByteOrder); - headerReader.ReadFileHeader(); + /// + /// Reads image file directories. + /// + /// Image file directories. + public IEnumerable Read() + { + this.ByteOrder = ReadByteOrder(this.stream); + var headerReader = new HeaderReader(this.stream, this.ByteOrder); + headerReader.ReadFileHeader(); - this.nextIfdOffset = headerReader.FirstIfdOffset; - this.IsBigTiff = headerReader.IsBigTiff; + this.nextIfdOffset = headerReader.FirstIfdOffset; + this.IsBigTiff = headerReader.IsBigTiff; - return this.ReadIfds(headerReader.IsBigTiff); - } + return this.ReadIfds(headerReader.IsBigTiff); + } - private static ByteOrder ReadByteOrder(Stream stream) + private static ByteOrder ReadByteOrder(Stream stream) + { + Span headerBytes = stackalloc byte[2]; + stream.Read(headerBytes); + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) { - Span headerBytes = stackalloc byte[2]; - stream.Read(headerBytes); - if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) - { - return ByteOrder.LittleEndian; - } - - if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) - { - return ByteOrder.BigEndian; - } - - throw TiffThrowHelper.ThrowInvalidHeader(); + return ByteOrder.LittleEndian; } - private IEnumerable ReadIfds(bool isBigTiff) + if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) { - var readers = new List(); - while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) - { - var reader = new EntryReader(this.stream, this.ByteOrder, this.allocator); - reader.ReadTags(isBigTiff, this.nextIfdOffset); + return ByteOrder.BigEndian; + } - if (reader.BigValues.Count > 0) - { - reader.BigValues.Sort((t1, t2) => t1.Offset.CompareTo(t2.Offset)); + throw TiffThrowHelper.ThrowInvalidHeader(); + } - // this means that most likely all elements are placed before next IFD - if (reader.BigValues[0].Offset < reader.NextIfdOffset) - { - reader.ReadBigValues(); - } - } + private IEnumerable ReadIfds(bool isBigTiff) + { + var readers = new List(); + while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) + { + var reader = new EntryReader(this.stream, this.ByteOrder, this.allocator); + reader.ReadTags(isBigTiff, this.nextIfdOffset); - this.nextIfdOffset = reader.NextIfdOffset; - readers.Add(reader); + if (reader.BigValues.Count > 0) + { + reader.BigValues.Sort((t1, t2) => t1.Offset.CompareTo(t2.Offset)); - if (readers.Count >= DirectoryMax) + // this means that most likely all elements are placed before next IFD + if (reader.BigValues[0].Offset < reader.NextIfdOffset) { - TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories"); + reader.ReadBigValues(); } } - var list = new List(readers.Count); - foreach (EntryReader reader in readers) + this.nextIfdOffset = reader.NextIfdOffset; + readers.Add(reader); + + if (readers.Count >= DirectoryMax) { - reader.ReadBigValues(); - var profile = new ExifProfile(reader.Values, reader.InvalidTags); - list.Add(profile); + TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories"); } + } - return list; + var list = new List(readers.Count); + foreach (EntryReader reader in readers) + { + reader.ReadBigValues(); + var profile = new ExifProfile(reader.Values, reader.InvalidTags); + list.Add(profile); } + + return list; } } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index c23831e507..4078ccaf59 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +internal class EntryReader : BaseExifReader { - internal class EntryReader : BaseExifReader - { - public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator) - : base(stream, allocator) => - this.IsBigEndian = byteOrder == ByteOrder.BigEndian; + public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator) + : base(stream, allocator) => + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - public List Values { get; } = new(); + public List Values { get; } = new(); - public ulong NextIfdOffset { get; private set; } + public ulong NextIfdOffset { get; private set; } - public void ReadTags(bool isBigTiff, ulong ifdOffset) + public void ReadTags(bool isBigTiff, ulong ifdOffset) + { + if (!isBigTiff) { - if (!isBigTiff) - { - this.ReadValues(this.Values, (uint)ifdOffset); - this.NextIfdOffset = this.ReadUInt32(); - - this.ReadSubIfd(this.Values); - } - else - { - this.ReadValues64(this.Values, ifdOffset); - this.NextIfdOffset = this.ReadUInt64(); + this.ReadValues(this.Values, (uint)ifdOffset); + this.NextIfdOffset = this.ReadUInt32(); - //// this.ReadSubIfd64(this.Values); - } + this.ReadSubIfd(this.Values); } + else + { + this.ReadValues64(this.Values, ifdOffset); + this.NextIfdOffset = this.ReadUInt64(); - public void ReadBigValues() => this.ReadBigValues(this.Values); + //// this.ReadSubIfd64(this.Values); + } } - internal class HeaderReader : BaseExifReader - { - public HeaderReader(Stream stream, ByteOrder byteOrder) - : base(stream, null) => - this.IsBigEndian = byteOrder == ByteOrder.BigEndian; + public void ReadBigValues() => this.ReadBigValues(this.Values); +} - public bool IsBigTiff { get; private set; } +internal class HeaderReader : BaseExifReader +{ + public HeaderReader(Stream stream, ByteOrder byteOrder) + : base(stream, null) => + this.IsBigEndian = byteOrder == ByteOrder.BigEndian; + + public bool IsBigTiff { get; private set; } - public ulong FirstIfdOffset { get; private set; } + public ulong FirstIfdOffset { get; private set; } - public void ReadFileHeader() + public void ReadFileHeader() + { + ushort magic = this.ReadUInt16(); + if (magic == TiffConstants.HeaderMagicNumber) { - ushort magic = this.ReadUInt16(); - if (magic == TiffConstants.HeaderMagicNumber) + this.IsBigTiff = false; + this.FirstIfdOffset = this.ReadUInt32(); + return; + } + else if (magic == TiffConstants.BigTiffHeaderMagicNumber) + { + this.IsBigTiff = true; + + ushort bytesize = this.ReadUInt16(); + ushort reserve = this.ReadUInt16(); + if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0) { - this.IsBigTiff = false; - this.FirstIfdOffset = this.ReadUInt32(); + this.FirstIfdOffset = this.ReadUInt64(); return; } - else if (magic == TiffConstants.BigTiffHeaderMagicNumber) - { - this.IsBigTiff = true; - - ushort bytesize = this.ReadUInt16(); - ushort reserve = this.ReadUInt16(); - if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0) - { - this.FirstIfdOffset = this.ReadUInt64(); - return; - } - } - - TiffThrowHelper.ThrowInvalidHeader(); } + + TiffThrowHelper.ThrowInvalidHeader(); } } diff --git a/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs b/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs index 5320c3e742..b06f5dd470 100644 --- a/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Tiff/MetadataExtensions.cs @@ -4,25 +4,24 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the tiff format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the tiff format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); + /// The metadata this method extends. + /// The . + public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); - /// - /// Gets the tiff format specific metadata for the image frame. - /// - /// The metadata this method extends. - /// The . - public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); - } + /// + /// Gets the tiff format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 1ee2819df6..8763f99570 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images. +/// +internal class BlackIsZero16TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly Configuration configuration; + /// - /// Implements the 'BlackIsZero' photometric interpretation for 16-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class BlackIsZero16TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// The configuration. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian) { - private readonly bool isBigEndian; - - private readonly Configuration configuration; + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero16TiffColor(Configuration configuration, bool isBigEndian) - { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - } + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + L16 l16 = TiffUtils.L16Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - L16 l16 = TiffUtils.L16Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; + ushort intensity = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; - pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); - } + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } - else - { - int byteCount = pixelRow.Length * 2; - PixelOperations.Instance.FromL16Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); + } + else + { + int byteCount = pixelRow.Length * 2; + PixelOperations.Instance.FromL16Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - offset += byteCount; - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs index 65d19ccd3d..6187ab8faa 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images). +/// +/// The pixel format. +internal class BlackIsZero1TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images). - /// - /// The pixel format. - internal class BlackIsZero1TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - nint offset = 0; - var colorBlack = default(TPixel); - var colorWhite = default(TPixel); + nint offset = 0; + var colorBlack = default(TPixel); + var colorWhite = default(TPixel); - colorBlack.FromRgba32(Color.Black); - colorWhite.FromRgba32(Color.White); - ref byte dataRef = ref MemoryMarshal.GetReference(data); - for (nint y = top; y < top + height; y++) + colorBlack.FromRgba32(Color.Black); + colorWhite.FromRgba32(Color.White); + ref byte dataRef = ref MemoryMarshal.GetReference(data); + for (nint y = top; y < top + height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); + ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); + for (nint x = left; x < left + width; x += 8) { - Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); - ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); - for (nint x = left; x < left + width; x += 8) - { - byte b = Unsafe.Add(ref dataRef, offset++); - nint maxShift = Math.Min(left + width - x, 8); + byte b = Unsafe.Add(ref dataRef, offset++); + nint maxShift = Math.Min(left + width - x, 8); - if (maxShift == 8) - { - int bit = (b >> 7) & 1; - ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); - pixel0 = bit == 0 ? colorBlack : colorWhite; + if (maxShift == 8) + { + int bit = (b >> 7) & 1; + ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); + pixel0 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 6) & 1; - ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); - pixel1 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 6) & 1; + ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); + pixel1 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 5) & 1; - ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); - pixel2 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 5) & 1; + ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); + pixel2 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 4) & 1; - ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); - pixel3 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 4) & 1; + ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); + pixel3 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 3) & 1; - ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); - pixel4 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 3) & 1; + ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); + pixel4 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 2) & 1; - ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); - pixel5 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 2) & 1; + ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); + pixel5 = bit == 0 ? colorBlack : colorWhite; - bit = (b >> 1) & 1; - ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); - pixel6 = bit == 0 ? colorBlack : colorWhite; + bit = (b >> 1) & 1; + ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); + pixel6 = bit == 0 ? colorBlack : colorWhite; - bit = b & 1; - ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); - pixel7 = bit == 0 ? colorBlack : colorWhite; - } - else + bit = b & 1; + ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); + pixel7 = bit == 0 ? colorBlack : colorWhite; + } + else + { + for (int shift = 0; shift < maxShift; shift++) { - for (int shift = 0; shift < maxShift; shift++) - { - int bit = (b >> (7 - shift)) & 1; + int bit = (b >> (7 - shift)) & 1; - ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); - pixel = bit == 0 ? colorBlack : colorWhite; - } + ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); + pixel = bit == 0 ? colorBlack : colorWhite; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index d68d70e9b1..d57130a5f5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -1,62 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation for 24-bit grayscale images. +/// +internal class BlackIsZero24TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'BlackIsZero' photometric interpretation for 24-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class BlackIsZero24TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public BlackIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + Span bufferSpan = buffer[bufferStartIdx..]; + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span bufferSpan = buffer[bufferStartIdx..]; - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong intensity = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs index 1f99e6d9b6..9007b3f5ab 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -1,64 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation for 32-bit float grayscale images. +/// +internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'BlackIsZero' photometric interpretation for 32-bit float grayscale images. + /// Initializes a new instance of the class. /// - internal class BlackIsZero32FloatTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public BlackIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + byte[] buffer = new byte[4]; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - byte[] buffer = new byte[4]; - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float intensity = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float intensity = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - float intensity = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float intensity = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index 0ce4d4c4a6..b5b3d8ee9e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation for 32-bit grayscale images. +/// +internal class BlackIsZero32TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'BlackIsZero' photometric interpretation for 32-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class BlackIsZero32TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public BlackIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public BlackIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong intensity = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong intensity = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs index 847ca90abe..16b995441c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images). +/// +internal class BlackIsZero4TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images). - /// - internal class BlackIsZero4TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + var color = default(TPixel); - int offset = 0; - bool isOddWidth = (width & 1) == 1; + int offset = 0; + bool isOddWidth = (width & 1) == 1; - var l8 = default(L8); - for (int y = top; y < top + height; y++) + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + for (int x = left; x < left + width - 1;) { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - for (int x = left; x < left + width - 1;) - { - byte byteData = data[offset++]; + byte byteData = data[offset++]; - byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); - l8.PackedValue = intensity1; - color.FromL8(l8); + byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); - pixelRowSpan[x++] = color; + pixelRowSpan[x++] = color; - byte intensity2 = (byte)((byteData & 0x0F) * 17); - l8.PackedValue = intensity2; - color.FromL8(l8); + byte intensity2 = (byte)((byteData & 0x0F) * 17); + l8.PackedValue = intensity2; + color.FromL8(l8); - pixelRowSpan[x++] = color; - } + pixelRowSpan[x++] = color; + } - if (isOddWidth) - { - byte byteData = data[offset++]; + if (isOddWidth) + { + byte byteData = data[offset++]; - byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); - l8.PackedValue = intensity1; - color.FromL8(l8); + byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); - pixelRowSpan[left + width - 1] = color; - } + pixelRowSpan[left + width - 1] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index 659ce4359f..2a8e6e001d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images). +/// +internal class BlackIsZero8TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images). - /// - internal class BlackIsZero8TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly Configuration configuration; + private readonly Configuration configuration; - public BlackIsZero8TiffColor(Configuration configuration) => this.configuration = configuration; + public BlackIsZero8TiffColor(Configuration configuration) => this.configuration = configuration; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length; - PixelOperations.Instance.FromL8Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + int byteCount = pixelRow.Length; + PixelOperations.Instance.FromL8Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - offset += byteCount; - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index f2bc59ffd3..b086cb43ee 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths). +/// +internal class BlackIsZeroTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'BlackIsZero' photometric interpretation (for all bit depths). - /// - internal class BlackIsZeroTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly ushort bitsPerSample0; + private readonly ushort bitsPerSample0; - private readonly float factor; + private readonly float factor; - public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - this.factor = (1 << this.bitsPerSample0) - 1.0f; - } + public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + this.factor = (1 << this.bitsPerSample0) - 1.0f; + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); - var bitReader = new BitReader(data); + var bitReader = new BitReader(data); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int value = bitReader.ReadBits(this.bitsPerSample0); - float intensity = value / this.factor; + int value = bitReader.ReadBits(this.bitsPerSample0); + float intensity = value / this.factor; - color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); - pixelRow[x] = color; - } - - bitReader.NextRow(); + color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + pixelRow[x] = color; } + + bitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs index 3baf60e786..216d173309 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabPlanarTiffColor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; @@ -9,40 +8,39 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration. +/// +internal class CieLabPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements decoding pixel data with photometric interpretation of type 'CieLab' with the planar configuration. - /// - internal class CieLabPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel - { - private static readonly ColorSpaceConverter ColorSpaceConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new(); - private const float Inv255 = 1.0f / 255.0f; + private const float Inv255 = 1.0f / 255.0f; - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - Span l = data[0].GetSpan(); - Span a = data[1].GetSpan(); - Span b = data[2].GetSpan(); + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + Span l = data[0].GetSpan(); + Span a = data[1].GetSpan(); + Span b = data[2].GetSpan(); - var color = default(TPixel); - int offset = 0; - for (int y = top; y < top + height; y++) + var color = default(TPixel); + int offset = 0; + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]); - var rgb = ColorSpaceConverter.ToRgb(lab); + var lab = new CieLab((l[offset] & 0xFF) * 100f * Inv255, (sbyte)a[offset], (sbyte)b[offset]); + var rgb = ColorSpaceConverter.ToRgb(lab); - color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); - pixelRow[x] = color; + color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); + pixelRow[x] = color; - offset++; - } + offset++; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs index 5b272b2be5..01ccfd3559 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/CieLabTiffColor{TPixel}.cs @@ -1,45 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements decoding pixel data with photometric interpretation of type 'CieLab'. +/// +internal class CieLabTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements decoding pixel data with photometric interpretation of type 'CieLab'. - /// - internal class CieLabTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private static readonly ColorSpaceConverter ColorSpaceConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new(); - private const float Inv255 = 1.0f / 255.0f; + private const float Inv255 = 1.0f / 255.0f; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float l = (data[offset] & 0xFF) * 100f * Inv255; - var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); - var rgb = ColorSpaceConverter.ToRgb(lab); + for (int x = 0; x < pixelRow.Length; x++) + { + float l = (data[offset] & 0xFF) * 100f * Inv255; + var lab = new CieLab(l, (sbyte)data[offset + 1], (sbyte)data[offset + 2]); + var rgb = ColorSpaceConverter.ToRgb(lab); - color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); - pixelRow[x] = color; + color.FromVector4(new Vector4(rgb.R, rgb.G, rgb.B, 1.0f)); + pixelRow[x] = color; - offset += 3; - } + offset += 3; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index 529c93b264..22db1918cb 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -1,70 +1,68 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). +/// +internal class PaletteTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths). - /// - internal class PaletteTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly ushort bitsPerSample0; + private readonly ushort bitsPerSample0; - private readonly TPixel[] palette; + private readonly TPixel[] palette; - private const float InvMax = 1.0f / 65535F; + private const float InvMax = 1.0f / 65535F; - /// The number of bits per sample for each pixel. - /// The RGB color lookup table to use for decoding the image. - public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - int colorCount = 1 << this.bitsPerSample0; - this.palette = GeneratePalette(colorMap, colorCount); - } + /// The number of bits per sample for each pixel. + /// The RGB color lookup table to use for decoding the image. + public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + int colorCount = 1 << this.bitsPerSample0; + this.palette = GeneratePalette(colorMap, colorCount); + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var bitReader = new BitReader(data); + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var bitReader = new BitReader(data); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int index = bitReader.ReadBits(this.bitsPerSample0); - pixelRow[x] = this.palette[index]; - } - - bitReader.NextRow(); + int index = bitReader.ReadBits(this.bitsPerSample0); + pixelRow[x] = this.palette[index]; } + + bitReader.NextRow(); } + } - private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) - { - var palette = new TPixel[colorCount]; + private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount) + { + var palette = new TPixel[colorCount]; - const int rOffset = 0; - int gOffset = colorCount; - int bOffset = colorCount * 2; + const int rOffset = 0; + int gOffset = colorCount; + int bOffset = colorCount * 2; - for (int i = 0; i < palette.Length; i++) - { - float r = colorMap[rOffset + i] * InvMax; - float g = colorMap[gOffset + i] * InvMax; - float b = colorMap[bOffset + i] * InvMax; - palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f)); - } - - return palette; + for (int i = 0; i < palette.Length; i++) + { + float r = colorMap[rOffset + i] * InvMax; + float g = colorMap[gOffset + i] * InvMax; + float b = colorMap[bOffset + i] * InvMax; + palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f)); } + + return palette; } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index e3b0e077a7..8ca45c9392 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -1,73 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 16 bits for each channel. +/// +internal class Rgb161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly Configuration configuration; + /// - /// Implements the 'RGB' photometric interpretation with 16 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgb161616TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// The configuration. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb161616TiffColor(Configuration configuration, bool isBigEndian) { - private readonly bool isBigEndian; + this.configuration = configuration; + this.isBigEndian = isBigEndian; + } - private readonly Configuration configuration; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb161616TiffColor(Configuration configuration, bool isBigEndian) - { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - } + int offset = 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + for (int y = top; y < top + height; y++) { - Rgba64 rgba = TiffUtils.Rgba64Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int offset = 0; - - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; + ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } - else - { - int byteCount = pixelRow.Length * 6; - PixelOperations.Instance.FromRgb48Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); + } + else + { + int byteCount = pixelRow.Length * 6; + PixelOperations.Instance.FromRgb48Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - offset += byteCount; - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 4c19a6a016..08fb6d8bea 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -1,69 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit. +/// +internal class Rgb16PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 16 bit. + /// Initializes a new instance of the class. /// - internal class Rgb16PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) { - private readonly bool isBigEndian; + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - Rgba64 rgba = TiffUtils.Rgba64Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); - offset += 2; + offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); - offset += 2; + offset += 2; - pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 10c61a6d0e..027dcce3bd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -1,79 +1,77 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 24 bits for each channel. +/// +internal class Rgb242424TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 24 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgb242424TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + int offset = 0; + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + Span bufferSpan = buffer[bufferStartIdx..]; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - int offset = 0; - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - Span bufferSpan = buffer[bufferStartIdx..]; - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index 0746610951..eba29a7f70 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -1,77 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit. +/// +internal class Rgb24PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 24 bit. + /// Initializes a new instance of the class. /// - internal class Rgb24PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) { - private readonly bool isBigEndian; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span bufferSpan = buffer[bufferStartIdx..]; - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span bufferSpan = buffer[bufferStartIdx..]; - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs index 99691f4145..79f66c1431 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs @@ -1,70 +1,68 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 32 bits for each channel. +/// +internal class Rgb323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 32 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgb323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + int offset = 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - int offset = 0; + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs index 3bcbf605c5..472697dd5e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs @@ -1,68 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 32 bit. +/// +internal class Rgb32PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 'Planar' layout for each color channel with 32 bit. + /// Initializes a new instance of the class. /// - internal class Rgb32PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgb32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) { - private readonly bool isBigEndian; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgb32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); - ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); - ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); + ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); - offset += 4; + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); - ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); - ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); + ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); - offset += 4; + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs index 04a4553848..7c6a4a0ec5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -1,59 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation for 4 bits per color channel images. +/// +internal class Rgb444TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation for 4 bits per color channel images. - /// - internal class Rgb444TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + var color = default(TPixel); - int offset = 0; + int offset = 0; - var bgra = default(Bgra4444); - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y); + var bgra = default(Bgra4444); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y); - for (int x = left; x < left + width; x += 2) + for (int x = left; x < left + width; x += 2) + { + byte r = (byte)((data[offset] & 0xF0) >> 4); + byte g = (byte)(data[offset] & 0xF); + offset++; + byte b = (byte)((data[offset] & 0xF0) >> 4); + + bgra.PackedValue = ToBgraPackedValue(b, g, r); + color.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x] = color; + if (x + 1 >= pixelRow.Length) { - byte r = (byte)((data[offset] & 0xF0) >> 4); - byte g = (byte)(data[offset] & 0xF); - offset++; - byte b = (byte)((data[offset] & 0xF0) >> 4); - - bgra.PackedValue = ToBgraPackedValue(b, g, r); - color.FromScaledVector4(bgra.ToScaledVector4()); - pixelRow[x] = color; - if (x + 1 >= pixelRow.Length) - { - offset++; - break; - } - - r = (byte)(data[offset] & 0xF); offset++; - g = (byte)((data[offset] & 0xF0) >> 4); - b = (byte)(data[offset] & 0xF); - offset++; - - bgra.PackedValue = ToBgraPackedValue(b, g, r); - color.FromScaledVector4(bgra.ToScaledVector4()); - pixelRow[x + 1] = color; + break; } + + r = (byte)(data[offset] & 0xF); + offset++; + g = (byte)((data[offset] & 0xF0) >> 4); + b = (byte)(data[offset] & 0xF); + offset++; + + bgra.PackedValue = ToBgraPackedValue(b, g, r); + color.FromScaledVector4(bgra.ToScaledVector4()); + pixelRow[x + 1] = color; } } - - private static ushort ToBgraPackedValue(byte b, byte g, byte r) => (ushort)(b | (g << 4) | (r << 8) | (0xF << 12)); } + + private static ushort ToBgraPackedValue(byte b, byte g, byte r) => (ushort)(b | (g << 4) | (r << 8) | (0xF << 12)); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs index e77bba4639..feefbe551c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images). +/// +internal class Rgb888TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images). - /// - internal class Rgb888TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly Configuration configuration; + private readonly Configuration configuration; - public Rgb888TiffColor(Configuration configuration) => this.configuration = configuration; + public Rgb888TiffColor(Configuration configuration) => this.configuration = configuration; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - int offset = 0; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + int offset = 0; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length * 3; - PixelOperations.Instance.FromRgb24Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + int byteCount = pixelRow.Length * 3; + PixelOperations.Instance.FromRgb24Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - offset += byteCount; - } + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index b26d0d62f6..31a53b65d0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -1,84 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 32 bits for each channel. +/// +internal class RgbFloat323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with 32 bits for each channel. + /// Initializes a new instance of the class. /// - internal class RgbFloat323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public RgbFloat323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public RgbFloat323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + int offset = 0; + byte[] buffer = new byte[4]; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - int offset = 0; - byte[] buffer = new byte[4]; + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float r = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float g = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float b = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(r, g, b, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(r, g, b, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - float r = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - float g = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - float b = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(r, g, b, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(r, g, b, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index 4aa573de8b..0b822f5a0f 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -1,78 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). +/// +internal class RgbPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths). - /// - internal class RgbPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly float rFactor; + private readonly float rFactor; - private readonly float gFactor; + private readonly float gFactor; - private readonly float bFactor; + private readonly float bFactor; - private readonly ushort bitsPerSampleR; + private readonly ushort bitsPerSampleR; - private readonly ushort bitsPerSampleG; + private readonly ushort bitsPerSampleG; - private readonly ushort bitsPerSampleB; + private readonly ushort bitsPerSampleB; - public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; + public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - } + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + } - /// - /// Decodes pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); - var rBitReader = new BitReader(data[0].GetSpan()); - var gBitReader = new BitReader(data[1].GetSpan()); - var bBitReader = new BitReader(data[2].GetSpan()); + var rBitReader = new BitReader(data[0].GetSpan()); + var gBitReader = new BitReader(data[1].GetSpan()); + var bBitReader = new BitReader(data[2].GetSpan()); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - - color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); - pixelRow[x] = color; - } + float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - rBitReader.NextRow(); - gBitReader.NextRow(); - bBitReader.NextRow(); + color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); + pixelRow[x] = color; } + + rBitReader.NextRow(); + gBitReader.NextRow(); + bBitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index dfec742af9..dcaab94a6a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -1,65 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation (for all bit depths). +/// +internal class RgbTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation (for all bit depths). - /// - internal class RgbTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly float rFactor; + private readonly float rFactor; - private readonly float gFactor; + private readonly float gFactor; - private readonly float bFactor; + private readonly float bFactor; - private readonly ushort bitsPerSampleR; + private readonly ushort bitsPerSampleR; - private readonly ushort bitsPerSampleG; + private readonly ushort bitsPerSampleG; - private readonly ushort bitsPerSampleB; + private readonly ushort bitsPerSampleB; - public RgbTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; + public RgbTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - } + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); - var bitReader = new BitReader(data); + var bitReader = new BitReader(data); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); - pixelRow[x] = color; - } - - bitReader.NextRow(); + color.FromScaledVector4(new Vector4(r, g, b, 1.0f)); + pixelRow[x] = color; } + + bitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs index 0d934b36b7..0e060bed31 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs @@ -1,95 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel. +/// +internal class Rgba16161616TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly Configuration configuration; + + private readonly MemoryAllocator memoryAllocator; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 16 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgba16161616TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// The configuration. + /// The memory allocator. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + /// The type of the extra samples. + public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian) { - private readonly bool isBigEndian; - - private readonly Configuration configuration; + this.configuration = configuration; + this.isBigEndian = isBigEndian; + this.memoryAllocator = memoryAllocator; + this.extraSamplesType = extraSamplesType; + } - private readonly MemoryAllocator memoryAllocator; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - private readonly TiffExtraSampleType? extraSamplesType; + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The memory allocator. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - /// The type of the extra samples. - public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian) + using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; + Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; + for (int y = top; y < top + height; y++) { - this.configuration = configuration; - this.isBigEndian = isBigEndian; - this.memoryAllocator = memoryAllocator; - this.extraSamplesType = extraSamplesType; - } + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - Rgba64 rgba = TiffUtils.Rgba64Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - - using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; - Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; - ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); - offset += 2; + ulong r = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong g = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; + ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); + offset += 2; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : - TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); } - else - { - int byteCount = pixelRow.Length * 8; - PixelOperations.Instance.FromRgba64Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - if (hasAssociatedAlpha) - { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); - } + } + else + { + int byteCount = pixelRow.Length * 8; + PixelOperations.Instance.FromRgba64Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); - offset += byteCount; + if (hasAssociatedAlpha) + { + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); } + + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs index fdcf0d8885..7426544d29 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs @@ -1,84 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit. +/// +internal class Rgba16PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 16 bit. + /// Initializes a new instance of the class. /// - internal class Rgba16PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// The extra samples type. + /// If set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) { - private readonly bool isBigEndian; + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } - private readonly TiffExtraSampleType? extraSamplesType; + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + Rgba64 rgba = TiffUtils.Rgba64Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// If set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; + for (int y = top; y < top + height; y++) { - Rgba64 rgba = TiffUtils.Rgba64Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); - ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortBigEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortBigEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortBigEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); - offset += 2; + offset += 2; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : - TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); - ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); - ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); - ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2)); + ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); + ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); + ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); + ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2)); - offset += 2; + offset += 2; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : - TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) : + TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs index be10495e75..eba60679a2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel. +/// +internal class Rgba24242424TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 24 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgba24242424TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// The type of the extra samples. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) + { + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - private readonly bool isBigEndian; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - private readonly TiffExtraSampleType? extraSamplesType; + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; - /// - /// Initializes a new instance of the class. - /// - /// The type of the extra samples. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + Span bufferSpan = buffer[bufferStartIdx..]; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; + if (this.isBigEndian) + { + for (int x = 0; x < pixelRow.Length; x++) + { + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - Span bufferSpan = buffer[bufferStartIdx..]; - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - if (this.isBigEndian) - { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; - - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); - } + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; + + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; - - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; - - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); - } + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; + + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs index 45f530d627..1b842a79be 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit. +/// +internal class Rgba24PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout for each color channel with 24 bit. + /// Initializes a new instance of the class. /// - internal class Rgba24PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// The extra samples type. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) { - private readonly bool isBigEndian; + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } - private readonly TiffExtraSampleType? extraSamplesType; + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); + Span bufferSpan = buffer[bufferStartIdx..]; - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - Span bufferSpan = buffer[bufferStartIdx..]; - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); - alphaData.Slice(offset, 3).CopyTo(bufferSpan); - ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntBigEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntBigEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntBigEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + offset += 3; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - redData.Slice(offset, 3).CopyTo(bufferSpan); - ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); - greenData.Slice(offset, 3).CopyTo(bufferSpan); - ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); - blueData.Slice(offset, 3).CopyTo(bufferSpan); - ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); - alphaData.Slice(offset, 3).CopyTo(bufferSpan); - ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); + redData.Slice(offset, 3).CopyTo(bufferSpan); + ulong r = TiffUtils.ConvertToUIntLittleEndian(buffer); + greenData.Slice(offset, 3).CopyTo(bufferSpan); + ulong g = TiffUtils.ConvertToUIntLittleEndian(buffer); + blueData.Slice(offset, 3).CopyTo(bufferSpan); + ulong b = TiffUtils.ConvertToUIntLittleEndian(buffer); + alphaData.Slice(offset, 3).CopyTo(bufferSpan); + ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + offset += 3; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs index 1951176228..2193f2e817 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs @@ -1,89 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. +/// +internal class Rgba32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// Initializes a new instance of the class. /// - internal class Rgba32323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// The type of the extra samples. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) { - private readonly bool isBigEndian; + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } - private readonly TiffExtraSampleType? extraSamplesType; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// The type of the extra samples. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong r = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong g = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong b = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong r = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong g = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong b = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs index 8ad9e59bcc..7d047cf7fd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit. +/// +internal class Rgba32PlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + + private readonly TiffExtraSampleType? extraSamplesType; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and a 'Planar' layout for each color channel with 32 bit. + /// Initializes a new instance of the class. /// - internal class Rgba32PlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + /// The extra samples type. + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) { - private readonly bool isBigEndian; + this.extraSamplesType = extraSamplesType; + this.isBigEndian = isBigEndian; + } - private readonly TiffExtraSampleType? extraSamplesType; + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - /// Initializes a new instance of the class. - /// - /// The extra samples type. - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian) - { - this.extraSamplesType = extraSamplesType; - this.isBigEndian = isBigEndian; - } + Span redData = data[0].GetSpan(); + Span greenData = data[1].GetSpan(); + Span blueData = data[2].GetSpan(); + Span alphaData = data[3].GetSpan(); - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - Span redData = data[0].GetSpan(); - Span greenData = data[1].GetSpan(); - Span blueData = data[2].GetSpan(); - Span alphaData = data[3].GetSpan(); - - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); - ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); - ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); - ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4)); + ulong r = TiffUtils.ConvertToUIntBigEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntBigEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntBigEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntBigEndian(alphaData.Slice(offset, 4)); - offset += 4; + offset += 4; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); - ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); - ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); - ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4)); + ulong r = TiffUtils.ConvertToUIntLittleEndian(redData.Slice(offset, 4)); + ulong g = TiffUtils.ConvertToUIntLittleEndian(greenData.Slice(offset, 4)); + ulong b = TiffUtils.ConvertToUIntLittleEndian(blueData.Slice(offset, 4)); + ulong a = TiffUtils.ConvertToUIntLittleEndian(alphaData.Slice(offset, 4)); - offset += 4; + offset += 4; - pixelRow[x] = hasAssociatedAlpha ? - TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : - TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); - } + pixelRow[x] = hasAssociatedAlpha ? + TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) : + TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs index 4a5ca09019..6e844f435b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs @@ -1,62 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel. +/// +internal class Rgba8888TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and 8 bits per channel. - /// - internal class Rgba8888TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly Configuration configuration; + private readonly Configuration configuration; - private readonly MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; - private readonly TiffExtraSampleType? extraSamplesType; + private readonly TiffExtraSampleType? extraSamplesType; - public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType) - { - this.configuration = configuration; - this.memoryAllocator = memoryAllocator; - this.extraSamplesType = extraSamplesType; - } + public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType) + { + this.configuration = configuration; + this.memoryAllocator = memoryAllocator; + this.extraSamplesType = extraSamplesType; + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + int offset = 0; + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; + Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; + for (int y = top; y < top + height; y++) { - int offset = 0; - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - using IMemoryOwner vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null; - Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + int byteCount = pixelRow.Length * 4; + PixelOperations.Instance.FromRgba32Bytes( + this.configuration, + data.Slice(offset, byteCount), + pixelRow, + pixelRow.Length); + + if (hasAssociatedAlpha) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - int byteCount = pixelRow.Length * 4; - PixelOperations.Instance.FromRgba32Bytes( - this.configuration, - data.Slice(offset, byteCount), - pixelRow, - pixelRow.Length); - - if (hasAssociatedAlpha) - { - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); - } - - offset += byteCount; + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); } + + offset += byteCount; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs index 6f0af33e8d..920f9fdc43 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs @@ -1,93 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. +/// +internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 32 bits for each channel. + /// Initializes a new instance of the class. /// - internal class RgbaFloat32323232TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public RgbaFloat32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + int offset = 0; + byte[] buffer = new byte[4]; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - int offset = 0; - byte[] buffer = new byte[4]; + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int y = top; y < top + height; y++) + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float r = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float g = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float b = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float a = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(r, g, b, a); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(r, g, b, a); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - float r = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float r = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - float g = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float g = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - float b = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float b = BitConverter.ToSingle(buffer, 0); + offset += 4; - data.Slice(offset, 4).CopyTo(buffer); - float a = BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float a = BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(r, g, b, a); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(r, g, b, a); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs index 6fa412e8c1..63aa64d867 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths). +/// +internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation with an alpha channel and with 'Planar' layout (for all bit depths). - /// - internal class RgbaPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly float rFactor; + private readonly float rFactor; - private readonly float gFactor; + private readonly float gFactor; - private readonly float bFactor; + private readonly float bFactor; - private readonly float aFactor; + private readonly float aFactor; - private readonly ushort bitsPerSampleR; + private readonly ushort bitsPerSampleR; - private readonly ushort bitsPerSampleG; + private readonly ushort bitsPerSampleG; - private readonly ushort bitsPerSampleB; + private readonly ushort bitsPerSampleB; - private readonly ushort bitsPerSampleA; + private readonly ushort bitsPerSampleA; - private readonly TiffExtraSampleType? extraSampleType; + private readonly TiffExtraSampleType? extraSampleType; - public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - this.bitsPerSampleA = bitsPerSample.Channel3; + public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; - this.extraSampleType = extraSampleType; - } + this.extraSampleType = extraSampleType; + } - /// - /// Decodes pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); - bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData; + /// + /// Decodes pixel data using the current photometric interpretation. + /// + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData; - var rBitReader = new BitReader(data[0].GetSpan()); - var gBitReader = new BitReader(data[1].GetSpan()); - var bBitReader = new BitReader(data[2].GetSpan()); - var aBitReader = new BitReader(data[3].GetSpan()); + var rBitReader = new BitReader(data[0].GetSpan()); + var gBitReader = new BitReader(data[1].GetSpan()); + var bBitReader = new BitReader(data[2].GetSpan()); + var aBitReader = new BitReader(data[3].GetSpan()); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) + float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; + + var vec = new Vector4(r, g, b, a); + if (hasAssociatedAlpha) + { + color = TiffUtils.UnPremultiply(ref vec, color); + } + else { - float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; - - var vec = new Vector4(r, g, b, a); - if (hasAssociatedAlpha) - { - color = TiffUtils.UnPremultiply(ref vec, color); - } - else - { - color.FromScaledVector4(vec); - } - - pixelRow[x] = color; + color.FromScaledVector4(vec); } - rBitReader.NextRow(); - gBitReader.NextRow(); - bBitReader.NextRow(); - aBitReader.NextRow(); + pixelRow[x] = color; } + + rBitReader.NextRow(); + gBitReader.NextRow(); + bBitReader.NextRow(); + aBitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs index 6e6c5ddcd3..0ea8a87fcc 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs @@ -1,86 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths). +/// +internal class RgbaTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'RGB' photometric interpretation with alpha channel (for all bit depths). - /// - internal class RgbaTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly float rFactor; + private readonly float rFactor; - private readonly float gFactor; + private readonly float gFactor; - private readonly float bFactor; + private readonly float bFactor; - private readonly float aFactor; + private readonly float aFactor; - private readonly ushort bitsPerSampleR; + private readonly ushort bitsPerSampleR; - private readonly ushort bitsPerSampleG; + private readonly ushort bitsPerSampleG; - private readonly ushort bitsPerSampleB; + private readonly ushort bitsPerSampleB; - private readonly ushort bitsPerSampleA; + private readonly ushort bitsPerSampleA; - private readonly TiffExtraSampleType? extraSamplesType; + private readonly TiffExtraSampleType? extraSamplesType; - public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) - { - this.bitsPerSampleR = bitsPerSample.Channel0; - this.bitsPerSampleG = bitsPerSample.Channel1; - this.bitsPerSampleB = bitsPerSample.Channel2; - this.bitsPerSampleA = bitsPerSample.Channel3; + public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample) + { + this.bitsPerSampleR = bitsPerSample.Channel0; + this.bitsPerSampleG = bitsPerSample.Channel1; + this.bitsPerSampleB = bitsPerSample.Channel2; + this.bitsPerSampleA = bitsPerSample.Channel3; - this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; - this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; - this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; - this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; + this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; + this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; + this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; + this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; - this.extraSamplesType = extraSampleType; - } + this.extraSamplesType = extraSampleType; + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); - var bitReader = new BitReader(data); + var bitReader = new BitReader(data); - bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; + bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData; - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) + float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; + float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; + float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; + float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; + + var vec = new Vector4(r, g, b, a); + if (hasAssociatedAlpha) { - float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; - float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; - float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; - float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; - - var vec = new Vector4(r, g, b, a); - if (hasAssociatedAlpha) - { - pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color); - } - else - { - color.FromScaledVector4(vec); - pixelRow[x] = color; - } + pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color); + } + else + { + color.FromScaledVector4(vec); + pixelRow[x] = color; } - - bitReader.NextRow(); } + + bitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs index 7b9160a1e6..e80aaa3b83 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs @@ -1,28 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// The base class for photometric interpretation decoders. +/// +/// The pixel format. +internal abstract class TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { /// - /// The base class for photometric interpretation decoders. + /// Decodes source raw pixel data using the current photometric interpretation. /// - /// The pixel format. - internal abstract class TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - /// - /// Decodes source raw pixel data using the current photometric interpretation. - /// - /// The buffer to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public abstract void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height); - } + /// The buffer to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public abstract void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs index 02e7833851..555b126dc6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBasePlanarColorDecoder{TPixel}.cs @@ -5,24 +5,23 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// The base class for planar color decoders. +/// +/// The pixel format. +internal abstract class TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { /// - /// The base class for planar color decoders. + /// Decodes source raw pixel data using the current photometric interpretation. /// - /// The pixel format. - internal abstract class TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel - { - /// - /// Decodes source raw pixel data using the current photometric interpretation. - /// - /// The buffers to read image data from. - /// The image buffer to write pixels to. - /// The x-coordinate of the left-hand side of the image block. - /// The y-coordinate of the top of the image block. - /// The width of the image block. - /// The height of the image block. - public abstract void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height); - } + /// The buffers to read image data from. + /// The image buffer to write pixels to. + /// The x-coordinate of the left-hand side of the image block. + /// The y-coordinate of the top of the image block. + /// The width of the image block. + /// The height of the image block. + public abstract void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height); } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs index 2ef46dd84e..2ad1d8677b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs @@ -4,462 +4,461 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +internal static class TiffColorDecoderFactory + where TPixel : unmanaged, IPixel { - internal static class TiffColorDecoderFactory - where TPixel : unmanaged, IPixel + public static TiffBaseColorDecoder Create( + Configuration configuration, + MemoryAllocator memoryAllocator, + TiffColorType colorType, + TiffBitsPerSample bitsPerSample, + TiffExtraSampleType? extraSampleType, + ushort[] colorMap, + Rational[] referenceBlackAndWhite, + Rational[] ycbcrCoefficients, + ushort[] ycbcrSubSampling, + ByteOrder byteOrder) { - public static TiffBaseColorDecoder Create( - Configuration configuration, - MemoryAllocator memoryAllocator, - TiffColorType colorType, - TiffBitsPerSample bitsPerSample, - TiffExtraSampleType? extraSampleType, - ushort[] colorMap, - Rational[] referenceBlackAndWhite, - Rational[] ycbcrCoefficients, - ushort[] ycbcrSubSampling, - ByteOrder byteOrder) + switch (colorType) { - switch (colorType) - { - case TiffColorType.WhiteIsZero: - DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZeroTiffColor(bitsPerSample); - - case TiffColorType.WhiteIsZero1: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero1TiffColor(); - - case TiffColorType.WhiteIsZero4: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero4TiffColor(); - - case TiffColorType.WhiteIsZero8: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero8TiffColor(); - - case TiffColorType.WhiteIsZero16: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero24: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero32: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.WhiteIsZero32Float: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new WhiteIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero: - DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZeroTiffColor(bitsPerSample); - - case TiffColorType.BlackIsZero1: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero1TiffColor(); - - case TiffColorType.BlackIsZero4: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero4TiffColor(); - - case TiffColorType.BlackIsZero8: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero8TiffColor(configuration); - - case TiffColorType.BlackIsZero16: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero16TiffColor(configuration, byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero24: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero32: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.BlackIsZero32Float: - DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new BlackIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgb222: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 2 - && bitsPerSample.Channel1 == 2 - && bitsPerSample.Channel0 == 2, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba2222: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 2 - && bitsPerSample.Channel2 == 2 - && bitsPerSample.Channel1 == 2 - && bitsPerSample.Channel0 == 2, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb333: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 3 - && bitsPerSample.Channel1 == 3 - && bitsPerSample.Channel0 == 3, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba3333: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 3 - && bitsPerSample.Channel2 == 3 - && bitsPerSample.Channel1 == 3 - && bitsPerSample.Channel0 == 3, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb444: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 4 - && bitsPerSample.Channel1 == 4 - && bitsPerSample.Channel0 == 4, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb444TiffColor(); - - case TiffColorType.Rgba4444: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 4 - && bitsPerSample.Channel2 == 4 - && bitsPerSample.Channel1 == 4 - && bitsPerSample.Channel0 == 4, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb555: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 5 - && bitsPerSample.Channel1 == 5 - && bitsPerSample.Channel0 == 5, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba5555: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 5 - && bitsPerSample.Channel2 == 5 - && bitsPerSample.Channel1 == 5 - && bitsPerSample.Channel0 == 5, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb666: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 6 - && bitsPerSample.Channel1 == 6 - && bitsPerSample.Channel0 == 6, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba6666: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 6 - && bitsPerSample.Channel2 == 6 - && bitsPerSample.Channel1 == 6 - && bitsPerSample.Channel0 == 6, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb888: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb888TiffColor(configuration); - - case TiffColorType.Rgba8888: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 8 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba8888TiffColor(configuration, memoryAllocator, extraSampleType); - - case TiffColorType.Rgb101010: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 10 - && bitsPerSample.Channel1 == 10 - && bitsPerSample.Channel0 == 10, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba10101010: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 10 - && bitsPerSample.Channel2 == 10 - && bitsPerSample.Channel1 == 10 - && bitsPerSample.Channel0 == 10, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb121212: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 12 - && bitsPerSample.Channel1 == 12 - && bitsPerSample.Channel0 == 12, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba12121212: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 12 - && bitsPerSample.Channel2 == 12 - && bitsPerSample.Channel1 == 12 - && bitsPerSample.Channel0 == 12, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb141414: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 14 - && bitsPerSample.Channel1 == 14 - && bitsPerSample.Channel0 == 14, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbTiffColor(bitsPerSample); - - case TiffColorType.Rgba14141414: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 14 - && bitsPerSample.Channel2 == 14 - && bitsPerSample.Channel1 == 14 - && bitsPerSample.Channel0 == 14, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaTiffColor(extraSampleType, bitsPerSample); - - case TiffColorType.Rgb161616: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 16 - && bitsPerSample.Channel1 == 16 - && bitsPerSample.Channel0 == 16, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba16161616: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 16 - && bitsPerSample.Channel2 == 16 - && bitsPerSample.Channel1 == 16 - && bitsPerSample.Channel0 == 16, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16161616TiffColor(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb242424: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 24 - && bitsPerSample.Channel1 == 24 - && bitsPerSample.Channel0 == 24, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba24242424: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 24 - && bitsPerSample.Channel2 == 24 - && bitsPerSample.Channel1 == 24 - && bitsPerSample.Channel0 == 24, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24242424TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgb323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.Rgba32323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 32 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32323232TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.RgbFloat323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbFloat323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.RgbaFloat32323232: - DebugGuard.IsTrue( - bitsPerSample.Channels == 4 - && bitsPerSample.Channel3 == 32 - && bitsPerSample.Channel2 == 32 - && bitsPerSample.Channel1 == 32 - && bitsPerSample.Channel0 == 32, - "bitsPerSample"); - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); - - case TiffColorType.PaletteColor: - DebugGuard.NotNull(colorMap, "colorMap"); - return new PaletteTiffColor(bitsPerSample, colorMap); - - case TiffColorType.YCbCr: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - return new YCbCrTiffColor(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); - - case TiffColorType.CieLab: - DebugGuard.IsTrue( - bitsPerSample.Channels == 3 - && bitsPerSample.Channel2 == 8 - && bitsPerSample.Channel1 == 8 - && bitsPerSample.Channel0 == 8, - "bitsPerSample"); - return new CieLabTiffColor(); - - default: - throw TiffThrowHelper.InvalidColorType(colorType.ToString()); - } + case TiffColorType.WhiteIsZero: + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZeroTiffColor(bitsPerSample); + + case TiffColorType.WhiteIsZero1: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero1TiffColor(); + + case TiffColorType.WhiteIsZero4: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero4TiffColor(); + + case TiffColorType.WhiteIsZero8: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero8TiffColor(); + + case TiffColorType.WhiteIsZero16: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero16TiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.WhiteIsZero24: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.WhiteIsZero32: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.WhiteIsZero32Float: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new WhiteIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.BlackIsZero: + DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZeroTiffColor(bitsPerSample); + + case TiffColorType.BlackIsZero1: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero1TiffColor(); + + case TiffColorType.BlackIsZero4: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero4TiffColor(); + + case TiffColorType.BlackIsZero8: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero8TiffColor(configuration); + + case TiffColorType.BlackIsZero16: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 16, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero16TiffColor(configuration, byteOrder == ByteOrder.BigEndian); + + case TiffColorType.BlackIsZero24: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 24, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero24TiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.BlackIsZero32: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero32TiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.BlackIsZero32Float: + DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 32, "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new BlackIsZero32FloatTiffColor(byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgb: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgb222: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba2222: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 2 + && bitsPerSample.Channel2 == 2 + && bitsPerSample.Channel1 == 2 + && bitsPerSample.Channel0 == 2, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba3333: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 3 + && bitsPerSample.Channel2 == 3 + && bitsPerSample.Channel1 == 3 + && bitsPerSample.Channel0 == 3, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb444: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb444TiffColor(); + + case TiffColorType.Rgba4444: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 4 + && bitsPerSample.Channel2 == 4 + && bitsPerSample.Channel1 == 4 + && bitsPerSample.Channel0 == 4, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba5555: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 5 + && bitsPerSample.Channel2 == 5 + && bitsPerSample.Channel1 == 5 + && bitsPerSample.Channel0 == 5, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba6666: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 6 + && bitsPerSample.Channel2 == 6 + && bitsPerSample.Channel1 == 6 + && bitsPerSample.Channel0 == 6, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb888: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb888TiffColor(configuration); + + case TiffColorType.Rgba8888: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 8 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba8888TiffColor(configuration, memoryAllocator, extraSampleType); + + case TiffColorType.Rgb101010: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba10101010: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 10 + && bitsPerSample.Channel2 == 10 + && bitsPerSample.Channel1 == 10 + && bitsPerSample.Channel0 == 10, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb121212: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba12121212: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 12 + && bitsPerSample.Channel2 == 12 + && bitsPerSample.Channel1 == 12 + && bitsPerSample.Channel0 == 12, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb141414: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbTiffColor(bitsPerSample); + + case TiffColorType.Rgba14141414: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 14 + && bitsPerSample.Channel2 == 14 + && bitsPerSample.Channel1 == 14 + && bitsPerSample.Channel0 == 14, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaTiffColor(extraSampleType, bitsPerSample); + + case TiffColorType.Rgb161616: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb161616TiffColor(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgba16161616: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 16 + && bitsPerSample.Channel2 == 16 + && bitsPerSample.Channel1 == 16 + && bitsPerSample.Channel0 == 16, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16161616TiffColor(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgb242424: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 24 + && bitsPerSample.Channel1 == 24 + && bitsPerSample.Channel0 == 24, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb242424TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgba24242424: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 24 + && bitsPerSample.Channel2 == 24 + && bitsPerSample.Channel1 == 24 + && bitsPerSample.Channel0 == 24, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24242424TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgb323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.Rgba32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32323232TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.RgbFloat323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbFloat323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.RgbaFloat32323232: + DebugGuard.IsTrue( + bitsPerSample.Channels == 4 + && bitsPerSample.Channel3 == 32 + && bitsPerSample.Channel2 == 32 + && bitsPerSample.Channel1 == 32 + && bitsPerSample.Channel0 == 32, + "bitsPerSample"); + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaFloat32323232TiffColor(isBigEndian: byteOrder == ByteOrder.BigEndian); + + case TiffColorType.PaletteColor: + DebugGuard.NotNull(colorMap, "colorMap"); + return new PaletteTiffColor(bitsPerSample, colorMap); + + case TiffColorType.YCbCr: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + return new YCbCrTiffColor(memoryAllocator, referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); + + case TiffColorType.CieLab: + DebugGuard.IsTrue( + bitsPerSample.Channels == 3 + && bitsPerSample.Channel2 == 8 + && bitsPerSample.Channel1 == 8 + && bitsPerSample.Channel0 == 8, + "bitsPerSample"); + return new CieLabTiffColor(); + + default: + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } + } - public static TiffBasePlanarColorDecoder CreatePlanar( - TiffColorType colorType, - TiffBitsPerSample bitsPerSample, - TiffExtraSampleType? extraSampleType, - ushort[] colorMap, - Rational[] referenceBlackAndWhite, - Rational[] ycbcrCoefficients, - ushort[] ycbcrSubSampling, - ByteOrder byteOrder) + public static TiffBasePlanarColorDecoder CreatePlanar( + TiffColorType colorType, + TiffBitsPerSample bitsPerSample, + TiffExtraSampleType? extraSampleType, + ushort[] colorMap, + Rational[] referenceBlackAndWhite, + Rational[] ycbcrCoefficients, + ushort[] ycbcrSubSampling, + ByteOrder byteOrder) + { + switch (colorType) { - switch (colorType) - { - case TiffColorType.Rgb888Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbPlanarTiffColor(bitsPerSample); + case TiffColorType.Rgb888Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbPlanarTiffColor(bitsPerSample); - case TiffColorType.Rgba8888Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new RgbaPlanarTiffColor(extraSampleType, bitsPerSample); + case TiffColorType.Rgba8888Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new RgbaPlanarTiffColor(extraSampleType, bitsPerSample); - case TiffColorType.YCbCrPlanar: - return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); + case TiffColorType.YCbCrPlanar: + return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); - case TiffColorType.CieLabPlanar: - return new CieLabPlanarTiffColor(); + case TiffColorType.CieLabPlanar: + return new CieLabPlanarTiffColor(); - case TiffColorType.Rgb161616Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb161616Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb16PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - case TiffColorType.Rgba16161616Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba16PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba16161616Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba16PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - case TiffColorType.Rgb242424Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb242424Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb24PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - case TiffColorType.Rgba24242424Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba24PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba24242424Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba24PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - case TiffColorType.Rgb323232Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgb323232Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgb32PlanarTiffColor(byteOrder == ByteOrder.BigEndian); - case TiffColorType.Rgba32323232Planar: - DebugGuard.IsTrue(colorMap == null, "colorMap"); - return new Rgba32PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); + case TiffColorType.Rgba32323232Planar: + DebugGuard.IsTrue(colorMap == null, "colorMap"); + return new Rgba32PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian); - default: - throw TiffThrowHelper.InvalidColorType(colorType.ToString()); - } + default: + throw TiffThrowHelper.InvalidColorType(colorType.ToString()); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs index cce5c95ad5..5857fce811 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs @@ -1,291 +1,290 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Provides enumeration of the various TIFF photometric interpretation implementation types. +/// +internal enum TiffColorType { /// - /// Provides enumeration of the various TIFF photometric interpretation implementation types. - /// - internal enum TiffColorType - { - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. - /// - BlackIsZero, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images. - /// - BlackIsZero1, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images. - /// - BlackIsZero4, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images. - /// - BlackIsZero8, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images. - /// - BlackIsZero16, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 24-bit images. - /// - BlackIsZero24, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 32-bit images. - /// - BlackIsZero32, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. - /// - BlackIsZero32Float, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. - /// - WhiteIsZero, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images. - /// - WhiteIsZero1, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images. - /// - WhiteIsZero4, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images. - /// - WhiteIsZero8, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images. - /// - WhiteIsZero16, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 24-bit images. - /// - WhiteIsZero24, - - /// - /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 32-bit images. - /// - WhiteIsZero32, - - /// - /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. - /// - WhiteIsZero32Float, - - /// - /// Palette-color. - /// - PaletteColor, - - /// - /// RGB Full Color. - /// - Rgb, - - /// - /// RGB color image with 2 bits for each channel. - /// - Rgb222, - - /// - /// RGBA color image with 2 bits for each channel. - /// - Rgba2222, - - /// - /// RGB color image with 3 bits for each channel. - /// - Rgb333, - - /// - /// RGBA color image with 3 bits for each channel. - /// - Rgba3333, - - /// - /// RGB color image with 4 bits for each channel. - /// - Rgb444, - - /// - /// RGBA color image with 4 bits for each channel. - /// - Rgba4444, - - /// - /// RGB color image with 5 bits for each channel. - /// - Rgb555, - - /// - /// RGBA color image with 5 bits for each channel. - /// - Rgba5555, - - /// - /// RGB color image with 6 bits for each channel. - /// - Rgb666, - - /// - /// RGBA color image with 6 bits for each channel. - /// - Rgba6666, - - /// - /// RGB Full Color. Optimized implementation for 8-bit images. - /// - Rgb888, - - /// - /// RGBA Full Color with 8-bit for each channel. - /// - Rgba8888, - - /// - /// RGB color image with 10 bits for each channel. - /// - Rgb101010, - - /// - /// RGBA color image with 10 bits for each channel. - /// - Rgba10101010, - - /// - /// RGB color image with 12 bits for each channel. - /// - Rgb121212, - - /// - /// RGBA color image with 12 bits for each channel. - /// - Rgba12121212, - - /// - /// RGB color image with 14 bits for each channel. - /// - Rgb141414, - - /// - /// RGBA color image with 14 bits for each channel. - /// - Rgba14141414, - - /// - /// RGB color image with 16 bits for each channel. - /// - Rgb161616, - - /// - /// RGBA color image with 16 bits for each channel. - /// - Rgba16161616, - - /// - /// RGB color image with 24 bits for each channel. - /// - Rgb242424, - - /// - /// RGBA color image with 24 bits for each channel. - /// - Rgba24242424, - - /// - /// RGB color image with 32 bits for each channel. - /// - Rgb323232, - - /// - /// RGBA color image with 32 bits for each channel. - /// - Rgba32323232, - - /// - /// RGB color image with 32 bits floats for each channel. - /// - RgbFloat323232, - - /// - /// RGBA color image with 32 bits floats for each channel. - /// - RgbaFloat32323232, - - /// - /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. - /// - Rgb888Planar, - - /// - /// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel. - /// - Rgba8888Planar, - - /// - /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. - /// - Rgb161616Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel. - /// - Rgba16161616Planar, - - /// - /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. - /// - Rgb242424Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel. - /// - Rgba24242424Planar, - - /// - /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. - /// - Rgb323232Planar, - - /// - /// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel. - /// - Rgba32323232Planar, - - /// - /// The pixels are stored in YCbCr format. - /// - YCbCr, - - /// - /// The pixels are stored in YCbCr format as planar. - /// - YCbCrPlanar, - - /// - /// The pixels are stored in CieLab format. - /// - CieLab, - - /// - /// The pixels are stored in CieLab format as planar. - /// - CieLabPlanar, - } + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. + /// + BlackIsZero, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images. + /// + BlackIsZero1, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images. + /// + BlackIsZero4, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images. + /// + BlackIsZero8, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 16-bit images. + /// + BlackIsZero16, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 24-bit images. + /// + BlackIsZero24, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 32-bit images. + /// + BlackIsZero32, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. + /// + BlackIsZero32Float, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// + WhiteIsZero, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images. + /// + WhiteIsZero1, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images. + /// + WhiteIsZero4, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images. + /// + WhiteIsZero8, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 16-bit images. + /// + WhiteIsZero16, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 24-bit images. + /// + WhiteIsZero24, + + /// + /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 32-bit images. + /// + WhiteIsZero32, + + /// + /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Pixel data is 32-bit float. + /// + WhiteIsZero32Float, + + /// + /// Palette-color. + /// + PaletteColor, + + /// + /// RGB Full Color. + /// + Rgb, + + /// + /// RGB color image with 2 bits for each channel. + /// + Rgb222, + + /// + /// RGBA color image with 2 bits for each channel. + /// + Rgba2222, + + /// + /// RGB color image with 3 bits for each channel. + /// + Rgb333, + + /// + /// RGBA color image with 3 bits for each channel. + /// + Rgba3333, + + /// + /// RGB color image with 4 bits for each channel. + /// + Rgb444, + + /// + /// RGBA color image with 4 bits for each channel. + /// + Rgba4444, + + /// + /// RGB color image with 5 bits for each channel. + /// + Rgb555, + + /// + /// RGBA color image with 5 bits for each channel. + /// + Rgba5555, + + /// + /// RGB color image with 6 bits for each channel. + /// + Rgb666, + + /// + /// RGBA color image with 6 bits for each channel. + /// + Rgba6666, + + /// + /// RGB Full Color. Optimized implementation for 8-bit images. + /// + Rgb888, + + /// + /// RGBA Full Color with 8-bit for each channel. + /// + Rgba8888, + + /// + /// RGB color image with 10 bits for each channel. + /// + Rgb101010, + + /// + /// RGBA color image with 10 bits for each channel. + /// + Rgba10101010, + + /// + /// RGB color image with 12 bits for each channel. + /// + Rgb121212, + + /// + /// RGBA color image with 12 bits for each channel. + /// + Rgba12121212, + + /// + /// RGB color image with 14 bits for each channel. + /// + Rgb141414, + + /// + /// RGBA color image with 14 bits for each channel. + /// + Rgba14141414, + + /// + /// RGB color image with 16 bits for each channel. + /// + Rgb161616, + + /// + /// RGBA color image with 16 bits for each channel. + /// + Rgba16161616, + + /// + /// RGB color image with 24 bits for each channel. + /// + Rgb242424, + + /// + /// RGBA color image with 24 bits for each channel. + /// + Rgba24242424, + + /// + /// RGB color image with 32 bits for each channel. + /// + Rgb323232, + + /// + /// RGBA color image with 32 bits for each channel. + /// + Rgba32323232, + + /// + /// RGB color image with 32 bits floats for each channel. + /// + RgbFloat323232, + + /// + /// RGBA color image with 32 bits floats for each channel. + /// + RgbaFloat32323232, + + /// + /// RGB Full Color. Planar configuration of data. 8 Bit per color channel. + /// + Rgb888Planar, + + /// + /// RGBA color image with an alpha channel. Planar configuration of data. 8 Bit per color channel. + /// + Rgba8888Planar, + + /// + /// RGB Full Color. Planar configuration of data. 16 Bit per color channel. + /// + Rgb161616Planar, + + /// + /// RGB Color with an alpha channel. Planar configuration of data. 16 Bit per color channel. + /// + Rgba16161616Planar, + + /// + /// RGB Full Color. Planar configuration of data. 24 Bit per color channel. + /// + Rgb242424Planar, + + /// + /// RGB Color with an alpha channel. Planar configuration of data. 24 Bit per color channel. + /// + Rgba24242424Planar, + + /// + /// RGB Full Color. Planar configuration of data. 32 Bit per color channel. + /// + Rgb323232Planar, + + /// + /// RGB Color with an alpha channel. Planar configuration of data. 32 Bit per color channel. + /// + Rgba32323232Planar, + + /// + /// The pixels are stored in YCbCr format. + /// + YCbCr, + + /// + /// The pixels are stored in YCbCr format as planar. + /// + YCbCrPlanar, + + /// + /// The pixels are stored in CieLab format. + /// + CieLab, + + /// + /// The pixels are stored in CieLab format as planar. + /// + CieLabPlanar, } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 05c80d41ed..f7fd55e523 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images. +/// +internal class WhiteIsZero16TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'WhiteIsZero' photometric interpretation for 16-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class WhiteIsZero16TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero16TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + L16 l16 = TiffUtils.L16Default; + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - L16 l16 = TiffUtils.L16Default; - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2))); - offset += 2; + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2))); + offset += 2; - pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); - } + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2))); - offset += 2; + ushort intensity = (ushort)(ushort.MaxValue - TiffUtils.ConvertToUShortLittleEndian(data.Slice(offset, 2))); + offset += 2; - pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); - } + pixelRow[x] = TiffUtils.ColorFromL16(l16, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs index 0b2009c402..a7519bc144 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs @@ -1,82 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images). +/// +internal class WhiteIsZero1TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images). - /// - internal class WhiteIsZero1TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - nint offset = 0; - var colorBlack = default(TPixel); - var colorWhite = default(TPixel); + nint offset = 0; + var colorBlack = default(TPixel); + var colorWhite = default(TPixel); - colorBlack.FromRgba32(Color.Black); - colorWhite.FromRgba32(Color.White); - ref byte dataRef = ref MemoryMarshal.GetReference(data); - for (nint y = top; y < top + height; y++) + colorBlack.FromRgba32(Color.Black); + colorWhite.FromRgba32(Color.White); + ref byte dataRef = ref MemoryMarshal.GetReference(data); + for (nint y = top; y < top + height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); + ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); + for (nint x = left; x < left + width; x += 8) { - Span pixelRowSpan = pixels.DangerousGetRowSpan((int)y); - ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan); - for (nint x = left; x < left + width; x += 8) - { - byte b = Unsafe.Add(ref dataRef, offset++); - nint maxShift = Math.Min(left + width - x, 8); + byte b = Unsafe.Add(ref dataRef, offset++); + nint maxShift = Math.Min(left + width - x, 8); - if (maxShift == 8) - { - int bit = (b >> 7) & 1; - ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); - pixel0 = bit == 0 ? colorWhite : colorBlack; + if (maxShift == 8) + { + int bit = (b >> 7) & 1; + ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x); + pixel0 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 6) & 1; - ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); - pixel1 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 6) & 1; + ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1); + pixel1 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 5) & 1; - ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); - pixel2 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 5) & 1; + ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2); + pixel2 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 4) & 1; - ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); - pixel3 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 4) & 1; + ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3); + pixel3 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 3) & 1; - ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); - pixel4 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 3) & 1; + ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4); + pixel4 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 2) & 1; - ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); - pixel5 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 2) & 1; + ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5); + pixel5 = bit == 0 ? colorWhite : colorBlack; - bit = (b >> 1) & 1; - ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); - pixel6 = bit == 0 ? colorWhite : colorBlack; + bit = (b >> 1) & 1; + ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6); + pixel6 = bit == 0 ? colorWhite : colorBlack; - bit = b & 1; - ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); - pixel7 = bit == 0 ? colorWhite : colorBlack; - } - else + bit = b & 1; + ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7); + pixel7 = bit == 0 ? colorWhite : colorBlack; + } + else + { + for (int shift = 0; shift < maxShift; shift++) { - for (int shift = 0; shift < maxShift; shift++) - { - int bit = (b >> (7 - shift)) & 1; + int bit = (b >> (7 - shift)) & 1; - ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); - pixel = bit == 0 ? colorWhite : colorBlack; - } + ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift); + pixel = bit == 0 ? colorWhite : colorBlack; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index 36832a198a..59e0df87d2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -1,63 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images. +/// +internal class WhiteIsZero24TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'WhiteIsZero' photometric interpretation for 24-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class WhiteIsZero24TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public WhiteIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero24TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + Span buffer = stackalloc byte[4]; + int bufferStartIdx = this.isBigEndian ? 1 : 0; + const uint maxValue = 0xFFFFFF; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + Span bufferSpan = buffer[bufferStartIdx..]; + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - Span buffer = stackalloc byte[4]; - int bufferStartIdx = this.isBigEndian ? 1 : 0; - const uint maxValue = 0xFFFFFF; - - Span bufferSpan = buffer[bufferStartIdx..]; - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 3).CopyTo(bufferSpan); - ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(buffer); - offset += 3; + data.Slice(offset, 3).CopyTo(bufferSpan); + ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(buffer); + offset += 3; - pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo24Bit(intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs index 9f0386d04c..78d557f30b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -1,65 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images. +/// +internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'WhiteIsZero' photometric interpretation for 32-bit float grayscale images. + /// Initializes a new instance of the class. /// - internal class WhiteIsZero32FloatTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public WhiteIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero32FloatTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + byte[] buffer = new byte[4]; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - byte[] buffer = new byte[4]; - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - Array.Reverse(buffer); - float intensity = 1.0f - BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + Array.Reverse(buffer); + float intensity = 1.0f - BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - data.Slice(offset, 4).CopyTo(buffer); - float intensity = 1.0f - BitConverter.ToSingle(buffer, 0); - offset += 4; + data.Slice(offset, 4).CopyTo(buffer); + float intensity = 1.0f - BitConverter.ToSingle(buffer, 0); + offset += 4; - var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); - color.FromScaledVector4(colorVector); - pixelRow[x] = color; - } + var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); + color.FromScaledVector4(colorVector); + pixelRow[x] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs index c27c941d8b..558779a147 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images. +/// +internal class WhiteIsZero32TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { + private readonly bool isBigEndian; + /// - /// Implements the 'WhiteIsZero' photometric interpretation for 32-bit grayscale images. + /// Initializes a new instance of the class. /// - internal class WhiteIsZero32TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly bool isBigEndian; + /// if set to true decodes the pixel data as big endian, otherwise as little endian. + public WhiteIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; - /// - /// Initializes a new instance of the class. - /// - /// if set to true decodes the pixel data as big endian, otherwise as little endian. - public WhiteIsZero32TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); + color.FromScaledVector4(Vector4.Zero); + const uint maxValue = 0xFFFFFFFF; - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + int offset = 0; + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - color.FromScaledVector4(Vector4.Zero); - const uint maxValue = 0xFFFFFFFF; - - int offset = 0; - for (int y = top; y < top + height; y++) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + if (this.isBigEndian) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - if (this.isBigEndian) + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); - offset += 4; + ulong intensity = maxValue - TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); } - else + } + else + { + for (int x = 0; x < pixelRow.Length; x++) { - for (int x = 0; x < pixelRow.Length; x++) - { - ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); - offset += 4; + ulong intensity = maxValue - TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); + offset += 4; - pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); - } + pixelRow[x] = TiffUtils.ColorScaleTo32Bit(intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs index 878034d69f..bc5e2fb645 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images). +/// +internal class WhiteIsZero4TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images). - /// - internal class WhiteIsZero4TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + var color = default(TPixel); - int offset = 0; - bool isOddWidth = (width & 1) == 1; + int offset = 0; + bool isOddWidth = (width & 1) == 1; - var l8 = default(L8); - for (int y = top; y < top + height; y++) + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + Span pixelRowSpan = pixels.DangerousGetRowSpan(y); + for (int x = left; x < left + width - 1;) { - Span pixelRowSpan = pixels.DangerousGetRowSpan(y); - for (int x = left; x < left + width - 1;) - { - byte byteData = data[offset++]; + byte byteData = data[offset++]; - byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); - l8.PackedValue = intensity1; - color.FromL8(l8); + byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); - pixelRowSpan[x++] = color; + pixelRowSpan[x++] = color; - byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); - l8.PackedValue = intensity2; - color.FromL8(l8); + byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17); + l8.PackedValue = intensity2; + color.FromL8(l8); - pixelRowSpan[x++] = color; - } + pixelRowSpan[x++] = color; + } - if (isOddWidth) - { - byte byteData = data[offset++]; + if (isOddWidth) + { + byte byteData = data[offset++]; - byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); - l8.PackedValue = intensity1; - color.FromL8(l8); + byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17); + l8.PackedValue = intensity1; + color.FromL8(l8); - pixelRowSpan[left + width - 1] = color; - } + pixelRowSpan[left + width - 1] = color; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index 0d5924a2ff..fb2653543b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -1,35 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images). +/// +internal class WhiteIsZero8TiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images). - /// - internal class WhiteIsZero8TiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) { - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + var color = default(TPixel); - int offset = 0; + int offset = 0; - var l8 = default(L8); - for (int y = top; y < top + height; y++) + var l8 = default(L8); + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - byte intensity = (byte)(byte.MaxValue - data[offset++]); - pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); - } + byte intensity = (byte)(byte.MaxValue - data[offset++]); + pixelRow[x] = TiffUtils.ColorFromL8(l8, intensity, color); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 52c26edded..b38868a0e4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths). +/// +internal class WhiteIsZeroTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths). - /// - internal class WhiteIsZeroTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly ushort bitsPerSample0; + private readonly ushort bitsPerSample0; - private readonly float factor; + private readonly float factor; - public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample) - { - this.bitsPerSample0 = bitsPerSample.Channel0; - this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; - } + public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample) + { + this.bitsPerSample0 = bitsPerSample.Channel0; + this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; + } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - var color = default(TPixel); + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + var color = default(TPixel); - var bitReader = new BitReader(data); + var bitReader = new BitReader(data); - for (int y = top; y < top + height; y++) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - int value = bitReader.ReadBits(this.bitsPerSample0); - float intensity = 1.0f - (value / this.factor); + int value = bitReader.ReadBits(this.bitsPerSample0); + float intensity = 1.0f - (value / this.factor); - color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); - pixelRow[x] = color; - } - - bitReader.NextRow(); + color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f)); + pixelRow[x] = color; } + + bitReader.NextRow(); } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs index 3ebe5c7bd4..0a1cf6ab98 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrConverter.cs @@ -1,121 +1,119 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Converts YCbCr data to rgb data. +/// +internal class YCbCrConverter { - /// - /// Converts YCbCr data to rgb data. - /// - internal class YCbCrConverter + private readonly CodingRangeExpander yExpander; + private readonly CodingRangeExpander cbExpander; + private readonly CodingRangeExpander crExpander; + private readonly YCbCrToRgbConverter converter; + + private static readonly Rational[] DefaultLuma = { - private readonly CodingRangeExpander yExpander; - private readonly CodingRangeExpander cbExpander; - private readonly CodingRangeExpander crExpander; - private readonly YCbCrToRgbConverter converter; + new(299, 1000), + new(587, 1000), + new(114, 1000) + }; - private static readonly Rational[] DefaultLuma = - { - new(299, 1000), - new(587, 1000), - new(114, 1000) - }; + private static readonly Rational[] DefaultReferenceBlackWhite = + { + new(0, 1), new(255, 1), + new(128, 1), new(255, 1), + new(128, 1), new(255, 1) + }; - private static readonly Rational[] DefaultReferenceBlackWhite = - { - new(0, 1), new(255, 1), - new(128, 1), new(255, 1), - new(128, 1), new(255, 1) - }; + public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients) + { + referenceBlackAndWhite ??= DefaultReferenceBlackWhite; + coefficients ??= DefaultLuma; - public YCbCrConverter(Rational[] referenceBlackAndWhite, Rational[] coefficients) + if (referenceBlackAndWhite.Length != 6) { - referenceBlackAndWhite ??= DefaultReferenceBlackWhite; - coefficients ??= DefaultLuma; - - if (referenceBlackAndWhite.Length != 6) - { - TiffThrowHelper.ThrowImageFormatException("reference black and white array should have 6 entry's"); - } - - if (coefficients.Length != 3) - { - TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's"); - } - - this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255); - this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127); - this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127); - this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]); + TiffThrowHelper.ThrowImageFormatException("reference black and white array should have 6 entry's"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ConvertToRgba32(byte y, byte cb, byte cr) + if (coefficients.Length != 3) { - float yExpanded = this.yExpander.Expand(y); - float cbExpanded = this.cbExpander.Expand(cb); - float crExpanded = this.crExpander.Expand(cr); + TiffThrowHelper.ThrowImageFormatException("luma coefficients array should have 6 entry's"); + } - Rgba32 rgba = this.converter.Convert(yExpanded, cbExpanded, crExpanded); + this.yExpander = new CodingRangeExpander(referenceBlackAndWhite[0], referenceBlackAndWhite[1], 255); + this.cbExpander = new CodingRangeExpander(referenceBlackAndWhite[2], referenceBlackAndWhite[3], 127); + this.crExpander = new CodingRangeExpander(referenceBlackAndWhite[4], referenceBlackAndWhite[5], 127); + this.converter = new YCbCrToRgbConverter(coefficients[0], coefficients[1], coefficients[2]); + } - return rgba; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ConvertToRgba32(byte y, byte cb, byte cr) + { + float yExpanded = this.yExpander.Expand(y); + float cbExpanded = this.cbExpander.Expand(cb); + float crExpanded = this.crExpander.Expand(cr); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte RoundAndClampTo8Bit(float value) + Rgba32 rgba = this.converter.Convert(yExpanded, cbExpanded, crExpanded); + + return rgba; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte RoundAndClampTo8Bit(float value) + { + int input = (int)MathF.Round(value); + return (byte)Numerics.Clamp(input, 0, 255); + } + + private readonly struct CodingRangeExpander + { + private readonly float f1; + private readonly float f2; + + public CodingRangeExpander(Rational referenceBlack, Rational referenceWhite, int codingRange) { - int input = (int)MathF.Round(value); - return (byte)Numerics.Clamp(input, 0, 255); + float black = referenceBlack.ToSingle(); + float white = referenceWhite.ToSingle(); + this.f1 = codingRange / (white - black); + this.f2 = this.f1 * black; } - private readonly struct CodingRangeExpander + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Expand(float code) => (code * this.f1) - this.f2; + } + + private readonly struct YCbCrToRgbConverter + { + private readonly float cr2R; + private readonly float cb2B; + private readonly float y2G; + private readonly float cr2G; + private readonly float cb2G; + + public YCbCrToRgbConverter(Rational lumaRed, Rational lumaGreen, Rational lumaBlue) { - private readonly float f1; - private readonly float f2; - - public CodingRangeExpander(Rational referenceBlack, Rational referenceWhite, int codingRange) - { - float black = referenceBlack.ToSingle(); - float white = referenceWhite.ToSingle(); - this.f1 = codingRange / (white - black); - this.f2 = this.f1 * black; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float code) => (code * this.f1) - this.f2; + this.cr2R = 2 - (2 * lumaRed.ToSingle()); + this.cb2B = 2 - (2 * lumaBlue.ToSingle()); + this.y2G = (1 - lumaBlue.ToSingle() - lumaRed.ToSingle()) / lumaGreen.ToSingle(); + this.cr2G = 2 * lumaRed.ToSingle() * (lumaRed.ToSingle() - 1) / lumaGreen.ToSingle(); + this.cb2G = 2 * lumaBlue.ToSingle() * (lumaBlue.ToSingle() - 1) / lumaGreen.ToSingle(); } - private readonly struct YCbCrToRgbConverter + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 Convert(float y, float cb, float cr) { - private readonly float cr2R; - private readonly float cb2B; - private readonly float y2G; - private readonly float cr2G; - private readonly float cb2G; - - public YCbCrToRgbConverter(Rational lumaRed, Rational lumaGreen, Rational lumaBlue) - { - this.cr2R = 2 - (2 * lumaRed.ToSingle()); - this.cb2B = 2 - (2 * lumaBlue.ToSingle()); - this.y2G = (1 - lumaBlue.ToSingle() - lumaRed.ToSingle()) / lumaGreen.ToSingle(); - this.cr2G = 2 * lumaRed.ToSingle() * (lumaRed.ToSingle() - 1) / lumaGreen.ToSingle(); - this.cb2G = 2 * lumaBlue.ToSingle() * (lumaBlue.ToSingle() - 1) / lumaGreen.ToSingle(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 Convert(float y, float cb, float cr) - { - var pixel = default(Rgba32); - pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y); - pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb)); - pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y); - pixel.A = byte.MaxValue; - - return pixel; - } + var pixel = default(Rgba32); + pixel.R = RoundAndClampTo8Bit((cr * this.cr2R) + y); + pixel.G = RoundAndClampTo8Bit((this.y2G * y) + (this.cr2G * cr) + (this.cb2G * cb)); + pixel.B = RoundAndClampTo8Bit((cb * this.cb2B) + y); + pixel.A = byte.MaxValue; + + return pixel; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs index 6abec16fa2..791bfa438a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs @@ -1,82 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration. +/// +internal class YCbCrPlanarTiffColor : TiffBasePlanarColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements decoding pixel data with photometric interpretation of type 'YCbCr' with the planar configuration. - /// - internal class YCbCrPlanarTiffColor : TiffBasePlanarColorDecoder - where TPixel : unmanaged, IPixel + private readonly YCbCrConverter converter; + + private readonly ushort[] ycbcrSubSampling; + + public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) { - private readonly YCbCrConverter converter; + this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); + this.ycbcrSubSampling = ycbcrSubSampling; + } - private readonly ushort[] ycbcrSubSampling; + /// + public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + { + Span yData = data[0].GetSpan(); + Span cbData = data[1].GetSpan(); + Span crData = data[2].GetSpan(); - public YCbCrPlanarTiffColor(Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) + if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) { - this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); - this.ycbcrSubSampling = ycbcrSubSampling; + ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], cbData, crData); } - /// - public override void Decode(IMemoryOwner[] data, Buffer2D pixels, int left, int top, int width, int height) + var color = default(TPixel); + int offset = 0; + int widthPadding = 0; + if (this.ycbcrSubSampling != null) { - Span yData = data[0].GetSpan(); - Span cbData = data[1].GetSpan(); - Span crData = data[2].GetSpan(); - - if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) - { - ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], cbData, crData); - } + // Round to the next integer multiple of horizontalSubSampling. + widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); + } - var color = default(TPixel); - int offset = 0; - int widthPadding = 0; - if (this.ycbcrSubSampling != null) + for (int y = top; y < top + height; y++) + { + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - // Round to the next integer multiple of horizontalSubSampling. - widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); + Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]); + color.FromRgba32(rgba); + pixelRow[x] = color; + offset++; } - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]); - color.FromRgba32(rgba); - pixelRow[x] = color; - offset++; - } - - offset += widthPadding; - } + offset += widthPadding; } + } - private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, Span planarCb, Span planarCr) - { - // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, - // then the source data will be padded. - width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling); - height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling); + private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, Span planarCb, Span planarCr) + { + // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, + // then the source data will be padded. + width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling); + height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling); - for (int row = height - 1; row >= 0; row--) + for (int row = height - 1; row >= 0; row--) + { + for (int col = width - 1; col >= 0; col--) { - for (int col = width - 1; col >= 0; col--) - { - int offset = (row * width) + col; - int subSampleOffset = (row / verticalSubSampling * (width / horizontalSubSampling)) + (col / horizontalSubSampling); - planarCb[offset] = planarCb[subSampleOffset]; - planarCr[offset] = planarCr[subSampleOffset]; - } + int offset = (row * width) + col; + int subSampleOffset = (row / verticalSubSampling * (width / horizontalSubSampling)) + (col / horizontalSubSampling); + planarCb[offset] = planarCb[subSampleOffset]; + planarCr[offset] = planarCr[subSampleOffset]; } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs index f4ff75c15e..2e47698a67 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs @@ -1,109 +1,107 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; + +/// +/// Implements decoding pixel data with photometric interpretation of type 'YCbCr'. +/// +internal class YCbCrTiffColor : TiffBaseColorDecoder + where TPixel : unmanaged, IPixel { - /// - /// Implements decoding pixel data with photometric interpretation of type 'YCbCr'. - /// - internal class YCbCrTiffColor : TiffBaseColorDecoder - where TPixel : unmanaged, IPixel - { - private readonly MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; - private readonly YCbCrConverter converter; + private readonly YCbCrConverter converter; - private readonly ushort[] ycbcrSubSampling; + private readonly ushort[] ycbcrSubSampling; - public YCbCrTiffColor(MemoryAllocator memoryAllocator, Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) + public YCbCrTiffColor(MemoryAllocator memoryAllocator, Rational[] referenceBlackAndWhite, Rational[] coefficients, ushort[] ycbcrSubSampling) + { + this.memoryAllocator = memoryAllocator; + this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); + this.ycbcrSubSampling = ycbcrSubSampling; + } + + /// + public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) + { + ReadOnlySpan ycbcrData = data; + if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) { - this.memoryAllocator = memoryAllocator; - this.converter = new YCbCrConverter(referenceBlackAndWhite, coefficients); - this.ycbcrSubSampling = ycbcrSubSampling; + // 4 extra rows and columns for possible padding. + int paddedWidth = width + 4; + int paddedHeight = height + 4; + int requiredBytes = paddedWidth * paddedHeight * 3; + using IMemoryOwner tmpBuffer = this.memoryAllocator.Allocate(requiredBytes); + Span tmpBufferSpan = tmpBuffer.GetSpan(); + ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan); + ycbcrData = tmpBufferSpan; + this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); + return; } - /// - public override void Decode(ReadOnlySpan data, Buffer2D pixels, int left, int top, int width, int height) - { - ReadOnlySpan ycbcrData = data; - if (this.ycbcrSubSampling != null && !(this.ycbcrSubSampling[0] == 1 && this.ycbcrSubSampling[1] == 1)) - { - // 4 extra rows and columns for possible padding. - int paddedWidth = width + 4; - int paddedHeight = height + 4; - int requiredBytes = paddedWidth * paddedHeight * 3; - using IMemoryOwner tmpBuffer = this.memoryAllocator.Allocate(requiredBytes); - Span tmpBufferSpan = tmpBuffer.GetSpan(); - ReverseChromaSubSampling(width, height, this.ycbcrSubSampling[0], this.ycbcrSubSampling[1], data, tmpBufferSpan); - ycbcrData = tmpBufferSpan; - this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); - return; - } + this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); + } - this.DecodeYCbCrData(pixels, left, top, width, height, ycbcrData); + private void DecodeYCbCrData(Buffer2D pixels, int left, int top, int width, int height, ReadOnlySpan ycbcrData) + { + var color = default(TPixel); + int offset = 0; + int widthPadding = 0; + if (this.ycbcrSubSampling != null) + { + // Round to the next integer multiple of horizontalSubSampling. + widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); } - private void DecodeYCbCrData(Buffer2D pixels, int left, int top, int width, int height, ReadOnlySpan ycbcrData) + for (int y = top; y < top + height; y++) { - var color = default(TPixel); - int offset = 0; - int widthPadding = 0; - if (this.ycbcrSubSampling != null) + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); + for (int x = 0; x < pixelRow.Length; x++) { - // Round to the next integer multiple of horizontalSubSampling. - widthPadding = TiffUtils.PaddingToNextInteger(width, this.ycbcrSubSampling[0]); + Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]); + color.FromRgba32(rgba); + pixelRow[x] = color; + offset += 3; } - for (int y = top; y < top + height; y++) - { - Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); - for (int x = 0; x < pixelRow.Length; x++) - { - Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]); - color.FromRgba32(rgba); - pixelRow[x] = color; - offset += 3; - } - - offset += widthPadding * 3; - } + offset += widthPadding * 3; } + } - private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, ReadOnlySpan source, Span destination) - { - // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, - // then the source data will be padded. - width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling); - height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling); - int blockWidth = width / horizontalSubSampling; - int blockHeight = height / verticalSubSampling; - int cbCrOffsetInBlock = horizontalSubSampling * verticalSubSampling; - int blockByteCount = cbCrOffsetInBlock + 2; + private static void ReverseChromaSubSampling(int width, int height, int horizontalSubSampling, int verticalSubSampling, ReadOnlySpan source, Span destination) + { + // If width and height are not multiples of ChromaSubsampleHoriz and ChromaSubsampleVert respectively, + // then the source data will be padded. + width += TiffUtils.PaddingToNextInteger(width, horizontalSubSampling); + height += TiffUtils.PaddingToNextInteger(height, verticalSubSampling); + int blockWidth = width / horizontalSubSampling; + int blockHeight = height / verticalSubSampling; + int cbCrOffsetInBlock = horizontalSubSampling * verticalSubSampling; + int blockByteCount = cbCrOffsetInBlock + 2; - for (int blockRow = blockHeight - 1; blockRow >= 0; blockRow--) + for (int blockRow = blockHeight - 1; blockRow >= 0; blockRow--) + { + for (int blockCol = blockWidth - 1; blockCol >= 0; blockCol--) { - for (int blockCol = blockWidth - 1; blockCol >= 0; blockCol--) - { - int blockOffset = (blockRow * blockWidth) + blockCol; - ReadOnlySpan blockData = source.Slice(blockOffset * blockByteCount, blockByteCount); - byte cr = blockData[cbCrOffsetInBlock + 1]; - byte cb = blockData[cbCrOffsetInBlock]; + int blockOffset = (blockRow * blockWidth) + blockCol; + ReadOnlySpan blockData = source.Slice(blockOffset * blockByteCount, blockByteCount); + byte cr = blockData[cbCrOffsetInBlock + 1]; + byte cb = blockData[cbCrOffsetInBlock]; - for (int row = verticalSubSampling - 1; row >= 0; row--) + for (int row = verticalSubSampling - 1; row >= 0; row--) + { + for (int col = horizontalSubSampling - 1; col >= 0; col--) { - for (int col = horizontalSubSampling - 1; col >= 0; col--) - { - int offset = 3 * ((((blockRow * verticalSubSampling) + row) * width) + (blockCol * horizontalSubSampling) + col); - destination[offset + 2] = cr; - destination[offset + 1] = cb; - destination[offset] = blockData[(row * horizontalSubSampling) + col]; - } + int offset = 3 * ((((blockRow * verticalSubSampling) + row) * width) + (blockCol * horizontalSubSampling) + col); + destination[offset + 2] = cr; + destination[offset + 1] = cb; + destination[offset] = blockData[(row * horizontalSubSampling) + col]; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index b7a9a09346..bcc249bbec 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -1,94 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Enumerates the available bits per pixel for the tiff format. +/// +public enum TiffBitsPerPixel { /// - /// Enumerates the available bits per pixel for the tiff format. + /// 1 bit per pixel, for bi-color image. /// - public enum TiffBitsPerPixel - { - /// - /// 1 bit per pixel, for bi-color image. - /// - Bit1 = 1, + Bit1 = 1, - /// - /// 4 bits per pixel, for images with a color palette. - /// - Bit4 = 4, + /// + /// 4 bits per pixel, for images with a color palette. + /// + Bit4 = 4, - /// - /// 6 bits per pixel. 2 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit6 = 6, + /// + /// 6 bits per pixel. 2 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit6 = 6, - /// - /// 8 bits per pixel, grayscale or color palette images. - /// - Bit8 = 8, + /// + /// 8 bits per pixel, grayscale or color palette images. + /// + Bit8 = 8, - /// - /// 10 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. - /// - Bit10 = 10, + /// + /// 10 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. + /// + Bit10 = 10, - /// - /// 12 bits per pixel. 4 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit12 = 12, + /// + /// 12 bits per pixel. 4 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit12 = 12, - /// - /// 14 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. - /// - Bit14 = 14, + /// + /// 14 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. + /// + Bit14 = 14, - /// - /// 16 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit16 = 16, + /// + /// 16 bits per pixel, for gray images. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit16 = 16, - /// - /// 24 bits per pixel. One byte for each color channel. - /// - Bit24 = 24, + /// + /// 24 bits per pixel. One byte for each color channel. + /// + Bit24 = 24, - /// - /// 30 bits per pixel. 10 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit30 = 30, + /// + /// 30 bits per pixel. 10 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit30 = 30, - /// - /// 36 bits per pixel. 12 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit36 = 36, + /// + /// 36 bits per pixel. 12 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit36 = 36, - /// - /// 42 bits per pixel. 14 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit42 = 42, + /// + /// 42 bits per pixel. 14 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit42 = 42, - /// - /// 48 bits per pixel. 16 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. - /// - Bit48 = 48, - } + /// + /// 48 bits per pixel. 16 bit for each color channel. + /// + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// + Bit48 = 48, } diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs index d277af9003..157561b743 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs @@ -1,184 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff; -namespace SixLabors.ImageSharp.Formats.Tiff +/// +/// The number of bits per component. +/// +public readonly struct TiffBitsPerSample : IEquatable { /// - /// The number of bits per component. + /// Initializes a new instance of the struct. /// - public readonly struct TiffBitsPerSample : IEquatable + /// The bits for the channel 0. + /// The bits for the channel 1. + /// The bits for the channel 2. + /// The bits for the channel 3. + public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0) { - /// - /// Initializes a new instance of the struct. - /// - /// The bits for the channel 0. - /// The bits for the channel 1. - /// The bits for the channel 2. - /// The bits for the channel 3. - public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2, ushort channel3 = 0) + this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); + this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); + this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); + this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32); + + this.Channels = 0; + this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); + this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0); + } + + /// + /// Gets the bits for the channel 0. + /// + public readonly ushort Channel0 { get; } + + /// + /// Gets the bits for the channel 1. + /// + public readonly ushort Channel1 { get; } + + /// + /// Gets the bits for the channel 2. + /// + public readonly ushort Channel2 { get; } + + /// + /// Gets the bits for the alpha channel. + /// + public readonly ushort Channel3 { get; } + + /// + /// Gets the number of channels. + /// + public readonly byte Channels { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(TiffBitsPerSample left, TiffBitsPerSample right) => left.Equals(right); + + /// + /// Checks whether two structures are not equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(TiffBitsPerSample left, TiffBitsPerSample right) => !(left == right); + + /// + /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct. + /// + /// The value to parse. + /// The tiff bits per sample. + /// True, if the value could be parsed. + public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) + { + if (value is null || value.Length == 0) { - this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32); - this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32); - this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32); - this.Channel3 = (ushort)Numerics.Clamp(channel3, 0, 32); - - this.Channels = 0; - this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0); - this.Channels += (byte)(this.Channel3 != 0 ? 1 : 0); + sample = default; + return false; } - /// - /// Gets the bits for the channel 0. - /// - public readonly ushort Channel0 { get; } - - /// - /// Gets the bits for the channel 1. - /// - public readonly ushort Channel1 { get; } - - /// - /// Gets the bits for the channel 2. - /// - public readonly ushort Channel2 { get; } - - /// - /// Gets the bits for the alpha channel. - /// - public readonly ushort Channel3 { get; } - - /// - /// Gets the number of channels. - /// - public readonly byte Channels { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(TiffBitsPerSample left, TiffBitsPerSample right) => left.Equals(right); - - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(TiffBitsPerSample left, TiffBitsPerSample right) => !(left == right); - - /// - /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct. - /// - /// The value to parse. - /// The tiff bits per sample. - /// True, if the value could be parsed. - public static bool TryParse(ushort[] value, out TiffBitsPerSample sample) + ushort c3 = 0; + ushort c2; + ushort c1; + ushort c0; + switch (value.Length) { - if (value is null || value.Length == 0) - { - sample = default; - return false; - } - - ushort c3 = 0; - ushort c2; - ushort c1; - ushort c0; - switch (value.Length) - { - case 4: - c3 = value[3]; - c2 = value[2]; - c1 = value[1]; - c0 = value[0]; - break; - - case 3: - c2 = value[2]; - c1 = value[1]; - c0 = value[0]; - break; - case 2: - c2 = 0; - c1 = value[1]; - c0 = value[0]; - break; - default: - c2 = 0; - c1 = 0; - c0 = value[0]; - break; - } - - sample = new TiffBitsPerSample(c0, c1, c2, c3); - return true; + case 4: + c3 = value[3]; + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + + case 3: + c2 = value[2]; + c1 = value[1]; + c0 = value[0]; + break; + case 2: + c2 = 0; + c1 = value[1]; + c0 = value[0]; + break; + default: + c2 = 0; + c1 = 0; + c0 = value[0]; + break; } - /// - public override bool Equals(object obj) - => obj is TiffBitsPerSample sample && this.Equals(sample); - - /// - public bool Equals(TiffBitsPerSample other) - => this.Channel0 == other.Channel0 - && this.Channel1 == other.Channel1 - && this.Channel2 == other.Channel2 - && this.Channel3 == other.Channel3; - - /// - public override int GetHashCode() - => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3); - - /// - /// Converts the bits per sample struct to an ushort array. - /// - /// Bits per sample as ushort array. - public ushort[] ToArray() + sample = new TiffBitsPerSample(c0, c1, c2, c3); + return true; + } + + /// + public override bool Equals(object obj) + => obj is TiffBitsPerSample sample && this.Equals(sample); + + /// + public bool Equals(TiffBitsPerSample other) + => this.Channel0 == other.Channel0 + && this.Channel1 == other.Channel1 + && this.Channel2 == other.Channel2 + && this.Channel3 == other.Channel3; + + /// + public override int GetHashCode() + => HashCode.Combine(this.Channel0, this.Channel1, this.Channel2, this.Channel3); + + /// + /// Converts the bits per sample struct to an ushort array. + /// + /// Bits per sample as ushort array. + public ushort[] ToArray() + { + if (this.Channel1 == 0) { - if (this.Channel1 == 0) - { - return new[] { this.Channel0 }; - } - - if (this.Channel2 == 0) - { - return new[] { this.Channel0, this.Channel1 }; - } - - if (this.Channel3 == 0) - { - return new[] { this.Channel0, this.Channel1, this.Channel2 }; - } - - return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 }; + return new[] { this.Channel0 }; } - /// - /// Gets the bits per pixel for the given bits per sample. - /// - /// Bits per pixel. - public TiffBitsPerPixel BitsPerPixel() + if (this.Channel2 == 0) { - int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3; - return (TiffBitsPerPixel)bitsPerPixel; + return new[] { this.Channel0, this.Channel1 }; } - /// - public override string ToString() - => this.Channel3 is 0 ? - $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})" - : $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})"; + if (this.Channel3 == 0) + { + return new[] { this.Channel0, this.Channel1, this.Channel2 }; + } + + return new[] { this.Channel0, this.Channel1, this.Channel2, this.Channel3 }; + } + + /// + /// Gets the bits per pixel for the given bits per sample. + /// + /// Bits per pixel. + public TiffBitsPerPixel BitsPerPixel() + { + int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2 + this.Channel3; + return (TiffBitsPerPixel)bitsPerPixel; } + + /// + public override string ToString() + => this.Channel3 is 0 ? + $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})" + : $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2}, {this.Channel3})"; } diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs index d658b1ceab..67b6517237 100644 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Registers the image encoders, decoders and mime type detectors for the TIFF format. +/// +public sealed class TiffConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the TIFF format. - /// - public sealed class TiffConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); - configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); - } + configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); + configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs index dfbee19a05..b433222a07 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs @@ -1,42 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Image decoder for generating an image out of a TIFF stream. +/// +public class TiffDecoder : IImageDecoder { - /// - /// Image decoder for generating an image out of a TIFF stream. - /// - public class TiffDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + return new TiffDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } - TiffDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options, image); + TiffDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options, image); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoder)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 8e3f8c16b7..60bce2174d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; @@ -15,443 +11,362 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Performs the tiff decoding operation. +/// +internal class TiffDecoderCore : IImageDecoderInternals { /// - /// Performs the tiff decoding operation. + /// General configuration options. /// - internal class TiffDecoderCore : IImageDecoderInternals - { - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// A value indicating whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// The stream to decode from. - /// - private BufferedReadStream inputStream; - - /// - /// Indicates the byte order of the stream. - /// - private ByteOrder byteOrder; - - /// - /// Indicating whether is BigTiff format. - /// - private bool isBigTiff; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public TiffDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.maxFrames = options.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } + private readonly Configuration configuration; - /// - /// Gets or sets the bits per sample. - /// - public TiffBitsPerSample BitsPerSample { get; set; } - - /// - /// Gets or sets the bits per pixel. - /// - public int BitsPerPixel { get; set; } - - /// - /// Gets or sets the lookup table for RGB palette colored images. - /// - public ushort[] ColorMap { get; set; } - - /// - /// Gets or sets the photometric interpretation implementation to use when decoding the image. - /// - public TiffColorType ColorType { get; set; } - - /// - /// Gets or sets the reference black and white for decoding YCbCr pixel data. - /// - public Rational[] ReferenceBlackAndWhite { get; set; } - - /// - /// Gets or sets the YCbCr coefficients. - /// - public Rational[] YcbcrCoefficients { get; set; } - - /// - /// Gets or sets the YCbCr sub sampling. - /// - public ushort[] YcbcrSubSampling { get; set; } - - /// - /// Gets or sets the compression used, when the image was encoded. - /// - public TiffDecoderCompressionType CompressionType { get; set; } - - /// - /// Gets or sets the Fax specific compression options. - /// - public FaxCompressionOptions FaxCompressionOptions { get; set; } - - /// - /// Gets or sets the the logical order of bits within a byte. - /// - public TiffFillOrder FillOrder { get; set; } - - /// - /// Gets or sets the extra samples type. - /// - public TiffExtraSampleType? ExtraSamplesType { get; set; } - - /// - /// Gets or sets the JPEG tables when jpeg compression is used. - /// - public byte[] JpegTables { get; set; } - - /// - /// Gets or sets the planar configuration type to use when decoding the image. - /// - public TiffPlanarConfiguration PlanarConfiguration { get; set; } - - /// - /// Gets or sets the photometric interpretation. - /// - public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } - - /// - /// Gets or sets the sample format. - /// - public TiffSampleFormat SampleFormat { get; set; } - - /// - /// Gets or sets the horizontal predictor. - /// - public TiffPredictor Predictor { get; set; } - - /// - public DecoderOptions Options { get; } - - /// - public Size Dimensions { get; private set; } - - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var frames = new List>(); - try - { - this.inputStream = stream; - var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - IEnumerable directories = reader.Read(); - this.byteOrder = reader.ByteOrder; - this.isBigTiff = reader.IsBigTiff; + /// + /// A value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool skipMetadata; - uint frameCount = 0; - foreach (ExifProfile ifd in directories) - { - cancellationToken.ThrowIfCancellationRequested(); - ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); - frames.Add(frame); - - if (++frameCount == this.maxFrames) - { - break; - } - } + /// + /// The maximum number of frames to decode. Inclusive. + /// + private readonly uint maxFrames; - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); + /// + /// The stream to decode from. + /// + private BufferedReadStream inputStream; - // TODO: Tiff frames can have different sizes. - ImageFrame root = frames[0]; - this.Dimensions = root.Size(); - foreach (ImageFrame frame in frames) - { - if (frame.Size() != root.Size()) - { - TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); - } - } + /// + /// Indicates the byte order of the stream. + /// + private ByteOrder byteOrder; - return new Image(this.configuration, metadata, frames); - } - catch - { - foreach (ImageFrame f in frames) - { - f.Dispose(); - } + /// + /// Indicating whether is BigTiff format. + /// + private bool isBigTiff; - throw; - } - } + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public TiffDecoderCore(DecoderOptions options) + { + this.Options = options; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; + } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) - { - this.inputStream = stream; - var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); - IEnumerable directories = reader.Read(); + /// + /// Gets or sets the bits per sample. + /// + public TiffBitsPerSample BitsPerSample { get; set; } - ExifProfile rootFrameExifProfile = directories.First(); - var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); + /// + /// Gets or sets the bits per pixel. + /// + public int BitsPerPixel { get; set; } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); - int width = GetImageWidth(rootFrameExifProfile); - int height = GetImageHeight(rootFrameExifProfile); + /// + /// Gets or sets the lookup table for RGB palette colored images. + /// + public ushort[] ColorMap { get; set; } - return new ImageInfo(new PixelTypeInfo((int)rootMetadata.BitsPerPixel), width, height, metadata); - } + /// + /// Gets or sets the photometric interpretation implementation to use when decoding the image. + /// + public TiffColorType ColorType { get; set; } - /// - /// Decodes the image data from a specified IFD. - /// - /// The pixel format. - /// The IFD tags. - /// The token to monitor cancellation. - /// The tiff frame. - private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var imageFrameMetaData = new ImageFrameMetadata(); - if (!this.skipMetadata) - { - imageFrameMetaData.ExifProfile = tags; - } + /// + /// Gets or sets the reference black and white for decoding YCbCr pixel data. + /// + public Rational[] ReferenceBlackAndWhite { get; set; } - TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); - TiffFrameMetadata.Parse(tiffFrameMetaData, tags); + /// + /// Gets or sets the YCbCr coefficients. + /// + public Rational[] YcbcrCoefficients { get; set; } - this.VerifyAndParse(tags, tiffFrameMetaData); + /// + /// Gets or sets the YCbCr sub sampling. + /// + public ushort[] YcbcrSubSampling { get; set; } - int width = GetImageWidth(tags); - int height = GetImageHeight(tags); - var frame = new ImageFrame(this.configuration, width, height, imageFrameMetaData); + /// + /// Gets or sets the compression used, when the image was encoded. + /// + public TiffDecoderCompressionType CompressionType { get; set; } - int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + /// + /// Gets or sets the Fax specific compression options. + /// + public FaxCompressionOptions FaxCompressionOptions { get; set; } - var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); - var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); + /// + /// Gets or sets the the logical order of bits within a byte. + /// + public TiffFillOrder FillOrder { get; set; } - using IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); - using IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); + /// + /// Gets or sets the extra samples type. + /// + public TiffExtraSampleType? ExtraSamplesType { get; set; } - if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) - { - this.DecodeStripsPlanar( - frame, - rowsPerStrip, - stripOffsets, - stripByteCounts, - cancellationToken); - } - else - { - this.DecodeStripsChunky( - frame, - rowsPerStrip, - stripOffsets, - stripByteCounts, - cancellationToken); - } + /// + /// Gets or sets the JPEG tables when jpeg compression is used. + /// + public byte[] JpegTables { get; set; } - return frame; - } + /// + /// Gets or sets the planar configuration type to use when decoding the image. + /// + public TiffPlanarConfiguration PlanarConfiguration { get; set; } - private IMemoryOwner ConvertNumbers(Array array, out Span span) - { - if (array is Number[] numbers) - { - IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); - span = memory.GetSpan(); - for (int i = 0; i < numbers.Length; i++) - { - span[i] = (uint)numbers[i]; - } + /// + /// Gets or sets the photometric interpretation. + /// + public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } - return memory; - } + /// + /// Gets or sets the sample format. + /// + public TiffSampleFormat SampleFormat { get; set; } - DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); - span = (ulong[])array; - return null; - } + /// + /// Gets or sets the horizontal predictor. + /// + public TiffPredictor Predictor { get; set; } + + /// + public DecoderOptions Options { get; } + + /// + public Size Dimensions { get; private set; } - /// - /// Calculates the size (in bytes) for a pixel buffer using the determined color format. - /// - /// The width for the desired pixel buffer. - /// The height for the desired pixel buffer. - /// The index of the plane for planar image configuration (or zero for chunky). - /// The size (in bytes) of the required pixel buffer. - private int CalculateStripBufferSize(int width, int height, int plane = -1) + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var frames = new List>(); + try { - DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); + this.inputStream = stream; + var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); - int bitsPerPixel = 0; + IEnumerable directories = reader.Read(); + this.byteOrder = reader.ByteOrder; + this.isBigTiff = reader.IsBigTiff; - if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + uint frameCount = 0; + foreach (ExifProfile ifd in directories) { - DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); - bitsPerPixel = this.BitsPerPixel; + cancellationToken.ThrowIfCancellationRequested(); + ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); + frames.Add(frame); + + if (++frameCount == this.maxFrames) + { + break; + } } - else + + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); + + // TODO: Tiff frames can have different sizes. + ImageFrame root = frames[0]; + this.Dimensions = root.Size(); + foreach (ImageFrame frame in frames) { - switch (plane) + if (frame.Size() != root.Size()) { - case 0: - bitsPerPixel = this.BitsPerSample.Channel0; - break; - case 1: - bitsPerPixel = this.BitsPerSample.Channel1; - break; - case 2: - bitsPerPixel = this.BitsPerSample.Channel2; - break; - case 3: - bitsPerPixel = this.BitsPerSample.Channel2; - break; - default: - TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); - break; + TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); } } - int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; - return bytesPerRow * height; + return new Image(this.configuration, metadata, frames); + } + catch + { + foreach (ImageFrame f in frames) + { + f.Dispose(); + } + + throw; } + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.inputStream = stream; + var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); + IEnumerable directories = reader.Read(); + + ExifProfile rootFrameExifProfile = directories.First(); + var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); - /// - /// Decodes the image data for planar encoded pixel data. - /// - /// The pixel format. - /// The image frame to decode data into. - /// The number of rows per strip of data. - /// An array of byte offsets to each strip in the image. - /// An array of the size of each strip (in bytes). - /// The token to monitor cancellation. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); + int width = GetImageWidth(rootFrameExifProfile); + int height = GetImageHeight(rootFrameExifProfile); + + return new ImageInfo(new PixelTypeInfo((int)rootMetadata.BitsPerPixel), width, height, metadata); + } + + /// + /// Decodes the image data from a specified IFD. + /// + /// The pixel format. + /// The IFD tags. + /// The token to monitor cancellation. + /// The tiff frame. + private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var imageFrameMetaData = new ImageFrameMetadata(); + if (!this.skipMetadata) { - int stripsPerPixel = this.BitsPerSample.Channels; - int stripsPerPlane = stripOffsets.Length / stripsPerPixel; - int bitsPerPixel = this.BitsPerPixel; + imageFrameMetaData.ExifProfile = tags; + } - Buffer2D pixels = frame.PixelBuffer; + TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); + TiffFrameMetadata.Parse(tiffFrameMetaData, tags); - var stripBuffers = new IMemoryOwner[stripsPerPixel]; + this.VerifyAndParse(tags, tiffFrameMetaData); - try - { - for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) - { - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); - stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); - } + int width = GetImageWidth(tags); + int height = GetImageHeight(tags); + var frame = new ImageFrame(this.configuration, width, height, imageFrameMetaData); - using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( - this.Options, - this.CompressionType, - this.memoryAllocator, - this.PhotometricInterpretation, - frame.Width, - bitsPerPixel, - this.ColorType, - this.Predictor, - this.FaxCompressionOptions, - this.JpegTables, - this.FillOrder, - this.byteOrder); - - TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar( - this.ColorType, - this.BitsPerSample, - this.ExtraSamplesType, - this.ColorMap, - this.ReferenceBlackAndWhite, - this.YcbcrCoefficients, - this.YcbcrSubSampling, - this.byteOrder); - - for (int i = 0; i < stripsPerPlane; i++) - { - cancellationToken.ThrowIfCancellationRequested(); + int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; + var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); + var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); - int stripIndex = i; - for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) - { - decompressor.Decompress( - this.inputStream, - stripOffsets[stripIndex], - stripByteCounts[stripIndex], - stripHeight, - stripBuffers[planeIndex].GetSpan(), - cancellationToken); + using IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); + using IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); - stripIndex += stripsPerPlane; - } + if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) + { + this.DecodeStripsPlanar( + frame, + rowsPerStrip, + stripOffsets, + stripByteCounts, + cancellationToken); + } + else + { + this.DecodeStripsChunky( + frame, + rowsPerStrip, + stripOffsets, + stripByteCounts, + cancellationToken); + } - colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); - } - } - finally + return frame; + } + + private IMemoryOwner ConvertNumbers(Array array, out Span span) + { + if (array is Number[] numbers) + { + IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); + span = memory.GetSpan(); + for (int i = 0; i < numbers.Length; i++) { - foreach (IMemoryOwner buf in stripBuffers) - { - buf?.Dispose(); - } + span[i] = (uint)numbers[i]; } + + return memory; } - /// - /// Decodes the image data for chunky encoded pixel data. - /// - /// The pixel format. - /// The image frame to decode data into. - /// The rows per strip. - /// The strip offsets. - /// The strip byte counts. - /// The token to monitor cancellation. - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); + span = (ulong[])array; + return null; + } + + /// + /// Calculates the size (in bytes) for a pixel buffer using the determined color format. + /// + /// The width for the desired pixel buffer. + /// The height for the desired pixel buffer. + /// The index of the plane for planar image configuration (or zero for chunky). + /// The size (in bytes) of the required pixel buffer. + private int CalculateStripBufferSize(int width, int height, int plane = -1) + { + DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); + + int bitsPerPixel = 0; + + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) { - // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. - if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) + DebugGuard.IsTrue(plane == -1, "Expected Chunky planar."); + bitsPerPixel = this.BitsPerPixel; + } + else + { + switch (plane) { - rowsPerStrip = frame.Height; + case 0: + bitsPerPixel = this.BitsPerSample.Channel0; + break; + case 1: + bitsPerPixel = this.BitsPerSample.Channel1; + break; + case 2: + bitsPerPixel = this.BitsPerSample.Channel2; + break; + case 3: + bitsPerPixel = this.BitsPerSample.Channel2; + break; + default: + TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported"); + break; } + } + + int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; + return bytesPerRow * height; + } + + /// + /// Decodes the image data for planar encoded pixel data. + /// + /// The pixel format. + /// The image frame to decode data into. + /// The number of rows per strip of data. + /// An array of byte offsets to each strip in the image. + /// An array of the size of each strip (in bytes). + /// The token to monitor cancellation. + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + int stripsPerPixel = this.BitsPerSample.Channels; + int stripsPerPlane = stripOffsets.Length / stripsPerPixel; + int bitsPerPixel = this.BitsPerPixel; - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); - int bitsPerPixel = this.BitsPerPixel; + Buffer2D pixels = frame.PixelBuffer; - using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); - Span stripBufferSpan = stripBuffer.GetSpan(); - Buffer2D pixels = frame.PixelBuffer; + var stripBuffers = new IMemoryOwner[stripsPerPixel]; + + try + { + for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) + { + int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); + stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); + } using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( this.Options, @@ -467,9 +382,7 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr this.FillOrder, this.byteOrder); - TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( - this.configuration, - this.memoryAllocator, + TiffBasePlanarColorDecoder colorDecoder = TiffColorDecoderFactory.CreatePlanar( this.ColorType, this.BitsPerSample, this.ExtraSamplesType, @@ -479,65 +392,147 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr this.YcbcrSubSampling, this.byteOrder); - for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) + for (int i = 0; i < stripsPerPlane; i++) { cancellationToken.ThrowIfCancellationRequested(); - int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 - ? rowsPerStrip - : frame.Height % rowsPerStrip; + int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; - int top = rowsPerStrip * stripIndex; - if (top + stripHeight > frame.Height) + int stripIndex = i; + for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { - // Make sure we ignore any strips that are not needed for the image (if too many are present). - break; + decompressor.Decompress( + this.inputStream, + stripOffsets[stripIndex], + stripByteCounts[stripIndex], + stripHeight, + stripBuffers[planeIndex].GetSpan(), + cancellationToken); + + stripIndex += stripsPerPlane; } - decompressor.Decompress( - this.inputStream, - stripOffsets[stripIndex], - stripByteCounts[stripIndex], - stripHeight, - stripBufferSpan, - cancellationToken); - - colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); + colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight); } } - - /// - /// Gets the width of the image frame. - /// - /// The image frame exif profile. - /// The image width. - private static int GetImageWidth(ExifProfile exifProfile) + finally { - IExifValue width = exifProfile.GetValue(ExifTag.ImageWidth); - if (width == null) + foreach (IMemoryOwner buf in stripBuffers) { - TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); + buf?.Dispose(); } + } + } - DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); - - return (int)width.Value; + /// + /// Decodes the image data for chunky encoded pixel data. + /// + /// The pixel format. + /// The image frame to decode data into. + /// The rows per strip. + /// The strip offsets. + /// The strip byte counts. + /// The token to monitor cancellation. + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. + if (rowsPerStrip == TiffConstants.RowsPerStripInfinity) + { + rowsPerStrip = frame.Height; } - /// - /// Gets the height of the image frame. - /// - /// The image frame exif profile. - /// The image height. - private static int GetImageHeight(ExifProfile exifProfile) + int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); + int bitsPerPixel = this.BitsPerPixel; + + using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); + Span stripBufferSpan = stripBuffer.GetSpan(); + Buffer2D pixels = frame.PixelBuffer; + + using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( + this.Options, + this.CompressionType, + this.memoryAllocator, + this.PhotometricInterpretation, + frame.Width, + bitsPerPixel, + this.ColorType, + this.Predictor, + this.FaxCompressionOptions, + this.JpegTables, + this.FillOrder, + this.byteOrder); + + TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create( + this.configuration, + this.memoryAllocator, + this.ColorType, + this.BitsPerSample, + this.ExtraSamplesType, + this.ColorMap, + this.ReferenceBlackAndWhite, + this.YcbcrCoefficients, + this.YcbcrSubSampling, + this.byteOrder); + + for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { - IExifValue height = exifProfile.GetValue(ExifTag.ImageLength); - if (height == null) + cancellationToken.ThrowIfCancellationRequested(); + + int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 + ? rowsPerStrip + : frame.Height % rowsPerStrip; + + int top = rowsPerStrip * stripIndex; + if (top + stripHeight > frame.Height) { - TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); + // Make sure we ignore any strips that are not needed for the image (if too many are present). + break; } - return (int)height.Value; + decompressor.Decompress( + this.inputStream, + stripOffsets[stripIndex], + stripByteCounts[stripIndex], + stripHeight, + stripBufferSpan, + cancellationToken); + + colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); } } + + /// + /// Gets the width of the image frame. + /// + /// The image frame exif profile. + /// The image width. + private static int GetImageWidth(ExifProfile exifProfile) + { + IExifValue width = exifProfile.GetValue(ExifTag.ImageWidth); + if (width == null) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); + } + + DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); + + return (int)width.Value; + } + + /// + /// Gets the height of the image frame. + /// + /// The image frame exif profile. + /// The image height. + private static int GetImageHeight(ExifProfile exifProfile) + { + IExifValue height = exifProfile.GetValue(ExifTag.ImageLength); + if (height == null) + { + TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageLength"); + } + + return (int)height.Value; + } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index 1978b346fc..85444e6568 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -12,124 +9,123 @@ using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// The decoder metadata creator. +/// +internal static class TiffDecoderMetadataCreator { - /// - /// The decoder metadata creator. - /// - internal static class TiffDecoderMetadataCreator + public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) + where TPixel : unmanaged, IPixel { - public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) - where TPixel : unmanaged, IPixel + if (frames.Count < 1) { - if (frames.Count < 1) - { - TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); - } + TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); + } - ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].Metadata.ExifProfile); + ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].Metadata.ExifProfile); - if (!ignoreMetadata) + if (!ignoreMetadata) + { + for (int i = 0; i < frames.Count; i++) { - for (int i = 0; i < frames.Count; i++) + ImageFrame frame = frames[i]; + ImageFrameMetadata frameMetaData = frame.Metadata; + if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) { - ImageFrame frame = frames[i]; - ImageFrameMetadata frameMetaData = frame.Metadata; - if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) - { - frameMetaData.IptcProfile = new IptcProfile(iptcBytes); - } - - IExifValue xmpProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.XMP); - if (xmpProfileBytes != null) - { - frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value); - } - - IExifValue iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); - if (iccProfileBytes != null) - { - frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); - } + frameMetaData.IptcProfile = new IptcProfile(iptcBytes); } - } - return imageMetaData; + IExifValue xmpProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.XMP); + if (xmpProfileBytes != null) + { + frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value); + } + + IExifValue iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); + if (iccProfileBytes != null) + { + frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); + } + } } - public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) - { - var imageMetaData = new ImageMetadata(); - SetResolution(imageMetaData, exifProfile); + return imageMetaData; + } - TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); - tiffMetadata.ByteOrder = byteOrder; - tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; + public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) + { + var imageMetaData = new ImageMetadata(); + SetResolution(imageMetaData, exifProfile); - return imageMetaData; - } + TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); + tiffMetadata.ByteOrder = byteOrder; + tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; + + return imageMetaData; + } - private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) + private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) + { + imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; + double? horizontalResolution = exifProfile?.GetValue(ExifTag.XResolution)?.Value.ToDouble(); + if (horizontalResolution != null) { - imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; - double? horizontalResolution = exifProfile?.GetValue(ExifTag.XResolution)?.Value.ToDouble(); - if (horizontalResolution != null) - { - imageMetaData.HorizontalResolution = horizontalResolution.Value; - } + imageMetaData.HorizontalResolution = horizontalResolution.Value; + } - double? verticalResolution = exifProfile?.GetValue(ExifTag.YResolution)?.Value.ToDouble(); - if (verticalResolution != null) - { - imageMetaData.VerticalResolution = verticalResolution.Value; - } + double? verticalResolution = exifProfile?.GetValue(ExifTag.YResolution)?.Value.ToDouble(); + if (verticalResolution != null) + { + imageMetaData.VerticalResolution = verticalResolution.Value; } + } + + private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] iptcBytes) + { + iptcBytes = null; + IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); - private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] iptcBytes) + if (iptc != null) { - iptcBytes = null; - IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); + if (iptc.DataType == ExifDataType.Byte || iptc.DataType == ExifDataType.Undefined) + { + iptcBytes = (byte[])iptc.GetValue(); + return true; + } - if (iptc != null) + // Some Encoders write the data type of IPTC as long. + if (iptc.DataType == ExifDataType.Long) { - if (iptc.DataType == ExifDataType.Byte || iptc.DataType == ExifDataType.Undefined) + uint[] iptcValues = (uint[])iptc.GetValue(); + iptcBytes = new byte[iptcValues.Length * 4]; + Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); + if (iptcBytes[0] == 0x1c) { - iptcBytes = (byte[])iptc.GetValue(); return true; } - - // Some Encoders write the data type of IPTC as long. - if (iptc.DataType == ExifDataType.Long) + else if (iptcBytes[3] != 0x1c) { - uint[] iptcValues = (uint[])iptc.GetValue(); - iptcBytes = new byte[iptcValues.Length * 4]; - Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); - if (iptcBytes[0] == 0x1c) - { - return true; - } - else if (iptcBytes[3] != 0x1c) - { - return false; - } - - // Probably wrong endianess, swap byte order. - Span iptcBytesSpan = iptcBytes.AsSpan(); - Span buffer = stackalloc byte[4]; - for (int i = 0; i < iptcBytes.Length; i += 4) - { - iptcBytesSpan.Slice(i, 4).CopyTo(buffer); - iptcBytes[i] = buffer[3]; - iptcBytes[i + 1] = buffer[2]; - iptcBytes[i + 2] = buffer[1]; - iptcBytes[i + 3] = buffer[0]; - } + return false; + } - return true; + // Probably wrong endianess, swap byte order. + Span iptcBytesSpan = iptcBytes.AsSpan(); + Span buffer = stackalloc byte[4]; + for (int i = 0; i < iptcBytes.Length; i += 4) + { + iptcBytesSpan.Slice(i, 4).CopyTo(buffer); + iptcBytes[i] = buffer[3]; + iptcBytes[i + 1] = buffer[2]; + iptcBytes[i + 2] = buffer[1]; + iptcBytes[i + 3] = buffer[0]; } - } - return false; + return true; + } } + + return false; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index d3686852d9..7593840bbb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -1,504 +1,502 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// The decoder options parser. +/// +internal static class TiffDecoderOptionsParser { + private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; + /// - /// The decoder options parser. + /// Determines the TIFF compression and color types, and reads any associated parameters. /// - internal static class TiffDecoderOptionsParser + /// The options. + /// The exif profile of the frame to decode. + /// The IFD entries container to read the image format information for current frame. + public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) { - private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; - - /// - /// Determines the TIFF compression and color types, and reads any associated parameters. - /// - /// The options. - /// The exif profile of the frame to decode. - /// The IFD entries container to read the image format information for current frame. - public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + if (exifProfile.GetValueInternal(ExifTag.TileOffsets) is not null || exifProfile.GetValueInternal(ExifTag.TileByteCounts) is not null) { - if (exifProfile.GetValueInternal(ExifTag.TileOffsets) is not null || exifProfile.GetValueInternal(ExifTag.TileByteCounts) is not null) - { - TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); - } + TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); + } - IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples); - if (extraSamplesExifValue is not null) + IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples); + if (extraSamplesExifValue is not null) + { + short[] extraSamples = (short[])extraSamplesExifValue.GetValue(); + if (extraSamples.Length != 1) { - short[] extraSamples = (short[])extraSamplesExifValue.GetValue(); - if (extraSamples.Length != 1) - { - TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data."); - } - - var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; - options.ExtraSamplesType = extraSamplesType; - if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData)) - { - TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData."); - } + TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data."); } - TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; - if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) + var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; + options.ExtraSamplesType = extraSamplesType; + if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData)) { - TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); + TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData."); } + } - if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) - { - TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); - } + TiffFillOrder fillOrder = (TiffFillOrder?)exifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; + if (fillOrder == TiffFillOrder.LeastSignificantBitFirst && frameMetadata.BitsPerPixel != TiffBitsPerPixel.Bit1) + { + TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is only supported in combination with 1bit per pixel bicolor tiff's."); + } + + if (frameMetadata.Predictor == TiffPredictor.FloatingPoint) + { + TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); + } - TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); - TiffSampleFormat? sampleFormat = null; - if (sampleFormats != null) + TiffSampleFormat[] sampleFormats = exifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); + TiffSampleFormat? sampleFormat = null; + if (sampleFormats != null) + { + sampleFormat = sampleFormats[0]; + foreach (TiffSampleFormat format in sampleFormats) { - sampleFormat = sampleFormats[0]; - foreach (TiffSampleFormat format in sampleFormats) + if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float) { - if (format is not TiffSampleFormat.UnsignedInteger and not TiffSampleFormat.Float) - { - TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); - } + TiffThrowHelper.ThrowNotSupported("ImageSharp only supports the UnsignedInteger and Float SampleFormat."); } } + } - ushort[] ycbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; - if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2) - { - TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values."); - } - - if (ycbcrSubSampling != null && ycbcrSubSampling[1] > ycbcrSubSampling[0]) - { - TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz."); - } + ushort[] ycbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; + if (ycbcrSubSampling != null && ycbcrSubSampling.Length != 2) + { + TiffThrowHelper.ThrowImageFormatException("Invalid YCbCrSubsampling, expected 2 values."); + } - if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null) - { - TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); - } + if (ycbcrSubSampling != null && ycbcrSubSampling[1] > ycbcrSubSampling[0]) + { + TiffThrowHelper.ThrowImageFormatException("ChromaSubsampleVert shall always be less than or equal to ChromaSubsampleHoriz."); + } - VerifyRequiredFieldsArePresent(exifProfile, frameMetadata); - - options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; - options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; - options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; - options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger; - options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; - options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); - options.ReferenceBlackAndWhite = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value; - options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value; - options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; - options.FillOrder = fillOrder; - options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value; - - options.ParseColorType(exifProfile); - options.ParseCompression(frameMetadata.Compression, exifProfile); + if (exifProfile.GetValue(ExifTag.StripRowCounts)?.Value != null) + { + TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); } - private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + VerifyRequiredFieldsArePresent(exifProfile, frameMetadata); + + options.PlanarConfiguration = (TiffPlanarConfiguration?)exifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; + options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; + options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; + options.SampleFormat = sampleFormat ?? TiffSampleFormat.UnsignedInteger; + options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; + options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0); + options.ReferenceBlackAndWhite = exifProfile.GetValue(ExifTag.ReferenceBlackWhite)?.Value; + options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value; + options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; + options.FillOrder = fillOrder; + options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value; + + options.ParseColorType(exifProfile); + options.ParseCompression(frameMetadata.Compression, exifProfile); + } + + private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) + { + if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) { - if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) - { - TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); - } + TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); + } - if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null) - { - TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); - } + if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null) + { + TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); + } - if (frameMetadata.BitsPerPixel == null) - { - TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!"); - } + if (frameMetadata.BitsPerPixel == null) + { + TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!"); } + } - private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) + private static void ParseColorType(this TiffDecoderCore options, ExifProfile exifProfile) + { + switch (options.PhotometricInterpretation) { - switch (options.PhotometricInterpretation) + case TiffPhotometricInterpretation.WhiteIsZero: { - case TiffPhotometricInterpretation.WhiteIsZero: + if (options.BitsPerSample.Channels != 1) { - if (options.BitsPerSample.Channels != 1) + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel > 32) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + + switch (bitsPerChannel) + { + case 32: { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + if (options.SampleFormat == TiffSampleFormat.Float) + { + options.ColorType = TiffColorType.WhiteIsZero32Float; + return; + } + + options.ColorType = TiffColorType.WhiteIsZero32; + break; } - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 32) + case 24: { - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + options.ColorType = TiffColorType.WhiteIsZero24; + break; } - switch (bitsPerChannel) + case 16: { - case 32: - { - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = TiffColorType.WhiteIsZero32Float; - return; - } + options.ColorType = TiffColorType.WhiteIsZero16; + break; + } - options.ColorType = TiffColorType.WhiteIsZero32; - break; - } + case 8: + { + options.ColorType = TiffColorType.WhiteIsZero8; + break; + } - case 24: - { - options.ColorType = TiffColorType.WhiteIsZero24; - break; - } + case 4: + { + options.ColorType = TiffColorType.WhiteIsZero4; + break; + } - case 16: - { - options.ColorType = TiffColorType.WhiteIsZero16; - break; - } + case 1: + { + options.ColorType = TiffColorType.WhiteIsZero1; + break; + } - case 8: - { - options.ColorType = TiffColorType.WhiteIsZero8; - break; - } + default: + { + options.ColorType = TiffColorType.WhiteIsZero; + break; + } + } - case 4: - { - options.ColorType = TiffColorType.WhiteIsZero4; - break; - } + break; + } - case 1: - { - options.ColorType = TiffColorType.WhiteIsZero1; - break; - } + case TiffPhotometricInterpretation.BlackIsZero: + { + if (options.BitsPerSample.Channels != 1) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } - default: + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel > 32) + { + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + } + + switch (bitsPerChannel) + { + case 32: + { + if (options.SampleFormat == TiffSampleFormat.Float) { - options.ColorType = TiffColorType.WhiteIsZero; - break; + options.ColorType = TiffColorType.BlackIsZero32Float; + return; } + + options.ColorType = TiffColorType.BlackIsZero32; + break; } - break; - } + case 24: + { + options.ColorType = TiffColorType.BlackIsZero24; + break; + } - case TiffPhotometricInterpretation.BlackIsZero: - { - if (options.BitsPerSample.Channels != 1) + case 16: { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + options.ColorType = TiffColorType.BlackIsZero16; + break; } - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel > 32) + case 8: { - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); + options.ColorType = TiffColorType.BlackIsZero8; + break; } + case 4: + { + options.ColorType = TiffColorType.BlackIsZero4; + break; + } + + case 1: + { + options.ColorType = TiffColorType.BlackIsZero1; + break; + } + + default: + { + options.ColorType = TiffColorType.BlackIsZero; + break; + } + } + + break; + } + + case TiffPhotometricInterpretation.Rgb: + { + TiffBitsPerSample bitsPerSample = options.BitsPerSample; + if (bitsPerSample.Channels is not (3 or 4)) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); + } + + if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) || + (bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3))) + { + TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); + } + + if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + ushort bitsPerChannel = options.BitsPerSample.Channel0; switch (bitsPerChannel) { case 32: - { if (options.SampleFormat == TiffSampleFormat.Float) { - options.ColorType = TiffColorType.BlackIsZero32Float; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232; return; } - options.ColorType = TiffColorType.BlackIsZero32; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232; break; - } case 24: - { - options.ColorType = TiffColorType.BlackIsZero24; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424; break; - } case 16: - { - options.ColorType = TiffColorType.BlackIsZero16; + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616; break; - } - case 8: - { - options.ColorType = TiffColorType.BlackIsZero8; + case 14: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414; break; - } - case 4: - { - options.ColorType = TiffColorType.BlackIsZero4; + case 12: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212; break; - } - case 1: - { - options.ColorType = TiffColorType.BlackIsZero1; + case 10: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010; break; - } + case 8: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888; + break; + case 6: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666; + break; + case 5: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555; + break; + case 4: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444; + break; + case 3: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333; + break; + case 2: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222; + break; default: - { - options.ColorType = TiffColorType.BlackIsZero; + TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); break; - } } - - break; } - - case TiffPhotometricInterpretation.Rgb: + else { - TiffBitsPerSample bitsPerSample = options.BitsPerSample; - if (bitsPerSample.Channels is not (3 or 4)) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } - - if ((bitsPerSample.Channels == 3 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2)) || - (bitsPerSample.Channels == 4 && !(bitsPerSample.Channel0 == bitsPerSample.Channel1 && bitsPerSample.Channel1 == bitsPerSample.Channel2 && bitsPerSample.Channel2 == bitsPerSample.Channel3))) - { - TiffThrowHelper.ThrowNotSupported("Only BitsPerSample with equal bits per channel are supported."); - } - - if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) - { - ushort bitsPerChannel = options.BitsPerSample.Channel0; - switch (bitsPerChannel) - { - case 32: - if (options.SampleFormat == TiffSampleFormat.Float) - { - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.RgbFloat323232 : TiffColorType.RgbaFloat32323232; - return; - } - - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232 : TiffColorType.Rgba32323232; - break; - - case 24: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424 : TiffColorType.Rgba24242424; - break; - - case 16: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616 : TiffColorType.Rgba16161616; - break; - - case 14: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb141414 : TiffColorType.Rgba14141414; - break; - - case 12: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb121212 : TiffColorType.Rgba12121212; - break; - - case 10: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb101010 : TiffColorType.Rgba10101010; - break; - - case 8: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888 : TiffColorType.Rgba8888; - break; - case 6: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb666 : TiffColorType.Rgba6666; - break; - case 5: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb555 : TiffColorType.Rgba5555; - break; - case 4: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb444 : TiffColorType.Rgba4444; - break; - case 3: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb333 : TiffColorType.Rgba3333; - break; - case 2: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb222 : TiffColorType.Rgba2222; - break; - default: - TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported."); - break; - } - } - else + ushort bitsPerChannel = options.BitsPerSample.Channel0; + switch (bitsPerChannel) { - ushort bitsPerChannel = options.BitsPerSample.Channel0; - switch (bitsPerChannel) - { - case 32: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar; - break; - case 24: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar; - break; - case 16: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar; - break; - default: - options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar; - break; - } + case 32: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb323232Planar : TiffColorType.Rgba32323232Planar; + break; + case 24: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb242424Planar : TiffColorType.Rgba24242424Planar; + break; + case 16: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb161616Planar : TiffColorType.Rgba16161616Planar; + break; + default: + options.ColorType = options.BitsPerSample.Channels is 3 ? TiffColorType.Rgb888Planar : TiffColorType.Rgba8888Planar; + break; } - - break; } - case TiffPhotometricInterpretation.PaletteColor: - { - options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; - if (options.ColorMap != null) - { - if (options.BitsPerSample.Channels != 1) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); - } + break; + } - options.ColorType = TiffColorType.PaletteColor; - } - else + case TiffPhotometricInterpretation.PaletteColor: + { + options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + if (options.ColorMap != null) + { + if (options.BitsPerSample.Channels != 1) { - TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); } - break; + options.ColorType = TiffColorType.PaletteColor; } - - case TiffPhotometricInterpretation.YCbCr: + else { - options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; - if (options.BitsPerSample.Channels != 3) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images."); - } - - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel != 8) - { - TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for YCbCr images."); - } + TiffThrowHelper.ThrowNotSupported("The TIFF ColorMap entry is missing for a palette color image."); + } - options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar; + break; + } - break; + case TiffPhotometricInterpretation.YCbCr: + { + options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + if (options.BitsPerSample.Channels != 3) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for YCbCr images."); } - case TiffPhotometricInterpretation.CieLab: + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel != 8) { - if (options.BitsPerSample.Channels != 3) - { - TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CieLab images."); - } + TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for YCbCr images."); + } - ushort bitsPerChannel = options.BitsPerSample.Channel0; - if (bitsPerChannel != 8) - { - TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CieLab images."); - } + options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.YCbCr : TiffColorType.YCbCrPlanar; - options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.CieLab : TiffColorType.CieLabPlanar; + break; + } - break; + case TiffPhotometricInterpretation.CieLab: + { + if (options.BitsPerSample.Channels != 3) + { + TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported for CieLab images."); } - default: + ushort bitsPerChannel = options.BitsPerSample.Channel0; + if (bitsPerChannel != 8) { - TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); + TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CieLab images."); } + options.ColorType = options.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? TiffColorType.CieLab : TiffColorType.CieLabPlanar; + break; } - } - private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) - { - // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html - switch (compression ?? TiffCompression.None) + default: { - case TiffCompression.None: - { - options.CompressionType = TiffDecoderCompressionType.None; - break; - } + TiffThrowHelper.ThrowNotSupported($"The specified TIFF photometric interpretation is not supported: {options.PhotometricInterpretation}"); + } - case TiffCompression.PackBits: - { - options.CompressionType = TiffDecoderCompressionType.PackBits; - break; - } + break; + } + } - case TiffCompression.Deflate: - case TiffCompression.OldDeflate: - { - options.CompressionType = TiffDecoderCompressionType.Deflate; - break; - } + private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) + { + // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html + switch (compression ?? TiffCompression.None) + { + case TiffCompression.None: + { + options.CompressionType = TiffDecoderCompressionType.None; + break; + } - case TiffCompression.Lzw: - { - options.CompressionType = TiffDecoderCompressionType.Lzw; - break; - } + case TiffCompression.PackBits: + { + options.CompressionType = TiffDecoderCompressionType.PackBits; + break; + } - case TiffCompression.CcittGroup3Fax: - { - options.CompressionType = TiffDecoderCompressionType.T4; - options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; + case TiffCompression.Deflate: + case TiffCompression.OldDeflate: + { + options.CompressionType = TiffDecoderCompressionType.Deflate; + break; + } - break; - } + case TiffCompression.Lzw: + { + options.CompressionType = TiffDecoderCompressionType.Lzw; + break; + } - case TiffCompression.CcittGroup4Fax: - { - options.CompressionType = TiffDecoderCompressionType.T6; - options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; + case TiffCompression.CcittGroup3Fax: + { + options.CompressionType = TiffDecoderCompressionType.T4; + options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; - break; - } + break; + } - case TiffCompression.Ccitt1D: - { - options.CompressionType = TiffDecoderCompressionType.HuffmanRle; - break; - } + case TiffCompression.CcittGroup4Fax: + { + options.CompressionType = TiffDecoderCompressionType.T6; + options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None; - case TiffCompression.Jpeg: - { - options.CompressionType = TiffDecoderCompressionType.Jpeg; + break; + } - if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null) - { - // Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data. - options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; - options.ColorType = TiffColorType.Rgb; - } + case TiffCompression.Ccitt1D: + { + options.CompressionType = TiffDecoderCompressionType.HuffmanRle; + break; + } - break; - } + case TiffCompression.Jpeg: + { + options.CompressionType = TiffDecoderCompressionType.Jpeg; - case TiffCompression.Webp: + if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null) { - options.CompressionType = TiffDecoderCompressionType.Webp; - break; + // Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data. + options.PhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + options.ColorType = TiffColorType.Rgb; } - default: - { - TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported"); - break; - } + break; + } + + case TiffCompression.Webp: + { + options.CompressionType = TiffDecoderCompressionType.Webp; + break; + } + + default: + { + TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported"); + break; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs index 3115dced3d..3b5d347722 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs @@ -1,54 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Encoder for writing the data image to a stream in TIFF format. +/// +public class TiffEncoder : IImageEncoder, ITiffEncoderOptions { - /// - /// Encoder for writing the data image to a stream in TIFF format. - /// - public class TiffEncoder : IImageEncoder, ITiffEncoderOptions + /// + public TiffBitsPerPixel? BitsPerPixel { get; set; } + + /// + public TiffCompression? Compression { get; set; } + + /// + public DeflateCompressionLevel? CompressionLevel { get; set; } + + /// + public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } + + /// + public TiffPredictor? HorizontalPredictor { get; set; } + + /// + public IQuantizer Quantizer { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encode = new TiffEncoderCore(this, image.GetMemoryAllocator()); + encode.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - /// - public TiffBitsPerPixel? BitsPerPixel { get; set; } - - /// - public TiffCompression? Compression { get; set; } - - /// - public DeflateCompressionLevel? CompressionLevel { get; set; } - - /// - public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } - - /// - public TiffPredictor? HorizontalPredictor { get; set; } - - /// - public IQuantizer Quantizer { get; set; } - - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encode = new TiffEncoderCore(this, image.GetMemoryAllocator()); - encode.Encode(image, stream); - } - - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + var encoder = new TiffEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 9ccd4416fc..971353dbc8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -1,10 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Compression; @@ -17,429 +13,428 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Performs the TIFF encoding operation. +/// +internal sealed class TiffEncoderCore : IImageEncoderInternals { + private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian + ? TiffConstants.ByteOrderLittleEndianShort + : TiffConstants.ByteOrderBigEndianShort; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The quantizer for creating color palette image. + /// + private readonly IQuantizer quantizer; + + /// + /// Sets the deflate compression level. + /// + private readonly DeflateCompressionLevel compressionLevel; + + /// + /// The default predictor is None. + /// + private const TiffPredictor DefaultPredictor = TiffPredictor.None; + + /// + /// The default bits per pixel is Bit24. + /// + private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24; + + /// + /// The default compression is None. + /// + private const TiffCompression DefaultCompression = TiffCompression.None; + /// - /// Performs the TIFF encoding operation. + /// The default photometric interpretation is Rgb. /// - internal sealed class TiffEncoderCore : IImageEncoderInternals + private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; + + private readonly List<(long, uint)> frameMarkers = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The options for the encoder. + /// The memory allocator. + public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) { - private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian - ? TiffConstants.ByteOrderLittleEndianShort - : TiffConstants.ByteOrderBigEndianShort; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// The quantizer for creating color palette image. - /// - private readonly IQuantizer quantizer; - - /// - /// Sets the deflate compression level. - /// - private readonly DeflateCompressionLevel compressionLevel; - - /// - /// The default predictor is None. - /// - private const TiffPredictor DefaultPredictor = TiffPredictor.None; - - /// - /// The default bits per pixel is Bit24. - /// - private const TiffBitsPerPixel DefaultBitsPerPixel = TiffBitsPerPixel.Bit24; - - /// - /// The default compression is None. - /// - private const TiffCompression DefaultCompression = TiffCompression.None; - - /// - /// The default photometric interpretation is Rgb. - /// - private const TiffPhotometricInterpretation DefaultPhotometricInterpretation = TiffPhotometricInterpretation.Rgb; - - private readonly List<(long, uint)> frameMarkers = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The options for the encoder. - /// The memory allocator. - public TiffEncoderCore(ITiffEncoderOptions options, MemoryAllocator memoryAllocator) - { - this.memoryAllocator = memoryAllocator; - this.PhotometricInterpretation = options.PhotometricInterpretation; - this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; - this.BitsPerPixel = options.BitsPerPixel; - this.HorizontalPredictor = options.HorizontalPredictor; - this.CompressionType = options.Compression; - this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; - } + this.memoryAllocator = memoryAllocator; + this.PhotometricInterpretation = options.PhotometricInterpretation; + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.BitsPerPixel = options.BitsPerPixel; + this.HorizontalPredictor = options.HorizontalPredictor; + this.CompressionType = options.Compression; + this.compressionLevel = options.CompressionLevel ?? DeflateCompressionLevel.DefaultCompression; + } - /// - /// Gets the photometric interpretation implementation to use when encoding the image. - /// - internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } - - /// - /// Gets or sets the compression implementation to use when encoding the image. - /// - internal TiffCompression? CompressionType { get; set; } - - /// - /// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. - /// - internal TiffPredictor? HorizontalPredictor { get; set; } - - /// - /// Gets the bits per pixel. - /// - internal TiffBitsPerPixel? BitsPerPixel { get; private set; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); + /// + /// Gets the photometric interpretation implementation to use when encoding the image. + /// + internal TiffPhotometricInterpretation? PhotometricInterpretation { get; private set; } + + /// + /// Gets or sets the compression implementation to use when encoding the image. + /// + internal TiffCompression? CompressionType { get; set; } - this.configuration = image.GetConfiguration(); + /// + /// Gets or sets a value indicating which horizontal predictor to use. This can improve the compression ratio with deflate compression. + /// + internal TiffPredictor? HorizontalPredictor { get; set; } - ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); + /// + /// Gets the bits per pixel. + /// + internal TiffBitsPerPixel? BitsPerPixel { get; private set; } - // Determine the correct values to encode with. - // EncoderOptions > Metadata > Default. - TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel; + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation; + this.configuration = image.GetConfiguration(); - TiffPredictor predictor = - this.HorizontalPredictor - ?? rootFrameTiffMetaData.Predictor - ?? DefaultPredictor; + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; + TiffFrameMetadata rootFrameTiffMetaData = rootFrameMetaData.GetTiffMetadata(); - TiffCompression compression = - this.CompressionType - ?? rootFrameTiffMetaData.Compression - ?? DefaultCompression; + // Determine the correct values to encode with. + // EncoderOptions > Metadata > Default. + TiffBitsPerPixel? bitsPerPixel = this.BitsPerPixel ?? rootFrameTiffMetaData.BitsPerPixel; - // Make sure, the Encoder options makes sense in combination with each other. - this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor); + TiffPhotometricInterpretation? photometricInterpretation = this.PhotometricInterpretation ?? rootFrameTiffMetaData.PhotometricInterpretation; - using TiffStreamWriter writer = new(stream); - long ifdMarker = WriteHeader(writer); + TiffPredictor predictor = + this.HorizontalPredictor + ?? rootFrameTiffMetaData.Predictor + ?? DefaultPredictor; - Image metadataImage = image; - foreach (ImageFrame frame in image.Frames) - { - cancellationToken.ThrowIfCancellationRequested(); + TiffCompression compression = + this.CompressionType + ?? rootFrameTiffMetaData.Compression + ?? DefaultCompression; - TiffNewSubfileType subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage); + // Make sure, the Encoder options makes sense in combination with each other. + this.SanitizeAndSetEncoderOptions(bitsPerPixel, image.PixelType.BitsPerPixel, photometricInterpretation, compression, predictor); - ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker); - metadataImage = null; - } + using TiffStreamWriter writer = new(stream); + long ifdMarker = WriteHeader(writer); - long currentOffset = writer.BaseStream.Position; - foreach ((long, uint) marker in this.frameMarkers) - { - writer.WriteMarkerFast(marker.Item1, marker.Item2); - } + Image metadataImage = image; + foreach (ImageFrame frame in image.Frames) + { + cancellationToken.ThrowIfCancellationRequested(); - writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin); + TiffNewSubfileType subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage); + + ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker); + metadataImage = null; } - /// - /// Writes the TIFF file header. - /// - /// The to write data to. - /// - /// The marker to write the first IFD offset. - /// - public static long WriteHeader(TiffStreamWriter writer) + long currentOffset = writer.BaseStream.Position; + foreach ((long, uint) marker in this.frameMarkers) { - writer.Write(ByteOrderMarker); - writer.Write(TiffConstants.HeaderMagicNumber); - return writer.PlaceMarker(); + writer.WriteMarkerFast(marker.Item1, marker.Item2); } - /// - /// Writes all data required to define an image. - /// - /// The pixel format. - /// The to write data to. - /// The tiff frame. - /// The image metadata (resolution values for each frame). - /// The image (common metadata for root frame). - /// The marker to write this IFD offset. - /// - /// The next IFD offset value. - /// - private long WriteFrame( - TiffStreamWriter writer, - ImageFrame frame, - ImageMetadata imageMetadata, - Image image, - long ifdOffset) - where TPixel : unmanaged, IPixel + writer.BaseStream.Seek(currentOffset, SeekOrigin.Begin); + } + + /// + /// Writes the TIFF file header. + /// + /// The to write data to. + /// + /// The marker to write the first IFD offset. + /// + public static long WriteHeader(TiffStreamWriter writer) + { + writer.Write(ByteOrderMarker); + writer.Write(TiffConstants.HeaderMagicNumber); + return writer.PlaceMarker(); + } + + /// + /// Writes all data required to define an image. + /// + /// The pixel format. + /// The to write data to. + /// The tiff frame. + /// The image metadata (resolution values for each frame). + /// The image (common metadata for root frame). + /// The marker to write this IFD offset. + /// + /// The next IFD offset value. + /// + private long WriteFrame( + TiffStreamWriter writer, + ImageFrame frame, + ImageMetadata imageMetadata, + Image image, + long ifdOffset) + where TPixel : unmanaged, IPixel + { + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( + this.CompressionType ?? TiffCompression.None, + writer.BaseStream, + this.memoryAllocator, + frame.Width, + (int)this.BitsPerPixel, + this.compressionLevel, + this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); + + TiffEncoderEntriesCollector entriesCollector = new(); + using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( + this.PhotometricInterpretation, + frame, + this.quantizer, + this.memoryAllocator, + this.configuration, + entriesCollector, + (int)this.BitsPerPixel); + + int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType); + + colorWriter.Write(compressor, rowsPerStrip); + + if (image != null) { - using TiffBaseCompressor compressor = TiffCompressorFactory.Create( - this.CompressionType ?? TiffCompression.None, - writer.BaseStream, - this.memoryAllocator, - frame.Width, - (int)this.BitsPerPixel, - this.compressionLevel, - this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor.Value : TiffPredictor.None); - - TiffEncoderEntriesCollector entriesCollector = new(); - using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( - this.PhotometricInterpretation, - frame, - this.quantizer, - this.memoryAllocator, - this.configuration, - entriesCollector, - (int)this.BitsPerPixel); - - int rowsPerStrip = CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType); - - colorWriter.Write(compressor, rowsPerStrip); - - if (image != null) - { - entriesCollector.ProcessMetadata(image); - } + entriesCollector.ProcessMetadata(image); + } - entriesCollector.ProcessFrameInfo(frame, imageMetadata); - entriesCollector.ProcessImageFormat(this); + entriesCollector.ProcessFrameInfo(frame, imageMetadata); + entriesCollector.ProcessImageFormat(this); - this.frameMarkers.Add((ifdOffset, (uint)writer.Position)); + this.frameMarkers.Add((ifdOffset, (uint)writer.Position)); - return this.WriteIfd(writer, entriesCollector.Entries); - } + return this.WriteIfd(writer, entriesCollector.Entries); + } - /// - /// Calculates the number of rows written per strip. - /// - /// The height of the image. - /// The number of bytes per row. - /// The compression used. - /// Number of rows per strip. - private static int CalcRowsPerStrip(int height, int bytesPerRow, TiffCompression? compression) - { - DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); - DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); + /// + /// Calculates the number of rows written per strip. + /// + /// The height of the image. + /// The number of bytes per row. + /// The compression used. + /// Number of rows per strip. + private static int CalcRowsPerStrip(int height, int bytesPerRow, TiffCompression? compression) + { + DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); + DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); - // Jpeg compressed images should be written in one strip. - if (compression is TiffCompression.Jpeg) - { - return height; - } + // Jpeg compressed images should be written in one strip. + if (compression is TiffCompression.Jpeg) + { + return height; + } - // If compression is used, change stripSizeInBytes heuristically to a larger value to not write to many strips. - int stripSizeInBytes = compression is TiffCompression.Deflate || compression is TiffCompression.Lzw ? TiffConstants.DefaultStripSize * 2 : TiffConstants.DefaultStripSize; - int rowsPerStrip = stripSizeInBytes / bytesPerRow; + // If compression is used, change stripSizeInBytes heuristically to a larger value to not write to many strips. + int stripSizeInBytes = compression is TiffCompression.Deflate || compression is TiffCompression.Lzw ? TiffConstants.DefaultStripSize * 2 : TiffConstants.DefaultStripSize; + int rowsPerStrip = stripSizeInBytes / bytesPerRow; - if (rowsPerStrip > 0) + if (rowsPerStrip > 0) + { + if (rowsPerStrip < height) { - if (rowsPerStrip < height) - { - return rowsPerStrip; - } - - return height; + return rowsPerStrip; } - return 1; + return height; } - /// - /// Writes a TIFF IFD block. - /// - /// The to write data to. - /// The IFD entries to write to the file. - /// The marker to write the next IFD offset (if present). - private long WriteIfd(TiffStreamWriter writer, List entries) + return 1; + } + + /// + /// Writes a TIFF IFD block. + /// + /// The to write data to. + /// The IFD entries to write to the file. + /// The marker to write the next IFD offset (if present). + private long WriteIfd(TiffStreamWriter writer, List entries) + { + if (entries.Count == 0) { - if (entries.Count == 0) - { - TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); - } + TiffThrowHelper.ThrowArgumentException("There must be at least one entry per IFD."); + } - uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); - List largeDataBlocks = new(); + uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12)); + List largeDataBlocks = new(); - entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); + entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag); - writer.Write((ushort)entries.Count); + writer.Write((ushort)entries.Count); - foreach (IExifValue entry in entries) - { - writer.Write((ushort)entry.Tag); - writer.Write((ushort)entry.DataType); - writer.Write(ExifWriter.GetNumberOfComponents(entry)); + foreach (IExifValue entry in entries) + { + writer.Write((ushort)entry.Tag); + writer.Write((ushort)entry.DataType); + writer.Write(ExifWriter.GetNumberOfComponents(entry)); - uint length = ExifWriter.GetLength(entry); - if (length <= 4) - { - int sz = ExifWriter.WriteValue(entry, this.buffer, 0); - DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); - writer.WritePadded(this.buffer.AsSpan(0, sz)); - } - else - { - byte[] raw = new byte[length]; - int sz = ExifWriter.WriteValue(entry, raw, 0); - DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); - largeDataBlocks.Add(raw); - writer.Write(dataOffset); - dataOffset += (uint)(raw.Length + (raw.Length % 2)); - } + uint length = ExifWriter.GetLength(entry); + if (length <= 4) + { + int sz = ExifWriter.WriteValue(entry, this.buffer, 0); + DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); + writer.WritePadded(this.buffer.AsSpan(0, sz)); + } + else + { + byte[] raw = new byte[length]; + int sz = ExifWriter.WriteValue(entry, raw, 0); + DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written"); + largeDataBlocks.Add(raw); + writer.Write(dataOffset); + dataOffset += (uint)(raw.Length + (raw.Length % 2)); } + } - long nextIfdMarker = writer.PlaceMarker(); + long nextIfdMarker = writer.PlaceMarker(); - foreach (byte[] dataBlock in largeDataBlocks) - { - writer.Write(dataBlock); + foreach (byte[] dataBlock in largeDataBlocks) + { + writer.Write(dataBlock); - if (dataBlock.Length % 2 == 1) - { - writer.Write(0); - } + if (dataBlock.Length % 2 == 1) + { + writer.Write(0); } - - return nextIfdMarker; } - private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + return nextIfdMarker; + } + + private void SanitizeAndSetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, int inputBitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + { + // BitsPerPixel should be the primary source of truth for the encoder options. + if (bitsPerPixel.HasValue) { - // BitsPerPixel should be the primary source of truth for the encoder options. - if (bitsPerPixel.HasValue) + switch (bitsPerPixel) { - switch (bitsPerPixel) - { - case TiffBitsPerPixel.Bit1: - if (IsOneBitCompression(compression)) - { - // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); - break; - } - - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None); - break; - case TiffBitsPerPixel.Bit4: - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None); - break; - case TiffBitsPerPixel.Bit8: - this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); - break; - case TiffBitsPerPixel.Bit6: - case TiffBitsPerPixel.Bit10: - case TiffBitsPerPixel.Bit12: - case TiffBitsPerPixel.Bit14: - case TiffBitsPerPixel.Bit16: - case TiffBitsPerPixel.Bit30: - case TiffBitsPerPixel.Bit36: - case TiffBitsPerPixel.Bit42: - case TiffBitsPerPixel.Bit48: - // Encoding not yet supported bits per pixel will default to 24 bits. - this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); - break; - default: - this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); + case TiffBitsPerPixel.Bit1: + if (IsOneBitCompression(compression)) + { + // The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero. + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None); break; - } - - // Make sure 1 Bit compression is only used with 1 bit pixel type. - if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1) - { - // Invalid compression / bits per pixel combination, fallback to no compression. - this.CompressionType = DefaultCompression; - } + } - return; + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.BlackIsZero, compression, TiffPredictor.None); + break; + case TiffBitsPerPixel.Bit4: + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.PaletteColor, compression, TiffPredictor.None); + break; + case TiffBitsPerPixel.Bit8: + this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); + break; + case TiffBitsPerPixel.Bit6: + case TiffBitsPerPixel.Bit10: + case TiffBitsPerPixel.Bit12: + case TiffBitsPerPixel.Bit14: + case TiffBitsPerPixel.Bit16: + case TiffBitsPerPixel.Bit30: + case TiffBitsPerPixel.Bit36: + case TiffBitsPerPixel.Bit42: + case TiffBitsPerPixel.Bit48: + // Encoding not yet supported bits per pixel will default to 24 bits. + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); + break; + default: + this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); + break; } - // If no photometric interpretation was chosen, the input image bit per pixel should be preserved. - if (!photometricInterpretation.HasValue) + // Make sure 1 Bit compression is only used with 1 bit pixel type. + if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1) { - // At the moment only 8 and 32 bits per pixel can be preserved by the tiff encoder. - if (inputBitsPerPixel == 8) - { - this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); - return; - } + // Invalid compression / bits per pixel combination, fallback to no compression. + this.CompressionType = DefaultCompression; + } - this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor); + return; + } + + // If no photometric interpretation was chosen, the input image bit per pixel should be preserved. + if (!photometricInterpretation.HasValue) + { + // At the moment only 8 and 32 bits per pixel can be preserved by the tiff encoder. + if (inputBitsPerPixel == 8) + { + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, compression, predictor); return; } - switch (photometricInterpretation) - { - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - if (IsOneBitCompression(this.CompressionType)) - { - this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None); - return; - } + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, predictor); + return; + } - this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); + switch (photometricInterpretation) + { + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (IsOneBitCompression(this.CompressionType)) + { + this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None); return; + } - case TiffPhotometricInterpretation.PaletteColor: - this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); - return; + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); + return; - case TiffPhotometricInterpretation.Rgb: - // Make sure 1 Bit compression is only used with 1 bit pixel type. - if (IsOneBitCompression(this.CompressionType)) - { - // Invalid compression / bits per pixel combination, fallback to no compression. - compression = DefaultCompression; - } + case TiffPhotometricInterpretation.PaletteColor: + this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor); + return; - this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor); - return; - } + case TiffPhotometricInterpretation.Rgb: + // Make sure 1 Bit compression is only used with 1 bit pixel type. + if (IsOneBitCompression(this.CompressionType)) + { + // Invalid compression / bits per pixel combination, fallback to no compression. + compression = DefaultCompression; + } - this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor); + this.SetEncoderOptions(TiffBitsPerPixel.Bit24, photometricInterpretation, compression, predictor); + return; } - private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) - { - this.BitsPerPixel = bitsPerPixel; - this.PhotometricInterpretation = photometricInterpretation; - this.CompressionType = compression; - this.HorizontalPredictor = predictor; - } + this.SetEncoderOptions(DefaultBitsPerPixel, DefaultPhotometricInterpretation, compression, predictor); + } - public static bool IsOneBitCompression(TiffCompression? compression) - => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax; + private void SetEncoderOptions(TiffBitsPerPixel? bitsPerPixel, TiffPhotometricInterpretation? photometricInterpretation, TiffCompression compression, TiffPredictor predictor) + { + this.BitsPerPixel = bitsPerPixel; + this.PhotometricInterpretation = photometricInterpretation; + this.CompressionType = compression; + this.HorizontalPredictor = predictor; } + + public static bool IsOneBitCompression(TiffCompression? compression) + => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax; } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 77e198fcb5..e158900cdc 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -1,412 +1,410 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +internal class TiffEncoderEntriesCollector { - internal class TiffEncoderEntriesCollector - { - private const string SoftwareValue = "ImageSharp"; + private const string SoftwareValue = "ImageSharp"; - public List Entries { get; } = new List(); + public List Entries { get; } = new List(); - public void ProcessMetadata(Image image) - => new MetadataProcessor(this).Process(image); + public void ProcessMetadata(Image image) + => new MetadataProcessor(this).Process(image); - public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata) - => new FrameInfoProcessor(this).Process(frame, imageMetadata); + public void ProcessFrameInfo(ImageFrame frame, ImageMetadata imageMetadata) + => new FrameInfoProcessor(this).Process(frame, imageMetadata); - public void ProcessImageFormat(TiffEncoderCore encoder) - => new ImageFormatProcessor(this).Process(encoder); + public void ProcessImageFormat(TiffEncoderCore encoder) + => new ImageFormatProcessor(this).Process(encoder); - public void AddOrReplace(IExifValue entry) + public void AddOrReplace(IExifValue entry) + { + int index = this.Entries.FindIndex(t => t.Tag == entry.Tag); + if (index >= 0) { - int index = this.Entries.FindIndex(t => t.Tag == entry.Tag); - if (index >= 0) - { - this.Entries[index] = entry; - } - else - { - this.Entries.Add(entry); - } + this.Entries[index] = entry; } + else + { + this.Entries.Add(entry); + } + } - private void Add(IExifValue entry) => this.Entries.Add(entry); + private void Add(IExifValue entry) => this.Entries.Add(entry); - private abstract class BaseProcessor - { - public BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector; + private abstract class BaseProcessor + { + public BaseProcessor(TiffEncoderEntriesCollector collector) => this.Collector = collector; + + protected TiffEncoderEntriesCollector Collector { get; } + } - protected TiffEncoderEntriesCollector Collector { get; } + private class MetadataProcessor : BaseProcessor + { + public MetadataProcessor(TiffEncoderEntriesCollector collector) + : base(collector) + { } - private class MetadataProcessor : BaseProcessor + public void Process(Image image) { - public MetadataProcessor(TiffEncoderEntriesCollector collector) - : base(collector) + ImageFrame rootFrame = image.Frames.RootFrame; + ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile(); + XmpProfile rootFrameXmpProfile = rootFrame.Metadata.XmpProfile; + + this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpProfile); + this.ProcessMetadata(rootFrameExifProfile); + + if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software)) { + this.Collector.Add(new ExifString(ExifTagValue.Software) + { + Value = SoftwareValue + }); } + } - public void Process(Image image) + private static bool IsPureMetadata(ExifTag tag) + { + switch ((ExifTagValue)(ushort)tag) { - ImageFrame rootFrame = image.Frames.RootFrame; - ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile(); - XmpProfile rootFrameXmpProfile = rootFrame.Metadata.XmpProfile; - - this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpProfile); - this.ProcessMetadata(rootFrameExifProfile); + case ExifTagValue.DocumentName: + case ExifTagValue.ImageDescription: + case ExifTagValue.Make: + case ExifTagValue.Model: + case ExifTagValue.Software: + case ExifTagValue.DateTime: + case ExifTagValue.Artist: + case ExifTagValue.HostComputer: + case ExifTagValue.TargetPrinter: + case ExifTagValue.XMP: + case ExifTagValue.Rating: + case ExifTagValue.RatingPercent: + case ExifTagValue.ImageID: + case ExifTagValue.Copyright: + case ExifTagValue.MDLabName: + case ExifTagValue.MDSampleInfo: + case ExifTagValue.MDPrepDate: + case ExifTagValue.MDPrepTime: + case ExifTagValue.MDFileUnits: + case ExifTagValue.SEMInfo: + case ExifTagValue.XPTitle: + case ExifTagValue.XPComment: + case ExifTagValue.XPAuthor: + case ExifTagValue.XPKeywords: + case ExifTagValue.XPSubject: + return true; + default: + return false; + } + } - if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software)) + private void ProcessMetadata(ExifProfile exifProfile) + { + foreach (IExifValue entry in exifProfile.Values) + { + // todo: skip subIfd + if (entry.DataType == ExifDataType.Ifd) { - this.Collector.Add(new ExifString(ExifTagValue.Software) - { - Value = SoftwareValue - }); + continue; } - } - private static bool IsPureMetadata(ExifTag tag) - { - switch ((ExifTagValue)(ushort)tag) + switch ((ExifTagValue)(ushort)entry.Tag) { - case ExifTagValue.DocumentName: - case ExifTagValue.ImageDescription: - case ExifTagValue.Make: - case ExifTagValue.Model: - case ExifTagValue.Software: - case ExifTagValue.DateTime: - case ExifTagValue.Artist: - case ExifTagValue.HostComputer: - case ExifTagValue.TargetPrinter: + case ExifTagValue.SubIFDOffset: + case ExifTagValue.GPSIFDOffset: + case ExifTagValue.SubIFDs: case ExifTagValue.XMP: - case ExifTagValue.Rating: - case ExifTagValue.RatingPercent: - case ExifTagValue.ImageID: - case ExifTagValue.Copyright: - case ExifTagValue.MDLabName: - case ExifTagValue.MDSampleInfo: - case ExifTagValue.MDPrepDate: - case ExifTagValue.MDPrepTime: - case ExifTagValue.MDFileUnits: - case ExifTagValue.SEMInfo: - case ExifTagValue.XPTitle: - case ExifTagValue.XPComment: - case ExifTagValue.XPAuthor: - case ExifTagValue.XPKeywords: - case ExifTagValue.XPSubject: - return true; - default: - return false; + case ExifTagValue.IPTC: + case ExifTagValue.IccProfile: + continue; } - } - private void ProcessMetadata(ExifProfile exifProfile) - { - foreach (IExifValue entry in exifProfile.Values) + switch (ExifTags.GetPart(entry.Tag)) { - // todo: skip subIfd - if (entry.DataType == ExifDataType.Ifd) - { - continue; - } + case ExifParts.ExifTags: + case ExifParts.GpsTags: + break; - switch ((ExifTagValue)(ushort)entry.Tag) - { - case ExifTagValue.SubIFDOffset: - case ExifTagValue.GPSIFDOffset: - case ExifTagValue.SubIFDs: - case ExifTagValue.XMP: - case ExifTagValue.IPTC: - case ExifTagValue.IccProfile: + case ExifParts.IfdTags: + if (!IsPureMetadata(entry.Tag)) + { continue; - } - - switch (ExifTags.GetPart(entry.Tag)) - { - case ExifParts.ExifTags: - case ExifParts.GpsTags: - break; - - case ExifParts.IfdTags: - if (!IsPureMetadata(entry.Tag)) - { - continue; - } + } - break; - } + break; + } - if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag)) - { - this.Collector.AddOrReplace(entry.DeepClone()); - } + if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag)) + { + this.Collector.AddOrReplace(entry.DeepClone()); } } + } - private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, XmpProfile xmpProfile) + private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, XmpProfile xmpProfile) + { + if (exifProfile != null && exifProfile.Parts != ExifParts.None) { - if (exifProfile != null && exifProfile.Parts != ExifParts.None) + foreach (IExifValue entry in exifProfile.Values) { - foreach (IExifValue entry in exifProfile.Values) + if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) { - if (!this.Collector.Entries.Exists(t => t.Tag == entry.Tag) && entry.GetValue() != null) + ExifParts entryPart = ExifTags.GetPart(entry.Tag); + if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) { - ExifParts entryPart = ExifTags.GetPart(entry.Tag); - if (entryPart != ExifParts.None && exifProfile.Parts.HasFlag(entryPart)) - { - this.Collector.AddOrReplace(entry.DeepClone()); - } + this.Collector.AddOrReplace(entry.DeepClone()); } } } - else - { - exifProfile.RemoveValue(ExifTag.SubIFDOffset); - } + } + else + { + exifProfile.RemoveValue(ExifTag.SubIFDOffset); + } - if (imageMetadata.IptcProfile != null) + if (imageMetadata.IptcProfile != null) + { + imageMetadata.IptcProfile.UpdateData(); + var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte) { - imageMetadata.IptcProfile.UpdateData(); - var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte) - { - Value = imageMetadata.IptcProfile.Data - }; + Value = imageMetadata.IptcProfile.Data + }; - this.Collector.Add(iptc); - } - else - { - exifProfile.RemoveValue(ExifTag.IPTC); - } + this.Collector.Add(iptc); + } + else + { + exifProfile.RemoveValue(ExifTag.IPTC); + } - if (imageMetadata.IccProfile != null) + if (imageMetadata.IccProfile != null) + { + var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined) { - var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined) - { - Value = imageMetadata.IccProfile.ToByteArray() - }; + Value = imageMetadata.IccProfile.ToByteArray() + }; - this.Collector.Add(icc); - } - else - { - exifProfile.RemoveValue(ExifTag.IccProfile); - } + this.Collector.Add(icc); + } + else + { + exifProfile.RemoveValue(ExifTag.IccProfile); + } - if (xmpProfile != null) + if (xmpProfile != null) + { + var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) { - var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) - { - Value = xmpProfile.Data - }; + Value = xmpProfile.Data + }; - this.Collector.Add(xmp); - } - else - { - exifProfile.RemoveValue(ExifTag.XMP); - } + this.Collector.Add(xmp); + } + else + { + exifProfile.RemoveValue(ExifTag.XMP); } } + } - private class FrameInfoProcessor : BaseProcessor + private class FrameInfoProcessor : BaseProcessor + { + public FrameInfoProcessor(TiffEncoderEntriesCollector collector) + : base(collector) { - public FrameInfoProcessor(TiffEncoderEntriesCollector collector) - : base(collector) + } + + public void Process(ImageFrame frame, ImageMetadata imageMetadata) + { + this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth) { - } + Value = (uint)frame.Width + }); - public void Process(ImageFrame frame, ImageMetadata imageMetadata) + this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength) { - this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageWidth) - { - Value = (uint)frame.Width - }); + Value = (uint)frame.Height + }); - this.Collector.AddOrReplace(new ExifLong(ExifTagValue.ImageLength) - { - Value = (uint)frame.Height - }); + this.ProcessResolution(imageMetadata); + } - this.ProcessResolution(imageMetadata); - } + private void ProcessResolution(ImageMetadata imageMetadata) + { + ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues( + imageMetadata.ResolutionUnits, + imageMetadata.HorizontalResolution, + imageMetadata.VerticalResolution); - private void ProcessResolution(ImageMetadata imageMetadata) + this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit) { - ExifResolutionValues resolution = UnitConverter.GetExifResolutionValues( - imageMetadata.ResolutionUnits, - imageMetadata.HorizontalResolution, - imageMetadata.VerticalResolution); + Value = resolution.ResolutionUnit + }); - this.Collector.AddOrReplace(new ExifShort(ExifTagValue.ResolutionUnit) + if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue) + { + this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution) { - Value = resolution.ResolutionUnit + Value = new Rational(resolution.HorizontalResolution.Value) }); - if (resolution.VerticalResolution.HasValue && resolution.HorizontalResolution.HasValue) + this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution) { - this.Collector.AddOrReplace(new ExifRational(ExifTagValue.XResolution) - { - Value = new Rational(resolution.HorizontalResolution.Value) - }); - - this.Collector.AddOrReplace(new ExifRational(ExifTagValue.YResolution) - { - Value = new Rational(resolution.VerticalResolution.Value) - }); - } + Value = new Rational(resolution.VerticalResolution.Value) + }); } } + } + + private class ImageFormatProcessor : BaseProcessor + { + public ImageFormatProcessor(TiffEncoderEntriesCollector collector) + : base(collector) + { + } - private class ImageFormatProcessor : BaseProcessor + public void Process(TiffEncoderCore encoder) { - public ImageFormatProcessor(TiffEncoderEntriesCollector collector) - : base(collector) + var planarConfig = new ExifShort(ExifTagValue.PlanarConfiguration) { - } + Value = (ushort)TiffPlanarConfiguration.Chunky + }; - public void Process(TiffEncoderCore encoder) + var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel) { - var planarConfig = new ExifShort(ExifTagValue.PlanarConfiguration) - { - Value = (ushort)TiffPlanarConfiguration.Chunky - }; + Value = GetSamplesPerPixel(encoder) + }; - var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel) - { - Value = GetSamplesPerPixel(encoder) - }; - - ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); - var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample) - { - Value = bitsPerSampleValue - }; + ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder); + var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample) + { + Value = bitsPerSampleValue + }; - ushort compressionType = GetCompressionType(encoder); - var compression = new ExifShort(ExifTagValue.Compression) - { - Value = compressionType - }; + ushort compressionType = GetCompressionType(encoder); + var compression = new ExifShort(ExifTagValue.Compression) + { + Value = compressionType + }; - var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) - { - Value = (ushort)encoder.PhotometricInterpretation - }; + var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation) + { + Value = (ushort)encoder.PhotometricInterpretation + }; - this.Collector.AddOrReplace(planarConfig); - this.Collector.AddOrReplace(samplesPerPixel); - this.Collector.AddOrReplace(bitPerSample); - this.Collector.AddOrReplace(compression); - this.Collector.AddOrReplace(photometricInterpretation); + this.Collector.AddOrReplace(planarConfig); + this.Collector.AddOrReplace(samplesPerPixel); + this.Collector.AddOrReplace(bitPerSample); + this.Collector.AddOrReplace(compression); + this.Collector.AddOrReplace(photometricInterpretation); - if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) + if (encoder.HorizontalPredictor == TiffPredictor.Horizontal) + { + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) { - if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || - encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || - encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) - { - var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; + var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal }; - this.Collector.AddOrReplace(predictor); - } + this.Collector.AddOrReplace(predictor); } } + } - private static uint GetSamplesPerPixel(TiffEncoderCore encoder) + private static uint GetSamplesPerPixel(TiffEncoderCore encoder) + { + switch (encoder.PhotometricInterpretation) { - switch (encoder.PhotometricInterpretation) - { - case TiffPhotometricInterpretation.PaletteColor: - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - return 1; - case TiffPhotometricInterpretation.Rgb: - default: - return 3; - } + case TiffPhotometricInterpretation.PaletteColor: + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + return 1; + case TiffPhotometricInterpretation.Rgb: + default: + return 3; } + } - private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) + private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder) + { + switch (encoder.PhotometricInterpretation) { - switch (encoder.PhotometricInterpretation) - { - case TiffPhotometricInterpretation.PaletteColor: - if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) - { - return TiffConstants.BitsPerSample4Bit.ToArray(); - } - else - { - return TiffConstants.BitsPerSample8Bit.ToArray(); - } + case TiffPhotometricInterpretation.PaletteColor: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) + { + return TiffConstants.BitsPerSample4Bit.ToArray(); + } + else + { + return TiffConstants.BitsPerSample8Bit.ToArray(); + } - case TiffPhotometricInterpretation.Rgb: - return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); + case TiffPhotometricInterpretation.Rgb: + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); - case TiffPhotometricInterpretation.WhiteIsZero: - if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) - { - return TiffConstants.BitsPerSample1Bit.ToArray(); - } + case TiffPhotometricInterpretation.WhiteIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit.ToArray(); + } - return TiffConstants.BitsPerSample8Bit.ToArray(); + return TiffConstants.BitsPerSample8Bit.ToArray(); - case TiffPhotometricInterpretation.BlackIsZero: - if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) - { - return TiffConstants.BitsPerSample1Bit.ToArray(); - } + case TiffPhotometricInterpretation.BlackIsZero: + if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) + { + return TiffConstants.BitsPerSample1Bit.ToArray(); + } - return TiffConstants.BitsPerSample8Bit.ToArray(); + return TiffConstants.BitsPerSample8Bit.ToArray(); - default: - return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); - } + default: + return TiffConstants.BitsPerSampleRgb8Bit.ToArray(); } + } - private static ushort GetCompressionType(TiffEncoderCore encoder) + private static ushort GetCompressionType(TiffEncoderCore encoder) + { + switch (encoder.CompressionType) { - switch (encoder.CompressionType) - { - case TiffCompression.Deflate: - // Deflate is allowed for all modes. - return (ushort)TiffCompression.Deflate; - case TiffCompression.PackBits: - // PackBits is allowed for all modes. - return (ushort)TiffCompression.PackBits; - case TiffCompression.Lzw: - if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || - encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || - encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) - { - return (ushort)TiffCompression.Lzw; - } - - break; + case TiffCompression.Deflate: + // Deflate is allowed for all modes. + return (ushort)TiffCompression.Deflate; + case TiffCompression.PackBits: + // PackBits is allowed for all modes. + return (ushort)TiffCompression.PackBits; + case TiffCompression.Lzw: + if (encoder.PhotometricInterpretation == TiffPhotometricInterpretation.Rgb || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor || + encoder.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + return (ushort)TiffCompression.Lzw; + } - case TiffCompression.CcittGroup3Fax: - return (ushort)TiffCompression.CcittGroup3Fax; + break; - case TiffCompression.CcittGroup4Fax: - return (ushort)TiffCompression.CcittGroup4Fax; + case TiffCompression.CcittGroup3Fax: + return (ushort)TiffCompression.CcittGroup3Fax; - case TiffCompression.Ccitt1D: - return (ushort)TiffCompression.Ccitt1D; + case TiffCompression.CcittGroup4Fax: + return (ushort)TiffCompression.CcittGroup4Fax; - case TiffCompression.Jpeg: - return (ushort)TiffCompression.Jpeg; - } + case TiffCompression.Ccitt1D: + return (ushort)TiffCompression.Ccitt1D; - return (ushort)TiffCompression.None; + case TiffCompression.Jpeg: + return (ushort)TiffCompression.Jpeg; } + + return (ushort)TiffCompression.None; } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs index ae525366e9..484fc993b0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs +++ b/src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Description of extra components. +/// +internal enum TiffExtraSampleType { /// - /// Description of extra components. + /// The data is unspecified, not supported. /// - internal enum TiffExtraSampleType - { - /// - /// The data is unspecified, not supported. - /// - UnspecifiedData = 0, + UnspecifiedData = 0, - /// - /// The extra data is associated alpha data (with pre-multiplied color). - /// - AssociatedAlphaData = 1, + /// + /// The extra data is associated alpha data (with pre-multiplied color). + /// + AssociatedAlphaData = 1, - /// - /// The extra data is unassociated alpha data is transparency information that logically exists independent of an image; - /// it is commonly called a soft matte. - /// - UnassociatedAlphaData = 2 - } + /// + /// The extra data is unassociated alpha data is transparency information that logically exists independent of an image; + /// it is commonly called a soft matte. + /// + UnassociatedAlphaData = 2 } diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs index 6945d1b374..62ad93c840 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFormat.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFormat.cs @@ -1,41 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff.Constants; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Encapsulates the means to encode and decode Tiff images. +/// +public sealed class TiffFormat : IImageFormat { - /// - /// Encapsulates the means to encode and decode Tiff images. - /// - public sealed class TiffFormat : IImageFormat + private TiffFormat() { - private TiffFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static TiffFormat Instance { get; } = new TiffFormat(); + /// + /// Gets the current instance. + /// + public static TiffFormat Instance { get; } = new TiffFormat(); - /// - public string Name => "TIFF"; + /// + public string Name => "TIFF"; - /// - public string DefaultMimeType => "image/tiff"; + /// + public string DefaultMimeType => "image/tiff"; - /// - public IEnumerable MimeTypes => TiffConstants.MimeTypes; + /// + public IEnumerable MimeTypes => TiffConstants.MimeTypes; - /// - public IEnumerable FileExtensions => TiffConstants.FileExtensions; + /// + public IEnumerable FileExtensions => TiffConstants.FileExtensions; - /// - public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata(); + /// + public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata(); - /// - public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata(); - } + /// + public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata(); } diff --git a/src/ImageSharp/Formats/Tiff/TiffFormatType.cs b/src/ImageSharp/Formats/Tiff/TiffFormatType.cs index 7e5f37ac97..ca3f278752 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFormatType.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFormatType.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// The TIFF format type enum. +/// +public enum TiffFormatType { /// - /// The TIFF format type enum. + /// The TIFF file format type. /// - public enum TiffFormatType - { - /// - /// The TIFF file format type. - /// - Default, + Default, - /// - /// The BigTIFF format type. - /// - BigTIFF - } + /// + /// The BigTIFF format type. + /// + BigTIFF } diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 7b509a592a..ee8ba1ce01 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -4,97 +4,96 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Provides Tiff specific metadata information for the frame. +/// +public class TiffFrameMetadata : IDeepCloneable { /// - /// Provides Tiff specific metadata information for the frame. + /// Initializes a new instance of the class. /// - public class TiffFrameMetadata : IDeepCloneable + public TiffFrameMetadata() { - /// - /// Initializes a new instance of the class. - /// - public TiffFrameMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The other tiff frame metadata. - private TiffFrameMetadata(TiffFrameMetadata other) - { - this.BitsPerPixel = other.BitsPerPixel; - this.Compression = other.Compression; - this.PhotometricInterpretation = other.PhotometricInterpretation; - this.Predictor = other.Predictor; - } + /// + /// Initializes a new instance of the class. + /// + /// The other tiff frame metadata. + private TiffFrameMetadata(TiffFrameMetadata other) + { + this.BitsPerPixel = other.BitsPerPixel; + this.Compression = other.Compression; + this.PhotometricInterpretation = other.PhotometricInterpretation; + this.Predictor = other.Predictor; + } - /// - /// Gets or sets the bits per pixel. - /// - public TiffBitsPerPixel? BitsPerPixel { get; set; } + /// + /// Gets or sets the bits per pixel. + /// + public TiffBitsPerPixel? BitsPerPixel { get; set; } - /// - /// Gets or sets number of bits per component. - /// - public TiffBitsPerSample? BitsPerSample { get; set; } + /// + /// Gets or sets number of bits per component. + /// + public TiffBitsPerSample? BitsPerSample { get; set; } - /// - /// Gets or sets the compression scheme used on the image data. - /// - public TiffCompression? Compression { get; set; } + /// + /// Gets or sets the compression scheme used on the image data. + /// + public TiffCompression? Compression { get; set; } - /// - /// Gets or sets the color space of the image data. - /// - public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } + /// + /// Gets or sets the color space of the image data. + /// + public TiffPhotometricInterpretation? PhotometricInterpretation { get; set; } - /// - /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. - /// - public TiffPredictor? Predictor { get; set; } + /// + /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. + /// + public TiffPredictor? Predictor { get; set; } - /// - /// Returns a new instance parsed from the given Exif profile. - /// - /// The Exif profile containing tiff frame directory tags to parse. - /// If null, a new instance is created and parsed instead. - /// The . - internal static TiffFrameMetadata Parse(ExifProfile profile) - { - var meta = new TiffFrameMetadata(); - Parse(meta, profile); - return meta; - } + /// + /// Returns a new instance parsed from the given Exif profile. + /// + /// The Exif profile containing tiff frame directory tags to parse. + /// If null, a new instance is created and parsed instead. + /// The . + internal static TiffFrameMetadata Parse(ExifProfile profile) + { + var meta = new TiffFrameMetadata(); + Parse(meta, profile); + return meta; + } - /// - /// Parses the given Exif profile to populate the properties of the tiff frame meta data. - /// - /// The tiff frame meta data. - /// The Exif profile containing tiff frame directory tags. - internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) + /// + /// Parses the given Exif profile to populate the properties of the tiff frame meta data. + /// + /// The tiff frame meta data. + /// The Exif profile containing tiff frame directory tags. + internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) + { + if (profile != null) { - if (profile != null) + if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample)) { - if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample)) - { - meta.BitsPerSample = bitsPerSample; - } + meta.BitsPerSample = bitsPerSample; + } - meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); - meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; - meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; - meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; + meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel(); + meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; + meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value; + meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; - profile.RemoveValue(ExifTag.BitsPerSample); - profile.RemoveValue(ExifTag.Compression); - profile.RemoveValue(ExifTag.PhotometricInterpretation); - profile.RemoveValue(ExifTag.Predictor); - } + profile.RemoveValue(ExifTag.BitsPerSample); + profile.RemoveValue(ExifTag.Compression); + profile.RemoveValue(ExifTag.PhotometricInterpretation); + profile.RemoveValue(ExifTag.Predictor); } - - /// - public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); } + + /// + public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); } diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs index 6a94f1e954..24166f6c0f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -1,67 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff; -namespace SixLabors.ImageSharp.Formats.Tiff +/// +/// Detects tiff file headers +/// +public sealed class TiffImageFormatDetector : IImageFormatDetector { - /// - /// Detects tiff file headers - /// - public sealed class TiffImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 8; + /// + public int HeaderSize => 8; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.IsSupportedFileFormat(header)) { - if (this.IsSupportedFileFormat(header)) - { - return TiffFormat.Instance; - } - - return null; + return TiffFormat.Instance; } - private bool IsSupportedFileFormat(ReadOnlySpan header) + return null; + } + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) { - if (header.Length >= this.HeaderSize) + if (header[0] == 0x49 && header[1] == 0x49) { - if (header[0] == 0x49 && header[1] == 0x49) + // Little-endian + if (header[2] == 0x2A && header[3] == 0x00) { - // Little-endian - if (header[2] == 0x2A && header[3] == 0x00) - { - // tiff - return true; - } - else if (header[2] == 0x2B && header[3] == 0x00 - && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) - { - // big tiff - return true; - } + // tiff + return true; } - else if (header[0] == 0x4D && header[1] == 0x4D) + else if (header[2] == 0x2B && header[3] == 0x00 + && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) { - // Big-endian - if (header[2] == 0 && header[3] == 0x2A) - { - // tiff - return true; - } - else - if (header[2] == 0 && header[3] == 0x2B - && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) - { - // big tiff - return true; - } + // big tiff + return true; + } + } + else if (header[0] == 0x4D && header[1] == 0x4D) + { + // Big-endian + if (header[2] == 0 && header[3] == 0x2A) + { + // tiff + return true; + } + else + if (header[2] == 0 && header[3] == 0x2B + && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) + { + // big tiff + return true; } } - - return false; } + + return false; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index 541f39f75b..29ffc82ce0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -1,37 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Provides Tiff specific metadata information for the image. +/// +public class TiffMetadata : IDeepCloneable { /// - /// Provides Tiff specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class TiffMetadata : IDeepCloneable + public TiffMetadata() { - /// - /// Initializes a new instance of the class. - /// - public TiffMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private TiffMetadata(TiffMetadata other) => this.ByteOrder = other.ByteOrder; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private TiffMetadata(TiffMetadata other) => this.ByteOrder = other.ByteOrder; - /// - /// Gets or sets the byte order. - /// - public ByteOrder ByteOrder { get; set; } + /// + /// Gets or sets the byte order. + /// + public ByteOrder ByteOrder { get; set; } - /// - /// Gets or sets the format type. - /// - public TiffFormatType FormatType { get; set; } + /// + /// Gets or sets the format type. + /// + public TiffFormatType FormatType { get; set; } - /// - public IDeepCloneable DeepClone() => new TiffMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new TiffMetadata(this); } diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs index 71eae308e6..8c7deecbfb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Tiff +namespace SixLabors.ImageSharp.Formats.Tiff; + +/// +/// Cold path optimizations for throwing tiff format based exceptions. +/// +internal static class TiffThrowHelper { /// - /// Cold path optimizations for throwing tiff format based exceptions. + /// Cold path optimization for throwing -s /// - internal static class TiffThrowHelper - { - /// - /// Cold path optimization for throwing -s - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] - public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); - [MethodImpl(InliningOptions.ColdPath)] - public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception NotSupportedCompressor(string compressionType) => throw new NotSupportedException($"Not supported encoder compression method: {compressionType}"); - [MethodImpl(InliningOptions.ColdPath)] - public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}"); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception InvalidColorType(string colorType) => throw new NotSupportedException($"Invalid color type: {colorType}"); - [MethodImpl(InliningOptions.ColdPath)] - public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header."); + [MethodImpl(InliningOptions.ColdPath)] + public static Exception ThrowInvalidHeader() => throw new ImageFormatException("Invalid TIFF file header."); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupported(string message) => throw new NotSupportedException(message); - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowArgumentException(string message) => throw new ArgumentException(message); - } + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowArgumentException(string message) => throw new ArgumentException(message); } diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs index 9f47a1b886..88722f8306 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs @@ -1,66 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Tiff.Utils; -namespace SixLabors.ImageSharp.Formats.Tiff.Utils +/// +/// Utility class to read a sequence of bits from an array +/// +internal ref struct BitReader { + private readonly ReadOnlySpan array; + private int offset; + private int bitOffset; + /// - /// Utility class to read a sequence of bits from an array + /// Initializes a new instance of the struct. /// - internal ref struct BitReader + /// The array to read data from. + public BitReader(ReadOnlySpan array) { - private readonly ReadOnlySpan array; - private int offset; - private int bitOffset; + this.array = array; + this.offset = 0; + this.bitOffset = 0; + } - /// - /// Initializes a new instance of the struct. - /// - /// The array to read data from. - public BitReader(ReadOnlySpan array) - { - this.array = array; - this.offset = 0; - this.bitOffset = 0; - } + /// + /// Reads the specified number of bits from the array. + /// + /// The number of bits to read. + /// The value read from the array. + public int ReadBits(uint bits) + { + int value = 0; - /// - /// Reads the specified number of bits from the array. - /// - /// The number of bits to read. - /// The value read from the array. - public int ReadBits(uint bits) + for (uint i = 0; i < bits; i++) { - int value = 0; - - for (uint i = 0; i < bits; i++) - { - int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01; - value = (value << 1) | bit; - - this.bitOffset++; - - if (this.bitOffset == 8) - { - this.bitOffset = 0; - this.offset++; - } - } + int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01; + value = (value << 1) | bit; - return value; - } + this.bitOffset++; - /// - /// Moves the reader to the next row of byte-aligned data. - /// - public void NextRow() - { - if (this.bitOffset > 0) + if (this.bitOffset == 8) { this.bitOffset = 0; this.offset++; } } + + return value; + } + + /// + /// Moves the reader to the next row of byte-aligned data. + /// + public void NextRow() + { + if (this.bitOffset > 0) + { + this.bitOffset = 0; + this.offset++; + } } } diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs index 2771bccf02..7e0251af6d 100644 --- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs +++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs @@ -1,178 +1,176 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Utils +namespace SixLabors.ImageSharp.Formats.Tiff.Utils; + +/// +/// Helper methods for TIFF decoding. +/// +internal static class TiffUtils { - /// - /// Helper methods for TIFF decoding. - /// - internal static class TiffUtils - { - private const float Scale24Bit = 1.0f / 0xFFFFFF; + private const float Scale24Bit = 1.0f / 0xFFFFFF; - private const float Scale32Bit = 1.0f / 0xFFFFFFFF; + private const float Scale32Bit = 1.0f / 0xFFFFFFFF; - public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0); + public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0); - public static L16 L16Default { get; } = new(0); + public static L16 L16Default { get; } = new(0); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ConvertToUShortBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16BigEndian(buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ConvertToUShortLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ConvertToUShortLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt16LittleEndian(buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ConvertToUIntBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertToUIntBigEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32BigEndian(buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ConvertToUIntLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ConvertToUIntLittleEndian(ReadOnlySpan buffer) => BinaryPrimitives.ReadUInt32LittleEndian(buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromL8(L8 l8, byte intensity, TPixel color) - where TPixel : unmanaged, IPixel - { - l8.PackedValue = intensity; - color.FromL8(l8); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromL8(L8 l8, byte intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + l8.PackedValue = intensity; + color.FromL8(l8); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgb64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) - where TPixel : unmanaged, IPixel - { - rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); - color.FromRgba64(rgba); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgb64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); + color.FromRgba64(rgba); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); - color.FromRgba64(rgba); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); + color.FromRgba64(rgba); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromRgba64Premultiplied(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); - var vec = rgba.ToVector4(); - return UnPremultiply(ref vec, color); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromRgba64Premultiplied(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48); + var vec = rgba.ToVector4(); + return UnPremultiply(ref vec, color); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) - where TPixel : unmanaged, IPixel - { - var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; - return UnPremultiply(ref colorVector, color); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit; + return UnPremultiply(ref colorVector, color); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) - where TPixel : unmanaged, IPixel - { - var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) - where TPixel : unmanaged, IPixel - { - Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; - return UnPremultiply(ref colorVector, color); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32BitPremultiplied(ulong r, ulong g, ulong b, ulong a, TPixel color) + where TPixel : unmanaged, IPixel + { + Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit; + return UnPremultiply(ref colorVector, color); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) - where TPixel : unmanaged, IPixel - { - l16.PackedValue = intensity; - color.FromL16(l16); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + l16.PackedValue = intensity; + color.FromL16(l16); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo24Bit(ulong intensity, TPixel color) - where TPixel : unmanaged, IPixel - { - var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo24Bit(ulong intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ColorScaleTo32Bit(ulong intensity, TPixel color) - where TPixel : unmanaged, IPixel - { - var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); - color.FromScaledVector4(colorVector); - return color; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorScaleTo32Bit(ulong intensity, TPixel color) + where TPixel : unmanaged, IPixel + { + var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); + color.FromScaledVector4(colorVector); + return color; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel UnPremultiply(ref Vector4 vector, TPixel color) - where TPixel : unmanaged, IPixel - { - Numerics.UnPremultiply(ref vector); - color.FromScaledVector4(vector); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel UnPremultiply(ref Vector4 vector, TPixel color) + where TPixel : unmanaged, IPixel + { + Numerics.UnPremultiply(ref vector); + color.FromScaledVector4(vector); - return color; - } + return color; + } - /// - /// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value. - /// - /// The width or height to round up. - /// The sub sampling. - /// The padding. - public static int PaddingToNextInteger(int valueToRoundUp, int subSampling) + /// + /// Finds the padding needed to round 'valueToRoundUp' to the next integer multiple of subSampling value. + /// + /// The width or height to round up. + /// The sub sampling. + /// The padding. + public static int PaddingToNextInteger(int valueToRoundUp, int subSampling) + { + if (valueToRoundUp % subSampling == 0) { - if (valueToRoundUp % subSampling == 0) - { - return 0; - } - - return subSampling - (valueToRoundUp % subSampling); + return 0; } + + return subSampling - (valueToRoundUp % subSampling); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs index 304437e65b..189c8fd6ac 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs @@ -1,110 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal abstract class TiffBaseColorWriter : IDisposable + where TPixel : unmanaged, IPixel { - internal abstract class TiffBaseColorWriter : IDisposable - where TPixel : unmanaged, IPixel + private bool isDisposed; + + protected TiffBaseColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) { - private bool isDisposed; + this.Image = image; + this.MemoryAllocator = memoryAllocator; + this.Configuration = configuration; + this.EntriesCollector = entriesCollector; + } - protected TiffBaseColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - { - this.Image = image; - this.MemoryAllocator = memoryAllocator; - this.Configuration = configuration; - this.EntriesCollector = entriesCollector; - } + /// + /// Gets the bits per pixel. + /// + public abstract int BitsPerPixel { get; } - /// - /// Gets the bits per pixel. - /// - public abstract int BitsPerPixel { get; } + /// + /// Gets the bytes per row. + /// + public int BytesPerRow => ((this.Image.Width * this.BitsPerPixel) + 7) / 8; - /// - /// Gets the bytes per row. - /// - public int BytesPerRow => ((this.Image.Width * this.BitsPerPixel) + 7) / 8; + protected ImageFrame Image { get; } - protected ImageFrame Image { get; } + protected MemoryAllocator MemoryAllocator { get; } - protected MemoryAllocator MemoryAllocator { get; } + protected Configuration Configuration { get; } - protected Configuration Configuration { get; } + protected TiffEncoderEntriesCollector EntriesCollector { get; } - protected TiffEncoderEntriesCollector EntriesCollector { get; } + public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) + { + DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); + int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; - public virtual void Write(TiffBaseCompressor compressor, int rowsPerStrip) - { - DebugGuard.IsTrue(this.BytesPerRow == compressor.BytesPerRow, "bytes per row of the compressor does not match tiff color writer"); - int stripsCount = (this.Image.Height + rowsPerStrip - 1) / rowsPerStrip; + uint[] stripOffsets = new uint[stripsCount]; + uint[] stripByteCounts = new uint[stripsCount]; - uint[] stripOffsets = new uint[stripsCount]; - uint[] stripByteCounts = new uint[stripsCount]; + int stripIndex = 0; + compressor.Initialize(rowsPerStrip); + for (int y = 0; y < this.Image.Height; y += rowsPerStrip) + { + long offset = compressor.Output.Position; - int stripIndex = 0; - compressor.Initialize(rowsPerStrip); - for (int y = 0; y < this.Image.Height; y += rowsPerStrip) - { - long offset = compressor.Output.Position; + int height = Math.Min(rowsPerStrip, this.Image.Height - y); + this.EncodeStrip(y, height, compressor); - int height = Math.Min(rowsPerStrip, this.Image.Height - y); - this.EncodeStrip(y, height, compressor); + long endOffset = compressor.Output.Position; + stripOffsets[stripIndex] = (uint)offset; + stripByteCounts[stripIndex] = (uint)(endOffset - offset); + stripIndex++; + } - long endOffset = compressor.Output.Position; - stripOffsets[stripIndex] = (uint)offset; - stripByteCounts[stripIndex] = (uint)(endOffset - offset); - stripIndex++; - } + DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match"); + this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); + } - DebugGuard.IsTrue(stripIndex == stripsCount, "stripIndex and stripsCount should match"); - this.AddStripTags(rowsPerStrip, stripOffsets, stripByteCounts); + /// + public void Dispose() + { + if (this.isDisposed) + { + return; } - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } + this.isDisposed = true; + this.Dispose(true); + } - this.isDisposed = true; - this.Dispose(true); - } + protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); - protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); + /// + /// Adds image format information to the specified IFD. + /// + /// The rows per strip. + /// The strip offsets. + /// The strip byte counts. + private void AddStripTags(int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) + { + this.EntriesCollector.AddOrReplace(new ExifLong(ExifTagValue.RowsPerStrip) + { + Value = (uint)rowsPerStrip + }); - /// - /// Adds image format information to the specified IFD. - /// - /// The rows per strip. - /// The strip offsets. - /// The strip byte counts. - private void AddStripTags(int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts) + this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripOffsets) { - this.EntriesCollector.AddOrReplace(new ExifLong(ExifTagValue.RowsPerStrip) - { - Value = (uint)rowsPerStrip - }); - - this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripOffsets) - { - Value = stripOffsets - }); - - this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripByteCounts) - { - Value = stripByteCounts - }); - } + Value = stripOffsets + }); - protected abstract void Dispose(bool disposing); + this.EntriesCollector.AddOrReplace(new ExifLongArray(ExifTagValue.StripByteCounts) + { + Value = stripByteCounts + }); } + + protected abstract void Dispose(bool disposing); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index 926dbf1c0f..28e3123dba 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Constants; @@ -10,104 +9,103 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal sealed class TiffBiColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel { - internal sealed class TiffBiColorWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel - { - private readonly Image imageBlackWhite; + private readonly Image imageBlackWhite; - private IMemoryOwner pixelsAsGray; + private IMemoryOwner pixelsAsGray; - private IMemoryOwner bitStrip; + private IMemoryOwner bitStrip; - public TiffBiColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) - { - // Convert image to black and white. - this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); - this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); - } + public TiffBiColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) + { + // Convert image to black and white. + this.imageBlackWhite = new Image(configuration, new ImageMetadata(), new[] { image.Clone() }); + this.imageBlackWhite.Mutate(img => img.BinaryDither(KnownDitherings.FloydSteinberg)); + } - /// - public override int BitsPerPixel => 1; + /// + public override int BitsPerPixel => 1; - /// - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) - { - int width = this.Image.Width; + /// + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + int width = this.Image.Width; - if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D || compressor.Method == TiffCompression.CcittGroup4Fax) + if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D || compressor.Method == TiffCompression.CcittGroup4Fax) + { + // Special case for T4BitCompressor. + int stripPixels = width * height; + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); + this.imageBlackWhite.ProcessPixelRows(accessor => { - // Special case for T4BitCompressor. - int stripPixels = width * height; - this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); - this.imageBlackWhite.ProcessPixelRows(accessor => + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + int lastRow = y + height; + int grayRowIdx = 0; + for (int row = y; row < lastRow; row++) { - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - int lastRow = y + height; - int grayRowIdx = 0; - for (int row = y; row < lastRow; row++) - { - Span pixelsBlackWhiteRow = accessor.GetRowSpan(row); - Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); - grayRowIdx++; - } + Span pixelsBlackWhiteRow = accessor.GetRowSpan(row); + Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); + grayRowIdx++; + } - compressor.CompressStrip(pixelAsGraySpan[..stripPixels], height); - }); - } - else - { - // Write uncompressed image. - int bytesPerStrip = this.BytesPerRow * height; - this.bitStrip ??= this.MemoryAllocator.Allocate(bytesPerStrip); - this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + compressor.CompressStrip(pixelAsGraySpan[..stripPixels], height); + }); + } + else + { + // Write uncompressed image. + int bytesPerStrip = this.BytesPerRow * height; + this.bitStrip ??= this.MemoryAllocator.Allocate(bytesPerStrip); + this.pixelsAsGray ??= this.MemoryAllocator.Allocate(width); + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - Span rows = this.bitStrip.Slice(0, bytesPerStrip); - rows.Clear(); - Buffer2D blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer; + Span rows = this.bitStrip.Slice(0, bytesPerStrip); + rows.Clear(); + Buffer2D blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer; - int outputRowIdx = 0; - int lastRow = y + height; - for (int row = y; row < lastRow; row++) + int outputRowIdx = 0; + int lastRow = y + height; + for (int row = y; row < lastRow; row++) + { + int bitIndex = 0; + int byteIndex = 0; + Span outputRow = rows[(outputRowIdx * this.BytesPerRow)..]; + Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); + for (int x = 0; x < this.Image.Width; x++) { - int bitIndex = 0; - int byteIndex = 0; - Span outputRow = rows[(outputRowIdx * this.BytesPerRow)..]; - Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); - for (int x = 0; x < this.Image.Width; x++) + int shift = 7 - bitIndex; + if (pixelAsGraySpan[x] == 255) { - int shift = 7 - bitIndex; - if (pixelAsGraySpan[x] == 255) - { - outputRow[byteIndex] |= (byte)(1 << shift); - } - - bitIndex++; - if (bitIndex == 8) - { - byteIndex++; - bitIndex = 0; - } + outputRow[byteIndex] |= (byte)(1 << shift); } - outputRowIdx++; + bitIndex++; + if (bitIndex == 8) + { + byteIndex++; + bitIndex = 0; + } } - compressor.CompressStrip(rows, height); + outputRowIdx++; } - } - /// - protected override void Dispose(bool disposing) - { - this.imageBlackWhite?.Dispose(); - this.pixelsAsGray?.Dispose(); - this.bitStrip?.Dispose(); + compressor.CompressStrip(rows, height); } } + + /// + protected override void Dispose(bool disposing) + { + this.imageBlackWhite?.Dispose(); + this.pixelsAsGray?.Dispose(); + this.bitStrip?.Dispose(); + } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs index c2dc9b7ee3..bb13137cef 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffColorWriterFactory.cs @@ -6,35 +6,34 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal static class TiffColorWriterFactory { - internal static class TiffColorWriterFactory + public static TiffBaseColorWriter Create( + TiffPhotometricInterpretation? photometricInterpretation, + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) + where TPixel : unmanaged, IPixel { - public static TiffBaseColorWriter Create( - TiffPhotometricInterpretation? photometricInterpretation, - ImageFrame image, - IQuantizer quantizer, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector, - int bitsPerPixel) - where TPixel : unmanaged, IPixel + switch (photometricInterpretation) { - switch (photometricInterpretation) - { - case TiffPhotometricInterpretation.PaletteColor: - return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); - case TiffPhotometricInterpretation.BlackIsZero: - case TiffPhotometricInterpretation.WhiteIsZero: - if (bitsPerPixel == 1) - { - return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); - } + case TiffPhotometricInterpretation.PaletteColor: + return new TiffPaletteWriter(image, quantizer, memoryAllocator, configuration, entriesCollector, bitsPerPixel); + case TiffPhotometricInterpretation.BlackIsZero: + case TiffPhotometricInterpretation.WhiteIsZero: + if (bitsPerPixel == 1) + { + return new TiffBiColorWriter(image, memoryAllocator, configuration, entriesCollector); + } - return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); - default: - return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); - } + return new TiffGrayWriter(image, memoryAllocator, configuration, entriesCollector); + default: + return new TiffRgbWriter(image, memoryAllocator, configuration, entriesCollector); } } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 5bb39771e7..3ead7960cb 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -1,57 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +/// +/// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). +/// +internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel { - /// - /// The base class for composite color types: 8-bit gray, 24-bit RGB (4-bit gray, 16-bit (565/555) RGB, 32-bit RGB, CMYK, YCbCr). - /// - internal abstract class TiffCompositeColorWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel + private IMemoryOwner rowBuffer; + + protected TiffCompositeColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { - private IMemoryOwner rowBuffer; + } - protected TiffCompositeColorWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + if (this.rowBuffer == null) { + this.rowBuffer = this.MemoryAllocator.Allocate(this.BytesPerRow * height); } - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + this.rowBuffer.Clear(); + + Span outputRowSpan = this.rowBuffer.GetSpan()[..(this.BytesPerRow * height)]; + + int width = this.Image.Width; + using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); + Span stripPixels = stripPixelBuffer.GetSpan(); + int lastRow = y + height; + int stripPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) { - if (this.rowBuffer == null) - { - this.rowBuffer = this.MemoryAllocator.Allocate(this.BytesPerRow * height); - } - - this.rowBuffer.Clear(); - - Span outputRowSpan = this.rowBuffer.GetSpan()[..(this.BytesPerRow * height)]; - - int width = this.Image.Width; - using IMemoryOwner stripPixelBuffer = this.MemoryAllocator.Allocate(height * width); - Span stripPixels = stripPixelBuffer.GetSpan(); - int lastRow = y + height; - int stripPixelsRowIdx = 0; - for (int row = y; row < lastRow; row++) - { - Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); - stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); - stripPixelsRowIdx++; - } - - this.EncodePixels(stripPixels, outputRowSpan); - compressor.CompressStrip(outputRowSpan, height); + Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); + stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); + stripPixelsRowIdx++; } - protected abstract void EncodePixels(Span pixels, Span buffer); - - /// - protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose(); + this.EncodePixels(stripPixels, outputRowSpan); + compressor.CompressStrip(outputRowSpan, height); } + + protected abstract void EncodePixels(Span pixels, Span buffer); + + /// + protected override void Dispose(bool disposing) => this.rowBuffer?.Dispose(); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs index 81120bd899..b2a476b9aa 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffGrayWriter{TPixel}.cs @@ -1,24 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal sealed class TiffGrayWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel { - internal sealed class TiffGrayWriter : TiffCompositeColorWriter - where TPixel : unmanaged, IPixel + public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { - public TiffGrayWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) - { - } + } - /// - public override int BitsPerPixel => 8; + /// + public override int BitsPerPixel => 8; - /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); - } + /// + protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToL8Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index 19d071c9ed..f8810d65ac 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Tiff.Compression; @@ -10,150 +9,149 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal sealed class TiffPaletteWriter : TiffBaseColorWriter + where TPixel : unmanaged, IPixel { - internal sealed class TiffPaletteWriter : TiffBaseColorWriter - where TPixel : unmanaged, IPixel + private readonly int maxColors; + private readonly int colorPaletteSize; + private readonly int colorPaletteBytes; + private readonly IndexedImageFrame quantizedImage; + private IMemoryOwner indexedPixelsBuffer; + + public TiffPaletteWriter( + ImageFrame image, + IQuantizer quantizer, + MemoryAllocator memoryAllocator, + Configuration configuration, + TiffEncoderEntriesCollector entriesCollector, + int bitsPerPixel) + : base(image, memoryAllocator, configuration, entriesCollector) { - private readonly int maxColors; - private readonly int colorPaletteSize; - private readonly int colorPaletteBytes; - private readonly IndexedImageFrame quantizedImage; - private IMemoryOwner indexedPixelsBuffer; - - public TiffPaletteWriter( - ImageFrame image, - IQuantizer quantizer, - MemoryAllocator memoryAllocator, - Configuration configuration, - TiffEncoderEntriesCollector entriesCollector, - int bitsPerPixel) - : base(image, memoryAllocator, configuration, entriesCollector) + DebugGuard.NotNull(quantizer, nameof(quantizer)); + DebugGuard.NotNull(configuration, nameof(configuration)); + DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); + DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); + + this.BitsPerPixel = bitsPerPixel; + this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; + this.colorPaletteSize = this.maxColors * 3; + this.colorPaletteBytes = this.colorPaletteSize * 2; + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions() { - DebugGuard.NotNull(quantizer, nameof(quantizer)); - DebugGuard.NotNull(configuration, nameof(configuration)); - DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); - DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); - - this.BitsPerPixel = bitsPerPixel; - this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; - this.colorPaletteSize = this.maxColors * 3; - this.colorPaletteBytes = this.colorPaletteSize * 2; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration, new QuantizerOptions() - { - MaxColors = this.maxColors - }); - this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + MaxColors = this.maxColors + }); + this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - this.AddColorMapTag(); - } + this.AddColorMapTag(); + } - /// - public override int BitsPerPixel { get; } + /// + public override int BitsPerPixel { get; } - /// - protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) - { - int width = this.Image.Width; + /// + protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) + { + int width = this.Image.Width; - if (this.BitsPerPixel == 4) + if (this.BitsPerPixel == 4) + { + int halfWidth = width >> 1; + int excess = (width & 1) * height; // (width % 2) * height + int rows4BitBufferLength = (halfWidth * height) + excess; + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(rows4BitBufferLength); + Span rows4bit = this.indexedPixelsBuffer.GetSpan(); + int idx4bitRows = 0; + int lastRow = y + height; + for (int row = y; row < lastRow; row++) { - int halfWidth = width >> 1; - int excess = (width & 1) * height; // (width % 2) * height - int rows4BitBufferLength = (halfWidth * height) + excess; - this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(rows4BitBufferLength); - Span rows4bit = this.indexedPixelsBuffer.GetSpan(); - int idx4bitRows = 0; - int lastRow = y + height; - for (int row = y; row < lastRow; row++) + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); + int idxPixels = 0; + for (int x = 0; x < halfWidth; x++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); - int idxPixels = 0; - for (int x = 0; x < halfWidth; x++) - { - rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF)); - idxPixels += 2; - idx4bitRows++; - } - - // Make sure rows are byte-aligned. - if (width % 2 != 0) - { - rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4); - } + rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF)); + idxPixels += 2; + idx4bitRows++; } - compressor.CompressStrip(rows4bit[..idx4bitRows], height); - } - else - { - int stripPixels = width * height; - this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(stripPixels); - Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); - int lastRow = y + height; - int indexedPixelsRowIdx = 0; - for (int row = y; row < lastRow; row++) + // Make sure rows are byte-aligned. + if (width % 2 != 0) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); - indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); - indexedPixelsRowIdx++; + rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4); } - - compressor.CompressStrip(indexedPixels[..stripPixels], height); } - } - /// - protected override void Dispose(bool disposing) - { - this.quantizedImage?.Dispose(); - this.indexedPixelsBuffer?.Dispose(); + compressor.CompressStrip(rows4bit[..idx4bitRows], height); } - - private void AddColorMapTag() + else { - using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes); - Span colorPalette = colorPaletteBuffer.GetSpan(); + int stripPixels = width * height; + this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate(stripPixels); + Span indexedPixels = this.indexedPixelsBuffer.GetSpan(); + int lastRow = y + height; + int indexedPixelsRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); + indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); + indexedPixelsRowIdx++; + } + + compressor.CompressStrip(indexedPixels[..stripPixels], height); + } + } - ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; - int quantizedColorBytes = quantizedColors.Length * 3 * 2; + /// + protected override void Dispose(bool disposing) + { + this.quantizedImage?.Dispose(); + this.indexedPixelsBuffer?.Dispose(); + } - // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. - Span quantizedColorRgb48 = MemoryMarshal.Cast(colorPalette[..quantizedColorBytes]); - PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); + private void AddColorMapTag() + { + using IMemoryOwner colorPaletteBuffer = this.MemoryAllocator.Allocate(this.colorPaletteBytes); + Span colorPalette = colorPaletteBuffer.GetSpan(); - // It can happen that the quantized colors are less than the expected maximum per channel. - int diffToMaxColors = this.maxColors - quantizedColors.Length; + ReadOnlySpan quantizedColors = this.quantizedImage.Palette.Span; + int quantizedColorBytes = quantizedColors.Length * 3 * 2; - // In a TIFF ColorMap, all the Red values come first, followed by the Green values, - // then the Blue values. Convert the quantized palette to this format. - var palette = new ushort[this.colorPaletteSize]; - int paletteIdx = 0; - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].R; - } + // In the ColorMap, black is represented by 0, 0, 0 and white is represented by 65535, 65535, 65535. + Span quantizedColorRgb48 = MemoryMarshal.Cast(colorPalette[..quantizedColorBytes]); + PixelOperations.Instance.ToRgb48(this.Configuration, quantizedColors, quantizedColorRgb48); - paletteIdx += diffToMaxColors; + // It can happen that the quantized colors are less than the expected maximum per channel. + int diffToMaxColors = this.maxColors - quantizedColors.Length; - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].G; - } + // In a TIFF ColorMap, all the Red values come first, followed by the Green values, + // then the Blue values. Convert the quantized palette to this format. + var palette = new ushort[this.colorPaletteSize]; + int paletteIdx = 0; + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].R; + } - paletteIdx += diffToMaxColors; + paletteIdx += diffToMaxColors; - for (int i = 0; i < quantizedColors.Length; i++) - { - palette[paletteIdx++] = quantizedColorRgb48[i].B; - } + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].G; + } - var colorMap = new ExifShortArray(ExifTagValue.ColorMap) - { - Value = palette - }; + paletteIdx += diffToMaxColors; - this.EntriesCollector.AddOrReplace(colorMap); + for (int i = 0; i < quantizedColors.Length; i++) + { + palette[paletteIdx++] = quantizedColorRgb48[i].B; } + + var colorMap = new ExifShortArray(ExifTagValue.ColorMap) + { + Value = palette + }; + + this.EntriesCollector.AddOrReplace(colorMap); } } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs index 84a33d626b..3494b6ceae 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffRgbWriter{TPixel}.cs @@ -1,24 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +internal sealed class TiffRgbWriter : TiffCompositeColorWriter + where TPixel : unmanaged, IPixel { - internal sealed class TiffRgbWriter : TiffCompositeColorWriter - where TPixel : unmanaged, IPixel + public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) + : base(image, memoryAllocator, configuration, entriesCollector) { - public TiffRgbWriter(ImageFrame image, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector) - : base(image, memoryAllocator, configuration, entriesCollector) - { - } + } - /// - public override int BitsPerPixel => 24; + /// + public override int BitsPerPixel => 24; - /// - protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); - } + /// + protected override void EncodePixels(Span pixels, Span buffer) => PixelOperations.Instance.ToRgb24Bytes(this.Configuration, pixels, buffer, pixels.Length); } diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs index d35d169488..be32ca9ed6 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffStreamWriter.cs @@ -1,146 +1,143 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; -namespace SixLabors.ImageSharp.Formats.Tiff.Writers +namespace SixLabors.ImageSharp.Formats.Tiff.Writers; + +/// +/// Utility class for writing TIFF data to a . +/// +internal sealed class TiffStreamWriter : IDisposable { + private static readonly byte[] PaddingBytes = new byte[4]; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[4]; + + /// + /// Initializes a new instance of the class. + /// + /// The output stream. + public TiffStreamWriter(Stream output) => this.BaseStream = output; + + /// + /// Gets a value indicating whether the architecture is little-endian. + /// + public static bool IsLittleEndian => BitConverter.IsLittleEndian; + + /// + /// Gets the current position within the stream. + /// + public long Position => this.BaseStream.Position; + /// - /// Utility class for writing TIFF data to a . + /// Gets the base stream. /// - internal sealed class TiffStreamWriter : IDisposable + public Stream BaseStream { get; } + + /// + /// Writes an empty four bytes to the stream, returning the offset to be written later. + /// + /// The offset to be written later. + public long PlaceMarker() { - private static readonly byte[] PaddingBytes = new byte[4]; - - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// Initializes a new instance of the class. - /// - /// The output stream. - public TiffStreamWriter(Stream output) => this.BaseStream = output; - - /// - /// Gets a value indicating whether the architecture is little-endian. - /// - public static bool IsLittleEndian => BitConverter.IsLittleEndian; - - /// - /// Gets the current position within the stream. - /// - public long Position => this.BaseStream.Position; - - /// - /// Gets the base stream. - /// - public Stream BaseStream { get; } - - /// - /// Writes an empty four bytes to the stream, returning the offset to be written later. - /// - /// The offset to be written later. - public long PlaceMarker() - { - long offset = this.BaseStream.Position; - this.Write(0u); - return offset; - } + long offset = this.BaseStream.Position; + this.Write(0u); + return offset; + } + + /// + /// Writes an array of bytes to the current stream. + /// + /// The bytes to write. + public void Write(byte[] value) => this.BaseStream.Write(value, 0, value.Length); + + /// + /// Writes the specified value. + /// + /// The bytes to write. + public void Write(ReadOnlySpan value) => this.BaseStream.Write(value); + + /// + /// Writes a byte to the current stream. + /// + /// The byte to write. + public void Write(byte value) => this.BaseStream.WriteByte(value); - /// - /// Writes an array of bytes to the current stream. - /// - /// The bytes to write. - public void Write(byte[] value) => this.BaseStream.Write(value, 0, value.Length); - - /// - /// Writes the specified value. - /// - /// The bytes to write. - public void Write(ReadOnlySpan value) => this.BaseStream.Write(value); - - /// - /// Writes a byte to the current stream. - /// - /// The byte to write. - public void Write(byte value) => this.BaseStream.WriteByte(value); - - /// - /// Writes a two-byte unsigned integer to the current stream. - /// - /// The two-byte unsigned integer to write. - public void Write(ushort value) + /// + /// Writes a two-byte unsigned integer to the current stream. + /// + /// The two-byte unsigned integer to write. + public void Write(ushort value) + { + if (IsLittleEndian) { - if (IsLittleEndian) - { - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value); - } - else - { - BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value); - } - - this.BaseStream.Write(this.buffer.AsSpan(0, 2)); + BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value); } - - /// - /// Writes a four-byte unsigned integer to the current stream. - /// - /// The four-byte unsigned integer to write. - public void Write(uint value) + else { - if (IsLittleEndian) - { - BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); - } - else - { - BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value); - } - - this.BaseStream.Write(this.buffer.AsSpan(0, 4)); + BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value); } - /// - /// Writes an array of bytes to the current stream, padded to four-bytes. - /// - /// The bytes to write. - public void WritePadded(Span value) - { - this.BaseStream.Write(value); + this.BaseStream.Write(this.buffer.AsSpan(0, 2)); + } - if (value.Length % 4 != 0) - { - this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4)); - } + /// + /// Writes a four-byte unsigned integer to the current stream. + /// + /// The four-byte unsigned integer to write. + public void Write(uint value) + { + if (IsLittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); } - - /// - /// Writes a four-byte unsigned integer to the specified marker in the stream. - /// - /// The offset returned when placing the marker - /// The four-byte unsigned integer to write. - public void WriteMarker(long offset, uint value) + else { - long back = this.BaseStream.Position; - this.BaseStream.Seek(offset, SeekOrigin.Begin); - this.Write(value); - this.BaseStream.Seek(back, SeekOrigin.Begin); + BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value); } - public void WriteMarkerFast(long offset, uint value) + this.BaseStream.Write(this.buffer.AsSpan(0, 4)); + } + + /// + /// Writes an array of bytes to the current stream, padded to four-bytes. + /// + /// The bytes to write. + public void WritePadded(Span value) + { + this.BaseStream.Write(value); + + if (value.Length % 4 != 0) { - this.BaseStream.Seek(offset, SeekOrigin.Begin); - this.Write(value); + this.BaseStream.Write(PaddingBytes, 0, 4 - (value.Length % 4)); } + } + + /// + /// Writes a four-byte unsigned integer to the specified marker in the stream. + /// + /// The offset returned when placing the marker + /// The four-byte unsigned integer to write. + public void WriteMarker(long offset, uint value) + { + long back = this.BaseStream.Position; + this.BaseStream.Seek(offset, SeekOrigin.Begin); + this.Write(value); + this.BaseStream.Seek(back, SeekOrigin.Begin); + } - /// - /// Disposes instance, ensuring any unwritten data is flushed. - /// - public void Dispose() => this.BaseStream.Flush(); + public void WriteMarkerFast(long offset, uint value) + { + this.BaseStream.Seek(offset, SeekOrigin.Begin); + this.Write(value); } + + /// + /// Disposes instance, ensuring any unwritten data is flushed. + /// + public void Dispose() => this.BaseStream.Flush(); } diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index a2628a1db4..4eb2aef4cf 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -12,478 +10,477 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Implements decoding for lossy alpha chunks which may be compressed. +/// +internal class AlphaDecoder : IDisposable { + private readonly MemoryAllocator memoryAllocator; + /// - /// Implements decoding for lossy alpha chunks which may be compressed. + /// Initializes a new instance of the class. /// - internal class AlphaDecoder : IDisposable + /// The width of the image. + /// The height of the image. + /// The (maybe compressed) alpha data. + /// The first byte of the alpha image stream contains information on how to decode the stream. + /// Used for allocating memory during decoding. + /// The configuration. + public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) { - private readonly MemoryAllocator memoryAllocator; - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image. - /// The height of the image. - /// The (maybe compressed) alpha data. - /// The first byte of the alpha image stream contains information on how to decode the stream. - /// Used for allocating memory during decoding. - /// The configuration. - public AlphaDecoder(int width, int height, IMemoryOwner data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration) + this.Width = width; + this.Height = height; + this.Data = data; + this.memoryAllocator = memoryAllocator; + this.LastRow = 0; + int totalPixels = width * height; + + var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); + if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) { - this.Width = width; - this.Height = height; - this.Data = data; - this.memoryAllocator = memoryAllocator; - this.LastRow = 0; - int totalPixels = width * height; - - var compression = (WebpAlphaCompressionMethod)(alphaChunkHeader & 0x03); - if (compression is not WebpAlphaCompressionMethod.NoCompression and not WebpAlphaCompressionMethod.WebpLosslessCompression) - { - WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); - } + WebpThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {compression} found"); + } - this.Compressed = compression == WebpAlphaCompressionMethod.WebpLosslessCompression; + this.Compressed = compression == WebpAlphaCompressionMethod.WebpLosslessCompression; - // The filtering method used. Only values between 0 and 3 are valid. - int filter = (alphaChunkHeader >> 2) & 0x03; - if (filter is < (int)WebpAlphaFilterType.None or > (int)WebpAlphaFilterType.Gradient) - { - WebpThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); - } + // The filtering method used. Only values between 0 and 3 are valid. + int filter = (alphaChunkHeader >> 2) & 0x03; + if (filter is < (int)WebpAlphaFilterType.None or > (int)WebpAlphaFilterType.Gradient) + { + WebpThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found"); + } - this.Alpha = memoryAllocator.Allocate(totalPixels); - this.AlphaFilterType = (WebpAlphaFilterType)filter; - this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); + this.Alpha = memoryAllocator.Allocate(totalPixels); + this.AlphaFilterType = (WebpAlphaFilterType)filter; + this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator); - if (this.Compressed) - { - var bitReader = new Vp8LBitReader(data); - this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); - this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); - this.Use8BDecode = this.Vp8LDec.Transforms.Count > 0 && Is8BOptimizable(this.Vp8LDec.Metadata); - } + if (this.Compressed) + { + var bitReader = new Vp8LBitReader(data); + this.LosslessDecoder = new WebpLosslessDecoder(bitReader, memoryAllocator, configuration); + this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true); + this.Use8BDecode = this.Vp8LDec.Transforms.Count > 0 && Is8BOptimizable(this.Vp8LDec.Metadata); } + } - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets the used filter type. - /// - public WebpAlphaFilterType AlphaFilterType { get; } - - /// - /// Gets or sets the last decoded row. - /// - public int LastRow { get; set; } - - /// - /// Gets or sets the row before the last decoded row. - /// - public int PrevRow { get; set; } - - /// - /// Gets information for decoding Vp8L compressed alpha data. - /// - public Vp8LDecoder Vp8LDec { get; } - - /// - /// Gets the decoded alpha data. - /// - public IMemoryOwner Alpha { get; } - - /// - /// Gets a value indicating whether the alpha channel uses compression. - /// - private bool Compressed { get; } - - /// - /// Gets the (maybe compressed) alpha data. - /// - private IMemoryOwner Data { get; } - - /// - /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. - /// - private WebpLosslessDecoder LosslessDecoder { get; } - - /// - /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. - /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate - /// 4 bytes per pixel internally during decode. - /// - public bool Use8BDecode { get; } - - /// - /// Decodes and filters the maybe compressed alpha data. - /// - public void Decode() - { - if (!this.Compressed) - { - Span dataSpan = this.Data.Memory.Span; - int pixelCount = this.Width * this.Height; - if (dataSpan.Length < pixelCount) - { - WebpThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); - } + /// + /// Gets the width of the image. + /// + public int Width { get; } - Span alphaSpan = this.Alpha.Memory.Span; - if (this.AlphaFilterType == WebpAlphaFilterType.None) - { - dataSpan[..pixelCount].CopyTo(alphaSpan); - return; - } + /// + /// Gets the height of the image. + /// + public int Height { get; } - Span deltas = dataSpan; - Span dst = alphaSpan; - Span prev = default; - for (int y = 0; y < this.Height; y++) - { - switch (this.AlphaFilterType) - { - case WebpAlphaFilterType.Horizontal: - HorizontalUnfilter(prev, deltas, dst, this.Width); - break; - case WebpAlphaFilterType.Vertical: - VerticalUnfilter(prev, deltas, dst, this.Width); - break; - case WebpAlphaFilterType.Gradient: - GradientUnfilter(prev, deltas, dst, this.Width); - break; - } + /// + /// Gets the used filter type. + /// + public WebpAlphaFilterType AlphaFilterType { get; } - prev = dst; - deltas = deltas[this.Width..]; - dst = dst[this.Width..]; - } - } - else + /// + /// Gets or sets the last decoded row. + /// + public int LastRow { get; set; } + + /// + /// Gets or sets the row before the last decoded row. + /// + public int PrevRow { get; set; } + + /// + /// Gets information for decoding Vp8L compressed alpha data. + /// + public Vp8LDecoder Vp8LDec { get; } + + /// + /// Gets the decoded alpha data. + /// + public IMemoryOwner Alpha { get; } + + /// + /// Gets a value indicating whether the alpha channel uses compression. + /// + private bool Compressed { get; } + + /// + /// Gets the (maybe compressed) alpha data. + /// + private IMemoryOwner Data { get; } + + /// + /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed. + /// + private WebpLosslessDecoder LosslessDecoder { get; } + + /// + /// Gets a value indicating whether the decoding needs 1 byte per pixel for decoding. + /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate + /// 4 bytes per pixel internally during decode. + /// + public bool Use8BDecode { get; } + + /// + /// Decodes and filters the maybe compressed alpha data. + /// + public void Decode() + { + if (!this.Compressed) + { + Span dataSpan = this.Data.Memory.Span; + int pixelCount = this.Width * this.Height; + if (dataSpan.Length < pixelCount) { - if (this.Use8BDecode) - { - this.LosslessDecoder.DecodeAlphaData(this); - } - else - { - this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); - this.ExtractAlphaRows(this.Vp8LDec); - } + WebpThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk"); } - } - /// - /// Applies filtering to a set of rows. - /// - /// The first row index to start filtering. - /// The last row index for filtering. - /// The destination to store the filtered data. - /// The stride to use. - public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) - { + Span alphaSpan = this.Alpha.Memory.Span; if (this.AlphaFilterType == WebpAlphaFilterType.None) { + dataSpan[..pixelCount].CopyTo(alphaSpan); return; } - Span alphaSpan = this.Alpha.Memory.Span; - Span prev = this.PrevRow == 0 ? null : alphaSpan[(this.Width * this.PrevRow)..]; - for (int y = firstRow; y < lastRow; y++) + Span deltas = dataSpan; + Span dst = alphaSpan; + Span prev = default; + for (int y = 0; y < this.Height; y++) { switch (this.AlphaFilterType) { case WebpAlphaFilterType.Horizontal: - HorizontalUnfilter(prev, dst, dst, this.Width); + HorizontalUnfilter(prev, deltas, dst, this.Width); break; case WebpAlphaFilterType.Vertical: - VerticalUnfilter(prev, dst, dst, this.Width); + VerticalUnfilter(prev, deltas, dst, this.Width); break; case WebpAlphaFilterType.Gradient: - GradientUnfilter(prev, dst, dst, this.Width); + GradientUnfilter(prev, deltas, dst, this.Width); break; } prev = dst; - dst = dst[stride..]; + deltas = deltas[this.Width..]; + dst = dst[this.Width..]; + } + } + else + { + if (this.Use8BDecode) + { + this.LosslessDecoder.DecodeAlphaData(this); } + else + { + this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); + this.ExtractAlphaRows(this.Vp8LDec); + } + } + } - this.PrevRow = lastRow - 1; + /// + /// Applies filtering to a set of rows. + /// + /// The first row index to start filtering. + /// The last row index for filtering. + /// The destination to store the filtered data. + /// The stride to use. + public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride) + { + if (this.AlphaFilterType == WebpAlphaFilterType.None) + { + return; } - public void ExtractPalettedAlphaRows(int lastRow) + Span alphaSpan = this.Alpha.Memory.Span; + Span prev = this.PrevRow == 0 ? null : alphaSpan[(this.Width * this.PrevRow)..]; + for (int y = firstRow; y < lastRow; y++) { - // For vertical and gradient filtering, we need to decode the part above the - // cropTop row, in order to have the correct spatial predictors. - int topRow = this.AlphaFilterType is WebpAlphaFilterType.None or WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; - int firstRow = this.LastRow < topRow ? topRow : this.LastRow; - if (lastRow > firstRow) + switch (this.AlphaFilterType) { - // Special method for paletted alpha data. - Span output = this.Alpha.Memory.Span; - Span pixelData = this.Vp8LDec.Pixels.Memory.Span; - Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - Span dst = output[(this.Width * firstRow)..]; - Span input = pixelDataAsBytes[(this.Vp8LDec.Width * firstRow)..]; - - if (this.Vp8LDec.Transforms.Count == 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) - { - WebpThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); - } - - Vp8LTransform transform = this.Vp8LDec.Transforms[0]; - ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); - this.AlphaApplyFilter(firstRow, lastRow, dst, this.Width); + case WebpAlphaFilterType.Horizontal: + HorizontalUnfilter(prev, dst, dst, this.Width); + break; + case WebpAlphaFilterType.Vertical: + VerticalUnfilter(prev, dst, dst, this.Width); + break; + case WebpAlphaFilterType.Gradient: + GradientUnfilter(prev, dst, dst, this.Width); + break; } - this.LastRow = lastRow; + prev = dst; + dst = dst[stride..]; } - /// - /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. - /// - /// The VP8L decoder. - private void ExtractAlphaRows(Vp8LDecoder dec) + this.PrevRow = lastRow - 1; + } + + public void ExtractPalettedAlphaRows(int lastRow) + { + // For vertical and gradient filtering, we need to decode the part above the + // cropTop row, in order to have the correct spatial predictors. + int topRow = this.AlphaFilterType is WebpAlphaFilterType.None or WebpAlphaFilterType.Horizontal ? 0 : this.LastRow; + int firstRow = this.LastRow < topRow ? topRow : this.LastRow; + if (lastRow > firstRow) { - int numRowsToProcess = dec.Height; - int width = dec.Width; - Span pixels = dec.Pixels.Memory.Span; - Span input = pixels; + // Special method for paletted alpha data. Span output = this.Alpha.Memory.Span; + Span pixelData = this.Vp8LDec.Pixels.Memory.Span; + Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); + Span dst = output[(this.Width * firstRow)..]; + Span input = pixelDataAsBytes[(this.Vp8LDec.Width * firstRow)..]; + + if (this.Vp8LDec.Transforms.Count == 0 || this.Vp8LDec.Transforms[0].TransformType != Vp8LTransformType.ColorIndexingTransform) + { + WebpThrowHelper.ThrowImageFormatException("error while decoding alpha channel, expected color index transform data is missing"); + } - // Extract alpha (which is stored in the green plane). - int pixelCount = width * numRowsToProcess; - WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); - ExtractGreen(input, output, pixelCount); - this.AlphaApplyFilter(0, numRowsToProcess, output, width); + Vp8LTransform transform = this.Vp8LDec.Transforms[0]; + ColorIndexInverseTransformAlpha(transform, firstRow, lastRow, input, dst); + this.AlphaApplyFilter(firstRow, lastRow, dst, this.Width); } - private static void ColorIndexInverseTransformAlpha( - Vp8LTransform transform, - int yStart, - int yEnd, - Span src, - Span dst) + this.LastRow = lastRow; + } + + /// + /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. + /// + /// The VP8L decoder. + private void ExtractAlphaRows(Vp8LDecoder dec) + { + int numRowsToProcess = dec.Height; + int width = dec.Width; + Span pixels = dec.Pixels.Memory.Span; + Span input = pixels; + Span output = this.Alpha.Memory.Span; + + // Extract alpha (which is stored in the green plane). + int pixelCount = width * numRowsToProcess; + WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); + ExtractGreen(input, output, pixelCount); + this.AlphaApplyFilter(0, numRowsToProcess, output, width); + } + + private static void ColorIndexInverseTransformAlpha( + Vp8LTransform transform, + int yStart, + int yEnd, + Span src, + Span dst) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + Span colorMap = transform.Data.Memory.Span; + if (bitsPerPixel < 8) { - int bitsPerPixel = 8 >> transform.Bits; - int width = transform.XSize; - Span colorMap = transform.Data.Memory.Span; - if (bitsPerPixel < 8) + int srcOffset = 0; + int dstOffset = 0; + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; + for (int y = yStart; y < yEnd; y++) { - int srcOffset = 0; - int dstOffset = 0; - int pixelsPerByte = 1 << transform.Bits; - int countMask = pixelsPerByte - 1; - int bitMask = (1 << bitsPerPixel) - 1; - for (int y = yStart; y < yEnd; y++) + int packedPixels = 0; + for (int x = 0; x < width; x++) { - int packedPixels = 0; - for (int x = 0; x < width; x++) + if ((x & countMask) == 0) { - if ((x & countMask) == 0) - { - packedPixels = src[srcOffset]; - srcOffset++; - } - - dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); - dstOffset++; - packedPixels >>= bitsPerPixel; + packedPixels = src[srcOffset]; + srcOffset++; } + + dst[dstOffset] = GetAlphaValue((int)colorMap[packedPixels & bitMask]); + dstOffset++; + packedPixels >>= bitsPerPixel; } } - else + } + else + { + MapAlpha(src, colorMap, dst, yStart, yEnd, width); + } + } + + private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) + { + if (Sse2.IsSupported) + { + dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); + if (width <= 1) { - MapAlpha(src, colorMap, dst, yStart, yEnd, width); + return; + } + + nint i; + Vector128 last = Vector128.Zero.WithElement(0, dst[0]); + ref byte srcRef = ref MemoryMarshal.GetReference(input); + for (i = 1; i + 8 <= width; i += 8) + { + var a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); + Vector128 a1 = Sse2.Add(a0.AsByte(), last.AsByte()); + Vector128 a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1); + Vector128 a3 = Sse2.Add(a1, a2); + Vector128 a4 = Sse2.ShiftLeftLogical128BitLane(a3, 2); + Vector128 a5 = Sse2.Add(a3, a4); + Vector128 a6 = Sse2.ShiftLeftLogical128BitLane(a5, 4); + Vector128 a7 = Sse2.Add(a5, a6); + + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); + Unsafe.As>(ref outputRef) = a7.GetLower(); + last = Sse2.ShiftRightLogical(a7.AsInt64(), 56).AsInt32(); } - } - private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) + for (; i < width; ++i) + { + dst[(int)i] = (byte)(input[(int)i] + dst[(int)i - 1]); + } + } + else { - if (Sse2.IsSupported) + byte pred = (byte)(prev.IsEmpty ? 0 : prev[0]); + + for (int i = 0; i < width; i++) { - dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); - if (width <= 1) - { - return; - } + byte val = (byte)(pred + input[i]); + pred = val; + dst[i] = val; + } + } + } + private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev.IsEmpty) + { + HorizontalUnfilter(null, input, dst, width); + } + else + { + if (Avx2.IsSupported) + { nint i; - Vector128 last = Vector128.Zero.WithElement(0, dst[0]); - ref byte srcRef = ref MemoryMarshal.GetReference(input); - for (i = 1; i + 8 <= width; i += 8) + int maxPos = width & ~31; + for (i = 0; i < maxPos; i += 32) { - var a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); - Vector128 a1 = Sse2.Add(a0.AsByte(), last.AsByte()); - Vector128 a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1); - Vector128 a3 = Sse2.Add(a1, a2); - Vector128 a4 = Sse2.ShiftLeftLogical128BitLane(a3, 2); - Vector128 a5 = Sse2.Add(a3, a4); - Vector128 a6 = Sse2.ShiftLeftLogical128BitLane(a5, 4); - Vector128 a7 = Sse2.Add(a5, a6); - + Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); + Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); + Vector256 c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); - Unsafe.As>(ref outputRef) = a7.GetLower(); - last = Sse2.ShiftRightLogical(a7.AsInt64(), 56).AsInt32(); + Unsafe.As>(ref outputRef) = c0; } - for (; i < width; ++i) + for (; i < width; i++) { - dst[(int)i] = (byte)(input[(int)i] + dst[(int)i - 1]); + dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); } } else { - byte pred = (byte)(prev.IsEmpty ? 0 : prev[0]); - for (int i = 0; i < width; i++) { - byte val = (byte)(pred + input[i]); - pred = val; - dst[i] = val; + dst[i] = (byte)(prev[i] + input[i]); } } } + } - private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) + private static void GradientUnfilter(Span prev, Span input, Span dst, int width) + { + if (prev.IsEmpty) { - if (prev.IsEmpty) + HorizontalUnfilter(null, input, dst, width); + } + else + { + byte prev0 = prev[0]; + byte topLeft = prev0; + byte left = prev0; + for (int i = 0; i < width; i++) { - HorizontalUnfilter(null, input, dst, width); - } - else - { - if (Avx2.IsSupported) - { - nint i; - int maxPos = width & ~31; - for (i = 0; i < maxPos; i += 32) - { - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); - Vector256 c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); - ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); - Unsafe.As>(ref outputRef) = c0; - } - - for (; i < width; i++) - { - dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); - } - } - else - { - for (int i = 0; i < width; i++) - { - dst[i] = (byte)(prev[i] + input[i]); - } - } + byte top = prev[i]; + left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + dst[i] = left; } } + } - private static void GradientUnfilter(Span prev, Span input, Span dst, int width) + /// + /// Row-processing for the special case when alpha data contains only one + /// transform (color indexing), and trivial non-green literals. + /// + /// The VP8L meta data. + /// True, if alpha channel needs one byte per pixel, otherwise 4. + private static bool Is8BOptimizable(Vp8LMetadata hdr) + { + if (hdr.ColorCacheSize > 0) { - if (prev.IsEmpty) - { - HorizontalUnfilter(null, input, dst, width); - } - else - { - byte prev0 = prev[0]; - byte topLeft = prev0; - byte left = prev0; - for (int i = 0; i < width; i++) - { - byte top = prev[i]; - left = (byte)(input[i] + GradientPredictor(left, top, topLeft)); - topLeft = top; - dst[i] = left; - } - } + return false; } - /// - /// Row-processing for the special case when alpha data contains only one - /// transform (color indexing), and trivial non-green literals. - /// - /// The VP8L meta data. - /// True, if alpha channel needs one byte per pixel, otherwise 4. - private static bool Is8BOptimizable(Vp8LMetadata hdr) + for (int i = 0; i < hdr.NumHTreeGroups; i++) { - if (hdr.ColorCacheSize > 0) + List htrees = hdr.HTreeGroups[i].HTrees; + if (htrees[HuffIndex.Red][0].BitsUsed > 0) { return false; } - for (int i = 0; i < hdr.NumHTreeGroups; i++) + if (htrees[HuffIndex.Blue][0].BitsUsed > 0) { - List htrees = hdr.HTreeGroups[i].HTrees; - if (htrees[HuffIndex.Red][0].BitsUsed > 0) - { - return false; - } - - if (htrees[HuffIndex.Blue][0].BitsUsed > 0) - { - return false; - } - - if (htrees[HuffIndex.Alpha][0].BitsUsed > 0) - { - return false; - } + return false; } - return true; - } - - private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) - { - int offset = 0; - for (int y = yStart; y < yEnd; y++) + if (htrees[HuffIndex.Alpha][0].BitsUsed > 0) { - for (int x = 0; x < width; x++) - { - dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); - offset++; - } + return false; } } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte GetAlphaValue(int val) => (byte)((val >> 8) & 0xff); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GradientPredictor(byte a, byte b, byte c) - { - int g = a + b - c; - return (g & ~0xff) == 0 ? g : g < 0 ? 0 : 255; // clip to 8bit. - } + return true; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void ExtractGreen(Span argb, Span alpha, int size) + private static void MapAlpha(Span src, Span colorMap, Span dst, int yStart, int yEnd, int width) + { + int offset = 0; + for (int y = yStart; y < yEnd; y++) { - for (int i = 0; i < size; i++) + for (int x = 0; x < width; x++) { - alpha[i] = (byte)(argb[i] >> 8); + dst[offset] = GetAlphaValue((int)colorMap[src[offset]]); + offset++; } } + } - /// - public void Dispose() + [MethodImpl(InliningOptions.ShortMethod)] + private static byte GetAlphaValue(int val) => (byte)((val >> 8) & 0xff); + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b - c; + return (g & ~0xff) == 0 ? g : g < 0 ? 0 : 255; // clip to 8bit. + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void ExtractGreen(Span argb, Span alpha, int size) + { + for (int i = 0; i < size; i++) { - this.Vp8LDec?.Dispose(); - this.Data.Dispose(); - this.Alpha?.Dispose(); + alpha[i] = (byte)(argb[i] >> 8); } } + + /// + public void Dispose() + { + this.Vp8LDec?.Dispose(); + this.Data.Dispose(); + this.Alpha?.Dispose(); + } } diff --git a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs index d467dacacc..1e60235e26 100644 --- a/src/ImageSharp/Formats/Webp/AlphaEncoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaEncoder.cs @@ -1,133 +1,131 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Methods for encoding the alpha data of a VP8 image. +/// +internal class AlphaEncoder : IDisposable { + private IMemoryOwner alphaData; + /// - /// Methods for encoding the alpha data of a VP8 image. + /// Encodes the alpha channel data. + /// Data is either compressed as lossless webp image or uncompressed. /// - internal class AlphaEncoder : IDisposable + /// The pixel format. + /// The to encode from. + /// The global configuration. + /// The memory manager. + /// Indicates, if the data should be compressed with the lossless webp compression. + /// The size in bytes of the alpha data. + /// The encoded alpha data. + public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size) + where TPixel : unmanaged, IPixel { - private IMemoryOwner alphaData; - - /// - /// Encodes the alpha channel data. - /// Data is either compressed as lossless webp image or uncompressed. - /// - /// The pixel format. - /// The to encode from. - /// The global configuration. - /// The memory manager. - /// Indicates, if the data should be compressed with the lossless webp compression. - /// The size in bytes of the alpha data. - /// The encoded alpha data. - public IMemoryOwner EncodeAlpha(Image image, Configuration configuration, MemoryAllocator memoryAllocator, bool compress, out int size) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); + int width = image.Width; + int height = image.Height; + this.alphaData = ExtractAlphaChannel(image, configuration, memoryAllocator); - if (compress) - { - WebpEncodingMethod effort = WebpEncodingMethod.Default; - int quality = 8 * (int)effort; - using var lossLessEncoder = new Vp8LEncoder( - memoryAllocator, - configuration, - width, - height, - quality, - effort, - WebpTransparentColorMode.Preserve, - false, - 0); - - // The transparency information will be stored in the green channel of the ARGB quadruplet. - // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, - // that can improve compression. - using Image alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan()); - - size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData); - - return this.alphaData; - } + if (compress) + { + WebpEncodingMethod effort = WebpEncodingMethod.Default; + int quality = 8 * (int)effort; + using var lossLessEncoder = new Vp8LEncoder( + memoryAllocator, + configuration, + width, + height, + quality, + effort, + WebpTransparentColorMode.Preserve, + false, + 0); + + // The transparency information will be stored in the green channel of the ARGB quadruplet. + // The green channel is allowed extra transformation steps in the specification -- unlike the other channels, + // that can improve compression. + using Image alphaAsImage = DispatchAlphaToGreen(image, this.alphaData.GetSpan()); + + size = lossLessEncoder.EncodeAlphaImageData(alphaAsImage, this.alphaData); - size = width * height; return this.alphaData; } - /// - /// Store the transparency in the green channel. - /// - /// The pixel format. - /// The to encode from. - /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - /// The transparency image. - private static Image DispatchAlphaToGreen(Image image, Span alphaData) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - var alphaAsImage = new Image(width, height); + size = width * height; + return this.alphaData; + } + + /// + /// Store the transparency in the green channel. + /// + /// The pixel format. + /// The to encode from. + /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. + /// The transparency image. + private static Image DispatchAlphaToGreen(Image image, Span alphaData) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + var alphaAsImage = new Image(width, height); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + Memory rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y); + Span pixelRow = rowBuffer.Span; + Span alphaRow = alphaData.Slice(y * width, width); + for (int x = 0; x < width; x++) { - Memory rowBuffer = alphaAsImage.DangerousGetPixelRowMemory(y); - Span pixelRow = rowBuffer.Span; - Span alphaRow = alphaData.Slice(y * width, width); - for (int x = 0; x < width; x++) - { - // Leave A/R/B channels zero'd. - pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0); - } + // Leave A/R/B channels zero'd. + pixelRow[x] = new Rgba32(0, alphaRow[x], 0, 0); } - - return alphaAsImage; } - /// - /// Extract the alpha data of the image. - /// - /// The pixel format. - /// The to encode from. - /// The global configuration. - /// The memory manager. - /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. - private static IMemoryOwner ExtractAlphaChannel(Image image, Configuration configuration, MemoryAllocator memoryAllocator) - where TPixel : unmanaged, IPixel - { - Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; - int height = image.Height; - int width = image.Width; - IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height); - Span alphaData = alphaDataBuffer.GetSpan(); + return alphaAsImage; + } - using IMemoryOwner rowBuffer = memoryAllocator.Allocate(width); - Span rgbaRow = rowBuffer.GetSpan(); + /// + /// Extract the alpha data of the image. + /// + /// The pixel format. + /// The to encode from. + /// The global configuration. + /// The memory manager. + /// A byte sequence of length width * height, containing all the 8-bit transparency values in scan order. + private static IMemoryOwner ExtractAlphaChannel(Image image, Configuration configuration, MemoryAllocator memoryAllocator) + where TPixel : unmanaged, IPixel + { + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + int height = image.Height; + int width = image.Width; + IMemoryOwner alphaDataBuffer = memoryAllocator.Allocate(width * height); + Span alphaData = alphaDataBuffer.GetSpan(); + + using IMemoryOwner rowBuffer = memoryAllocator.Allocate(width); + Span rgbaRow = rowBuffer.GetSpan(); - for (int y = 0; y < height; y++) + for (int y = 0; y < height; y++) + { + Span rowSpan = imageBuffer.DangerousGetRowSpan(y); + PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow); + int offset = y * width; + for (int x = 0; x < width; x++) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba32(configuration, rowSpan, rgbaRow); - int offset = y * width; - for (int x = 0; x < width; x++) - { - alphaData[offset + x] = rgbaRow[x].A; - } + alphaData[offset + x] = rgbaRow[x].A; } - - return alphaDataBuffer; } - /// - public void Dispose() => this.alphaData?.Dispose(); + return alphaDataBuffer; } + + /// + public void Dispose() => this.alphaData?.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs b/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs index 659f9ef9ae..99b2462cea 100644 --- a/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs +++ b/src/ImageSharp/Formats/Webp/AnimationBlendingMethod.cs @@ -1,23 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. +/// +internal enum AnimationBlendingMethod { /// - /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. + /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending. + /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. /// - internal enum AnimationBlendingMethod - { - /// - /// Use alpha blending. After disposing of the previous frame, render the current frame on the canvas using alpha-blending. - /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. - /// - AlphaBlending = 0, + AlphaBlending = 0, - /// - /// Do not blend. After disposing of the previous frame, - /// render the current frame on the canvas by overwriting the rectangle covered by the current frame. - /// - DoNotBlend = 1 - } + /// + /// Do not blend. After disposing of the previous frame, + /// render the current frame on the canvas by overwriting the rectangle covered by the current frame. + /// + DoNotBlend = 1 } diff --git a/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs b/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs index 4ba74c8959..23bc37c283 100644 --- a/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs +++ b/src/ImageSharp/Formats/Webp/AnimationDisposalMethod.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. +/// +internal enum AnimationDisposalMethod { /// - /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. + /// Do not dispose. Leave the canvas as is. /// - internal enum AnimationDisposalMethod - { - /// - /// Do not dispose. Leave the canvas as is. - /// - DoNotDispose = 0, + DoNotDispose = 0, - /// - /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame with background color specified in the ANIM chunk. - /// - Dispose = 1 - } + /// + /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame with background color specified in the ANIM chunk. + /// + Dispose = 1 } diff --git a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs b/src/ImageSharp/Formats/Webp/AnimationFrameData.cs index 7485bddf30..714ec428ec 100644 --- a/src/ImageSharp/Formats/Webp/AnimationFrameData.cs +++ b/src/ImageSharp/Formats/Webp/AnimationFrameData.cs @@ -1,49 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal struct AnimationFrameData { - internal struct AnimationFrameData - { - /// - /// The animation chunk size. - /// - public uint DataSize; - - /// - /// The X coordinate of the upper left corner of the frame is Frame X * 2. - /// - public uint X; - - /// - /// The Y coordinate of the upper left corner of the frame is Frame Y * 2. - /// - public uint Y; - - /// - /// The width of the frame. - /// - public uint Width; - - /// - /// The height of the frame. - /// - public uint Height; - - /// - /// The time to wait before displaying the next frame, in 1 millisecond units. - /// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined. - /// - public uint Duration; - - /// - /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. - /// - public AnimationBlendingMethod BlendingMethod; - - /// - /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. - /// - public AnimationDisposalMethod DisposalMethod; - } + /// + /// The animation chunk size. + /// + public uint DataSize; + + /// + /// The X coordinate of the upper left corner of the frame is Frame X * 2. + /// + public uint X; + + /// + /// The Y coordinate of the upper left corner of the frame is Frame Y * 2. + /// + public uint Y; + + /// + /// The width of the frame. + /// + public uint Width; + + /// + /// The height of the frame. + /// + public uint Height; + + /// + /// The time to wait before displaying the next frame, in 1 millisecond units. + /// Note the interpretation of frame duration of 0 (and often smaller then 10) is implementation defined. + /// + public uint Duration; + + /// + /// Indicates how transparent pixels of the current frame are to be blended with corresponding pixels of the previous canvas. + /// + public AnimationBlendingMethod BlendingMethod; + + /// + /// Indicates how the current frame is to be treated after it has been displayed (before rendering the next frame) on the canvas. + /// + public AnimationDisposalMethod DisposalMethod; } diff --git a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs index ae8ac31dd9..b88dac8ede 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/BitReaderBase.cs @@ -1,58 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader; + +/// +/// Base class for VP8 and VP8L bitreader. +/// +internal abstract class BitReaderBase : IDisposable { + private bool isDisposed; + + /// + /// Gets or sets the raw encoded image data. + /// + public IMemoryOwner Data { get; set; } + /// - /// Base class for VP8 and VP8L bitreader. + /// Copies the raw encoded image data from the stream into a byte array. /// - internal abstract class BitReaderBase : IDisposable + /// The input stream. + /// Number of bytes to read as indicated from the chunk size. + /// Used for allocating memory during reading data from the stream. + protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) { - private bool isDisposed; - - /// - /// Gets or sets the raw encoded image data. - /// - public IMemoryOwner Data { get; set; } - - /// - /// Copies the raw encoded image data from the stream into a byte array. - /// - /// The input stream. - /// Number of bytes to read as indicated from the chunk size. - /// Used for allocating memory during reading data from the stream. - protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator) - { - this.Data = memoryAllocator.Allocate(bytesToRead); - Span dataSpan = this.Data.Memory.Span; - input.Read(dataSpan[..bytesToRead], 0, bytesToRead); - } + this.Data = memoryAllocator.Allocate(bytesToRead); + Span dataSpan = this.Data.Memory.Span; + input.Read(dataSpan[..bytesToRead], 0, bytesToRead); + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.Data?.Dispose(); - } - - this.isDisposed = true; + return; } - /// - public void Dispose() + if (disposing) { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); + this.Data?.Dispose(); } + + this.isDisposed = true; + } + + /// + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); } } diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs index 24761213e8..07bfcccd91 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8BitReader.cs @@ -3,230 +3,228 @@ using System.Buffers; using System.Buffers.Binary; -using System.IO; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader; + +/// +/// A bit reader for VP8 streams. +/// +internal class Vp8BitReader : BitReaderBase { + private const int BitsCount = 56; + + /// + /// Current value. + /// + private ulong value; + + /// + /// Current range minus 1. In [127, 254] interval. + /// + private uint range; + + /// + /// Number of valid bits left. + /// + private int bits; + + /// + /// Max packed-read position of the buffer. + /// + private uint bufferMax; + + private uint bufferEnd; + + /// + /// True if input is exhausted. + /// + private bool eof; + /// - /// A bit reader for VP8 streams. + /// Byte position in buffer. /// - internal class Vp8BitReader : BitReaderBase + private long pos; + + /// + /// Initializes a new instance of the class. + /// + /// The input stream to read from. + /// The raw image data size in bytes. + /// Used for allocating memory during reading data from the stream. + /// The partition length. + /// Start index in the data array. Defaults to 0. + public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) { - private const int BitsCount = 56; - - /// - /// Current value. - /// - private ulong value; - - /// - /// Current range minus 1. In [127, 254] interval. - /// - private uint range; - - /// - /// Number of valid bits left. - /// - private int bits; - - /// - /// Max packed-read position of the buffer. - /// - private uint bufferMax; - - private uint bufferEnd; - - /// - /// True if input is exhausted. - /// - private bool eof; - - /// - /// Byte position in buffer. - /// - private long pos; - - /// - /// Initializes a new instance of the class. - /// - /// The input stream to read from. - /// The raw image data size in bytes. - /// Used for allocating memory during reading data from the stream. - /// The partition length. - /// Start index in the data array. Defaults to 0. - public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0) - { - Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); + Guard.MustBeLessThan(imageDataSize, int.MaxValue, nameof(imageDataSize)); - this.ImageDataSize = imageDataSize; - this.PartitionLength = partitionLength; - this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); - this.InitBitreader(partitionLength, startPos); - } + this.ImageDataSize = imageDataSize; + this.PartitionLength = partitionLength; + this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); + this.InitBitreader(partitionLength, startPos); + } - /// - /// Initializes a new instance of the class. - /// - /// The raw encoded image data. - /// The partition length. - /// Start index in the data array. Defaults to 0. - public Vp8BitReader(IMemoryOwner imageData, uint partitionLength, int startPos = 0) - { - this.Data = imageData; - this.ImageDataSize = (uint)imageData.Memory.Length; - this.PartitionLength = partitionLength; - this.InitBitreader(partitionLength, startPos); - } + /// + /// Initializes a new instance of the class. + /// + /// The raw encoded image data. + /// The partition length. + /// Start index in the data array. Defaults to 0. + public Vp8BitReader(IMemoryOwner imageData, uint partitionLength, int startPos = 0) + { + this.Data = imageData; + this.ImageDataSize = (uint)imageData.Memory.Length; + this.PartitionLength = partitionLength; + this.InitBitreader(partitionLength, startPos); + } - public int Pos => (int)this.pos; + public int Pos => (int)this.pos; - public uint ImageDataSize { get; } + public uint ImageDataSize { get; } - public uint PartitionLength { get; } + public uint PartitionLength { get; } - public uint Remaining { get; set; } + public uint Remaining { get; set; } - [MethodImpl(InliningOptions.ShortMethod)] - public int GetBit(int prob) + [MethodImpl(InliningOptions.ShortMethod)] + public int GetBit(int prob) + { + uint range = this.range; + if (this.bits < 0) { - uint range = this.range; - if (this.bits < 0) - { - this.LoadNewBytes(); - } - - int pos = this.bits; - uint split = (uint)((range * prob) >> 8); - ulong value = this.value >> pos; - bool bit = value > split; - if (bit) - { - range -= split; - this.value -= (ulong)(split + 1) << pos; - } - else - { - range = split + 1; - } - - int shift = 7 ^ BitOperations.Log2(range); - range <<= shift; - this.bits -= shift; - - this.range = range - 1; - - return bit ? 1 : 0; + this.LoadNewBytes(); } - // Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) - public int GetSigned(int v) + int pos = this.bits; + uint split = (uint)((range * prob) >> 8); + ulong value = this.value >> pos; + bool bit = value > split; + if (bit) + { + range -= split; + this.value -= (ulong)(split + 1) << pos; + } + else { - if (this.bits < 0) - { - this.LoadNewBytes(); - } - - int pos = this.bits; - uint split = this.range >> 1; - ulong value = this.value >> pos; - ulong mask = (split - value) >> 31; // -1 or 0 - this.bits--; - this.range = (this.range + (uint)mask) | 1; - this.value -= ((split + 1) & mask) << pos; - - return (v ^ (int)mask) - (int)mask; + range = split + 1; } - [MethodImpl(InliningOptions.ShortMethod)] - public bool ReadBool() => this.ReadValue(1) is 1; + int shift = 7 ^ BitOperations.Log2(range); + range <<= shift; + this.bits -= shift; - [MethodImpl(InliningOptions.ShortMethod)] - public uint ReadValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); + this.range = range - 1; - uint v = 0; - while (nBits-- > 0) - { - v |= (uint)this.GetBit(0x80) << nBits; - } + return bit ? 1 : 0; + } - return v; + // Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) + public int GetSigned(int v) + { + if (this.bits < 0) + { + this.LoadNewBytes(); } - [MethodImpl(InliningOptions.ShortMethod)] - public int ReadSignedValue(int nBits) - { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); + int pos = this.bits; + uint split = this.range >> 1; + ulong value = this.value >> pos; + ulong mask = (split - value) >> 31; // -1 or 0 + this.bits--; + this.range = (this.range + (uint)mask) | 1; + this.value -= ((split + 1) & mask) << pos; - int value = (int)this.ReadValue(nBits); - return this.ReadValue(1) != 0 ? -value : value; - } + return (v ^ (int)mask) - (int)mask; + } - private void InitBitreader(uint size, int pos = 0) - { - long posPlusSize = pos + size; - this.range = 255 - 1; - this.value = 0; - this.bits = -8; // to load the very first 8 bits. - this.eof = false; - this.pos = pos; - this.bufferEnd = (uint)posPlusSize; - this.bufferMax = (uint)(size > 8 ? posPlusSize - 8 + 1 : pos); + [MethodImpl(InliningOptions.ShortMethod)] + public bool ReadBool() => this.ReadValue(1) is 1; - this.LoadNewBytes(); - } + [MethodImpl(InliningOptions.ShortMethod)] + public uint ReadValue(int nBits) + { + DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); - [MethodImpl(InliningOptions.ColdPath)] - private void LoadNewBytes() + uint v = 0; + while (nBits-- > 0) { - if (this.pos < this.bufferMax) - { - ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.Memory.Span.Slice((int)this.pos, 8)); - this.pos += BitsCount >> 3; - ulong bits = ByteSwap64(inBits); - bits >>= 64 - BitsCount; - this.value = bits | (this.value << BitsCount); - this.bits += BitsCount; - } - else - { - this.LoadFinalBytes(); - } + v |= (uint)this.GetBit(0x80) << nBits; } - private void LoadFinalBytes() + return v; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public int ReadSignedValue(int nBits) + { + DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + DebugGuard.MustBeLessThanOrEqualTo(nBits, 32, nameof(nBits)); + + int value = (int)this.ReadValue(nBits); + return this.ReadValue(1) != 0 ? -value : value; + } + + private void InitBitreader(uint size, int pos = 0) + { + long posPlusSize = pos + size; + this.range = 255 - 1; + this.value = 0; + this.bits = -8; // to load the very first 8 bits. + this.eof = false; + this.pos = pos; + this.bufferEnd = (uint)posPlusSize; + this.bufferMax = (uint)(size > 8 ? posPlusSize - 8 + 1 : pos); + + this.LoadNewBytes(); + } + + [MethodImpl(InliningOptions.ColdPath)] + private void LoadNewBytes() + { + if (this.pos < this.bufferMax) + { + ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.Memory.Span.Slice((int)this.pos, 8)); + this.pos += BitsCount >> 3; + ulong bits = ByteSwap64(inBits); + bits >>= 64 - BitsCount; + this.value = bits | (this.value << BitsCount); + this.bits += BitsCount; + } + else { - // Only read 8bits at a time. - if (this.pos < this.bufferEnd) - { - this.bits += 8; - this.value = this.Data.Memory.Span[(int)this.pos++] | (this.value << 8); - } - else if (!this.eof) - { - this.value <<= 8; - this.bits += 8; - this.eof = true; - } - else - { - this.bits = 0; // This is to avoid undefined behaviour with shifts. - } + this.LoadFinalBytes(); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static ulong ByteSwap64(ulong x) + private void LoadFinalBytes() + { + // Only read 8bits at a time. + if (this.pos < this.bufferEnd) + { + this.bits += 8; + this.value = this.Data.Memory.Span[(int)this.pos++] | (this.value << 8); + } + else if (!this.eof) + { + this.value <<= 8; + this.bits += 8; + this.eof = true; + } + else { - x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); - x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16); - x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); - return x; + this.bits = 0; // This is to avoid undefined behaviour with shifts. } } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong ByteSwap64(ulong x) + { + x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32); + x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16); + x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8); + return x; + } } diff --git a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs index cbc9360e7d..057abf134a 100644 --- a/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs +++ b/src/ImageSharp/Formats/Webp/BitReader/Vp8LBitReader.cs @@ -2,206 +2,204 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.BitReader +namespace SixLabors.ImageSharp.Formats.Webp.BitReader; + +/// +/// A bit reader for reading lossless webp streams. +/// +internal class Vp8LBitReader : BitReaderBase { /// - /// A bit reader for reading lossless webp streams. + /// Maximum number of bits (inclusive) the bit-reader can handle. /// - internal class Vp8LBitReader : BitReaderBase + private const int Vp8LMaxNumBitRead = 24; + + /// + /// Number of bits prefetched. + /// + private const int Lbits = 64; + + /// + /// Minimum number of bytes ready after VP8LFillBitWindow. + /// + private const int Wbits = 32; + + private static readonly uint[] BitMask = { - /// - /// Maximum number of bits (inclusive) the bit-reader can handle. - /// - private const int Vp8LMaxNumBitRead = 24; - - /// - /// Number of bits prefetched. - /// - private const int Lbits = 64; - - /// - /// Minimum number of bytes ready after VP8LFillBitWindow. - /// - private const int Wbits = 32; - - private static readonly uint[] BitMask = - { - 0, - 0x000001, 0x000003, 0x000007, 0x00000f, - 0x00001f, 0x00003f, 0x00007f, 0x0000ff, - 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, - 0x001fff, 0x003fff, 0x007fff, 0x00ffff, - 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, - 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff - }; - - /// - /// Pre-fetched bits. - /// - private ulong value; - - /// - /// Buffer length. - /// - private readonly long len; - - /// - /// Byte position in buffer. - /// - private long pos; - - /// - /// Current bit-reading position in value. - /// - private int bitPos; - - /// - /// Initializes a new instance of the class. - /// - /// Lossless compressed image data. - public Vp8LBitReader(IMemoryOwner data) - { - this.Data = data; - this.len = data.Memory.Length; - this.value = 0; - this.bitPos = 0; - this.Eos = false; - - ulong currentValue = 0; - System.Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < 8; i++) - { - currentValue |= (ulong)dataSpan[i] << (8 * i); - } - - this.value = currentValue; - this.pos = 8; - } + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff + }; + + /// + /// Pre-fetched bits. + /// + private ulong value; + + /// + /// Buffer length. + /// + private readonly long len; - /// - /// Initializes a new instance of the class. - /// - /// The input stream to read from. - /// The raw image data size in bytes. - /// Used for allocating memory during reading data from the stream. - public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) + /// + /// Byte position in buffer. + /// + private long pos; + + /// + /// Current bit-reading position in value. + /// + private int bitPos; + + /// + /// Initializes a new instance of the class. + /// + /// Lossless compressed image data. + public Vp8LBitReader(IMemoryOwner data) + { + this.Data = data; + this.len = data.Memory.Length; + this.value = 0; + this.bitPos = 0; + this.Eos = false; + + ulong currentValue = 0; + System.Span dataSpan = this.Data.Memory.Span; + for (int i = 0; i < 8; i++) { - long length = imageDataSize; + currentValue |= (ulong)dataSpan[i] << (8 * i); + } - this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); + this.value = currentValue; + this.pos = 8; + } - this.len = length; - this.value = 0; - this.bitPos = 0; - this.Eos = false; + /// + /// Initializes a new instance of the class. + /// + /// The input stream to read from. + /// The raw image data size in bytes. + /// Used for allocating memory during reading data from the stream. + public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator) + { + long length = imageDataSize; - if (length > sizeof(long)) - { - length = sizeof(long); - } + this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator); - ulong currentValue = 0; - System.Span dataSpan = this.Data.Memory.Span; - for (int i = 0; i < length; i++) - { - currentValue |= (ulong)dataSpan[i] << (8 * i); - } + this.len = length; + this.value = 0; + this.bitPos = 0; + this.Eos = false; - this.value = currentValue; - this.pos = length; + if (length > sizeof(long)) + { + length = sizeof(long); } - /// - /// Gets or sets a value indicating whether a bit was read past the end of buffer. - /// - public bool Eos { get; set; } - - /// - /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. - /// - /// The number of bits to read (should not exceed 16). - /// A ushort value. - [MethodImpl(InliningOptions.ShortMethod)] - public uint ReadValue(int nBits) + ulong currentValue = 0; + System.Span dataSpan = this.Data.Memory.Span; + for (int i = 0; i < length; i++) { - DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); + currentValue |= (ulong)dataSpan[i] << (8 * i); + } - if (!this.Eos && nBits <= Vp8LMaxNumBitRead) - { - ulong val = this.PrefetchBits() & BitMask[nBits]; - this.bitPos += nBits; - this.ShiftBytes(); - return (uint)val; - } + this.value = currentValue; + this.pos = length; + } - return 0; - } + /// + /// Gets or sets a value indicating whether a bit was read past the end of buffer. + /// + public bool Eos { get; set; } + + /// + /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order. + /// + /// The number of bits to read (should not exceed 16). + /// A ushort value. + [MethodImpl(InliningOptions.ShortMethod)] + public uint ReadValue(int nBits) + { + DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits)); - /// - /// Reads a single bit from the stream. - /// - /// True if the bit read was 1, false otherwise. - [MethodImpl(InliningOptions.ShortMethod)] - public bool ReadBit() + if (!this.Eos && nBits <= Vp8LMaxNumBitRead) { - uint bit = this.ReadValue(1); - return bit != 0; + ulong val = this.PrefetchBits() & BitMask[nBits]; + this.bitPos += nBits; + this.ShiftBytes(); + return (uint)val; } - /// - /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow. - /// - /// The number of bits to advance the position. - [MethodImpl(InliningOptions.ShortMethod)] - public void AdvanceBitPosition(int numberOfBits) => this.bitPos += numberOfBits; - - /// - /// Return the pre-fetched bits, so they can be looked up. - /// - /// The pre-fetched bits. - [MethodImpl(InliningOptions.ShortMethod)] - public ulong PrefetchBits() => this.value >> (this.bitPos & (Lbits - 1)); - - /// - /// Advances the read buffer by 4 bytes to make room for reading next 32 bits. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FillBitWindow() + return 0; + } + + /// + /// Reads a single bit from the stream. + /// + /// True if the bit read was 1, false otherwise. + [MethodImpl(InliningOptions.ShortMethod)] + public bool ReadBit() + { + uint bit = this.ReadValue(1); + return bit != 0; + } + + /// + /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow. + /// + /// The number of bits to advance the position. + [MethodImpl(InliningOptions.ShortMethod)] + public void AdvanceBitPosition(int numberOfBits) => this.bitPos += numberOfBits; + + /// + /// Return the pre-fetched bits, so they can be looked up. + /// + /// The pre-fetched bits. + [MethodImpl(InliningOptions.ShortMethod)] + public ulong PrefetchBits() => this.value >> (this.bitPos & (Lbits - 1)); + + /// + /// Advances the read buffer by 4 bytes to make room for reading next 32 bits. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FillBitWindow() + { + if (this.bitPos >= Wbits) { - if (this.bitPos >= Wbits) - { - this.DoFillBitWindow(); - } + this.DoFillBitWindow(); } + } + + /// + /// Returns true if there was an attempt at reading bit past the end of the buffer. + /// + /// True, if end of buffer was reached. + [MethodImpl(InliningOptions.ShortMethod)] + public bool IsEndOfStream() => this.Eos || (this.pos == this.len && this.bitPos > Lbits); - /// - /// Returns true if there was an attempt at reading bit past the end of the buffer. - /// - /// True, if end of buffer was reached. - [MethodImpl(InliningOptions.ShortMethod)] - public bool IsEndOfStream() => this.Eos || (this.pos == this.len && this.bitPos > Lbits); - - [MethodImpl(InliningOptions.ShortMethod)] - private void DoFillBitWindow() => this.ShiftBytes(); - - /// - /// If not at EOS, reload up to Vp8LLbits byte-by-byte. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private void ShiftBytes() + [MethodImpl(InliningOptions.ShortMethod)] + private void DoFillBitWindow() => this.ShiftBytes(); + + /// + /// If not at EOS, reload up to Vp8LLbits byte-by-byte. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private void ShiftBytes() + { + System.Span dataSpan = this.Data.Memory.Span; + while (this.bitPos >= 8 && this.pos < this.len) { - System.Span dataSpan = this.Data.Memory.Span; - while (this.bitPos >= 8 && this.pos < this.len) - { - this.value >>= 8; - this.value |= (ulong)dataSpan[(int)this.pos] << (Lbits - 8); - ++this.pos; - this.bitPos -= 8; - } + this.value >>= 8; + this.value |= (ulong)dataSpan[(int)this.pos] << (Lbits - 8); + ++this.pos; + this.bitPos -= 8; } } } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index ed41c29fe6..2df02727e0 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -1,262 +1,259 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; + +internal abstract class BitWriterBase { - internal abstract class BitWriterBase + private const uint MaxDimension = 16777215; + + private const ulong MaxCanvasPixels = 4294967295ul; + + protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; + + /// + /// Buffer to write to. + /// + private byte[] buffer; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[4]; + + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + protected BitWriterBase(int expectedSize) => this.buffer = new byte[expectedSize]; + + /// + /// Initializes a new instance of the class. + /// Used internally for cloning. + /// + /// The byte buffer. + private protected BitWriterBase(byte[] buffer) => this.buffer = buffer; + + public byte[] Buffer => this.buffer; + + /// + /// Writes the encoded bytes of the image to the stream. Call Finish() before this. + /// + /// The stream to write to. + public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); + + /// + /// Writes the encoded bytes of the image to the given buffer. Call Finish() before this. + /// + /// The destination buffer. + public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest); + + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public abstract void BitWriterResize(int extraSize); + + /// + /// Returns the number of bytes of the encoded image data. + /// + /// The number of bytes of the image data. + public abstract int NumBytes(); + + /// + /// Flush leftover bits. + /// + public abstract void Finish(); + + protected void ResizeBuffer(int maxBytes, int sizeRequired) { - private const uint MaxDimension = 16777215; - - private const ulong MaxCanvasPixels = 4294967295ul; - - protected const uint ExtendedFileChunkSize = WebpConstants.ChunkHeaderSize + WebpConstants.Vp8XChunkSize; - - /// - /// Buffer to write to. - /// - private byte[] buffer; - - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBuffer = new byte[4]; - - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - protected BitWriterBase(int expectedSize) => this.buffer = new byte[expectedSize]; - - /// - /// Initializes a new instance of the class. - /// Used internally for cloning. - /// - /// The byte buffer. - private protected BitWriterBase(byte[] buffer) => this.buffer = buffer; - - public byte[] Buffer => this.buffer; - - /// - /// Writes the encoded bytes of the image to the stream. Call Finish() before this. - /// - /// The stream to write to. - public void WriteToStream(Stream stream) => stream.Write(this.Buffer.AsSpan(0, this.NumBytes())); - - /// - /// Writes the encoded bytes of the image to the given buffer. Call Finish() before this. - /// - /// The destination buffer. - public void WriteToBuffer(Span dest) => this.Buffer.AsSpan(0, this.NumBytes()).CopyTo(dest); - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public abstract void BitWriterResize(int extraSize); - - /// - /// Returns the number of bytes of the encoded image data. - /// - /// The number of bytes of the image data. - public abstract int NumBytes(); - - /// - /// Flush leftover bits. - /// - public abstract void Finish(); - - protected void ResizeBuffer(int maxBytes, int sizeRequired) + int newSize = (3 * maxBytes) >> 1; + if (newSize < sizeRequired) { - int newSize = (3 * maxBytes) >> 1; - if (newSize < sizeRequired) - { - newSize = sizeRequired; - } - - // Make new size multiple of 1k. - newSize = ((newSize >> 10) + 1) << 10; - Array.Resize(ref this.buffer, newSize); + newSize = sizeRequired; } - /// - /// Writes the RIFF header to the stream. - /// - /// The stream to write to. - /// The block length. - protected void WriteRiffHeader(Stream stream, uint riffSize) + // Make new size multiple of 1k. + newSize = ((newSize >> 10) + 1) << 10; + Array.Resize(ref this.buffer, newSize); + } + + /// + /// Writes the RIFF header to the stream. + /// + /// The stream to write to. + /// The block length. + protected void WriteRiffHeader(Stream stream, uint riffSize) + { + stream.Write(WebpConstants.RiffFourCc); + BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize); + stream.Write(this.scratchBuffer.AsSpan(0, 4)); + stream.Write(WebpConstants.WebpHeader); + } + + /// + /// Calculates the chunk size of EXIF, XMP or ICCP metadata. + /// + /// The metadata profile bytes. + /// The metadata chunk size in bytes. + protected static uint MetadataChunkSize(byte[] metadataBytes) + { + uint metaSize = (uint)metadataBytes.Length; + return WebpConstants.ChunkHeaderSize + metaSize + (metaSize & 1); + } + + /// + /// Calculates the chunk size of a alpha chunk. + /// + /// The alpha chunk bytes. + /// The alpha data chunk size in bytes. + protected static uint AlphaChunkSize(Span alphaBytes) + { + uint alphaSize = (uint)alphaBytes.Length + 1; + return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1); + } + + /// + /// Writes a metadata profile (EXIF or XMP) to the stream. + /// + /// The stream to write to. + /// The metadata profile's bytes. + /// The chuck type to write. + protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType) + { + DebugGuard.NotNull(metadataBytes, nameof(metadataBytes)); + + uint size = (uint)metadataBytes.Length; + Span buf = this.scratchBuffer.AsSpan(0, 4); + BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, size); + stream.Write(buf); + stream.Write(metadataBytes); + + // Add padding byte if needed. + if ((size & 1) == 1) { - stream.Write(WebpConstants.RiffFourCc); - BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, riffSize); - stream.Write(this.scratchBuffer.AsSpan(0, 4)); - stream.Write(WebpConstants.WebpHeader); + stream.WriteByte(0); } + } - /// - /// Calculates the chunk size of EXIF, XMP or ICCP metadata. - /// - /// The metadata profile bytes. - /// The metadata chunk size in bytes. - protected static uint MetadataChunkSize(byte[] metadataBytes) + /// + /// Writes the alpha chunk to the stream. + /// + /// The stream to write to. + /// The alpha channel data bytes. + /// Indicates, if the alpha channel data is compressed. + protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed) + { + uint size = (uint)dataBytes.Length + 1; + Span buf = this.scratchBuffer.AsSpan(0, 4); + BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, size); + stream.Write(buf); + + byte flags = 0; + if (alphaDataIsCompressed) { - uint metaSize = (uint)metadataBytes.Length; - return WebpConstants.ChunkHeaderSize + metaSize + (metaSize & 1); + flags |= 1; } - /// - /// Calculates the chunk size of a alpha chunk. - /// - /// The alpha chunk bytes. - /// The alpha data chunk size in bytes. - protected static uint AlphaChunkSize(Span alphaBytes) + stream.WriteByte(flags); + stream.Write(dataBytes); + + // Add padding byte if needed. + if ((size & 1) == 1) { - uint alphaSize = (uint)alphaBytes.Length + 1; - return WebpConstants.ChunkHeaderSize + alphaSize + (alphaSize & 1); + stream.WriteByte(0); } + } + + /// + /// Writes the color profile to the stream. + /// + /// The stream to write to. + /// The color profile bytes. + protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) + { + uint size = (uint)iccProfileBytes.Length; - /// - /// Writes a metadata profile (EXIF or XMP) to the stream. - /// - /// The stream to write to. - /// The metadata profile's bytes. - /// The chuck type to write. - protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType) + Span buf = this.scratchBuffer.AsSpan(0, 4); + BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, size); + stream.Write(buf); + + stream.Write(iccProfileBytes); + + // Add padding byte if needed. + if ((size & 1) == 1) { - DebugGuard.NotNull(metadataBytes, nameof(metadataBytes)); - - uint size = (uint)metadataBytes.Length; - Span buf = this.scratchBuffer.AsSpan(0, 4); - BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, size); - stream.Write(buf); - stream.Write(metadataBytes); - - // Add padding byte if needed. - if ((size & 1) == 1) - { - stream.WriteByte(0); - } + stream.WriteByte(0); } + } - /// - /// Writes the alpha chunk to the stream. - /// - /// The stream to write to. - /// The alpha channel data bytes. - /// Indicates, if the alpha channel data is compressed. - protected void WriteAlphaChunk(Stream stream, Span dataBytes, bool alphaDataIsCompressed) + /// + /// Writes a VP8X header to the stream. + /// + /// The stream to write to. + /// A exif profile or null, if it does not exist. + /// A XMP profile or null, if it does not exist. + /// The color profile bytes. + /// The width of the image. + /// The height of the image. + /// Flag indicating, if a alpha channel is present. + protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, byte[] iccProfileBytes, uint width, uint height, bool hasAlpha) + { + if (width > MaxDimension || height > MaxDimension) { - uint size = (uint)dataBytes.Length + 1; - Span buf = this.scratchBuffer.AsSpan(0, 4); - BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Alpha); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, size); - stream.Write(buf); - - byte flags = 0; - if (alphaDataIsCompressed) - { - flags |= 1; - } - - stream.WriteByte(flags); - stream.Write(dataBytes); - - // Add padding byte if needed. - if ((size & 1) == 1) - { - stream.WriteByte(0); - } + WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}"); } - /// - /// Writes the color profile to the stream. - /// - /// The stream to write to. - /// The color profile bytes. - protected void WriteColorProfile(Stream stream, byte[] iccProfileBytes) + // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1. + if (width * height > MaxCanvasPixels) { - uint size = (uint)iccProfileBytes.Length; + WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1"); + } - Span buf = this.scratchBuffer.AsSpan(0, 4); - BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Iccp); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, size); - stream.Write(buf); + uint flags = 0; + if (exifProfile != null) + { + // Set exif bit. + flags |= 8; + } - stream.Write(iccProfileBytes); + if (xmpProfile != null) + { + // Set xmp bit. + flags |= 4; + } - // Add padding byte if needed. - if ((size & 1) == 1) - { - stream.WriteByte(0); - } + if (hasAlpha) + { + // Set alpha bit. + flags |= 16; } - /// - /// Writes a VP8X header to the stream. - /// - /// The stream to write to. - /// A exif profile or null, if it does not exist. - /// A XMP profile or null, if it does not exist. - /// The color profile bytes. - /// The width of the image. - /// The height of the image. - /// Flag indicating, if a alpha channel is present. - protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, byte[] iccProfileBytes, uint width, uint height, bool hasAlpha) + if (iccProfileBytes != null) { - if (width > MaxDimension || height > MaxDimension) - { - WebpThrowHelper.ThrowInvalidImageDimensions($"Image width or height exceeds maximum allowed dimension of {MaxDimension}"); - } - - // The spec states that the product of Canvas Width and Canvas Height MUST be at most 2^32 - 1. - if (width * height > MaxCanvasPixels) - { - WebpThrowHelper.ThrowInvalidImageDimensions("The product of image width and height MUST be at most 2^32 - 1"); - } - - uint flags = 0; - if (exifProfile != null) - { - // Set exif bit. - flags |= 8; - } - - if (xmpProfile != null) - { - // Set xmp bit. - flags |= 4; - } - - if (hasAlpha) - { - // Set alpha bit. - flags |= 16; - } - - if (iccProfileBytes != null) - { - // Set iccp flag. - flags |= 32; - } - - Span buf = this.scratchBuffer.AsSpan(0, 4); - stream.Write(WebpConstants.Vp8XMagicBytes); - BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, flags); - stream.Write(buf); - BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1); - stream.Write(buf[..3]); - BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1); - stream.Write(buf[..3]); + // Set iccp flag. + flags |= 32; } + + Span buf = this.scratchBuffer.AsSpan(0, 4); + stream.Write(WebpConstants.Vp8XMagicBytes); + BinaryPrimitives.WriteUInt32LittleEndian(buf, WebpConstants.Vp8XChunkSize); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, flags); + stream.Write(buf); + BinaryPrimitives.WriteUInt32LittleEndian(buf, width - 1); + stream.Write(buf[..3]); + BinaryPrimitives.WriteUInt32LittleEndian(buf, height - 1); + stream.Write(buf[..3]); } } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index a218d50f4a..87f993d93f 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -1,750 +1,747 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; + +/// +/// A bit writer for writing lossy webp streams. +/// +internal class Vp8BitWriter : BitWriterBase { - /// - /// A bit writer for writing lossy webp streams. - /// - internal class Vp8BitWriter : BitWriterBase - { #pragma warning disable SA1310 // Field names should not contain underscore - private const int DC_PRED = 0; - private const int TM_PRED = 1; - private const int V_PRED = 2; - private const int H_PRED = 3; - - // 4x4 modes - private const int B_DC_PRED = 0; - private const int B_TM_PRED = 1; - private const int B_VE_PRED = 2; - private const int B_HE_PRED = 3; - private const int B_RD_PRED = 4; - private const int B_VR_PRED = 5; - private const int B_LD_PRED = 6; - private const int B_VL_PRED = 7; - private const int B_HD_PRED = 8; - private const int B_HU_PRED = 9; + private const int DC_PRED = 0; + private const int TM_PRED = 1; + private const int V_PRED = 2; + private const int H_PRED = 3; + + // 4x4 modes + private const int B_DC_PRED = 0; + private const int B_TM_PRED = 1; + private const int B_VE_PRED = 2; + private const int B_HE_PRED = 3; + private const int B_RD_PRED = 4; + private const int B_VR_PRED = 5; + private const int B_LD_PRED = 6; + private const int B_VL_PRED = 7; + private const int B_HD_PRED = 8; + private const int B_HU_PRED = 9; #pragma warning restore SA1310 // Field names should not contain underscore - private readonly Vp8Encoder enc; + private readonly Vp8Encoder enc; - private int range; + private int range; - private int value; + private int value; - /// - /// Number of outstanding bits. - /// - private int run; + /// + /// Number of outstanding bits. + /// + private int run; - /// - /// Number of pending bits. - /// - private int nbBits; + /// + /// Number of pending bits. + /// + private int nbBits; - private uint pos; + private uint pos; - private readonly int maxPos; + private readonly int maxPos; - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - public Vp8BitWriter(int expectedSize) - : base(expectedSize) - { - this.range = 255 - 1; - this.value = 0; - this.run = 0; - this.nbBits = -8; - this.pos = 0; - this.maxPos = 0; - } + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + public Vp8BitWriter(int expectedSize) + : base(expectedSize) + { + this.range = 255 - 1; + this.value = 0; + this.run = 0; + this.nbBits = -8; + this.pos = 0; + this.maxPos = 0; + } + + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + /// The Vp8Encoder. + public Vp8BitWriter(int expectedSize, Vp8Encoder enc) + : this(expectedSize) => this.enc = enc; - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - /// The Vp8Encoder. - public Vp8BitWriter(int expectedSize, Vp8Encoder enc) - : this(expectedSize) => this.enc = enc; + /// + public override int NumBytes() => (int)this.pos; - /// - public override int NumBytes() => (int)this.pos; + public int PutCoeffs(int ctx, Vp8Residual residual) + { + int n = residual.First; + Vp8ProbaArray p = residual.Prob[n].Probabilities[ctx]; + if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) + { + return 0; + } - public int PutCoeffs(int ctx, Vp8Residual residual) + while (n < 16) { - int n = residual.First; - Vp8ProbaArray p = residual.Prob[n].Probabilities[ctx]; - if (!this.PutBit(residual.Last >= 0, p.Probabilities[0])) + int c = residual.Coeffs[n++]; + bool sign = c < 0; + int v = sign ? -c : c; + if (!this.PutBit(v != 0, p.Probabilities[1])) { - return 0; + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[0]; + continue; } - while (n < 16) + if (!this.PutBit(v > 1, p.Probabilities[2])) { - int c = residual.Coeffs[n++]; - bool sign = c < 0; - int v = sign ? -c : c; - if (!this.PutBit(v != 0, p.Probabilities[1])) + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[1]; + } + else + { + if (!this.PutBit(v > 4, p.Probabilities[3])) { - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[0]; - continue; + if (this.PutBit(v != 2, p.Probabilities[4])) + { + this.PutBit(v == 4, p.Probabilities[5]); + } } - - if (!this.PutBit(v > 1, p.Probabilities[2])) + else if (!this.PutBit(v > 10, p.Probabilities[6])) { - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[1]; + if (!this.PutBit(v > 6, p.Probabilities[7])) + { + this.PutBit(v == 6, 159); + } + else + { + this.PutBit(v >= 9, 165); + this.PutBit(!((v & 1) != 0), 145); + } } else { - if (!this.PutBit(v > 4, p.Probabilities[3])) + int mask; + byte[] tab; + if (v < 3 + (8 << 1)) { - if (this.PutBit(v != 2, p.Probabilities[4])) - { - this.PutBit(v == 4, p.Probabilities[5]); - } + // VP8Cat3 (3b) + this.PutBit(0, p.Probabilities[8]); + this.PutBit(0, p.Probabilities[9]); + v -= 3 + (8 << 0); + mask = 1 << 2; + tab = WebpConstants.Cat3; } - else if (!this.PutBit(v > 10, p.Probabilities[6])) + else if (v < 3 + (8 << 2)) { - if (!this.PutBit(v > 6, p.Probabilities[7])) - { - this.PutBit(v == 6, 159); - } - else - { - this.PutBit(v >= 9, 165); - this.PutBit(!((v & 1) != 0), 145); - } + // VP8Cat4 (4b) + this.PutBit(0, p.Probabilities[8]); + this.PutBit(1, p.Probabilities[9]); + v -= 3 + (8 << 1); + mask = 1 << 3; + tab = WebpConstants.Cat4; + } + else if (v < 3 + (8 << 3)) + { + // VP8Cat5 (5b) + this.PutBit(1, p.Probabilities[8]); + this.PutBit(0, p.Probabilities[10]); + v -= 3 + (8 << 2); + mask = 1 << 4; + tab = WebpConstants.Cat5; } else { - int mask; - byte[] tab; - if (v < 3 + (8 << 1)) - { - // VP8Cat3 (3b) - this.PutBit(0, p.Probabilities[8]); - this.PutBit(0, p.Probabilities[9]); - v -= 3 + (8 << 0); - mask = 1 << 2; - tab = WebpConstants.Cat3; - } - else if (v < 3 + (8 << 2)) - { - // VP8Cat4 (4b) - this.PutBit(0, p.Probabilities[8]); - this.PutBit(1, p.Probabilities[9]); - v -= 3 + (8 << 1); - mask = 1 << 3; - tab = WebpConstants.Cat4; - } - else if (v < 3 + (8 << 3)) - { - // VP8Cat5 (5b) - this.PutBit(1, p.Probabilities[8]); - this.PutBit(0, p.Probabilities[10]); - v -= 3 + (8 << 2); - mask = 1 << 4; - tab = WebpConstants.Cat5; - } - else - { - // VP8Cat6 (11b) - this.PutBit(1, p.Probabilities[8]); - this.PutBit(1, p.Probabilities[10]); - v -= 3 + (8 << 3); - mask = 1 << 10; - tab = WebpConstants.Cat6; - } - - int tabIdx = 0; - while (mask != 0) - { - this.PutBit(v & mask, tab[tabIdx++]); - mask >>= 1; - } + // VP8Cat6 (11b) + this.PutBit(1, p.Probabilities[8]); + this.PutBit(1, p.Probabilities[10]); + v -= 3 + (8 << 3); + mask = 1 << 10; + tab = WebpConstants.Cat6; } - p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[2]; + int tabIdx = 0; + while (mask != 0) + { + this.PutBit(v & mask, tab[tabIdx++]); + mask >>= 1; + } } - this.PutBitUniform(sign ? 1 : 0); - if (n == 16 || !this.PutBit(n <= residual.Last, p.Probabilities[0])) - { - return 1; // EOB - } + p = residual.Prob[WebpConstants.Vp8EncBands[n]].Probabilities[2]; } - return 1; - } - - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public override void BitWriterResize(int extraSize) - { - long neededSize = this.pos + extraSize; - if (neededSize <= this.maxPos) + this.PutBitUniform(sign ? 1 : 0); + if (n == 16 || !this.PutBit(n <= residual.Last, p.Probabilities[0])) { - return; + return 1; // EOB } - - this.ResizeBuffer(this.maxPos, (int)neededSize); } - /// - public override void Finish() + return 1; + } + + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public override void BitWriterResize(int extraSize) + { + long neededSize = this.pos + extraSize; + if (neededSize <= this.maxPos) { - this.PutBits(0, 9 - this.nbBits); - this.nbBits = 0; // pad with zeroes. - this.Flush(); + return; } - public void PutSegment(int s, Span p) - { - if (this.PutBit(s >= 2, p[0])) - { - p = p[1..]; - } + this.ResizeBuffer(this.maxPos, (int)neededSize); + } - this.PutBit(s & 1, p[1]); + /// + public override void Finish() + { + this.PutBits(0, 9 - this.nbBits); + this.nbBits = 0; // pad with zeroes. + this.Flush(); + } + + public void PutSegment(int s, Span p) + { + if (this.PutBit(s >= 2, p[0])) + { + p = p[1..]; } - public void PutI16Mode(int mode) + this.PutBit(s & 1, p[1]); + } + + public void PutI16Mode(int mode) + { + if (this.PutBit(mode is TM_PRED or H_PRED, 156)) { - if (this.PutBit(mode is TM_PRED or H_PRED, 156)) - { - this.PutBit(mode == TM_PRED, 128); // TM or HE - } - else - { - this.PutBit(mode == V_PRED, 163); // VE or DC - } + this.PutBit(mode == TM_PRED, 128); // TM or HE + } + else + { + this.PutBit(mode == V_PRED, 163); // VE or DC } + } - public int PutI4Mode(int mode, Span prob) + public int PutI4Mode(int mode, Span prob) + { + if (this.PutBit(mode != B_DC_PRED, prob[0])) { - if (this.PutBit(mode != B_DC_PRED, prob[0])) + if (this.PutBit(mode != B_TM_PRED, prob[1])) { - if (this.PutBit(mode != B_TM_PRED, prob[1])) + if (this.PutBit(mode != B_VE_PRED, prob[2])) { - if (this.PutBit(mode != B_VE_PRED, prob[2])) + if (!this.PutBit(mode >= B_LD_PRED, prob[3])) { - if (!this.PutBit(mode >= B_LD_PRED, prob[3])) + if (this.PutBit(mode != B_HE_PRED, prob[4])) { - if (this.PutBit(mode != B_HE_PRED, prob[4])) - { - this.PutBit(mode != B_RD_PRED, prob[5]); - } + this.PutBit(mode != B_RD_PRED, prob[5]); } - else + } + else + { + if (this.PutBit(mode != B_LD_PRED, prob[6])) { - if (this.PutBit(mode != B_LD_PRED, prob[6])) + if (this.PutBit(mode != B_VL_PRED, prob[7])) { - if (this.PutBit(mode != B_VL_PRED, prob[7])) - { - this.PutBit(mode != B_HD_PRED, prob[8]); - } + this.PutBit(mode != B_HD_PRED, prob[8]); } } } } } - - return mode; } - public void PutUvMode(int uvMode) + return mode; + } + + public void PutUvMode(int uvMode) + { + // DC_PRED + if (this.PutBit(uvMode != DC_PRED, 142)) { - // DC_PRED - if (this.PutBit(uvMode != DC_PRED, 142)) + // V_PRED + if (this.PutBit(uvMode != V_PRED, 114)) { - // V_PRED - if (this.PutBit(uvMode != V_PRED, 114)) - { - // H_PRED - this.PutBit(uvMode != H_PRED, 183); - } + // H_PRED + this.PutBit(uvMode != H_PRED, 183); } } + } - private void PutBits(uint value, int nbBits) + private void PutBits(uint value, int nbBits) + { + for (uint mask = 1u << (nbBits - 1); mask != 0; mask >>= 1) { - for (uint mask = 1u << (nbBits - 1); mask != 0; mask >>= 1) - { - this.PutBitUniform((int)(value & mask)); - } + this.PutBitUniform((int)(value & mask)); } + } - private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob); + private bool PutBit(bool bit, int prob) => this.PutBit(bit ? 1 : 0, prob); - private bool PutBit(int bit, int prob) + private bool PutBit(int bit, int prob) + { + int split = (this.range * prob) >> 8; + if (bit != 0) { - int split = (this.range * prob) >> 8; - if (bit != 0) - { - this.value += split + 1; - this.range -= split + 1; - } - else - { - this.range = split; - } + this.value += split + 1; + this.range -= split + 1; + } + else + { + this.range = split; + } - if (this.range < 127) + if (this.range < 127) + { + // emit 'shift' bits out and renormalize. + int shift = WebpLookupTables.Norm[this.range]; + this.range = WebpLookupTables.NewRange[this.range]; + this.value <<= shift; + this.nbBits += shift; + if (this.nbBits > 0) { - // emit 'shift' bits out and renormalize. - int shift = WebpLookupTables.Norm[this.range]; - this.range = WebpLookupTables.NewRange[this.range]; - this.value <<= shift; - this.nbBits += shift; - if (this.nbBits > 0) - { - this.Flush(); - } + this.Flush(); } + } + + return bit != 0; + } - return bit != 0; + private int PutBitUniform(int bit) + { + int split = this.range >> 1; + if (bit != 0) + { + this.value += split + 1; + this.range -= split + 1; + } + else + { + this.range = split; } - private int PutBitUniform(int bit) + if (this.range < 127) { - int split = this.range >> 1; - if (bit != 0) + this.range = WebpLookupTables.NewRange[this.range]; + this.value <<= 1; + this.nbBits += 1; + if (this.nbBits > 0) { - this.value += split + 1; - this.range -= split + 1; - } - else - { - this.range = split; + this.Flush(); } + } - if (this.range < 127) - { - this.range = WebpLookupTables.NewRange[this.range]; - this.value <<= 1; - this.nbBits += 1; - if (this.nbBits > 0) - { - this.Flush(); - } - } + return bit; + } - return bit; + private void PutSignedBits(int value, int nbBits) + { + if (this.PutBitUniform(value != 0 ? 1 : 0) == 0) + { + return; } - private void PutSignedBits(int value, int nbBits) + if (value < 0) { - if (this.PutBitUniform(value != 0 ? 1 : 0) == 0) - { - return; - } - - if (value < 0) - { - int valueToWrite = (-value << 1) | 1; - this.PutBits((uint)valueToWrite, nbBits + 1); - } - else - { - this.PutBits((uint)(value << 1), nbBits + 1); - } + int valueToWrite = (-value << 1) | 1; + this.PutBits((uint)valueToWrite, nbBits + 1); + } + else + { + this.PutBits((uint)(value << 1), nbBits + 1); } + } - private void Flush() + private void Flush() + { + int s = 8 + this.nbBits; + int bits = this.value >> s; + this.value -= bits << s; + this.nbBits -= 8; + if ((bits & 0xff) != 0xff) { - int s = 8 + this.nbBits; - int bits = this.value >> s; - this.value -= bits << s; - this.nbBits -= 8; - if ((bits & 0xff) != 0xff) - { - uint pos = this.pos; - this.BitWriterResize(this.run + 1); + uint pos = this.pos; + this.BitWriterResize(this.run + 1); - if ((bits & 0x100) != 0) + if ((bits & 0x100) != 0) + { + // overflow -> propagate carry over pending 0xff's + if (pos > 0) { - // overflow -> propagate carry over pending 0xff's - if (pos > 0) - { - this.Buffer[pos - 1]++; - } + this.Buffer[pos - 1]++; } + } - if (this.run > 0) + if (this.run > 0) + { + int value = (bits & 0x100) != 0 ? 0x00 : 0xff; + for (; this.run > 0; --this.run) { - int value = (bits & 0x100) != 0 ? 0x00 : 0xff; - for (; this.run > 0; --this.run) - { - this.Buffer[pos++] = (byte)value; - } + this.Buffer[pos++] = (byte)value; } - - this.Buffer[pos++] = (byte)(bits & 0xff); - this.pos = pos; - } - else - { - this.run++; // Delay writing of bytes 0xff, pending eventual carry. } - } - /// - /// Writes the encoded image to the stream. - /// - /// The stream to write to. - /// The exif profile. - /// The XMP profile. - /// The color profile. - /// The width of the image. - /// The height of the image. - /// Flag indicating, if a alpha channel is present. - /// The alpha channel data. - /// Indicates, if the alpha data is compressed. - public void WriteEncodedImageToStream( - Stream stream, - ExifProfile exifProfile, - XmpProfile xmpProfile, - IccProfile iccProfile, - uint width, - uint height, - bool hasAlpha, - Span alphaData, - bool alphaDataIsCompressed) - { - bool isVp8X = false; - byte[] exifBytes = null; - byte[] xmpBytes = null; - byte[] iccProfileBytes = null; - uint riffSize = 0; - if (exifProfile != null) - { - isVp8X = true; - exifBytes = exifProfile.ToByteArray(); - riffSize += MetadataChunkSize(exifBytes); - } + this.Buffer[pos++] = (byte)(bits & 0xff); + this.pos = pos; + } + else + { + this.run++; // Delay writing of bytes 0xff, pending eventual carry. + } + } - if (xmpProfile != null) - { - isVp8X = true; - xmpBytes = xmpProfile.Data; - riffSize += MetadataChunkSize(xmpBytes); - } + /// + /// Writes the encoded image to the stream. + /// + /// The stream to write to. + /// The exif profile. + /// The XMP profile. + /// The color profile. + /// The width of the image. + /// The height of the image. + /// Flag indicating, if a alpha channel is present. + /// The alpha channel data. + /// Indicates, if the alpha data is compressed. + public void WriteEncodedImageToStream( + Stream stream, + ExifProfile exifProfile, + XmpProfile xmpProfile, + IccProfile iccProfile, + uint width, + uint height, + bool hasAlpha, + Span alphaData, + bool alphaDataIsCompressed) + { + bool isVp8X = false; + byte[] exifBytes = null; + byte[] xmpBytes = null; + byte[] iccProfileBytes = null; + uint riffSize = 0; + if (exifProfile != null) + { + isVp8X = true; + exifBytes = exifProfile.ToByteArray(); + riffSize += MetadataChunkSize(exifBytes); + } - if (iccProfile != null) - { - isVp8X = true; - iccProfileBytes = iccProfile.ToByteArray(); - riffSize += MetadataChunkSize(iccProfileBytes); - } + if (xmpProfile != null) + { + isVp8X = true; + xmpBytes = xmpProfile.Data; + riffSize += MetadataChunkSize(xmpBytes); + } - if (hasAlpha) - { - isVp8X = true; - riffSize += AlphaChunkSize(alphaData); - } + if (iccProfile != null) + { + isVp8X = true; + iccProfileBytes = iccProfile.ToByteArray(); + riffSize += MetadataChunkSize(iccProfileBytes); + } - if (isVp8X) - { - riffSize += ExtendedFileChunkSize; - } + if (hasAlpha) + { + isVp8X = true; + riffSize += AlphaChunkSize(alphaData); + } - this.Finish(); - uint numBytes = (uint)this.NumBytes(); - int mbSize = this.enc.Mbw * this.enc.Mbh; - int expectedSize = mbSize * 7 / 8; + if (isVp8X) + { + riffSize += ExtendedFileChunkSize; + } - var bitWriterPartZero = new Vp8BitWriter(expectedSize); + this.Finish(); + uint numBytes = (uint)this.NumBytes(); + int mbSize = this.enc.Mbw * this.enc.Mbh; + int expectedSize = mbSize * 7 / 8; - // Partition #0 with header and partition sizes. - uint size0 = this.GeneratePartition0(bitWriterPartZero); + var bitWriterPartZero = new Vp8BitWriter(expectedSize); - uint vp8Size = WebpConstants.Vp8FrameHeaderSize + size0; - vp8Size += numBytes; - uint pad = vp8Size & 1; - vp8Size += pad; + // Partition #0 with header and partition sizes. + uint size0 = this.GeneratePartition0(bitWriterPartZero); - // Compute RIFF size. - // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. - riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; + uint vp8Size = WebpConstants.Vp8FrameHeaderSize + size0; + vp8Size += numBytes; + uint pad = vp8Size & 1; + vp8Size += pad; - // Emit headers and partition #0 - this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, iccProfileBytes, hasAlpha, alphaData, alphaDataIsCompressed); - bitWriterPartZero.WriteToStream(stream); + // Compute RIFF size. + // At the minimum it is: "WEBPVP8 nnnn" + VP8 data size. + riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; - // Write the encoded image to the stream. - this.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } + // Emit headers and partition #0 + this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, iccProfileBytes, hasAlpha, alphaData, alphaDataIsCompressed); + bitWriterPartZero.WriteToStream(stream); - if (exifProfile != null) - { - this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); - } + // Write the encoded image to the stream. + this.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); + } - if (xmpProfile != null) - { - this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); - } + if (exifProfile != null) + { + this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); } - private uint GeneratePartition0(Vp8BitWriter bitWriter) + if (xmpProfile != null) { - bitWriter.PutBitUniform(0); // colorspace - bitWriter.PutBitUniform(0); // clamp type + this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); + } + } - this.WriteSegmentHeader(bitWriter); - this.WriteFilterHeader(bitWriter); + private uint GeneratePartition0(Vp8BitWriter bitWriter) + { + bitWriter.PutBitUniform(0); // colorspace + bitWriter.PutBitUniform(0); // clamp type - bitWriter.PutBits(0, 2); + this.WriteSegmentHeader(bitWriter); + this.WriteFilterHeader(bitWriter); - this.WriteQuant(bitWriter); - bitWriter.PutBitUniform(0); - this.WriteProbas(bitWriter); - this.CodeIntraModes(bitWriter); + bitWriter.PutBits(0, 2); - bitWriter.Finish(); + this.WriteQuant(bitWriter); + bitWriter.PutBitUniform(0); + this.WriteProbas(bitWriter); + this.CodeIntraModes(bitWriter); - return (uint)bitWriter.NumBytes(); - } + bitWriter.Finish(); + + return (uint)bitWriter.NumBytes(); + } - private void WriteSegmentHeader(Vp8BitWriter bitWriter) + private void WriteSegmentHeader(Vp8BitWriter bitWriter) + { + Vp8EncSegmentHeader hdr = this.enc.SegmentHeader; + Vp8EncProba proba = this.enc.Proba; + if (bitWriter.PutBitUniform(hdr.NumSegments > 1 ? 1 : 0) != 0) { - Vp8EncSegmentHeader hdr = this.enc.SegmentHeader; - Vp8EncProba proba = this.enc.Proba; - if (bitWriter.PutBitUniform(hdr.NumSegments > 1 ? 1 : 0) != 0) - { - // We always 'update' the quant and filter strength values. - int updateData = 1; - bitWriter.PutBitUniform(hdr.UpdateMap ? 1 : 0); - if (bitWriter.PutBitUniform(updateData) != 0) + // We always 'update' the quant and filter strength values. + int updateData = 1; + bitWriter.PutBitUniform(hdr.UpdateMap ? 1 : 0); + if (bitWriter.PutBitUniform(updateData) != 0) + { + // We always use absolute values, not relative ones. + bitWriter.PutBitUniform(1); // (segment_feature_mode = 1. Paragraph 9.3.) + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) { - // We always use absolute values, not relative ones. - bitWriter.PutBitUniform(1); // (segment_feature_mode = 1. Paragraph 9.3.) - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) - { - bitWriter.PutSignedBits(this.enc.SegmentInfos[s].Quant, 7); - } + bitWriter.PutSignedBits(this.enc.SegmentInfos[s].Quant, 7); + } - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) - { - bitWriter.PutSignedBits(this.enc.SegmentInfos[s].FStrength, 6); - } + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) + { + bitWriter.PutSignedBits(this.enc.SegmentInfos[s].FStrength, 6); } + } - if (hdr.UpdateMap) + if (hdr.UpdateMap) + { + for (int s = 0; s < 3; ++s) { - for (int s = 0; s < 3; ++s) + if (bitWriter.PutBitUniform(proba.Segments[s] != 255 ? 1 : 0) != 0) { - if (bitWriter.PutBitUniform(proba.Segments[s] != 255 ? 1 : 0) != 0) - { - bitWriter.PutBits(proba.Segments[s], 8); - } + bitWriter.PutBits(proba.Segments[s], 8); } } } } + } - private void WriteFilterHeader(Vp8BitWriter bitWriter) + private void WriteFilterHeader(Vp8BitWriter bitWriter) + { + Vp8FilterHeader hdr = this.enc.FilterHeader; + bool useLfDelta = hdr.I4x4LfDelta != 0; + bitWriter.PutBitUniform(hdr.Simple ? 1 : 0); + bitWriter.PutBits((uint)hdr.FilterLevel, 6); + bitWriter.PutBits((uint)hdr.Sharpness, 3); + if (bitWriter.PutBitUniform(useLfDelta ? 1 : 0) != 0) { - Vp8FilterHeader hdr = this.enc.FilterHeader; - bool useLfDelta = hdr.I4x4LfDelta != 0; - bitWriter.PutBitUniform(hdr.Simple ? 1 : 0); - bitWriter.PutBits((uint)hdr.FilterLevel, 6); - bitWriter.PutBits((uint)hdr.Sharpness, 3); - if (bitWriter.PutBitUniform(useLfDelta ? 1 : 0) != 0) + // '0' is the default value for i4x4LfDelta at frame #0. + bool needUpdate = hdr.I4x4LfDelta != 0; + if (bitWriter.PutBitUniform(needUpdate ? 1 : 0) != 0) { - // '0' is the default value for i4x4LfDelta at frame #0. - bool needUpdate = hdr.I4x4LfDelta != 0; - if (bitWriter.PutBitUniform(needUpdate ? 1 : 0) != 0) - { - // we don't use refLfDelta => emit four 0 bits. - bitWriter.PutBits(0, 4); + // we don't use refLfDelta => emit four 0 bits. + bitWriter.PutBits(0, 4); - // we use modeLfDelta for i4x4 - bitWriter.PutSignedBits(hdr.I4x4LfDelta, 6); - bitWriter.PutBits(0, 3); // all others unused. - } + // we use modeLfDelta for i4x4 + bitWriter.PutSignedBits(hdr.I4x4LfDelta, 6); + bitWriter.PutBits(0, 3); // all others unused. } } + } - // Nominal quantization parameters - private void WriteQuant(Vp8BitWriter bitWriter) - { - bitWriter.PutBits((uint)this.enc.BaseQuant, 7); - bitWriter.PutSignedBits(this.enc.DqY1Dc, 4); - bitWriter.PutSignedBits(this.enc.DqY2Dc, 4); - bitWriter.PutSignedBits(this.enc.DqY2Ac, 4); - bitWriter.PutSignedBits(this.enc.DqUvDc, 4); - bitWriter.PutSignedBits(this.enc.DqUvAc, 4); - } + // Nominal quantization parameters + private void WriteQuant(Vp8BitWriter bitWriter) + { + bitWriter.PutBits((uint)this.enc.BaseQuant, 7); + bitWriter.PutSignedBits(this.enc.DqY1Dc, 4); + bitWriter.PutSignedBits(this.enc.DqY2Dc, 4); + bitWriter.PutSignedBits(this.enc.DqY2Ac, 4); + bitWriter.PutSignedBits(this.enc.DqUvDc, 4); + bitWriter.PutSignedBits(this.enc.DqUvAc, 4); + } - private void WriteProbas(Vp8BitWriter bitWriter) + private void WriteProbas(Vp8BitWriter bitWriter) + { + Vp8EncProba probas = this.enc.Proba; + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - Vp8EncProba probas = this.enc.Proba; - for (int t = 0; t < WebpConstants.NumTypes; ++t) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int b = 0; b < WebpConstants.NumBands; ++b) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int c = 0; c < WebpConstants.NumCtx; ++c) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - for (int p = 0; p < WebpConstants.NumProbas; ++p) + byte p0 = probas.Coeffs[t][b].Probabilities[c].Probabilities[p]; + bool update = p0 != WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; + if (bitWriter.PutBit(update, WebpLookupTables.CoeffsUpdateProba[t, b, c, p])) { - byte p0 = probas.Coeffs[t][b].Probabilities[c].Probabilities[p]; - bool update = p0 != WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - if (bitWriter.PutBit(update, WebpLookupTables.CoeffsUpdateProba[t, b, c, p])) - { - bitWriter.PutBits(p0, 8); - } + bitWriter.PutBits(p0, 8); } } } } - - if (bitWriter.PutBitUniform(probas.UseSkipProba ? 1 : 0) != 0) - { - bitWriter.PutBits(probas.SkipProba, 8); - } } - // Writes the partition #0 modes (that is: all intra modes) - private void CodeIntraModes(Vp8BitWriter bitWriter) + if (bitWriter.PutBitUniform(probas.UseSkipProba ? 1 : 0) != 0) { - var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.TopDerr, this.enc.Mbw, this.enc.Mbh); - int predsWidth = this.enc.PredsWidth; - - do - { - Vp8MacroBlockInfo mb = it.CurrentMacroBlockInfo; - int predIdx = it.PredIdx; - Span preds = it.Preds.AsSpan(predIdx); - if (this.enc.SegmentHeader.UpdateMap) - { - bitWriter.PutSegment(mb.Segment, this.enc.Proba.Segments); - } + bitWriter.PutBits(probas.SkipProba, 8); + } + } - if (this.enc.Proba.UseSkipProba) - { - bitWriter.PutBit(mb.Skip, this.enc.Proba.SkipProba); - } + // Writes the partition #0 modes (that is: all intra modes) + private void CodeIntraModes(Vp8BitWriter bitWriter) + { + var it = new Vp8EncIterator(this.enc.YTop, this.enc.UvTop, this.enc.Nz, this.enc.MbInfo, this.enc.Preds, this.enc.TopDerr, this.enc.Mbw, this.enc.Mbh); + int predsWidth = this.enc.PredsWidth; - if (bitWriter.PutBit(mb.MacroBlockType != 0, 145)) - { - // i16x16 - bitWriter.PutI16Mode(preds[0]); - } - else - { - Span topPred = it.Preds.AsSpan(predIdx - predsWidth); - for (int y = 0; y < 4; y++) - { - int left = it.Preds[predIdx - 1]; - for (int x = 0; x < 4; x++) - { - byte[] probas = WebpLookupTables.ModesProba[topPred[x], left]; - left = bitWriter.PutI4Mode(it.Preds[predIdx + x], probas); - } + do + { + Vp8MacroBlockInfo mb = it.CurrentMacroBlockInfo; + int predIdx = it.PredIdx; + Span preds = it.Preds.AsSpan(predIdx); + if (this.enc.SegmentHeader.UpdateMap) + { + bitWriter.PutSegment(mb.Segment, this.enc.Proba.Segments); + } - topPred = it.Preds.AsSpan(predIdx); - predIdx += predsWidth; - } - } + if (this.enc.Proba.UseSkipProba) + { + bitWriter.PutBit(mb.Skip, this.enc.Proba.SkipProba); + } - bitWriter.PutUvMode(mb.UvMode); + if (bitWriter.PutBit(mb.MacroBlockType != 0, 145)) + { + // i16x16 + bitWriter.PutI16Mode(preds[0]); } - while (it.Next()); - } - - private void WriteWebpHeaders( - Stream stream, - uint size0, - uint vp8Size, - uint riffSize, - bool isVp8X, - uint width, - uint height, - ExifProfile exifProfile, - XmpProfile xmpProfile, - byte[] iccProfileBytes, - bool hasAlpha, - Span alphaData, - bool alphaDataIsCompressed) - { - this.WriteRiffHeader(stream, riffSize); - - // Write VP8X, header if necessary. - if (isVp8X) + else { - this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfileBytes, width, height, hasAlpha); - - if (iccProfileBytes != null) + Span topPred = it.Preds.AsSpan(predIdx - predsWidth); + for (int y = 0; y < 4; y++) { - this.WriteColorProfile(stream, iccProfileBytes); - } + int left = it.Preds[predIdx - 1]; + for (int x = 0; x < 4; x++) + { + byte[] probas = WebpLookupTables.ModesProba[topPred[x], left]; + left = bitWriter.PutI4Mode(it.Preds[predIdx + x], probas); + } - if (hasAlpha) - { - this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed); + topPred = it.Preds.AsSpan(predIdx); + predIdx += predsWidth; } } - this.WriteVp8Header(stream, vp8Size); - this.WriteFrameHeader(stream, size0); + bitWriter.PutUvMode(mb.UvMode); } + while (it.Next()); + } + + private void WriteWebpHeaders( + Stream stream, + uint size0, + uint vp8Size, + uint riffSize, + bool isVp8X, + uint width, + uint height, + ExifProfile exifProfile, + XmpProfile xmpProfile, + byte[] iccProfileBytes, + bool hasAlpha, + Span alphaData, + bool alphaDataIsCompressed) + { + this.WriteRiffHeader(stream, riffSize); - private void WriteVp8Header(Stream stream, uint size) + // Write VP8X, header if necessary. + if (isVp8X) { - Span vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize]; + this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccProfileBytes, width, height, hasAlpha); - WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader); - BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader[4..], size); + if (iccProfileBytes != null) + { + this.WriteColorProfile(stream, iccProfileBytes); + } - stream.Write(vp8ChunkHeader); + if (hasAlpha) + { + this.WriteAlphaChunk(stream, alphaData, alphaDataIsCompressed); + } } - private void WriteFrameHeader(Stream stream, uint size0) - { - uint profile = 0; - int width = this.enc.Width; - int height = this.enc.Height; - byte[] vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize]; - - // Paragraph 9.1. - uint bits = 0 // keyframe (1b) - | (profile << 1) // profile (3b) - | (1 << 4) // visible (1b) - | (size0 << 5); // partition length (19b) + this.WriteVp8Header(stream, vp8Size); + this.WriteFrameHeader(stream, size0); + } - vp8FrameHeader[0] = (byte)((bits >> 0) & 0xff); - vp8FrameHeader[1] = (byte)((bits >> 8) & 0xff); - vp8FrameHeader[2] = (byte)((bits >> 16) & 0xff); + private void WriteVp8Header(Stream stream, uint size) + { + Span vp8ChunkHeader = stackalloc byte[WebpConstants.ChunkHeaderSize]; - // signature - vp8FrameHeader[3] = WebpConstants.Vp8HeaderMagicBytes[0]; - vp8FrameHeader[4] = WebpConstants.Vp8HeaderMagicBytes[1]; - vp8FrameHeader[5] = WebpConstants.Vp8HeaderMagicBytes[2]; + WebpConstants.Vp8MagicBytes.AsSpan().CopyTo(vp8ChunkHeader); + BinaryPrimitives.WriteUInt32LittleEndian(vp8ChunkHeader[4..], size); - // dimensions - vp8FrameHeader[6] = (byte)(width & 0xff); - vp8FrameHeader[7] = (byte)(width >> 8); - vp8FrameHeader[8] = (byte)(height & 0xff); - vp8FrameHeader[9] = (byte)(height >> 8); + stream.Write(vp8ChunkHeader); + } - stream.Write(vp8FrameHeader); - } + private void WriteFrameHeader(Stream stream, uint size0) + { + uint profile = 0; + int width = this.enc.Width; + int height = this.enc.Height; + byte[] vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize]; + + // Paragraph 9.1. + uint bits = 0 // keyframe (1b) + | (profile << 1) // profile (3b) + | (1 << 4) // visible (1b) + | (size0 << 5); // partition length (19b) + + vp8FrameHeader[0] = (byte)((bits >> 0) & 0xff); + vp8FrameHeader[1] = (byte)((bits >> 8) & 0xff); + vp8FrameHeader[2] = (byte)((bits >> 16) & 0xff); + + // signature + vp8FrameHeader[3] = WebpConstants.Vp8HeaderMagicBytes[0]; + vp8FrameHeader[4] = WebpConstants.Vp8HeaderMagicBytes[1]; + vp8FrameHeader[5] = WebpConstants.Vp8HeaderMagicBytes[2]; + + // dimensions + vp8FrameHeader[6] = (byte)(width & 0xff); + vp8FrameHeader[7] = (byte)(width >> 8); + vp8FrameHeader[8] = (byte)(height & 0xff); + vp8FrameHeader[9] = (byte)(height >> 8); + + stream.Write(vp8FrameHeader); } } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index cb4d6d6543..5fbbeed114 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -1,252 +1,249 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Formats.Webp.BitWriter +namespace SixLabors.ImageSharp.Formats.Webp.BitWriter; + +/// +/// A bit writer for writing lossless webp streams. +/// +internal class Vp8LBitWriter : BitWriterBase { /// - /// A bit writer for writing lossless webp streams. + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBuffer = new byte[8]; + + /// + /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. + /// + private const int MinExtraSize = 32768; + + private const int WriterBytes = 4; + + private const int WriterBits = 32; + + /// + /// Bit accumulator. + /// + private ulong bits; + + /// + /// Number of bits used in accumulator. /// - internal class Vp8LBitWriter : BitWriterBase + private int used; + + /// + /// Current write position. + /// + private int cur; + + /// + /// Initializes a new instance of the class. + /// + /// The expected size in bytes. + public Vp8LBitWriter(int expectedSize) + : base(expectedSize) { - /// - /// A scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBuffer = new byte[8]; - - /// - /// This is the minimum amount of size the memory buffer is guaranteed to grow when extra space is needed. - /// - private const int MinExtraSize = 32768; - - private const int WriterBytes = 4; - - private const int WriterBits = 32; - - /// - /// Bit accumulator. - /// - private ulong bits; - - /// - /// Number of bits used in accumulator. - /// - private int used; - - /// - /// Current write position. - /// - private int cur; - - /// - /// Initializes a new instance of the class. - /// - /// The expected size in bytes. - public Vp8LBitWriter(int expectedSize) - : base(expectedSize) - { - } + } - /// - /// Initializes a new instance of the class. - /// Used internally for cloning. - /// - private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) - : base(buffer) - { - this.bits = bits; - this.used = used; - this.cur = cur; - } + /// + /// Initializes a new instance of the class. + /// Used internally for cloning. + /// + private Vp8LBitWriter(byte[] buffer, ulong bits, int used, int cur) + : base(buffer) + { + this.bits = bits; + this.used = used; + this.cur = cur; + } - /// - /// This function writes bits into bytes in increasing addresses (little endian), - /// and within a byte least-significant-bit first. This function can write up to 32 bits in one go. - /// - public void PutBits(uint bits, int nBits) + /// + /// This function writes bits into bytes in increasing addresses (little endian), + /// and within a byte least-significant-bit first. This function can write up to 32 bits in one go. + /// + public void PutBits(uint bits, int nBits) + { + if (nBits > 0) { - if (nBits > 0) + if (this.used >= 32) { - if (this.used >= 32) - { - this.PutBitsFlushBits(); - } - - this.bits |= (ulong)bits << this.used; - this.used += nBits; + this.PutBitsFlushBits(); } - } - public void Reset(Vp8LBitWriter bwInit) - { - this.bits = bwInit.bits; - this.used = bwInit.used; - this.cur = bwInit.cur; + this.bits |= (ulong)bits << this.used; + this.used += nBits; } + } - public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) - { - int depth = code.CodeLengths[codeIndex]; - int symbol = code.Codes[codeIndex]; - this.PutBits((uint)symbol, depth); - } + public void Reset(Vp8LBitWriter bwInit) + { + this.bits = bwInit.bits; + this.used = bwInit.used; + this.cur = bwInit.cur; + } + + public void WriteHuffmanCode(HuffmanTreeCode code, int codeIndex) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)symbol, depth); + } + + public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits) + { + int depth = code.CodeLengths[codeIndex]; + int symbol = code.Codes[codeIndex]; + this.PutBits((uint)((bits << depth) | symbol), depth + nBits); + } + + /// + public override int NumBytes() => this.cur + ((this.used + 7) >> 3); + + public Vp8LBitWriter Clone() + { + byte[] clonedBuffer = new byte[this.Buffer.Length]; + System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur); + return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); + } - public void WriteHuffmanCodeWithExtraBits(HuffmanTreeCode code, int codeIndex, int bits, int nBits) + /// + public override void Finish() + { + this.BitWriterResize((this.used + 7) >> 3); + while (this.used > 0) { - int depth = code.CodeLengths[codeIndex]; - int symbol = code.Codes[codeIndex]; - this.PutBits((uint)((bits << depth) | symbol), depth + nBits); + this.Buffer[this.cur++] = (byte)this.bits; + this.bits >>= 8; + this.used -= 8; } - /// - public override int NumBytes() => this.cur + ((this.used + 7) >> 3); + this.used = 0; + } - public Vp8LBitWriter Clone() + /// + /// Writes the encoded image to the stream. + /// + /// The stream to write to. + /// The exif profile. + /// The XMP profile. + /// The color profile. + /// The width of the image. + /// The height of the image. + /// Flag indicating, if a alpha channel is present. + public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, uint width, uint height, bool hasAlpha) + { + bool isVp8X = false; + byte[] exifBytes = null; + byte[] xmpBytes = null; + byte[] iccBytes = null; + uint riffSize = 0; + if (exifProfile != null) { - byte[] clonedBuffer = new byte[this.Buffer.Length]; - System.Buffer.BlockCopy(this.Buffer, 0, clonedBuffer, 0, this.cur); - return new Vp8LBitWriter(clonedBuffer, this.bits, this.used, this.cur); + isVp8X = true; + exifBytes = exifProfile.ToByteArray(); + riffSize += MetadataChunkSize(exifBytes); } - /// - public override void Finish() + if (xmpProfile != null) { - this.BitWriterResize((this.used + 7) >> 3); - while (this.used > 0) - { - this.Buffer[this.cur++] = (byte)this.bits; - this.bits >>= 8; - this.used -= 8; - } - - this.used = 0; + isVp8X = true; + xmpBytes = xmpProfile.Data; + riffSize += MetadataChunkSize(xmpBytes); } - /// - /// Writes the encoded image to the stream. - /// - /// The stream to write to. - /// The exif profile. - /// The XMP profile. - /// The color profile. - /// The width of the image. - /// The height of the image. - /// Flag indicating, if a alpha channel is present. - public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, IccProfile iccProfile, uint width, uint height, bool hasAlpha) + if (iccProfile != null) { - bool isVp8X = false; - byte[] exifBytes = null; - byte[] xmpBytes = null; - byte[] iccBytes = null; - uint riffSize = 0; - if (exifProfile != null) - { - isVp8X = true; - exifBytes = exifProfile.ToByteArray(); - riffSize += MetadataChunkSize(exifBytes); - } - - if (xmpProfile != null) - { - isVp8X = true; - xmpBytes = xmpProfile.Data; - riffSize += MetadataChunkSize(xmpBytes); - } + isVp8X = true; + iccBytes = iccProfile.ToByteArray(); + riffSize += MetadataChunkSize(iccBytes); + } - if (iccProfile != null) - { - isVp8X = true; - iccBytes = iccProfile.ToByteArray(); - riffSize += MetadataChunkSize(iccBytes); - } + if (isVp8X) + { + riffSize += ExtendedFileChunkSize; + } - if (isVp8X) - { - riffSize += ExtendedFileChunkSize; - } + this.Finish(); + uint size = (uint)this.NumBytes(); + size++; // One byte extra for the VP8L signature. - this.Finish(); - uint size = (uint)this.NumBytes(); - size++; // One byte extra for the VP8L signature. + // Write RIFF header. + uint pad = size & 1; + riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad; + this.WriteRiffHeader(stream, riffSize); - // Write RIFF header. - uint pad = size & 1; - riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + size + pad; - this.WriteRiffHeader(stream, riffSize); + // Write VP8X, header if necessary. + if (isVp8X) + { + this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccBytes, width, height, hasAlpha); - // Write VP8X, header if necessary. - if (isVp8X) + if (iccBytes != null) { - this.WriteVp8XHeader(stream, exifProfile, xmpProfile, iccBytes, width, height, hasAlpha); - - if (iccBytes != null) - { - this.WriteColorProfile(stream, iccBytes); - } + this.WriteColorProfile(stream, iccBytes); } + } - // Write magic bytes indicating its a lossless webp. - stream.Write(WebpConstants.Vp8LMagicBytes); - - // Write Vp8 Header. - BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size); - stream.Write(this.scratchBuffer.AsSpan(0, 4)); - stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); - - // Write the encoded bytes of the image to the stream. - this.WriteToStream(stream); - if (pad == 1) - { - stream.WriteByte(0); - } + // Write magic bytes indicating its a lossless webp. + stream.Write(WebpConstants.Vp8LMagicBytes); - if (exifProfile != null) - { - this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); - } + // Write Vp8 Header. + BinaryPrimitives.WriteUInt32LittleEndian(this.scratchBuffer, size); + stream.Write(this.scratchBuffer.AsSpan(0, 4)); + stream.WriteByte(WebpConstants.Vp8LHeaderMagicByte); - if (xmpProfile != null) - { - this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); - } + // Write the encoded bytes of the image to the stream. + this.WriteToStream(stream); + if (pad == 1) + { + stream.WriteByte(0); } - /// - /// Internal function for PutBits flushing 32 bits from the written state. - /// - private void PutBitsFlushBits() + if (exifProfile != null) { - // If needed, make some room by flushing some bits out. - if (this.cur + WriterBytes > this.Buffer.Length) - { - int extraSize = this.Buffer.Length - this.cur + MinExtraSize; - this.BitWriterResize(extraSize); - } - - BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits); - this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur)); + this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); + } - this.cur += WriterBytes; - this.bits >>= WriterBits; - this.used -= WriterBits; + if (xmpProfile != null) + { + this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); } + } - /// - /// Resizes the buffer to write to. - /// - /// The extra size in bytes needed. - public override void BitWriterResize(int extraSize) + /// + /// Internal function for PutBits flushing 32 bits from the written state. + /// + private void PutBitsFlushBits() + { + // If needed, make some room by flushing some bits out. + if (this.cur + WriterBytes > this.Buffer.Length) { - int maxBytes = this.Buffer.Length + this.Buffer.Length; - int sizeRequired = this.cur + extraSize; - this.ResizeBuffer(maxBytes, sizeRequired); + int extraSize = this.Buffer.Length - this.cur + MinExtraSize; + this.BitWriterResize(extraSize); } + + BinaryPrimitives.WriteUInt64LittleEndian(this.scratchBuffer, this.bits); + this.scratchBuffer.AsSpan(0, 4).CopyTo(this.Buffer.AsSpan(this.cur)); + + this.cur += WriterBytes; + this.bits >>= WriterBits; + this.used -= WriterBits; + } + + /// + /// Resizes the buffer to write to. + /// + /// The extra size in bytes needed. + public override void BitWriterResize(int extraSize) + { + int maxBytes = this.Buffer.Length + this.Buffer.Length; + int sizeRequired = this.cur + extraSize; + this.ResizeBuffer(maxBytes, sizeRequired); } } diff --git a/src/ImageSharp/Formats/Webp/EntropyIx.cs b/src/ImageSharp/Formats/Webp/EntropyIx.cs index aacec88a66..e1dcf218ad 100644 --- a/src/ImageSharp/Formats/Webp/EntropyIx.cs +++ b/src/ImageSharp/Formats/Webp/EntropyIx.cs @@ -1,25 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// These five modes are evaluated and their respective entropy is computed. +/// +internal enum EntropyIx : byte { - /// - /// These five modes are evaluated and their respective entropy is computed. - /// - internal enum EntropyIx : byte - { - Direct = 0, + Direct = 0, - Spatial = 1, + Spatial = 1, - SubGreen = 2, + SubGreen = 2, - SpatialSubGreen = 3, + SpatialSubGreen = 3, - Palette = 4, + Palette = 4, - PaletteAndSpatial = 5, + PaletteAndSpatial = 5, - NumEntropyIx = 6 - } + NumEntropyIx = 6 } diff --git a/src/ImageSharp/Formats/Webp/HistoIx.cs b/src/ImageSharp/Formats/Webp/HistoIx.cs index f239e3aaff..1d84494fd9 100644 --- a/src/ImageSharp/Formats/Webp/HistoIx.cs +++ b/src/ImageSharp/Formats/Webp/HistoIx.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal enum HistoIx : byte { - internal enum HistoIx : byte - { - HistoAlpha = 0, + HistoAlpha = 0, - HistoAlphaPred, + HistoAlphaPred, - HistoGreen, + HistoGreen, - HistoGreenPred, + HistoGreenPred, - HistoRed, + HistoRed, - HistoRedPred, + HistoRedPred, - HistoBlue, + HistoBlue, - HistoBluePred, + HistoBluePred, - HistoRedSubGreen, + HistoRedSubGreen, - HistoRedPredSubGreen, + HistoRedPredSubGreen, - HistoBlueSubGreen, + HistoBlueSubGreen, - HistoBluePredSubGreen, + HistoBluePredSubGreen, - HistoPalette, + HistoPalette, - HistoTotal - } + HistoTotal } diff --git a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs index a6b75005c7..bc316d08c7 100644 --- a/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Webp/IWebpEncoderOptions.cs @@ -1,80 +1,79 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Configuration options for use during webp encoding. +/// +internal interface IWebpEncoderOptions { /// - /// Configuration options for use during webp encoding. + /// Gets the webp file format used. Either lossless or lossy. + /// Defaults to lossy. /// - internal interface IWebpEncoderOptions - { - /// - /// Gets the webp file format used. Either lossless or lossy. - /// Defaults to lossy. - /// - WebpFileFormatType? FileFormat { get; } + WebpFileFormatType? FileFormat { get; } - /// - /// Gets the compression quality. Between 0 and 100. - /// For lossy, 0 gives the smallest size and 100 the largest. For lossless, - /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger - /// files compared to the slowest, but best, 100. - /// Defaults to 75. - /// - int Quality { get; } + /// + /// Gets the compression quality. Between 0 and 100. + /// For lossy, 0 gives the smallest size and 100 the largest. For lossless, + /// this parameter is the amount of effort put into the compression: 0 is the fastest but gives larger + /// files compared to the slowest, but best, 100. + /// Defaults to 75. + /// + int Quality { get; } - /// - /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). - /// Defaults to 4. - /// - WebpEncodingMethod Method { get; } + /// + /// Gets the encoding method to use. Its a quality/speed trade-off (0=fast, 6=slower-better). + /// Defaults to 4. + /// + WebpEncodingMethod Method { get; } - /// - /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. - /// Defaults to true. - /// - bool UseAlphaCompression { get; } + /// + /// Gets a value indicating whether the alpha plane should be compressed with Webp lossless format. + /// Defaults to true. + /// + bool UseAlphaCompression { get; } - /// - /// Gets the number of entropy-analysis passes (in [1..10]). - /// Defaults to 1. - /// - int EntropyPasses { get; } + /// + /// Gets the number of entropy-analysis passes (in [1..10]). + /// Defaults to 1. + /// + int EntropyPasses { get; } - /// - /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms - /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits. - /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect). - /// Defaults to 50. - /// - int SpatialNoiseShaping { get; } + /// + /// Gets the amplitude of the spatial noise shaping. Spatial noise shaping (or sns for short) refers to a general collection of built-in algorithms + /// used to decide which area of the picture should use relatively less bits, and where else to better transfer these bits. + /// The possible range goes from 0 (algorithm is off) to 100 (the maximal effect). + /// Defaults to 50. + /// + int SpatialNoiseShaping { get; } - /// - /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture. - /// The higher the value the smoother the picture will appear. - /// Typical values are usually in the range of 20 to 50. - /// Defaults to 60. - /// - int FilterStrength { get; } + /// + /// Gets the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// A value of 0 will turn off any filtering. Higher value will increase the strength of the filtering process applied after decoding the picture. + /// The higher the value the smoother the picture will appear. + /// Typical values are usually in the range of 20 to 50. + /// Defaults to 60. + /// + int FilterStrength { get; } - /// - /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible - /// RGB information for better compression. - /// The default value is Clear. - /// - WebpTransparentColorMode TransparentColorMode { get; } + /// + /// Gets a value indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. + /// The default value is Clear. + /// + WebpTransparentColorMode TransparentColorMode { get; } - /// - /// Gets a value indicating whether near lossless mode should be used. - /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. - /// - bool NearLossless { get; } + /// + /// Gets a value indicating whether near lossless mode should be used. + /// This option adjusts pixel values to help compressibility, but has minimal impact on the visual quality. + /// + bool NearLossless { get; } - /// - /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results. - /// - int NearLosslessQuality { get; } - } + /// + /// Gets the quality of near-lossless image preprocessing. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// The typical value is around 60. Note that lossy with -q 100 can at times yield better results. + /// + int NearLosslessQuality { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 88d6261cfe..09908f6c38 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -1,869 +1,866 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal static class BackwardReferenceEncoder { - internal static class BackwardReferenceEncoder + /// + /// Maximum bit length. + /// + public const int MaxLengthBits = 12; + + private const float MaxEntropy = 1e30f; + + private const int WindowOffsetsSizeMax = 32; + + /// + /// We want the max value to be attainable and stored in MaxLengthBits bits. + /// + public const int MaxLength = (1 << MaxLengthBits) - 1; + + /// + /// Minimum number of pixels for which it is cheaper to encode a + /// distance + length instead of each pixel as a literal. + /// + private const int MinLength = 4; + + /// + /// Evaluates best possible backward references for specified quality. The input cacheBits to 'GetBackwardReferences' + /// sets the maximum cache bits to use (passing 0 implies disabling the local color cache). + /// The optimal cache bits is evaluated and set for the cacheBits parameter. + /// The return value is the pointer to the best of the two backward refs viz, refs[0] or refs[1]. + /// + public static Vp8LBackwardRefs GetBackwardReferences( + int width, + int height, + ReadOnlySpan bgra, + int quality, + int lz77TypesToTry, + ref int cacheBits, + MemoryAllocator memoryAllocator, + Vp8LHashChain hashChain, + Vp8LBackwardRefs best, + Vp8LBackwardRefs worst) { - /// - /// Maximum bit length. - /// - public const int MaxLengthBits = 12; - - private const float MaxEntropy = 1e30f; - - private const int WindowOffsetsSizeMax = 32; - - /// - /// We want the max value to be attainable and stored in MaxLengthBits bits. - /// - public const int MaxLength = (1 << MaxLengthBits) - 1; - - /// - /// Minimum number of pixels for which it is cheaper to encode a - /// distance + length instead of each pixel as a literal. - /// - private const int MinLength = 4; - - /// - /// Evaluates best possible backward references for specified quality. The input cacheBits to 'GetBackwardReferences' - /// sets the maximum cache bits to use (passing 0 implies disabling the local color cache). - /// The optimal cache bits is evaluated and set for the cacheBits parameter. - /// The return value is the pointer to the best of the two backward refs viz, refs[0] or refs[1]. - /// - public static Vp8LBackwardRefs GetBackwardReferences( - int width, - int height, - ReadOnlySpan bgra, - int quality, - int lz77TypesToTry, - ref int cacheBits, - MemoryAllocator memoryAllocator, - Vp8LHashChain hashChain, - Vp8LBackwardRefs best, - Vp8LBackwardRefs worst) + int lz77TypeBest = 0; + double bitCostBest = -1; + int cacheBitsInitial = cacheBits; + Vp8LHashChain hashChainBox = null; + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) { - int lz77TypeBest = 0; - double bitCostBest = -1; - int cacheBitsInitial = cacheBits; - Vp8LHashChain hashChainBox = null; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - for (int lz77Type = 1; lz77TypesToTry > 0; lz77TypesToTry &= ~lz77Type, lz77Type <<= 1) - { - int cacheBitsTmp = cacheBitsInitial; - if ((lz77TypesToTry & lz77Type) == 0) - { - continue; - } + int cacheBitsTmp = cacheBitsInitial; + if ((lz77TypesToTry & lz77Type) == 0) + { + continue; + } - switch ((Vp8LLz77Type)lz77Type) - { - case Vp8LLz77Type.Lz77Rle: - BackwardReferencesRle(width, height, bgra, 0, worst); - break; - case Vp8LLz77Type.Lz77Standard: - // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache is not that different in practice. - BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); - break; - case Vp8LLz77Type.Lz77Box: - hashChainBox = new Vp8LHashChain(memoryAllocator, width * height); - BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); - break; - } + switch ((Vp8LLz77Type)lz77Type) + { + case Vp8LLz77Type.Lz77Rle: + BackwardReferencesRle(width, height, bgra, 0, worst); + break; + case Vp8LLz77Type.Lz77Standard: + // Compute LZ77 with no cache (0 bits), as the ideal LZ77 with a color cache is not that different in practice. + BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); + break; + case Vp8LLz77Type.Lz77Box: + hashChainBox = new Vp8LHashChain(memoryAllocator, width * height); + BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); + break; + } - // Next, try with a color cache and update the references. - cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); - if (cacheBitsTmp > 0) - { - BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst); - } + // Next, try with a color cache and update the references. + cacheBitsTmp = CalculateBestCacheSize(bgra, quality, worst, cacheBitsTmp); + if (cacheBitsTmp > 0) + { + BackwardRefsWithLocalCache(bgra, cacheBitsTmp, worst); + } - // Keep the best backward references. - var histo = new Vp8LHistogram(worst, cacheBitsTmp); - double bitCost = histo.EstimateBits(stats, bitsEntropy); + // Keep the best backward references. + var histo = new Vp8LHistogram(worst, cacheBitsTmp); + double bitCost = histo.EstimateBits(stats, bitsEntropy); - if (lz77TypeBest == 0 || bitCost < bitCostBest) - { - Vp8LBackwardRefs tmp = worst; - worst = best; - best = tmp; - bitCostBest = bitCost; - cacheBits = cacheBitsTmp; - lz77TypeBest = lz77Type; - } + if (lz77TypeBest == 0 || bitCost < bitCostBest) + { + Vp8LBackwardRefs tmp = worst; + worst = best; + best = tmp; + bitCostBest = bitCost; + cacheBits = cacheBitsTmp; + lz77TypeBest = lz77Type; } + } - // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). - if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) + // Improve on simple LZ77 but only for high quality (TraceBackwards is costly). + if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) + { + Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; + BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); + var histo = new Vp8LHistogram(worst, cacheBits); + double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); + if (bitCostTrace < bitCostBest) { - Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; - BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); - var histo = new Vp8LHistogram(worst, cacheBits); - double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); - if (bitCostTrace < bitCostBest) - { - best = worst; - } + best = worst; } + } + + BackwardReferences2DLocality(width, best); - BackwardReferences2DLocality(width, best); + hashChainBox?.Dispose(); - hashChainBox?.Dispose(); + return best; + } - return best; + /// + /// Evaluate optimal cache bits for the local color cache. + /// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). + /// The local color cache is also disabled for the lower (smaller then 25) quality. + /// + /// Best cache size. + private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) + { + int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; + if (cacheBitsMax == 0) + { + // Local color cache is disabled. + return 0; } - /// - /// Evaluate optimal cache bits for the local color cache. - /// The input bestCacheBits sets the maximum cache bits to use (passing 0 implies disabling the local color cache). - /// The local color cache is also disabled for the lower (smaller then 25) quality. - /// - /// Best cache size. - private static int CalculateBestCacheSize(ReadOnlySpan bgra, int quality, Vp8LBackwardRefs refs, int bestCacheBits) + double entropyMin = MaxEntropy; + int pos = 0; + var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; + var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1]; + for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) { - int cacheBitsMax = quality <= 25 ? 0 : bestCacheBits; - if (cacheBitsMax == 0) - { - // Local color cache is disabled. - return 0; - } + histos[i] = new Vp8LHistogram(paletteCodeBits: i); + colorCache[i] = new ColorCache(); + colorCache[i].Init(i); + } - double entropyMin = MaxEntropy; - int pos = 0; - var colorCache = new ColorCache[WebpConstants.MaxColorCacheBits + 1]; - var histos = new Vp8LHistogram[WebpConstants.MaxColorCacheBits + 1]; - for (int i = 0; i <= WebpConstants.MaxColorCacheBits; i++) + // Find the cacheBits giving the lowest entropy. + for (int idx = 0; idx < refs.Refs.Count; idx++) + { + PixOrCopy v = refs.Refs[idx]; + if (v.IsLiteral()) { - histos[i] = new Vp8LHistogram(paletteCodeBits: i); - colorCache[i] = new ColorCache(); - colorCache[i].Init(i); - } + uint pix = bgra[pos++]; + uint a = (pix >> 24) & 0xff; + uint r = (pix >> 16) & 0xff; + uint g = (pix >> 8) & 0xff; + uint b = (pix >> 0) & 0xff; - // Find the cacheBits giving the lowest entropy. - for (int idx = 0; idx < refs.Refs.Count; idx++) - { - PixOrCopy v = refs.Refs[idx]; - if (v.IsLiteral()) + // The keys of the caches can be derived from the longest one. + int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); + + // Do not use the color cache for cacheBits = 0. + ++histos[0].Blue[b]; + ++histos[0].Literal[g]; + ++histos[0].Red[r]; + ++histos[0].Alpha[a]; + + // Deal with cacheBits > 0. + for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) { - uint pix = bgra[pos++]; - uint a = (pix >> 24) & 0xff; - uint r = (pix >> 16) & 0xff; - uint g = (pix >> 8) & 0xff; - uint b = (pix >> 0) & 0xff; - - // The keys of the caches can be derived from the longest one. - int key = ColorCache.HashPix(pix, 32 - cacheBitsMax); - - // Do not use the color cache for cacheBits = 0. - ++histos[0].Blue[b]; - ++histos[0].Literal[g]; - ++histos[0].Red[r]; - ++histos[0].Alpha[a]; - - // Deal with cacheBits > 0. - for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) + if (colorCache[i].Lookup(key) == pix) { - if (colorCache[i].Lookup(key) == pix) - { - ++histos[i].Literal[WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + key]; - } - else - { - colorCache[i].Set((uint)key, pix); - ++histos[i].Blue[b]; - ++histos[i].Literal[g]; - ++histos[i].Red[r]; - ++histos[i].Alpha[a]; - } + ++histos[i].Literal[WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + key]; } - } - else - { - // We should compute the contribution of the (distance, length) - // histograms but those are the same independently from the cache size. - // As those constant contributions are in the end added to the other - // histogram contributions, we can ignore them, except for the length - // prefix that is part of the literal_ histogram. - int len = v.Len; - uint bgraPrev = bgra[pos] ^ 0xffffffffu; - - int extraBits = 0, extraBitsValue = 0; - int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); - for (int i = 0; i <= cacheBitsMax; i++) + else { - ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; + colorCache[i].Set((uint)key, pix); + ++histos[i].Blue[b]; + ++histos[i].Literal[g]; + ++histos[i].Red[r]; + ++histos[i].Alpha[a]; } + } + } + else + { + // We should compute the contribution of the (distance, length) + // histograms but those are the same independently from the cache size. + // As those constant contributions are in the end added to the other + // histogram contributions, we can ignore them, except for the length + // prefix that is part of the literal_ histogram. + int len = v.Len; + uint bgraPrev = bgra[pos] ^ 0xffffffffu; - // Update the color caches. - do + int extraBits = 0, extraBitsValue = 0; + int code = LosslessUtils.PrefixEncode(len, ref extraBits, ref extraBitsValue); + for (int i = 0; i <= cacheBitsMax; i++) + { + ++histos[i].Literal[WebpConstants.NumLiteralCodes + code]; + } + + // Update the color caches. + do + { + if (bgra[pos] != bgraPrev) { - if (bgra[pos] != bgraPrev) + // Efficiency: insert only if the color changes. + int key = ColorCache.HashPix(bgra[pos], 32 - cacheBitsMax); + for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) { - // Efficiency: insert only if the color changes. - int key = ColorCache.HashPix(bgra[pos], 32 - cacheBitsMax); - for (int i = cacheBitsMax; i >= 1; --i, key >>= 1) - { - colorCache[i].Colors[key] = bgra[pos]; - } - - bgraPrev = bgra[pos]; + colorCache[i].Colors[key] = bgra[pos]; } - pos++; + bgraPrev = bgra[pos]; } - while (--len != 0); + + pos++; } + while (--len != 0); } + } - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - for (int i = 0; i <= cacheBitsMax; i++) + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + for (int i = 0; i <= cacheBitsMax; i++) + { + double entropy = histos[i].EstimateBits(stats, bitsEntropy); + if (i == 0 || entropy < entropyMin) { - double entropy = histos[i].EstimateBits(stats, bitsEntropy); - if (i == 0 || entropy < entropyMin) - { - entropyMin = entropy; - bestCacheBits = i; - } + entropyMin = entropy; + bestCacheBits = i; } - - return bestCacheBits; } - private static void BackwardReferencesTraceBackwards( - int xSize, - int ySize, - MemoryAllocator memoryAllocator, - ReadOnlySpan bgra, - int cacheBits, - Vp8LHashChain hashChain, - Vp8LBackwardRefs refsSrc, - Vp8LBackwardRefs refsDst) + return bestCacheBits; + } + + private static void BackwardReferencesTraceBackwards( + int xSize, + int ySize, + MemoryAllocator memoryAllocator, + ReadOnlySpan bgra, + int cacheBits, + Vp8LHashChain hashChain, + Vp8LBackwardRefs refsSrc, + Vp8LBackwardRefs refsDst) + { + int distArraySize = xSize * ySize; + using IMemoryOwner distArrayBuffer = memoryAllocator.Allocate(distArraySize); + Span distArray = distArrayBuffer.GetSpan(); + + BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer); + int chosenPathSize = TraceBackwards(distArray, distArraySize); + Span chosenPath = distArray[(distArraySize - chosenPathSize)..]; + BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); + } + + private static void BackwardReferencesHashChainDistanceOnly( + int xSize, + int ySize, + MemoryAllocator memoryAllocator, + ReadOnlySpan bgra, + int cacheBits, + Vp8LHashChain hashChain, + Vp8LBackwardRefs refs, + IMemoryOwner distArrayBuffer) + { + int pixCount = xSize * ySize; + bool useColorCache = cacheBits > 0; + int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0); + var costModel = new CostModel(literalArraySize); + int offsetPrev = -1; + int lenPrev = -1; + double offsetCost = -1; + int firstOffsetIsConstant = -1; // initialized with 'impossible' value. + int reach = 0; + var colorCache = new ColorCache(); + + if (useColorCache) { - int distArraySize = xSize * ySize; - using IMemoryOwner distArrayBuffer = memoryAllocator.Allocate(distArraySize); - Span distArray = distArrayBuffer.GetSpan(); - - BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer); - int chosenPathSize = TraceBackwards(distArray, distArraySize); - Span chosenPath = distArray[(distArraySize - chosenPathSize)..]; - BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); + colorCache.Init(cacheBits); } - private static void BackwardReferencesHashChainDistanceOnly( - int xSize, - int ySize, - MemoryAllocator memoryAllocator, - ReadOnlySpan bgra, - int cacheBits, - Vp8LHashChain hashChain, - Vp8LBackwardRefs refs, - IMemoryOwner distArrayBuffer) - { - int pixCount = xSize * ySize; - bool useColorCache = cacheBits > 0; - int literalArraySize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (cacheBits > 0 ? 1 << cacheBits : 0); - var costModel = new CostModel(literalArraySize); - int offsetPrev = -1; - int lenPrev = -1; - double offsetCost = -1; - int firstOffsetIsConstant = -1; // initialized with 'impossible' value. - int reach = 0; - var colorCache = new ColorCache(); + costModel.Build(xSize, cacheBits, refs); + using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + Span costManagerCosts = costManager.Costs.GetSpan(); + Span distArray = distArrayBuffer.GetSpan(); - if (useColorCache) - { - colorCache.Init(cacheBits); - } + // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. + distArray[0] = 0; - costModel.Build(xSize, cacheBits, refs); - using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); - Span costManagerCosts = costManager.Costs.GetSpan(); - Span distArray = distArrayBuffer.GetSpan(); + // Add first pixel as literal. + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray); - // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. - distArray[0] = 0; + for (int i = 1; i < pixCount; i++) + { + float prevCost = costManagerCosts[i - 1]; + int offset = hashChain.FindOffset(i); + int len = hashChain.FindLength(i); - // Add first pixel as literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray); + // Try adding the pixel as a literal. + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray); - for (int i = 1; i < pixCount; i++) + // If we are dealing with a non-literal. + if (len >= 2) { - float prevCost = costManagerCosts[i - 1]; - int offset = hashChain.FindOffset(i); - int len = hashChain.FindLength(i); - - // Try adding the pixel as a literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray); - - // If we are dealing with a non-literal. - if (len >= 2) + if (offset != offsetPrev) + { + int code = DistanceToPlaneCode(xSize, offset); + offsetCost = costModel.GetDistanceCost(code); + firstOffsetIsConstant = 1; + costManager.PushInterval(prevCost + offsetCost, i, len); + } + else { - if (offset != offsetPrev) + // Instead of considering all contributions from a pixel i by calling: + // costManager.PushInterval(prevCost + offsetCost, i, len); + // we optimize these contributions in case offsetCost stays the same + // for consecutive pixels. This describes a set of pixels similar to a + // previous set (e.g. constant color regions). + if (firstOffsetIsConstant != 0) { - int code = DistanceToPlaneCode(xSize, offset); - offsetCost = costModel.GetDistanceCost(code); - firstOffsetIsConstant = 1; - costManager.PushInterval(prevCost + offsetCost, i, len); + reach = i - 1 + lenPrev - 1; + firstOffsetIsConstant = 0; } - else - { - // Instead of considering all contributions from a pixel i by calling: - // costManager.PushInterval(prevCost + offsetCost, i, len); - // we optimize these contributions in case offsetCost stays the same - // for consecutive pixels. This describes a set of pixels similar to a - // previous set (e.g. constant color regions). - if (firstOffsetIsConstant != 0) - { - reach = i - 1 + lenPrev - 1; - firstOffsetIsConstant = 0; - } - if (i + len - 1 > reach) + if (i + len - 1 > reach) + { + int lenJ = 0; + int j; + for (j = i; j <= reach; j++) { - int lenJ = 0; - int j; - for (j = i; j <= reach; j++) + int offsetJ = hashChain.FindOffset(j + 1); + lenJ = hashChain.FindLength(j + 1); + if (offsetJ != offset) { - int offsetJ = hashChain.FindOffset(j + 1); - lenJ = hashChain.FindLength(j + 1); - if (offsetJ != offset) - { - lenJ = hashChain.FindLength(j); - break; - } + lenJ = hashChain.FindLength(j); + break; } + } - // Update the cost at j - 1 and j. - costManager.UpdateCostAtIndex(j - 1, false); - costManager.UpdateCostAtIndex(j, false); + // Update the cost at j - 1 and j. + costManager.UpdateCostAtIndex(j - 1, false); + costManager.UpdateCostAtIndex(j, false); - costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ); - reach = j + lenJ - 1; - } + costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ); + reach = j + lenJ - 1; } } - - costManager.UpdateCostAtIndex(i, true); - offsetPrev = offset; - lenPrev = len; } + + costManager.UpdateCostAtIndex(i, true); + offsetPrev = offset; + lenPrev = len; } + } - private static int TraceBackwards(Span distArray, int distArraySize) + private static int TraceBackwards(Span distArray, int distArraySize) + { + int chosenPathSize = 0; + int pathPos = distArraySize; + int curPos = distArraySize - 1; + while (curPos >= 0) { - int chosenPathSize = 0; - int pathPos = distArraySize; - int curPos = distArraySize - 1; - while (curPos >= 0) - { - ushort cur = distArray[curPos]; - pathPos--; - chosenPathSize++; - distArray[pathPos] = cur; - curPos -= cur; - } - - return chosenPathSize; + ushort cur = distArray[curPos]; + pathPos--; + chosenPathSize++; + distArray[pathPos] = cur; + curPos -= cur; } - private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) + return chosenPathSize; + } + + private static void BackwardReferencesHashChainFollowChosenPath(ReadOnlySpan bgra, int cacheBits, Span chosenPath, int chosenPathSize, Vp8LHashChain hashChain, Vp8LBackwardRefs backwardRefs) + { + bool useColorCache = cacheBits > 0; + var colorCache = new ColorCache(); + int i = 0; + + if (useColorCache) { - bool useColorCache = cacheBits > 0; - var colorCache = new ColorCache(); - int i = 0; + colorCache.Init(cacheBits); + } - if (useColorCache) + backwardRefs.Refs.Clear(); + for (int ix = 0; ix < chosenPathSize; ix++) + { + int len = chosenPath[ix]; + if (len != 1) { - colorCache.Init(cacheBits); - } + int offset = hashChain.FindOffset(i); + backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); - backwardRefs.Refs.Clear(); - for (int ix = 0; ix < chosenPathSize; ix++) - { - int len = chosenPath[ix]; - if (len != 1) + if (useColorCache) { - int offset = hashChain.FindOffset(i); - backwardRefs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); - - if (useColorCache) + for (int k = 0; k < len; k++) { - for (int k = 0; k < len; k++) - { - colorCache.Insert(bgra[i + k]); - } + colorCache.Insert(bgra[i + k]); } + } - i += len; + i += len; + } + else + { + PixOrCopy v; + int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1; + if (idx >= 0) + { + // useColorCache is true and color cache contains bgra[i] + // Push pixel as a color cache index. + v = PixOrCopy.CreateCacheIdx(idx); } else { - PixOrCopy v; - int idx = useColorCache ? colorCache.Contains(bgra[i]) : -1; - if (idx >= 0) - { - // useColorCache is true and color cache contains bgra[i] - // Push pixel as a color cache index. - v = PixOrCopy.CreateCacheIdx(idx); - } - else + if (useColorCache) { - if (useColorCache) - { - colorCache.Insert(bgra[i]); - } - - v = PixOrCopy.CreateLiteral(bgra[i]); + colorCache.Insert(bgra[i]); } - backwardRefs.Add(v); - i++; + v = PixOrCopy.CreateLiteral(bgra[i]); } + + backwardRefs.Add(v); + i++; } } + } - private static void AddSingleLiteralWithCostModel( - ReadOnlySpan bgra, - ColorCache colorCache, - CostModel costModel, - int idx, - bool useColorCache, - float prevCost, - Span cost, - Span distArray) + private static void AddSingleLiteralWithCostModel( + ReadOnlySpan bgra, + ColorCache colorCache, + CostModel costModel, + int idx, + bool useColorCache, + float prevCost, + Span cost, + Span distArray) + { + double costVal = prevCost; + uint color = bgra[idx]; + int ix = useColorCache ? colorCache.Contains(color) : -1; + if (ix >= 0) { - double costVal = prevCost; - uint color = bgra[idx]; - int ix = useColorCache ? colorCache.Contains(color) : -1; - if (ix >= 0) + double mul0 = 0.68; + costVal += costModel.GetCacheCost((uint)ix) * mul0; + } + else + { + double mul1 = 0.82; + if (useColorCache) { - double mul0 = 0.68; - costVal += costModel.GetCacheCost((uint)ix) * mul0; + colorCache.Insert(color); } - else - { - double mul1 = 0.82; - if (useColorCache) - { - colorCache.Insert(color); - } - costVal += costModel.GetLiteralCost(color) * mul1; - } + costVal += costModel.GetLiteralCost(color) * mul1; + } - if (cost[idx] > costVal) - { - cost[idx] = (float)costVal; - distArray[idx] = 1; // only one is inserted. - } + if (cost[idx] > costVal) + { + cost[idx] = (float)costVal; + distArray[idx] = 1; // only one is inserted. } + } - private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + private static void BackwardReferencesLz77(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + { + int iLastCheck = -1; + bool useColorCache = cacheBits > 0; + int pixCount = xSize * ySize; + var colorCache = new ColorCache(); + if (useColorCache) { - int iLastCheck = -1; - bool useColorCache = cacheBits > 0; - int pixCount = xSize * ySize; - var colorCache = new ColorCache(); - if (useColorCache) - { - colorCache.Init(cacheBits); - } + colorCache.Init(cacheBits); + } - refs.Refs.Clear(); - for (int i = 0; i < pixCount;) + refs.Refs.Clear(); + for (int i = 0; i < pixCount;) + { + // Alternative #1: Code the pixels starting at 'i' using backward reference. + int j; + int offset = hashChain.FindOffset(i); + int len = hashChain.FindLength(i); + if (len >= MinLength) { - // Alternative #1: Code the pixels starting at 'i' using backward reference. - int j; - int offset = hashChain.FindOffset(i); - int len = hashChain.FindLength(i); - if (len >= MinLength) - { - int lenIni = len; - int maxReach = 0; - int jMax = i + lenIni >= pixCount ? pixCount - 1 : i + lenIni; - - // Only start from what we have not checked already. - iLastCheck = i > iLastCheck ? i : iLastCheck; - - // We know the best match for the current pixel but we try to find the - // best matches for the current pixel AND the next one combined. - // The naive method would use the intervals: - // [i,i+len) + [i+len, length of best match at i+len) - // while we check if we can use: - // [i,j) (where j<=i+len) + [j, length of best match at j) - for (j = iLastCheck + 1; j <= jMax; j++) + int lenIni = len; + int maxReach = 0; + int jMax = i + lenIni >= pixCount ? pixCount - 1 : i + lenIni; + + // Only start from what we have not checked already. + iLastCheck = i > iLastCheck ? i : iLastCheck; + + // We know the best match for the current pixel but we try to find the + // best matches for the current pixel AND the next one combined. + // The naive method would use the intervals: + // [i,i+len) + [i+len, length of best match at i+len) + // while we check if we can use: + // [i,j) (where j<=i+len) + [j, length of best match at j) + for (j = iLastCheck + 1; j <= jMax; j++) + { + int lenJ = hashChain.FindLength(j); + int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. + if (reach > maxReach) { - int lenJ = hashChain.FindLength(j); - int reach = j + (lenJ >= MinLength ? lenJ : 1); // 1 for single literal. - if (reach > maxReach) + len = j - i; + maxReach = reach; + if (maxReach >= pixCount) { - len = j - i; - maxReach = reach; - if (maxReach >= pixCount) - { - break; - } + break; } } } - else - { - len = 1; - } + } + else + { + len = 1; + } - // Go with literal or backward reference. - if (len == 1) - { - AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); - } - else + // Go with literal or backward reference. + if (len == 1) + { + AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); + } + else + { + refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); + if (useColorCache) { - refs.Add(PixOrCopy.CreateCopy((uint)offset, (ushort)len)); - if (useColorCache) + for (j = i; j < i + len; j++) { - for (j = i; j < i + len; j++) - { - colorCache.Insert(bgra[j]); - } + colorCache.Insert(bgra[j]); } } - - i += len; } + + i += len; } + } - /// - /// Compute an LZ77 by forcing matches to happen within a given distance cost. - /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. - /// - private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + /// + /// Compute an LZ77 by forcing matches to happen within a given distance cost. + /// We therefore limit the algorithm to the lowest 32 values in the PlaneCode definition. + /// + private static void BackwardReferencesLz77Box(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChainBest, Vp8LHashChain hashChain, Vp8LBackwardRefs refs) + { + int pixelCount = xSize * ySize; + int[] windowOffsets = new int[WindowOffsetsSizeMax]; + int[] windowOffsetsNew = new int[WindowOffsetsSizeMax]; + int windowOffsetsSize = 0; + int windowOffsetsNewSize = 0; + short[] counts = new short[xSize * ySize]; + int bestOffsetPrev = -1; + int bestLengthPrev = -1; + + // counts[i] counts how many times a pixel is repeated starting at position i. + int i = pixelCount - 2; + int countsPos = i; + counts[countsPos + 1] = 1; + for (; i >= 0; --i, --countsPos) { - int pixelCount = xSize * ySize; - int[] windowOffsets = new int[WindowOffsetsSizeMax]; - int[] windowOffsetsNew = new int[WindowOffsetsSizeMax]; - int windowOffsetsSize = 0; - int windowOffsetsNewSize = 0; - short[] counts = new short[xSize * ySize]; - int bestOffsetPrev = -1; - int bestLengthPrev = -1; - - // counts[i] counts how many times a pixel is repeated starting at position i. - int i = pixelCount - 2; - int countsPos = i; - counts[countsPos + 1] = 1; - for (; i >= 0; --i, --countsPos) - { - if (bgra[i] == bgra[i + 1]) - { - // Max out the counts to MaxLength. - counts[countsPos] = counts[countsPos + 1]; - if (counts[countsPos + 1] != MaxLength) - { - counts[countsPos]++; - } - } - else + if (bgra[i] == bgra[i + 1]) + { + // Max out the counts to MaxLength. + counts[countsPos] = counts[countsPos + 1]; + if (counts[countsPos + 1] != MaxLength) { - counts[countsPos] = 1; + counts[countsPos]++; } } - - // Figure out the window offsets around a pixel. They are stored in a - // spiraling order around the pixel as defined by DistanceToPlaneCode. - for (int y = 0; y <= 6; y++) + else { - for (int x = -6; x <= 6; x++) - { - int offset = (y * xSize) + x; - - // Ignore offsets that bring us after the pixel. - if (offset <= 0) - { - continue; - } + counts[countsPos] = 1; + } + } - int planeCode = DistanceToPlaneCode(xSize, offset) - 1; - if (planeCode >= WindowOffsetsSizeMax) - { - continue; - } + // Figure out the window offsets around a pixel. They are stored in a + // spiraling order around the pixel as defined by DistanceToPlaneCode. + for (int y = 0; y <= 6; y++) + { + for (int x = -6; x <= 6; x++) + { + int offset = (y * xSize) + x; - windowOffsets[planeCode] = offset; + // Ignore offsets that bring us after the pixel. + if (offset <= 0) + { + continue; } - } - // For narrow images, not all plane codes are reached, so remove those. - for (i = 0; i < WindowOffsetsSizeMax; i++) - { - if (windowOffsets[i] == 0) + int planeCode = DistanceToPlaneCode(xSize, offset) - 1; + if (planeCode >= WindowOffsetsSizeMax) { continue; } - windowOffsets[windowOffsetsSize++] = windowOffsets[i]; + windowOffsets[planeCode] = offset; + } + } + + // For narrow images, not all plane codes are reached, so remove those. + for (i = 0; i < WindowOffsetsSizeMax; i++) + { + if (windowOffsets[i] == 0) + { + continue; } - // Given a pixel P, find the offsets that reach pixels unreachable from P-1 - // with any of the offsets in windowOffsets[]. - for (i = 0; i < windowOffsetsSize; i++) + windowOffsets[windowOffsetsSize++] = windowOffsets[i]; + } + + // Given a pixel P, find the offsets that reach pixels unreachable from P-1 + // with any of the offsets in windowOffsets[]. + for (i = 0; i < windowOffsetsSize; i++) + { + bool isReachable = false; + for (int j = 0; j < windowOffsetsSize && !isReachable; j++) { - bool isReachable = false; - for (int j = 0; j < windowOffsetsSize && !isReachable; j++) - { - isReachable |= windowOffsets[i] == windowOffsets[j] + 1; - } + isReachable |= windowOffsets[i] == windowOffsets[j] + 1; + } - if (!isReachable) + if (!isReachable) + { + windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i]; + ++windowOffsetsNewSize; + } + } + + Span hashChainOffsetLength = hashChain.OffsetLength.GetSpan(); + hashChainOffsetLength[0] = 0; + for (i = 1; i < pixelCount; i++) + { + int ind; + int bestLength = hashChainBest.FindLength(i); + int bestOffset = 0; + bool doCompute = true; + + if (bestLength >= MaxLength) + { + // Do not recompute the best match if we already have a maximal one in the window. + bestOffset = hashChainBest.FindOffset(i); + for (ind = 0; ind < windowOffsetsSize; ind++) { - windowOffsetsNew[windowOffsetsNewSize] = windowOffsets[i]; - ++windowOffsetsNewSize; + if (bestOffset == windowOffsets[ind]) + { + doCompute = false; + break; + } } } - Span hashChainOffsetLength = hashChain.OffsetLength.GetSpan(); - hashChainOffsetLength[0] = 0; - for (i = 1; i < pixelCount; i++) + if (doCompute) { - int ind; - int bestLength = hashChainBest.FindLength(i); - int bestOffset = 0; - bool doCompute = true; + // Figure out if we should use the offset/length from the previous pixel + // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. + bool usePrev = bestLengthPrev is > 1 and < MaxLength; + int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; + bestLength = usePrev ? bestLengthPrev - 1 : 0; + bestOffset = usePrev ? bestOffsetPrev : 0; + + // Find the longest match in a window around the pixel. + for (ind = 0; ind < numInd; ind++) + { + int currLength = 0; + int j = i; + int jOffset = usePrev ? i - windowOffsetsNew[ind] : i - windowOffsets[ind]; + if (jOffset < 0 || bgra[jOffset] != bgra[i]) + { + continue; + } - if (bestLength >= MaxLength) - { - // Do not recompute the best match if we already have a maximal one in the window. - bestOffset = hashChainBest.FindOffset(i); - for (ind = 0; ind < windowOffsetsSize; ind++) + // The longest match is the sum of how many times each pixel is repeated. + do { - if (bestOffset == windowOffsets[ind]) + int countsJOffset = counts[jOffset]; + int countsJ = counts[j]; + if (countsJOffset != countsJ) { - doCompute = false; + currLength += countsJOffset < countsJ ? countsJOffset : countsJ; break; } + + // The same color is repeated counts_pos times at jOffset and j. + currLength += countsJOffset; + jOffset += countsJOffset; + j += countsJOffset; } - } + while (currLength <= MaxLength && j < pixelCount && bgra[jOffset] == bgra[j]); - if (doCompute) - { - // Figure out if we should use the offset/length from the previous pixel - // as an initial guess and therefore only inspect the offsets in windowOffsetsNew[]. - bool usePrev = bestLengthPrev is > 1 and < MaxLength; - int numInd = usePrev ? windowOffsetsNewSize : windowOffsetsSize; - bestLength = usePrev ? bestLengthPrev - 1 : 0; - bestOffset = usePrev ? bestOffsetPrev : 0; - - // Find the longest match in a window around the pixel. - for (ind = 0; ind < numInd; ind++) + if (bestLength < currLength) { - int currLength = 0; - int j = i; - int jOffset = usePrev ? i - windowOffsetsNew[ind] : i - windowOffsets[ind]; - if (jOffset < 0 || bgra[jOffset] != bgra[i]) + bestOffset = usePrev ? windowOffsetsNew[ind] : windowOffsets[ind]; + if (currLength >= MaxLength) { - continue; - } - - // The longest match is the sum of how many times each pixel is repeated. - do - { - int countsJOffset = counts[jOffset]; - int countsJ = counts[j]; - if (countsJOffset != countsJ) - { - currLength += countsJOffset < countsJ ? countsJOffset : countsJ; - break; - } - - // The same color is repeated counts_pos times at jOffset and j. - currLength += countsJOffset; - jOffset += countsJOffset; - j += countsJOffset; + bestLength = MaxLength; + break; } - while (currLength <= MaxLength && j < pixelCount && bgra[jOffset] == bgra[j]); - - if (bestLength < currLength) + else { - bestOffset = usePrev ? windowOffsetsNew[ind] : windowOffsets[ind]; - if (currLength >= MaxLength) - { - bestLength = MaxLength; - break; - } - else - { - bestLength = currLength; - } + bestLength = currLength; } } } - - if (bestLength <= MinLength) - { - hashChainOffsetLength[i] = 0; - bestOffsetPrev = 0; - bestLengthPrev = 0; - } - else - { - hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); - bestOffsetPrev = bestOffset; - bestLengthPrev = bestLength; - } } - hashChainOffsetLength[0] = 0; - BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); - } - - private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) - { - int pixelCount = xSize * ySize; - bool useColorCache = cacheBits > 0; - var colorCache = new ColorCache(); - - if (useColorCache) + if (bestLength <= MinLength) { - colorCache.Init(cacheBits); + hashChainOffsetLength[i] = 0; + bestOffsetPrev = 0; + bestLengthPrev = 0; } - - refs.Refs.Clear(); - - // Add first pixel as literal. - AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); - int i = 1; - while (i < pixelCount) + else { - int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); - int rleLen = LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - 1)..], 0, maxLen); - int prevRowLen = i < xSize ? 0 : LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - xSize)..], 0, maxLen); - if (rleLen >= prevRowLen && rleLen >= MinLength) - { - refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); - - // We don't need to update the color cache here since it is always the - // same pixel being copied, and that does not change the color cache state. - i += rleLen; - } - else if (prevRowLen >= MinLength) - { - refs.Add(PixOrCopy.CreateCopy((uint)xSize, (ushort)prevRowLen)); - if (useColorCache) - { - for (int k = 0; k < prevRowLen; ++k) - { - colorCache.Insert(bgra[i + k]); - } - } - - i += prevRowLen; - } - else - { - AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); - i++; - } + hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); + bestOffsetPrev = bestOffset; + bestLengthPrev = bestLength; } } - /// - /// Update (in-place) backward references for the specified cacheBits. - /// - private static void BackwardRefsWithLocalCache(ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) + hashChainOffsetLength[0] = 0; + BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); + } + + private static void BackwardReferencesRle(int xSize, int ySize, ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) + { + int pixelCount = xSize * ySize; + bool useColorCache = cacheBits > 0; + var colorCache = new ColorCache(); + + if (useColorCache) { - int pixelIndex = 0; - var colorCache = new ColorCache(); colorCache.Init(cacheBits); - for (int idx = 0; idx < refs.Refs.Count; idx++) + } + + refs.Refs.Clear(); + + // Add first pixel as literal. + AddSingleLiteral(bgra[0], useColorCache, colorCache, refs); + int i = 1; + while (i < pixelCount) + { + int maxLen = LosslessUtils.MaxFindCopyLength(pixelCount - i); + int rleLen = LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - 1)..], 0, maxLen); + int prevRowLen = i < xSize ? 0 : LosslessUtils.FindMatchLength(bgra[i..], bgra[(i - xSize)..], 0, maxLen); + if (rleLen >= prevRowLen && rleLen >= MinLength) { - PixOrCopy v = refs.Refs[idx]; - if (v.IsLiteral()) - { - uint bgraLiteral = v.BgraOrDistance; - int ix = colorCache.Contains(bgraLiteral); - if (ix >= 0) - { - // Color cache contains bgraLiteral - v.Mode = PixOrCopyMode.CacheIdx; - v.BgraOrDistance = (uint)ix; - v.Len = 1; - } - else - { - colorCache.Insert(bgraLiteral); - } + refs.Add(PixOrCopy.CreateCopy(1, (ushort)rleLen)); - pixelIndex++; - } - else + // We don't need to update the color cache here since it is always the + // same pixel being copied, and that does not change the color cache state. + i += rleLen; + } + else if (prevRowLen >= MinLength) + { + refs.Add(PixOrCopy.CreateCopy((uint)xSize, (ushort)prevRowLen)); + if (useColorCache) { - // refs was created without local cache, so it can not have cache indexes. - for (int k = 0; k < v.Len; ++k) + for (int k = 0; k < prevRowLen; ++k) { - colorCache.Insert(bgra[pixelIndex++]); + colorCache.Insert(bgra[i + k]); } } - } - } - private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) - { - using List.Enumerator c = refs.Refs.GetEnumerator(); - while (c.MoveNext()) + i += prevRowLen; + } + else { - if (c.Current.IsCopy()) - { - int dist = (int)c.Current.BgraOrDistance; - int transformedDist = DistanceToPlaneCode(xSize, dist); - c.Current.BgraOrDistance = (uint)transformedDist; - } + AddSingleLiteral(bgra[i], useColorCache, colorCache, refs); + i++; } } + } - private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs) + /// + /// Update (in-place) backward references for the specified cacheBits. + /// + private static void BackwardRefsWithLocalCache(ReadOnlySpan bgra, int cacheBits, Vp8LBackwardRefs refs) + { + int pixelIndex = 0; + var colorCache = new ColorCache(); + colorCache.Init(cacheBits); + for (int idx = 0; idx < refs.Refs.Count; idx++) { - PixOrCopy v; - if (useColorCache) + PixOrCopy v = refs.Refs[idx]; + if (v.IsLiteral()) { - int key = colorCache.GetIndex(pixel); - if (colorCache.Lookup(key) == pixel) + uint bgraLiteral = v.BgraOrDistance; + int ix = colorCache.Contains(bgraLiteral); + if (ix >= 0) { - v = PixOrCopy.CreateCacheIdx(key); + // Color cache contains bgraLiteral + v.Mode = PixOrCopyMode.CacheIdx; + v.BgraOrDistance = (uint)ix; + v.Len = 1; } else { - v = PixOrCopy.CreateLiteral(pixel); - colorCache.Set((uint)key, pixel); + colorCache.Insert(bgraLiteral); } + + pixelIndex++; } else { - v = PixOrCopy.CreateLiteral(pixel); + // refs was created without local cache, so it can not have cache indexes. + for (int k = 0; k < v.Len; ++k) + { + colorCache.Insert(bgra[pixelIndex++]); + } } + } + } - refs.Add(v); + private static void BackwardReferences2DLocality(int xSize, Vp8LBackwardRefs refs) + { + using List.Enumerator c = refs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + if (c.Current.IsCopy()) + { + int dist = (int)c.Current.BgraOrDistance; + int transformedDist = DistanceToPlaneCode(xSize, dist); + c.Current.BgraOrDistance = (uint)transformedDist; + } } + } - public static int DistanceToPlaneCode(int xSize, int dist) + private static void AddSingleLiteral(uint pixel, bool useColorCache, ColorCache colorCache, Vp8LBackwardRefs refs) + { + PixOrCopy v; + if (useColorCache) { - int yOffset = dist / xSize; - int xOffset = dist - (yOffset * xSize); - if (xOffset <= 8 && yOffset < 8) + int key = colorCache.GetIndex(pixel); + if (colorCache.Lookup(key) == pixel) { - return (int)WebpLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; + v = PixOrCopy.CreateCacheIdx(key); } - else if (xOffset > xSize - 8 && yOffset < 7) + else { - return (int)WebpLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; + v = PixOrCopy.CreateLiteral(pixel); + colorCache.Set((uint)key, pixel); } + } + else + { + v = PixOrCopy.CreateLiteral(pixel); + } + + refs.Add(v); + } - return dist + 120; + public static int DistanceToPlaneCode(int xSize, int dist) + { + int yOffset = dist / xSize; + int xOffset = dist - (yOffset * xSize); + if (xOffset <= 8 && yOffset < 8) + { + return (int)WebpLookupTables.PlaneToCodeLut[(yOffset * 16) + 8 - xOffset] + 1; } + else if (xOffset > xSize - 8 && yOffset < 7) + { + return (int)WebpLookupTables.PlaneToCodeLut[((yOffset + 1) * 16) + 8 + (xSize - xOffset)] + 1; + } + + return dist + 120; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs index 8f7418694e..659da10686 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorCache.cs @@ -3,90 +3,89 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. +/// +internal class ColorCache { + private const uint HashMul = 0x1e35a7bdu; + /// - /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes. + /// Gets the color entries. /// - internal class ColorCache - { - private const uint HashMul = 0x1e35a7bdu; - - /// - /// Gets the color entries. - /// - public uint[] Colors { get; private set; } + public uint[] Colors { get; private set; } - /// - /// Gets the hash shift: 32 - hashBits. - /// - public int HashShift { get; private set; } + /// + /// Gets the hash shift: 32 - hashBits. + /// + public int HashShift { get; private set; } - /// - /// Gets the hash bits. - /// - public int HashBits { get; private set; } + /// + /// Gets the hash bits. + /// + public int HashBits { get; private set; } - /// - /// Initializes a new color cache. - /// - /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. - public void Init(int hashBits) - { - int hashSize = 1 << hashBits; - this.Colors = new uint[hashSize]; - this.HashBits = hashBits; - this.HashShift = 32 - hashBits; - } + /// + /// Initializes a new color cache. + /// + /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits. + public void Init(int hashBits) + { + int hashSize = 1 << hashBits; + this.Colors = new uint[hashSize]; + this.HashBits = hashBits; + this.HashShift = 32 - hashBits; + } - /// - /// Inserts a new color into the cache. - /// - /// The color to insert. - [MethodImpl(InliningOptions.ShortMethod)] - public void Insert(uint bgra) - { - int key = HashPix(bgra, this.HashShift); - this.Colors[key] = bgra; - } + /// + /// Inserts a new color into the cache. + /// + /// The color to insert. + [MethodImpl(InliningOptions.ShortMethod)] + public void Insert(uint bgra) + { + int key = HashPix(bgra, this.HashShift); + this.Colors[key] = bgra; + } - /// - /// Gets a color for a given key. - /// - /// The key to lookup. - /// The color for the key. - [MethodImpl(InliningOptions.ShortMethod)] - public uint Lookup(int key) => this.Colors[key]; + /// + /// Gets a color for a given key. + /// + /// The key to lookup. + /// The color for the key. + [MethodImpl(InliningOptions.ShortMethod)] + public uint Lookup(int key) => this.Colors[key]; - /// - /// Returns the index of the given color. - /// - /// The color to check. - /// The index of the color in the cache or -1 if its not present. - [MethodImpl(InliningOptions.ShortMethod)] - public int Contains(uint bgra) - { - int key = HashPix(bgra, this.HashShift); - return (this.Colors[key] == bgra) ? key : -1; - } + /// + /// Returns the index of the given color. + /// + /// The color to check. + /// The index of the color in the cache or -1 if its not present. + [MethodImpl(InliningOptions.ShortMethod)] + public int Contains(uint bgra) + { + int key = HashPix(bgra, this.HashShift); + return (this.Colors[key] == bgra) ? key : -1; + } - /// - /// Gets the index of a color. - /// - /// The color. - /// The index for the color. - [MethodImpl(InliningOptions.ShortMethod)] - public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift); + /// + /// Gets the index of a color. + /// + /// The color. + /// The index for the color. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetIndex(uint bgra) => HashPix(bgra, this.HashShift); - /// - /// Adds a new color to the cache. - /// - /// The key. - /// The color to add. - [MethodImpl(InliningOptions.ShortMethod)] - public void Set(uint key, uint bgra) => this.Colors[key] = bgra; + /// + /// Adds a new color to the cache. + /// + /// The key. + /// The color to add. + [MethodImpl(InliningOptions.ShortMethod)] + public void Set(uint key, uint bgra) => this.Colors[key] = bgra; - [MethodImpl(InliningOptions.ShortMethod)] - public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static int HashPix(uint argb, int shift) => (int)((argb * HashMul) >> shift); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs index d80cdc2758..45de1b9553 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs @@ -1,246 +1,244 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal static class ColorSpaceTransformUtils { - internal static class ColorSpaceTransformUtils + public static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) { - public static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) + if (Avx2.IsSupported && tileWidth >= 16) { - if (Avx2.IsSupported && tileWidth >= 16) + const int span = 16; + Span values = stackalloc ushort[span]; + var collectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); + var collectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); + var collectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + var collectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var collectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); + for (int y = 0; y < tileHeight; y++) { - const int span = 16; - Span values = stackalloc ushort[span]; - var collectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); - var collectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); - var collectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - var collectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - var collectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); - var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) + Span srcSpan = bgra[(y * stride)..]; + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (nint x = 0; x <= tileWidth - span; x += span) { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nint x = 0; x <= tileWidth - span; x += span) + nint input0Idx = x; + nint input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 r0 = Avx2.Shuffle(input0, collectColorBlueTransformsShuffleLowMask256); + Vector256 r1 = Avx2.Shuffle(input1, collectColorBlueTransformsShuffleHighMask256); + Vector256 r = Avx2.Or(r0, r1); + Vector256 gb0 = Avx2.And(input0, collectColorBlueTransformsGreenBlueMask256); + Vector256 gb1 = Avx2.And(input1, collectColorBlueTransformsGreenBlueMask256); + Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector256 g = Avx2.And(gb.AsByte(), collectColorBlueTransformsGreenMask256); + Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); + Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); + Vector256 d = Avx2.Subtract(c, a.AsByte()); + Vector256 e = Avx2.And(d, collectColorBlueTransformsBlueMask256); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) { - nint input0Idx = x; - nint input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 r0 = Avx2.Shuffle(input0, collectColorBlueTransformsShuffleLowMask256); - Vector256 r1 = Avx2.Shuffle(input1, collectColorBlueTransformsShuffleHighMask256); - Vector256 r = Avx2.Or(r0, r1); - Vector256 gb0 = Avx2.And(input0, collectColorBlueTransformsGreenBlueMask256); - Vector256 gb1 = Avx2.And(input1, collectColorBlueTransformsGreenBlueMask256); - Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector256 g = Avx2.And(gb.AsByte(), collectColorBlueTransformsGreenMask256); - Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); - Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); - Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); - Vector256 d = Avx2.Subtract(c, a.AsByte()); - Vector256 e = Avx2.And(d, collectColorBlueTransformsBlueMask256); - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } + } - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); } - else if (Sse41.IsSupported) + } + else if (Sse41.IsSupported) + { + const int span = 8; + Span values = stackalloc ushort[span]; + var collectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); + var collectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); + var collectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + var collectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var collectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); + for (int y = 0; y < tileHeight; y++) { - const int span = 8; - Span values = stackalloc ushort[span]; - var collectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); - var collectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); - var collectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - var collectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - var collectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); - var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) + Span srcSpan = bgra[(y * stride)..]; + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (nint x = 0; x <= tileWidth - span; x += span) { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nint x = 0; x <= tileWidth - span; x += span) + nint input0Idx = x; + nint input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 r0 = Ssse3.Shuffle(input0, collectColorBlueTransformsShuffleLowMask); + Vector128 r1 = Ssse3.Shuffle(input1, collectColorBlueTransformsShuffleHighMask); + Vector128 r = Sse2.Or(r0, r1); + Vector128 gb0 = Sse2.And(input0, collectColorBlueTransformsGreenBlueMask); + Vector128 gb1 = Sse2.And(input1, collectColorBlueTransformsGreenBlueMask); + Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector128 g = Sse2.And(gb.AsByte(), collectColorBlueTransformsGreenMask); + Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); + Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); + Vector128 d = Sse2.Subtract(c, a.AsByte()); + Vector128 e = Sse2.And(d, collectColorBlueTransformsBlueMask); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) { - nint input0Idx = x; - nint input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 r0 = Ssse3.Shuffle(input0, collectColorBlueTransformsShuffleLowMask); - Vector128 r1 = Ssse3.Shuffle(input1, collectColorBlueTransformsShuffleHighMask); - Vector128 r = Sse2.Or(r0, r1); - Vector128 gb0 = Sse2.And(input0, collectColorBlueTransformsGreenBlueMask); - Vector128 gb1 = Sse2.And(input1, collectColorBlueTransformsGreenBlueMask); - Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector128 g = Sse2.And(gb.AsByte(), collectColorBlueTransformsGreenMask); - Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); - Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); - Vector128 d = Sse2.Subtract(c, a.AsByte()); - Vector128 e = Sse2.And(d, collectColorBlueTransformsBlueMask); - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } } - else + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) { - CollectColorBlueTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + CollectColorBlueTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); } } + else + { + CollectColorBlueTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + } + } - private static void CollectColorBlueTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) + private static void CollectColorBlueTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) + { + int pos = 0; + while (tileHeight-- > 0) { - int pos = 0; - while (tileHeight-- > 0) + for (int x = 0; x < tileWidth; x++) { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; + int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); + ++histo[idx]; } + + pos += stride; } + } - public static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + public static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + { + if (Avx2.IsSupported && tileWidth >= 16) { - if (Avx2.IsSupported && tileWidth >= 16) + Vector256 collectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); + Vector256 collectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); + const int span = 16; + Span values = stackalloc ushort[span]; + for (int y = 0; y < tileHeight; y++) { - Vector256 collectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); - Vector256 collectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); - var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 16; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) + Span srcSpan = bgra[(y * stride)..]; + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (nint x = 0; x <= tileWidth - span; x += span) { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nint x = 0; x <= tileWidth - span; x += span) + nint input0Idx = x; + nint input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 g0 = Avx2.And(input0, collectColorRedTransformsGreenMask256); // 0 0 | g 0 + Vector256 g1 = Avx2.And(input1, collectColorRedTransformsGreenMask256); + Vector256 g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector256 a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector256 a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); + Vector256 a = Avx2.PackUnsignedSaturate(a0, a1); // x r + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector256 c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector256 d = Avx2.And(c, collectColorRedTransformsAndMask256); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) { - nint input0Idx = x; - nint input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 g0 = Avx2.And(input0, collectColorRedTransformsGreenMask256); // 0 0 | g 0 - Vector256 g1 = Avx2.And(input1, collectColorRedTransformsGreenMask256); - Vector256 g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector256 a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector256 a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); - Vector256 a = Avx2.PackUnsignedSaturate(a0, a1); // x r - Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector256 c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' - Vector256 d = Avx2.And(c, collectColorRedTransformsAndMask256); // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } + } - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); - } + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorRedTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); } - else if (Sse41.IsSupported) + } + else if (Sse41.IsSupported) + { + Vector128 collectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte(); + Vector128 collectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); + const int span = 8; + Span values = stackalloc ushort[span]; + for (int y = 0; y < tileHeight; y++) { - Vector128 collectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte(); - Vector128 collectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); - var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 8; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) + Span srcSpan = bgra[(y * stride)..]; + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (nint x = 0; x <= tileWidth - span; x += span) { - Span srcSpan = bgra[(y * stride)..]; - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (nint x = 0; x <= tileWidth - span; x += span) + nint input0Idx = x; + nint input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 g0 = Sse2.And(input0, collectColorRedTransformsGreenMask); // 0 0 | g 0 + Vector128 g1 = Sse2.And(input1, collectColorRedTransformsGreenMask); + Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); + Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector128 d = Sse2.And(c, collectColorRedTransformsAndMask); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) { - nint input0Idx = x; - nint input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 g0 = Sse2.And(input0, collectColorRedTransformsGreenMask); // 0 0 | g 0 - Vector128 g1 = Sse2.And(input1, collectColorRedTransformsGreenMask); - Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); - Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' - Vector128 d = Sse2.And(c, collectColorRedTransformsAndMask); // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); - } } - else + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) { - CollectColorRedTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToRed, histo); + CollectColorRedTransformsNoneVectorized(bgra[(tileWidth - leftOver)..], stride, leftOver, tileHeight, greenToRed, histo); } } + else + { + CollectColorRedTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToRed, histo); + } + } - private static void CollectColorRedTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + private static void CollectColorRedTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + { + int pos = 0; + while (tileHeight-- > 0) { - int pos = 0; - while (tileHeight-- > 0) + for (int x = 0; x < tileWidth; x++) { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; + int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); + ++histo[idx]; } + + pos += stride; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs index 266fe941d8..a98b67166e 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostCacheInterval.cs @@ -3,18 +3,17 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. +/// +[DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] +internal class CostCacheInterval { - /// - /// The GetLengthCost(costModel, k) are cached in a CostCacheInterval. - /// - [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] - internal class CostCacheInterval - { - public double Cost { get; set; } + public double Cost { get; set; } - public int Start { get; set; } + public int Start { get; set; } - public int End { get; set; } // Exclusive. - } + public int End { get; set; } // Exclusive. } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs index 281c2392a8..bee5e4cc36 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostInterval.cs @@ -3,37 +3,36 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// To perform backward reference every pixel at index index_ is considered and +/// the cost for the MAX_LENGTH following pixels computed. Those following pixels +/// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of: +/// cost = distance cost at index + GetLengthCost(costModel, k) +/// and the minimum value is kept. GetLengthCost(costModel, k) is cached in an +/// array of size MAX_LENGTH. +/// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the +/// minimal values using intervals of constant cost. +/// An interval is defined by the index_ of the pixel that generated it and +/// is only useful in a range of indices from start to end (exclusive), i.e. +/// it contains the minimum value for pixels between start and end. +/// Intervals are stored in a linked list and ordered by start. When a new +/// interval has a better value, old intervals are split or removed. There are +/// therefore no overlapping intervals. +/// +[DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] +internal class CostInterval { - /// - /// To perform backward reference every pixel at index index_ is considered and - /// the cost for the MAX_LENGTH following pixels computed. Those following pixels - /// at index index_ + k (k from 0 to MAX_LENGTH) have a cost of: - /// cost = distance cost at index + GetLengthCost(costModel, k) - /// and the minimum value is kept. GetLengthCost(costModel, k) is cached in an - /// array of size MAX_LENGTH. - /// Instead of performing MAX_LENGTH comparisons per pixel, we keep track of the - /// minimal values using intervals of constant cost. - /// An interval is defined by the index_ of the pixel that generated it and - /// is only useful in a range of indices from start to end (exclusive), i.e. - /// it contains the minimum value for pixels between start and end. - /// Intervals are stored in a linked list and ordered by start. When a new - /// interval has a better value, old intervals are split or removed. There are - /// therefore no overlapping intervals. - /// - [DebuggerDisplay("Start: {Start}, End: {End}, Cost: {Cost}")] - internal class CostInterval - { - public float Cost { get; set; } - - public int Start { get; set; } - - public int End { get; set; } - - public int Index { get; set; } - - public CostInterval Previous { get; set; } - - public CostInterval Next { get; set; } - } + public float Cost { get; set; } + + public int Start { get; set; } + + public int End { get; set; } + + public int Index { get; set; } + + public CostInterval Previous { get; set; } + + public CostInterval Next { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 25f2bd5dea..f852f2137c 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -1,332 +1,329 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// The CostManager is in charge of managing intervals and costs. +/// It caches the different CostCacheInterval, caches the different +/// GetLengthCost(costModel, k) in costCache and the CostInterval's. +/// +internal sealed class CostManager : IDisposable { - /// - /// The CostManager is in charge of managing intervals and costs. - /// It caches the different CostCacheInterval, caches the different - /// GetLengthCost(costModel, k) in costCache and the CostInterval's. - /// - internal sealed class CostManager : IDisposable - { - private CostInterval head; + private CostInterval head; - private const int FreeIntervalsStartCount = 25; + private const int FreeIntervalsStartCount = 25; - private readonly Stack freeIntervals = new(FreeIntervalsStartCount); + private readonly Stack freeIntervals = new(FreeIntervalsStartCount); - public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArray, int pixCount, CostModel costModel) - { - int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; + public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArray, int pixCount, CostModel costModel) + { + int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; - this.CacheIntervals = new List(); - this.CostCache = new List(); - this.Costs = memoryAllocator.Allocate(pixCount); - this.DistArray = distArray; - this.Count = 0; + this.CacheIntervals = new List(); + this.CostCache = new List(); + this.Costs = memoryAllocator.Allocate(pixCount); + this.DistArray = distArray; + this.Count = 0; - for (int i = 0; i < FreeIntervalsStartCount; i++) - { - this.freeIntervals.Push(new CostInterval()); - } + for (int i = 0; i < FreeIntervalsStartCount; i++) + { + this.freeIntervals.Push(new CostInterval()); + } - // Fill in the cost cache. - this.CacheIntervalsSize++; - this.CostCache.Add(costModel.GetLengthCost(0)); - for (int i = 1; i < costCacheSize; i++) - { - this.CostCache.Add(costModel.GetLengthCost(i)); + // Fill in the cost cache. + this.CacheIntervalsSize++; + this.CostCache.Add(costModel.GetLengthCost(0)); + for (int i = 1; i < costCacheSize; i++) + { + this.CostCache.Add(costModel.GetLengthCost(i)); - // Get the number of bound intervals. - if (this.CostCache[i] != this.CostCache[i - 1]) - { - this.CacheIntervalsSize++; - } + // Get the number of bound intervals. + if (this.CostCache[i] != this.CostCache[i - 1]) + { + this.CacheIntervalsSize++; } + } - // Fill in the cache intervals. - var cur = new CostCacheInterval() - { - Start = 0, - End = 1, - Cost = this.CostCache[0] - }; - this.CacheIntervals.Add(cur); + // Fill in the cache intervals. + var cur = new CostCacheInterval() + { + Start = 0, + End = 1, + Cost = this.CostCache[0] + }; + this.CacheIntervals.Add(cur); - for (int i = 1; i < costCacheSize; i++) + for (int i = 1; i < costCacheSize; i++) + { + double costVal = this.CostCache[i]; + if (costVal != cur.Cost) { - double costVal = this.CostCache[i]; - if (costVal != cur.Cost) + cur = new CostCacheInterval() { - cur = new CostCacheInterval() - { - Start = i, - Cost = costVal - }; - this.CacheIntervals.Add(cur); - } - - cur.End = i + 1; + Start = i, + Cost = costVal + }; + this.CacheIntervals.Add(cur); } - // Set the initial costs high for every pixel as we will keep the minimum. - this.Costs.GetSpan().Fill(1e38f); + cur.End = i + 1; } - /// - /// Gets or sets the number of stored intervals. - /// - public int Count { get; set; } + // Set the initial costs high for every pixel as we will keep the minimum. + this.Costs.GetSpan().Fill(1e38f); + } + + /// + /// Gets or sets the number of stored intervals. + /// + public int Count { get; set; } - /// - /// Gets the costs cache. Contains the GetLengthCost(costModel, k). - /// - public List CostCache { get; } + /// + /// Gets the costs cache. Contains the GetLengthCost(costModel, k). + /// + public List CostCache { get; } - public int CacheIntervalsSize { get; } + public int CacheIntervalsSize { get; } - public IMemoryOwner Costs { get; } + public IMemoryOwner Costs { get; } - public IMemoryOwner DistArray { get; } + public IMemoryOwner DistArray { get; } - public List CacheIntervals { get; } + public List CacheIntervals { get; } - /// - /// Update the cost at index i by going over all the stored intervals that overlap with i. - /// - /// The index to update. - /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. - public void UpdateCostAtIndex(int i, bool doCleanIntervals) + /// + /// Update the cost at index i by going over all the stored intervals that overlap with i. + /// + /// The index to update. + /// If 'doCleanIntervals' is true, intervals that end before 'i' will be popped. + public void UpdateCostAtIndex(int i, bool doCleanIntervals) + { + CostInterval current = this.head; + while (current != null && current.Start <= i) { - CostInterval current = this.head; - while (current != null && current.Start <= i) + CostInterval next = current.Next; + if (current.End <= i) { - CostInterval next = current.Next; - if (current.End <= i) - { - if (doCleanIntervals) - { - // We have an outdated interval, remove it. - this.PopInterval(current); - } - } - else + if (doCleanIntervals) { - this.UpdateCost(i, current.Index, current.Cost); + // We have an outdated interval, remove it. + this.PopInterval(current); } - - current = next; } + else + { + this.UpdateCost(i, current.Index, current.Cost); + } + + current = next; } + } - /// - /// Given a new cost interval defined by its start at position, its length value - /// and distanceCost, add its contributions to the previous intervals and costs. - /// If handling the interval or one of its sub-intervals becomes to heavy, its - /// contribution is added to the costs right away. - /// - public void PushInterval(double distanceCost, int position, int len) - { - // If the interval is small enough, no need to deal with the heavy - // interval logic, just serialize it right away. This constant is empirical. - int skipDistance = 10; + /// + /// Given a new cost interval defined by its start at position, its length value + /// and distanceCost, add its contributions to the previous intervals and costs. + /// If handling the interval or one of its sub-intervals becomes to heavy, its + /// contribution is added to the costs right away. + /// + public void PushInterval(double distanceCost, int position, int len) + { + // If the interval is small enough, no need to deal with the heavy + // interval logic, just serialize it right away. This constant is empirical. + int skipDistance = 10; - Span costs = this.Costs.GetSpan(); - Span distArray = this.DistArray.GetSpan(); - if (len < skipDistance) + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); + if (len < skipDistance) + { + for (int j = position; j < position + len; j++) { - for (int j = position; j < position + len; j++) - { - int k = j - position; - float costTmp = (float)(distanceCost + this.CostCache[k]); + int k = j - position; + float costTmp = (float)(distanceCost + this.CostCache[k]); - if (costs[j] > costTmp) - { - costs[j] = costTmp; - distArray[j] = (ushort)(k + 1); - } + if (costs[j] > costTmp) + { + costs[j] = costTmp; + distArray[j] = (ushort)(k + 1); } - - return; } - CostInterval interval = this.head; - for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) + return; + } + + CostInterval interval = this.head; + for (int i = 0; i < this.CacheIntervalsSize && this.CacheIntervals[i].Start < len; i++) + { + // Define the intersection of the ith interval with the new one. + int start = position + this.CacheIntervals[i].Start; + int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); + float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); + + CostInterval intervalNext; + for (; interval != null && interval.Start < end; interval = intervalNext) { - // Define the intersection of the ith interval with the new one. - int start = position + this.CacheIntervals[i].Start; - int end = position + (this.CacheIntervals[i].End > len ? len : this.CacheIntervals[i].End); - float cost = (float)(distanceCost + this.CacheIntervals[i].Cost); + intervalNext = interval.Next; - CostInterval intervalNext; - for (; interval != null && interval.Start < end; interval = intervalNext) + // Make sure we have some overlap. + if (start >= interval.End) { - intervalNext = interval.Next; + continue; + } - // Make sure we have some overlap. - if (start >= interval.End) + if (cost >= interval.Cost) + { + // If we are worse than what we already have, add whatever we have so far up to interval. + int startNew = interval.End; + this.InsertInterval(interval, cost, position, start, interval.Start); + start = startNew; + if (start >= end) { - continue; + break; } - if (cost >= interval.Cost) - { - // If we are worse than what we already have, add whatever we have so far up to interval. - int startNew = interval.End; - this.InsertInterval(interval, cost, position, start, interval.Start); - start = startNew; - if (start >= end) - { - break; - } - - continue; - } + continue; + } - if (start <= interval.Start) + if (start <= interval.Start) + { + if (interval.End <= end) { - if (interval.End <= end) - { - // We can safely remove the old interval as it is fully included. - this.PopInterval(interval); - } - else - { - interval.Start = end; - break; - } + // We can safely remove the old interval as it is fully included. + this.PopInterval(interval); } else { - if (end < interval.End) - { - // We have to split the old interval as it fully contains the new one. - int endOriginal = interval.End; - interval.End = start; - this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); - break; - } - - interval.End = start; + interval.Start = end; + break; } } + else + { + if (end < interval.End) + { + // We have to split the old interval as it fully contains the new one. + int endOriginal = interval.End; + interval.End = start; + this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); + break; + } - // Insert the remaining interval from start to end. - this.InsertInterval(interval, cost, position, start, end); + interval.End = start; + } } + + // Insert the remaining interval from start to end. + this.InsertInterval(interval, cost, position, start, end); } + } - /// - /// Pop an interval from the manager. - /// - /// The interval to remove. - private void PopInterval(CostInterval interval) + /// + /// Pop an interval from the manager. + /// + /// The interval to remove. + private void PopInterval(CostInterval interval) + { + if (interval == null) { - if (interval == null) - { - return; - } - - this.ConnectIntervals(interval.Previous, interval.Next); - this.Count--; - - interval.Next = null; - interval.Previous = null; - this.freeIntervals.Push(interval); + return; } - private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) - { - if (start >= end) - { - return; - } + this.ConnectIntervals(interval.Previous, interval.Next); + this.Count--; - // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? - CostInterval intervalNew; - if (this.freeIntervals.Count > 0) - { - intervalNew = this.freeIntervals.Pop(); - intervalNew.Cost = cost; - intervalNew.Start = start; - intervalNew.End = end; - intervalNew.Index = position; - } - else - { - intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position }; - } + interval.Next = null; + interval.Previous = null; + this.freeIntervals.Push(interval); + } - this.PositionOrphanInterval(intervalNew, intervalIn); - this.Count++; + private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) + { + if (start >= end) + { + return; } - /// - /// Given a current orphan interval and its previous interval, before - /// it was orphaned (which can be NULL), set it at the right place in the list - /// of intervals using the start_ ordering and the previous interval as a hint. - /// - private void PositionOrphanInterval(CostInterval current, CostInterval previous) + // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? + CostInterval intervalNew; + if (this.freeIntervals.Count > 0) + { + intervalNew = this.freeIntervals.Pop(); + intervalNew.Cost = cost; + intervalNew.Start = start; + intervalNew.End = end; + intervalNew.Index = position; + } + else { - previous ??= this.head; + intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position }; + } - while (previous != null && current.Start < previous.Start) - { - previous = previous.Previous; - } + this.PositionOrphanInterval(intervalNew, intervalIn); + this.Count++; + } - while (previous?.Next != null && previous.Next.Start < current.Start) - { - previous = previous.Next; - } + /// + /// Given a current orphan interval and its previous interval, before + /// it was orphaned (which can be NULL), set it at the right place in the list + /// of intervals using the start_ ordering and the previous interval as a hint. + /// + private void PositionOrphanInterval(CostInterval current, CostInterval previous) + { + previous ??= this.head; - this.ConnectIntervals(current, previous != null ? previous.Next : this.head); - this.ConnectIntervals(previous, current); + while (previous != null && current.Start < previous.Start) + { + previous = previous.Previous; } - /// - /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. - /// - private void ConnectIntervals(CostInterval prev, CostInterval next) + while (previous?.Next != null && previous.Next.Start < current.Start) { - if (prev != null) - { - prev.Next = next; - } - else - { - this.head = next; - } + previous = previous.Next; + } - if (next != null) - { - next.Previous = prev; - } + this.ConnectIntervals(current, previous != null ? previous.Next : this.head); + this.ConnectIntervals(previous, current); + } + + /// + /// Given two intervals, make 'prev' be the previous one of 'next' in 'manager'. + /// + private void ConnectIntervals(CostInterval prev, CostInterval next) + { + if (prev != null) + { + prev.Next = next; + } + else + { + this.head = next; } - /// - /// Given the cost and the position that define an interval, update the cost at - /// pixel 'i' if it is smaller than the previously computed value. - /// - private void UpdateCost(int i, int position, float cost) + if (next != null) { - Span costs = this.Costs.GetSpan(); - Span distArray = this.DistArray.GetSpan(); - int k = i - position; - if (costs[i] > cost) - { - costs[i] = cost; - distArray[i] = (ushort)(k + 1); - } + next.Previous = prev; } + } - /// - public void Dispose() => this.Costs.Dispose(); + /// + /// Given the cost and the position that define an interval, update the cost at + /// pixel 'i' if it is smaller than the previously computed value. + /// + private void UpdateCost(int i, int position, float cost) + { + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); + int k = i - position; + if (costs[i] > cost) + { + costs[i] = cost; + distArray[i] = (ushort)(k + 1); + } } + + /// + public void Dispose() => this.Costs.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs index 288dec4e62..c99e8fe6e2 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs @@ -1,101 +1,98 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +internal class CostModel { - internal class CostModel + private const int ValuesInBytes = 256; + + /// + /// Initializes a new instance of the class. + /// + /// The literal array size. + public CostModel(int literalArraySize) { - private const int ValuesInBytes = 256; + this.Alpha = new double[ValuesInBytes]; + this.Red = new double[ValuesInBytes]; + this.Blue = new double[ValuesInBytes]; + this.Distance = new double[WebpConstants.NumDistanceCodes]; + this.Literal = new double[literalArraySize]; + } - /// - /// Initializes a new instance of the class. - /// - /// The literal array size. - public CostModel(int literalArraySize) - { - this.Alpha = new double[ValuesInBytes]; - this.Red = new double[ValuesInBytes]; - this.Blue = new double[ValuesInBytes]; - this.Distance = new double[WebpConstants.NumDistanceCodes]; - this.Literal = new double[literalArraySize]; - } + public double[] Alpha { get; } - public double[] Alpha { get; } + public double[] Red { get; } - public double[] Red { get; } + public double[] Blue { get; } - public double[] Blue { get; } + public double[] Distance { get; } - public double[] Distance { get; } + public double[] Literal { get; } - public double[] Literal { get; } + public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs) + { + var histogram = new Vp8LHistogram(cacheBits); + using System.Collections.Generic.List.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator(); - public void Build(int xSize, int cacheBits, Vp8LBackwardRefs backwardRefs) + // The following code is similar to HistogramCreate but converts the distance to plane code. + while (refsEnumerator.MoveNext()) { - var histogram = new Vp8LHistogram(cacheBits); - using System.Collections.Generic.List.Enumerator refsEnumerator = backwardRefs.Refs.GetEnumerator(); + histogram.AddSinglePixOrCopy(refsEnumerator.Current, true, xSize); + } - // The following code is similar to HistogramCreate but converts the distance to plane code. - while (refsEnumerator.MoveNext()) - { - histogram.AddSinglePixOrCopy(refsEnumerator.Current, true, xSize); - } + ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue); + ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha); + ConvertPopulationCountTableToBitEstimates(WebpConstants.NumDistanceCodes, histogram.Distance, this.Distance); + } - ConvertPopulationCountTableToBitEstimates(histogram.NumCodes(), histogram.Literal, this.Literal); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Red, this.Red); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Blue, this.Blue); - ConvertPopulationCountTableToBitEstimates(ValuesInBytes, histogram.Alpha, this.Alpha); - ConvertPopulationCountTableToBitEstimates(WebpConstants.NumDistanceCodes, histogram.Distance, this.Distance); - } + public double GetLengthCost(int length) + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits); + return this.Literal[ValuesInBytes + code] + extraBits; + } - public double GetLengthCost(int length) - { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(length, ref extraBits); - return this.Literal[ValuesInBytes + code] + extraBits; - } + public double GetDistanceCost(int distance) + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits); + return this.Distance[code] + extraBits; + } + + public double GetCacheCost(uint idx) + { + int literalIdx = (int)(ValuesInBytes + WebpConstants.NumLengthCodes + idx); + return this.Literal[literalIdx]; + } - public double GetDistanceCost(int distance) + public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; + + private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) + { + uint sum = 0; + int nonzeros = 0; + for (int i = 0; i < numSymbols; i++) { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(distance, ref extraBits); - return this.Distance[code] + extraBits; + sum += populationCounts[i]; + if (populationCounts[i] > 0) + { + nonzeros++; + } } - public double GetCacheCost(uint idx) + if (nonzeros <= 1) { - int literalIdx = (int)(ValuesInBytes + WebpConstants.NumLengthCodes + idx); - return this.Literal[literalIdx]; + output.AsSpan(0, numSymbols).Clear(); } - - public double GetLiteralCost(uint v) => this.Alpha[v >> 24] + this.Red[(v >> 16) & 0xff] + this.Literal[(v >> 8) & 0xff] + this.Blue[v & 0xff]; - - private static void ConvertPopulationCountTableToBitEstimates(int numSymbols, uint[] populationCounts, double[] output) + else { - uint sum = 0; - int nonzeros = 0; + double logsum = LosslessUtils.FastLog2(sum); for (int i = 0; i < numSymbols; i++) { - sum += populationCounts[i]; - if (populationCounts[i] > 0) - { - nonzeros++; - } - } - - if (nonzeros <= 1) - { - output.AsSpan(0, numSymbols).Clear(); - } - else - { - double logsum = LosslessUtils.FastLog2(sum); - for (int i = 0; i < numSymbols; i++) - { - output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]); - } + output[i] = logsum - LosslessUtils.FastLog2(populationCounts[i]); } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs index b595a44d3e..7488f03ca4 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CrunchConfig.cs @@ -1,14 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +internal class CrunchConfig { - internal class CrunchConfig - { - public EntropyIx EntropyIdx { get; set; } + public EntropyIx EntropyIdx { get; set; } - public List SubConfigs { get; } = new List(); - } + public List SubConfigs { get; } = new List(); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs b/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs index a5640fe869..e442fbe106 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CrunchSubConfig.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal class CrunchSubConfig { - internal class CrunchSubConfig - { - public int Lz77 { get; set; } + public int Lz77 { get; set; } - public bool DoNotCache { get; set; } - } + public bool DoNotCache { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs b/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs index 043e172a23..02fa356948 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/DominantCostRange.cs @@ -1,92 +1,91 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Data container to keep track of cost range for the three dominant entropy symbols. +/// +internal class DominantCostRange { /// - /// Data container to keep track of cost range for the three dominant entropy symbols. + /// Initializes a new instance of the class. /// - internal class DominantCostRange + public DominantCostRange() { - /// - /// Initializes a new instance of the class. - /// - public DominantCostRange() - { - this.LiteralMax = 0.0d; - this.LiteralMin = double.MaxValue; - this.RedMax = 0.0d; - this.RedMin = double.MaxValue; - this.BlueMax = 0.0d; - this.BlueMin = double.MaxValue; - } + this.LiteralMax = 0.0d; + this.LiteralMin = double.MaxValue; + this.RedMax = 0.0d; + this.RedMin = double.MaxValue; + this.BlueMax = 0.0d; + this.BlueMin = double.MaxValue; + } + + public double LiteralMax { get; set; } + + public double LiteralMin { get; set; } - public double LiteralMax { get; set; } + public double RedMax { get; set; } - public double LiteralMin { get; set; } + public double RedMin { get; set; } - public double RedMax { get; set; } + public double BlueMax { get; set; } - public double RedMin { get; set; } + public double BlueMin { get; set; } - public double BlueMax { get; set; } + public void UpdateDominantCostRange(Vp8LHistogram h) + { + if (this.LiteralMax < h.LiteralCost) + { + this.LiteralMax = h.LiteralCost; + } + + if (this.LiteralMin > h.LiteralCost) + { + this.LiteralMin = h.LiteralCost; + } - public double BlueMin { get; set; } + if (this.RedMax < h.RedCost) + { + this.RedMax = h.RedCost; + } - public void UpdateDominantCostRange(Vp8LHistogram h) + if (this.RedMin > h.RedCost) { - if (this.LiteralMax < h.LiteralCost) - { - this.LiteralMax = h.LiteralCost; - } - - if (this.LiteralMin > h.LiteralCost) - { - this.LiteralMin = h.LiteralCost; - } - - if (this.RedMax < h.RedCost) - { - this.RedMax = h.RedCost; - } - - if (this.RedMin > h.RedCost) - { - this.RedMin = h.RedCost; - } - - if (this.BlueMax < h.BlueCost) - { - this.BlueMax = h.BlueCost; - } - - if (this.BlueMin > h.BlueCost) - { - this.BlueMin = h.BlueCost; - } + this.RedMin = h.RedCost; } - public int GetHistoBinIndex(Vp8LHistogram h, int numPartitions) + if (this.BlueMax < h.BlueCost) { - int binId = GetBinIdForEntropy(this.LiteralMin, this.LiteralMax, h.LiteralCost, numPartitions); - binId = (binId * numPartitions) + GetBinIdForEntropy(this.RedMin, this.RedMax, h.RedCost, numPartitions); - binId = (binId * numPartitions) + GetBinIdForEntropy(this.BlueMin, this.BlueMax, h.BlueCost, numPartitions); + this.BlueMax = h.BlueCost; + } - return binId; + if (this.BlueMin > h.BlueCost) + { + this.BlueMin = h.BlueCost; } + } + + public int GetHistoBinIndex(Vp8LHistogram h, int numPartitions) + { + int binId = GetBinIdForEntropy(this.LiteralMin, this.LiteralMax, h.LiteralCost, numPartitions); + binId = (binId * numPartitions) + GetBinIdForEntropy(this.RedMin, this.RedMax, h.RedCost, numPartitions); + binId = (binId * numPartitions) + GetBinIdForEntropy(this.BlueMin, this.BlueMax, h.BlueCost, numPartitions); + + return binId; + } - private static int GetBinIdForEntropy(double min, double max, double val, int numPartitions) + private static int GetBinIdForEntropy(double min, double max, double val, int numPartitions) + { + double range = max - min; + if (range > 0.0d) + { + double delta = val - min; + return (int)((numPartitions - 1e-6) * delta / range); + } + else { - double range = max - min; - if (range > 0.0d) - { - double delta = val - min; - return (int)((numPartitions - 1e-6) * delta / range); - } - else - { - return 0; - } + return 0; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs index 22a6d77163..5806ee5b5c 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs @@ -1,59 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; - -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Huffman table group. +/// Includes special handling for the following cases: +/// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN) +/// - IsTrivialCode: only 1 code (no bit is read from the bitstream) +/// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] +/// The common literal base, if applicable, is stored in 'LiteralArb'. +/// +internal struct HTreeGroup { - /// - /// Huffman table group. - /// Includes special handling for the following cases: - /// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN) - /// - IsTrivialCode: only 1 code (no bit is read from the bitstream) - /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] - /// The common literal base, if applicable, is stored in 'LiteralArb'. - /// - internal struct HTreeGroup + public HTreeGroup(uint packedTableSize) { - public HTreeGroup(uint packedTableSize) - { - this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); - this.PackedTable = new HuffmanCode[packedTableSize]; - this.IsTrivialCode = false; - this.IsTrivialLiteral = false; - this.LiteralArb = 0; - this.UsePackedTable = false; - } - - /// - /// Gets the Huffman trees. This has a maximum of (5) entry's. - /// - public List HTrees { get; } - - /// - /// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code). - /// - public bool IsTrivialLiteral { get; set; } - - /// - /// Gets or sets a the literal argb value of the pixel. - /// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero. - /// - public uint LiteralArb { get; set; } - - /// - /// Gets or sets a value indicating whether there is only one code. - /// - public bool IsTrivialCode { get; set; } - - /// - /// Gets or sets a value indicating whether to use packed table below for short literal code. - /// - public bool UsePackedTable { get; set; } - - /// - /// Gets or sets table mapping input bits to packed values, or escape case to literal code. - /// - public HuffmanCode[] PackedTable { get; set; } + this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); + this.PackedTable = new HuffmanCode[packedTableSize]; + this.IsTrivialCode = false; + this.IsTrivialLiteral = false; + this.LiteralArb = 0; + this.UsePackedTable = false; } + + /// + /// Gets the Huffman trees. This has a maximum of (5) entry's. + /// + public List HTrees { get; } + + /// + /// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code). + /// + public bool IsTrivialLiteral { get; set; } + + /// + /// Gets or sets a the literal argb value of the pixel. + /// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero. + /// + public uint LiteralArb { get; set; } + + /// + /// Gets or sets a value indicating whether there is only one code. + /// + public bool IsTrivialCode { get; set; } + + /// + /// Gets or sets a value indicating whether to use packed table below for short literal code. + /// + public bool UsePackedTable { get; set; } + + /// + /// Gets or sets table mapping input bits to packed values, or escape case to literal code. + /// + public HuffmanCode[] PackedTable { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs index bbe7296adc..b354cabf17 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramBinInfo.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal struct HistogramBinInfo { - internal struct HistogramBinInfo - { - /// - /// Position of the histogram that accumulates all histograms with the same binId. - /// - public short First; + /// + /// Position of the histogram that accumulates all histograms with the same binId. + /// + public short First; - /// - /// Number of combine failures per binId. - /// - public ushort NumCombineFailures; - } + /// + /// Number of combine failures per binId. + /// + public ushort NumCombineFailures; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs index 81e246c628..45cbcfa94f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs @@ -1,702 +1,698 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal class HistogramEncoder { - internal class HistogramEncoder - { - /// - /// Number of partitions for the three dominant (literal, red and blue) symbol costs. - /// - private const int NumPartitions = 4; + /// + /// Number of partitions for the three dominant (literal, red and blue) symbol costs. + /// + private const int NumPartitions = 4; - /// - /// The size of the bin-hash corresponding to the three dominant costs. - /// - private const int BinSize = NumPartitions * NumPartitions * NumPartitions; + /// + /// The size of the bin-hash corresponding to the three dominant costs. + /// + private const int BinSize = NumPartitions * NumPartitions * NumPartitions; - /// - /// Maximum number of histograms allowed in greedy combining algorithm. - /// - private const int MaxHistoGreedy = 100; + /// + /// Maximum number of histograms allowed in greedy combining algorithm. + /// + private const int MaxHistoGreedy = 100; - private const uint NonTrivialSym = 0xffffffff; + private const uint NonTrivialSym = 0xffffffff; - private const ushort InvalidHistogramSymbol = ushort.MaxValue; + private const ushort InvalidHistogramSymbol = ushort.MaxValue; - public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols) + public static void GetHistoImageSymbols(int xSize, int ySize, Vp8LBackwardRefs refs, int quality, int histoBits, int cacheBits, List imageHisto, Vp8LHistogram tmpHisto, ushort[] histogramSymbols) + { + int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; + int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; + int imageHistoRawSize = histoXSize * histoYSize; + int entropyCombineNumBins = BinSize; + ushort[] mapTmp = new ushort[imageHistoRawSize]; + ushort[] clusterMappings = new ushort[imageHistoRawSize]; + var origHisto = new List(imageHistoRawSize); + for (int i = 0; i < imageHistoRawSize; i++) { - int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(xSize, histoBits) : 1; - int histoYSize = histoBits > 0 ? LosslessUtils.SubSampleSize(ySize, histoBits) : 1; - int imageHistoRawSize = histoXSize * histoYSize; - int entropyCombineNumBins = BinSize; - ushort[] mapTmp = new ushort[imageHistoRawSize]; - ushort[] clusterMappings = new ushort[imageHistoRawSize]; - var origHisto = new List(imageHistoRawSize); - for (int i = 0; i < imageHistoRawSize; i++) - { - origHisto.Add(new Vp8LHistogram(cacheBits)); - } - - // Construct the histograms from the backward references. - HistogramBuild(xSize, histoBits, refs, origHisto); + origHisto.Add(new Vp8LHistogram(cacheBits)); + } - // Copies the histograms and computes its bitCost. histogramSymbols is optimized. - int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); + // Construct the histograms from the backward references. + HistogramBuild(xSize, histoBits, refs, origHisto); - bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; - if (entropyCombine) - { - ushort[] binMap = mapTmp; - int numClusters = numUsed; - double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); - HistogramAnalyzeEntropyBin(imageHisto, binMap); + // Copies the histograms and computes its bitCost. histogramSymbols is optimized. + int numUsed = HistogramCopyAndAnalyze(origHisto, imageHisto, histogramSymbols); - // Collapse histograms with similar entropy. - HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); + bool entropyCombine = numUsed > entropyCombineNumBins * 2 && quality < 100; + if (entropyCombine) + { + ushort[] binMap = mapTmp; + int numClusters = numUsed; + double combineCostFactor = GetCombineCostFactor(imageHistoRawSize, quality); + HistogramAnalyzeEntropyBin(imageHisto, binMap); - OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); - } + // Collapse histograms with similar entropy. + HistogramCombineEntropyBin(imageHisto, histogramSymbols, clusterMappings, tmpHisto, binMap, entropyCombineNumBins, combineCostFactor); - float x = quality / 100.0f; + OptimizeHistogramSymbols(clusterMappings, numClusters, mapTmp, histogramSymbols); + } - // Cubic ramp between 1 and MaxHistoGreedy: - int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); - bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); - if (doGreedy) - { - RemoveEmptyHistograms(imageHisto); - HistogramCombineGreedy(imageHisto); - } + float x = quality / 100.0f; - // Find the optimal map from original histograms to the final ones. + // Cubic ramp between 1 and MaxHistoGreedy: + int thresholdSize = (int)(1 + (x * x * x * (MaxHistoGreedy - 1))); + bool doGreedy = HistogramCombineStochastic(imageHisto, thresholdSize); + if (doGreedy) + { RemoveEmptyHistograms(imageHisto); - HistogramRemap(origHisto, imageHisto, histogramSymbols); + HistogramCombineGreedy(imageHisto); } - private static void RemoveEmptyHistograms(List histograms) + // Find the optimal map from original histograms to the final ones. + RemoveEmptyHistograms(imageHisto); + HistogramRemap(origHisto, imageHisto, histogramSymbols); + } + + private static void RemoveEmptyHistograms(List histograms) + { + int size = 0; + for (int i = 0; i < histograms.Count; i++) { - int size = 0; - for (int i = 0; i < histograms.Count; i++) + if (histograms[i] == null) { - if (histograms[i] == null) - { - continue; - } - - histograms[size++] = histograms[i]; + continue; } - histograms.RemoveRange(size, histograms.Count - size); + histograms[size++] = histograms[i]; } - /// - /// Construct the histograms from the backward references. - /// - private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms) + histograms.RemoveRange(size, histograms.Count - size); + } + + /// + /// Construct the histograms from the backward references. + /// + private static void HistogramBuild(int xSize, int histoBits, Vp8LBackwardRefs backwardRefs, List histograms) + { + int x = 0, y = 0; + int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); + using List.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator(); + while (backwardRefsEnumerator.MoveNext()) { - int x = 0, y = 0; - int histoXSize = LosslessUtils.SubSampleSize(xSize, histoBits); - using List.Enumerator backwardRefsEnumerator = backwardRefs.Refs.GetEnumerator(); - while (backwardRefsEnumerator.MoveNext()) + PixOrCopy v = backwardRefsEnumerator.Current; + int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); + histograms[ix].AddSinglePixOrCopy(v, false); + x += v.Len; + while (x >= xSize) { - PixOrCopy v = backwardRefsEnumerator.Current; - int ix = ((y >> histoBits) * histoXSize) + (x >> histoBits); - histograms[ix].AddSinglePixOrCopy(v, false); - x += v.Len; - while (x >= xSize) - { - x -= xSize; - y++; - } + x -= xSize; + y++; } } + } - /// - /// Partition histograms to different entropy bins for three dominant (literal, - /// red and blue) symbol costs and compute the histogram aggregate bitCost. - /// - private static void HistogramAnalyzeEntropyBin(List histograms, ushort[] binMap) - { - int histoSize = histograms.Count; - var costRange = new DominantCostRange(); + /// + /// Partition histograms to different entropy bins for three dominant (literal, + /// red and blue) symbol costs and compute the histogram aggregate bitCost. + /// + private static void HistogramAnalyzeEntropyBin(List histograms, ushort[] binMap) + { + int histoSize = histograms.Count; + var costRange = new DominantCostRange(); - // Analyze the dominant (literal, red and blue) entropy costs. - for (int i = 0; i < histoSize; i++) + // Analyze the dominant (literal, red and blue) entropy costs. + for (int i = 0; i < histoSize; i++) + { + if (histograms[i] == null) { - if (histograms[i] == null) - { - continue; - } - - costRange.UpdateDominantCostRange(histograms[i]); + continue; } - // bin-hash histograms on three of the dominant (literal, red and blue) - // symbol costs and store the resulting bin_id for each histogram. - for (int i = 0; i < histoSize; i++) - { - if (histograms[i] == null) - { - continue; - } - - binMap[i] = (ushort)costRange.GetHistoBinIndex(histograms[i], NumPartitions); - } + costRange.UpdateDominantCostRange(histograms[i]); } - private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, ushort[] histogramSymbols) + // bin-hash histograms on three of the dominant (literal, red and blue) + // symbol costs and store the resulting bin_id for each histogram. + for (int i = 0; i < histoSize; i++) { - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) + if (histograms[i] == null) { - Vp8LHistogram origHistogram = origHistograms[i]; - origHistogram.UpdateHistogramCost(stats, bitsEntropy); - - // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77). - if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4]) - { - origHistograms[i] = null; - histograms[i] = null; - histogramSymbols[i] = InvalidHistogramSymbol; - } - else - { - histograms[i] = (Vp8LHistogram)origHistogram.DeepClone(); - histogramSymbols[i] = (ushort)clusterId++; - } + continue; } - int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol); - return numUsed; + binMap[i] = (ushort)costRange.GetHistoBinIndex(histograms[i], NumPartitions); } + } - private static void HistogramCombineEntropyBin( - List histograms, - ushort[] clusters, - ushort[] clusterMappings, - Vp8LHistogram curCombo, - ushort[] binMap, - int numBins, - double combineCostFactor) + private static int HistogramCopyAndAnalyze(List origHistograms, List histograms, ushort[] histogramSymbols) + { + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + for (int clusterId = 0, i = 0; i < origHistograms.Count; i++) { - var binInfo = new HistogramBinInfo[BinSize]; - for (int idx = 0; idx < numBins; idx++) - { - binInfo[idx].First = -1; - binInfo[idx].NumCombineFailures = 0; - } + Vp8LHistogram origHistogram = origHistograms[i]; + origHistogram.UpdateHistogramCost(stats, bitsEntropy); - // By default, a cluster matches itself. - for (int idx = 0; idx < histograms.Count; idx++) + // Skip the histogram if it is completely empty, which can happen for tiles with no information (when they are skipped because of LZ77). + if (!origHistogram.IsUsed[0] && !origHistogram.IsUsed[1] && !origHistogram.IsUsed[2] && !origHistogram.IsUsed[3] && !origHistogram.IsUsed[4]) { - clusterMappings[idx] = (ushort)idx; + origHistograms[i] = null; + histograms[i] = null; + histogramSymbols[i] = InvalidHistogramSymbol; } - - var indicesToRemove = new List(); - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - for (int idx = 0; idx < histograms.Count; idx++) + else { - if (histograms[idx] == null) - { - continue; - } + histograms[i] = (Vp8LHistogram)origHistogram.DeepClone(); + histogramSymbols[i] = (ushort)clusterId++; + } + } - int binId = binMap[idx]; - int first = binInfo[binId].First; - if (first == -1) - { - binInfo[binId].First = (short)idx; - } - else - { - // Try to merge #idx into #first (both share the same binId) - double bitCost = histograms[idx].BitCost; - double bitCostThresh = -bitCost * combineCostFactor; - double currCostDiff = histograms[first].AddEval(histograms[idx], stats, bitsEntropy, bitCostThresh, curCombo); + int numUsed = histogramSymbols.Count(h => h != InvalidHistogramSymbol); + return numUsed; + } - if (currCostDiff < bitCostThresh) - { - // Try to merge two histograms only if the combo is a trivial one or - // the two candidate histograms are already non-trivial. - // For some images, 'tryCombine' turns out to be false for a lot of - // histogram pairs. In that case, we fallback to combining - // histograms as usual to avoid increasing the header size. - bool tryCombine = curCombo.TrivialSymbol != NonTrivialSym || (histograms[idx].TrivialSymbol == NonTrivialSym && histograms[first].TrivialSymbol == NonTrivialSym); - int maxCombineFailures = 32; - if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) - { - // Move the (better) merged histogram to its final slot. - Vp8LHistogram tmp = curCombo; - curCombo = histograms[first]; - histograms[first] = tmp; - - histograms[idx] = null; - indicesToRemove.Add(idx); - clusterMappings[clusters[idx]] = clusters[first]; - } - else - { - binInfo[binId].NumCombineFailures++; - } - } - } - } + private static void HistogramCombineEntropyBin( + List histograms, + ushort[] clusters, + ushort[] clusterMappings, + Vp8LHistogram curCombo, + ushort[] binMap, + int numBins, + double combineCostFactor) + { + var binInfo = new HistogramBinInfo[BinSize]; + for (int idx = 0; idx < numBins; idx++) + { + binInfo[idx].First = -1; + binInfo[idx].NumCombineFailures = 0; + } - foreach (int index in indicesToRemove.OrderByDescending(i => i)) - { - histograms.RemoveAt(index); - } + // By default, a cluster matches itself. + for (int idx = 0; idx < histograms.Count; idx++) + { + clusterMappings[idx] = (ushort)idx; } - /// - /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the - /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. - /// - private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols) + var indicesToRemove = new List(); + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + for (int idx = 0; idx < histograms.Count; idx++) { - bool doContinue = true; + if (histograms[idx] == null) + { + continue; + } - // First, assign the lowest cluster to each pixel. - while (doContinue) + int binId = binMap[idx]; + int first = binInfo[binId].First; + if (first == -1) + { + binInfo[binId].First = (short)idx; + } + else { - doContinue = false; - for (int i = 0; i < numClusters; i++) + // Try to merge #idx into #first (both share the same binId) + double bitCost = histograms[idx].BitCost; + double bitCostThresh = -bitCost * combineCostFactor; + double currCostDiff = histograms[first].AddEval(histograms[idx], stats, bitsEntropy, bitCostThresh, curCombo); + + if (currCostDiff < bitCostThresh) { - int k = clusterMappings[i]; - while (k != clusterMappings[k]) + // Try to merge two histograms only if the combo is a trivial one or + // the two candidate histograms are already non-trivial. + // For some images, 'tryCombine' turns out to be false for a lot of + // histogram pairs. In that case, we fallback to combining + // histograms as usual to avoid increasing the header size. + bool tryCombine = curCombo.TrivialSymbol != NonTrivialSym || (histograms[idx].TrivialSymbol == NonTrivialSym && histograms[first].TrivialSymbol == NonTrivialSym); + int maxCombineFailures = 32; + if (tryCombine || binInfo[binId].NumCombineFailures >= maxCombineFailures) { - clusterMappings[k] = clusterMappings[clusterMappings[k]]; - k = clusterMappings[k]; + // Move the (better) merged histogram to its final slot. + Vp8LHistogram tmp = curCombo; + curCombo = histograms[first]; + histograms[first] = tmp; + + histograms[idx] = null; + indicesToRemove.Add(idx); + clusterMappings[clusters[idx]] = clusters[first]; } - - if (k != clusterMappings[i]) + else { - doContinue = true; - clusterMappings[i] = (ushort)k; + binInfo[binId].NumCombineFailures++; } } } + } + + foreach (int index in indicesToRemove.OrderByDescending(i => i)) + { + histograms.RemoveAt(index); + } + } - // Create a mapping from a cluster id to its minimal version. - int clusterMax = 0; - clusterMappingsTmp.AsSpan().Clear(); + /// + /// Given a Histogram set, the mapping of clusters 'clusterMapping' and the + /// current assignment of the cells in 'symbols', merge the clusters and assign the smallest possible clusters values. + /// + private static void OptimizeHistogramSymbols(ushort[] clusterMappings, int numClusters, ushort[] clusterMappingsTmp, ushort[] symbols) + { + bool doContinue = true; - // Re-map the ids. - for (int i = 0; i < symbols.Length; i++) + // First, assign the lowest cluster to each pixel. + while (doContinue) + { + doContinue = false; + for (int i = 0; i < numClusters; i++) { - if (symbols[i] == InvalidHistogramSymbol) + int k = clusterMappings[i]; + while (k != clusterMappings[k]) { - continue; + clusterMappings[k] = clusterMappings[clusterMappings[k]]; + k = clusterMappings[k]; } - int cluster = clusterMappings[symbols[i]]; - if (cluster > 0 && clusterMappingsTmp[cluster] == 0) + if (k != clusterMappings[i]) { - clusterMax++; - clusterMappingsTmp[cluster] = (ushort)clusterMax; + doContinue = true; + clusterMappings[i] = (ushort)k; } + } + } + + // Create a mapping from a cluster id to its minimal version. + int clusterMax = 0; + clusterMappingsTmp.AsSpan().Clear(); - symbols[i] = clusterMappingsTmp[cluster]; + // Re-map the ids. + for (int i = 0; i < symbols.Length; i++) + { + if (symbols[i] == InvalidHistogramSymbol) + { + continue; } + + int cluster = clusterMappings[symbols[i]]; + if (cluster > 0 && clusterMappingsTmp[cluster] == 0) + { + clusterMax++; + clusterMappingsTmp[cluster] = (ushort)clusterMax; + } + + symbols[i] = clusterMappingsTmp[cluster]; + } + } + + /// + /// Perform histogram aggregation using a stochastic approach. + /// + /// true if a greedy approach needs to be performed afterwards, false otherwise. + private static bool HistogramCombineStochastic(List histograms, int minClusterSize) + { + uint seed = 1; + int triesWithNoSuccess = 0; + int numUsed = histograms.Count(h => h != null); + int outerIters = numUsed; + int numTriesNoSuccess = outerIters / 2; + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + + if (numUsed < minClusterSize) + { + return true; } - /// - /// Perform histogram aggregation using a stochastic approach. - /// - /// true if a greedy approach needs to be performed afterwards, false otherwise. - private static bool HistogramCombineStochastic(List histograms, int minClusterSize) + // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: + // the smaller the faster but the worse for the compression. + var histoPriorityList = new List(); + int maxSize = 9; + + // Fill the initial mapping. + int[] mappings = new int[histograms.Count]; + for (int j = 0, iter = 0; iter < histograms.Count; iter++) { - uint seed = 1; - int triesWithNoSuccess = 0; - int numUsed = histograms.Count(h => h != null); - int outerIters = numUsed; - int numTriesNoSuccess = outerIters / 2; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - - if (numUsed < minClusterSize) + if (histograms[iter] == null) { - return true; + continue; } - // Priority list of histogram pairs. Its size impacts the quality of the compression and the speed: - // the smaller the faster but the worse for the compression. - var histoPriorityList = new List(); - int maxSize = 9; + mappings[j++] = iter; + } + + // Collapse similar histograms. + for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) + { + double bestCost = histoPriorityList.Count == 0 ? 0.0d : histoPriorityList[0].CostDiff; + int numTries = numUsed / 2; + uint randRange = (uint)((numUsed - 1) * numUsed); - // Fill the initial mapping. - int[] mappings = new int[histograms.Count]; - for (int j = 0, iter = 0; iter < histograms.Count; iter++) + // Pick random samples. + for (int j = 0; numUsed >= 2 && j < numTries; j++) { - if (histograms[iter] == null) + // Choose two different histograms at random and try to combine them. + uint tmp = MyRand(ref seed) % randRange; + int idx1 = (int)(tmp / (numUsed - 1)); + int idx2 = (int)(tmp % (numUsed - 1)); + if (idx2 >= idx1) { - continue; + idx2++; } - mappings[j++] = iter; - } + idx1 = mappings[idx1]; + idx2 = mappings[idx2]; - // Collapse similar histograms. - for (int iter = 0; iter < outerIters && numUsed >= minClusterSize && ++triesWithNoSuccess < numTriesNoSuccess; iter++) - { - double bestCost = histoPriorityList.Count == 0 ? 0.0d : histoPriorityList[0].CostDiff; - int numTries = numUsed / 2; - uint randRange = (uint)((numUsed - 1) * numUsed); + // Calculate cost reduction on combination. + double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy); - // Pick random samples. - for (int j = 0; numUsed >= 2 && j < numTries; j++) + // Found a better pair? + if (currCost < 0) { - // Choose two different histograms at random and try to combine them. - uint tmp = MyRand(ref seed) % randRange; - int idx1 = (int)(tmp / (numUsed - 1)); - int idx2 = (int)(tmp % (numUsed - 1)); - if (idx2 >= idx1) + bestCost = currCost; + + if (histoPriorityList.Count == maxSize) { - idx2++; + break; } + } + } - idx1 = mappings[idx1]; - idx2 = mappings[idx2]; + if (histoPriorityList.Count == 0) + { + continue; + } - // Calculate cost reduction on combination. - double currCost = HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, idx2, bestCost, stats, bitsEntropy); + // Get the best histograms. + int bestIdx1 = histoPriorityList[0].Idx1; + int bestIdx2 = histoPriorityList[0].Idx2; - // Found a better pair? - if (currCost < 0) - { - bestCost = currCost; + int mappingIndex = Array.IndexOf(mappings, bestIdx2); + Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); + Span dst = mappings.AsSpan(mappingIndex); + src.CopyTo(dst); - if (histoPriorityList.Count == maxSize) - { - break; - } - } - } + // Merge the histograms and remove bestIdx2 from the list. + HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); + histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; + histograms[bestIdx2] = null; + numUsed--; - if (histoPriorityList.Count == 0) + for (int j = 0; j < histoPriorityList.Count;) + { + HistogramPair p = histoPriorityList[j]; + bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; + bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; + bool doEval = false; + + // The front pair could have been duplicated by a random pick so + // check for it all the time nevertheless. + if (isIdx1Best && isIdx2Best) { + histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } - // Get the best histograms. - int bestIdx1 = histoPriorityList[0].Idx1; - int bestIdx2 = histoPriorityList[0].Idx2; - - int mappingIndex = Array.IndexOf(mappings, bestIdx2); - Span src = mappings.AsSpan(mappingIndex + 1, numUsed - mappingIndex - 1); - Span dst = mappings.AsSpan(mappingIndex); - src.CopyTo(dst); + // Any pair containing one of the two best indices should only refer to + // bestIdx1. Its cost should also be updated. + if (isIdx1Best) + { + p.Idx1 = bestIdx1; + doEval = true; + } + else if (isIdx2Best) + { + p.Idx2 = bestIdx1; + doEval = true; + } - // Merge the histograms and remove bestIdx2 from the list. - HistogramAdd(histograms[bestIdx2], histograms[bestIdx1], histograms[bestIdx1]); - histograms.ElementAt(bestIdx1).BitCost = histoPriorityList[0].CostCombo; - histograms[bestIdx2] = null; - numUsed--; + // Make sure the index order is respected. + if (p.Idx1 > p.Idx2) + { + int tmp = p.Idx2; + p.Idx2 = p.Idx1; + p.Idx1 = tmp; + } - for (int j = 0; j < histoPriorityList.Count;) + if (doEval) { - HistogramPair p = histoPriorityList[j]; - bool isIdx1Best = p.Idx1 == bestIdx1 || p.Idx1 == bestIdx2; - bool isIdx2Best = p.Idx2 == bestIdx1 || p.Idx2 == bestIdx2; - bool doEval = false; - - // The front pair could have been duplicated by a random pick so - // check for it all the time nevertheless. - if (isIdx1Best && isIdx2Best) + // Re-evaluate the cost of an updated pair. + HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p); + if (p.CostDiff >= 0.0d) { histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; histoPriorityList.RemoveAt(histoPriorityList.Count - 1); continue; } - - // Any pair containing one of the two best indices should only refer to - // bestIdx1. Its cost should also be updated. - if (isIdx1Best) - { - p.Idx1 = bestIdx1; - doEval = true; - } - else if (isIdx2Best) - { - p.Idx2 = bestIdx1; - doEval = true; - } - - // Make sure the index order is respected. - if (p.Idx1 > p.Idx2) - { - int tmp = p.Idx2; - p.Idx2 = p.Idx1; - p.Idx1 = tmp; - } - - if (doEval) - { - // Re-evaluate the cost of an updated pair. - HistoListUpdatePair(histograms[p.Idx1], histograms[p.Idx2], stats, bitsEntropy, 0.0d, p); - if (p.CostDiff >= 0.0d) - { - histoPriorityList[j] = histoPriorityList[histoPriorityList.Count - 1]; - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - continue; - } - } - - HistoListUpdateHead(histoPriorityList, p); - j++; } - triesWithNoSuccess = 0; + HistoListUpdateHead(histoPriorityList, p); + j++; } - bool doGreedy = numUsed <= minClusterSize; - - return doGreedy; + triesWithNoSuccess = 0; } - private static void HistogramCombineGreedy(List histograms) - { - int histoSize = histograms.Count(h => h != null); + bool doGreedy = numUsed <= minClusterSize; - // Priority list of histogram pairs. - var histoPriorityList = new List(); - int maxSize = histoSize * histoSize; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); + return doGreedy; + } - for (int i = 0; i < histoSize; i++) - { - if (histograms[i] == null) - { - continue; - } + private static void HistogramCombineGreedy(List histograms) + { + int histoSize = histograms.Count(h => h != null); - for (int j = i + 1; j < histoSize; j++) - { - if (histograms[j] == null) - { - continue; - } + // Priority list of histogram pairs. + var histoPriorityList = new List(); + int maxSize = histoSize * histoSize; + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); - HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy); - } + for (int i = 0; i < histoSize; i++) + { + if (histograms[i] == null) + { + continue; } - while (histoPriorityList.Count > 0) + for (int j = i + 1; j < histoSize; j++) { - int idx1 = histoPriorityList[0].Idx1; - int idx2 = histoPriorityList[0].Idx2; - HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); - histograms[idx1].BitCost = histoPriorityList[0].CostCombo; - - // Remove merged histogram. - histograms[idx2] = null; - - // Remove pairs intersecting the just combined best pair. - for (int i = 0; i < histoPriorityList.Count;) + if (histograms[j] == null) { - HistogramPair p = histoPriorityList.ElementAt(i); - if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) - { - // Replace item at pos i with the last one and shrinking the list. - histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1]; - histoPriorityList.RemoveAt(histoPriorityList.Count - 1); - } - else - { - HistoListUpdateHead(histoPriorityList, p); - i++; - } + continue; } - // Push new pairs formed with combined histogram to the list. - for (int i = 0; i < histoSize; i++) - { - if (i == idx1 || histograms[i] == null) - { - continue; - } - - HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy); - } + HistoPriorityListPush(histoPriorityList, maxSize, histograms, i, j, 0.0d, stats, bitsEntropy); } } - private static void HistogramRemap(List input, List output, ushort[] symbols) + while (histoPriorityList.Count > 0) { - int inSize = input.Count; - int outSize = output.Count; - var stats = new Vp8LStreaks(); - var bitsEntropy = new Vp8LBitEntropy(); - if (outSize > 1) - { - for (int i = 0; i < inSize; i++) - { - if (input[i] == null) - { - // Arbitrarily set to the previous value if unused to help future LZ77. - symbols[i] = symbols[i - 1]; - continue; - } + int idx1 = histoPriorityList[0].Idx1; + int idx2 = histoPriorityList[0].Idx2; + HistogramAdd(histograms[idx2], histograms[idx1], histograms[idx1]); + histograms[idx1].BitCost = histoPriorityList[0].CostCombo; - int bestOut = 0; - double bestBits = double.MaxValue; - for (int k = 0; k < outSize; k++) - { - double curBits = output[k].AddThresh(input[i], stats, bitsEntropy, bestBits); - if (k == 0 || curBits < bestBits) - { - bestBits = curBits; - bestOut = k; - } - } + // Remove merged histogram. + histograms[idx2] = null; - symbols[i] = (ushort)bestOut; - } - } - else + // Remove pairs intersecting the just combined best pair. + for (int i = 0; i < histoPriorityList.Count;) { - for (int i = 0; i < inSize; i++) + HistogramPair p = histoPriorityList.ElementAt(i); + if (p.Idx1 == idx1 || p.Idx2 == idx1 || p.Idx1 == idx2 || p.Idx2 == idx2) { - symbols[i] = 0; + // Replace item at pos i with the last one and shrinking the list. + histoPriorityList[i] = histoPriorityList[histoPriorityList.Count - 1]; + histoPriorityList.RemoveAt(histoPriorityList.Count - 1); + } + else + { + HistoListUpdateHead(histoPriorityList, p); + i++; } } - // Recompute each output. - int paletteCodeBits = output.First().PaletteCodeBits; - output.Clear(); - for (int i = 0; i < outSize; i++) + // Push new pairs formed with combined histogram to the list. + for (int i = 0; i < histoSize; i++) { - output.Add(new Vp8LHistogram(paletteCodeBits)); + if (i == idx1 || histograms[i] == null) + { + continue; + } + + HistoPriorityListPush(histoPriorityList, maxSize, histograms, idx1, i, 0.0d, stats, bitsEntropy); } + } + } + private static void HistogramRemap(List input, List output, ushort[] symbols) + { + int inSize = input.Count; + int outSize = output.Count; + var stats = new Vp8LStreaks(); + var bitsEntropy = new Vp8LBitEntropy(); + if (outSize > 1) + { for (int i = 0; i < inSize; i++) { if (input[i] == null) { + // Arbitrarily set to the previous value if unused to help future LZ77. + symbols[i] = symbols[i - 1]; continue; } - int idx = symbols[i]; - input[i].Add(output[idx], output[idx]); + int bestOut = 0; + double bestBits = double.MaxValue; + for (int k = 0; k < outSize; k++) + { + double curBits = output[k].AddThresh(input[i], stats, bitsEntropy, bestBits); + if (k == 0 || curBits < bestBits) + { + bestBits = curBits; + bestOut = k; + } + } + + symbols[i] = (ushort)bestOut; } } - - /// - /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. - /// - /// The cost of the pair, or 0 if it superior to threshold. - private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + else { - var pair = new HistogramPair(); - - if (histoList.Count == maxSize) - { - return 0.0d; - } - - if (idx1 > idx2) + for (int i = 0; i < inSize; i++) { - int tmp = idx2; - idx2 = idx1; - idx1 = tmp; + symbols[i] = 0; } + } - pair.Idx1 = idx1; - pair.Idx2 = idx2; - Vp8LHistogram h1 = histograms[idx1]; - Vp8LHistogram h2 = histograms[idx2]; - - HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair); + // Recompute each output. + int paletteCodeBits = output.First().PaletteCodeBits; + output.Clear(); + for (int i = 0; i < outSize; i++) + { + output.Add(new Vp8LHistogram(paletteCodeBits)); + } - // Do not even consider the pair if it does not improve the entropy. - if (pair.CostDiff >= threshold) + for (int i = 0; i < inSize; i++) + { + if (input[i] == null) { - return 0.0d; + continue; } - histoList.Add(pair); + int idx = symbols[i]; + input[i].Add(output[idx], output[idx]); + } + } - HistoListUpdateHead(histoList, pair); + /// + /// Create a pair from indices "idx1" and "idx2" provided its cost is inferior to "threshold", a negative entropy. + /// + /// The cost of the pair, or 0 if it superior to threshold. + private static double HistoPriorityListPush(List histoList, int maxSize, List histograms, int idx1, int idx2, double threshold, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + { + var pair = new HistogramPair(); - return pair.CostDiff; + if (histoList.Count == maxSize) + { + return 0.0d; } - /// - /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one. - /// - private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double threshold, HistogramPair pair) + if (idx1 > idx2) { - double sumCost = h1.BitCost + h2.BitCost; - pair.CostCombo = 0.0d; - h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost); - pair.CostCombo = cost; - pair.CostDiff = pair.CostCombo - sumCost; + int tmp = idx2; + idx2 = idx1; + idx1 = tmp; } - /// - /// Check whether a pair in the list should be updated as head or not. - /// - private static void HistoListUpdateHead(List histoList, HistogramPair pair) + pair.Idx1 = idx1; + pair.Idx2 = idx2; + Vp8LHistogram h1 = histograms[idx1]; + Vp8LHistogram h2 = histograms[idx2]; + + HistoListUpdatePair(h1, h2, stats, bitsEntropy, threshold, pair); + + // Do not even consider the pair if it does not improve the entropy. + if (pair.CostDiff >= threshold) { - if (pair.CostDiff < histoList[0].CostDiff) - { - // Replace the best pair. - int oldIdx = histoList.IndexOf(pair); - histoList[oldIdx] = histoList[0]; - histoList[0] = pair; - } + return 0.0d; } - private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) + histoList.Add(pair); + + HistoListUpdateHead(histoList, pair); + + return pair.CostDiff; + } + + /// + /// Update the cost diff and combo of a pair of histograms. This needs to be called when the the histograms have been merged with a third one. + /// + private static void HistoListUpdatePair(Vp8LHistogram h1, Vp8LHistogram h2, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double threshold, HistogramPair pair) + { + double sumCost = h1.BitCost + h2.BitCost; + pair.CostCombo = 0.0d; + h1.GetCombinedHistogramEntropy(h2, stats, bitsEntropy, sumCost + threshold, costInitial: pair.CostCombo, out double cost); + pair.CostCombo = cost; + pair.CostDiff = pair.CostCombo - sumCost; + } + + /// + /// Check whether a pair in the list should be updated as head or not. + /// + private static void HistoListUpdateHead(List histoList, HistogramPair pair) + { + if (pair.CostDiff < histoList[0].CostDiff) { - a.Add(b, output); - output.TrivialSymbol = a.TrivialSymbol == b.TrivialSymbol ? a.TrivialSymbol : NonTrivialSym; + // Replace the best pair. + int oldIdx = histoList.IndexOf(pair); + histoList[oldIdx] = histoList[0]; + histoList[0] = pair; } + } + + private static void HistogramAdd(Vp8LHistogram a, Vp8LHistogram b, Vp8LHistogram output) + { + a.Add(b, output); + output.TrivialSymbol = a.TrivialSymbol == b.TrivialSymbol ? a.TrivialSymbol : NonTrivialSym; + } - private static double GetCombineCostFactor(int histoSize, int quality) + private static double GetCombineCostFactor(int histoSize, int quality) + { + double combineCostFactor = 0.16d; + if (quality < 90) { - double combineCostFactor = 0.16d; - if (quality < 90) + if (histoSize > 256) { - if (histoSize > 256) - { - combineCostFactor /= 2.0d; - } - - if (histoSize > 512) - { - combineCostFactor /= 2.0d; - } + combineCostFactor /= 2.0d; + } - if (histoSize > 1024) - { - combineCostFactor /= 2.0d; - } + if (histoSize > 512) + { + combineCostFactor /= 2.0d; + } - if (quality <= 50) - { - combineCostFactor /= 2.0d; - } + if (histoSize > 1024) + { + combineCostFactor /= 2.0d; } - return combineCostFactor; + if (quality <= 50) + { + combineCostFactor /= 2.0d; + } } - // Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint MyRand(ref uint seed) - { - seed = (uint)(((ulong)seed * 48271u) % 2147483647u); - return seed; - } + return combineCostFactor; + } + + // Implement a Lehmer random number generator with a multiplicative constant of 48271 and a modulo constant of 2^31 - 1. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint MyRand(ref uint seed) + { + seed = (uint)(((ulong)seed * 48271u) % 2147483647u); + return seed; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs index b746f0722f..5a31f1c820 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramPair.cs @@ -3,20 +3,19 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Pair of histograms. Negative Idx1 value means that pair is out-of-date. +/// +[DebuggerDisplay("Idx1: {Idx1}, Idx2: {Idx2}, CostDiff: {CostDiff}, CostCombo: {CostCombo}")] +internal class HistogramPair { - /// - /// Pair of histograms. Negative Idx1 value means that pair is out-of-date. - /// - [DebuggerDisplay("Idx1: {Idx1}, Idx2: {Idx2}, CostDiff: {CostDiff}, CostCombo: {CostCombo}")] - internal class HistogramPair - { - public int Idx1 { get; set; } + public int Idx1 { get; set; } - public int Idx2 { get; set; } + public int Idx2 { get; set; } - public double CostDiff { get; set; } + public double CostDiff { get; set; } - public double CostCombo { get; set; } - } + public double CostCombo { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs index 8cb0f3f03d..82c1ce74dc 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffIndex.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Five Huffman codes are used at each meta code. +/// +internal static class HuffIndex { /// - /// Five Huffman codes are used at each meta code. + /// Green + length prefix codes + color cache codes. /// - internal static class HuffIndex - { - /// - /// Green + length prefix codes + color cache codes. - /// - public const int Green = 0; + public const int Green = 0; - /// - /// Red. - /// - public const int Red = 1; + /// + /// Red. + /// + public const int Red = 1; - /// - /// Blue. - /// - public const int Blue = 2; + /// + /// Blue. + /// + public const int Blue = 2; - /// - /// Alpha. - /// - public const int Alpha = 3; + /// + /// Alpha. + /// + public const int Alpha = 3; - /// - /// Distance prefix codes. - /// - public const int Dist = 4; - } + /// + /// Distance prefix codes. + /// + public const int Dist = 4; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs index 28a1878f53..b0cac23327 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs @@ -3,22 +3,21 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. +/// +[DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] +internal struct HuffmanCode { /// - /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. + /// Gets or sets the number of bits used for this symbol. /// - [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] - internal struct HuffmanCode - { - /// - /// Gets or sets the number of bits used for this symbol. - /// - public int BitsUsed { get; set; } + public int BitsUsed { get; set; } - /// - /// Gets or sets the symbol value or table offset. - /// - public uint Value { get; set; } - } + /// + /// Gets or sets the symbol value or table offset. + /// + public uint Value { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs index e554395338..25af3e2d34 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs @@ -3,59 +3,58 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Represents the Huffman tree. +/// +[DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] +internal struct HuffmanTree { /// - /// Represents the Huffman tree. + /// Initializes a new instance of the struct. /// - [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] - internal struct HuffmanTree + /// The HuffmanTree to create an instance from. + private HuffmanTree(HuffmanTree other) { - /// - /// Initializes a new instance of the struct. - /// - /// The HuffmanTree to create an instance from. - private HuffmanTree(HuffmanTree other) - { - this.TotalCount = other.TotalCount; - this.Value = other.Value; - this.PoolIndexLeft = other.PoolIndexLeft; - this.PoolIndexRight = other.PoolIndexRight; - } + this.TotalCount = other.TotalCount; + this.Value = other.Value; + this.PoolIndexLeft = other.PoolIndexLeft; + this.PoolIndexRight = other.PoolIndexRight; + } - /// - /// Gets or sets the symbol frequency. - /// - public int TotalCount { get; set; } + /// + /// Gets or sets the symbol frequency. + /// + public int TotalCount { get; set; } - /// - /// Gets or sets the symbol value. - /// - public int Value { get; set; } + /// + /// Gets or sets the symbol value. + /// + public int Value { get; set; } - /// - /// Gets or sets the index for the left sub-tree. - /// - public int PoolIndexLeft { get; set; } + /// + /// Gets or sets the index for the left sub-tree. + /// + public int PoolIndexLeft { get; set; } - /// - /// Gets or sets the index for the right sub-tree. - /// - public int PoolIndexRight { get; set; } + /// + /// Gets or sets the index for the right sub-tree. + /// + public int PoolIndexRight { get; set; } - public static int Compare(HuffmanTree t1, HuffmanTree t2) + public static int Compare(HuffmanTree t1, HuffmanTree t2) + { + if (t1.TotalCount > t2.TotalCount) { - if (t1.TotalCount > t2.TotalCount) - { - return -1; - } - - if (t1.TotalCount < t2.TotalCount) - { - return 1; - } + return -1; + } - return t1.Value < t2.Value ? -1 : 1; + if (t1.TotalCount < t2.TotalCount) + { + return 1; } + + return t1.Value < t2.Value ? -1 : 1; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs index 0884e563da..4c101bc499 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeCode.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Represents the tree codes (depth and bits array). +/// +internal struct HuffmanTreeCode { /// - /// Represents the tree codes (depth and bits array). + /// Gets or sets the number of symbols. /// - internal struct HuffmanTreeCode - { - /// - /// Gets or sets the number of symbols. - /// - public int NumSymbols { get; set; } + public int NumSymbols { get; set; } - /// - /// Gets or sets the code lengths of the symbols. - /// - public byte[] CodeLengths { get; set; } + /// + /// Gets or sets the code lengths of the symbols. + /// + public byte[] CodeLengths { get; set; } - /// - /// Gets or sets the symbol Codes. - /// - public short[] Codes { get; set; } - } + /// + /// Gets or sets the symbol Codes. + /// + public short[] Codes { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs index c813f6fca3..39cb3bef38 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTreeToken.cs @@ -3,22 +3,21 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Holds the tree header in coded form. +/// +[DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")] +internal class HuffmanTreeToken { /// - /// Holds the tree header in coded form. + /// Gets or sets the code. Value (0..15) or escape code (16, 17, 18). /// - [DebuggerDisplay("Code = {Code}, ExtraBits = {ExtraBits}")] - internal class HuffmanTreeToken - { - /// - /// Gets or sets the code. Value (0..15) or escape code (16, 17, 18). - /// - public byte Code { get; set; } + public byte Code { get; set; } - /// - /// Gets or sets the extra bits for escape codes. - /// - public byte ExtraBits { get; set; } - } + /// + /// Gets or sets the extra bits for escape codes. + /// + public byte ExtraBits { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 515ae7a7d9..5cee6bc396 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -1,662 +1,660 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Utility functions related to creating the huffman tables. +/// +internal static class HuffmanUtils { - /// - /// Utility functions related to creating the huffman tables. - /// - internal static class HuffmanUtils - { - public const int HuffmanTableBits = 8; + public const int HuffmanTableBits = 8; - public const int HuffmanPackedBits = 6; + public const int HuffmanPackedBits = 6; - public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; + public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1; - public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; + public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits; - // Pre-reversed 4-bit values. - private static readonly byte[] ReversedBits = - { - 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, - 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf - }; + // Pre-reversed 4-bit values. + private static readonly byte[] ReversedBits = + { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf + }; - public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) - { - int numSymbols = huffCode.NumSymbols; - bufRle.AsSpan().Clear(); - OptimizeHuffmanForRle(numSymbols, bufRle, histogram); - GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); + public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) + { + int numSymbols = huffCode.NumSymbols; + bufRle.AsSpan().Clear(); + OptimizeHuffmanForRle(numSymbols, bufRle, histogram); + GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); - // Create the actual bit codes for the bit lengths. - ConvertBitDepthsToSymbols(huffCode); - } + // Create the actual bit codes for the bit lengths. + ConvertBitDepthsToSymbols(huffCode); + } - /// - /// Change the population counts in a way that the consequent - /// Huffman tree compression, especially its RLE-part, give smaller output. - /// - public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) + /// + /// Change the population counts in a way that the consequent + /// Huffman tree compression, especially its RLE-part, give smaller output. + /// + public static void OptimizeHuffmanForRle(int length, bool[] goodForRle, uint[] counts) + { + // 1) Let's make the Huffman code more compatible with rle encoding. + for (; length >= 0; --length) { - // 1) Let's make the Huffman code more compatible with rle encoding. - for (; length >= 0; --length) + if (length == 0) { - if (length == 0) - { - return; // All zeros. - } + return; // All zeros. + } - if (counts[length - 1] != 0) - { - // Now counts[0..length - 1] does not have trailing zeros. - break; - } + if (counts[length - 1] != 0) + { + // Now counts[0..length - 1] does not have trailing zeros. + break; } + } - // 2) Let's mark all population counts that already can be encoded with an rle code. - // Let's not spoil any of the existing good rle codes. - // Mark any seq of 0's that is longer as 5 as a goodForRle. - // Mark any seq of non-0's that is longer as 7 as a goodForRle. - uint symbol = counts[0]; - int stride = 0; - for (int i = 0; i < length + 1; i++) + // 2) Let's mark all population counts that already can be encoded with an rle code. + // Let's not spoil any of the existing good rle codes. + // Mark any seq of 0's that is longer as 5 as a goodForRle. + // Mark any seq of non-0's that is longer as 7 as a goodForRle. + uint symbol = counts[0]; + int stride = 0; + for (int i = 0; i < length + 1; i++) + { + if (i == length || counts[i] != symbol) { - if (i == length || counts[i] != symbol) + if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) { - if ((symbol == 0 && stride >= 5) || (symbol != 0 && stride >= 7)) - { - for (int k = 0; k < stride; k++) - { - goodForRle[i - k - 1] = true; - } - } - - stride = 1; - if (i != length) + for (int k = 0; k < stride; k++) { - symbol = counts[i]; + goodForRle[i - k - 1] = true; } } - else + + stride = 1; + if (i != length) { - ++stride; + symbol = counts[i]; } } + else + { + ++stride; + } + } - // 3) Let's replace those population counts that lead to more rle codes. - stride = 0; - uint limit = counts[0]; - uint sum = 0; - for (int i = 0; i < length + 1; i++) + // 3) Let's replace those population counts that lead to more rle codes. + stride = 0; + uint limit = counts[0]; + uint sum = 0; + for (int i = 0; i < length + 1; i++) + { + if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit)) { - if (i == length || goodForRle[i] || (i != 0 && goodForRle[i - 1]) || !ValuesShouldBeCollapsedToStrideAverage((int)counts[i], (int)limit)) + if (stride >= 4 || (stride >= 3 && sum == 0)) { - if (stride >= 4 || (stride >= 3 && sum == 0)) - { - uint k; - - // The stride must end, collapse what we have, if we have enough (4). - uint count = (uint)((sum + (stride / 2)) / stride); - if (count < 1) - { - count = 1; - } - - if (sum == 0) - { - // Don't make an all zeros stride to be upgraded to ones. - count = 0; - } - - for (k = 0; k < stride; k++) - { - // We don't want to change value at counts[i], - // that is already belonging to the next stride. Thus - 1. - counts[i - k - 1] = count; - } - } + uint k; - stride = 0; - sum = 0; - if (i < length - 3) + // The stride must end, collapse what we have, if we have enough (4). + uint count = (uint)((sum + (stride / 2)) / stride); + if (count < 1) { - // All interesting strides have a count of at least 4, at least when non-zeros. - limit = (counts[i] + counts[i + 1] + - counts[i + 2] + counts[i + 3] + 2) / 4; + count = 1; } - else if (i < length) + + if (sum == 0) { - limit = counts[i]; + // Don't make an all zeros stride to be upgraded to ones. + count = 0; } - else + + for (k = 0; k < stride; k++) { - limit = 0; + // We don't want to change value at counts[i], + // that is already belonging to the next stride. Thus - 1. + counts[i - k - 1] = count; } } - ++stride; - if (i != length) + stride = 0; + sum = 0; + if (i < length - 3) { - sum += counts[i]; - if (stride >= 4) - { - limit = (uint)((sum + (stride / 2)) / stride); - } + // All interesting strides have a count of at least 4, at least when non-zeros. + limit = (counts[i] + counts[i + 1] + + counts[i + 2] + counts[i + 3] + 2) / 4; + } + else if (i < length) + { + limit = counts[i]; + } + else + { + limit = 0; } } - } - - /// - /// Create an optimal Huffman tree. - /// - /// - /// The huffman tree. - /// The histogram. - /// The size of the histogram. - /// The tree depth limit. - /// How many bits are used for the symbol. - public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) - { - uint countMin; - int treeSizeOrig = 0; - for (int i = 0; i < histogramSize; i++) + ++stride; + if (i != length) { - if (histogram[i] != 0) + sum += counts[i]; + if (stride >= 4) { - ++treeSizeOrig; + limit = (uint)((sum + (stride / 2)) / stride); } } + } + } - if (treeSizeOrig == 0) + /// + /// Create an optimal Huffman tree. + /// + /// + /// The huffman tree. + /// The histogram. + /// The size of the histogram. + /// The tree depth limit. + /// How many bits are used for the symbol. + public static void GenerateOptimalTree(HuffmanTree[] tree, uint[] histogram, int histogramSize, int treeDepthLimit, byte[] bitDepths) + { + uint countMin; + int treeSizeOrig = 0; + + for (int i = 0; i < histogramSize; i++) + { + if (histogram[i] != 0) { - return; + ++treeSizeOrig; } + } + + if (treeSizeOrig == 0) + { + return; + } - Span treePool = tree.AsSpan(treeSizeOrig); + Span treePool = tree.AsSpan(treeSizeOrig); - // For block sizes with less than 64k symbols we never need to do a - // second iteration of this loop. - for (countMin = 1; ; countMin *= 2) - { - int treeSize = treeSizeOrig; + // For block sizes with less than 64k symbols we never need to do a + // second iteration of this loop. + for (countMin = 1; ; countMin *= 2) + { + int treeSize = treeSizeOrig; - // We need to pack the Huffman tree in treeDepthLimit bits. - // So, we try by faking histogram entries to be at least 'countMin'. - int idx = 0; - for (int j = 0; j < histogramSize; j++) + // We need to pack the Huffman tree in treeDepthLimit bits. + // So, we try by faking histogram entries to be at least 'countMin'. + int idx = 0; + for (int j = 0; j < histogramSize; j++) + { + if (histogram[j] != 0) { - if (histogram[j] != 0) - { - uint count = histogram[j] < countMin ? countMin : histogram[j]; - tree[idx].TotalCount = (int)count; - tree[idx].Value = j; - tree[idx].PoolIndexLeft = -1; - tree[idx].PoolIndexRight = -1; - idx++; - } + uint count = histogram[j] < countMin ? countMin : histogram[j]; + tree[idx].TotalCount = (int)count; + tree[idx].Value = j; + tree[idx].PoolIndexLeft = -1; + tree[idx].PoolIndexRight = -1; + idx++; } + } - // Build the Huffman tree. + // Build the Huffman tree. #if NET5_0_OR_GREATER - Span treeSlice = tree.AsSpan(0, treeSize); - treeSlice.Sort(HuffmanTree.Compare); + Span treeSlice = tree.AsSpan(0, treeSize); + treeSlice.Sort(HuffmanTree.Compare); #else - HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray(); - Array.Sort(treeCopy, HuffmanTree.Compare); - treeCopy.AsSpan().CopyTo(tree); + HuffmanTree[] treeCopy = tree.AsSpan(0, treeSize).ToArray(); + Array.Sort(treeCopy, HuffmanTree.Compare); + treeCopy.AsSpan().CopyTo(tree); #endif - if (treeSize > 1) + if (treeSize > 1) + { + // Normal case. + int treePoolSize = 0; + while (treeSize > 1) { - // Normal case. - int treePoolSize = 0; - while (treeSize > 1) + // Finish when we have only one root. + treePool[treePoolSize++] = tree[treeSize - 1]; + treePool[treePoolSize++] = tree[treeSize - 2]; + int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; + treeSize -= 2; + + // Search for the insertion point. + int k; + for (k = 0; k < treeSize; k++) { - // Finish when we have only one root. - treePool[treePoolSize++] = tree[treeSize - 1]; - treePool[treePoolSize++] = tree[treeSize - 2]; - int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; - treeSize -= 2; - - // Search for the insertion point. - int k; - for (k = 0; k < treeSize; k++) - { - if (tree[k].TotalCount <= count) - { - break; - } - } - - int endIdx = k + 1; - int num = treeSize - k; - int startIdx = endIdx + num - 1; - for (int i = startIdx; i >= endIdx; i--) + if (tree[k].TotalCount <= count) { - tree[i] = tree[i - 1]; + break; } - - tree[k].TotalCount = count; - tree[k].Value = -1; - tree[k].PoolIndexLeft = treePoolSize - 1; - tree[k].PoolIndexRight = treePoolSize - 2; - treeSize++; } - SetBitDepths(tree, treePool, bitDepths, 0); - } - else if (treeSize == 1) - { - // Trivial case: only one element. - bitDepths[tree[0].Value] = 1; - } - - // Test if this Huffman tree satisfies our 'treeDepthLimit' criteria. - int maxDepth = bitDepths[0]; - for (int j = 1; j < histogramSize; j++) - { - if (maxDepth < bitDepths[j]) + int endIdx = k + 1; + int num = treeSize - k; + int startIdx = endIdx + num - 1; + for (int i = startIdx; i >= endIdx; i--) { - maxDepth = bitDepths[j]; + tree[i] = tree[i - 1]; } + + tree[k].TotalCount = count; + tree[k].Value = -1; + tree[k].PoolIndexLeft = treePoolSize - 1; + tree[k].PoolIndexRight = treePoolSize - 2; + treeSize++; } - if (maxDepth <= treeDepthLimit) + SetBitDepths(tree, treePool, bitDepths, 0); + } + else if (treeSize == 1) + { + // Trivial case: only one element. + bitDepths[tree[0].Value] = 1; + } + + // Test if this Huffman tree satisfies our 'treeDepthLimit' criteria. + int maxDepth = bitDepths[0]; + for (int j = 1; j < histogramSize; j++) + { + if (maxDepth < bitDepths[j]) { - break; + maxDepth = bitDepths[j]; } } + + if (maxDepth <= treeDepthLimit) + { + break; + } } + } - public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokensArray) + public static int CreateCompressedHuffmanTree(HuffmanTreeCode tree, HuffmanTreeToken[] tokensArray) + { + int depthSize = tree.NumSymbols; + int prevValue = 8; // 8 is the initial value for rle. + int i = 0; + int tokenPos = 0; + while (i < depthSize) { - int depthSize = tree.NumSymbols; - int prevValue = 8; // 8 is the initial value for rle. - int i = 0; - int tokenPos = 0; - while (i < depthSize) + int value = tree.CodeLengths[i]; + int k = i + 1; + while (k < depthSize && tree.CodeLengths[k] == value) { - int value = tree.CodeLengths[i]; - int k = i + 1; - while (k < depthSize && tree.CodeLengths[k] == value) - { - k++; - } - - int runs = k - i; - if (value == 0) - { - tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); - } - else - { - tokenPos += CodeRepeatedValues(runs, tokensArray.AsSpan(tokenPos), value, prevValue); - prevValue = value; - } + k++; + } - i += runs; + int runs = k - i; + if (value == 0) + { + tokenPos += CodeRepeatedZeros(runs, tokensArray.AsSpan(tokenPos)); + } + else + { + tokenPos += CodeRepeatedValues(runs, tokensArray.AsSpan(tokenPos), value, prevValue); + prevValue = value; } - return tokenPos; + i += runs; } - public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) + return tokenPos; + } + + public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) + { + DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); + DebugGuard.NotNull(codeLengths, nameof(codeLengths)); + DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); + + // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. + int[] sorted = new int[codeLengthsSize]; + int totalSize = 1 << rootBits; // total size root table + 2nd level table. + int len; // current code length. + int symbol; // symbol index in original or sorted table. + int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. + int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. + + // Build histogram of code lengths. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) { - DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); - DebugGuard.NotNull(codeLengths, nameof(codeLengths)); - DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); - - // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. - int[] sorted = new int[codeLengthsSize]; - int totalSize = 1 << rootBits; // total size root table + 2nd level table. - int len; // current code length. - int symbol; // symbol index in original or sorted table. - int[] counts = new int[WebpConstants.MaxAllowedCodeLength + 1]; // number of codes of each length. - int[] offsets = new int[WebpConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length. - - // Build histogram of code lengths. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) + int codeLengthOfSymbol = codeLengths[symbol]; + if (codeLengthOfSymbol > WebpConstants.MaxAllowedCodeLength) { - int codeLengthOfSymbol = codeLengths[symbol]; - if (codeLengthOfSymbol > WebpConstants.MaxAllowedCodeLength) - { - return 0; - } - - counts[codeLengthOfSymbol]++; + return 0; } - // Error, all code lengths are zeros. - if (counts[0] == codeLengthsSize) + counts[codeLengthOfSymbol]++; + } + + // Error, all code lengths are zeros. + if (counts[0] == codeLengthsSize) + { + return 0; + } + + // Generate offsets into sorted symbol table by code length. + offsets[1] = 0; + for (len = 1; len < WebpConstants.MaxAllowedCodeLength; ++len) + { + int codesOfLength = counts[len]; + if (codesOfLength > 1 << len) { return 0; } - // Generate offsets into sorted symbol table by code length. - offsets[1] = 0; - for (len = 1; len < WebpConstants.MaxAllowedCodeLength; ++len) - { - int codesOfLength = counts[len]; - if (codesOfLength > 1 << len) - { - return 0; - } + offsets[len + 1] = offsets[len] + codesOfLength; + } - offsets[len + 1] = offsets[len] + codesOfLength; + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < codeLengthsSize; ++symbol) + { + int symbolCodeLength = codeLengths[symbol]; + if (symbolCodeLength > 0) + { + sorted[offsets[symbolCodeLength]++] = symbol; } + } - // Sort symbols by length, by symbol order within each length. - for (symbol = 0; symbol < codeLengthsSize; ++symbol) + // Special case code with only one value. + if (offsets[WebpConstants.MaxAllowedCodeLength] == 1) + { + var huffmanCode = new HuffmanCode() { - int symbolCodeLength = codeLengths[symbol]; - if (symbolCodeLength > 0) - { - sorted[offsets[symbolCodeLength]++] = symbol; - } + BitsUsed = 0, + Value = (uint)sorted[0] + }; + ReplicateValue(table, 1, totalSize, huffmanCode); + return totalSize; + } + + int step; // step size to replicate values in current table + int low = -1; // low bits for current root entry + int mask = totalSize - 1; // mask for low bits + int key = 0; // reversed prefix code + int numNodes = 1; // number of Huffman tree nodes + int numOpen = 1; // number of open branches in current tree level + int tableBits = rootBits; // key length of current table + int tableSize = 1 << tableBits; // size of current table + symbol = 0; + + // Fill in root table. + for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) + { + int countsLen = counts[len]; + numOpen <<= 1; + numNodes += numOpen; + numOpen -= counts[len]; + if (numOpen < 0) + { + return 0; } - // Special case code with only one value. - if (offsets[WebpConstants.MaxAllowedCodeLength] == 1) + for (; countsLen > 0; countsLen--) { var huffmanCode = new HuffmanCode() { - BitsUsed = 0, - Value = (uint)sorted[0] + BitsUsed = len, + Value = (uint)sorted[symbol++] }; - ReplicateValue(table, 1, totalSize, huffmanCode); - return totalSize; + ReplicateValue(table[key..], step, tableSize, huffmanCode); + key = GetNextKey(key, len); } - int step; // step size to replicate values in current table - int low = -1; // low bits for current root entry - int mask = totalSize - 1; // mask for low bits - int key = 0; // reversed prefix code - int numNodes = 1; // number of Huffman tree nodes - int numOpen = 1; // number of open branches in current tree level - int tableBits = rootBits; // key length of current table - int tableSize = 1 << tableBits; // size of current table - symbol = 0; - - // Fill in root table. - for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1) - { - int countsLen = counts[len]; - numOpen <<= 1; - numNodes += numOpen; - numOpen -= counts[len]; - if (numOpen < 0) - { - return 0; - } - - for (; countsLen > 0; countsLen--) - { - var huffmanCode = new HuffmanCode() - { - BitsUsed = len, - Value = (uint)sorted[symbol++] - }; - ReplicateValue(table[key..], step, tableSize, huffmanCode); - key = GetNextKey(key, len); - } + counts[len] = countsLen; + } - counts[len] = countsLen; + // Fill in 2nd level tables and add pointers to root table. + Span tableSpan = table; + int tablePos = 0; + for (len = rootBits + 1, step = 2; len <= WebpConstants.MaxAllowedCodeLength; ++len, step <<= 1) + { + numOpen <<= 1; + numNodes += numOpen; + numOpen -= counts[len]; + if (numOpen < 0) + { + return 0; } - // Fill in 2nd level tables and add pointers to root table. - Span tableSpan = table; - int tablePos = 0; - for (len = rootBits + 1, step = 2; len <= WebpConstants.MaxAllowedCodeLength; ++len, step <<= 1) + for (; counts[len] > 0; --counts[len]) { - numOpen <<= 1; - numNodes += numOpen; - numOpen -= counts[len]; - if (numOpen < 0) - { - return 0; - } - - for (; counts[len] > 0; --counts[len]) + if ((key & mask) != low) { - if ((key & mask) != low) - { - tableSpan = tableSpan[tableSize..]; - tablePos += tableSize; - tableBits = NextTableBitSize(counts, len, rootBits); - tableSize = 1 << tableBits; - totalSize += tableSize; - low = key & mask; - table[low] = new HuffmanCode - { - BitsUsed = tableBits + rootBits, - Value = (uint)(tablePos - low) - }; - } - - var huffmanCode = new HuffmanCode + tableSpan = tableSpan[tableSize..]; + tablePos += tableSize; + tableBits = NextTableBitSize(counts, len, rootBits); + tableSize = 1 << tableBits; + totalSize += tableSize; + low = key & mask; + table[low] = new HuffmanCode { - BitsUsed = len - rootBits, - Value = (uint)sorted[symbol++] + BitsUsed = tableBits + rootBits, + Value = (uint)(tablePos - low) }; - ReplicateValue(tableSpan[(key >> rootBits)..], step, tableSize, huffmanCode); - key = GetNextKey(key, len); } - } - return totalSize; + var huffmanCode = new HuffmanCode + { + BitsUsed = len - rootBits, + Value = (uint)sorted[symbol++] + }; + ReplicateValue(tableSpan[(key >> rootBits)..], step, tableSize, huffmanCode); + key = GetNextKey(key, len); + } } - private static int CodeRepeatedZeros(int repetitions, Span tokens) + return totalSize; + } + + private static int CodeRepeatedZeros(int repetitions, Span tokens) + { + int pos = 0; + while (repetitions >= 1) { - int pos = 0; - while (repetitions >= 1) + if (repetitions < 3) { - if (repetitions < 3) - { - for (int i = 0; i < repetitions; i++) - { - tokens[pos].Code = 0; // 0-value - tokens[pos].ExtraBits = 0; - pos++; - } - - break; - } - - if (repetitions < 11) + for (int i = 0; i < repetitions; i++) { - tokens[pos].Code = 17; - tokens[pos].ExtraBits = (byte)(repetitions - 3); + tokens[pos].Code = 0; // 0-value + tokens[pos].ExtraBits = 0; pos++; - break; } - if (repetitions < 139) - { - tokens[pos].Code = 18; - tokens[pos].ExtraBits = (byte)(repetitions - 11); - pos++; - break; - } + break; + } - tokens[pos].Code = 18; - tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + if (repetitions < 11) + { + tokens[pos].Code = 17; + tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; - repetitions -= 138; + break; } - return pos; - } - - private static int CodeRepeatedValues(int repetitions, Span tokens, int value, int prevValue) - { - int pos = 0; - - if (value != prevValue) + if (repetitions < 139) { - tokens[pos].Code = (byte)value; - tokens[pos].ExtraBits = 0; + tokens[pos].Code = 18; + tokens[pos].ExtraBits = (byte)(repetitions - 11); pos++; - repetitions--; + break; } - while (repetitions >= 1) - { - if (repetitions < 3) - { - int i; - for (i = 0; i < repetitions; i++) - { - tokens[pos].Code = (byte)value; - tokens[pos].ExtraBits = 0; - pos++; - } + tokens[pos].Code = 18; + tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + pos++; + repetitions -= 138; + } - break; - } + return pos; + } + + private static int CodeRepeatedValues(int repetitions, Span tokens, int value, int prevValue) + { + int pos = 0; - if (repetitions < 7) + if (value != prevValue) + { + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; + pos++; + repetitions--; + } + + while (repetitions >= 1) + { + if (repetitions < 3) + { + int i; + for (i = 0; i < repetitions; i++) { - tokens[pos].Code = 16; - tokens[pos].ExtraBits = (byte)(repetitions - 3); + tokens[pos].Code = (byte)value; + tokens[pos].ExtraBits = 0; pos++; - break; } + break; + } + + if (repetitions < 7) + { tokens[pos].Code = 16; - tokens[pos].ExtraBits = 3; + tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; - repetitions -= 6; + break; } - return pos; + tokens[pos].Code = 16; + tokens[pos].ExtraBits = 3; + pos++; + repetitions -= 6; } - /// - /// Get the actual bit values for a tree of bit depths. - /// - /// The huffman tree. - private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) - { - // 0 bit-depth means that the symbol does not exist. - uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1]; - int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1]; + return pos; + } - int len = tree.NumSymbols; - for (int i = 0; i < len; i++) - { - int codeLength = tree.CodeLengths[i]; - depthCount[codeLength]++; - } + /// + /// Get the actual bit values for a tree of bit depths. + /// + /// The huffman tree. + private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) + { + // 0 bit-depth means that the symbol does not exist. + uint[] nextCode = new uint[WebpConstants.MaxAllowedCodeLength + 1]; + int[] depthCount = new int[WebpConstants.MaxAllowedCodeLength + 1]; - depthCount[0] = 0; // ignore unused symbol. - nextCode[0] = 0; + int len = tree.NumSymbols; + for (int i = 0; i < len; i++) + { + int codeLength = tree.CodeLengths[i]; + depthCount[codeLength]++; + } - uint code = 0; - for (int i = 1; i <= WebpConstants.MaxAllowedCodeLength; i++) - { - code = (uint)((code + depthCount[i - 1]) << 1); - nextCode[i] = code; - } + depthCount[0] = 0; // ignore unused symbol. + nextCode[0] = 0; - for (int i = 0; i < len; i++) - { - int codeLength = tree.CodeLengths[i]; - tree.Codes[i] = (short)ReverseBits(codeLength, nextCode[codeLength]++); - } + uint code = 0; + for (int i = 1; i <= WebpConstants.MaxAllowedCodeLength; i++) + { + code = (uint)((code + depthCount[i - 1]) << 1); + nextCode[i] = code; } - private static void SetBitDepths(Span tree, Span pool, byte[] bitDepths, int level) + for (int i = 0; i < len; i++) { - if (tree[0].PoolIndexLeft >= 0) - { - SetBitDepths(pool[tree[0].PoolIndexLeft..], pool, bitDepths, level + 1); - SetBitDepths(pool[tree[0].PoolIndexRight..], pool, bitDepths, level + 1); - } - else - { - bitDepths[tree[0].Value] = (byte)level; - } + int codeLength = tree.CodeLengths[i]; + tree.Codes[i] = (short)ReverseBits(codeLength, nextCode[codeLength]++); } + } - private static uint ReverseBits(int numBits, uint bits) + private static void SetBitDepths(Span tree, Span pool, byte[] bitDepths, int level) + { + if (tree[0].PoolIndexLeft >= 0) { - uint retval = 0; - int i = 0; - while (i < numBits) - { - i += 4; - retval |= (uint)(ReversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); - bits >>= 4; - } + SetBitDepths(pool[tree[0].PoolIndexLeft..], pool, bitDepths, level + 1); + SetBitDepths(pool[tree[0].PoolIndexRight..], pool, bitDepths, level + 1); + } + else + { + bitDepths[tree[0].Value] = (byte)level; + } + } - retval >>= WebpConstants.MaxAllowedCodeLength + 1 - numBits; - return retval; + private static uint ReverseBits(int numBits, uint bits) + { + uint retval = 0; + int i = 0; + while (i < numBits) + { + i += 4; + retval |= (uint)(ReversedBits[bits & 0xf] << (WebpConstants.MaxAllowedCodeLength + 1 - i)); + bits >>= 4; } - /// - /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, - /// len is the code length of the next processed symbol. - /// - private static int NextTableBitSize(int[] count, int len, int rootBits) + retval >>= WebpConstants.MaxAllowedCodeLength + 1 - numBits; + return retval; + } + + /// + /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols, + /// len is the code length of the next processed symbol. + /// + private static int NextTableBitSize(int[] count, int len, int rootBits) + { + int left = 1 << (len - rootBits); + while (len < WebpConstants.MaxAllowedCodeLength) { - int left = 1 << (len - rootBits); - while (len < WebpConstants.MaxAllowedCodeLength) + left -= count[len]; + if (left <= 0) { - left -= count[len]; - if (left <= 0) - { - break; - } - - ++len; - left <<= 1; + break; } - return len - rootBits; + ++len; + left <<= 1; } - /// - /// Stores code in table[0], table[step], table[2*step], ..., table[end-step]. - /// Assumes that end is an integer multiple of step. - /// - private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) - { - DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); + return len - rootBits; + } - do - { - end -= step; - table[end] = code; - } - while (end > 0); - } + /// + /// Stores code in table[0], table[step], table[2*step], ..., table[end-step]. + /// Assumes that end is an integer multiple of step. + /// + private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) + { + DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); - /// - /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the - /// bit-wise reversal of the len least significant bits of key. - /// - private static int GetNextKey(int key, int len) + do { - int step = 1 << (len - 1); - while ((key & step) != 0) - { - step >>= 1; - } + end -= step; + table[end] = code; + } + while (end > 0); + } - return step != 0 ? (key & (step - 1)) + step : key; + /// + /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the + /// bit-wise reversal of the len least significant bits of key. + /// + private static int GetNextKey(int key, int len) + { + int step = 1 << (len - 1); + while ((key & step) != 0) + { + step >>= 1; } - /// - /// Heuristics for selecting the stride ranges to collapse. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; + return step != 0 ? (key & (step - 1)) + step : key; } + + /// + /// Heuristics for selecting the stride ranges to collapse. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index cb826f75d1..5d9c207096 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,1523 +8,1522 @@ using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Utility functions for the lossless decoder. +/// +internal static unsafe class LosslessUtils { - /// - /// Utility functions for the lossless decoder. - /// - internal static unsafe class LosslessUtils - { - private const int PrefixLookupIdxMax = 512; + private const int PrefixLookupIdxMax = 512; - private const int LogLookupIdxMax = 256; + private const int LogLookupIdxMax = 256; - private const int ApproxLogMax = 4096; + private const int ApproxLogMax = 4096; - private const int ApproxLogWithCorrectionMax = 65536; + private const int ApproxLogWithCorrectionMax = 65536; - private const double Log2Reciprocal = 1.44269504088896338700465094007086; + private const double Log2Reciprocal = 1.44269504088896338700465094007086; - /// - /// Returns the exact index where array1 and array2 are different. For an index - /// inferior or equal to bestLenMatch, the return value just has to be strictly - /// inferior to bestLenMatch match. The current behavior is to return 0 if this index - /// is bestLenMatch, and the index itself otherwise. - /// If no two elements are the same, it returns maxLimit. - /// - public static int FindMatchLength(ReadOnlySpan array1, ReadOnlySpan array2, int bestLenMatch, int maxLimit) + /// + /// Returns the exact index where array1 and array2 are different. For an index + /// inferior or equal to bestLenMatch, the return value just has to be strictly + /// inferior to bestLenMatch match. The current behavior is to return 0 if this index + /// is bestLenMatch, and the index itself otherwise. + /// If no two elements are the same, it returns maxLimit. + /// + public static int FindMatchLength(ReadOnlySpan array1, ReadOnlySpan array2, int bestLenMatch, int maxLimit) + { + // Before 'expensive' linear match, check if the two arrays match at the + // current best length index. + if (array1[bestLenMatch] != array2[bestLenMatch]) { - // Before 'expensive' linear match, check if the two arrays match at the - // current best length index. - if (array1[bestLenMatch] != array2[bestLenMatch]) - { - return 0; - } - - return VectorMismatch(array1, array2, maxLimit); + return 0; } - [MethodImpl(InliningOptions.ShortMethod)] - public static int VectorMismatch(ReadOnlySpan array1, ReadOnlySpan array2, int length) + return VectorMismatch(array1, array2, maxLimit); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int VectorMismatch(ReadOnlySpan array1, ReadOnlySpan array2, int length) + { + int matchLen = 0; + ref uint array1Ref = ref MemoryMarshal.GetReference(array1); + ref uint array2Ref = ref MemoryMarshal.GetReference(array2); + + while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) { - int matchLen = 0; - ref uint array1Ref = ref MemoryMarshal.GetReference(array1); - ref uint array2Ref = ref MemoryMarshal.GetReference(array2); + matchLen++; + } - while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) - { - matchLen++; - } + return matchLen; + } - return matchLen; + [MethodImpl(InliningOptions.ShortMethod)] + public static int MaxFindCopyLength(int len) => len < BackwardReferenceEncoder.MaxLength ? len : BackwardReferenceEncoder.MaxLength; + + public static int PrefixEncodeBits(int distance, ref int extraBits) + { + if (distance < PrefixLookupIdxMax) + { + (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; + extraBits = bits; + return code; } - [MethodImpl(InliningOptions.ShortMethod)] - public static int MaxFindCopyLength(int len) => len < BackwardReferenceEncoder.MaxLength ? len : BackwardReferenceEncoder.MaxLength; + return PrefixEncodeBitsNoLut(distance, ref extraBits); + } - public static int PrefixEncodeBits(int distance, ref int extraBits) + public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) + { + if (distance < PrefixLookupIdxMax) { - if (distance < PrefixLookupIdxMax) - { - (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = bits; - return code; - } + (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; + extraBits = bits; + extraBitsValue = WebpLookupTables.PrefixEncodeExtraBitsValue[distance]; - return PrefixEncodeBitsNoLut(distance, ref extraBits); + return code; } - public static int PrefixEncode(int distance, ref int extraBits, ref int extraBitsValue) + return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); + } + + /// + /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). + /// + /// The pixel data to apply the transformation. + public static void AddGreenToBlueAndRed(Span pixelData) + { + if (Avx2.IsSupported) { - if (distance < PrefixLookupIdxMax) + var addGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); + int numPixels = pixelData.Length; + nint i; + for (i = 0; i <= numPixels - 8; i += 8) { - (int code, int bits) = WebpLookupTables.PrefixEncodeCode[distance]; - extraBits = bits; - extraBitsValue = WebpLookupTables.PrefixEncodeExtraBitsValue[distance]; - - return code; + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector256 input = Unsafe.As>(ref pos).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, addGreenToBlueAndRedMaskAvx2); + Vector256 output = Avx2.Add(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); } - return PrefixEncodeNoLut(distance, ref extraBits, ref extraBitsValue); + if (i != numPixels) + { + AddGreenToBlueAndRedScalar(pixelData[(int)i..]); + } } - - /// - /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green'). - /// - /// The pixel data to apply the transformation. - public static void AddGreenToBlueAndRed(Span pixelData) + else if (Ssse3.IsSupported) { - if (Avx2.IsSupported) + var addGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); + int numPixels = pixelData.Length; + nint i; + for (i = 0; i <= numPixels - 4; i += 4) { - var addGreenToBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 8; i += 8) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector256 input = Unsafe.As>(ref pos).AsByte(); - Vector256 in0g0g = Avx2.Shuffle(input, addGreenToBlueAndRedMaskAvx2); - Vector256 output = Avx2.Add(input, in0g0g); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - - if (i != numPixels) - { - AddGreenToBlueAndRedScalar(pixelData[(int)i..]); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, addGreenToBlueAndRedMaskSsse3); + Vector128 output = Sse2.Add(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else if (Ssse3.IsSupported) - { - var addGreenToBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 4; i += 4) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 in0g0g = Ssse3.Shuffle(input, addGreenToBlueAndRedMaskSsse3); - Vector128 output = Sse2.Add(input, in0g0g); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - if (i != numPixels) - { - AddGreenToBlueAndRedScalar(pixelData[(int)i..]); - } + if (i != numPixels) + { + AddGreenToBlueAndRedScalar(pixelData[(int)i..]); } - else if (Sse2.IsSupported) + } + else if (Sse2.IsSupported) + { + int numPixels = pixelData.Length; + nint i; + for (i = 0; i <= numPixels - 4; i += 4) { - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 4; i += 4) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g - Vector128 b = Sse2.ShuffleLow(a, mmShuffle_2200); - Vector128 c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g - Vector128 output = Sse2.Add(input.AsByte(), c.AsByte()); - Unsafe.As>(ref pos) = output.AsUInt32(); - } + const byte mmShuffle_2200 = 0b_10_10_00_00; - if (i != numPixels) - { - AddGreenToBlueAndRedScalar(pixelData[(int)i..]); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, mmShuffle_2200); + Vector128 c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g + Vector128 output = Sse2.Add(input.AsByte(), c.AsByte()); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else + + if (i != numPixels) { - AddGreenToBlueAndRedScalar(pixelData); + AddGreenToBlueAndRedScalar(pixelData[(int)i..]); } } + else + { + AddGreenToBlueAndRedScalar(pixelData); + } + } - private static void AddGreenToBlueAndRedScalar(Span pixelData) + private static void AddGreenToBlueAndRedScalar(Span pixelData) + { + int numPixels = pixelData.Length; + for (int i = 0; i < numPixels; i++) { - int numPixels = pixelData.Length; - for (int i = 0; i < numPixels; i++) - { - uint argb = pixelData[i]; - uint green = (argb >> 8) & 0xff; - uint redBlue = argb & 0x00ff00ffu; - redBlue += (green << 16) | green; - redBlue &= 0x00ff00ffu; - pixelData[i] = (argb & 0xff00ff00u) | redBlue; - } + uint argb = pixelData[i]; + uint green = (argb >> 8) & 0xff; + uint redBlue = argb & 0x00ff00ffu; + redBlue += (green << 16) | green; + redBlue &= 0x00ff00ffu; + pixelData[i] = (argb & 0xff00ff00u) | redBlue; } + } - public static void SubtractGreenFromBlueAndRed(Span pixelData) + public static void SubtractGreenFromBlueAndRed(Span pixelData) + { + if (Avx2.IsSupported) { - if (Avx2.IsSupported) + var subtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); + int numPixels = pixelData.Length; + nint i; + for (i = 0; i <= numPixels - 8; i += 8) { - var subtractGreenFromBlueAndRedMaskAvx2 = Vector256.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255, 17, 255, 17, 255, 21, 255, 21, 255, 25, 255, 25, 255, 29, 255, 29, 255); - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 8; i += 8) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector256 input = Unsafe.As>(ref pos).AsByte(); - Vector256 in0g0g = Avx2.Shuffle(input, subtractGreenFromBlueAndRedMaskAvx2); - Vector256 output = Avx2.Subtract(input, in0g0g); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - - if (i != numPixels) - { - SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector256 input = Unsafe.As>(ref pos).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, subtractGreenFromBlueAndRedMaskAvx2); + Vector256 output = Avx2.Subtract(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else if (Ssse3.IsSupported) - { - var subtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 4; i += 4) - { - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 in0g0g = Ssse3.Shuffle(input, subtractGreenFromBlueAndRedMaskSsse3); - Vector128 output = Sse2.Subtract(input, in0g0g); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - if (i != numPixels) - { - SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); - } + if (i != numPixels) + { + SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); } - else if (Sse2.IsSupported) + } + else if (Ssse3.IsSupported) + { + var subtractGreenFromBlueAndRedMaskSsse3 = Vector128.Create(1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255); + int numPixels = pixelData.Length; + nint i; + for (i = 0; i <= numPixels - 4; i += 4) { - int numPixels = pixelData.Length; - nint i; - for (i = 0; i <= numPixels - 4; i += 4) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); - Vector128 input = Unsafe.As>(ref pos).AsByte(); - Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g - Vector128 b = Sse2.ShuffleLow(a, mmShuffle_2200); - Vector128 c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g - Vector128 output = Sse2.Subtract(input.AsByte(), c.AsByte()); - Unsafe.As>(ref pos) = output.AsUInt32(); - } - - if (i != numPixels) - { - SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, subtractGreenFromBlueAndRedMaskSsse3); + Vector128 output = Sse2.Subtract(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else + + if (i != numPixels) { - SubtractGreenFromBlueAndRedScalar(pixelData); + SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); } } - - private static void SubtractGreenFromBlueAndRedScalar(Span pixelData) + else if (Sse2.IsSupported) { int numPixels = pixelData.Length; - for (int i = 0; i < numPixels; i++) + nint i; + for (i = 0; i <= numPixels - 4; i += 4) { - uint argb = pixelData[i]; - uint green = (argb >> 8) & 0xff; - uint newR = (((argb >> 16) & 0xff) - green) & 0xff; - uint newB = (((argb >> 0) & 0xff) - green) & 0xff; - pixelData[i] = (argb & 0xff00ff00u) | (newR << 16) | newB; + const byte mmShuffle_2200 = 0b_10_10_00_00; + + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, mmShuffle_2200); + Vector128 c = Sse2.ShuffleHigh(b, mmShuffle_2200); // 0g0g + Vector128 output = Sse2.Subtract(input.AsByte(), c.AsByte()); + Unsafe.As>(ref pos) = output.AsUInt32(); } - } - /// - /// If there are not many unique pixel values, it is more efficient to create a color index array and replace the pixel values by the array's indices. - /// This will reverse the color index transform. - /// - /// The transform data contains color table size and the entries in the color table. - /// The pixel data to apply the reverse transform on. - public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) - { - int bitsPerPixel = 8 >> transform.Bits; - int width = transform.XSize; - int height = transform.YSize; - Span colorMap = transform.Data.GetSpan(); - int decodedPixels = 0; - if (bitsPerPixel < 8) + if (i != numPixels) { - int pixelsPerByte = 1 << transform.Bits; - int countMask = pixelsPerByte - 1; - int bitMask = (1 << bitsPerPixel) - 1; + SubtractGreenFromBlueAndRedScalar(pixelData[(int)i..]); + } + } + else + { + SubtractGreenFromBlueAndRedScalar(pixelData); + } + } - uint[] decodedPixelData = new uint[width * height]; - int pixelDataPos = 0; - for (int y = 0; y < height; y++) - { - uint packedPixels = 0; - for (int x = 0; x < width; x++) - { - // We need to load fresh 'packed_pixels' once every - // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte - // is a power of 2, so we can just use a mask for that, instead of - // decrementing a counter. - if ((x & countMask) == 0) - { - packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); - } + private static void SubtractGreenFromBlueAndRedScalar(Span pixelData) + { + int numPixels = pixelData.Length; + for (int i = 0; i < numPixels; i++) + { + uint argb = pixelData[i]; + uint green = (argb >> 8) & 0xff; + uint newR = (((argb >> 16) & 0xff) - green) & 0xff; + uint newB = (((argb >> 0) & 0xff) - green) & 0xff; + pixelData[i] = (argb & 0xff00ff00u) | (newR << 16) | newB; + } + } - decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; - packedPixels >>= bitsPerPixel; - } - } + /// + /// If there are not many unique pixel values, it is more efficient to create a color index array and replace the pixel values by the array's indices. + /// This will reverse the color index transform. + /// + /// The transform data contains color table size and the entries in the color table. + /// The pixel data to apply the reverse transform on. + public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) + { + int bitsPerPixel = 8 >> transform.Bits; + int width = transform.XSize; + int height = transform.YSize; + Span colorMap = transform.Data.GetSpan(); + int decodedPixels = 0; + if (bitsPerPixel < 8) + { + int pixelsPerByte = 1 << transform.Bits; + int countMask = pixelsPerByte - 1; + int bitMask = (1 << bitsPerPixel) - 1; - decodedPixelData.AsSpan().CopyTo(pixelData); - } - else + uint[] decodedPixelData = new uint[width * height]; + int pixelDataPos = 0; + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + uint packedPixels = 0; + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + // We need to load fresh 'packed_pixels' once every + // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte + // is a power of 2, so we can just use a mask for that, instead of + // decrementing a counter. + if ((x & countMask) == 0) { - uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); - pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; - decodedPixels++; + packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } + + decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; + packedPixels >>= bitsPerPixel; } } - } - /// - /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - /// The transform data. - /// The pixel data to apply the inverse transform on. - public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) - { - int width = transform.XSize; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int safeWidth = width & ~mask; - int remainingWidth = width - safeWidth; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int y = 0; - int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; - Span transformData = transform.Data.GetSpan(); - - int pixelPos = 0; - while (y < yEnd) + decodedPixelData.AsSpan().CopyTo(pixelData); + } + else + { + for (int y = 0; y < height; y++) { - int predRowIdx = predRowIdxStart; - var m = default(Vp8LMultipliers); - int srcSafeEnd = pixelPos + safeWidth; - int srcEnd = pixelPos + width; - while (pixelPos < srcSafeEnd) + for (int x = 0; x < width; x++) { - uint colorCode = transformData[predRowIdx++]; - ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData.Slice(pixelPos, tileWidth)); - pixelPos += tileWidth; - } - - if (pixelPos < srcEnd) - { - uint colorCode = transformData[predRowIdx]; - ColorCodeToMultipliers(colorCode, ref m); - TransformColorInverse(m, pixelData.Slice(pixelPos, remainingWidth)); - pixelPos += remainingWidth; - } - - y++; - if ((y & mask) == 0) - { - predRowIdxStart += tilesPerRow; + uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]); + pixelData[decodedPixels] = colorMap[(int)colorMapIndex]; + decodedPixels++; } } } + } - /// - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - /// The Vp8LMultipliers. - /// The pixel data to transform. - /// The number of pixels to process. - public static void TransformColor(Vp8LMultipliers m, Span pixelData, int numPixels) + /// + /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + /// The transform data. + /// The pixel data to apply the inverse transform on. + public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData) + { + int width = transform.XSize; + int yEnd = transform.YSize; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int safeWidth = width & ~mask; + int remainingWidth = width - safeWidth; + int tilesPerRow = SubSampleSize(width, transform.Bits); + int y = 0; + int predRowIdxStart = (y >> transform.Bits) * tilesPerRow; + Span transformData = transform.Data.GetSpan(); + + int pixelPos = 0; + while (y < yEnd) { - if (Avx2.IsSupported && numPixels >= 8) + int predRowIdx = predRowIdxStart; + var m = default(Vp8LMultipliers); + int srcSafeEnd = pixelPos + safeWidth; + int srcEnd = pixelPos + width; + while (pixelPos < srcSafeEnd) { - var transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - var transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); + uint colorCode = transformData[predRowIdx++]; + ColorCodeToMultipliers(colorCode, ref m); + TransformColorInverse(m, pixelData.Slice(pixelPos, tileWidth)); + pixelPos += tileWidth; + } - nint idx; - for (idx = 0; idx <= numPixels - 8; idx += 8) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector256 input = Unsafe.As>(ref pos); - Vector256 a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); - Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); - Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); - Vector256 i = Avx2.And(h, transformColorRedBlueMask256); - Vector256 output = Avx2.Subtract(input.AsByte(), i); - Unsafe.As>(ref pos) = output.AsUInt32(); - } + if (pixelPos < srcEnd) + { + uint colorCode = transformData[predRowIdx]; + ColorCodeToMultipliers(colorCode, ref m); + TransformColorInverse(m, pixelData.Slice(pixelPos, remainingWidth)); + pixelPos += remainingWidth; + } - if (idx != numPixels) - { - TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); - } + y++; + if ((y & mask) == 0) + { + predRowIdxStart += tilesPerRow; } - else if (Sse2.IsSupported) + } + } + + /// + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + /// The Vp8LMultipliers. + /// The pixel data to transform. + /// The number of pixels to process. + public static void TransformColor(Vp8LMultipliers m, Span pixelData, int numPixels) + { + if (Avx2.IsSupported && numPixels >= 8) + { + var transformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var transformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); + + nint idx; + for (idx = 0; idx <= numPixels - 8; idx += 8) { - var transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - var transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - nint idx; - for (idx = 0; idx <= numPixels - 4; idx += 4) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector128 input = Unsafe.As>(ref pos); - Vector128 a = Sse2.And(input.AsByte(), transformColorAlphaGreenMask); - Vector128 b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200); - Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); - Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); - Vector128 f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector128 g = Sse2.ShiftRightLogical(f.AsInt32(), 16); - Vector128 h = Sse2.Add(g.AsByte(), d.AsByte()); - Vector128 i = Sse2.And(h, transformColorRedBlueMask); - Vector128 output = Sse2.Subtract(input.AsByte(), i); - Unsafe.As>(ref pos) = output.AsUInt32(); - } + const byte mmShuffle_2200 = 0b_10_10_00_00; - if (idx != numPixels) - { - TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector256 input = Unsafe.As>(ref pos); + Vector256 a = Avx2.And(input.AsByte(), transformColorAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); + Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); + Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); + Vector256 i = Avx2.And(h, transformColorRedBlueMask256); + Vector256 output = Avx2.Subtract(input.AsByte(), i); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else + + if (idx != numPixels) { - TransformColorScalar(m, pixelData, numPixels); + TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); } } - - private static void TransformColorScalar(Vp8LMultipliers m, Span data, int numPixels) + else if (Sse2.IsSupported) { - for (int i = 0; i < numPixels; i++) - { - uint argb = data[i]; - sbyte green = U32ToS8(argb >> 8); - sbyte red = U32ToS8(argb >> 16); - int newRed = red & 0xff; - int newBlue = (int)(argb & 0xff); - newRed -= ColorTransformDelta((sbyte)m.GreenToRed, green); - newRed &= 0xff; - newBlue -= ColorTransformDelta((sbyte)m.GreenToBlue, green); - newBlue -= ColorTransformDelta((sbyte)m.RedToBlue, red); - newBlue &= 0xff; - data[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; + var transformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + var transformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); + nint idx; + for (idx = 0; idx <= numPixels - 4; idx += 4) + { + const byte mmShuffle_2200 = 0b_10_10_00_00; + + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector128 input = Unsafe.As>(ref pos); + Vector128 a = Sse2.And(input.AsByte(), transformColorAlphaGreenMask); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); + Vector128 f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector128 g = Sse2.ShiftRightLogical(f.AsInt32(), 16); + Vector128 h = Sse2.Add(g.AsByte(), d.AsByte()); + Vector128 i = Sse2.And(h, transformColorRedBlueMask); + Vector128 output = Sse2.Subtract(input.AsByte(), i); + Unsafe.As>(ref pos) = output.AsUInt32(); + } + + if (idx != numPixels) + { + TransformColorScalar(m, pixelData[(int)idx..], numPixels - (int)idx); } } + else + { + TransformColorScalar(m, pixelData, numPixels); + } + } - /// - /// Reverses the color space transform. - /// - /// The color transform element. - /// The pixel data to apply the inverse transform on. - public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData) + private static void TransformColorScalar(Vp8LMultipliers m, Span data, int numPixels) + { + for (int i = 0; i < numPixels; i++) { - if (Avx2.IsSupported && pixelData.Length >= 8) - { - var transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - nint idx; - for (idx = 0; idx <= pixelData.Length - 8; idx += 8) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector256 input = Unsafe.As>(ref pos); - Vector256 a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); - Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); - Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); - Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); - Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); - Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); - Vector256 output = Avx2.Or(j.AsByte(), a); - Unsafe.As>(ref pos) = output.AsUInt32(); - } + uint argb = data[i]; + sbyte green = U32ToS8(argb >> 8); + sbyte red = U32ToS8(argb >> 16); + int newRed = red & 0xff; + int newBlue = (int)(argb & 0xff); + newRed -= ColorTransformDelta((sbyte)m.GreenToRed, green); + newRed &= 0xff; + newBlue -= ColorTransformDelta((sbyte)m.GreenToBlue, green); + newBlue -= ColorTransformDelta((sbyte)m.RedToBlue, red); + newBlue &= 0xff; + data[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; + } + } - if (idx != pixelData.Length) - { - TransformColorInverseScalar(m, pixelData[(int)idx..]); - } + /// + /// Reverses the color space transform. + /// + /// The color transform element. + /// The pixel data to apply the inverse transform on. + public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData) + { + if (Avx2.IsSupported && pixelData.Length >= 8) + { + var transformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); + nint idx; + for (idx = 0; idx <= pixelData.Length - 8; idx += 8) + { + const byte mmShuffle_2200 = 0b_10_10_00_00; + + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector256 input = Unsafe.As>(ref pos); + Vector256 a = Avx2.And(input.AsByte(), transformColorInverseAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), mmShuffle_2200); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); + Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); + Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); + Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); + Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); + Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); + Vector256 output = Avx2.Or(j.AsByte(), a); + Unsafe.As>(ref pos) = output.AsUInt32(); + } + + if (idx != pixelData.Length) + { + TransformColorInverseScalar(m, pixelData[(int)idx..]); } - else if (Sse2.IsSupported) - { - var transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); - Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); + } + else if (Sse2.IsSupported) + { + var transformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - nint idx; - for (idx = 0; idx <= pixelData.Length - 4; idx += 4) - { - const byte mmShuffle_2200 = 0b_10_10_00_00; - - ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); - Vector128 input = Unsafe.As>(ref pos); - Vector128 a = Sse2.And(input.AsByte(), transformColorInverseAlphaGreenMask); - Vector128 b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200); - Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); - Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = Sse2.Add(input.AsByte(), d.AsByte()); - Vector128 f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); - Vector128 g = Sse2.MultiplyHigh(f, multsb2.AsInt16()); - Vector128 h = Sse2.ShiftRightLogical(g.AsInt32(), 8); - Vector128 i = Sse2.Add(h.AsByte(), f.AsByte()); - Vector128 j = Sse2.ShiftRightLogical(i.AsInt16(), 8); - Vector128 output = Sse2.Or(j.AsByte(), a); - Unsafe.As>(ref pos) = output.AsUInt32(); - } + nint idx; + for (idx = 0; idx <= pixelData.Length - 4; idx += 4) + { + const byte mmShuffle_2200 = 0b_10_10_00_00; - if (idx != pixelData.Length) - { - TransformColorInverseScalar(m, pixelData[(int)idx..]); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector128 input = Unsafe.As>(ref pos); + Vector128 a = Sse2.And(input.AsByte(), transformColorInverseAlphaGreenMask); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), mmShuffle_2200); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), mmShuffle_2200); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.Add(input.AsByte(), d.AsByte()); + Vector128 f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); + Vector128 g = Sse2.MultiplyHigh(f, multsb2.AsInt16()); + Vector128 h = Sse2.ShiftRightLogical(g.AsInt32(), 8); + Vector128 i = Sse2.Add(h.AsByte(), f.AsByte()); + Vector128 j = Sse2.ShiftRightLogical(i.AsInt16(), 8); + Vector128 output = Sse2.Or(j.AsByte(), a); + Unsafe.As>(ref pos) = output.AsUInt32(); } - else + + if (idx != pixelData.Length) { - TransformColorInverseScalar(m, pixelData); + TransformColorInverseScalar(m, pixelData[(int)idx..]); } } + else + { + TransformColorInverseScalar(m, pixelData); + } + } - private static void TransformColorInverseScalar(Vp8LMultipliers m, Span pixelData) + private static void TransformColorInverseScalar(Vp8LMultipliers m, Span pixelData) + { + for (int i = 0; i < pixelData.Length; i++) { - for (int i = 0; i < pixelData.Length; i++) - { - uint argb = pixelData[i]; - sbyte green = (sbyte)(argb >> 8); - uint red = argb >> 16; - int newRed = (int)(red & 0xff); - int newBlue = (int)argb & 0xff; - newRed += ColorTransformDelta((sbyte)m.GreenToRed, green); - newRed &= 0xff; - newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, green); - newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); - newBlue &= 0xff; - - pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; - } + uint argb = pixelData[i]; + sbyte green = (sbyte)(argb >> 8); + uint red = argb >> 16; + int newRed = (int)(red & 0xff); + int newBlue = (int)argb & 0xff; + newRed += ColorTransformDelta((sbyte)m.GreenToRed, green); + newRed &= 0xff; + newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, green); + newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed); + newBlue &= 0xff; + + pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue; } + } - /// - /// This will reverse the predictor transform. - /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. - /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded. - /// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode. - /// - /// The transform data. - /// The pixel data to apply the inverse transform. - /// The resulting pixel data with the reversed transformation data. - public static void PredictorInverseTransform( - Vp8LTransform transform, - Span pixelData, - Span outputSpan) - { - fixed (uint* inputFixed = pixelData) - { - fixed (uint* outputFixed = outputSpan) + /// + /// This will reverse the predictor transform. + /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. + /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded. + /// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode. + /// + /// The transform data. + /// The pixel data to apply the inverse transform. + /// The resulting pixel data with the reversed transformation data. + public static void PredictorInverseTransform( + Vp8LTransform transform, + Span pixelData, + Span outputSpan) + { + fixed (uint* inputFixed = pixelData) + { + fixed (uint* outputFixed = outputSpan) + { + uint* input = inputFixed; + uint* output = outputFixed; + + int width = transform.XSize; + Span transformData = transform.Data.GetSpan(); + + // First Row follows the L (mode=1) mode. + PredictorAdd0(input, 1, output); + PredictorAdd1(input + 1, width - 1, output + 1); + input += width; + output += width; + + int y = 1; + int yEnd = transform.YSize; + int tileWidth = 1 << transform.Bits; + int mask = tileWidth - 1; + int tilesPerRow = SubSampleSize(width, transform.Bits); + int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; + Span scratch = stackalloc short[8]; + while (y < yEnd) { - uint* input = inputFixed; - uint* output = outputFixed; - - int width = transform.XSize; - Span transformData = transform.Data.GetSpan(); + int predictorModeIdx = predictorModeIdxBase; + int x = 1; - // First Row follows the L (mode=1) mode. - PredictorAdd0(input, 1, output); - PredictorAdd1(input + 1, width - 1, output + 1); - input += width; - output += width; + // First pixel follows the T (mode=2) mode. + PredictorAdd2(input, output - width, 1, output); - int y = 1; - int yEnd = transform.YSize; - int tileWidth = 1 << transform.Bits; - int mask = tileWidth - 1; - int tilesPerRow = SubSampleSize(width, transform.Bits); - int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow; - Span scratch = stackalloc short[8]; - while (y < yEnd) + // .. the rest: + while (x < width) { - int predictorModeIdx = predictorModeIdxBase; - int x = 1; - - // First pixel follows the T (mode=2) mode. - PredictorAdd2(input, output - width, 1, output); - - // .. the rest: - while (x < width) + uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; + int xEnd = (x & ~mask) + tileWidth; + if (xEnd > width) { - uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf; - int xEnd = (x & ~mask) + tileWidth; - if (xEnd > width) - { - xEnd = width; - } - - // There are 14 different prediction modes. - // In each prediction mode, the current pixel value is predicted from one - // or more neighboring pixels whose values are already known. - switch (predictorMode) - { - case 0: - PredictorAdd0(input + x, xEnd - x, output + x); - break; - case 1: - PredictorAdd1(input + x, xEnd - x, output + x); - break; - case 2: - PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); - break; - case 3: - PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); - break; - case 4: - PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); - break; - case 5: - PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); - break; - case 6: - PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); - break; - case 7: - PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); - break; - case 8: - PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); - break; - case 9: - PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); - break; - case 10: - PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); - break; - case 11: - PredictorAdd11(input + x, output + x - width, xEnd - x, output + x, scratch); - break; - case 12: - PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); - break; - case 13: - PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); - break; - } - - x = xEnd; + xEnd = width; } - input += width; - output += width; - y++; - - if ((y & mask) == 0) + // There are 14 different prediction modes. + // In each prediction mode, the current pixel value is predicted from one + // or more neighboring pixels whose values are already known. + switch (predictorMode) { - // Use the same mask, since tiles are squares. - predictorModeIdxBase += tilesPerRow; + case 0: + PredictorAdd0(input + x, xEnd - x, output + x); + break; + case 1: + PredictorAdd1(input + x, xEnd - x, output + x); + break; + case 2: + PredictorAdd2(input + x, output + x - width, xEnd - x, output + x); + break; + case 3: + PredictorAdd3(input + x, output + x - width, xEnd - x, output + x); + break; + case 4: + PredictorAdd4(input + x, output + x - width, xEnd - x, output + x); + break; + case 5: + PredictorAdd5(input + x, output + x - width, xEnd - x, output + x); + break; + case 6: + PredictorAdd6(input + x, output + x - width, xEnd - x, output + x); + break; + case 7: + PredictorAdd7(input + x, output + x - width, xEnd - x, output + x); + break; + case 8: + PredictorAdd8(input + x, output + x - width, xEnd - x, output + x); + break; + case 9: + PredictorAdd9(input + x, output + x - width, xEnd - x, output + x); + break; + case 10: + PredictorAdd10(input + x, output + x - width, xEnd - x, output + x); + break; + case 11: + PredictorAdd11(input + x, output + x - width, xEnd - x, output + x, scratch); + break; + case 12: + PredictorAdd12(input + x, output + x - width, xEnd - x, output + x); + break; + case 13: + PredictorAdd13(input + x, output + x - width, xEnd - x, output + x); + break; } + + x = xEnd; + } + + input += width; + output += width; + y++; + + if ((y & mask) == 0) + { + // Use the same mask, since tiles are squares. + predictorModeIdxBase += tilesPerRow; } } } - - outputSpan.CopyTo(pixelData); } - public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) - { - newColorMap[0] = transformData[0]; - Span data = MemoryMarshal.Cast(transformData); - Span newData = MemoryMarshal.Cast(newColorMap); - int numColorsX4 = 4 * numColors; - int i; - for (i = 4; i < numColorsX4; i++) - { - // Equivalent to AddPixelEq(), on a byte-basis. - newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); - } + outputSpan.CopyTo(pixelData); + } - int colorMapLength4 = 4 * newColorMap.Length; - for (; i < colorMapLength4; i++) - { - newData[i] = 0; // black tail. - } + public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap) + { + newColorMap[0] = transformData[0]; + Span data = MemoryMarshal.Cast(transformData); + Span newData = MemoryMarshal.Cast(newColorMap); + int numColorsX4 = 4 * numColors; + int i; + for (i = 4; i < numColorsX4; i++) + { + // Equivalent to AddPixelEq(), on a byte-basis. + newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff); } - /// - /// Difference of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static uint SubPixels(uint a, uint b) + int colorMapLength4 = 4 * newColorMap.Length; + for (; i < colorMapLength4; i++) { - uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); - uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + newData[i] = 0; // black tail. } + } - /// - /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. - /// - public static void BundleColorMap(Span row, int width, int xBits, Span dst) + /// + /// Difference of each component, mod 256. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint SubPixels(uint a, uint b) + { + uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } + + /// + /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. + /// + public static void BundleColorMap(Span row, int width, int xBits, Span dst) + { + int x; + if (xBits > 0) { - int x; - if (xBits > 0) + int bitDepth = 1 << (3 - xBits); + int mask = (1 << xBits) - 1; + uint code = 0xff000000; + for (x = 0; x < width; x++) { - int bitDepth = 1 << (3 - xBits); - int mask = (1 << xBits) - 1; - uint code = 0xff000000; - for (x = 0; x < width; x++) + int xsub = x & mask; + if (xsub == 0) { - int xsub = x & mask; - if (xsub == 0) - { - code = 0xff000000; - } - - code |= (uint)(row[x] << (8 + (bitDepth * xsub))); - dst[x >> xBits] = code; + code = 0xff000000; } + + code |= (uint)(row[x] << (8 + (bitDepth * xsub))); + dst[x >> xBits] = code; } - else + } + else + { + for (x = 0; x < width; x++) { - for (x = 0; x < width; x++) - { - dst[x] = (uint)(0xff000000 | (row[x] << 8)); - } + dst[x] = (uint)(0xff000000 | (row[x] << 8)); } } + } - /// - /// Compute the combined Shanon's entropy for distribution {X} and {X+Y}. - /// - /// Shanon entropy. - public static float CombinedShannonEntropy(Span x, Span y) + /// + /// Compute the combined Shanon's entropy for distribution {X} and {X+Y}. + /// + /// Shanon entropy. + public static float CombinedShannonEntropy(Span x, Span y) + { + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - double retVal = 0.0d; - Vector256 tmp = Vector256.Zero; // has the size of the scratch space of sizeof(int) * 8 - ref int xRef = ref MemoryMarshal.GetReference(x); - ref int yRef = ref MemoryMarshal.GetReference(y); - Vector256 sumXY256 = Vector256.Zero; - Vector256 sumX256 = Vector256.Zero; - ref int tmpRef = ref Unsafe.As, int>(ref tmp); - for (nint i = 0; i < 256; i += 8) + double retVal = 0.0d; + Vector256 tmp = Vector256.Zero; // has the size of the scratch space of sizeof(int) * 8 + ref int xRef = ref MemoryMarshal.GetReference(x); + ref int yRef = ref MemoryMarshal.GetReference(y); + Vector256 sumXY256 = Vector256.Zero; + Vector256 sumX256 = Vector256.Zero; + ref int tmpRef = ref Unsafe.As, int>(ref tmp); + for (nint i = 0; i < 256; i += 8) + { + Vector256 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector256 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + + // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. + int mask = Avx2.MoveMask(Avx2.CompareEqual(xVec, Vector256.Zero).AsByte()); + if (mask != -1) { - Vector256 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector256 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + Vector256 xy256 = Avx2.Add(xVec, yVec); + sumXY256 = Avx2.Add(sumXY256, xy256); + sumX256 = Avx2.Add(sumX256, xVec); - // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. - int mask = Avx2.MoveMask(Avx2.CompareEqual(xVec, Vector256.Zero).AsByte()); - if (mask != -1) + // Analyze the different X + Y. + Unsafe.As>(ref tmpRef) = xy256; + if (tmpRef != 0) { - Vector256 xy256 = Avx2.Add(xVec, yVec); - sumXY256 = Avx2.Add(sumXY256, xy256); - sumX256 = Avx2.Add(sumX256, xVec); - - // Analyze the different X + Y. - Unsafe.As>(ref tmpRef) = xy256; - if (tmpRef != 0) + retVal -= FastSLog2((uint)tmpRef); + if (Unsafe.Add(ref xRef, i) != 0) { - retVal -= FastSLog2((uint)tmpRef); - if (Unsafe.Add(ref xRef, i) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); } + } - if (Unsafe.Add(ref tmpRef, 1) != 0) + if (Unsafe.Add(ref tmpRef, 1) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); + if (Unsafe.Add(ref xRef, i + 1) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); - if (Unsafe.Add(ref xRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); } + } - if (Unsafe.Add(ref tmpRef, 2) != 0) + if (Unsafe.Add(ref tmpRef, 2) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); + if (Unsafe.Add(ref xRef, i + 2) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); - if (Unsafe.Add(ref xRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); } + } - if (Unsafe.Add(ref tmpRef, 3) != 0) + if (Unsafe.Add(ref tmpRef, 3) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); + if (Unsafe.Add(ref xRef, i + 3) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); - if (Unsafe.Add(ref xRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); } + } - if (Unsafe.Add(ref tmpRef, 4) != 0) + if (Unsafe.Add(ref tmpRef, 4) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 4)); + if (Unsafe.Add(ref xRef, i + 4) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 4)); - if (Unsafe.Add(ref xRef, i + 4) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 4)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 4)); } + } - if (Unsafe.Add(ref tmpRef, 5) != 0) + if (Unsafe.Add(ref tmpRef, 5) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 5)); + if (Unsafe.Add(ref xRef, i + 5) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 5)); - if (Unsafe.Add(ref xRef, i + 5) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 5)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 5)); } + } - if (Unsafe.Add(ref tmpRef, 6) != 0) + if (Unsafe.Add(ref tmpRef, 6) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 6)); + if (Unsafe.Add(ref xRef, i + 6) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 6)); - if (Unsafe.Add(ref xRef, i + 6) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 6)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 6)); } + } - if (Unsafe.Add(ref tmpRef, 7) != 0) + if (Unsafe.Add(ref tmpRef, 7) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 7)); + if (Unsafe.Add(ref xRef, i + 7) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 7)); - if (Unsafe.Add(ref xRef, i + 7) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 7)); - } + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 7)); } } - else - { - // X is fully 0, so only deal with Y. - sumXY256 = Avx2.Add(sumXY256, yVec); + } + else + { + // X is fully 0, so only deal with Y. + sumXY256 = Avx2.Add(sumXY256, yVec); - if (Unsafe.Add(ref yRef, i) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); - } + if (Unsafe.Add(ref yRef, i) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); + } - if (Unsafe.Add(ref yRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); - } + if (Unsafe.Add(ref yRef, i + 1) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); + } - if (Unsafe.Add(ref yRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); - } + if (Unsafe.Add(ref yRef, i + 2) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); + } - if (Unsafe.Add(ref yRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); - } + if (Unsafe.Add(ref yRef, i + 3) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); + } - if (Unsafe.Add(ref yRef, i + 4) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 4)); - } + if (Unsafe.Add(ref yRef, i + 4) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 4)); + } - if (Unsafe.Add(ref yRef, i + 5) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 5)); - } + if (Unsafe.Add(ref yRef, i + 5) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 5)); + } - if (Unsafe.Add(ref yRef, i + 6) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 6)); - } + if (Unsafe.Add(ref yRef, i + 6) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 6)); + } - if (Unsafe.Add(ref yRef, i + 7) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 7)); - } + if (Unsafe.Add(ref yRef, i + 7) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 7)); } } + } - // Sum up sumX256 to get sumX and sum up sumXY256 to get sumXY. - int sumX = Numerics.ReduceSum(sumX256); - int sumXY = Numerics.ReduceSum(sumXY256); + // Sum up sumX256 to get sumX and sum up sumXY256 to get sumXY. + int sumX = Numerics.ReduceSum(sumX256); + int sumXY = Numerics.ReduceSum(sumXY256); - retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); + retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); - return (float)retVal; - } - else + return (float)retVal; + } + else + { + double retVal = 0.0d; + uint sumX = 0, sumXY = 0; + for (int i = 0; i < 256; i++) { - double retVal = 0.0d; - uint sumX = 0, sumXY = 0; - for (int i = 0; i < 256; i++) + uint xi = (uint)x[i]; + if (xi != 0) { - uint xi = (uint)x[i]; - if (xi != 0) - { - uint xy = xi + (uint)y[i]; - sumX += xi; - retVal -= FastSLog2(xi); - sumXY += xy; - retVal -= FastSLog2(xy); - } - else if (y[i] != 0) - { - sumXY += (uint)y[i]; - retVal -= FastSLog2((uint)y[i]); - } + uint xy = xi + (uint)y[i]; + sumX += xi; + retVal -= FastSLog2(xi); + sumXY += xy; + retVal -= FastSLog2(xy); + } + else if (y[i] != 0) + { + sumXY += (uint)y[i]; + retVal -= FastSLog2((uint)y[i]); } - - retVal += FastSLog2(sumX) + FastSLog2(sumXY); - return (float)retVal; } - } - [MethodImpl(InliningOptions.ShortMethod)] - public static byte TransformColorRed(sbyte greenToRed, uint argb) - { - sbyte green = U32ToS8(argb >> 8); - int newRed = (int)(argb >> 16); - newRed -= ColorTransformDelta(greenToRed, green); - return (byte)(newRed & 0xff); + retVal += FastSLog2(sumX) + FastSLog2(sumXY); + return (float)retVal; } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static byte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) - { - sbyte green = U32ToS8(argb >> 8); - sbyte red = U32ToS8(argb >> 16); - int newBlue = (int)(argb & 0xff); - newBlue -= ColorTransformDelta(greenToBlue, green); - newBlue -= ColorTransformDelta(redToBlue, red); - return (byte)(newBlue & 0xff); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static byte TransformColorRed(sbyte greenToRed, uint argb) + { + sbyte green = U32ToS8(argb >> 8); + int newRed = (int)(argb >> 16); + newRed -= ColorTransformDelta(greenToRed, green); + return (byte)(newRed & 0xff); + } - /// - /// Fast calculation of log2(v) for integer input. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); + [MethodImpl(InliningOptions.ShortMethod)] + public static byte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) + { + sbyte green = U32ToS8(argb >> 8); + sbyte red = U32ToS8(argb >> 16); + int newBlue = (int)(argb & 0xff); + newBlue -= ColorTransformDelta(greenToBlue, green); + newBlue -= ColorTransformDelta(redToBlue, red); + return (byte)(newBlue & 0xff); + } - /// - /// Fast calculation of v * log2(v) for integer input. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float FastSLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); + /// + /// Fast calculation of log2(v) for integer input. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float FastLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.Log2Table[v] : FastLog2Slow(v); - [MethodImpl(InliningOptions.ShortMethod)] - public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) - { - m.GreenToRed = (byte)(colorCode & 0xff); - m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); - m.RedToBlue = (byte)((colorCode >> 16) & 0xff); - } + /// + /// Fast calculation of v * log2(v) for integer input. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static float FastSLog2(uint v) => v < LogLookupIdxMax ? WebpLookupTables.SLog2Table[v] : FastSLog2Slow(v); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m) + { + m.GreenToRed = (byte)(colorCode & 0xff); + m.GreenToBlue = (byte)((colorCode >> 8) & 0xff); + m.RedToBlue = (byte)((colorCode >> 16) & 0xff); + } - // Converts near lossless quality into max number of bits shaved off. - // 100 -> 0 - // 80..99 -> 1 - // 60..79 -> 2 - // 40..59 -> 3 - // 20..39 -> 4 - // 0..19 -> 5 - [MethodImpl(InliningOptions.ShortMethod)] - public static int NearLosslessBits(int nearLosslessQuality) => 5 - (nearLosslessQuality / 20); + // Converts near lossless quality into max number of bits shaved off. + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 + [MethodImpl(InliningOptions.ShortMethod)] + public static int NearLosslessBits(int nearLosslessQuality) => 5 - (nearLosslessQuality / 20); + + private static float FastSLog2Slow(uint v) + { + DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); - private static float FastSLog2Slow(uint v) + if (v < ApproxLogWithCorrectionMax) { - DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + int logCnt = 0; + uint y = 1; + float vF = v; + uint origV = v; + do + { + ++logCnt; + v >>= 1; + y <<= 1; + } + while (v >= LogLookupIdxMax); + + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + int correction = (int)((23 * (origV & (y - 1))) >> 4); + return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; + } - if (v < ApproxLogWithCorrectionMax) - { - int logCnt = 0; - uint y = 1; - float vF = v; - uint origV = v; - do - { - ++logCnt; - v >>= 1; - y <<= 1; - } - while (v >= LogLookupIdxMax); - - // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 - // Xf = floor(Xf) * (1 + (v % y) / v) - // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) - // The correction factor: log(1 + d) ~ d; for very small d values, so - // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v - // LOG_2_RECIPROCAL ~ 23/16 - int correction = (int)((23 * (origV & (y - 1))) >> 4); - return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; - } + return (float)(Log2Reciprocal * v * Math.Log(v)); + } - return (float)(Log2Reciprocal * v * Math.Log(v)); - } + private static float FastLog2Slow(uint v) + { + DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); - private static float FastLog2Slow(uint v) + if (v < ApproxLogWithCorrectionMax) { - DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); - - if (v < ApproxLogWithCorrectionMax) + int logCnt = 0; + uint y = 1; + uint origV = v; + do { - int logCnt = 0; - uint y = 1; - uint origV = v; - do - { - ++logCnt; - v >>= 1; - y <<= 1; - } - while (v >= LogLookupIdxMax); - - double log2 = WebpLookupTables.Log2Table[v] + logCnt; - if (origV >= ApproxLogMax) - { - // Since the division is still expensive, add this correction factor only - // for large values of 'v'. - int correction = (int)(23 * (origV & (y - 1))) >> 4; - log2 += (double)correction / origV; - } + ++logCnt; + v >>= 1; + y <<= 1; + } + while (v >= LogLookupIdxMax); - return (float)log2; + double log2 = WebpLookupTables.Log2Table[v] + logCnt; + if (origV >= ApproxLogMax) + { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + int correction = (int)(23 * (origV & (y - 1))) >> 4; + log2 += (double)correction / origV; } - return (float)(Log2Reciprocal * Math.Log(v)); + return (float)log2; } - /// - /// Splitting of distance and length codes into prefixes and - /// extra bits. The prefixes are encoded with an entropy code - /// while the extra bits are stored just as normal bits. - /// - private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) - { - int highestBit = BitOperations.Log2((uint)--distance); - int secondHighestBit = (distance >> (highestBit - 1)) & 1; - extraBits = highestBit - 1; - int code = (2 * highestBit) + secondHighestBit; - return code; - } + return (float)(Log2Reciprocal * Math.Log(v)); + } - private static int PrefixEncodeNoLut(int distance, ref int extraBits, ref int extraBitsValue) - { - int highestBit = BitOperations.Log2((uint)--distance); - int secondHighestBit = (distance >> (highestBit - 1)) & 1; - extraBits = highestBit - 1; - extraBitsValue = distance & ((1 << extraBits) - 1); - int code = (2 * highestBit) + secondHighestBit; - return code; - } + /// + /// Splitting of distance and length codes into prefixes and + /// extra bits. The prefixes are encoded with an entropy code + /// while the extra bits are stored just as normal bits. + /// + private static int PrefixEncodeBitsNoLut(int distance, ref int extraBits) + { + int highestBit = BitOperations.Log2((uint)--distance); + int secondHighestBit = (distance >> (highestBit - 1)) & 1; + extraBits = highestBit - 1; + int code = (2 * highestBit) + secondHighestBit; + return code; + } + + private static int PrefixEncodeNoLut(int distance, ref int extraBits, ref int extraBitsValue) + { + int highestBit = BitOperations.Log2((uint)--distance); + int secondHighestBit = (distance >> (highestBit - 1)) & 1; + extraBits = highestBit - 1; + extraBitsValue = distance & ((1 << extraBits) - 1); + int code = (2 * highestBit) + secondHighestBit; + return code; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd0(uint* input, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd0(uint* input, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - output[x] = AddPixels(input[x], WebpConstants.ArgbBlack); - } + output[x] = AddPixels(input[x], WebpConstants.ArgbBlack); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd1(uint* input, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd1(uint* input, int numberOfPixels, uint* output) + { + uint left = output[-1]; + for (int x = 0; x < numberOfPixels; x++) { - uint left = output[-1]; - for (int x = 0; x < numberOfPixels; x++) - { - output[x] = left = AddPixels(input[x], left); - } + output[x] = left = AddPixels(input[x], left); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd2(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd2(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor2(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor2(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd3(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd3(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor3(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor3(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd4(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd4(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor4(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor4(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd5(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd5(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor5(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor5(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd6(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd6(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor6(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor6(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd7(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd7(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor7(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor7(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd8(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd8(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor8(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor8(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd9(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd9(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor9(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor9(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd10(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd10(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor10(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor10(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd11(uint* input, uint* upper, int numberOfPixels, uint* output, Span scratch) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd11(uint* input, uint* upper, int numberOfPixels, uint* output, Span scratch) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor11(output[x - 1], upper + x, scratch); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor11(output[x - 1], upper + x, scratch); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd12(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd12(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor12(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor12(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void PredictorAdd13(uint* input, uint* upper, int numberOfPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + private static void PredictorAdd13(uint* input, uint* upper, int numberOfPixels, uint* output) + { + for (int x = 0; x < numberOfPixels; x++) { - for (int x = 0; x < numberOfPixels; x++) - { - uint pred = Predictor13(output[x - 1], upper + x); - output[x] = AddPixels(input[x], pred); - } + uint pred = Predictor13(output[x - 1], upper + x); + output[x] = AddPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor2(uint left, uint* top) => top[0]; + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor2(uint left, uint* top) => top[0]; - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor3(uint left, uint* top) => top[1]; + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor3(uint left, uint* top) => top[1]; - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor4(uint left, uint* top) => top[-1]; + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor4(uint left, uint* top) => top[-1]; - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor5(uint left, uint* top) => Average3(left, top[0], top[1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor5(uint left, uint* top) => Average3(left, top[0], top[1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor6(uint left, uint* top) => Average2(left, top[-1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor6(uint left, uint* top) => Average2(left, top[-1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor7(uint left, uint* top) => Average2(left, top[0]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor7(uint left, uint* top) => Average2(left, top[0]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor8(uint left, uint* top) => Average2(top[-1], top[0]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor8(uint left, uint* top) => Average2(top[-1], top[0]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor9(uint left, uint* top) => Average2(top[0], top[1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor9(uint left, uint* top) => Average2(top[0], top[1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor10(uint left, uint* top) => Average4(left, top[-1], top[0], top[1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor10(uint left, uint* top) => Average4(left, top[-1], top[0], top[1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor11(uint left, uint* top, Span scratch) => Select(top[0], left, top[-1], scratch); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor11(uint left, uint* top, Span scratch) => Select(top[0], left, top[-1], scratch); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor12(uint left, uint* top) => ClampedAddSubtractFull(left, top[0], top[-1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor12(uint left, uint* top) => ClampedAddSubtractFull(left, top[0], top[-1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static uint Predictor13(uint left, uint* top) => ClampedAddSubtractHalf(left, top[0], top[-1]); + [MethodImpl(InliningOptions.ShortMethod)] + public static uint Predictor13(uint left, uint* top) => ClampedAddSubtractHalf(left, top[0], top[-1]); - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub0(uint* input, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub0(uint* input, int numPixels, uint* output) + { + for (int i = 0; i < numPixels; i++) { - for (int i = 0; i < numPixels; i++) - { - output[i] = SubPixels(input[i], WebpConstants.ArgbBlack); - } + output[i] = SubPixels(input[i], WebpConstants.ArgbBlack); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub1(uint* input, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub1(uint* input, int numPixels, uint* output) + { + for (int i = 0; i < numPixels; i++) { - for (int i = 0; i < numPixels; i++) - { - output[i] = SubPixels(input[i], input[i - 1]); - } + output[i] = SubPixels(input[i], input[i - 1]); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub2(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub2(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor2(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor2(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub3(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub3(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor3(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor3(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub4(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub4(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor4(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor4(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub5(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub5(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor5(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor5(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub6(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub6(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor6(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor6(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub7(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub7(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor7(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor7(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub8(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub8(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor8(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor8(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub9(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub9(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor9(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor9(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub10(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub10(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor10(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor10(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub11(uint* input, uint* upper, int numPixels, uint* output, Span scratch) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub11(uint* input, uint* upper, int numPixels, uint* output, Span scratch) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor11(input[x - 1], upper + x, scratch); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor11(input[x - 1], upper + x, scratch); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub12(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub12(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor12(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor12(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void PredictorSub13(uint* input, uint* upper, int numPixels, uint* output) + [MethodImpl(InliningOptions.ShortMethod)] + public static void PredictorSub13(uint* input, uint* upper, int numPixels, uint* output) + { + for (int x = 0; x < numPixels; x++) { - for (int x = 0; x < numPixels; x++) - { - uint pred = Predictor13(input[x - 1], upper + x); - output[x] = SubPixels(input[x], pred); - } + uint pred = Predictor13(input[x - 1], upper + x); + output[x] = SubPixels(input[x], pred); } + } - /// - /// Computes sampled size of 'size' when sampling using 'sampling bits'. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static int SubSampleSize(int size, int samplingBits) => (size + (1 << samplingBits) - 1) >> samplingBits; + /// + /// Computes sampled size of 'size' when sampling using 'sampling bits'. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static int SubSampleSize(int size, int samplingBits) => (size + (1 << samplingBits) - 1) >> samplingBits; - /// - /// Sum of each component, mod 256. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static uint AddPixels(uint a, uint b) - { - uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); - uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); - return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); - } + /// + /// Sum of each component, mod 256. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static uint AddPixels(uint a, uint b) + { + uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u); + uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu); + } - // For sign-extended multiplying constants, pre-shifted by 5: - [MethodImpl(InliningOptions.ShortMethod)] - public static short Cst5b(int x) => (short)(((short)(x << 8)) >> 5); + // For sign-extended multiplying constants, pre-shifted by 5: + [MethodImpl(InliningOptions.ShortMethod)] + public static short Cst5b(int x) => (short)(((short)(x << 8)) >> 5); - private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) + private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); - Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); - Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); - Vector128 v1 = Sse2.Add(c0Vec.AsInt16(), c1Vec.AsInt16()); - Vector128 v2 = Sse2.Subtract(v1, c2Vec.AsInt16()); - Vector128 b = Sse2.PackUnsignedSaturate(v2, v2); - return Sse2.ConvertToUInt32(b.AsUInt32()); - } + Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); + Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); + Vector128 c2Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); + Vector128 v1 = Sse2.Add(c0Vec.AsInt16(), c1Vec.AsInt16()); + Vector128 v2 = Sse2.Subtract(v1, c2Vec.AsInt16()); + Vector128 b = Sse2.PackUnsignedSaturate(v2, v2); + return Sse2.ConvertToUInt32(b.AsUInt32()); + } - { - int a = AddSubtractComponentFull( - (int)(c0 >> 24), - (int)(c1 >> 24), - (int)(c2 >> 24)); - int r = AddSubtractComponentFull( - (int)((c0 >> 16) & 0xff), - (int)((c1 >> 16) & 0xff), - (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentFull( - (int)((c0 >> 8) & 0xff), - (int)((c1 >> 8) & 0xff), - (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; - } + { + int a = AddSubtractComponentFull( + (int)(c0 >> 24), + (int)(c1 >> 24), + (int)(c2 >> 24)); + int r = AddSubtractComponentFull( + (int)((c0 >> 16) & 0xff), + (int)((c1 >> 16) & 0xff), + (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentFull( + (int)((c0 >> 8) & 0xff), + (int)((c1 >> 8) & 0xff), + (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff)); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; } + } - private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) + private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); - Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); - Vector128 b0 = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); - Vector128 avg = Sse2.Add(c1Vec.AsInt16(), c0Vec.AsInt16()); - Vector128 a0 = Sse2.ShiftRightLogical(avg, 1); - Vector128 a1 = Sse2.Subtract(a0, b0.AsInt16()); - Vector128 bgta = Sse2.CompareGreaterThan(b0.AsInt16(), a0.AsInt16()); - Vector128 a2 = Sse2.Subtract(a1, bgta); - Vector128 a3 = Sse2.ShiftRightArithmetic(a2, 1); - Vector128 a4 = Sse2.Add(a0, a3).AsInt16(); - Vector128 a5 = Sse2.PackUnsignedSaturate(a4, a4); - return Sse2.ConvertToUInt32(a5.AsUInt32()); - } + Vector128 c0Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c0).AsByte(), Vector128.Zero); + Vector128 c1Vec = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c1).AsByte(), Vector128.Zero); + Vector128 b0 = Sse2.UnpackLow(Sse2.ConvertScalarToVector128UInt32(c2).AsByte(), Vector128.Zero); + Vector128 avg = Sse2.Add(c1Vec.AsInt16(), c0Vec.AsInt16()); + Vector128 a0 = Sse2.ShiftRightLogical(avg, 1); + Vector128 a1 = Sse2.Subtract(a0, b0.AsInt16()); + Vector128 bgta = Sse2.CompareGreaterThan(b0.AsInt16(), a0.AsInt16()); + Vector128 a2 = Sse2.Subtract(a1, bgta); + Vector128 a3 = Sse2.ShiftRightArithmetic(a2, 1); + Vector128 a4 = Sse2.Add(a0, a3).AsInt16(); + Vector128 a5 = Sse2.PackUnsignedSaturate(a4, a4); + return Sse2.ConvertToUInt32(a5.AsUInt32()); + } - { - uint ave = Average2(c0, c1); - int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); - int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); - int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); - int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; - } + { + uint ave = Average2(c0, c1); + int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24)); + int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff)); + int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff)); + int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff)); + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentHalf(int a, int b) => (int)Clip255((uint)(a + ((a - b) / 2))); + [MethodImpl(InliningOptions.ShortMethod)] + private static int AddSubtractComponentHalf(int a, int b) => (int)Clip255((uint)(a + ((a - b) / 2))); - [MethodImpl(InliningOptions.ShortMethod)] - private static int AddSubtractComponentFull(int a, int b, int c) => (int)Clip255((uint)(a + b - c)); + [MethodImpl(InliningOptions.ShortMethod)] + private static int AddSubtractComponentFull(int a, int b, int c) => (int)Clip255((uint)(a + b - c)); - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Clip255(uint a) => a < 256 ? a : ~a >> 24; + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Clip255(uint a) => a < 256 ? a : ~a >> 24; - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 MkCst16(int hi, int lo) => Vector128.Create((hi << 16) | (lo & 0xffff)); + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 MkCst16(int hi, int lo) => Vector128.Create((hi << 16) | (lo & 0xffff)); - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 MkCst32(int hi, int lo) => Vector256.Create((hi << 16) | (lo & 0xffff)); + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector256 MkCst32(int hi, int lo) => Vector256.Create((hi << 16) | (lo & 0xffff)); - private static uint Select(uint a, uint b, uint c, Span scratch) + private static uint Select(uint a, uint b, uint c, Span scratch) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - Span output = scratch; - fixed (short* p = output) - { - Vector128 a0 = Sse2.ConvertScalarToVector128UInt32(a).AsByte(); - Vector128 b0 = Sse2.ConvertScalarToVector128UInt32(b).AsByte(); - Vector128 c0 = Sse2.ConvertScalarToVector128UInt32(c).AsByte(); - Vector128 ac0 = Sse2.SubtractSaturate(a0, c0); - Vector128 ca0 = Sse2.SubtractSaturate(c0, a0); - Vector128 bc0 = Sse2.SubtractSaturate(b0, c0); - Vector128 cb0 = Sse2.SubtractSaturate(c0, b0); - Vector128 ac = Sse2.Or(ac0, ca0); - Vector128 bc = Sse2.Or(bc0, cb0); - Vector128 pa = Sse2.UnpackLow(ac, Vector128.Zero); // |a - c| - Vector128 pb = Sse2.UnpackLow(bc, Vector128.Zero); // |b - c| - Vector128 diff = Sse2.Subtract(pb.AsUInt16(), pa.AsUInt16()); - Sse2.Store((ushort*)p, diff); - int paMinusPb = output[3] + output[2] + output[1] + output[0]; - return (paMinusPb <= 0) ? a : b; - } - } - else - { - int paMinusPb = - Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) + - Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + - Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + - Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); - return paMinusPb <= 0 ? a : b; + Span output = scratch; + fixed (short* p = output) + { + Vector128 a0 = Sse2.ConvertScalarToVector128UInt32(a).AsByte(); + Vector128 b0 = Sse2.ConvertScalarToVector128UInt32(b).AsByte(); + Vector128 c0 = Sse2.ConvertScalarToVector128UInt32(c).AsByte(); + Vector128 ac0 = Sse2.SubtractSaturate(a0, c0); + Vector128 ca0 = Sse2.SubtractSaturate(c0, a0); + Vector128 bc0 = Sse2.SubtractSaturate(b0, c0); + Vector128 cb0 = Sse2.SubtractSaturate(c0, b0); + Vector128 ac = Sse2.Or(ac0, ca0); + Vector128 bc = Sse2.Or(bc0, cb0); + Vector128 pa = Sse2.UnpackLow(ac, Vector128.Zero); // |a - c| + Vector128 pb = Sse2.UnpackLow(bc, Vector128.Zero); // |b - c| + Vector128 diff = Sse2.Subtract(pb.AsUInt16(), pa.AsUInt16()); + Sse2.Store((ushort*)p, diff); + int paMinusPb = output[3] + output[2] + output[1] + output[0]; + return (paMinusPb <= 0) ? a : b; } } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int Sub3(int a, int b, int c) + else { - int pb = b - c; - int pa = a - c; - return Math.Abs(pb) - Math.Abs(pa); + int paMinusPb = + Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) + + Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) + + Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) + + Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff)); + return paMinusPb <= 0 ? a : b; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average2(uint a0, uint a1) => (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); + [MethodImpl(InliningOptions.ShortMethod)] + private static int Sub3(int a, int b, int c) + { + int pb = b - c; + int pa = a - c; + return Math.Abs(pb) - Math.Abs(pa); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average3(uint a0, uint a1, uint a2) => Average2(Average2(a0, a2), a1); + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Average2(uint a0, uint a1) => (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Average4(uint a0, uint a1, uint a2, uint a3) => Average2(Average2(a0, a1), Average2(a2, a3)); + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Average3(uint a0, uint a1, uint a2) => Average2(Average2(a0, a2), a1); - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetArgbIndex(uint idx) => (idx >> 8) & 0xff; + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Average4(uint a0, uint a1, uint a2, uint a3) => Average2(Average2(a0, a1), Average2(a2, a3)); - [MethodImpl(InliningOptions.ShortMethod)] - private static int ColorTransformDelta(sbyte colorPred, sbyte color) => (colorPred * color) >> 5; + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GetArgbIndex(uint idx) => (idx >> 8) & 0xff; - [MethodImpl(InliningOptions.ShortMethod)] - private static sbyte U32ToS8(uint v) => (sbyte)(v & 0xff); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int ColorTransformDelta(sbyte colorPred, sbyte color) => (colorPred * color) >> 5; + + [MethodImpl(InliningOptions.ShortMethod)] + private static sbyte U32ToS8(uint v) => (sbyte)(v & 0xff); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs b/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs index b2753ab223..83e8085351 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/NearLosslessEnc.cs @@ -1,125 +1,122 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +/// +/// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee +/// of maximum deviation between original and resulting pixel values. +/// +internal static class NearLosslessEnc { - /// - /// Near-lossless image preprocessing adjusts pixel values to help compressibility with a guarantee - /// of maximum deviation between original and resulting pixel values. - /// - internal static class NearLosslessEnc + private const int MinDimForNearLossless = 64; + + public static void ApplyNearLossless(int xSize, int ySize, int quality, Span argbSrc, Span argbDst, int stride) { - private const int MinDimForNearLossless = 64; + uint[] copyBuffer = new uint[xSize * 3]; + int limitBits = LosslessUtils.NearLosslessBits(quality); - public static void ApplyNearLossless(int xSize, int ySize, int quality, Span argbSrc, Span argbDst, int stride) + // For small icon images, don't attempt to apply near-lossless compression. + if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) { - uint[] copyBuffer = new uint[xSize * 3]; - int limitBits = LosslessUtils.NearLosslessBits(quality); - - // For small icon images, don't attempt to apply near-lossless compression. - if ((xSize < MinDimForNearLossless && ySize < MinDimForNearLossless) || ySize < 3) + for (int i = 0; i < ySize; i++) { - for (int i = 0; i < ySize; i++) - { - argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); - } - - return; + argbSrc.Slice(i * stride, xSize).CopyTo(argbDst.Slice(i * xSize, xSize)); } - NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); - for (int i = limitBits - 1; i != 0; i--) - { - NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); - } + return; } - // Adjusts pixel values of image with given maximum error. - private static void NearLossless(int xSize, int ySize, Span argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst) + NearLossless(xSize, ySize, argbSrc, stride, limitBits, copyBuffer, argbDst); + for (int i = limitBits - 1; i != 0; i--) { - int y; - int limit = 1 << limitBits; - Span prevRow = copyBuffer; - Span currRow = copyBuffer.Slice(xSize, xSize); - Span nextRow = copyBuffer.Slice(xSize * 2, xSize); - argbSrc[..xSize].CopyTo(currRow); - argbSrc.Slice(xSize, xSize).CopyTo(nextRow); + NearLossless(xSize, ySize, argbDst, xSize, i, copyBuffer, argbDst); + } + } + + // Adjusts pixel values of image with given maximum error. + private static void NearLossless(int xSize, int ySize, Span argbSrc, int stride, int limitBits, Span copyBuffer, Span argbDst) + { + int y; + int limit = 1 << limitBits; + Span prevRow = copyBuffer; + Span currRow = copyBuffer.Slice(xSize, xSize); + Span nextRow = copyBuffer.Slice(xSize * 2, xSize); + argbSrc[..xSize].CopyTo(currRow); + argbSrc.Slice(xSize, xSize).CopyTo(nextRow); - int srcOffset = 0; - int dstOffset = 0; - for (y = 0; y < ySize; y++) + int srcOffset = 0; + int dstOffset = 0; + for (y = 0; y < ySize; y++) + { + if (y == 0 || y == ySize - 1) { - if (y == 0 || y == ySize - 1) - { - argbSrc.Slice(srcOffset, xSize).CopyTo(argbDst.Slice(dstOffset, xSize)); - } - else + argbSrc.Slice(srcOffset, xSize).CopyTo(argbDst.Slice(dstOffset, xSize)); + } + else + { + argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); + argbDst[dstOffset] = argbSrc[srcOffset]; + argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; + for (int x = 1; x < xSize - 1; x++) { - argbSrc.Slice(srcOffset + stride, xSize).CopyTo(nextRow); - argbDst[dstOffset] = argbSrc[srcOffset]; - argbDst[dstOffset + xSize - 1] = argbSrc[srcOffset + xSize - 1]; - for (int x = 1; x < xSize - 1; x++) + if (IsSmooth(prevRow, currRow, nextRow, x, limit)) { - if (IsSmooth(prevRow, currRow, nextRow, x, limit)) - { - argbDst[dstOffset + x] = currRow[x]; - } - else - { - argbDst[dstOffset + x] = ClosestDiscretizedArgb(currRow[x], limitBits); - } + argbDst[dstOffset + x] = currRow[x]; + } + else + { + argbDst[dstOffset + x] = ClosestDiscretizedArgb(currRow[x], limitBits); } } - - Span temp = prevRow; - prevRow = currRow; - currRow = nextRow; - nextRow = temp; - srcOffset += stride; - dstOffset += xSize; } + + Span temp = prevRow; + prevRow = currRow; + currRow = nextRow; + nextRow = temp; + srcOffset += stride; + dstOffset += xSize; } + } - // Applies FindClosestDiscretized to all channels of pixel. - private static uint ClosestDiscretizedArgb(uint a, int bits) => - (FindClosestDiscretized(a >> 24, bits) << 24) | - (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | - (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | - FindClosestDiscretized(a & 0xff, bits); + // Applies FindClosestDiscretized to all channels of pixel. + private static uint ClosestDiscretizedArgb(uint a, int bits) => + (FindClosestDiscretized(a >> 24, bits) << 24) | + (FindClosestDiscretized((a >> 16) & 0xff, bits) << 16) | + (FindClosestDiscretized((a >> 8) & 0xff, bits) << 8) | + FindClosestDiscretized(a & 0xff, bits); - private static uint FindClosestDiscretized(uint a, int bits) + private static uint FindClosestDiscretized(uint a, int bits) + { + uint mask = (1u << bits) - 1; + uint biased = a + (mask >> 1) + ((a >> bits) & 1); + if (biased > 0xff) { - uint mask = (1u << bits) - 1; - uint biased = a + (mask >> 1) + ((a >> bits) & 1); - if (biased > 0xff) - { - return 0xff; - } - - return biased & ~mask; + return 0xff; } - private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) => - IsNear(currRow[ix], currRow[ix - 1], limit) && // Check that all pixels in 4-connected neighborhood are smooth. - IsNear(currRow[ix], currRow[ix + 1], limit) && - IsNear(currRow[ix], prevRow[ix], limit) && - IsNear(currRow[ix], nextRow[ix], limit); + return biased & ~mask; + } + + private static bool IsSmooth(Span prevRow, Span currRow, Span nextRow, int ix, int limit) => + IsNear(currRow[ix], currRow[ix - 1], limit) && // Check that all pixels in 4-connected neighborhood are smooth. + IsNear(currRow[ix], currRow[ix + 1], limit) && + IsNear(currRow[ix], prevRow[ix], limit) && + IsNear(currRow[ix], nextRow[ix], limit); - // Checks if distance between corresponding channel values of pixels a and b is within the given limit. - private static bool IsNear(uint a, uint b, int limit) + // Checks if distance between corresponding channel values of pixels a and b is within the given limit. + private static bool IsNear(uint a, uint b, int limit) + { + for (int k = 0; k < 4; ++k) { - for (int k = 0; k < 4; ++k) + int delta = (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff); + if (delta >= limit || delta <= -limit) { - int delta = (int)((a >> (k * 8)) & 0xff) - (int)((b >> (k * 8)) & 0xff); - if (delta >= limit || delta <= -limit) - { - return false; - } + return false; } - - return true; } + + return true; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs index bd9ced7a43..6a28e5b3fb 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs @@ -3,52 +3,51 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless -{ - [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] - internal sealed class PixOrCopy - { - public PixOrCopyMode Mode { get; set; } +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; - public ushort Len { get; set; } +[DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] +internal sealed class PixOrCopy +{ + public PixOrCopyMode Mode { get; set; } - public uint BgraOrDistance { get; set; } + public ushort Len { get; set; } - public static PixOrCopy CreateCacheIdx(int idx) => - new() - { - Mode = PixOrCopyMode.CacheIdx, - BgraOrDistance = (uint)idx, - Len = 1 - }; + public uint BgraOrDistance { get; set; } - public static PixOrCopy CreateLiteral(uint bgra) => - new() - { - Mode = PixOrCopyMode.Literal, - BgraOrDistance = bgra, - Len = 1 - }; + public static PixOrCopy CreateCacheIdx(int idx) => + new() + { + Mode = PixOrCopyMode.CacheIdx, + BgraOrDistance = (uint)idx, + Len = 1 + }; - public static PixOrCopy CreateCopy(uint distance, ushort len) => new() + public static PixOrCopy CreateLiteral(uint bgra) => + new() { - Mode = PixOrCopyMode.Copy, - BgraOrDistance = distance, - Len = len + Mode = PixOrCopyMode.Literal, + BgraOrDistance = bgra, + Len = 1 }; - public uint Literal(int component) => (this.BgraOrDistance >> (component * 8)) & 0xff; + public static PixOrCopy CreateCopy(uint distance, ushort len) => new() + { + Mode = PixOrCopyMode.Copy, + BgraOrDistance = distance, + Len = len + }; + + public uint Literal(int component) => (this.BgraOrDistance >> (component * 8)) & 0xff; - public uint CacheIdx() => this.BgraOrDistance; + public uint CacheIdx() => this.BgraOrDistance; - public ushort Length() => this.Len; + public ushort Length() => this.Len; - public uint Distance() => this.BgraOrDistance; + public uint Distance() => this.BgraOrDistance; - public bool IsLiteral() => this.Mode == PixOrCopyMode.Literal; + public bool IsLiteral() => this.Mode == PixOrCopyMode.Literal; - public bool IsCacheIdx() => this.Mode == PixOrCopyMode.CacheIdx; + public bool IsCacheIdx() => this.Mode == PixOrCopyMode.CacheIdx; - public bool IsCopy() => this.Mode == PixOrCopyMode.Copy; - } + public bool IsCopy() => this.Mode == PixOrCopyMode.Copy; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs index 6f20371f6a..78da2d1a52 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal enum PixOrCopyMode : byte { - internal enum PixOrCopyMode : byte - { - Literal, + Literal, - CacheIdx, + CacheIdx, - Copy, + Copy, - None - } + None } diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 1383eba78d..5d8d3203b1 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -1,1086 +1,1084 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Image transform methods for the lossless webp encoder. +/// +internal static unsafe class PredictorEncoder { + private static readonly sbyte[][] Offset = + { + new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 } + }; + + private const int GreenRedToBlueNumAxis = 8; + + private const int GreenRedToBlueMaxIters = 7; + + private const float MaxDiffCost = 1e30f; + + private const uint MaskAlpha = 0xff000000; + + private const float SpatialPredictorBias = 15.0f; + + private const int PredLowEffort = 11; + + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan DeltaLut => new sbyte[] { 16, 16, 8, 4, 2, 2, 2 }; + /// - /// Image transform methods for the lossless webp encoder. + /// Finds the best predictor for each tile, and converts the image to residuals + /// with respect to predictions. If nearLosslessQuality < 100, applies + /// near lossless processing, shaving off more bits of residuals for lower qualities. /// - internal static unsafe class PredictorEncoder + public static void ResidualImage( + int width, + int height, + int bits, + Span bgra, + Span bgraScratch, + Span image, + int[][] histoArgb, + int[][] bestHisto, + bool nearLossless, + int nearLosslessQuality, + WebpTransparentColorMode transparentColorMode, + bool usedSubtractGreen, + bool lowEffort) { - private static readonly sbyte[][] Offset = - { - new sbyte[] { 0, -1 }, new sbyte[] { 0, 1 }, new sbyte[] { -1, 0 }, new sbyte[] { 1, 0 }, new sbyte[] { -1, -1 }, new sbyte[] { -1, 1 }, new sbyte[] { 1, -1 }, new sbyte[] { 1, 1 } - }; - - private const int GreenRedToBlueNumAxis = 8; - - private const int GreenRedToBlueMaxIters = 7; - - private const float MaxDiffCost = 1e30f; - - private const uint MaskAlpha = 0xff000000; - - private const float SpatialPredictorBias = 15.0f; - - private const int PredLowEffort = 11; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan DeltaLut => new sbyte[] { 16, 16, 8, 4, 2, 2, 2 }; - - /// - /// Finds the best predictor for each tile, and converts the image to residuals - /// with respect to predictions. If nearLosslessQuality < 100, applies - /// near lossless processing, shaving off more bits of residuals for lower qualities. - /// - public static void ResidualImage( - int width, - int height, - int bits, - Span bgra, - Span bgraScratch, - Span image, - int[][] histoArgb, - int[][] bestHisto, - bool nearLossless, - int nearLosslessQuality, - WebpTransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool lowEffort) + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); + int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); + Span scratch = stackalloc short[8]; + + // TODO: Can we optimize this? + int[][] histo = new int[4][]; + for (int i = 0; i < 4; i++) { - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - int tilesPerCol = LosslessUtils.SubSampleSize(height, bits); - int maxQuantization = 1 << LosslessUtils.NearLosslessBits(nearLosslessQuality); - Span scratch = stackalloc short[8]; - - // TODO: Can we optimize this? - int[][] histo = new int[4][]; - for (int i = 0; i < 4; i++) - { - histo[i] = new int[256]; - } + histo[i] = new int[256]; + } - if (lowEffort) - { - for (int i = 0; i < tilesPerRow * tilesPerCol; i++) - { - image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); - } - } - else + if (lowEffort) + { + for (int i = 0; i < tilesPerRow * tilesPerCol; i++) { - for (int tileY = 0; tileY < tilesPerCol; tileY++) - { - for (int tileX = 0; tileX < tilesPerRow; tileX++) - { - int pred = GetBestPredictorForTile( - width, - height, - tileX, - tileY, - bits, - histo, - bgraScratch, - bgra, - histoArgb, - bestHisto, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - image, - scratch); - - image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); - } - } + image[i] = WebpConstants.ArgbBlack | (PredLowEffort << 8); } - - CopyImageWithPrediction( - width, - height, - bits, - image, - bgraScratch, - bgra, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - lowEffort); } - - public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span bgra, Span image, Span scratch) + else { - int maxTileSize = 1 << bits; - int tileXSize = LosslessUtils.SubSampleSize(width, bits); - int tileYSize = LosslessUtils.SubSampleSize(height, bits); - int[] accumulatedRedHisto = new int[256]; - int[] accumulatedBlueHisto = new int[256]; - var prevX = default(Vp8LMultipliers); - var prevY = default(Vp8LMultipliers); - for (int tileY = 0; tileY < tileYSize; tileY++) + for (int tileY = 0; tileY < tilesPerCol; tileY++) { - for (int tileX = 0; tileX < tileXSize; tileX++) + for (int tileX = 0; tileX < tilesPerRow; tileX++) { - int tileXOffset = tileX * maxTileSize; - int tileYOffset = tileY * maxTileSize; - int allXMax = GetMin(tileXOffset + maxTileSize, width); - int allYMax = GetMin(tileYOffset + maxTileSize, height); - int offset = (tileY * tileXSize) + tileX; - if (tileY != 0) - { - LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); - } - - prevX = GetBestColorTransformForTile( + int pred = GetBestPredictorForTile( + width, + height, tileX, tileY, bits, - prevX, - prevY, - quality, - width, - height, - accumulatedRedHisto, - accumulatedBlueHisto, + histo, + bgraScratch, bgra, + histoArgb, + bestHisto, + maxQuantization, + transparentColorMode, + usedSubtractGreen, + nearLossless, + image, scratch); - image[offset] = MultipliersToColorCode(prevX); - CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra); - - // Gather accumulated histogram data. - for (int y = tileYOffset; y < allYMax; y++) - { - int ix = (y * width) + tileXOffset; - int ixEnd = ix + allXMax - tileXOffset; - - for (; ix < ixEnd; ix++) - { - uint pix = bgra[ix]; - if (ix >= 2 && pix == bgra[ix - 2] && pix == bgra[ix - 1]) - { - continue; // Repeated pixels are handled by backward references. - } - - if (ix >= width + 2 && bgra[ix - 2] == bgra[ix - width - 2] && bgra[ix - 1] == bgra[ix - width - 1] && pix == bgra[ix - width]) - { - continue; // Repeated pixels are handled by backward references. - } - - accumulatedRedHisto[(pix >> 16) & 0xff]++; - accumulatedBlueHisto[(pix >> 0) & 0xff]++; - } - } + image[(tileY * tilesPerRow) + tileX] = (uint)(WebpConstants.ArgbBlack | (pred << 8)); } } } - /// - /// Returns best predictor and updates the accumulated histogram. - /// If maxQuantization > 1, assumes that near lossless processing will be - /// applied, quantizing residuals to multiples of quantization levels up to - /// maxQuantization (the actual quantization level depends on smoothness near - /// the given pixel). - /// - /// Best predictor. - private static int GetBestPredictorForTile( - int width, - int height, - int tileX, - int tileY, - int bits, - int[][] accumulated, - Span argbScratch, - Span argb, - int[][] histoArgb, - int[][] bestHisto, - int maxQuantization, - WebpTransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - Span modes, - Span scratch) - { - const int numPredModes = 14; - int startX = tileX << bits; - int startY = tileY << bits; - int tileSize = 1 << bits; - int maxY = GetMin(tileSize, height - startY); - int maxX = GetMin(tileSize, width - startX); - - // Whether there exist columns just outside the tile. - int haveLeft = startX > 0 ? 1 : 0; - - // Position and size of the strip covering the tile and adjacent columns if they exist. - int contextStartX = startX - haveLeft; - int contextWidth = maxX + haveLeft + (maxX < width ? 1 : 0) - startX; - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - - // Prediction modes of the left and above neighbor tiles. - int leftMode = (int)(tileX > 0 ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); - int aboveMode = (int)(tileY > 0 ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); - - // The width of upper_row and current_row is one pixel larger than image width - // to allow the top right pixel to point to the leftmost pixel of the next row - // when at the right edge. - Span upperRow = argbScratch; - Span currentRow = upperRow[(width + 1)..]; - Span maxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); - float bestDiff = MaxDiffCost; - int bestMode = 0; - uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits]; - for (int i = 0; i < 4; i++) - { - histoArgb[i].AsSpan().Clear(); - bestHisto[i].AsSpan().Clear(); - } + CopyImageWithPrediction( + width, + height, + bits, + image, + bgraScratch, + bgra, + maxQuantization, + transparentColorMode, + usedSubtractGreen, + nearLossless, + lowEffort); + } - for (int mode = 0; mode < numPredModes; mode++) + public static void ColorSpaceTransform(int width, int height, int bits, int quality, Span bgra, Span image, Span scratch) + { + int maxTileSize = 1 << bits; + int tileXSize = LosslessUtils.SubSampleSize(width, bits); + int tileYSize = LosslessUtils.SubSampleSize(height, bits); + int[] accumulatedRedHisto = new int[256]; + int[] accumulatedBlueHisto = new int[256]; + var prevX = default(Vp8LMultipliers); + var prevY = default(Vp8LMultipliers); + for (int tileY = 0; tileY < tileYSize; tileY++) + { + for (int tileX = 0; tileX < tileXSize; tileX++) { - if (startY > 0) + int tileXOffset = tileX * maxTileSize; + int tileYOffset = tileY * maxTileSize; + int allXMax = GetMin(tileXOffset + maxTileSize, width); + int allYMax = GetMin(tileYOffset + maxTileSize, height); + int offset = (tileY * tileXSize) + tileX; + if (tileY != 0) { - // Read the row above the tile which will become the first upper_row. - // Include a pixel to the left if it exists; include a pixel to the right - // in all cases (wrapping to the leftmost pixel of the next row if it does - // not exist). - Span src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1); - Span dst = currentRow[contextStartX..]; - src.CopyTo(dst); + LosslessUtils.ColorCodeToMultipliers(image[offset - tileXSize], ref prevY); } - for (int relativeY = 0; relativeY < maxY; relativeY++) + prevX = GetBestColorTransformForTile( + tileX, + tileY, + bits, + prevX, + prevY, + quality, + width, + height, + accumulatedRedHisto, + accumulatedBlueHisto, + bgra, + scratch); + + image[offset] = MultipliersToColorCode(prevX); + CopyTileWithColorTransform(width, height, tileXOffset, tileYOffset, maxTileSize, prevX, bgra); + + // Gather accumulated histogram data. + for (int y = tileYOffset; y < allYMax; y++) { - int y = startY + relativeY; - Span tmp = upperRow; - upperRow = currentRow; - currentRow = tmp; - - // Read currentRow. Include a pixel to the left if it exists; include a - // pixel to the right in all cases except at the bottom right corner of - // the image (wrapping to the leftmost pixel of the next row if it does - // not exist in the currentRow). - int offset = (y * width) + contextStartX; - Span src = argb.Slice(offset, maxX + haveLeft + (y + 1 < height ? 1 : 0)); - Span dst = currentRow[contextStartX..]; - src.CopyTo(dst); + int ix = (y * width) + tileXOffset; + int ixEnd = ix + allXMax - tileXOffset; - if (nearLossless) + for (; ix < ixEnd; ix++) { - if (maxQuantization > 1 && y >= 1 && y + 1 < height) + uint pix = bgra[ix]; + if (ix >= 2 && pix == bgra[ix - 2] && pix == bgra[ix - 1]) { - MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs[contextStartX..], usedSubtractGreen); + continue; // Repeated pixels are handled by backward references. } - } - GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, residuals, scratch); - for (int relativeX = 0; relativeX < maxX; ++relativeX) - { - UpdateHisto(histoArgb, residuals[relativeX]); + if (ix >= width + 2 && bgra[ix - 2] == bgra[ix - width - 2] && bgra[ix - 1] == bgra[ix - width - 1] && pix == bgra[ix - width]) + { + continue; // Repeated pixels are handled by backward references. + } + + accumulatedRedHisto[(pix >> 16) & 0xff]++; + accumulatedBlueHisto[(pix >> 0) & 0xff]++; } } + } + } + } - float curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); + /// + /// Returns best predictor and updates the accumulated histogram. + /// If maxQuantization > 1, assumes that near lossless processing will be + /// applied, quantizing residuals to multiples of quantization levels up to + /// maxQuantization (the actual quantization level depends on smoothness near + /// the given pixel). + /// + /// Best predictor. + private static int GetBestPredictorForTile( + int width, + int height, + int tileX, + int tileY, + int bits, + int[][] accumulated, + Span argbScratch, + Span argb, + int[][] histoArgb, + int[][] bestHisto, + int maxQuantization, + WebpTransparentColorMode transparentColorMode, + bool usedSubtractGreen, + bool nearLossless, + Span modes, + Span scratch) + { + const int numPredModes = 14; + int startX = tileX << bits; + int startY = tileY << bits; + int tileSize = 1 << bits; + int maxY = GetMin(tileSize, height - startY); + int maxX = GetMin(tileSize, width - startX); + + // Whether there exist columns just outside the tile. + int haveLeft = startX > 0 ? 1 : 0; + + // Position and size of the strip covering the tile and adjacent columns if they exist. + int contextStartX = startX - haveLeft; + int contextWidth = maxX + haveLeft + (maxX < width ? 1 : 0) - startX; + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + + // Prediction modes of the left and above neighbor tiles. + int leftMode = (int)(tileX > 0 ? (modes[(tileY * tilesPerRow) + tileX - 1] >> 8) & 0xff : 0xff); + int aboveMode = (int)(tileY > 0 ? (modes[((tileY - 1) * tilesPerRow) + tileX] >> 8) & 0xff : 0xff); + + // The width of upper_row and current_row is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + Span upperRow = argbScratch; + Span currentRow = upperRow[(width + 1)..]; + Span maxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); + float bestDiff = MaxDiffCost; + int bestMode = 0; + uint[] residuals = new uint[1 << WebpConstants.MaxTransformBits]; + for (int i = 0; i < 4; i++) + { + histoArgb[i].AsSpan().Clear(); + bestHisto[i].AsSpan().Clear(); + } - // Favor keeping the areas locally similar. - if (mode == leftMode) - { - curDiff -= SpatialPredictorBias; - } + for (int mode = 0; mode < numPredModes; mode++) + { + if (startY > 0) + { + // Read the row above the tile which will become the first upper_row. + // Include a pixel to the left if it exists; include a pixel to the right + // in all cases (wrapping to the leftmost pixel of the next row if it does + // not exist). + Span src = argb.Slice(((startY - 1) * width) + contextStartX, maxX + haveLeft + 1); + Span dst = currentRow[contextStartX..]; + src.CopyTo(dst); + } - if (mode == aboveMode) + for (int relativeY = 0; relativeY < maxY; relativeY++) + { + int y = startY + relativeY; + Span tmp = upperRow; + upperRow = currentRow; + currentRow = tmp; + + // Read currentRow. Include a pixel to the left if it exists; include a + // pixel to the right in all cases except at the bottom right corner of + // the image (wrapping to the leftmost pixel of the next row if it does + // not exist in the currentRow). + int offset = (y * width) + contextStartX; + Span src = argb.Slice(offset, maxX + haveLeft + (y + 1 < height ? 1 : 0)); + Span dst = currentRow[contextStartX..]; + src.CopyTo(dst); + + if (nearLossless) { - curDiff -= SpatialPredictorBias; + if (maxQuantization > 1 && y >= 1 && y + 1 < height) + { + MaxDiffsForRow(contextWidth, width, argb, offset, maxDiffs[contextStartX..], usedSubtractGreen); + } } - if (curDiff < bestDiff) + GetResidual(width, height, upperRow, currentRow, maxDiffs, mode, startX, startX + maxX, y, maxQuantization, transparentColorMode, usedSubtractGreen, nearLossless, residuals, scratch); + for (int relativeX = 0; relativeX < maxX; ++relativeX) { - int[][] tmp = histoArgb; - histoArgb = bestHisto; - bestHisto = tmp; - bestDiff = curDiff; - bestMode = mode; + UpdateHisto(histoArgb, residuals[relativeX]); } + } - for (int i = 0; i < 4; i++) - { - histoArgb[i].AsSpan().Clear(); - } + float curDiff = PredictionCostSpatialHistogram(accumulated, histoArgb); + + // Favor keeping the areas locally similar. + if (mode == leftMode) + { + curDiff -= SpatialPredictorBias; } - for (int i = 0; i < 4; i++) + if (mode == aboveMode) { - for (int j = 0; j < 256; j++) - { - accumulated[i][j] += bestHisto[i][j]; - } + curDiff -= SpatialPredictorBias; } - return bestMode; + if (curDiff < bestDiff) + { + int[][] tmp = histoArgb; + histoArgb = bestHisto; + bestHisto = tmp; + bestDiff = curDiff; + bestMode = mode; + } + + for (int i = 0; i < 4; i++) + { + histoArgb[i].AsSpan().Clear(); + } } - /// - /// Stores the difference between the pixel and its prediction in "output". - /// In case of a lossy encoding, updates the source image to avoid propagating - /// the deviation further to pixels which depend on the current pixel for their - /// predictions. - /// - private static void GetResidual( - int width, - int height, - Span upperRowSpan, - Span currentRowSpan, - Span maxDiffs, - int mode, - int xStart, - int xEnd, - int y, - int maxQuantization, - WebpTransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - Span output, - Span scratch) + for (int i = 0; i < 4; i++) { - if (transparentColorMode == WebpTransparentColorMode.Preserve) + for (int j = 0; j < 256; j++) { - PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output, scratch); + accumulated[i][j] += bestHisto[i][j]; } - else - { + } + + return bestMode; + } + + /// + /// Stores the difference between the pixel and its prediction in "output". + /// In case of a lossy encoding, updates the source image to avoid propagating + /// the deviation further to pixels which depend on the current pixel for their + /// predictions. + /// + private static void GetResidual( + int width, + int height, + Span upperRowSpan, + Span currentRowSpan, + Span maxDiffs, + int mode, + int xStart, + int xEnd, + int y, + int maxQuantization, + WebpTransparentColorMode transparentColorMode, + bool usedSubtractGreen, + bool nearLossless, + Span output, + Span scratch) + { + if (transparentColorMode == WebpTransparentColorMode.Preserve) + { + PredictBatch(mode, xStart, y, xEnd - xStart, currentRowSpan, upperRowSpan, output, scratch); + } + else + { #pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* currentRow = currentRowSpan) - fixed (uint* upperRow = upperRowSpan) + fixed (uint* currentRow = currentRowSpan) + fixed (uint* upperRow = upperRowSpan) + { + for (int x = xStart; x < xEnd; x++) { - for (int x = xStart; x < xEnd; x++) + uint predict = 0; + uint residual; + if (y == 0) { - uint predict = 0; - uint residual; - if (y == 0) - { - predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. - } - else if (x == 0) - { - predict = upperRow[x]; // Top. - } - else + predict = x == 0 ? WebpConstants.ArgbBlack : currentRow[x - 1]; // Left. + } + else if (x == 0) + { + predict = upperRow[x]; // Top. + } + else + { + switch (mode) { - switch (mode) - { - case 0: - predict = WebpConstants.ArgbBlack; - break; - case 1: - predict = currentRow[x - 1]; - break; - case 2: - predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x); - break; - case 3: - predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x); - break; - case 4: - predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x); - break; - case 5: - predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x); - break; - case 6: - predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x); - break; - case 7: - predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x); - break; - case 8: - predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x); - break; - case 9: - predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x); - break; - case 10: - predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x); - break; - case 11: - predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x, scratch); - break; - case 12: - predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x); - break; - case 13: - predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x); - break; - } + case 0: + predict = WebpConstants.ArgbBlack; + break; + case 1: + predict = currentRow[x - 1]; + break; + case 2: + predict = LosslessUtils.Predictor2(currentRow[x - 1], upperRow + x); + break; + case 3: + predict = LosslessUtils.Predictor3(currentRow[x - 1], upperRow + x); + break; + case 4: + predict = LosslessUtils.Predictor4(currentRow[x - 1], upperRow + x); + break; + case 5: + predict = LosslessUtils.Predictor5(currentRow[x - 1], upperRow + x); + break; + case 6: + predict = LosslessUtils.Predictor6(currentRow[x - 1], upperRow + x); + break; + case 7: + predict = LosslessUtils.Predictor7(currentRow[x - 1], upperRow + x); + break; + case 8: + predict = LosslessUtils.Predictor8(currentRow[x - 1], upperRow + x); + break; + case 9: + predict = LosslessUtils.Predictor9(currentRow[x - 1], upperRow + x); + break; + case 10: + predict = LosslessUtils.Predictor10(currentRow[x - 1], upperRow + x); + break; + case 11: + predict = LosslessUtils.Predictor11(currentRow[x - 1], upperRow + x, scratch); + break; + case 12: + predict = LosslessUtils.Predictor12(currentRow[x - 1], upperRow + x); + break; + case 13: + predict = LosslessUtils.Predictor13(currentRow[x - 1], upperRow + x); + break; } + } - if (nearLossless) - { - if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) - { - residual = LosslessUtils.SubPixels(currentRow[x], predict); - } - else - { - residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); - - // Update the source image. - currentRow[x] = LosslessUtils.AddPixels(predict, residual); - - // x is never 0 here so we do not need to update upperRow like below. - } - } - else + if (nearLossless) + { + if (maxQuantization == 1 || mode == 0 || y == 0 || y == height - 1 || x == 0 || x == width - 1) { residual = LosslessUtils.SubPixels(currentRow[x], predict); } - - if ((currentRow[x] & MaskAlpha) == 0) + else { - // If alpha is 0, cleanup RGB. We can choose the RGB values of the - // residual for best compression. The prediction of alpha itself can be - // non-zero and must be kept though. We choose RGB of the residual to be - // 0. - residual &= MaskAlpha; + residual = NearLossless(currentRow[x], predict, maxQuantization, maxDiffs[x], usedSubtractGreen); // Update the source image. - currentRow[x] = predict & ~MaskAlpha; - - // The prediction for the rightmost pixel in a row uses the leftmost - // pixel - // in that row as its top-right context pixel. Hence if we change the - // leftmost pixel of current_row, the corresponding change must be - // applied - // to upperRow as well where top-right context is being read from. - if (x == 0 && y != 0) - { - upperRow[width] = currentRow[0]; - } + currentRow[x] = LosslessUtils.AddPixels(predict, residual); + + // x is never 0 here so we do not need to update upperRow like below. } + } + else + { + residual = LosslessUtils.SubPixels(currentRow[x], predict); + } - output[x - xStart] = residual; + if ((currentRow[x] & MaskAlpha) == 0) + { + // If alpha is 0, cleanup RGB. We can choose the RGB values of the + // residual for best compression. The prediction of alpha itself can be + // non-zero and must be kept though. We choose RGB of the residual to be + // 0. + residual &= MaskAlpha; + + // Update the source image. + currentRow[x] = predict & ~MaskAlpha; + + // The prediction for the rightmost pixel in a row uses the leftmost + // pixel + // in that row as its top-right context pixel. Hence if we change the + // leftmost pixel of current_row, the corresponding change must be + // applied + // to upperRow as well where top-right context is being read from. + if (x == 0 && y != 0) + { + upperRow[width] = currentRow[0]; + } } + + output[x - xStart] = residual; } } } + } #pragma warning restore SA1503 // Braces should not be omitted - /// - /// Quantize every component of the difference between the actual pixel value and - /// its prediction to a multiple of a quantization (a power of 2, not larger than - /// maxQuantization which is a power of 2, smaller than maxDiff). Take care if - /// value and predict have undergone subtract green, which means that red and - /// blue are represented as offsets from green. - /// - private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) + /// + /// Quantize every component of the difference between the actual pixel value and + /// its prediction to a multiple of a quantization (a power of 2, not larger than + /// maxQuantization which is a power of 2, smaller than maxDiff). Take care if + /// value and predict have undergone subtract green, which means that red and + /// blue are represented as offsets from green. + /// + private static uint NearLossless(uint value, uint predict, int maxQuantization, int maxDiff, bool usedSubtractGreen) + { + byte newGreen = 0; + byte greenDiff = 0; + byte a; + if (maxDiff <= 2) { - byte newGreen = 0; - byte greenDiff = 0; - byte a; - if (maxDiff <= 2) - { - return LosslessUtils.SubPixels(value, predict); - } - - int quantization = maxQuantization; - while (quantization >= maxDiff) - { - quantization >>= 1; - } - - if (value >> 24 is 0 or 0xff) - { - // Preserve transparency of fully transparent or fully opaque pixels. - a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); - } - else - { - a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); - } + return LosslessUtils.SubPixels(value, predict); + } - byte g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); + int quantization = maxQuantization; + while (quantization >= maxDiff) + { + quantization >>= 1; + } - if (usedSubtractGreen) - { - // The green offset will be added to red and blue components during decoding - // to obtain the actual red and blue values. - newGreen = (byte)(((predict >> 8) + g) & 0xff); - - // The amount by which green has been adjusted during quantization. It is - // subtracted from red and blue for compensation, to avoid accumulating two - // quantization errors in them. - greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); - } + if (value >> 24 is 0 or 0xff) + { + // Preserve transparency of fully transparent or fully opaque pixels. + a = NearLosslessDiff((byte)((value >> 24) & 0xff), (byte)((predict >> 24) & 0xff)); + } + else + { + a = NearLosslessComponent((byte)(value >> 24), (byte)(predict >> 24), 0xff, quantization); + } - byte r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); - byte b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); + byte g = NearLosslessComponent((byte)((value >> 8) & 0xff), (byte)((predict >> 8) & 0xff), 0xff, quantization); - return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; + if (usedSubtractGreen) + { + // The green offset will be added to red and blue components during decoding + // to obtain the actual red and blue values. + newGreen = (byte)(((predict >> 8) + g) & 0xff); + + // The amount by which green has been adjusted during quantization. It is + // subtracted from red and blue for compensation, to avoid accumulating two + // quantization errors in them. + greenDiff = NearLosslessDiff(newGreen, (byte)((value >> 8) & 0xff)); } - /// - /// Quantize the difference between the actual component value and its prediction - /// to a multiple of quantization, working modulo 256, taking care not to cross - /// a boundary (inclusive upper limit). - /// - private static byte NearLosslessComponent(byte value, byte predict, byte boundary, int quantization) - { - int residual = (value - predict) & 0xff; - int boundaryResidual = (boundary - predict) & 0xff; - int lower = residual & ~(quantization - 1); - int upper = lower + quantization; + byte r = NearLosslessComponent(NearLosslessDiff((byte)((value >> 16) & 0xff), greenDiff), (byte)((predict >> 16) & 0xff), (byte)(0xff - newGreen), quantization); + byte b = NearLosslessComponent(NearLosslessDiff((byte)(value & 0xff), greenDiff), (byte)(predict & 0xff), (byte)(0xff - newGreen), quantization); - // Resolve ties towards a value closer to the prediction (i.e. towards lower - // if value comes after prediction and towards upper otherwise). - int bias = ((boundary - value) & 0xff) < boundaryResidual ? 1 : 0; + return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | b; + } - if (residual - lower < upper - residual + bias) - { - // lower is closer to residual than upper. - if (residual > boundaryResidual && lower <= boundaryResidual) - { - // Halve quantization step to avoid crossing boundary. This midpoint is - // on the same side of boundary as residual because midpoint >= residual - // (since lower is closer than upper) and residual is above the boundary. - return (byte)(lower + (quantization >> 1)); - } + /// + /// Quantize the difference between the actual component value and its prediction + /// to a multiple of quantization, working modulo 256, taking care not to cross + /// a boundary (inclusive upper limit). + /// + private static byte NearLosslessComponent(byte value, byte predict, byte boundary, int quantization) + { + int residual = (value - predict) & 0xff; + int boundaryResidual = (boundary - predict) & 0xff; + int lower = residual & ~(quantization - 1); + int upper = lower + quantization; - return (byte)lower; - } + // Resolve ties towards a value closer to the prediction (i.e. towards lower + // if value comes after prediction and towards upper otherwise). + int bias = ((boundary - value) & 0xff) < boundaryResidual ? 1 : 0; - // upper is closer to residual than lower. - if (residual <= boundaryResidual && upper > boundaryResidual) + if (residual - lower < upper - residual + bias) + { + // lower is closer to residual than upper. + if (residual > boundaryResidual && lower <= boundaryResidual) { // Halve quantization step to avoid crossing boundary. This midpoint is - // on the same side of boundary as residual because midpoint <= residual - // (since upper is closer than lower) and residual is below the boundary. + // on the same side of boundary as residual because midpoint >= residual + // (since lower is closer than upper) and residual is above the boundary. return (byte)(lower + (quantization >> 1)); } - return (byte)upper; + return (byte)lower; } - /// - /// Converts pixels of the image to residuals with respect to predictions. - /// If max_quantization > 1, applies near lossless processing, quantizing - /// residuals to multiples of quantization levels up to max_quantization - /// (the actual quantization level depends on smoothness near the given pixel). - /// - private static void CopyImageWithPrediction( - int width, - int height, - int bits, - Span modes, - Span argbScratch, - Span argb, - int maxQuantization, - WebpTransparentColorMode transparentColorMode, - bool usedSubtractGreen, - bool nearLossless, - bool lowEffort) + // upper is closer to residual than lower. + if (residual <= boundaryResidual && upper > boundaryResidual) { - int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); - - // The width of upperRow and currentRow is one pixel larger than image width - // to allow the top right pixel to point to the leftmost pixel of the next row - // when at the right edge. - Span upperRow = argbScratch; - Span currentRow = upperRow[(width + 1)..]; - Span currentMaxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); - - Span lowerMaxDiffs = currentMaxDiffs[width..]; - Span scratch = stackalloc short[8]; - for (int y = 0; y < height; y++) - { - Span tmp32 = upperRow; - upperRow = currentRow; - currentRow = tmp32; - Span src = argb.Slice(y * width, width + (y + 1 < height ? 1 : 0)); - src.CopyTo(currentRow); + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint <= residual + // (since upper is closer than lower) and residual is below the boundary. + return (byte)(lower + (quantization >> 1)); + } + + return (byte)upper; + } + + /// + /// Converts pixels of the image to residuals with respect to predictions. + /// If max_quantization > 1, applies near lossless processing, quantizing + /// residuals to multiples of quantization levels up to max_quantization + /// (the actual quantization level depends on smoothness near the given pixel). + /// + private static void CopyImageWithPrediction( + int width, + int height, + int bits, + Span modes, + Span argbScratch, + Span argb, + int maxQuantization, + WebpTransparentColorMode transparentColorMode, + bool usedSubtractGreen, + bool nearLossless, + bool lowEffort) + { + int tilesPerRow = LosslessUtils.SubSampleSize(width, bits); + + // The width of upperRow and currentRow is one pixel larger than image width + // to allow the top right pixel to point to the leftmost pixel of the next row + // when at the right edge. + Span upperRow = argbScratch; + Span currentRow = upperRow[(width + 1)..]; + Span currentMaxDiffs = MemoryMarshal.Cast(currentRow[(width + 1)..]); + + Span lowerMaxDiffs = currentMaxDiffs[width..]; + Span scratch = stackalloc short[8]; + for (int y = 0; y < height; y++) + { + Span tmp32 = upperRow; + upperRow = currentRow; + currentRow = tmp32; + Span src = argb.Slice(y * width, width + (y + 1 < height ? 1 : 0)); + src.CopyTo(currentRow); - if (lowEffort) + if (lowEffort) + { + PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb[(y * width)..], scratch); + } + else + { + if (nearLossless && maxQuantization > 1) { - PredictBatch(PredLowEffort, 0, y, width, currentRow, upperRow, argb[(y * width)..], scratch); + // Compute maxDiffs for the lower row now, because that needs the + // contents of bgra for the current row, which we will overwrite with + // residuals before proceeding with the next row. + Span tmp8 = currentMaxDiffs; + currentMaxDiffs = lowerMaxDiffs; + lowerMaxDiffs = tmp8; + if (y + 2 < height) + { + MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); + } } - else + + for (int x = 0; x < width;) { - if (nearLossless && maxQuantization > 1) + int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); + int xEnd = x + (1 << bits); + if (xEnd > width) { - // Compute maxDiffs for the lower row now, because that needs the - // contents of bgra for the current row, which we will overwrite with - // residuals before proceeding with the next row. - Span tmp8 = currentMaxDiffs; - currentMaxDiffs = lowerMaxDiffs; - lowerMaxDiffs = tmp8; - if (y + 2 < height) - { - MaxDiffsForRow(width, width, argb, (y + 1) * width, lowerMaxDiffs, usedSubtractGreen); - } + xEnd = width; } - for (int x = 0; x < width;) - { - int mode = (int)((modes[((y >> bits) * tilesPerRow) + (x >> bits)] >> 8) & 0xff); - int xEnd = x + (1 << bits); - if (xEnd > width) - { - xEnd = width; - } + GetResidual( + width, + height, + upperRow, + currentRow, + currentMaxDiffs, + mode, + x, + xEnd, + y, + maxQuantization, + transparentColorMode, + usedSubtractGreen, + nearLossless, + argb[((y * width) + x)..], + scratch); - GetResidual( - width, - height, - upperRow, - currentRow, - currentMaxDiffs, - mode, - x, - xEnd, - y, - maxQuantization, - transparentColorMode, - usedSubtractGreen, - nearLossless, - argb[((y * width) + x)..], - scratch); - - x = xEnd; - } + x = xEnd; } } } + } - private static void PredictBatch( - int mode, - int xStart, - int y, - int numPixels, - Span currentSpan, - Span upperSpan, - Span outputSpan, - Span scratch) - { + private static void PredictBatch( + int mode, + int xStart, + int y, + int numPixels, + Span currentSpan, + Span upperSpan, + Span outputSpan, + Span scratch) + { #pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* current = currentSpan) - fixed (uint* upper = upperSpan) - fixed (uint* outputFixed = outputSpan) + fixed (uint* current = currentSpan) + fixed (uint* upper = upperSpan) + fixed (uint* outputFixed = outputSpan) + { + uint* output = outputFixed; + if (xStart == 0) { - uint* output = outputFixed; - if (xStart == 0) - { - if (y == 0) - { - // ARGB_BLACK. - LosslessUtils.PredictorSub0(current, 1, output); - } - else - { - // Top one. - LosslessUtils.PredictorSub2(current, upper, 1, output); - } - - ++xStart; - ++output; - --numPixels; - } - if (y == 0) { - // Left one. - LosslessUtils.PredictorSub1(current + xStart, numPixels, output); + // ARGB_BLACK. + LosslessUtils.PredictorSub0(current, 1, output); } else { - switch (mode) - { - case 0: - LosslessUtils.PredictorSub0(current + xStart, numPixels, output); - break; - case 1: - LosslessUtils.PredictorSub1(current + xStart, numPixels, output); - break; - case 2: - LosslessUtils.PredictorSub2(current + xStart, upper + xStart, numPixels, output); - break; - case 3: - LosslessUtils.PredictorSub3(current + xStart, upper + xStart, numPixels, output); - break; - case 4: - LosslessUtils.PredictorSub4(current + xStart, upper + xStart, numPixels, output); - break; - case 5: - LosslessUtils.PredictorSub5(current + xStart, upper + xStart, numPixels, output); - break; - case 6: - LosslessUtils.PredictorSub6(current + xStart, upper + xStart, numPixels, output); - break; - case 7: - LosslessUtils.PredictorSub7(current + xStart, upper + xStart, numPixels, output); - break; - case 8: - LosslessUtils.PredictorSub8(current + xStart, upper + xStart, numPixels, output); - break; - case 9: - LosslessUtils.PredictorSub9(current + xStart, upper + xStart, numPixels, output); - break; - case 10: - LosslessUtils.PredictorSub10(current + xStart, upper + xStart, numPixels, output); - break; - case 11: - LosslessUtils.PredictorSub11(current + xStart, upper + xStart, numPixels, output, scratch); - break; - case 12: - LosslessUtils.PredictorSub12(current + xStart, upper + xStart, numPixels, output); - break; - case 13: - LosslessUtils.PredictorSub13(current + xStart, upper + xStart, numPixels, output); - break; - } + // Top one. + LosslessUtils.PredictorSub2(current, upper, 1, output); } - } - } -#pragma warning restore SA1503 // Braces should not be omitted - private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span maxDiffs, bool usedSubtractGreen) - { - if (width <= 2) - { - return; + ++xStart; + ++output; + --numPixels; } - uint current = argb[offset]; - uint right = argb[offset + 1]; - if (usedSubtractGreen) + if (y == 0) { - current = AddGreenToBlueAndRed(current); - right = AddGreenToBlueAndRed(right); + // Left one. + LosslessUtils.PredictorSub1(current + xStart, numPixels, output); } - - for (int x = 1; x < width - 1; x++) + else { - uint up = argb[offset - stride + x]; - uint down = argb[offset + stride + x]; - uint left = current; - current = right; - right = argb[offset + x + 1]; - if (usedSubtractGreen) + switch (mode) { - up = AddGreenToBlueAndRed(up); - down = AddGreenToBlueAndRed(down); - right = AddGreenToBlueAndRed(right); + case 0: + LosslessUtils.PredictorSub0(current + xStart, numPixels, output); + break; + case 1: + LosslessUtils.PredictorSub1(current + xStart, numPixels, output); + break; + case 2: + LosslessUtils.PredictorSub2(current + xStart, upper + xStart, numPixels, output); + break; + case 3: + LosslessUtils.PredictorSub3(current + xStart, upper + xStart, numPixels, output); + break; + case 4: + LosslessUtils.PredictorSub4(current + xStart, upper + xStart, numPixels, output); + break; + case 5: + LosslessUtils.PredictorSub5(current + xStart, upper + xStart, numPixels, output); + break; + case 6: + LosslessUtils.PredictorSub6(current + xStart, upper + xStart, numPixels, output); + break; + case 7: + LosslessUtils.PredictorSub7(current + xStart, upper + xStart, numPixels, output); + break; + case 8: + LosslessUtils.PredictorSub8(current + xStart, upper + xStart, numPixels, output); + break; + case 9: + LosslessUtils.PredictorSub9(current + xStart, upper + xStart, numPixels, output); + break; + case 10: + LosslessUtils.PredictorSub10(current + xStart, upper + xStart, numPixels, output); + break; + case 11: + LosslessUtils.PredictorSub11(current + xStart, upper + xStart, numPixels, output, scratch); + break; + case 12: + LosslessUtils.PredictorSub12(current + xStart, upper + xStart, numPixels, output); + break; + case 13: + LosslessUtils.PredictorSub13(current + xStart, upper + xStart, numPixels, output); + break; } - - maxDiffs[x] = (byte)MaxDiffAroundPixel(current, up, down, left, right); } } + } +#pragma warning restore SA1503 // Braces should not be omitted - [MethodImpl(InliningOptions.ShortMethod)] - private static int MaxDiffBetweenPixels(uint p1, uint p2) + private static void MaxDiffsForRow(int width, int stride, Span argb, int offset, Span maxDiffs, bool usedSubtractGreen) + { + if (width <= 2) { - int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); - int diffR = Math.Abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); - int diffG = Math.Abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); - int diffB = Math.Abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); - return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); + return; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) + uint current = argb[offset]; + uint right = argb[offset + 1]; + if (usedSubtractGreen) { - int diffUp = MaxDiffBetweenPixels(current, up); - int diffDown = MaxDiffBetweenPixels(current, down); - int diffLeft = MaxDiffBetweenPixels(current, left); - int diffRight = MaxDiffBetweenPixels(current, right); - return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); + current = AddGreenToBlueAndRed(current); + right = AddGreenToBlueAndRed(right); } - [MethodImpl(InliningOptions.ShortMethod)] - private static void UpdateHisto(int[][] histoArgb, uint argb) + for (int x = 1; x < width - 1; x++) { - ++histoArgb[0][argb >> 24]; - ++histoArgb[1][(argb >> 16) & 0xff]; - ++histoArgb[2][(argb >> 8) & 0xff]; - ++histoArgb[3][argb & 0xff]; - } + uint up = argb[offset - stride + x]; + uint down = argb[offset + stride + x]; + uint left = current; + current = right; + right = argb[offset + x + 1]; + if (usedSubtractGreen) + { + up = AddGreenToBlueAndRed(up); + down = AddGreenToBlueAndRed(down); + right = AddGreenToBlueAndRed(right); + } - private static uint AddGreenToBlueAndRed(uint argb) - { - uint green = (argb >> 8) & 0xff; - uint redBlue = argb & 0x00ff00ffu; - redBlue += (green << 16) | green; - redBlue &= 0x00ff00ffu; - return (argb & 0xff00ff00u) | redBlue; + maxDiffs[x] = (byte)MaxDiffAroundPixel(current, up, down, left, right); } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int MaxDiffBetweenPixels(uint p1, uint p2) + { + int diffA = Math.Abs((int)(p1 >> 24) - (int)(p2 >> 24)); + int diffR = Math.Abs((int)((p1 >> 16) & 0xff) - (int)((p2 >> 16) & 0xff)); + int diffG = Math.Abs((int)((p1 >> 8) & 0xff) - (int)((p2 >> 8) & 0xff)); + int diffB = Math.Abs((int)(p1 & 0xff) - (int)(p2 & 0xff)); + return GetMax(GetMax(diffA, diffR), GetMax(diffG, diffB)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int MaxDiffAroundPixel(uint current, uint up, uint down, uint left, uint right) + { + int diffUp = MaxDiffBetweenPixels(current, up); + int diffDown = MaxDiffBetweenPixels(current, down); + int diffLeft = MaxDiffBetweenPixels(current, left); + int diffRight = MaxDiffBetweenPixels(current, right); + return GetMax(GetMax(diffUp, diffDown), GetMax(diffLeft, diffRight)); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void UpdateHisto(int[][] histoArgb, uint argb) + { + ++histoArgb[0][argb >> 24]; + ++histoArgb[1][(argb >> 16) & 0xff]; + ++histoArgb[2][(argb >> 8) & 0xff]; + ++histoArgb[3][argb & 0xff]; + } - private static void CopyTileWithColorTransform(int xSize, int ySize, int tileX, int tileY, int maxTileSize, Vp8LMultipliers colorTransform, Span argb) + private static uint AddGreenToBlueAndRed(uint argb) + { + uint green = (argb >> 8) & 0xff; + uint redBlue = argb & 0x00ff00ffu; + redBlue += (green << 16) | green; + redBlue &= 0x00ff00ffu; + return (argb & 0xff00ff00u) | redBlue; + } + + private static void CopyTileWithColorTransform(int xSize, int ySize, int tileX, int tileY, int maxTileSize, Vp8LMultipliers colorTransform, Span argb) + { + int xScan = GetMin(maxTileSize, xSize - tileX); + int yScan = GetMin(maxTileSize, ySize - tileY); + argb = argb[((tileY * xSize) + tileX)..]; + while (yScan-- > 0) { - int xScan = GetMin(maxTileSize, xSize - tileX); - int yScan = GetMin(maxTileSize, ySize - tileY); - argb = argb[((tileY * xSize) + tileX)..]; - while (yScan-- > 0) - { - LosslessUtils.TransformColor(colorTransform, argb, xScan); + LosslessUtils.TransformColor(colorTransform, argb, xScan); - if (argb.Length > xSize) - { - argb = argb[xSize..]; - } + if (argb.Length > xSize) + { + argb = argb[xSize..]; } } + } - private static Vp8LMultipliers GetBestColorTransformForTile( - int tileX, - int tileY, - int bits, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int quality, - int xSize, - int ySize, - int[] accumulatedRedHisto, - int[] accumulatedBlueHisto, - Span argb, - Span scratch) - { - int maxTileSize = 1 << bits; - int tileYOffset = tileY * maxTileSize; - int tileXOffset = tileX * maxTileSize; - int allXMax = GetMin(tileXOffset + maxTileSize, xSize); - int allYMax = GetMin(tileYOffset + maxTileSize, ySize); - int tileWidth = allXMax - tileXOffset; - int tileHeight = allYMax - tileYOffset; - Span tileArgb = argb[((tileYOffset * xSize) + tileXOffset)..]; + private static Vp8LMultipliers GetBestColorTransformForTile( + int tileX, + int tileY, + int bits, + Vp8LMultipliers prevX, + Vp8LMultipliers prevY, + int quality, + int xSize, + int ySize, + int[] accumulatedRedHisto, + int[] accumulatedBlueHisto, + Span argb, + Span scratch) + { + int maxTileSize = 1 << bits; + int tileYOffset = tileY * maxTileSize; + int tileXOffset = tileX * maxTileSize; + int allXMax = GetMin(tileXOffset + maxTileSize, xSize); + int allYMax = GetMin(tileYOffset + maxTileSize, ySize); + int tileWidth = allXMax - tileXOffset; + int tileHeight = allYMax - tileYOffset; + Span tileArgb = argb[((tileYOffset * xSize) + tileXOffset)..]; - var bestTx = default(Vp8LMultipliers); + var bestTx = default(Vp8LMultipliers); - GetBestGreenToRed(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); + GetBestGreenToRed(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedRedHisto, ref bestTx); - GetBestGreenRedToBlue(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); + GetBestGreenRedToBlue(tileArgb, xSize, scratch, tileWidth, tileHeight, prevX, prevY, quality, accumulatedBlueHisto, ref bestTx); - return bestTx; - } + return bestTx; + } - private static void GetBestGreenToRed( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int quality, - int[] accumulatedRedHisto, - ref Vp8LMultipliers bestTx) + private static void GetBestGreenToRed( + Span argb, + int stride, + Span scratch, + int tileWidth, + int tileHeight, + Vp8LMultipliers prevX, + Vp8LMultipliers prevY, + int quality, + int[] accumulatedRedHisto, + ref Vp8LMultipliers bestTx) + { + int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6] + int greenToRedBest = 0; + double bestDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); + for (int iter = 0; iter < maxIters; iter++) { - int maxIters = 4 + ((7 * quality) >> 8); // in range [4..6] - int greenToRedBest = 0; - double bestDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedBest, accumulatedRedHisto); - for (int iter = 0; iter < maxIters; iter++) - { - // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to - // one in color computation. Having initial delta here as 1 is sufficient - // to explore the range of (-2, 2). - int delta = 32 >> iter; + // ColorTransformDelta is a 3.5 bit fixed point, so 32 is equal to + // one in color computation. Having initial delta here as 1 is sufficient + // to explore the range of (-2, 2). + int delta = 32 >> iter; - // Try a negative and a positive delta from the best known value. - for (int offset = -delta; offset <= delta; offset += 2 * delta) + // Try a negative and a positive delta from the best known value. + for (int offset = -delta; offset <= delta; offset += 2 * delta) + { + int greenToRedCur = offset + greenToRedBest; + double curDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); + if (curDiff < bestDiff) { - int greenToRedCur = offset + greenToRedBest; - double curDiff = GetPredictionCostCrossColorRed(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToRedCur, accumulatedRedHisto); - if (curDiff < bestDiff) - { - bestDiff = curDiff; - greenToRedBest = greenToRedCur; - } + bestDiff = curDiff; + greenToRedBest = greenToRedCur; } } - - bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); } - private static void GetBestGreenRedToBlue(Span argb, int stride, Span scratch, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) - { - int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; - int greenToBlueBest = 0; - int redToBlueBest = 0; + bestTx.GreenToRed = (byte)(greenToRedBest & 0xff); + } + + private static void GetBestGreenRedToBlue(Span argb, int stride, Span scratch, int tileWidth, int tileHeight, Vp8LMultipliers prevX, Vp8LMultipliers prevY, int quality, int[] accumulatedBlueHisto, ref Vp8LMultipliers bestTx) + { + int iters = (quality < 25) ? 1 : (quality > 50) ? GreenRedToBlueMaxIters : 4; + int greenToBlueBest = 0; + int redToBlueBest = 0; - // Initial value at origin: - double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); - for (int iter = 0; iter < iters; iter++) + // Initial value at origin: + double bestDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueBest, redToBlueBest, accumulatedBlueHisto); + for (int iter = 0; iter < iters; iter++) + { + int delta = DeltaLut[iter]; + for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) { - int delta = DeltaLut[iter]; - for (int axis = 0; axis < GreenRedToBlueNumAxis; axis++) + int greenToBlueCur = (Offset[axis][0] * delta) + greenToBlueBest; + int redToBlueCur = (Offset[axis][1] * delta) + redToBlueBest; + double curDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); + if (curDiff < bestDiff) { - int greenToBlueCur = (Offset[axis][0] * delta) + greenToBlueBest; - int redToBlueCur = (Offset[axis][1] * delta) + redToBlueBest; - double curDiff = GetPredictionCostCrossColorBlue(argb, stride, scratch, tileWidth, tileHeight, prevX, prevY, greenToBlueCur, redToBlueCur, accumulatedBlueHisto); - if (curDiff < bestDiff) - { - bestDiff = curDiff; - greenToBlueBest = greenToBlueCur; - redToBlueBest = redToBlueCur; - } - - if (quality < 25 && iter == 4) - { - // Only axis aligned diffs for lower quality. - break; // next iter. - } + bestDiff = curDiff; + greenToBlueBest = greenToBlueCur; + redToBlueBest = redToBlueCur; } - if (delta == 2 && greenToBlueBest == 0 && redToBlueBest == 0) + if (quality < 25 && iter == 4) { - // Further iterations would not help. - break; // out of iter-loop. + // Only axis aligned diffs for lower quality. + break; // next iter. } } - bestTx.GreenToBlue = (byte)(greenToBlueBest & 0xff); - bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); - } - - private static double GetPredictionCostCrossColorRed( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int greenToRed, - int[] accumulatedRedHisto) - { - Span histo = scratch[..256]; - histo.Clear(); - - ColorSpaceTransformUtils.CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); - double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); - - if ((byte)greenToRed == prevX.GreenToRed) + if (delta == 2 && greenToBlueBest == 0 && redToBlueBest == 0) { - // Favor keeping the areas locally similar. - curDiff -= 3; + // Further iterations would not help. + break; // out of iter-loop. } + } - if ((byte)greenToRed == prevY.GreenToRed) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } + bestTx.GreenToBlue = (byte)(greenToBlueBest & 0xff); + bestTx.RedToBlue = (byte)(redToBlueBest & 0xff); + } - if (greenToRed == 0) - { - curDiff -= 3; - } + private static double GetPredictionCostCrossColorRed( + Span argb, + int stride, + Span scratch, + int tileWidth, + int tileHeight, + Vp8LMultipliers prevX, + Vp8LMultipliers prevY, + int greenToRed, + int[] accumulatedRedHisto) + { + Span histo = scratch[..256]; + histo.Clear(); - return curDiff; - } + ColorSpaceTransformUtils.CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); + double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); - private static double GetPredictionCostCrossColorBlue( - Span argb, - int stride, - Span scratch, - int tileWidth, - int tileHeight, - Vp8LMultipliers prevX, - Vp8LMultipliers prevY, - int greenToBlue, - int redToBlue, - int[] accumulatedBlueHisto) + if ((byte)greenToRed == prevX.GreenToRed) { - Span histo = scratch[..256]; - histo.Clear(); - - ColorSpaceTransformUtils.CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); - double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); - if ((byte)greenToBlue == prevX.GreenToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } + // Favor keeping the areas locally similar. + curDiff -= 3; + } - if ((byte)greenToBlue == prevY.GreenToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } + if ((byte)greenToRed == prevY.GreenToRed) + { + // Favor keeping the areas locally similar. + curDiff -= 3; + } - if ((byte)redToBlue == prevX.RedToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } + if (greenToRed == 0) + { + curDiff -= 3; + } - if ((byte)redToBlue == prevY.RedToBlue) - { - // Favor keeping the areas locally similar. - curDiff -= 3; - } + return curDiff; + } - if (greenToBlue == 0) - { - curDiff -= 3; - } + private static double GetPredictionCostCrossColorBlue( + Span argb, + int stride, + Span scratch, + int tileWidth, + int tileHeight, + Vp8LMultipliers prevX, + Vp8LMultipliers prevY, + int greenToBlue, + int redToBlue, + int[] accumulatedBlueHisto) + { + Span histo = scratch[..256]; + histo.Clear(); - if (redToBlue == 0) - { - curDiff -= 3; - } + ColorSpaceTransformUtils.CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); + if ((byte)greenToBlue == prevX.GreenToBlue) + { + // Favor keeping the areas locally similar. + curDiff -= 3; + } - return curDiff; + if ((byte)greenToBlue == prevY.GreenToBlue) + { + // Favor keeping the areas locally similar. + curDiff -= 3; } - private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) + if ((byte)redToBlue == prevX.RedToBlue) { - double retVal = 0.0d; - for (int i = 0; i < 4; i++) - { - double kExpValue = 0.94; - retVal += PredictionCostSpatial(tile[i], 1, kExpValue); - retVal += LosslessUtils.CombinedShannonEntropy(tile[i], accumulated[i]); - } + // Favor keeping the areas locally similar. + curDiff -= 3; + } - return (float)retVal; + if ((byte)redToBlue == prevY.RedToBlue) + { + // Favor keeping the areas locally similar. + curDiff -= 3; } - [MethodImpl(InliningOptions.ShortMethod)] - private static double PredictionCostCrossColor(int[] accumulated, Span counts) + if (greenToBlue == 0) { - // Favor low entropy, locally and globally. - // Favor small absolute values for PredictionCostSpatial. - const double expValue = 2.4d; - return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); + curDiff -= 3; } - [MethodImpl(InliningOptions.ShortMethod)] - private static float PredictionCostSpatial(Span counts, int weight0, double expVal) + if (redToBlue == 0) { - int significantSymbols = 256 >> 4; - double expDecayFactor = 0.6; - double bits = weight0 * counts[0]; - for (int i = 1; i < significantSymbols; i++) - { - bits += expVal * (counts[i] + counts[256 - i]); - expVal *= expDecayFactor; - } + curDiff -= 3; + } + + return curDiff; + } - return (float)(-0.1 * bits); + private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) + { + double retVal = 0.0d; + for (int i = 0; i < 4; i++) + { + double kExpValue = 0.94; + retVal += PredictionCostSpatial(tile[i], 1, kExpValue); + retVal += LosslessUtils.CombinedShannonEntropy(tile[i], accumulated[i]); } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte NearLosslessDiff(byte a, byte b) => (byte)((a - b) & 0xff); + return (float)retVal; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint MultipliersToColorCode(Vp8LMultipliers m) => 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; + [MethodImpl(InliningOptions.ShortMethod)] + private static double PredictionCostCrossColor(int[] accumulated, Span counts) + { + // Favor low entropy, locally and globally. + // Favor small absolute values for PredictionCostSpatial. + const double expValue = 2.4d; + return LosslessUtils.CombinedShannonEntropy(counts, accumulated) + PredictionCostSpatial(counts, 3, expValue); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMin(int a, int b) => a > b ? b : a; + [MethodImpl(InliningOptions.ShortMethod)] + private static float PredictionCostSpatial(Span counts, int weight0, double expVal) + { + int significantSymbols = 256 >> 4; + double expDecayFactor = 0.6; + double bits = weight0 * counts[0]; + for (int i = 1; i < significantSymbols; i++) + { + bits += expVal * (counts[i] + counts[256 - i]); + expVal *= expDecayFactor; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMax(int a, int b) => (a < b) ? b : a; + return (float)(-0.1 * bits); } + + [MethodImpl(InliningOptions.ShortMethod)] + private static byte NearLosslessDiff(byte a, byte b) => (byte)((a - b) & 0xff); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint MultipliersToColorCode(Vp8LMultipliers m) => 0xff000000u | ((uint)m.RedToBlue << 16) | ((uint)m.GreenToBlue << 8) | m.GreenToRed; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMin(int a, int b) => a > b ? b : a; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMax(int a, int b) => (a < b) ? b : a; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs index fc39798f44..ace9d62271 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs @@ -1,24 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +internal class Vp8LBackwardRefs { - internal class Vp8LBackwardRefs - { - public Vp8LBackwardRefs(int pixels) => this.Refs = new List(pixels); + public Vp8LBackwardRefs(int pixels) => this.Refs = new List(pixels); - /// - /// Gets or sets the common block-size. - /// - public int BlockSize { get; set; } + /// + /// Gets or sets the common block-size. + /// + public int BlockSize { get; set; } - /// - /// Gets the backward references. - /// - public List Refs { get; } + /// + /// Gets the backward references. + /// + public List Refs { get; } - public void Add(PixOrCopy pixOrCopy) => this.Refs.Add(pixOrCopy); - } + public void Add(PixOrCopy pixOrCopy) => this.Refs.Add(pixOrCopy); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs index cb7a5d8a87..649845b025 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBitEntropy.cs @@ -1,221 +1,218 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +/// +/// Holds bit entropy results and entropy-related functions. +/// +internal class Vp8LBitEntropy { /// - /// Holds bit entropy results and entropy-related functions. + /// Not a trivial literal symbol. /// - internal class Vp8LBitEntropy + private const uint NonTrivialSym = 0xffffffff; + + /// + /// Initializes a new instance of the class. + /// + public Vp8LBitEntropy() { - /// - /// Not a trivial literal symbol. - /// - private const uint NonTrivialSym = 0xffffffff; - - /// - /// Initializes a new instance of the class. - /// - public Vp8LBitEntropy() - { - this.Entropy = 0.0d; - this.Sum = 0; - this.NoneZeros = 0; - this.MaxVal = 0; - this.NoneZeroCode = NonTrivialSym; - } + this.Entropy = 0.0d; + this.Sum = 0; + this.NoneZeros = 0; + this.MaxVal = 0; + this.NoneZeroCode = NonTrivialSym; + } - /// - /// Gets or sets the entropy. - /// - public double Entropy { get; set; } + /// + /// Gets or sets the entropy. + /// + public double Entropy { get; set; } - /// - /// Gets or sets the sum of the population. - /// - public uint Sum { get; set; } + /// + /// Gets or sets the sum of the population. + /// + public uint Sum { get; set; } - /// - /// Gets or sets the number of non-zero elements in the population. - /// - public int NoneZeros { get; set; } + /// + /// Gets or sets the number of non-zero elements in the population. + /// + public int NoneZeros { get; set; } - /// - /// Gets or sets the maximum value in the population. - /// - public uint MaxVal { get; set; } + /// + /// Gets or sets the maximum value in the population. + /// + public uint MaxVal { get; set; } - /// - /// Gets or sets the index of the last non-zero in the population. - /// - public uint NoneZeroCode { get; set; } + /// + /// Gets or sets the index of the last non-zero in the population. + /// + public uint NoneZeroCode { get; set; } - public void Init() - { - this.Entropy = 0.0d; - this.Sum = 0; - this.NoneZeros = 0; - this.MaxVal = 0; - this.NoneZeroCode = NonTrivialSym; - } + public void Init() + { + this.Entropy = 0.0d; + this.Sum = 0; + this.NoneZeros = 0; + this.MaxVal = 0; + this.NoneZeroCode = NonTrivialSym; + } - public double BitsEntropyRefine() + public double BitsEntropyRefine() + { + double mix; + if (this.NoneZeros < 5) { - double mix; - if (this.NoneZeros < 5) + if (this.NoneZeros <= 1) { - if (this.NoneZeros <= 1) - { - return 0; - } + return 0; + } - // Two symbols, they will be 0 and 1 in a Huffman code. - // Let's mix in a bit of entropy to favor good clustering when - // distributions of these are combined. - if (this.NoneZeros == 2) - { - return (0.99 * this.Sum) + (0.01 * this.Entropy); - } + // Two symbols, they will be 0 and 1 in a Huffman code. + // Let's mix in a bit of entropy to favor good clustering when + // distributions of these are combined. + if (this.NoneZeros == 2) + { + return (0.99 * this.Sum) + (0.01 * this.Entropy); + } - // No matter what the entropy says, we cannot be better than minLimit - // with Huffman coding. I am mixing a bit of entropy into the - // minLimit since it produces much better (~0.5 %) compression results - // perhaps because of better entropy clustering. - if (this.NoneZeros == 3) - { - mix = 0.95; - } - else - { - mix = 0.7; // nonzeros == 4. - } + // No matter what the entropy says, we cannot be better than minLimit + // with Huffman coding. I am mixing a bit of entropy into the + // minLimit since it produces much better (~0.5 %) compression results + // perhaps because of better entropy clustering. + if (this.NoneZeros == 3) + { + mix = 0.95; } else { - mix = 0.627; + mix = 0.7; // nonzeros == 4. } - - double minLimit = (2 * this.Sum) - this.MaxVal; - minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); - return this.Entropy < minLimit ? minLimit : this.Entropy; } - - public void BitsEntropyUnrefined(Span array, int n) + else { - this.Init(); + mix = 0.627; + } + + double minLimit = (2 * this.Sum) - this.MaxVal; + minLimit = (mix * minLimit) + ((1.0 - mix) * this.Entropy); + return this.Entropy < minLimit ? minLimit : this.Entropy; + } - for (int i = 0; i < n; i++) + public void BitsEntropyUnrefined(Span array, int n) + { + this.Init(); + + for (int i = 0; i < n; i++) + { + if (array[i] != 0) { - if (array[i] != 0) + this.Sum += array[i]; + this.NoneZeroCode = (uint)i; + this.NoneZeros++; + this.Entropy -= LosslessUtils.FastSLog2(array[i]); + if (this.MaxVal < array[i]) { - this.Sum += array[i]; - this.NoneZeroCode = (uint)i; - this.NoneZeros++; - this.Entropy -= LosslessUtils.FastSLog2(array[i]); - if (this.MaxVal < array[i]) - { - this.MaxVal = array[i]; - } + this.MaxVal = array[i]; } } - - this.Entropy += LosslessUtils.FastSLog2(this.Sum); } - /// - /// Get the entropy for the distribution 'X'. - /// - public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xPrev = x[0]; + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } - this.Init(); + /// + /// Get the entropy for the distribution 'X'. + /// + public void BitsEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xPrev = x[0]; - for (i = 1; i < length; i++) + this.Init(); + + for (i = 1; i < length; i++) + { + uint xi = x[i]; + if (xi != xPrev) { - uint xi = x[i]; - if (xi != xPrev) - { - this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); - } + this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); } + } - this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); + this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } - public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xyPrev = x[0] + y[0]; + public void GetCombinedEntropyUnrefined(uint[] x, uint[] y, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xyPrev = x[0] + y[0]; - this.Init(); + this.Init(); - for (i = 1; i < length; i++) + for (i = 1; i < length; i++) + { + uint xy = x[i] + y[i]; + if (xy != xyPrev) { - uint xy = x[i] + y[i]; - if (xy != xyPrev) - { - this.GetEntropyUnrefined(xy, i, ref xyPrev, ref iPrev, stats); - } + this.GetEntropyUnrefined(xy, i, ref xyPrev, ref iPrev, stats); } + } - this.GetEntropyUnrefined(0, i, ref xyPrev, ref iPrev, stats); + this.GetEntropyUnrefined(0, i, ref xyPrev, ref iPrev, stats); - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } - public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) - { - int i; - int iPrev = 0; - uint xPrev = x[0]; + public void GetEntropyUnrefined(uint[] x, int length, Vp8LStreaks stats) + { + int i; + int iPrev = 0; + uint xPrev = x[0]; - this.Init(); + this.Init(); - for (i = 1; i < length; i++) + for (i = 1; i < length; i++) + { + uint xi = x[i]; + if (xi != xPrev) { - uint xi = x[i]; - if (xi != xPrev) - { - this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); - } + this.GetEntropyUnrefined(xi, i, ref xPrev, ref iPrev, stats); } + } - this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); + this.GetEntropyUnrefined(0, i, ref xPrev, ref iPrev, stats); - this.Entropy += LosslessUtils.FastSLog2(this.Sum); - } + this.Entropy += LosslessUtils.FastSLog2(this.Sum); + } - private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) - { - int streak = i - iPrev; + private void GetEntropyUnrefined(uint val, int i, ref uint valPrev, ref int iPrev, Vp8LStreaks stats) + { + int streak = i - iPrev; - // Gather info for the bit entropy. - if (valPrev != 0) + // Gather info for the bit entropy. + if (valPrev != 0) + { + this.Sum += (uint)(valPrev * streak); + this.NoneZeros += streak; + this.NoneZeroCode = (uint)iPrev; + this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak; + if (this.MaxVal < valPrev) { - this.Sum += (uint)(valPrev * streak); - this.NoneZeros += streak; - this.NoneZeroCode = (uint)iPrev; - this.Entropy -= LosslessUtils.FastSLog2(valPrev) * streak; - if (this.MaxVal < valPrev) - { - this.MaxVal = valPrev; - } + this.MaxVal = valPrev; } + } - // Gather info for the Huffman cost. - stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; - stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; + // Gather info for the Huffman cost. + stats.Counts[valPrev != 0 ? 1 : 0] += streak > 3 ? 1 : 0; + stats.Streaks[valPrev != 0 ? 1 : 0][streak > 3 ? 1 : 0] += streak; - valPrev = val; - iPrev = i; - } + valPrev = val; + iPrev = i; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs index e6945eb0b1..80c0bb437b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LDecoder.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Holds information for decoding a lossless webp image. +/// +internal class Vp8LDecoder : IDisposable { /// - /// Holds information for decoding a lossless webp image. + /// Initializes a new instance of the class. /// - internal class Vp8LDecoder : IDisposable + /// The width of the image. + /// The height of the image. + /// Used for allocating memory for the pixel data output. + public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator) { - /// - /// Initializes a new instance of the class. - /// - /// The width of the image. - /// The height of the image. - /// Used for allocating memory for the pixel data output. - public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator) - { - this.Width = width; - this.Height = height; - this.Metadata = new Vp8LMetadata(); - this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean); - } + this.Width = width; + this.Height = height; + this.Metadata = new Vp8LMetadata(); + this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean); + } - /// - /// Gets or sets the width of the image to decode. - /// - public int Width { get; set; } + /// + /// Gets or sets the width of the image to decode. + /// + public int Width { get; set; } - /// - /// Gets or sets the height of the image to decode. - /// - public int Height { get; set; } + /// + /// Gets or sets the height of the image to decode. + /// + public int Height { get; set; } - /// - /// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image. - /// - public Vp8LMetadata Metadata { get; set; } + /// + /// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image. + /// + public Vp8LMetadata Metadata { get; set; } + + /// + /// Gets or sets the transformations which needs to be reversed. + /// + public List Transforms { get; set; } - /// - /// Gets or sets the transformations which needs to be reversed. - /// - public List Transforms { get; set; } + /// + /// Gets the pixel data. + /// + public IMemoryOwner Pixels { get; } - /// - /// Gets the pixel data. - /// - public IMemoryOwner Pixels { get; } + /// + public void Dispose() + { + this.Pixels.Dispose(); + this.Metadata?.HuffmanImage?.Dispose(); - /// - public void Dispose() + if (this.Transforms != null) { - this.Pixels.Dispose(); - this.Metadata?.HuffmanImage?.Dispose(); - - if (this.Transforms != null) + foreach (Vp8LTransform transform in this.Transforms) { - foreach (Vp8LTransform transform in this.Transforms) - { - transform.Data?.Dispose(); - } + transform.Data?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 6e7cbd5e82..3917c863b9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -14,803 +10,641 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Encoder for lossless webp images. +/// +internal class Vp8LEncoder : IDisposable { /// - /// Encoder for lossless webp images. + /// Scratch buffer to reduce allocations. + /// + private readonly int[] scratch = new int[256]; + + private readonly int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] }; + + private readonly int[][] bestHisto = { new int[256], new int[256], new int[256], new int[256] }; + + /// + /// The to use for buffer allocations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Maximum number of reference blocks the image will be segmented into. + /// + private const int MaxRefsBlockPerImage = 16; + + /// + /// Minimum block size for backward references. + /// + private const int MinBlockSize = 256; + + /// + /// A bit writer for writing lossless webp streams. + /// + private Vp8LBitWriter bitWriter; + + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; + + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly WebpEncodingMethod method; + + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. /// - internal class Vp8LEncoder : IDisposable + private readonly WebpTransparentColorMode transparentColorMode; + + /// + /// Indicating whether near lossless mode should be used. + /// + private readonly bool nearLossless; + + /// + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// + private readonly int nearLosslessQuality; + + private const int ApplyPaletteGreedyMax = 4; + + private const int PaletteInvSizeBits = 11; + + private const int PaletteInvSize = 1 << PaletteInvSizeBits; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The global configuration. + /// The width of the input image. + /// The height of the input image. + /// The encoding quality. + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// Flag indicating whether to preserve the exact RGB values under transparent area. + /// Otherwise, discard this invisible RGB information for better compression. + /// Indicating whether near lossless mode should be used. + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + public Vp8LEncoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + WebpTransparentColorMode transparentColorMode, + bool nearLossless, + int nearLosslessQuality) { - /// - /// Scratch buffer to reduce allocations. - /// - private readonly int[] scratch = new int[256]; - - private readonly int[][] histoArgb = { new int[256], new int[256], new int[256], new int[256] }; - - private readonly int[][] bestHisto = { new int[256], new int[256], new int[256], new int[256] }; - - /// - /// The to use for buffer allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Maximum number of reference blocks the image will be segmented into. - /// - private const int MaxRefsBlockPerImage = 16; - - /// - /// Minimum block size for backward references. - /// - private const int MinBlockSize = 256; - - /// - /// A bit writer for writing lossless webp streams. - /// - private Vp8LBitWriter bitWriter; - - /// - /// The quality, that will be used to encode the image. - /// - private readonly int quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible - /// RGB information for better compression. - /// - private readonly WebpTransparentColorMode transparentColorMode; - - /// - /// Indicating whether near lossless mode should be used. - /// - private readonly bool nearLossless; - - /// - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// - private readonly int nearLosslessQuality; - - private const int ApplyPaletteGreedyMax = 4; - - private const int PaletteInvSizeBits = 11; - - private const int PaletteInvSize = 1 << PaletteInvSizeBits; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The width of the input image. - /// The height of the input image. - /// The encoding quality. - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// Flag indicating whether to preserve the exact RGB values under transparent area. - /// Otherwise, discard this invisible RGB information for better compression. - /// Indicating whether near lossless mode should be used. - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - public Vp8LEncoder( - MemoryAllocator memoryAllocator, - Configuration configuration, - int width, - int height, - int quality, - WebpEncodingMethod method, - WebpTransparentColorMode transparentColorMode, - bool nearLossless, - int nearLosslessQuality) - { - int pixelCount = width * height; - int initialSize = pixelCount * 2; - - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.quality = Numerics.Clamp(quality, 0, 100); - this.method = method; - this.transparentColorMode = transparentColorMode; - this.nearLossless = nearLossless; - this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); - this.bitWriter = new Vp8LBitWriter(initialSize); - this.Bgra = memoryAllocator.Allocate(pixelCount); - this.EncodedData = memoryAllocator.Allocate(pixelCount); - this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); - this.Refs = new Vp8LBackwardRefs[3]; - this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount); - - // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: - int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; - for (int i = 0; i < this.Refs.Length; i++) + int pixelCount = width * height; + int initialSize = pixelCount * 2; + + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.quality = Numerics.Clamp(quality, 0, 100); + this.method = method; + this.transparentColorMode = transparentColorMode; + this.nearLossless = nearLossless; + this.nearLosslessQuality = Numerics.Clamp(nearLosslessQuality, 0, 100); + this.bitWriter = new Vp8LBitWriter(initialSize); + this.Bgra = memoryAllocator.Allocate(pixelCount); + this.EncodedData = memoryAllocator.Allocate(pixelCount); + this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); + this.Refs = new Vp8LBackwardRefs[3]; + this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount); + + // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: + int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; + for (int i = 0; i < this.Refs.Length; i++) + { + this.Refs[i] = new Vp8LBackwardRefs(pixelCount) { - this.Refs[i] = new Vp8LBackwardRefs(pixelCount) - { - BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize - }; - } + BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize + }; } + } - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan StorageOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Order => new byte[] { 1, 2, 0, 3 }; - - /// - /// Gets the memory for the image data as packed bgra values. - /// - public IMemoryOwner Bgra { get; } - - /// - /// Gets the memory for the encoded output image data. - /// - public IMemoryOwner EncodedData { get; } - - /// - /// Gets or sets the scratch memory for bgra rows used for predictions. - /// - public IMemoryOwner BgraScratch { get; set; } - - /// - /// Gets or sets the packed image width. - /// - public int CurrentWidth { get; set; } - - /// - /// Gets or sets the huffman image bits. - /// - public int HistoBits { get; set; } - - /// - /// Gets or sets the bits used for the transformation. - /// - public int TransformBits { get; set; } - - /// - /// Gets or sets the transform data. - /// - public IMemoryOwner TransformData { get; set; } - - /// - /// Gets or sets the cache bits. If equal to 0, don't use color cache. - /// - public int CacheBits { get; set; } - - /// - /// Gets or sets a value indicating whether to use the cross color transform. - /// - public bool UseCrossColorTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use the subtract green transform. - /// - public bool UseSubtractGreenTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use the predictor transform. - /// - public bool UsePredictorTransform { get; set; } - - /// - /// Gets or sets a value indicating whether to use color indexing transform. - /// - public bool UsePalette { get; set; } - - /// - /// Gets or sets the palette size. - /// - public int PaletteSize { get; set; } - - /// - /// Gets the palette. - /// - public IMemoryOwner Palette { get; } - - /// - /// Gets the backward references. - /// - public Vp8LBackwardRefs[] Refs { get; } - - /// - /// Gets the hash chain. - /// - public Vp8LHashChain HashChain { get; } - - /// - /// Encodes the image as lossless webp to the specified stream. - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - - ImageMetadata metadata = image.Metadata; - metadata.SyncProfiles(); - - // Convert image pixels to bgra array. - bool hasAlpha = this.ConvertPixelsToBgra(image, width, height); - - // Write the image size. - this.WriteImageSize(width, height); - - // Write the non-trivial Alpha flag and lossless version. - this.WriteAlphaAndVersion(hasAlpha); - - // Encode the main image stream. - this.EncodeStream(image); - - // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha); - } + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan StorageOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - /// - /// Encodes the alpha image data using the webp lossless compression. - /// - /// The type of the pixel. - /// The to encode from. - /// The destination buffer to write the encoded alpha data to. - /// The size of the compressed data in bytes. - /// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed. - /// - public int EncodeAlphaImageData(Image image, IMemoryOwner alphaData) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - int pixelCount = width * height; - - // Convert image pixels to bgra array. - this.ConvertPixelsToBgra(image, width, height); - - // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known. - this.EncodeStream(image); - this.bitWriter.Finish(); - int size = this.bitWriter.NumBytes(); - if (size >= pixelCount) - { - // Compressing would not yield in smaller data -> leave the data uncompressed. - return pixelCount; - } + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan Order => new byte[] { 1, 2, 0, 3 }; - this.bitWriter.WriteToBuffer(alphaData.GetSpan()); - return size; - } + /// + /// Gets the memory for the image data as packed bgra values. + /// + public IMemoryOwner Bgra { get; } - /// - /// Writes the image size to the bitwriter buffer. - /// - /// The input image width. - /// The input image height. - private void WriteImageSize(int inputImgWidth, int inputImgHeight) - { - Guard.MustBeLessThan(inputImgWidth, WebpConstants.MaxDimension, nameof(inputImgWidth)); - Guard.MustBeLessThan(inputImgHeight, WebpConstants.MaxDimension, nameof(inputImgHeight)); + /// + /// Gets the memory for the encoded output image data. + /// + public IMemoryOwner EncodedData { get; } - uint width = (uint)inputImgWidth - 1; - uint height = (uint)inputImgHeight - 1; + /// + /// Gets or sets the scratch memory for bgra rows used for predictions. + /// + public IMemoryOwner BgraScratch { get; set; } - this.bitWriter.PutBits(width, WebpConstants.Vp8LImageSizeBits); - this.bitWriter.PutBits(height, WebpConstants.Vp8LImageSizeBits); - } + /// + /// Gets or sets the packed image width. + /// + public int CurrentWidth { get; set; } - /// - /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer. - /// - /// Indicates if a alpha channel is present. - private void WriteAlphaAndVersion(bool hasAlpha) - { - this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); - this.bitWriter.PutBits(WebpConstants.Vp8LVersion, WebpConstants.Vp8LVersionBits); - } + /// + /// Gets or sets the huffman image bits. + /// + public int HistoBits { get; set; } - /// - /// Encodes the image stream using lossless webp format. - /// - /// The pixel type. - /// The image to encode. - private void EncodeStream(Image image) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - - Span bgra = this.Bgra.GetSpan(); - Span encodedData = this.EncodedData.GetSpan(); - bool lowEffort = this.method == 0; - - // Analyze image (entropy, numPalettes etc). - CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); - - int bestSize = 0; - Vp8LBitWriter bitWriterInit = this.bitWriter; - Vp8LBitWriter bitWriterBest = this.bitWriter.Clone(); - bool isFirstConfig = true; - foreach (CrunchConfig crunchConfig in crunchConfigs) - { - bgra.CopyTo(encodedData); - const bool useCache = true; - this.UsePalette = crunchConfig.EntropyIdx is EntropyIx.Palette or EntropyIx.PaletteAndSpatial; - this.UseSubtractGreenTransform = crunchConfig.EntropyIdx is EntropyIx.SubGreen or EntropyIx.SpatialSubGreen; - this.UsePredictorTransform = crunchConfig.EntropyIdx is EntropyIx.Spatial or EntropyIx.SpatialSubGreen; - if (lowEffort) - { - this.UseCrossColorTransform = false; - } - else - { - this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; - } + /// + /// Gets or sets the bits used for the transformation. + /// + public int TransformBits { get; set; } - this.AllocateTransformBuffer(width, height); + /// + /// Gets or sets the transform data. + /// + public IMemoryOwner TransformData { get; set; } - // Reset any parameter in the encoder that is set in the previous iteration. - this.CacheBits = 0; - this.ClearRefs(); + /// + /// Gets or sets the cache bits. If equal to 0, don't use color cache. + /// + public int CacheBits { get; set; } - if (this.nearLossless) - { - // Apply near-lossless preprocessing. - bool useNearLossless = this.nearLosslessQuality < 100 && !this.UsePalette && !this.UsePredictorTransform; - if (useNearLossless) - { - this.AllocateTransformBuffer(width, height); - NearLosslessEnc.ApplyNearLossless(width, height, this.nearLosslessQuality, bgra, bgra, width); - } - } + /// + /// Gets or sets a value indicating whether to use the cross color transform. + /// + public bool UseCrossColorTransform { get; set; } - // Encode palette. - if (this.UsePalette) - { - this.EncodePalette(lowEffort); - this.MapImageFromPalette(width, height); + /// + /// Gets or sets a value indicating whether to use the subtract green transform. + /// + public bool UseSubtractGreenTransform { get; set; } - // If using a color cache, do not have it bigger than the number of colors. - if (useCache && this.PaletteSize < 1 << WebpConstants.MaxColorCacheBits) - { - this.CacheBits = BitOperations.Log2((uint)this.PaletteSize) + 1; - } - } + /// + /// Gets or sets a value indicating whether to use the predictor transform. + /// + public bool UsePredictorTransform { get; set; } - // Apply transforms and write transform data. - if (this.UseSubtractGreenTransform) - { - this.ApplySubtractGreen(); - } + /// + /// Gets or sets a value indicating whether to use color indexing transform. + /// + public bool UsePalette { get; set; } - if (this.UsePredictorTransform) - { - this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort); - } + /// + /// Gets or sets the palette size. + /// + public int PaletteSize { get; set; } - if (this.UseCrossColorTransform) - { - this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort); - } + /// + /// Gets the palette. + /// + public IMemoryOwner Palette { get; } - this.bitWriter.PutBits(0, 1); // No more transforms. + /// + /// Gets the backward references. + /// + public Vp8LBackwardRefs[] Refs { get; } - // Encode and write the transformed image. - this.EncodeImage( - this.CurrentWidth, - height, - useCache, - crunchConfig, - this.CacheBits, - lowEffort); + /// + /// Gets the hash chain. + /// + public Vp8LHashChain HashChain { get; } - // If we are better than what we already have. - if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) - { - bestSize = this.bitWriter.NumBytes(); - BitWriterSwap(ref this.bitWriter, ref bitWriterBest); - } + /// + /// Encodes the image as lossless webp to the specified stream. + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; - // Reset the bit writer for the following iteration if any. - if (crunchConfigs.Length > 1) - { - this.bitWriter.Reset(bitWriterInit); - } + ImageMetadata metadata = image.Metadata; + metadata.SyncProfiles(); - isFirstConfig = false; - } + // Convert image pixels to bgra array. + bool hasAlpha = this.ConvertPixelsToBgra(image, width, height); - BitWriterSwap(ref bitWriterBest, ref this.bitWriter); - } + // Write the image size. + this.WriteImageSize(width, height); - /// - /// Converts the pixels of the image to bgra. - /// - /// The type of the pixels. - /// The image to convert. - /// The width of the image. - /// The height of the image. - /// true, if the image is non opaque. - private bool ConvertPixelsToBgra(Image image, int width, int height) - where TPixel : unmanaged, IPixel - { - Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; - bool nonOpaque = false; - Span bgra = this.Bgra.GetSpan(); - Span bgraBytes = MemoryMarshal.Cast(bgra); - int widthBytes = width * 4; - for (int y = 0; y < height; y++) - { - Span rowSpan = imageBuffer.DangerousGetRowSpan(y); - Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); - PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); - if (!nonOpaque) - { - Span rowBgra = MemoryMarshal.Cast(rowBytes); - nonOpaque = WebpCommonUtils.CheckNonOpaque(rowBgra); - } - } + // Write the non-trivial Alpha flag and lossless version. + this.WriteAlphaAndVersion(hasAlpha); - return nonOpaque; - } + // Encode the main image stream. + this.EncodeStream(image); + + // Write bytes from the bitwriter buffer to the stream. + this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, metadata.IccProfile, (uint)width, (uint)height, hasAlpha); + } - /// - /// Analyzes the image and decides which transforms should be used. - /// - /// The image as packed bgra values. - /// The image width. - /// The image height. - /// Indicates if red and blue are always zero. - private CrunchConfig[] EncoderAnalyze(ReadOnlySpan bgra, int width, int height, out bool redAndBlueAlwaysZero) + /// + /// Encodes the alpha image data using the webp lossless compression. + /// + /// The type of the pixel. + /// The to encode from. + /// The destination buffer to write the encoded alpha data to. + /// The size of the compressed data in bytes. + /// If the size of the data is the same as the pixel count, the compression would not yield in smaller data and is left uncompressed. + /// + public int EncodeAlphaImageData(Image image, IMemoryOwner alphaData) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + int pixelCount = width * height; + + // Convert image pixels to bgra array. + this.ConvertPixelsToBgra(image, width, height); + + // The image-stream will NOT contain any headers describing the image dimension, the dimension is already known. + this.EncodeStream(image); + this.bitWriter.Finish(); + int size = this.bitWriter.NumBytes(); + if (size >= pixelCount) { - // Check if we only deal with a small number of colors and should use a palette. - bool usePalette = this.AnalyzeAndCreatePalette(bgra, width, height); + // Compressing would not yield in smaller data -> leave the data uncompressed. + return pixelCount; + } - // Empirical bit sizes. - this.HistoBits = GetHistoBits(this.method, usePalette, width, height); - this.TransformBits = GetTransformBits(this.method, this.HistoBits); + this.bitWriter.WriteToBuffer(alphaData.GetSpan()); + return size; + } - // Try out multiple LZ77 on images with few colors. - int nlz77s = this.PaletteSize is > 0 and <= 16 ? 2 : 1; - EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); + /// + /// Writes the image size to the bitwriter buffer. + /// + /// The input image width. + /// The input image height. + private void WriteImageSize(int inputImgWidth, int inputImgHeight) + { + Guard.MustBeLessThan(inputImgWidth, WebpConstants.MaxDimension, nameof(inputImgWidth)); + Guard.MustBeLessThan(inputImgHeight, WebpConstants.MaxDimension, nameof(inputImgHeight)); - bool doNotCache = false; - List crunchConfigs = new(); + uint width = (uint)inputImgWidth - 1; + uint height = (uint)inputImgHeight - 1; - if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) - { - doNotCache = true; + this.bitWriter.PutBits(width, WebpConstants.Vp8LImageSizeBits); + this.bitWriter.PutBits(height, WebpConstants.Vp8LImageSizeBits); + } - // Go brute force on all transforms. - foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast()) - { - // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette. - if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) - { - crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx }); - } - } + /// + /// Writes a flag indicating if alpha channel is used and the VP8L version to the bitwriter buffer. + /// + /// Indicates if a alpha channel is present. + private void WriteAlphaAndVersion(bool hasAlpha) + { + this.bitWriter.PutBits(hasAlpha ? 1U : 0, 1); + this.bitWriter.PutBits(WebpConstants.Vp8LVersion, WebpConstants.Vp8LVersionBits); + } + + /// + /// Encodes the image stream using lossless webp format. + /// + /// The pixel type. + /// The image to encode. + private void EncodeStream(Image image) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + + Span bgra = this.Bgra.GetSpan(); + Span encodedData = this.EncodedData.GetSpan(); + bool lowEffort = this.method == 0; + + // Analyze image (entropy, numPalettes etc). + CrunchConfig[] crunchConfigs = this.EncoderAnalyze(bgra, width, height, out bool redAndBlueAlwaysZero); + + int bestSize = 0; + Vp8LBitWriter bitWriterInit = this.bitWriter; + Vp8LBitWriter bitWriterBest = this.bitWriter.Clone(); + bool isFirstConfig = true; + foreach (CrunchConfig crunchConfig in crunchConfigs) + { + bgra.CopyTo(encodedData); + const bool useCache = true; + this.UsePalette = crunchConfig.EntropyIdx is EntropyIx.Palette or EntropyIx.PaletteAndSpatial; + this.UseSubtractGreenTransform = crunchConfig.EntropyIdx is EntropyIx.SubGreen or EntropyIx.SpatialSubGreen; + this.UsePredictorTransform = crunchConfig.EntropyIdx is EntropyIx.Spatial or EntropyIx.SpatialSubGreen; + if (lowEffort) + { + this.UseCrossColorTransform = false; } else { - // Only choose the guessed best transform. - crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); - if (this.quality >= 75 && this.method == WebpEncodingMethod.Level5) - { - // Test with and without color cache. - doNotCache = true; + this.UseCrossColorTransform = !redAndBlueAlwaysZero && this.UsePredictorTransform; + } - // If we have a palette, also check in combination with spatial. - if (entropyIdx == EntropyIx.Palette) - { - crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial }); - } + this.AllocateTransformBuffer(width, height); + + // Reset any parameter in the encoder that is set in the previous iteration. + this.CacheBits = 0; + this.ClearRefs(); + + if (this.nearLossless) + { + // Apply near-lossless preprocessing. + bool useNearLossless = this.nearLosslessQuality < 100 && !this.UsePalette && !this.UsePredictorTransform; + if (useNearLossless) + { + this.AllocateTransformBuffer(width, height); + NearLosslessEnc.ApplyNearLossless(width, height, this.nearLosslessQuality, bgra, bgra, width); } } - // Fill in the different LZ77s. - foreach (CrunchConfig crunchConfig in crunchConfigs) + // Encode palette. + if (this.UsePalette) { - for (int j = 0; j < nlz77s; j++) + this.EncodePalette(lowEffort); + this.MapImageFromPalette(width, height); + + // If using a color cache, do not have it bigger than the number of colors. + if (useCache && this.PaletteSize < 1 << WebpConstants.MaxColorCacheBits) { - crunchConfig.SubConfigs.Add(new CrunchSubConfig - { - Lz77 = j == 0 ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, - DoNotCache = doNotCache - }); + this.CacheBits = BitOperations.Log2((uint)this.PaletteSize) + 1; } } - return crunchConfigs.ToArray(); - } - - private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort) - { - // bgra data with transformations applied. - Span bgra = this.EncodedData.GetSpan(); - int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits); - ushort[] histogramSymbols = new ushort[histogramImageXySize]; - HuffmanTree[] huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) + // Apply transforms and write transform data. + if (this.UseSubtractGreenTransform) { - huffTree[i] = default; + this.ApplySubtractGreen(); } - if (useCache) + if (this.UsePredictorTransform) { - if (cacheBits == 0) - { - cacheBits = WebpConstants.MaxColorCacheBits; - } + this.ApplyPredictFilter(this.CurrentWidth, height, lowEffort); } - else + + if (this.UseCrossColorTransform) { - cacheBits = 0; + this.ApplyCrossColorFilter(this.CurrentWidth, height, lowEffort); } - // Calculate backward references from BGRA image. - this.HashChain.Fill(bgra, this.quality, width, height, lowEffort); + this.bitWriter.PutBits(0, 1); // No more transforms. + + // Encode and write the transformed image. + this.EncodeImage( + this.CurrentWidth, + height, + useCache, + crunchConfig, + this.CacheBits, + lowEffort); - Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; - Vp8LBitWriter bwInit = this.bitWriter; - bool isFirstIteration = true; - foreach (CrunchSubConfig subConfig in config.SubConfigs) + // If we are better than what we already have. + if (isFirstConfig || this.bitWriter.NumBytes() < bestSize) { - Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( - width, - height, - bgra, - this.quality, - subConfig.Lz77, - ref cacheBits, - this.memoryAllocator, - this.HashChain, - this.Refs[0], - this.Refs[1]); + bestSize = this.bitWriter.NumBytes(); + BitWriterSwap(ref this.bitWriter, ref bitWriterBest); + } - // Keep the best references aside and use the other element from the first - // two as a temporary for later usage. - Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; + // Reset the bit writer for the following iteration if any. + if (crunchConfigs.Length > 1) + { + this.bitWriter.Reset(bitWriterInit); + } - this.bitWriter.Reset(bwInit); - Vp8LHistogram tmpHisto = new(cacheBits); - List histogramImage = new(histogramImageXySize); - for (int i = 0; i < histogramImageXySize; i++) - { - histogramImage.Add(new Vp8LHistogram(cacheBits)); - } + isFirstConfig = false; + } - // Build histogram image and symbols from backward references. - HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, this.HistoBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); + BitWriterSwap(ref bitWriterBest, ref this.bitWriter); + } - // Create Huffman bit lengths and codes for each histogram image. - int histogramImageSize = histogramImage.Count; - int bitArraySize = 5 * histogramImageSize; - HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[bitArraySize]; - for (int i = 0; i < huffmanCodes.Length; i++) - { - huffmanCodes[i] = default; - } + /// + /// Converts the pixels of the image to bgra. + /// + /// The type of the pixels. + /// The image to convert. + /// The width of the image. + /// The height of the image. + /// true, if the image is non opaque. + private bool ConvertPixelsToBgra(Image image, int width, int height) + where TPixel : unmanaged, IPixel + { + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + bool nonOpaque = false; + Span bgra = this.Bgra.GetSpan(); + Span bgraBytes = MemoryMarshal.Cast(bgra); + int widthBytes = width * 4; + for (int y = 0; y < height; y++) + { + Span rowSpan = imageBuffer.DangerousGetRowSpan(y); + Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); + PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); + if (!nonOpaque) + { + Span rowBgra = MemoryMarshal.Cast(rowBytes); + nonOpaque = WebpCommonUtils.CheckNonOpaque(rowBgra); + } + } - GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); + return nonOpaque; + } - // Color Cache parameters. - if (cacheBits > 0) - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)cacheBits, 4); - } - else - { - this.bitWriter.PutBits(0, 1); - } + /// + /// Analyzes the image and decides which transforms should be used. + /// + /// The image as packed bgra values. + /// The image width. + /// The image height. + /// Indicates if red and blue are always zero. + private CrunchConfig[] EncoderAnalyze(ReadOnlySpan bgra, int width, int height, out bool redAndBlueAlwaysZero) + { + // Check if we only deal with a small number of colors and should use a palette. + bool usePalette = this.AnalyzeAndCreatePalette(bgra, width, height); - // Huffman image + meta huffman. - bool writeHistogramImage = histogramImageSize > 1; - this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); - if (writeHistogramImage) - { - using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); - Span histogramBgra = histogramBgraBuffer.GetSpan(); - int maxIndex = 0; - for (int i = 0; i < histogramImageXySize; i++) - { - int symbolIndex = histogramSymbols[i] & 0xffff; - histogramBgra[i] = (uint)(symbolIndex << 8); - if (symbolIndex >= maxIndex) - { - maxIndex = symbolIndex + 1; - } - } + // Empirical bit sizes. + this.HistoBits = GetHistoBits(this.method, usePalette, width, height); + this.TransformBits = GetTransformBits(this.method, this.HistoBits); - this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3); - this.EncodeImageNoHuffman( - histogramBgra, - this.HashChain, - refsTmp, - this.Refs[2], - LosslessUtils.SubSampleSize(width, this.HistoBits), - LosslessUtils.SubSampleSize(height, this.HistoBits), - this.quality, - lowEffort); - } + // Try out multiple LZ77 on images with few colors. + int nlz77s = this.PaletteSize is > 0 and <= 16 ? 2 : 1; + EntropyIx entropyIdx = this.AnalyzeEntropy(bgra, width, height, usePalette, this.PaletteSize, this.TransformBits, out redAndBlueAlwaysZero); - // Store Huffman codes. - // Find maximum number of symbols for the huffman tree-set. - int maxTokens = 0; - for (int i = 0; i < 5 * histogramImage.Count; i++) - { - HuffmanTreeCode codes = huffmanCodes[i]; - if (maxTokens < codes.NumSymbols) - { - maxTokens = codes.NumSymbols; - } - } + bool doNotCache = false; + List crunchConfigs = new(); - HuffmanTreeToken[] tokens = new HuffmanTreeToken[maxTokens]; - for (int i = 0; i < tokens.Length; i++) - { - tokens[i] = new HuffmanTreeToken(); - } + if (this.method == WebpEncodingMethod.BestQuality && this.quality == 100) + { + doNotCache = true; - for (int i = 0; i < 5 * histogramImage.Count; i++) + // Go brute force on all transforms. + foreach (EntropyIx entropyIx in Enum.GetValues(typeof(EntropyIx)).Cast()) + { + // We can only apply kPalette or kPaletteAndSpatial if we can indeed use a palette. + if ((entropyIx != EntropyIx.Palette && entropyIx != EntropyIx.PaletteAndSpatial) || usePalette) { - HuffmanTreeCode codes = huffmanCodes[i]; - this.StoreHuffmanCode(huffTree, tokens, codes); - ClearHuffmanTreeIfOnlyOneSymbol(codes); + crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIx }); } + } + } + else + { + // Only choose the guessed best transform. + crunchConfigs.Add(new CrunchConfig { EntropyIdx = entropyIdx }); + if (this.quality >= 75 && this.method == WebpEncodingMethod.Level5) + { + // Test with and without color cache. + doNotCache = true; - // Store actual literals. - this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes); - - // Keep track of the smallest image so far. - if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) + // If we have a palette, also check in combination with spatial. + if (entropyIdx == EntropyIx.Palette) { - Vp8LBitWriter tmp = this.bitWriter; - this.bitWriter = bitWriterBest; - bitWriterBest = tmp; + crunchConfigs.Add(new CrunchConfig { EntropyIdx = EntropyIx.PaletteAndSpatial }); } - - isFirstIteration = false; } - - this.bitWriter = bitWriterBest; } - /// - /// Save the palette to the bitstream. - /// - private void EncodePalette(bool lowEffort) - { - Span tmpPalette = new uint[WebpConstants.MaxPaletteSize]; - int paletteSize = this.PaletteSize; - Span palette = this.Palette.Memory.Span; - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); - this.bitWriter.PutBits((uint)paletteSize - 1, 8); - for (int i = paletteSize - 1; i >= 1; i--) + // Fill in the different LZ77s. + foreach (CrunchConfig crunchConfig in crunchConfigs) + { + for (int j = 0; j < nlz77s; j++) { - tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); + crunchConfig.SubConfigs.Add(new CrunchSubConfig + { + Lz77 = j == 0 ? (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle : (int)Vp8LLz77Type.Lz77Box, + DoNotCache = doNotCache + }); } - - tmpPalette[0] = palette[0]; - this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20, lowEffort); - } - - /// - /// Applies the subtract green transformation to the pixel data of the image. - /// - private void ApplySubtractGreen() - { - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); - LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); } - private void ApplyPredictFilter(int width, int height, bool lowEffort) - { - // We disable near-lossless quantization if palette is used. - int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; - int predBits = this.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, predBits); - int transformHeight = LosslessUtils.SubSampleSize(height, predBits); - - PredictorEncoder.ResidualImage( - width, - height, - predBits, - this.EncodedData.GetSpan(), - this.BgraScratch.GetSpan(), - this.TransformData.GetSpan(), - this.histoArgb, - this.bestHisto, - this.nearLossless, - nearLosslessStrength, - this.transparentColorMode, - this.UseSubtractGreenTransform, - lowEffort); - - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); - this.bitWriter.PutBits((uint)(predBits - 2), 3); - - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); - } + return crunchConfigs.ToArray(); + } - private void ApplyCrossColorFilter(int width, int height, bool lowEffort) + private void EncodeImage(int width, int height, bool useCache, CrunchConfig config, int cacheBits, bool lowEffort) + { + // bgra data with transformations applied. + Span bgra = this.EncodedData.GetSpan(); + int histogramImageXySize = LosslessUtils.SubSampleSize(width, this.HistoBits) * LosslessUtils.SubSampleSize(height, this.HistoBits); + ushort[] histogramSymbols = new ushort[histogramImageXySize]; + HuffmanTree[] huffTree = new HuffmanTree[3 * WebpConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) { - int colorTransformBits = this.TransformBits; - int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); - - PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch); - - this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); - this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); - this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); - - this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); + huffTree[i] = default; } - private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality, bool lowEffort) + if (useCache) { - int cacheBits = 0; - ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. - - HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[5]; - for (int i = 0; i < huffmanCodes.Length; i++) + if (cacheBits == 0) { - huffmanCodes[i] = default; - } - - HuffmanTree[] huffTree = new HuffmanTree[3UL * WebpConstants.CodeLengthCodes]; - for (int i = 0; i < huffTree.Length; i++) - { - huffTree[i] = default; + cacheBits = WebpConstants.MaxColorCacheBits; } + } + else + { + cacheBits = 0; + } - // Calculate backward references from the image pixels. - hashChain.Fill(bgra, quality, width, height, lowEffort); + // Calculate backward references from BGRA image. + this.HashChain.Fill(bgra, this.quality, width, height, lowEffort); - Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( + Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; + Vp8LBitWriter bwInit = this.bitWriter; + bool isFirstIteration = true; + foreach (CrunchSubConfig subConfig in config.SubConfigs) + { + Vp8LBackwardRefs refsBest = BackwardReferenceEncoder.GetBackwardReferences( width, height, bgra, - quality, - (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, + this.quality, + subConfig.Lz77, ref cacheBits, this.memoryAllocator, - hashChain, - refsTmp1, - refsTmp2); + this.HashChain, + this.Refs[0], + this.Refs[1]); + + // Keep the best references aside and use the other element from the first + // two as a temporary for later usage. + Vp8LBackwardRefs refsTmp = this.Refs[refsBest.Equals(this.Refs[0]) ? 1 : 0]; - List histogramImage = new() + this.bitWriter.Reset(bwInit); + Vp8LHistogram tmpHisto = new(cacheBits); + List histogramImage = new(histogramImageXySize); + for (int i = 0; i < histogramImageXySize; i++) { - new(cacheBits) - }; + histogramImage.Add(new Vp8LHistogram(cacheBits)); + } // Build histogram image and symbols from backward references. - histogramImage[0].StoreRefs(refs); + HistogramEncoder.GetHistoImageSymbols(width, height, refsBest, this.quality, this.HistoBits, cacheBits, histogramImage, tmpHisto, histogramSymbols); // Create Huffman bit lengths and codes for each histogram image. + int histogramImageSize = histogramImage.Count; + int bitArraySize = 5 * histogramImageSize; + HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[bitArraySize]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = default; + } + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); - // No color cache, no Huffman image. - this.bitWriter.PutBits(0, 1); + // Color Cache parameters. + if (cacheBits > 0) + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)cacheBits, 4); + } + else + { + this.bitWriter.PutBits(0, 1); + } + + // Huffman image + meta huffman. + bool writeHistogramImage = histogramImageSize > 1; + this.bitWriter.PutBits((uint)(writeHistogramImage ? 1 : 0), 1); + if (writeHistogramImage) + { + using IMemoryOwner histogramBgraBuffer = this.memoryAllocator.Allocate(histogramImageXySize); + Span histogramBgra = histogramBgraBuffer.GetSpan(); + int maxIndex = 0; + for (int i = 0; i < histogramImageXySize; i++) + { + int symbolIndex = histogramSymbols[i] & 0xffff; + histogramBgra[i] = (uint)(symbolIndex << 8); + if (symbolIndex >= maxIndex) + { + maxIndex = symbolIndex + 1; + } + } + + this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3); + this.EncodeImageNoHuffman( + histogramBgra, + this.HashChain, + refsTmp, + this.Refs[2], + LosslessUtils.SubSampleSize(width, this.HistoBits), + LosslessUtils.SubSampleSize(height, this.HistoBits), + this.quality, + lowEffort); + } + // Store Huffman codes. // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -825,8 +659,7 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L tokens[i] = new HuffmanTreeToken(); } - // Store Huffman codes. - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5 * histogramImage.Count; i++) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); @@ -834,622 +667,664 @@ private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8L } // Store actual literals. - this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); + this.StoreImageToBitMask(width, this.HistoBits, refsBest, histogramSymbols, huffmanCodes); + + // Keep track of the smallest image so far. + if (isFirstIteration || (bitWriterBest != null && this.bitWriter.NumBytes() < bitWriterBest.NumBytes())) + { + Vp8LBitWriter tmp = this.bitWriter; + this.bitWriter = bitWriterBest; + bitWriterBest = tmp; + } + + isFirstIteration = false; } - private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + this.bitWriter = bitWriterBest; + } + + /// + /// Save the palette to the bitstream. + /// + private void EncodePalette(bool lowEffort) + { + Span tmpPalette = new uint[WebpConstants.MaxPaletteSize]; + int paletteSize = this.PaletteSize; + Span palette = this.Palette.Memory.Span; + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.ColorIndexingTransform, 2); + this.bitWriter.PutBits((uint)paletteSize - 1, 8); + for (int i = paletteSize - 1; i >= 1; i--) { - int count = 0; - Span symbols = this.scratch.AsSpan(0, 2); - symbols.Clear(); - const int maxBits = 8; - const int maxSymbol = 1 << maxBits; + tmpPalette[i] = LosslessUtils.SubPixels(palette[i], palette[i - 1]); + } - // Check whether it's a small tree. - for (int i = 0; i < huffmanCode.NumSymbols && count < 3; i++) - { - if (huffmanCode.CodeLengths[i] != 0) - { - if (count < 2) - { - symbols[count] = i; - } + tmpPalette[0] = palette[0]; + this.EncodeImageNoHuffman(tmpPalette, this.HashChain, this.Refs[0], this.Refs[1], width: paletteSize, height: 1, quality: 20, lowEffort); + } - count++; - } - } + /// + /// Applies the subtract green transformation to the pixel data of the image. + /// + private void ApplySubtractGreen() + { + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.SubtractGreen, 2); + LosslessUtils.SubtractGreenFromBlueAndRed(this.EncodedData.GetSpan()); + } - if (count == 0) - { - // Emit minimal tree for empty cases. - // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 - this.bitWriter.PutBits(0x01, 4); - } - else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) - { - this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. - this.bitWriter.PutBits((uint)(count - 1), 1); - if (symbols[0] <= 1) - { - this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. - this.bitWriter.PutBits((uint)symbols[0], 1); - } - else - { - this.bitWriter.PutBits(1, 1); - this.bitWriter.PutBits((uint)symbols[0], 8); - } + private void ApplyPredictFilter(int width, int height, bool lowEffort) + { + // We disable near-lossless quantization if palette is used. + int nearLosslessStrength = this.UsePalette ? 100 : this.nearLosslessQuality; + int predBits = this.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, predBits); + int transformHeight = LosslessUtils.SubSampleSize(height, predBits); + + PredictorEncoder.ResidualImage( + width, + height, + predBits, + this.EncodedData.GetSpan(), + this.BgraScratch.GetSpan(), + this.TransformData.GetSpan(), + this.histoArgb, + this.bestHisto, + this.nearLossless, + nearLosslessStrength, + this.transparentColorMode, + this.UseSubtractGreenTransform, + lowEffort); + + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.PredictorTransform, 2); + this.bitWriter.PutBits((uint)(predBits - 2), 3); + + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); + } - if (count == 2) - { - this.bitWriter.PutBits((uint)symbols[1], 8); - } - } - else - { - this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); - } + private void ApplyCrossColorFilter(int width, int height, bool lowEffort) + { + int colorTransformBits = this.TransformBits; + int transformWidth = LosslessUtils.SubSampleSize(width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(height, colorTransformBits); + + PredictorEncoder.ColorSpaceTransform(width, height, colorTransformBits, this.quality, this.EncodedData.GetSpan(), this.TransformData.GetSpan(), this.scratch); + + this.bitWriter.PutBits(WebpConstants.TransformPresent, 1); + this.bitWriter.PutBits((uint)Vp8LTransformType.CrossColorTransform, 2); + this.bitWriter.PutBits((uint)(colorTransformBits - 2), 3); + + this.EncodeImageNoHuffman(this.TransformData.GetSpan(), this.HashChain, this.Refs[0], this.Refs[1], transformWidth, transformHeight, this.quality, lowEffort); + } + + private void EncodeImageNoHuffman(Span bgra, Vp8LHashChain hashChain, Vp8LBackwardRefs refsTmp1, Vp8LBackwardRefs refsTmp2, int width, int height, int quality, bool lowEffort) + { + int cacheBits = 0; + ushort[] histogramSymbols = new ushort[1]; // Only one tree, one symbol. + + HuffmanTreeCode[] huffmanCodes = new HuffmanTreeCode[5]; + for (int i = 0; i < huffmanCodes.Length; i++) + { + huffmanCodes[i] = default; } - private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + HuffmanTree[] huffTree = new HuffmanTree[3UL * WebpConstants.CodeLengthCodes]; + for (int i = 0; i < huffTree.Length; i++) { - int i; - byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; - short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; - HuffmanTreeCode huffmanCode = new() - { - NumSymbols = WebpConstants.CodeLengthCodes, - CodeLengths = codeLengthBitDepth, - Codes = codeLengthBitDepthSymbols - }; + huffTree[i] = default; + } - this.bitWriter.PutBits(0, 1); - int numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); - uint[] histogram = new uint[WebpConstants.CodeLengthCodes + 1]; - bool[] bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; - for (i = 0; i < numTokens; i++) - { - histogram[tokens[i].Code]++; - } + // Calculate backward references from the image pixels. + hashChain.Fill(bgra, quality, width, height, lowEffort); + + Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( + width, + height, + bgra, + quality, + (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, + ref cacheBits, + this.memoryAllocator, + hashChain, + refsTmp1, + refsTmp2); + + List histogramImage = new() + { + new(cacheBits) + }; - HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); - this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); - ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); + // Build histogram image and symbols from backward references. + histogramImage[0].StoreRefs(refs); - int trailingZeroBits = 0; - int trimmedLength = numTokens; - i = numTokens; - while (i-- > 0) - { - int ix = tokens[i].Code; - if (ix is 0 or 17 or 18) - { - trimmedLength--; // Discount trailing zeros. - trailingZeroBits += codeLengthBitDepth[ix]; - if (ix == 17) - { - trailingZeroBits += 3; - } - else if (ix == 18) - { - trailingZeroBits += 7; - } - } - else - { - break; - } - } + // Create Huffman bit lengths and codes for each histogram image. + GetHuffBitLengthsAndCodes(histogramImage, huffmanCodes); + + // No color cache, no Huffman image. + this.bitWriter.PutBits(0, 1); - bool writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; - int length = writeTrimmedLength ? trimmedLength : numTokens; - this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); - if (writeTrimmedLength) + // Find maximum number of symbols for the huffman tree-set. + int maxTokens = 0; + for (int i = 0; i < 5; i++) + { + HuffmanTreeCode codes = huffmanCodes[i]; + if (maxTokens < codes.NumSymbols) { - if (trimmedLength == 2) - { - this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmedLength=2 - } - else - { - int nBits = BitOperations.Log2((uint)trimmedLength - 2); - int nBitPairs = (nBits / 2) + 1; - this.bitWriter.PutBits((uint)nBitPairs - 1, 3); - this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); - } + maxTokens = codes.NumSymbols; } + } - this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); + HuffmanTreeToken[] tokens = new HuffmanTreeToken[maxTokens]; + for (int i = 0; i < tokens.Length; i++) + { + tokens[i] = new HuffmanTreeToken(); } - private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) + // Store Huffman codes. + for (int i = 0; i < 5; i++) { - for (int i = 0; i < numTokens; i++) + HuffmanTreeCode codes = huffmanCodes[i]; + this.StoreHuffmanCode(huffTree, tokens, codes); + ClearHuffmanTreeIfOnlyOneSymbol(codes); + } + + // Store actual literals. + this.StoreImageToBitMask(width, 0, refs, histogramSymbols, huffmanCodes); + } + + private void StoreHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode huffmanCode) + { + int count = 0; + Span symbols = this.scratch.AsSpan(0, 2); + symbols.Clear(); + const int maxBits = 8; + const int maxSymbol = 1 << maxBits; + + // Check whether it's a small tree. + for (int i = 0; i < huffmanCode.NumSymbols && count < 3; i++) + { + if (huffmanCode.CodeLengths[i] != 0) { - int ix = tokens[i].Code; - int extraBits = tokens[i].ExtraBits; - this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); - switch (ix) + if (count < 2) { - case 16: - this.bitWriter.PutBits((uint)extraBits, 2); - break; - case 17: - this.bitWriter.PutBits((uint)extraBits, 3); - break; - case 18: - this.bitWriter.PutBits((uint)extraBits, 7); - break; + symbols[count] = i; } + + count++; } } - private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) + if (count == 0) { - // Throw away trailing zeros: - int codesToStore = WebpConstants.CodeLengthCodes; - for (; codesToStore > 4; codesToStore--) + // Emit minimal tree for empty cases. + // bits: small tree marker: 1, count-1: 0, large 8-bit code: 0, code: 0 + this.bitWriter.PutBits(0x01, 4); + } + else if (count <= 2 && symbols[0] < maxSymbol && symbols[1] < maxSymbol) + { + this.bitWriter.PutBits(1, 1); // Small tree marker to encode 1 or 2 symbols. + this.bitWriter.PutBits((uint)(count - 1), 1); + if (symbols[0] <= 1) { - if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0) - { - break; - } + this.bitWriter.PutBits(0, 1); // Code bit for small (1 bit) symbol value. + this.bitWriter.PutBits((uint)symbols[0], 1); + } + else + { + this.bitWriter.PutBits(1, 1); + this.bitWriter.PutBits((uint)symbols[0], 8); } - this.bitWriter.PutBits((uint)codesToStore - 4, 4); - for (int i = 0; i < codesToStore; i++) + if (count == 2) { - this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3); + this.bitWriter.PutBits((uint)symbols[1], 8); } } + else + { + this.StoreFullHuffmanCode(huffTree, tokens, huffmanCode); + } + } - private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + private void StoreFullHuffmanCode(HuffmanTree[] huffTree, HuffmanTreeToken[] tokens, HuffmanTreeCode tree) + { + int i; + byte[] codeLengthBitDepth = new byte[WebpConstants.CodeLengthCodes]; + short[] codeLengthBitDepthSymbols = new short[WebpConstants.CodeLengthCodes]; + HuffmanTreeCode huffmanCode = new() + { + NumSymbols = WebpConstants.CodeLengthCodes, + CodeLengths = codeLengthBitDepth, + Codes = codeLengthBitDepthSymbols + }; + + this.bitWriter.PutBits(0, 1); + int numTokens = HuffmanUtils.CreateCompressedHuffmanTree(tree, tokens); + uint[] histogram = new uint[WebpConstants.CodeLengthCodes + 1]; + bool[] bufRle = new bool[WebpConstants.CodeLengthCodes + 1]; + for (i = 0; i < numTokens; i++) { - int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; - int tileMask = histoBits == 0 ? 0 : -(1 << histoBits); + histogram[tokens[i].Code]++; + } - // x and y trace the position in the image. - int x = 0; - int y = 0; - int tileX = x & tileMask; - int tileY = y & tileMask; - int histogramIx = histogramSymbols[0]; - Span codes = huffmanCodes.AsSpan(5 * histogramIx); - using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); - while (c.MoveNext()) - { - PixOrCopy v = c.Current; - if (tileX != (x & tileMask) || tileY != (y & tileMask)) - { - tileX = x & tileMask; - tileY = y & tileMask; - histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; - codes = huffmanCodes.AsSpan(5 * histogramIx); - } + HuffmanUtils.CreateHuffmanTree(histogram, 7, bufRle, huffTree, huffmanCode); + this.StoreHuffmanTreeOfHuffmanTreeToBitMask(codeLengthBitDepth); + ClearHuffmanTreeIfOnlyOneSymbol(huffmanCode); - if (v.IsLiteral()) - { - for (int k = 0; k < 4; k++) - { - int code = (int)v.Literal(Order[k]); - this.bitWriter.WriteHuffmanCode(codes[k], code); - } - } - else if (v.IsCacheIdx()) - { - int code = (int)v.CacheIdx(); - int literalIx = 256 + WebpConstants.NumLengthCodes + code; - this.bitWriter.WriteHuffmanCode(codes[0], literalIx); - } - else + int trailingZeroBits = 0; + int trimmedLength = numTokens; + i = numTokens; + while (i-- > 0) + { + int ix = tokens[i].Code; + if (ix is 0 or 17 or 18) + { + trimmedLength--; // Discount trailing zeros. + trailingZeroBits += codeLengthBitDepth[ix]; + if (ix == 17) { - int bits = 0; - int nBits = 0; - int distance = (int)v.Distance(); - int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); - - // Don't write the distance with the extra bits code since - // the distance can be up to 18 bits of extra bits, and the prefix - // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. - code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); - this.bitWriter.WriteHuffmanCode(codes[4], code); - this.bitWriter.PutBits((uint)bits, nBits); + trailingZeroBits += 3; } - - x += v.Length(); - while (x >= width) + else if (ix == 18) { - x -= width; - y++; + trailingZeroBits += 7; } } + else + { + break; + } } - /// - /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. - /// - /// The image to analyze as a bgra span. - /// The image width. - /// The image height. - /// Indicates whether a palette should be used. - /// The palette size. - /// The transformation bits. - /// Indicates if red and blue are always zero. - /// The entropy mode to use. - private EntropyIx AnalyzeEntropy(ReadOnlySpan bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) - { - if (usePalette && paletteSize <= 16) + bool writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; + int length = writeTrimmedLength ? trimmedLength : numTokens; + this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); + if (writeTrimmedLength) + { + if (trimmedLength == 2) { - // In the case of small palettes, we pack 2, 4 or 8 pixels together. In - // practice, small palettes are better than any other transform. - redAndBlueAlwaysZero = true; - return EntropyIx.Palette; + this.bitWriter.PutBits(0, 3 + 2); // nbitpairs=1, trimmedLength=2 } - - using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean); - Span histo = histoBuffer.Memory.Span; - uint pixPrev = bgra[0]; // Skip the first pixel. - ReadOnlySpan prevRow = null; - for (int y = 0; y < height; y++) + else { - ReadOnlySpan currentRow = bgra.Slice(y * width, width); - for (int x = 0; x < width; x++) - { - uint pix = currentRow[x]; - uint pixDiff = LosslessUtils.SubPixels(pix, pixPrev); - pixPrev = pix; - if (pixDiff == 0 || (prevRow.Length > 0 && pix == prevRow[x])) - { - continue; - } - - AddSingle( - pix, - histo[..], - histo[((int)HistoIx.HistoRed * 256)..], - histo[((int)HistoIx.HistoGreen * 256)..], - histo[((int)HistoIx.HistoBlue * 256)..]); - AddSingle( - pixDiff, - histo[((int)HistoIx.HistoAlphaPred * 256)..], - histo[((int)HistoIx.HistoRedPred * 256)..], - histo[((int)HistoIx.HistoGreenPred * 256)..], - histo[((int)HistoIx.HistoBluePred * 256)..]); - AddSingleSubGreen( - pix, - histo[((int)HistoIx.HistoRedSubGreen * 256)..], - histo[((int)HistoIx.HistoBlueSubGreen * 256)..]); - AddSingleSubGreen( - pixDiff, - histo[((int)HistoIx.HistoRedPredSubGreen * 256)..], - histo[((int)HistoIx.HistoBluePredSubGreen * 256)..]); - - // Approximate the palette by the entropy of the multiplicative hash. - uint hash = HashPix(pix); - histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; - } - - prevRow = currentRow; + int nBits = BitOperations.Log2((uint)trimmedLength - 2); + int nBitPairs = (nBits / 2) + 1; + this.bitWriter.PutBits((uint)nBitPairs - 1, 3); + this.bitWriter.PutBits((uint)trimmedLength - 2, nBitPairs * 2); } + } + + this.StoreHuffmanTreeToBitMask(tokens, length, huffmanCode); + } - double[] entropyComp = new double[(int)HistoIx.HistoTotal]; - double[] entropy = new double[(int)EntropyIx.NumEntropyIx]; - int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; - - // Let's add one zero to the predicted histograms. The zeros are removed - // too efficiently by the pixDiff == 0 comparison, at least one of the - // zeros is likely to exist. - histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; - histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; - histo[(int)HistoIx.HistoRedPred * 256]++; - histo[(int)HistoIx.HistoGreenPred * 256]++; - histo[(int)HistoIx.HistoBluePred * 256]++; - histo[(int)HistoIx.HistoAlphaPred * 256]++; - - Vp8LBitEntropy bitEntropy = new(); - for (int j = 0; j < (int)HistoIx.HistoTotal; j++) + private void StoreHuffmanTreeToBitMask(HuffmanTreeToken[] tokens, int numTokens, HuffmanTreeCode huffmanCode) + { + for (int i = 0; i < numTokens; i++) + { + int ix = tokens[i].Code; + int extraBits = tokens[i].ExtraBits; + this.bitWriter.PutBits((uint)huffmanCode.Codes[ix], huffmanCode.CodeLengths[ix]); + switch (ix) { - bitEntropy.Init(); - Span curHisto = histo.Slice(j * 256, 256); - bitEntropy.BitsEntropyUnrefined(curHisto, 256); - entropyComp[j] = bitEntropy.BitsEntropyRefine(); + case 16: + this.bitWriter.PutBits((uint)extraBits, 2); + break; + case 17: + this.bitWriter.PutBits((uint)extraBits, 3); + break; + case 18: + this.bitWriter.PutBits((uint)extraBits, 7); + break; } + } + } - entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRed] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlue]; - entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPred] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePred]; - entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + - entropyComp[(int)HistoIx.HistoRedSubGreen] + - entropyComp[(int)HistoIx.HistoGreen] + - entropyComp[(int)HistoIx.HistoBlueSubGreen]; - entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + - entropyComp[(int)HistoIx.HistoRedPredSubGreen] + - entropyComp[(int)HistoIx.HistoGreenPred] + - entropyComp[(int)HistoIx.HistoBluePredSubGreen]; - entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; - - // When including transforms, there is an overhead in bits from - // storing them. This overhead is small but matters for small images. - // For spatial, there are 14 transformations. - entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(14); - - // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. - entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * - LosslessUtils.SubSampleSize(height, transformBits) * - LosslessUtils.FastLog2(24); - - // For palettes, add the cost of storing the palette. - // We empirically estimate the cost of a compressed entry as 8 bits. - // The palette is differential-coded when compressed hence a much - // lower cost than sizeof(uint32_t)*8. - entropy[(int)EntropyIx.Palette] += paletteSize * 8; - - EntropyIx minEntropyIx = EntropyIx.Direct; - for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) + private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) + { + // Throw away trailing zeros: + int codesToStore = WebpConstants.CodeLengthCodes; + for (; codesToStore > 4; codesToStore--) + { + if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0) { - if (entropy[(int)minEntropyIx] > entropy[k]) - { - minEntropyIx = (EntropyIx)k; - } + break; } + } - redAndBlueAlwaysZero = true; + this.bitWriter.PutBits((uint)codesToStore - 4, 4); + for (int i = 0; i < codesToStore; i++) + { + this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3); + } + } - // Let's check if the histogram of the chosen entropy mode has - // non-zero red and blue values. If all are zero, we can later skip - // the cross color optimization. - byte[][] histoPairs = + private void StoreImageToBitMask(int width, int histoBits, Vp8LBackwardRefs backwardRefs, ushort[] histogramSymbols, HuffmanTreeCode[] huffmanCodes) + { + int histoXSize = histoBits > 0 ? LosslessUtils.SubSampleSize(width, histoBits) : 1; + int tileMask = histoBits == 0 ? 0 : -(1 << histoBits); + + // x and y trace the position in the image. + int x = 0; + int y = 0; + int tileX = x & tileMask; + int tileY = y & tileMask; + int histogramIx = histogramSymbols[0]; + Span codes = huffmanCodes.AsSpan(5 * histogramIx); + using List.Enumerator c = backwardRefs.Refs.GetEnumerator(); + while (c.MoveNext()) + { + PixOrCopy v = c.Current; + if (tileX != (x & tileMask) || tileY != (y & tileMask)) { - new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, - new[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, - new[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, - new[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, - new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } - }; - Span redHisto = histo[(256 * histoPairs[(int)minEntropyIx][0])..]; - Span blueHisto = histo[(256 * histoPairs[(int)minEntropyIx][1])..]; - for (int i = 1; i < 256; i++) + tileX = x & tileMask; + tileY = y & tileMask; + histogramIx = histogramSymbols[((y >> histoBits) * histoXSize) + (x >> histoBits)]; + codes = huffmanCodes.AsSpan(5 * histogramIx); + } + + if (v.IsLiteral()) { - if ((redHisto[i] | blueHisto[i]) != 0) + for (int k = 0; k < 4; k++) { - redAndBlueAlwaysZero = false; - break; + int code = (int)v.Literal(Order[k]); + this.bitWriter.WriteHuffmanCode(codes[k], code); } } - - return minEntropyIx; - } - - /// - /// If number of colors in the image is less than or equal to MaxPaletteSize, - /// creates a palette and returns true, else returns false. - /// - /// The image as packed bgra values. - /// The image width. - /// The image height. - /// true, if a palette should be used. - private bool AnalyzeAndCreatePalette(ReadOnlySpan bgra, int width, int height) - { - Span palette = this.Palette.Memory.Span; - this.PaletteSize = GetColorPalette(bgra, width, height, palette); - if (this.PaletteSize > WebpConstants.MaxPaletteSize) + else if (v.IsCacheIdx()) { - this.PaletteSize = 0; - return false; + int code = (int)v.CacheIdx(); + int literalIx = 256 + WebpConstants.NumLengthCodes + code; + this.bitWriter.WriteHuffmanCode(codes[0], literalIx); + } + else + { + int bits = 0; + int nBits = 0; + int distance = (int)v.Distance(); + int code = LosslessUtils.PrefixEncode(v.Len, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCodeWithExtraBits(codes[0], 256 + code, bits, nBits); + + // Don't write the distance with the extra bits code since + // the distance can be up to 18 bits of extra bits, and the prefix + // 15 bits, totaling to 33, and our PutBits only supports up to 32 bits. + code = LosslessUtils.PrefixEncode(distance, ref nBits, ref bits); + this.bitWriter.WriteHuffmanCode(codes[4], code); + this.bitWriter.PutBits((uint)bits, nBits); } - Span paletteSlice = palette[..this.PaletteSize]; - paletteSlice.Sort(); - - if (PaletteHasNonMonotonousDeltas(palette, this.PaletteSize)) + x += v.Length(); + while (x >= width) { - GreedyMinimizeDeltas(palette, this.PaletteSize); + x -= width; + y++; } + } + } - return true; + /// + /// Analyzes the entropy of the input image to determine which transforms to use during encoding the image. + /// + /// The image to analyze as a bgra span. + /// The image width. + /// The image height. + /// Indicates whether a palette should be used. + /// The palette size. + /// The transformation bits. + /// Indicates if red and blue are always zero. + /// The entropy mode to use. + private EntropyIx AnalyzeEntropy(ReadOnlySpan bgra, int width, int height, bool usePalette, int paletteSize, int transformBits, out bool redAndBlueAlwaysZero) + { + if (usePalette && paletteSize <= 16) + { + // In the case of small palettes, we pack 2, 4 or 8 pixels together. In + // practice, small palettes are better than any other transform. + redAndBlueAlwaysZero = true; + return EntropyIx.Palette; } - /// - /// Gets the color palette. - /// - /// The image to get the palette from as packed bgra values. - /// The image width. - /// The image height. - /// The span to store the palette into. - /// The number of palette entries. - private static int GetColorPalette(ReadOnlySpan bgra, int width, int height, Span palette) - { - HashSet colors = new(); - for (int y = 0; y < height; y++) + using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean); + Span histo = histoBuffer.Memory.Span; + uint pixPrev = bgra[0]; // Skip the first pixel. + ReadOnlySpan prevRow = null; + for (int y = 0; y < height; y++) + { + ReadOnlySpan currentRow = bgra.Slice(y * width, width); + for (int x = 0; x < width; x++) { - ReadOnlySpan bgraRow = bgra.Slice(y * width, width); - for (int x = 0; x < width; x++) + uint pix = currentRow[x]; + uint pixDiff = LosslessUtils.SubPixels(pix, pixPrev); + pixPrev = pix; + if (pixDiff == 0 || (prevRow.Length > 0 && pix == prevRow[x])) { - colors.Add(bgraRow[x]); - if (colors.Count > WebpConstants.MaxPaletteSize) - { - // Exact count is not needed, because a palette will not be used then anyway. - return WebpConstants.MaxPaletteSize + 1; - } + continue; } - } - // Fill the colors into the palette. - using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); - int idx = 0; - while (colorEnumerator.MoveNext()) - { - palette[idx++] = colorEnumerator.Current; + AddSingle( + pix, + histo[..], + histo[((int)HistoIx.HistoRed * 256)..], + histo[((int)HistoIx.HistoGreen * 256)..], + histo[((int)HistoIx.HistoBlue * 256)..]); + AddSingle( + pixDiff, + histo[((int)HistoIx.HistoAlphaPred * 256)..], + histo[((int)HistoIx.HistoRedPred * 256)..], + histo[((int)HistoIx.HistoGreenPred * 256)..], + histo[((int)HistoIx.HistoBluePred * 256)..]); + AddSingleSubGreen( + pix, + histo[((int)HistoIx.HistoRedSubGreen * 256)..], + histo[((int)HistoIx.HistoBlueSubGreen * 256)..]); + AddSingleSubGreen( + pixDiff, + histo[((int)HistoIx.HistoRedPredSubGreen * 256)..], + histo[((int)HistoIx.HistoBluePredSubGreen * 256)..]); + + // Approximate the palette by the entropy of the multiplicative hash. + uint hash = HashPix(pix); + histo[((int)HistoIx.HistoPalette * 256) + (int)hash]++; } - return colors.Count; + prevRow = currentRow; } - private void MapImageFromPalette(int width, int height) + double[] entropyComp = new double[(int)HistoIx.HistoTotal]; + double[] entropy = new double[(int)EntropyIx.NumEntropyIx]; + int lastModeToAnalyze = usePalette ? (int)EntropyIx.Palette : (int)EntropyIx.SpatialSubGreen; + + // Let's add one zero to the predicted histograms. The zeros are removed + // too efficiently by the pixDiff == 0 comparison, at least one of the + // zeros is likely to exist. + histo[(int)HistoIx.HistoRedPredSubGreen * 256]++; + histo[(int)HistoIx.HistoBluePredSubGreen * 256]++; + histo[(int)HistoIx.HistoRedPred * 256]++; + histo[(int)HistoIx.HistoGreenPred * 256]++; + histo[(int)HistoIx.HistoBluePred * 256]++; + histo[(int)HistoIx.HistoAlphaPred * 256]++; + + Vp8LBitEntropy bitEntropy = new(); + for (int j = 0; j < (int)HistoIx.HistoTotal; j++) { - Span src = this.EncodedData.GetSpan(); - int srcStride = this.CurrentWidth; - Span dst = this.EncodedData.GetSpan(); // Applying the palette will be done in place. - Span palette = this.Palette.GetSpan(); - int paletteSize = this.PaletteSize; - int xBits; + bitEntropy.Init(); + Span curHisto = histo.Slice(j * 256, 256); + bitEntropy.BitsEntropyUnrefined(curHisto, 256); + entropyComp[j] = bitEntropy.BitsEntropyRefine(); + } - // Replace each input pixel by corresponding palette index. - // This is done line by line. - if (paletteSize <= 4) - { - xBits = paletteSize <= 2 ? 3 : 2; - } - else + entropy[(int)EntropyIx.Direct] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRed] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlue]; + entropy[(int)EntropyIx.Spatial] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPred] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePred]; + entropy[(int)EntropyIx.SubGreen] = entropyComp[(int)HistoIx.HistoAlpha] + + entropyComp[(int)HistoIx.HistoRedSubGreen] + + entropyComp[(int)HistoIx.HistoGreen] + + entropyComp[(int)HistoIx.HistoBlueSubGreen]; + entropy[(int)EntropyIx.SpatialSubGreen] = entropyComp[(int)HistoIx.HistoAlphaPred] + + entropyComp[(int)HistoIx.HistoRedPredSubGreen] + + entropyComp[(int)HistoIx.HistoGreenPred] + + entropyComp[(int)HistoIx.HistoBluePredSubGreen]; + entropy[(int)EntropyIx.Palette] = entropyComp[(int)HistoIx.HistoPalette]; + + // When including transforms, there is an overhead in bits from + // storing them. This overhead is small but matters for small images. + // For spatial, there are 14 transformations. + entropy[(int)EntropyIx.Spatial] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(14); + + // For color transforms: 24 as only 3 channels are considered in a ColorTransformElement. + entropy[(int)EntropyIx.SpatialSubGreen] += LosslessUtils.SubSampleSize(width, transformBits) * + LosslessUtils.SubSampleSize(height, transformBits) * + LosslessUtils.FastLog2(24); + + // For palettes, add the cost of storing the palette. + // We empirically estimate the cost of a compressed entry as 8 bits. + // The palette is differential-coded when compressed hence a much + // lower cost than sizeof(uint32_t)*8. + entropy[(int)EntropyIx.Palette] += paletteSize * 8; + + EntropyIx minEntropyIx = EntropyIx.Direct; + for (int k = (int)EntropyIx.Direct + 1; k <= lastModeToAnalyze; k++) + { + if (entropy[(int)minEntropyIx] > entropy[k]) { - xBits = paletteSize <= 16 ? 1 : 0; + minEntropyIx = (EntropyIx)k; } - - this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits); - this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits); } - /// - /// Remap bgra values in src[] to packed palettes entries in dst[] - /// using 'row' as a temporary buffer of size 'width'. - /// We assume that all src[] values have a corresponding entry in the palette. - /// Note: src[] can be the same as dst[] - /// - private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) - { - using IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); - Span tmpRow = tmpRowBuffer.GetSpan(); + redAndBlueAlwaysZero = true; - if (paletteSize < ApplyPaletteGreedyMax) + // Let's check if the histogram of the chosen entropy mode has + // non-zero red and blue values. If all are zero, we can later skip + // the cross color optimization. + byte[][] histoPairs = + { + new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue }, + new[] { (byte)HistoIx.HistoRedPred, (byte)HistoIx.HistoBluePred }, + new[] { (byte)HistoIx.HistoRedSubGreen, (byte)HistoIx.HistoBlueSubGreen }, + new[] { (byte)HistoIx.HistoRedPredSubGreen, (byte)HistoIx.HistoBluePredSubGreen }, + new[] { (byte)HistoIx.HistoRed, (byte)HistoIx.HistoBlue } + }; + Span redHisto = histo[(256 * histoPairs[(int)minEntropyIx][0])..]; + Span blueHisto = histo[(256 * histoPairs[(int)minEntropyIx][1])..]; + for (int i = 1; i < 256; i++) + { + if ((redHisto[i] | blueHisto[i]) != 0) { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - prevIdx = SearchColorGreedy(palette, pix); - prevPix = pix; - } - - tmpRow[x] = (byte)prevIdx; - } - - BundleColorMap(tmpRow, width, xBits, dst); - src = src[srcStride..]; - dst = dst[dstStride..]; - } + redAndBlueAlwaysZero = false; + break; } - else - { - uint[] buffer = new uint[PaletteInvSize]; + } - // Try to find a perfect hash function able to go from a color to an index - // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. - int i; - for (i = 0; i < 3; i++) - { - bool useLut = true; + return minEntropyIx; + } - // Set each element in buffer to max value. - buffer.AsSpan().Fill(uint.MaxValue); + /// + /// If number of colors in the image is less than or equal to MaxPaletteSize, + /// creates a palette and returns true, else returns false. + /// + /// The image as packed bgra values. + /// The image width. + /// The image height. + /// true, if a palette should be used. + private bool AnalyzeAndCreatePalette(ReadOnlySpan bgra, int width, int height) + { + Span palette = this.Palette.Memory.Span; + this.PaletteSize = GetColorPalette(bgra, width, height, palette); + if (this.PaletteSize > WebpConstants.MaxPaletteSize) + { + this.PaletteSize = 0; + return false; + } - for (int j = 0; j < paletteSize; j++) - { - uint ind = 0; - switch (i) - { - case 0: - ind = ApplyPaletteHash0(palette[j]); - break; - case 1: - ind = ApplyPaletteHash1(palette[j]); - break; - case 2: - ind = ApplyPaletteHash2(palette[j]); - break; - } - - if (buffer[ind] != uint.MaxValue) - { - useLut = false; - break; - } - else - { - buffer[ind] = (uint)j; - } - } + Span paletteSlice = palette[..this.PaletteSize]; + paletteSlice.Sort(); - if (useLut) - { - break; - } - } + if (PaletteHasNonMonotonousDeltas(palette, this.PaletteSize)) + { + GreedyMinimizeDeltas(palette, this.PaletteSize); + } - if (i is 0 or 1 or 2) - { - ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); - } - else + return true; + } + + /// + /// Gets the color palette. + /// + /// The image to get the palette from as packed bgra values. + /// The image width. + /// The image height. + /// The span to store the palette into. + /// The number of palette entries. + private static int GetColorPalette(ReadOnlySpan bgra, int width, int height, Span palette) + { + HashSet colors = new(); + for (int y = 0; y < height; y++) + { + ReadOnlySpan bgraRow = bgra.Slice(y * width, width); + for (int x = 0; x < width; x++) + { + colors.Add(bgraRow[x]); + if (colors.Count > WebpConstants.MaxPaletteSize) { - uint[] idxMap = new uint[paletteSize]; - uint[] paletteSorted = new uint[paletteSize]; - PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); - ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); + // Exact count is not needed, because a palette will not be used then anyway. + return WebpConstants.MaxPaletteSize + 1; } } } - private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) + // Fill the colors into the palette. + using HashSet.Enumerator colorEnumerator = colors.GetEnumerator(); + int idx = 0; + while (colorEnumerator.MoveNext()) { - uint prevPix = palette[0]; - uint prevIdx = 0; - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - uint pix = src[x]; - if (pix != prevPix) - { - switch (hashIdx) - { - case 0: - prevIdx = buffer[ApplyPaletteHash0(pix)]; - break; - case 1: - prevIdx = buffer[ApplyPaletteHash1(pix)]; - break; - case 2: - prevIdx = buffer[ApplyPaletteHash2(pix)]; - break; - } + palette[idx++] = colorEnumerator.Current; + } - prevPix = pix; - } + return colors.Count; + } - tmpRow[x] = (byte)prevIdx; - } + private void MapImageFromPalette(int width, int height) + { + Span src = this.EncodedData.GetSpan(); + int srcStride = this.CurrentWidth; + Span dst = this.EncodedData.GetSpan(); // Applying the palette will be done in place. + Span palette = this.Palette.GetSpan(); + int paletteSize = this.PaletteSize; + int xBits; + + // Replace each input pixel by corresponding palette index. + // This is done line by line. + if (paletteSize <= 4) + { + xBits = paletteSize <= 2 ? 3 : 2; + } + else + { + xBits = paletteSize <= 16 ? 1 : 0; + } - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + this.CurrentWidth = LosslessUtils.SubSampleSize(width, xBits); + this.ApplyPalette(src, srcStride, dst, this.CurrentWidth, palette, paletteSize, width, height, xBits); + } - src = src[srcStride..]; - dst = dst[dstStride..]; - } - } + /// + /// Remap bgra values in src[] to packed palettes entries in dst[] + /// using 'row' as a temporary buffer of size 'width'. + /// We assume that all src[] values have a corresponding entry in the palette. + /// Note: src[] can be the same as dst[] + /// + private void ApplyPalette(Span src, int srcStride, Span dst, int dstStride, Span palette, int paletteSize, int width, int height, int xBits) + { + using IMemoryOwner tmpRowBuffer = this.memoryAllocator.Allocate(width); + Span tmpRow = tmpRowBuffer.GetSpan(); - private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) + if (paletteSize < ApplyPaletteGreedyMax) { uint prevPix = palette[0]; uint prevIdx = 0; @@ -1460,384 +1335,504 @@ private static void ApplyPaletteForWithIdxMap(int width, int height, Span uint pix = src[x]; if (pix != prevPix) { - prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; + prevIdx = SearchColorGreedy(palette, pix); prevPix = pix; } tmpRow[x] = (byte)prevIdx; } - LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); - + BundleColorMap(tmpRow, width, xBits, dst); src = src[srcStride..]; dst = dst[dstStride..]; } } - - /// - /// Sort palette in increasing order and prepare an inverse mapping array. - /// - private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) + else { - palette[..numColors].CopyTo(sorted); - Array.Sort(sorted, PaletteCompareColorsForSort); - for (int i = 0; i < numColors; i++) - { - idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; - } - } + uint[] buffer = new uint[PaletteInvSize]; - private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) - { - int low = 0; - if (sorted[low] == color) + // Try to find a perfect hash function able to go from a color to an index + // within 1 << PaletteInvSize in order to build a hash map to go from color to index in palette. + int i; + for (i = 0; i < 3; i++) { - return low; // loop invariant: sorted[low] != color - } + bool useLut = true; - while (true) - { - int mid = (low + hi) >> 1; - if (sorted[mid] == color) - { - return mid; - } + // Set each element in buffer to max value. + buffer.AsSpan().Fill(uint.MaxValue); - if (sorted[mid] < color) + for (int j = 0; j < paletteSize; j++) { - low = mid; + uint ind = 0; + switch (i) + { + case 0: + ind = ApplyPaletteHash0(palette[j]); + break; + case 1: + ind = ApplyPaletteHash1(palette[j]); + break; + case 2: + ind = ApplyPaletteHash2(palette[j]); + break; + } + + if (buffer[ind] != uint.MaxValue) + { + useLut = false; + break; + } + else + { + buffer[ind] = (uint)j; + } } - else + + if (useLut) { - hi = mid; + break; } } - } - private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) - { - int count = 0; - for (int k = 0; k < huffmanCode.NumSymbols; k++) + if (i is 0 or 1 or 2) { - if (huffmanCode.CodeLengths[k] != 0) - { - count++; - if (count > 1) - { - return; - } - } + ApplyPaletteFor(width, height, palette, i, src, srcStride, dst, dstStride, tmpRow, buffer, xBits); } - - for (int k = 0; k < huffmanCode.NumSymbols; k++) + else { - huffmanCode.CodeLengths[k] = 0; - huffmanCode.Codes[k] = 0; + uint[] idxMap = new uint[paletteSize]; + uint[] paletteSorted = new uint[paletteSize]; + PrepareMapToPalette(palette, paletteSize, paletteSorted, idxMap); + ApplyPaletteForWithIdxMap(width, height, palette, src, srcStride, dst, dstStride, tmpRow, idxMap, xBits, paletteSorted, paletteSize); } } + } - /// - /// The palette has been sorted by alpha. This function checks if the other components of the palette - /// have a monotonic development with regards to position in the palette. - /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development - /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. - /// - /// The palette. - /// Number of colors in the palette. - /// True, if the palette has no monotonous deltas. - private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) - { - const uint predict = 0x000000; - byte signFound = 0x00; - for (int i = 0; i < numColors; i++) + private static void ApplyPaletteFor(int width, int height, Span palette, int hashIdx, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] buffer, int xBits) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) { - uint diff = LosslessUtils.SubPixels(palette[i], predict); - byte rd = (byte)((diff >> 16) & 0xff); - byte gd = (byte)((diff >> 8) & 0xff); - byte bd = (byte)((diff >> 0) & 0xff); - if (rd != 0x00) + uint pix = src[x]; + if (pix != prevPix) { - signFound |= (byte)(rd < 0x80 ? 1 : 2); - } + switch (hashIdx) + { + case 0: + prevIdx = buffer[ApplyPaletteHash0(pix)]; + break; + case 1: + prevIdx = buffer[ApplyPaletteHash1(pix)]; + break; + case 2: + prevIdx = buffer[ApplyPaletteHash2(pix)]; + break; + } - if (gd != 0x00) - { - signFound |= (byte)(gd < 0x80 ? 8 : 16); + prevPix = pix; } - if (bd != 0x00) - { - signFound |= (byte)(bd < 0x80 ? 64 : 128); - } + tmpRow[x] = (byte)prevIdx; } - return (signFound & (signFound << 1)) != 0; // two consequent signs. + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src[srcStride..]; + dst = dst[dstStride..]; } + } - /// - /// Find greedily always the closest color of the predicted color to minimize - /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. - /// - /// The palette. - /// The number of colors in the palette. - private static void GreedyMinimizeDeltas(Span palette, int numColors) + private static void ApplyPaletteForWithIdxMap(int width, int height, Span palette, Span src, int srcStride, Span dst, int dstStride, Span tmpRow, uint[] idxMap, int xBits, uint[] paletteSorted, int paletteSize) + { + uint prevPix = palette[0]; + uint prevIdx = 0; + for (int y = 0; y < height; y++) { - uint predict = 0x00000000; - for (int i = 0; i < numColors; i++) + for (int x = 0; x < width; x++) { - int bestIdx = i; - uint bestScore = ~0U; - for (int k = i; k < numColors; k++) + uint pix = src[x]; + if (pix != prevPix) { - uint curScore = PaletteColorDistance(palette[k], predict); - if (bestScore > curScore) - { - bestScore = curScore; - bestIdx = k; - } + prevIdx = idxMap[SearchColorNoIdx(paletteSorted, pix, paletteSize)]; + prevPix = pix; } - // Swap color(palette[bestIdx], palette[i]); - uint best = palette[bestIdx]; - palette[bestIdx] = palette[i]; - palette[i] = best; - predict = palette[i]; + tmpRow[x] = (byte)prevIdx; } + + LosslessUtils.BundleColorMap(tmpRow, width, xBits, dst); + + src = src[srcStride..]; + dst = dst[dstStride..]; } + } - private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) + /// + /// Sort palette in increasing order and prepare an inverse mapping array. + /// + private static void PrepareMapToPalette(Span palette, int numColors, uint[] sorted, uint[] idxMap) + { + palette[..numColors].CopyTo(sorted); + Array.Sort(sorted, PaletteCompareColorsForSort); + for (int i = 0; i < numColors; i++) { - int maxNumSymbols = 0; + idxMap[SearchColorNoIdx(sorted, palette[i], numColors)] = (uint)i; + } + } - // Iterate over all histograms and get the aggregate number of codes used. - for (int i = 0; i < histogramImage.Count; i++) - { - Vp8LHistogram histo = histogramImage[i]; - int startIdx = 5 * i; - for (int k = 0; k < 5; k++) - { - int numSymbols = - k == 0 ? histo.NumCodes() : - k == 4 ? WebpConstants.NumDistanceCodes : 256; - huffmanCodes[startIdx + k].NumSymbols = numSymbols; - } - } + private static int SearchColorNoIdx(uint[] sorted, uint color, int hi) + { + int low = 0; + if (sorted[low] == color) + { + return low; // loop invariant: sorted[low] != color + } - int end = 5 * histogramImage.Count; - for (int i = 0; i < end; i++) + while (true) + { + int mid = (low + hi) >> 1; + if (sorted[mid] == color) { - int bitLength = huffmanCodes[i].NumSymbols; - huffmanCodes[i].Codes = new short[bitLength]; - huffmanCodes[i].CodeLengths = new byte[bitLength]; - if (maxNumSymbols < bitLength) - { - maxNumSymbols = bitLength; - } + return mid; } - // Create Huffman trees. - bool[] bufRle = new bool[maxNumSymbols]; - HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols]; - for (int i = 0; i < huffTree.Length; i++) + if (sorted[mid] < color) { - huffTree[i] = default; + low = mid; } - - for (int i = 0; i < histogramImage.Count; i++) + else { - int codesStartIdx = 5 * i; - Vp8LHistogram histo = histogramImage[i]; - HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); - HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); - HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); - HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); - HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); + hi = mid; } } + } - /// - /// Computes a value that is related to the entropy created by the palette entry diff. - /// - /// First color. - /// Second color. - /// The color distance. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteColorDistance(uint col1, uint col2) - { - uint diff = LosslessUtils.SubPixels(col1, col2); - const uint moreWeightForRGBThanForAlpha = 9; - uint score = PaletteComponentDistance((diff >> 0) & 0xff); - score += PaletteComponentDistance((diff >> 8) & 0xff); - score += PaletteComponentDistance((diff >> 16) & 0xff); - score *= moreWeightForRGBThanForAlpha; - score += PaletteComponentDistance((diff >> 24) & 0xff); - - return score; - } - - /// - /// Calculates the huffman image bits. - /// - private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int width, int height) + private static void ClearHuffmanTreeIfOnlyOneSymbol(HuffmanTreeCode huffmanCode) + { + int count = 0; + for (int k = 0; k < huffmanCode.NumSymbols; k++) { - // Make tile size a function of encoding method (Range: 0 to 6). - int histoBits = (usePalette ? 9 : 7) - (int)method; - while (true) + if (huffmanCode.CodeLengths[k] != 0) { - int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); - if (huffImageSize <= WebpConstants.MaxHuffImageSize) + count++; + if (count > 1) { - break; + return; } - - histoBits++; } + } - return histoBits < WebpConstants.MinHuffmanBits ? WebpConstants.MinHuffmanBits : - histoBits > WebpConstants.MaxHuffmanBits ? WebpConstants.MaxHuffmanBits : histoBits; + for (int k = 0; k < huffmanCode.NumSymbols; k++) + { + huffmanCode.CodeLengths[k] = 0; + huffmanCode.Codes[k] = 0; } + } - /// - /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. - /// - private static void BundleColorMap(Span row, int width, int xBits, Span dst) + /// + /// The palette has been sorted by alpha. This function checks if the other components of the palette + /// have a monotonic development with regards to position in the palette. + /// If all have monotonic development, there is no benefit to re-organize them greedily. A monotonic development + /// would be spotted in green-only situations (like lossy alpha) or gray-scale images. + /// + /// The palette. + /// Number of colors in the palette. + /// True, if the palette has no monotonous deltas. + private static bool PaletteHasNonMonotonousDeltas(Span palette, int numColors) + { + const uint predict = 0x000000; + byte signFound = 0x00; + for (int i = 0; i < numColors; i++) { - int x; - if (xBits > 0) + uint diff = LosslessUtils.SubPixels(palette[i], predict); + byte rd = (byte)((diff >> 16) & 0xff); + byte gd = (byte)((diff >> 8) & 0xff); + byte bd = (byte)((diff >> 0) & 0xff); + if (rd != 0x00) { - int bitDepth = 1 << (3 - xBits); - int mask = (1 << xBits) - 1; - uint code = 0xff000000; - for (x = 0; x < width; x++) - { - int xSub = x & mask; - if (xSub == 0) - { - code = 0xff000000; - } + signFound |= (byte)(rd < 0x80 ? 1 : 2); + } - code |= (uint)(row[x] << (8 + (bitDepth * xSub))); - dst[x >> xBits] = code; - } + if (gd != 0x00) + { + signFound |= (byte)(gd < 0x80 ? 8 : 16); } - else + + if (bd != 0x00) + { + signFound |= (byte)(bd < 0x80 ? 64 : 128); + } + } + + return (signFound & (signFound << 1)) != 0; // two consequent signs. + } + + /// + /// Find greedily always the closest color of the predicted color to minimize + /// deltas in the palette. This reduces storage needs since the palette is stored with delta encoding. + /// + /// The palette. + /// The number of colors in the palette. + private static void GreedyMinimizeDeltas(Span palette, int numColors) + { + uint predict = 0x00000000; + for (int i = 0; i < numColors; i++) + { + int bestIdx = i; + uint bestScore = ~0U; + for (int k = i; k < numColors; k++) { - for (x = 0; x < width; x++) + uint curScore = PaletteColorDistance(palette[k], predict); + if (bestScore > curScore) { - dst[x] = (uint)(0xff000000 | (row[x] << 8)); + bestScore = curScore; + bestIdx = k; } } + + // Swap color(palette[bestIdx], palette[i]); + uint best = palette[bestIdx]; + palette[bestIdx] = palette[i]; + palette[i] = best; + predict = palette[i]; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst) + private static void GetHuffBitLengthsAndCodes(List histogramImage, HuffmanTreeCode[] huffmanCodes) + { + int maxNumSymbols = 0; + + // Iterate over all histograms and get the aggregate number of codes used. + for (int i = 0; i < histogramImage.Count; i++) { - Vp8LBitWriter tmp = src; - src = dst; - dst = tmp; + Vp8LHistogram histo = histogramImage[i]; + int startIdx = 5 * i; + for (int k = 0; k < 5; k++) + { + int numSymbols = + k == 0 ? histo.NumCodes() : + k == 4 ? WebpConstants.NumDistanceCodes : 256; + huffmanCodes[startIdx + k].NumSymbols = numSymbols; + } } - /// - /// Calculates the bits used for the transformation. - /// - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetTransformBits(WebpEncodingMethod method, int histoBits) + int end = 5 * histogramImage.Count; + for (int i = 0; i < end; i++) { - int maxTransformBits = (int)method < 4 ? 6 : method > WebpEncodingMethod.Level4 ? 4 : 5; - int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; - return res; + int bitLength = huffmanCodes[i].NumSymbols; + huffmanCodes[i].Codes = new short[bitLength]; + huffmanCodes[i].CodeLengths = new byte[bitLength]; + if (maxNumSymbols < bitLength) + { + maxNumSymbols = bitLength; + } } - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingle(uint p, Span a, Span r, Span g, Span b) + // Create Huffman trees. + bool[] bufRle = new bool[maxNumSymbols]; + HuffmanTree[] huffTree = new HuffmanTree[3 * maxNumSymbols]; + for (int i = 0; i < huffTree.Length; i++) { - a[(int)(p >> 24) & 0xff]++; - r[(int)(p >> 16) & 0xff]++; - g[(int)(p >> 8) & 0xff]++; - b[(int)(p >> 0) & 0xff]++; + huffTree[i] = default; } - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddSingleSubGreen(uint p, Span r, Span b) + for (int i = 0; i < histogramImage.Count; i++) { - int green = (int)p >> 8; // The upper bits are masked away later. - r[(int)((p >> 16) - green) & 0xff]++; - b[(int)((p >> 0) - green) & 0xff]++; + int codesStartIdx = 5 * i; + Vp8LHistogram histo = histogramImage[i]; + HuffmanUtils.CreateHuffmanTree(histo.Literal, 15, bufRle, huffTree, huffmanCodes[codesStartIdx]); + HuffmanUtils.CreateHuffmanTree(histo.Red, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 1]); + HuffmanUtils.CreateHuffmanTree(histo.Blue, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 2]); + HuffmanUtils.CreateHuffmanTree(histo.Alpha, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 3]); + HuffmanUtils.CreateHuffmanTree(histo.Distance, 15, bufRle, huffTree, huffmanCodes[codesStartIdx + 4]); } + } + + /// + /// Computes a value that is related to the entropy created by the palette entry diff. + /// + /// First color. + /// Second color. + /// The color distance. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint PaletteColorDistance(uint col1, uint col2) + { + uint diff = LosslessUtils.SubPixels(col1, col2); + const uint moreWeightForRGBThanForAlpha = 9; + uint score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= moreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + + return score; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint SearchColorGreedy(Span palette, uint color) + /// + /// Calculates the huffman image bits. + /// + private static int GetHistoBits(WebpEncodingMethod method, bool usePalette, int width, int height) + { + // Make tile size a function of encoding method (Range: 0 to 6). + int histoBits = (usePalette ? 9 : 7) - (int)method; + while (true) { - if (color == palette[0]) + int huffImageSize = LosslessUtils.SubSampleSize(width, histoBits) * LosslessUtils.SubSampleSize(height, histoBits); + if (huffImageSize <= WebpConstants.MaxHuffImageSize) { - return 0; + break; } - if (color == palette[1]) + histoBits++; + } + + return histoBits < WebpConstants.MinHuffmanBits ? WebpConstants.MinHuffmanBits : + histoBits > WebpConstants.MaxHuffmanBits ? WebpConstants.MaxHuffmanBits : histoBits; + } + + /// + /// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. + /// + private static void BundleColorMap(Span row, int width, int xBits, Span dst) + { + int x; + if (xBits > 0) + { + int bitDepth = 1 << (3 - xBits); + int mask = (1 << xBits) - 1; + uint code = 0xff000000; + for (x = 0; x < width; x++) { - return 1; - } + int xSub = x & mask; + if (xSub == 0) + { + code = 0xff000000; + } - if (color == palette[2]) + code |= (uint)(row[x] << (8 + (bitDepth * xSub))); + dst[x >> xBits] = code; + } + } + else + { + for (x = 0; x < width; x++) { - return 2; + dst[x] = (uint)(0xff000000 | (row[x] << 8)); } - - return 3; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash0(uint color) => (color >> 8) & 0xff; // Focus on the green color. - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash1(uint color) => (uint)((color & 0x00ffffffu) * 4222244071ul) >> (32 - PaletteInvSizeBits); // Forget about alpha. - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint ApplyPaletteHash2(uint color) => (uint)((color & 0x00ffffffu) * ((1ul << 31) - 1)) >> (32 - PaletteInvSizeBits); // Forget about alpha. + [MethodImpl(InliningOptions.ShortMethod)] + private static void BitWriterSwap(ref Vp8LBitWriter src, ref Vp8LBitWriter dst) + { + Vp8LBitWriter tmp = src; + src = dst; + dst = tmp; + } - // Note that masking with 0xffffffffu is for preventing an - // 'unsigned int overflow' warning. Doesn't impact the compiled code. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint HashPix(uint pix) => (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; + /// + /// Calculates the bits used for the transformation. + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetTransformBits(WebpEncodingMethod method, int histoBits) + { + int maxTransformBits = (int)method < 4 ? 6 : method > WebpEncodingMethod.Level4 ? 4 : 5; + int res = histoBits > maxTransformBits ? maxTransformBits : histoBits; + return res; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int PaletteCompareColorsForSort(uint p1, uint p2) => p1 < p2 ? -1 : 1; + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingle(uint p, Span a, Span r, Span g, Span b) + { + a[(int)(p >> 24) & 0xff]++; + r[(int)(p >> 16) & 0xff]++; + g[(int)(p >> 8) & 0xff]++; + b[(int)(p >> 0) & 0xff]++; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint PaletteComponentDistance(uint v) => (v <= 128) ? v : (256 - v); + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddSingleSubGreen(uint p, Span r, Span b) + { + int green = (int)p >> 8; // The upper bits are masked away later. + r[(int)((p >> 16) - green) & 0xff]++; + b[(int)((p >> 0) - green) & 0xff]++; + } - public void AllocateTransformBuffer(int width, int height) + [MethodImpl(InliningOptions.ShortMethod)] + private static uint SearchColorGreedy(Span palette, uint color) + { + if (color == palette[0]) { - // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra - // pixel in each, plus 2 regular scanlines of bytes. - int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; - int transformDataSize = this.UsePredictorTransform || this.UseCrossColorTransform ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; + return 0; + } - this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize); - this.TransformData = this.memoryAllocator.Allocate(transformDataSize); - this.CurrentWidth = width; + if (color == palette[1]) + { + return 1; } - /// - /// Clears the backward references. - /// - public void ClearRefs() + if (color == palette[2]) { - for (int i = 0; i < this.Refs.Length; i++) - { - this.Refs[i].Refs.Clear(); - } + return 2; } - /// - public void Dispose() + return 3; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash0(uint color) => (color >> 8) & 0xff; // Focus on the green color. + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash1(uint color) => (uint)((color & 0x00ffffffu) * 4222244071ul) >> (32 - PaletteInvSizeBits); // Forget about alpha. + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint ApplyPaletteHash2(uint color) => (uint)((color & 0x00ffffffu) * ((1ul << 31) - 1)) >> (32 - PaletteInvSizeBits); // Forget about alpha. + + // Note that masking with 0xffffffffu is for preventing an + // 'unsigned int overflow' warning. Doesn't impact the compiled code. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint HashPix(uint pix) => (uint)((((long)pix + (pix >> 19)) * 0x39c5fba7L) & 0xffffffffu) >> 24; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int PaletteCompareColorsForSort(uint p1, uint p2) => p1 < p2 ? -1 : 1; + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint PaletteComponentDistance(uint v) => (v <= 128) ? v : (256 - v); + + public void AllocateTransformBuffer(int width, int height) + { + // VP8LResidualImage needs room for 2 scanlines of uint32 pixels with an extra + // pixel in each, plus 2 regular scanlines of bytes. + int bgraScratchSize = this.UsePredictorTransform ? ((width + 1) * 2) + (((width * 2) + 4 - 1) / 4) : 0; + int transformDataSize = this.UsePredictorTransform || this.UseCrossColorTransform ? LosslessUtils.SubSampleSize(width, this.TransformBits) * LosslessUtils.SubSampleSize(height, this.TransformBits) : 0; + + this.BgraScratch = this.memoryAllocator.Allocate(bgraScratchSize); + this.TransformData = this.memoryAllocator.Allocate(transformDataSize); + this.CurrentWidth = width; + } + + /// + /// Clears the backward references. + /// + public void ClearRefs() + { + for (int i = 0; i < this.Refs.Length; i++) { - this.Bgra.Dispose(); - this.EncodedData.Dispose(); - this.BgraScratch.Dispose(); - this.Palette.Dispose(); - this.TransformData.Dispose(); - this.HashChain.Dispose(); + this.Refs[i].Refs.Clear(); } } + + /// + public void Dispose() + { + this.Bgra.Dispose(); + this.EncodedData.Dispose(); + this.BgraScratch.Dispose(); + this.Palette.Dispose(); + this.TransformData.Dispose(); + this.HashChain.Dispose(); + } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs index a496719179..527242906b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs @@ -1,292 +1,290 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal sealed class Vp8LHashChain : IDisposable { - internal sealed class Vp8LHashChain : IDisposable - { - private const uint HashMultiplierHi = 0xc6a4a793u; + private const uint HashMultiplierHi = 0xc6a4a793u; - private const uint HashMultiplierLo = 0x5bd1e996u; + private const uint HashMultiplierLo = 0x5bd1e996u; - private const int HashBits = 18; + private const int HashBits = 18; - private const int HashSize = 1 << HashBits; + private const int HashSize = 1 << HashBits; - /// - /// The number of bits for the window size. - /// - private const int WindowSizeBits = 20; + /// + /// The number of bits for the window size. + /// + private const int WindowSizeBits = 20; - /// - /// 1M window (4M bytes) minus 120 special codes for short distances. - /// - private const int WindowSize = (1 << WindowSizeBits) - 120; + /// + /// 1M window (4M bytes) minus 120 special codes for short distances. + /// + private const int WindowSize = (1 << WindowSizeBits) - 120; - private readonly MemoryAllocator memoryAllocator; + private readonly MemoryAllocator memoryAllocator; - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The size off the chain. - public Vp8LHashChain(MemoryAllocator memoryAllocator, int size) - { - this.memoryAllocator = memoryAllocator; - this.OffsetLength = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); - this.Size = size; - } + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The size off the chain. + public Vp8LHashChain(MemoryAllocator memoryAllocator, int size) + { + this.memoryAllocator = memoryAllocator; + this.OffsetLength = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); + this.Size = size; + } - /// - /// Gets the offset length. - /// The 20 most significant bits contain the offset at which the best match is found. - /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). - /// The lower 12 bits contain the length of the match. - /// - public IMemoryOwner OffsetLength { get; } - - /// - /// Gets the size of the hash chain. - /// This is the maximum size of the hashchain that can be constructed. - /// Typically this is the pixel count (width x height) for a given image. - /// - public int Size { get; } - - public void Fill(ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) - { - int size = xSize * ySize; - int iterMax = GetMaxItersForQuality(quality); - int windowSize = GetWindowSizeForHashChain(quality, xSize); - int pos; + /// + /// Gets the offset length. + /// The 20 most significant bits contain the offset at which the best match is found. + /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). + /// The lower 12 bits contain the length of the match. + /// + public IMemoryOwner OffsetLength { get; } + + /// + /// Gets the size of the hash chain. + /// This is the maximum size of the hashchain that can be constructed. + /// Typically this is the pixel count (width x height) for a given image. + /// + public int Size { get; } + + public void Fill(ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) + { + int size = xSize * ySize; + int iterMax = GetMaxItersForQuality(quality); + int windowSize = GetWindowSizeForHashChain(quality, xSize); + int pos; - if (size <= 2) - { - this.OffsetLength.GetSpan()[0] = 0; - return; - } + if (size <= 2) + { + this.OffsetLength.GetSpan()[0] = 0; + return; + } - using IMemoryOwner hashToFirstIndexBuffer = this.memoryAllocator.Allocate(HashSize); - using IMemoryOwner chainBuffer = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); - Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); - Span chain = chainBuffer.GetSpan(); + using IMemoryOwner hashToFirstIndexBuffer = this.memoryAllocator.Allocate(HashSize); + using IMemoryOwner chainBuffer = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); + Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); + Span chain = chainBuffer.GetSpan(); - // Initialize hashToFirstIndex array to -1. - hashToFirstIndex.Fill(-1); + // Initialize hashToFirstIndex array to -1. + hashToFirstIndex.Fill(-1); - // Fill the chain linking pixels with the same hash. - bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; - Span tmp = stackalloc uint[2]; - for (pos = 0; pos < size - 2;) + // Fill the chain linking pixels with the same hash. + bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; + Span tmp = stackalloc uint[2]; + for (pos = 0; pos < size - 2;) + { + uint hashCode; + bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; + if (bgraComp && bgraCompNext) { - uint hashCode; - bool bgraCompNext = bgra[pos + 1] == bgra[pos + 2]; - if (bgraComp && bgraCompNext) + // Consecutive pixels with the same color will share the same hash. + // We therefore use a different hash: the color and its repetition length. + tmp.Clear(); + uint len = 1; + tmp[0] = bgra[pos]; + + // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, + // as its next pixel does not have the same color, so we just need to get to + // the last pixel equal to its follower. + while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) { - // Consecutive pixels with the same color will share the same hash. - // We therefore use a different hash: the color and its repetition length. - tmp.Clear(); - uint len = 1; - tmp[0] = bgra[pos]; - - // Figure out how far the pixels are the same. The last pixel has a different 64 bit hash, - // as its next pixel does not have the same color, so we just need to get to - // the last pixel equal to its follower. - while (pos + (int)len + 2 < size && bgra[(int)(pos + len + 2)] == bgra[pos]) - { - ++len; - } - - if (len > BackwardReferenceEncoder.MaxLength) - { - // Skip the pixels that match for distance=1 and length>MaxLength - // because they are linked to their predecessor and we automatically - // check that in the main for loop below. Skipping means setting no - // predecessor in the chain, hence -1. - pos += (int)(len - BackwardReferenceEncoder.MaxLength); - len = BackwardReferenceEncoder.MaxLength; - } - - // Process the rest of the hash chain. - while (len > 0) - { - tmp[1] = len--; - hashCode = GetPixPairHash64(tmp); - chain[pos] = hashToFirstIndex[(int)hashCode]; - hashToFirstIndex[(int)hashCode] = pos++; - } + ++len; + } - bgraComp = false; + if (len > BackwardReferenceEncoder.MaxLength) + { + // Skip the pixels that match for distance=1 and length>MaxLength + // because they are linked to their predecessor and we automatically + // check that in the main for loop below. Skipping means setting no + // predecessor in the chain, hence -1. + pos += (int)(len - BackwardReferenceEncoder.MaxLength); + len = BackwardReferenceEncoder.MaxLength; } - else + + // Process the rest of the hash chain. + while (len > 0) { - // Just move one pixel forward. - hashCode = GetPixPairHash64(bgra[pos..]); + tmp[1] = len--; + hashCode = GetPixPairHash64(tmp); chain[pos] = hashToFirstIndex[(int)hashCode]; hashToFirstIndex[(int)hashCode] = pos++; - bgraComp = bgraCompNext; } + + bgraComp = false; + } + else + { + // Just move one pixel forward. + hashCode = GetPixPairHash64(bgra[pos..]); + chain[pos] = hashToFirstIndex[(int)hashCode]; + hashToFirstIndex[(int)hashCode] = pos++; + bgraComp = bgraCompNext; } + } - // Process the penultimate pixel. - chain[pos] = hashToFirstIndex[(int)GetPixPairHash64(bgra[pos..])]; + // Process the penultimate pixel. + chain[pos] = hashToFirstIndex[(int)GetPixPairHash64(bgra[pos..])]; - // Find the best match interval at each pixel, defined by an offset to the - // pixel and a length. The right-most pixel cannot match anything to the right - // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). - Span offsetLength = this.OffsetLength.GetSpan(); - offsetLength[0] = offsetLength[size - 1] = 0; - for (int basePosition = size - 2; basePosition > 0;) + // Find the best match interval at each pixel, defined by an offset to the + // pixel and a length. The right-most pixel cannot match anything to the right + // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). + Span offsetLength = this.OffsetLength.GetSpan(); + offsetLength[0] = offsetLength[size - 1] = 0; + for (int basePosition = size - 2; basePosition > 0;) + { + int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); + int bgraStart = basePosition; + int iter = iterMax; + int bestLength = 0; + uint bestDistance = 0; + int minPos = basePosition > windowSize ? basePosition - windowSize : 0; + int lengthMax = maxLen < 256 ? maxLen : 256; + pos = chain[basePosition]; + int currLength; + + if (!lowEffort) { - int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); - int bgraStart = basePosition; - int iter = iterMax; - int bestLength = 0; - uint bestDistance = 0; - int minPos = basePosition > windowSize ? basePosition - windowSize : 0; - int lengthMax = maxLen < 256 ? maxLen : 256; - pos = chain[basePosition]; - int currLength; - - if (!lowEffort) + // Heuristic: use the comparison with the above line as an initialization. + if (basePosition >= (uint)xSize) { - // Heuristic: use the comparison with the above line as an initialization. - if (basePosition >= (uint)xSize) - { - currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - xSize)..], bgra[bgraStart..], bestLength, maxLen); - if (currLength > bestLength) - { - bestLength = currLength; - bestDistance = (uint)xSize; - } - - iter--; - } - - // Heuristic: compare to the previous pixel. - currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - 1)..], bgra[bgraStart..], bestLength, maxLen); + currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - xSize)..], bgra[bgraStart..], bestLength, maxLen); if (currLength > bestLength) { bestLength = currLength; - bestDistance = 1; + bestDistance = (uint)xSize; } iter--; + } - // Skip the for loop if we already have the maximum. - if (bestLength == BackwardReferenceEncoder.MaxLength) - { - pos = minPos - 1; - } + // Heuristic: compare to the previous pixel. + currLength = LosslessUtils.FindMatchLength(bgra[(bgraStart - 1)..], bgra[bgraStart..], bestLength, maxLen); + if (currLength > bestLength) + { + bestLength = currLength; + bestDistance = 1; } - uint bestBgra = bgra[bgraStart..][bestLength]; + iter--; - for (; pos >= minPos && (--iter > 0); pos = chain[pos]) + // Skip the for loop if we already have the maximum. + if (bestLength == BackwardReferenceEncoder.MaxLength) { - if (bgra[pos + bestLength] != bestBgra) - { - continue; - } + pos = minPos - 1; + } + } - currLength = LosslessUtils.VectorMismatch(bgra[pos..], bgra[bgraStart..], maxLen); - if (bestLength < currLength) - { - bestLength = currLength; - bestDistance = (uint)(basePosition - pos); - bestBgra = bgra[bgraStart..][bestLength]; - - // Stop if we have reached a good enough length. - if (bestLength >= lengthMax) - { - break; - } - } + uint bestBgra = bgra[bgraStart..][bestLength]; + + for (; pos >= minPos && (--iter > 0); pos = chain[pos]) + { + if (bgra[pos + bestLength] != bestBgra) + { + continue; } - // We have the best match but in case the two intervals continue matching - // to the left, we have the best matches for the left-extended pixels. - uint maxBasePosition = (uint)basePosition; - while (true) + currLength = LosslessUtils.VectorMismatch(bgra[pos..], bgra[bgraStart..], maxLen); + if (bestLength < currLength) { - offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; - --basePosition; + bestLength = currLength; + bestDistance = (uint)(basePosition - pos); + bestBgra = bgra[bgraStart..][bestLength]; - // Stop if we don't have a match or if we are out of bounds. - if (bestDistance == 0 || basePosition == 0) + // Stop if we have reached a good enough length. + if (bestLength >= lengthMax) { break; } + } + } - // Stop if we cannot extend the matching intervals to the left. - if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) - { - break; - } + // We have the best match but in case the two intervals continue matching + // to the left, we have the best matches for the left-extended pixels. + uint maxBasePosition = (uint)basePosition; + while (true) + { + offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; + --basePosition; - // Stop if we are matching at its limit because there could be a closer - // matching interval with the same maximum length. Then again, if the - // matching interval is as close as possible (best_distance == 1), we will - // never find anything better so let's continue. - if (bestLength == BackwardReferenceEncoder.MaxLength && bestDistance != 1 && basePosition + BackwardReferenceEncoder.MaxLength < maxBasePosition) - { - break; - } + // Stop if we don't have a match or if we are out of bounds. + if (bestDistance == 0 || basePosition == 0) + { + break; + } - if (bestLength < BackwardReferenceEncoder.MaxLength) - { - bestLength++; - maxBasePosition = (uint)basePosition; - } + // Stop if we cannot extend the matching intervals to the left. + if (basePosition < bestDistance || bgra[(int)(basePosition - bestDistance)] != bgra[basePosition]) + { + break; + } + + // Stop if we are matching at its limit because there could be a closer + // matching interval with the same maximum length. Then again, if the + // matching interval is as close as possible (best_distance == 1), we will + // never find anything better so let's continue. + if (bestLength == BackwardReferenceEncoder.MaxLength && bestDistance != 1 && basePosition + BackwardReferenceEncoder.MaxLength < maxBasePosition) + { + break; + } + + if (bestLength < BackwardReferenceEncoder.MaxLength) + { + bestLength++; + maxBasePosition = (uint)basePosition; } } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); + [MethodImpl(InliningOptions.ShortMethod)] + public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); - /// - /// Calculates the hash for a pixel pair. - /// - /// An Span with two pixels. - /// The hash. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetPixPairHash64(ReadOnlySpan bgra) - { - uint key = bgra[1] * HashMultiplierHi; - key += bgra[0] * HashMultiplierLo; - key >>= 32 - HashBits; - return key; - } + [MethodImpl(InliningOptions.ShortMethod)] + public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); - /// - /// Returns the maximum number of hash chain lookups to do for a - /// given compression quality. Return value in range [8, 86]. - /// - /// The quality. - /// Number of hash chain lookups. - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetMaxItersForQuality(int quality) => 8 + (quality * quality / 128); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetWindowSizeForHashChain(int quality, int xSize) - { - int maxWindowSize = quality > 75 ? WindowSize - : quality > 50 ? xSize << 8 - : quality > 25 ? xSize << 6 - : xSize << 4; + /// + /// Calculates the hash for a pixel pair. + /// + /// An Span with two pixels. + /// The hash. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GetPixPairHash64(ReadOnlySpan bgra) + { + uint key = bgra[1] * HashMultiplierHi; + key += bgra[0] * HashMultiplierLo; + key >>= 32 - HashBits; + return key; + } - return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; - } + /// + /// Returns the maximum number of hash chain lookups to do for a + /// given compression quality. Return value in range [8, 86]. + /// + /// The quality. + /// Number of hash chain lookups. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetMaxItersForQuality(int quality) => 8 + (quality * quality / 128); + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetWindowSizeForHashChain(int quality, int xSize) + { + int maxWindowSize = quality > 75 ? WindowSize + : quality > 50 ? xSize << 8 + : quality > 25 ? xSize << 6 + : xSize << 4; - /// - public void Dispose() => this.OffsetLength.Dispose(); + return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } + + /// + public void Dispose() => this.OffsetLength.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index dca239e85e..a4bb2168d2 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -1,558 +1,555 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal sealed class Vp8LHistogram : IDeepCloneable { - internal sealed class Vp8LHistogram : IDeepCloneable + private const uint NonTrivialSym = 0xffffffff; + + /// + /// Initializes a new instance of the class. + /// + /// The histogram to create an instance from. + private Vp8LHistogram(Vp8LHistogram other) + : this(other.PaletteCodeBits) { - private const uint NonTrivialSym = 0xffffffff; - - /// - /// Initializes a new instance of the class. - /// - /// The histogram to create an instance from. - private Vp8LHistogram(Vp8LHistogram other) - : this(other.PaletteCodeBits) - { - other.Red.AsSpan().CopyTo(this.Red); - other.Blue.AsSpan().CopyTo(this.Blue); - other.Alpha.AsSpan().CopyTo(this.Alpha); - other.Literal.AsSpan().CopyTo(this.Literal); - other.Distance.AsSpan().CopyTo(this.Distance); - other.IsUsed.AsSpan().CopyTo(this.IsUsed); - this.LiteralCost = other.LiteralCost; - this.RedCost = other.RedCost; - this.BlueCost = other.BlueCost; - this.BitCost = other.BitCost; - this.TrivialSymbol = other.TrivialSymbol; - this.PaletteCodeBits = other.PaletteCodeBits; - } - - /// - /// Initializes a new instance of the class. - /// - /// The backward references to initialize the histogram with. - /// The palette code bits. - public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) - : this(paletteCodeBits) => this.StoreRefs(refs); - - /// - /// Initializes a new instance of the class. - /// - /// The palette code bits. - public Vp8LHistogram(int paletteCodeBits) - { - this.PaletteCodeBits = paletteCodeBits; - this.Red = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Blue = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Alpha = new uint[WebpConstants.NumLiteralCodes + 1]; - this.Distance = new uint[WebpConstants.NumDistanceCodes]; - - int literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); - this.Literal = new uint[literalSize + 1]; - - // 5 for literal, red, blue, alpha, distance. - this.IsUsed = new bool[5]; - } - - /// - /// Gets or sets the palette code bits. - /// - public int PaletteCodeBits { get; set; } - - /// - /// Gets or sets the cached value of bit cost. - /// - public double BitCost { get; set; } - - /// - /// Gets or sets the cached value of literal entropy costs. - /// - public double LiteralCost { get; set; } - - /// - /// Gets or sets the cached value of red entropy costs. - /// - public double RedCost { get; set; } - - /// - /// Gets or sets the cached value of blue entropy costs. - /// - public double BlueCost { get; set; } - - public uint[] Red { get; } - - public uint[] Blue { get; } - - public uint[] Alpha { get; } - - public uint[] Literal { get; } - - public uint[] Distance { get; } - - public uint TrivialSymbol { get; set; } - - public bool[] IsUsed { get; } - - /// - public IDeepCloneable DeepClone() => new Vp8LHistogram(this); - - /// - /// Collect all the references into a histogram (without reset). - /// - /// The backward references. - public void StoreRefs(Vp8LBackwardRefs refs) - { - using List.Enumerator c = refs.Refs.GetEnumerator(); - while (c.MoveNext()) - { - this.AddSinglePixOrCopy(c.Current, false); - } - } + other.Red.AsSpan().CopyTo(this.Red); + other.Blue.AsSpan().CopyTo(this.Blue); + other.Alpha.AsSpan().CopyTo(this.Alpha); + other.Literal.AsSpan().CopyTo(this.Literal); + other.Distance.AsSpan().CopyTo(this.Distance); + other.IsUsed.AsSpan().CopyTo(this.IsUsed); + this.LiteralCost = other.LiteralCost; + this.RedCost = other.RedCost; + this.BlueCost = other.BlueCost; + this.BitCost = other.BitCost; + this.TrivialSymbol = other.TrivialSymbol; + this.PaletteCodeBits = other.PaletteCodeBits; + } - /// - /// Accumulate a token 'v' into a histogram. - /// - /// The token to add. - /// Indicates whether to use the distance modifier. - /// xSize is only used when useDistanceModifier is true. - public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0) - { - if (v.IsLiteral()) - { - this.Alpha[v.Literal(3)]++; - this.Red[v.Literal(2)]++; - this.Literal[v.Literal(1)]++; - this.Blue[v.Literal(0)]++; - } - else if (v.IsCacheIdx()) - { - int literalIx = (int)(WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + v.CacheIdx()); - this.Literal[literalIx]++; - } - else - { - int extraBits = 0; - int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); - this.Literal[WebpConstants.NumLiteralCodes + code]++; - if (!useDistanceModifier) - { - code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); - } - else - { - code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits); - } - - this.Distance[code]++; - } - } + /// + /// Initializes a new instance of the class. + /// + /// The backward references to initialize the histogram with. + /// The palette code bits. + public Vp8LHistogram(Vp8LBackwardRefs refs, int paletteCodeBits) + : this(paletteCodeBits) => this.StoreRefs(refs); + + /// + /// Initializes a new instance of the class. + /// + /// The palette code bits. + public Vp8LHistogram(int paletteCodeBits) + { + this.PaletteCodeBits = paletteCodeBits; + this.Red = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Blue = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Alpha = new uint[WebpConstants.NumLiteralCodes + 1]; + this.Distance = new uint[WebpConstants.NumDistanceCodes]; + + int literalSize = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (1 << WebpConstants.MaxColorCacheBits); + this.Literal = new uint[literalSize + 1]; + + // 5 for literal, red, blue, alpha, distance. + this.IsUsed = new bool[5]; + } + + /// + /// Gets or sets the palette code bits. + /// + public int PaletteCodeBits { get; set; } + + /// + /// Gets or sets the cached value of bit cost. + /// + public double BitCost { get; set; } + + /// + /// Gets or sets the cached value of literal entropy costs. + /// + public double LiteralCost { get; set; } + + /// + /// Gets or sets the cached value of red entropy costs. + /// + public double RedCost { get; set; } - public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (this.PaletteCodeBits > 0 ? 1 << this.PaletteCodeBits : 0); + /// + /// Gets or sets the cached value of blue entropy costs. + /// + public double BlueCost { get; set; } - /// - /// Estimate how many bits the combined entropy of literals and distance approximately maps to. - /// - /// Estimated bits. - public double EstimateBits(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + public uint[] Red { get; } + + public uint[] Blue { get; } + + public uint[] Alpha { get; } + + public uint[] Literal { get; } + + public uint[] Distance { get; } + + public uint TrivialSymbol { get; set; } + + public bool[] IsUsed { get; } + + /// + public IDeepCloneable DeepClone() => new Vp8LHistogram(this); + + /// + /// Collect all the references into a histogram (without reset). + /// + /// The backward references. + public void StoreRefs(Vp8LBackwardRefs refs) + { + using List.Enumerator c = refs.Refs.GetEnumerator(); + while (c.MoveNext()) { - uint notUsed = 0; - return - PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) - + PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1], stats, bitsEntropy) - + PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2], stats, bitsEntropy) - + PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3], stats, bitsEntropy) - + PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) - + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes) - + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); + this.AddSinglePixOrCopy(c.Current, false); } + } - public void UpdateHistogramCost(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + /// + /// Accumulate a token 'v' into a histogram. + /// + /// The token to add. + /// Indicates whether to use the distance modifier. + /// xSize is only used when useDistanceModifier is true. + public void AddSinglePixOrCopy(PixOrCopy v, bool useDistanceModifier, int xSize = 0) + { + if (v.IsLiteral()) { - uint alphaSym = 0, redSym = 0, blueSym = 0; - uint notUsed = 0; - - double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3], stats, bitsEntropy); - double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); - int numCodes = this.NumCodes(); - this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); - this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1], stats, bitsEntropy); - this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2], stats, bitsEntropy); - this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; - if ((alphaSym | redSym | blueSym) == NonTrivialSym) + this.Alpha[v.Literal(3)]++; + this.Red[v.Literal(2)]++; + this.Literal[v.Literal(1)]++; + this.Blue[v.Literal(0)]++; + } + else if (v.IsCacheIdx()) + { + int literalIx = (int)(WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + v.CacheIdx()); + this.Literal[literalIx]++; + } + else + { + int extraBits = 0; + int code = LosslessUtils.PrefixEncodeBits(v.Length(), ref extraBits); + this.Literal[WebpConstants.NumLiteralCodes + code]++; + if (!useDistanceModifier) { - this.TrivialSymbol = NonTrivialSym; + code = LosslessUtils.PrefixEncodeBits((int)v.Distance(), ref extraBits); } else { - this.TrivialSymbol = (alphaSym << 24) | (redSym << 16) | (blueSym << 0); + code = LosslessUtils.PrefixEncodeBits(BackwardReferenceEncoder.DistanceToPlaneCode(xSize, (int)v.Distance()), ref extraBits); } + + this.Distance[code]++; } + } - /// - /// Performs output = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing - /// to the threshold value 'costThreshold'. The score returned is - /// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. - /// Since the previous score passed is 'costThreshold', we only need to compare - /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early. - /// - public double AddEval(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold, Vp8LHistogram output) - { - double sumCost = this.BitCost + b.BitCost; - costThreshold += sumCost; - if (this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial: 0, out double cost)) - { - this.Add(b, output); - output.BitCost = cost; - output.PaletteCodeBits = this.PaletteCodeBits; - } + public int NumCodes() => WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes + (this.PaletteCodeBits > 0 ? 1 << this.PaletteCodeBits : 0); - return cost - sumCost; - } + /// + /// Estimate how many bits the combined entropy of literals and distance approximately maps to. + /// + /// Estimated bits. + public double EstimateBits(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + { + uint notUsed = 0; + return + PopulationCost(this.Literal, this.NumCodes(), ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) + + PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[1], stats, bitsEntropy) + + PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[2], stats, bitsEntropy) + + PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref notUsed, ref this.IsUsed[3], stats, bitsEntropy) + + PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) + + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes) + + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); + } - public double AddThresh(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold) + public void UpdateHistogramCost(Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy) + { + uint alphaSym = 0, redSym = 0, blueSym = 0; + uint notUsed = 0; + + double alphaCost = PopulationCost(this.Alpha, WebpConstants.NumLiteralCodes, ref alphaSym, ref this.IsUsed[3], stats, bitsEntropy); + double distanceCost = PopulationCost(this.Distance, WebpConstants.NumDistanceCodes, ref notUsed, ref this.IsUsed[4], stats, bitsEntropy) + ExtraCost(this.Distance, WebpConstants.NumDistanceCodes); + int numCodes = this.NumCodes(); + this.LiteralCost = PopulationCost(this.Literal, numCodes, ref notUsed, ref this.IsUsed[0], stats, bitsEntropy) + ExtraCost(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); + this.RedCost = PopulationCost(this.Red, WebpConstants.NumLiteralCodes, ref redSym, ref this.IsUsed[1], stats, bitsEntropy); + this.BlueCost = PopulationCost(this.Blue, WebpConstants.NumLiteralCodes, ref blueSym, ref this.IsUsed[2], stats, bitsEntropy); + this.BitCost = this.LiteralCost + this.RedCost + this.BlueCost + alphaCost + distanceCost; + if ((alphaSym | redSym | blueSym) == NonTrivialSym) + { + this.TrivialSymbol = NonTrivialSym; + } + else { - double costInitial = -this.BitCost; - this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial, out double cost); - return cost; + this.TrivialSymbol = (alphaSym << 24) | (redSym << 16) | (blueSym << 0); } + } - public void Add(Vp8LHistogram b, Vp8LHistogram output) + /// + /// Performs output = a + b, computing the cost C(a+b) - C(a) - C(b) while comparing + /// to the threshold value 'costThreshold'. The score returned is + /// Score = C(a+b) - C(a) - C(b), where C(a) + C(b) is known and fixed. + /// Since the previous score passed is 'costThreshold', we only need to compare + /// the partial cost against 'costThreshold + C(a) + C(b)' to possibly bail-out early. + /// + public double AddEval(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold, Vp8LHistogram output) + { + double sumCost = this.BitCost + b.BitCost; + costThreshold += sumCost; + if (this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial: 0, out double cost)) { - int literalSize = this.NumCodes(); + this.Add(b, output); + output.BitCost = cost; + output.PaletteCodeBits = this.PaletteCodeBits; + } - this.AddLiteral(b, output, literalSize); - this.AddRed(b, output, WebpConstants.NumLiteralCodes); - this.AddBlue(b, output, WebpConstants.NumLiteralCodes); - this.AddAlpha(b, output, WebpConstants.NumLiteralCodes); - this.AddDistance(b, output, WebpConstants.NumDistanceCodes); + return cost - sumCost; + } - for (int i = 0; i < 5; i++) - { - output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i]; - } + public double AddThresh(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitsEntropy, double costThreshold) + { + double costInitial = -this.BitCost; + this.GetCombinedHistogramEntropy(b, stats, bitsEntropy, costThreshold, costInitial, out double cost); + return cost; + } - output.TrivialSymbol = this.TrivialSymbol == b.TrivialSymbol - ? this.TrivialSymbol - : NonTrivialSym; - } + public void Add(Vp8LHistogram b, Vp8LHistogram output) + { + int literalSize = this.NumCodes(); - public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy, double costThreshold, double costInitial, out double cost) + this.AddLiteral(b, output, literalSize); + this.AddRed(b, output, WebpConstants.NumLiteralCodes); + this.AddBlue(b, output, WebpConstants.NumLiteralCodes); + this.AddAlpha(b, output, WebpConstants.NumLiteralCodes); + this.AddDistance(b, output, WebpConstants.NumDistanceCodes); + + for (int i = 0; i < 5; i++) { - bool trivialAtEnd = false; - cost = costInitial; + output.IsUsed[i] = this.IsUsed[i] | b.IsUsed[i]; + } - cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false, stats, bitEntropy); + output.TrivialSymbol = this.TrivialSymbol == b.TrivialSymbol + ? this.TrivialSymbol + : NonTrivialSym; + } - cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); + public bool GetCombinedHistogramEntropy(Vp8LHistogram b, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy, double costThreshold, double costInitial, out double cost) + { + bool trivialAtEnd = false; + cost = costInitial; - if (cost > costThreshold) - { - return false; - } + cost += GetCombinedEntropy(this.Literal, b.Literal, this.NumCodes(), this.IsUsed[0], b.IsUsed[0], false, stats, bitEntropy); - if (this.TrivialSymbol != NonTrivialSym && this.TrivialSymbol == b.TrivialSymbol) - { - // A, R and B are all 0 or 0xff. - uint colorA = (this.TrivialSymbol >> 24) & 0xff; - uint colorR = (this.TrivialSymbol >> 16) & 0xff; - uint colorB = (this.TrivialSymbol >> 0) & 0xff; - if ((colorA == 0 || colorA == 0xff) && - (colorR == 0 || colorR == 0xff) && - (colorB == 0 || colorB == 0xff)) - { - trivialAtEnd = true; - } - } + cost += ExtraCostCombined(this.Literal.AsSpan(WebpConstants.NumLiteralCodes), b.Literal.AsSpan(WebpConstants.NumLiteralCodes), WebpConstants.NumLengthCodes); - cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } + if (cost > costThreshold) + { + return false; + } - cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) + if (this.TrivialSymbol != NonTrivialSym && this.TrivialSymbol == b.TrivialSymbol) + { + // A, R and B are all 0 or 0xff. + uint colorA = (this.TrivialSymbol >> 24) & 0xff; + uint colorR = (this.TrivialSymbol >> 16) & 0xff; + uint colorB = (this.TrivialSymbol >> 0) & 0xff; + if ((colorA == 0 || colorA == 0xff) && + (colorR == 0 || colorR == 0xff) && + (colorB == 0 || colorB == 0xff)) { - return false; + trivialAtEnd = true; } + } - cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } + cost += GetCombinedEntropy(this.Red, b.Red, WebpConstants.NumLiteralCodes, this.IsUsed[1], b.IsUsed[1], trivialAtEnd, stats, bitEntropy); + if (cost > costThreshold) + { + return false; + } - cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false, stats, bitEntropy); - if (cost > costThreshold) - { - return false; - } + cost += GetCombinedEntropy(this.Blue, b.Blue, WebpConstants.NumLiteralCodes, this.IsUsed[2], b.IsUsed[2], trivialAtEnd, stats, bitEntropy); + if (cost > costThreshold) + { + return false; + } - cost += ExtraCostCombined(this.Distance, b.Distance, WebpConstants.NumDistanceCodes); - if (cost > costThreshold) - { - return false; - } + cost += GetCombinedEntropy(this.Alpha, b.Alpha, WebpConstants.NumLiteralCodes, this.IsUsed[3], b.IsUsed[3], trivialAtEnd, stats, bitEntropy); + if (cost > costThreshold) + { + return false; + } - return true; + cost += GetCombinedEntropy(this.Distance, b.Distance, WebpConstants.NumDistanceCodes, this.IsUsed[4], b.IsUsed[4], false, stats, bitEntropy); + if (cost > costThreshold) + { + return false; } - private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) + cost += ExtraCostCombined(this.Distance, b.Distance, WebpConstants.NumDistanceCodes); + if (cost > costThreshold) { - if (this.IsUsed[0]) - { - if (b.IsUsed[0]) - { - AddVector(this.Literal, b.Literal, output.Literal, literalSize); - } - else - { - this.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); - } - } - else if (b.IsUsed[0]) + return false; + } + + return true; + } + + private void AddLiteral(Vp8LHistogram b, Vp8LHistogram output, int literalSize) + { + if (this.IsUsed[0]) + { + if (b.IsUsed[0]) { - b.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + AddVector(this.Literal, b.Literal, output.Literal, literalSize); } else { - output.Literal.AsSpan(0, literalSize).Clear(); + this.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); } } + else if (b.IsUsed[0]) + { + b.Literal.AsSpan(0, literalSize).CopyTo(output.Literal); + } + else + { + output.Literal.AsSpan(0, literalSize).Clear(); + } + } - private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size) + private void AddRed(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[1]) { - if (this.IsUsed[1]) - { - if (b.IsUsed[1]) - { - AddVector(this.Red, b.Red, output.Red, size); - } - else - { - this.Red.AsSpan(0, size).CopyTo(output.Red); - } - } - else if (b.IsUsed[1]) + if (b.IsUsed[1]) { - b.Red.AsSpan(0, size).CopyTo(output.Red); + AddVector(this.Red, b.Red, output.Red, size); } else { - output.Red.AsSpan(0, size).Clear(); + this.Red.AsSpan(0, size).CopyTo(output.Red); } } + else if (b.IsUsed[1]) + { + b.Red.AsSpan(0, size).CopyTo(output.Red); + } + else + { + output.Red.AsSpan(0, size).Clear(); + } + } - private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size) + private void AddBlue(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[2]) { - if (this.IsUsed[2]) - { - if (b.IsUsed[2]) - { - AddVector(this.Blue, b.Blue, output.Blue, size); - } - else - { - this.Blue.AsSpan(0, size).CopyTo(output.Blue); - } - } - else if (b.IsUsed[2]) + if (b.IsUsed[2]) { - b.Blue.AsSpan(0, size).CopyTo(output.Blue); + AddVector(this.Blue, b.Blue, output.Blue, size); } else { - output.Blue.AsSpan(0, size).Clear(); + this.Blue.AsSpan(0, size).CopyTo(output.Blue); } } + else if (b.IsUsed[2]) + { + b.Blue.AsSpan(0, size).CopyTo(output.Blue); + } + else + { + output.Blue.AsSpan(0, size).Clear(); + } + } - private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size) + private void AddAlpha(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[3]) { - if (this.IsUsed[3]) + if (b.IsUsed[3]) { - if (b.IsUsed[3]) - { - AddVector(this.Alpha, b.Alpha, output.Alpha, size); - } - else - { - this.Alpha.AsSpan(0, size).CopyTo(output.Alpha); - } - } - else if (b.IsUsed[3]) - { - b.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + AddVector(this.Alpha, b.Alpha, output.Alpha, size); } else { - output.Alpha.AsSpan(0, size).Clear(); + this.Alpha.AsSpan(0, size).CopyTo(output.Alpha); } } + else if (b.IsUsed[3]) + { + b.Alpha.AsSpan(0, size).CopyTo(output.Alpha); + } + else + { + output.Alpha.AsSpan(0, size).Clear(); + } + } - private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size) + private void AddDistance(Vp8LHistogram b, Vp8LHistogram output, int size) + { + if (this.IsUsed[4]) { - if (this.IsUsed[4]) + if (b.IsUsed[4]) { - if (b.IsUsed[4]) - { - AddVector(this.Distance, b.Distance, output.Distance, size); - } - else - { - this.Distance.AsSpan(0, size).CopyTo(output.Distance); - } - } - else if (b.IsUsed[4]) - { - b.Distance.AsSpan(0, size).CopyTo(output.Distance); + AddVector(this.Distance, b.Distance, output.Distance, size); } else { - output.Distance.AsSpan(0, size).Clear(); + this.Distance.AsSpan(0, size).CopyTo(output.Distance); } } + else if (b.IsUsed[4]) + { + b.Distance.AsSpan(0, size).CopyTo(output.Distance); + } + else + { + output.Distance.AsSpan(0, size).Clear(); + } + } - private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + private static double GetCombinedEntropy(uint[] x, uint[] y, int length, bool isXUsed, bool isYUsed, bool trivialAtEnd, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + { + stats.Clear(); + bitEntropy.Init(); + if (trivialAtEnd) { - stats.Clear(); - bitEntropy.Init(); - if (trivialAtEnd) - { - // This configuration is due to palettization that transforms an indexed - // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. - // BitsEntropyRefine is 0 for histograms with only one non-zero value. - // Only FinalHuffmanCost needs to be evaluated. + // This configuration is due to palettization that transforms an indexed + // pixel into 0xff000000 | (pixel << 8) in BundleColorMap. + // BitsEntropyRefine is 0 for histograms with only one non-zero value. + // Only FinalHuffmanCost needs to be evaluated. - // Deal with the non-zero value at index 0 or length-1. - stats.Streaks[1][0] = 1; + // Deal with the non-zero value at index 0 or length-1. + stats.Streaks[1][0] = 1; - // Deal with the following/previous zero streak. - stats.Counts[0] = 1; - stats.Streaks[0][1] = length - 1; + // Deal with the following/previous zero streak. + stats.Counts[0] = 1; + stats.Streaks[0][1] = length - 1; - return stats.FinalHuffmanCost(); - } + return stats.FinalHuffmanCost(); + } - if (isXUsed) + if (isXUsed) + { + if (isYUsed) { - if (isYUsed) - { - bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); - } - else - { - bitEntropy.GetEntropyUnrefined(x, length, stats); - } + bitEntropy.GetCombinedEntropyUnrefined(x, y, length, stats); } else { - if (isYUsed) - { - bitEntropy.GetEntropyUnrefined(y, length, stats); - } - else - { - stats.Counts[0] = 1; - stats.Streaks[0][length > 3 ? 1 : 0] = length; - bitEntropy.Init(); - } + bitEntropy.GetEntropyUnrefined(x, length, stats); } - - return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); } - - private static double ExtraCostCombined(Span x, Span y, int length) + else { - double cost = 0.0d; - for (int i = 2; i < length - 2; i++) + if (isYUsed) { - int xy = (int)(x[i + 2] + y[i + 2]); - cost += (i >> 1) * xy; + bitEntropy.GetEntropyUnrefined(y, length, stats); + } + else + { + stats.Counts[0] = 1; + stats.Streaks[0][length > 3 ? 1 : 0] = length; + bitEntropy.Init(); } - - return cost; } - /// - /// Get the symbol entropy for the distribution 'population'. - /// - private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); + } + + private static double ExtraCostCombined(Span x, Span y, int length) + { + double cost = 0.0d; + for (int i = 2; i < length - 2; i++) { - bitEntropy.Init(); - stats.Clear(); - bitEntropy.BitsEntropyUnrefined(population, length, stats); + int xy = (int)(x[i + 2] + y[i + 2]); + cost += (i >> 1) * xy; + } - trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; + return cost; + } - // The histogram is used if there is at least one non-zero streak. - isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; + /// + /// Get the symbol entropy for the distribution 'population'. + /// + private static double PopulationCost(uint[] population, int length, ref uint trivialSym, ref bool isUsed, Vp8LStreaks stats, Vp8LBitEntropy bitEntropy) + { + bitEntropy.Init(); + stats.Clear(); + bitEntropy.BitsEntropyUnrefined(population, length, stats); - return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); - } + trivialSym = (bitEntropy.NoneZeros == 1) ? bitEntropy.NoneZeroCode : NonTrivialSym; - private static double ExtraCost(Span population, int length) - { - double cost = 0.0d; - for (int i = 2; i < length - 2; i++) - { - cost += (i >> 1) * population[i + 2]; - } + // The histogram is used if there is at least one non-zero streak. + isUsed = stats.Streaks[1][0] != 0 || stats.Streaks[1][1] != 0; - return cost; + return bitEntropy.BitsEntropyRefine() + stats.FinalHuffmanCost(); + } + + private static double ExtraCost(Span population, int length) + { + double cost = 0.0d; + for (int i = 2; i < length - 2; i++) + { + cost += (i >> 1) * population[i + 2]; } - private static void AddVector(Span a, Span b, Span output, int count) + return cost; + } + + private static void AddVector(Span a, Span b, Span output, int count) + { + DebugGuard.MustBeGreaterThanOrEqualTo(a.Length, count, nameof(a.Length)); + DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); + DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); + + if (Avx2.IsSupported) { - DebugGuard.MustBeGreaterThanOrEqualTo(a.Length, count, nameof(a.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); - DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); + ref uint aRef = ref MemoryMarshal.GetReference(a); + ref uint bRef = ref MemoryMarshal.GetReference(b); + ref uint outputRef = ref MemoryMarshal.GetReference(output); + int i; - if (Avx2.IsSupported) + for (i = 0; i + 32 <= count; i += 32) { - ref uint aRef = ref MemoryMarshal.GetReference(a); - ref uint bRef = ref MemoryMarshal.GetReference(b); - ref uint outputRef = ref MemoryMarshal.GetReference(output); - int i; - - for (i = 0; i + 32 <= count; i += 32) - { - // Load values. - Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, i)); - Vector256 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 8)); - Vector256 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 16)); - Vector256 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 24)); - Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, i)); - Vector256 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 8)); - Vector256 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 16)); - Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 24)); - - // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But - // that's ok since the histogram values are less than 1<<28 (max picture count). - Unsafe.As>(ref Unsafe.Add(ref outputRef, i)) = Avx2.Add(a0, b0); - Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 8)) = Avx2.Add(a1, b1); - Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 16)) = Avx2.Add(a2, b2); - Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 24)) = Avx2.Add(a3, b3); - } - - for (; i < count; i++) - { - output[i] = a[i] + b[i]; - } + // Load values. + Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, i)); + Vector256 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 8)); + Vector256 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 16)); + Vector256 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 24)); + Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, i)); + Vector256 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 8)); + Vector256 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 16)); + Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 24)); + + // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But + // that's ok since the histogram values are less than 1<<28 (max picture count). + Unsafe.As>(ref Unsafe.Add(ref outputRef, i)) = Avx2.Add(a0, b0); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 8)) = Avx2.Add(a1, b1); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 16)) = Avx2.Add(a2, b2); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 24)) = Avx2.Add(a3, b3); } - else + + for (; i < count; i++) + { + output[i] = a[i] + b[i]; + } + } + else + { + for (int i = 0; i < count; i++) { - for (int i = 0; i < count; i++) - { - output[i] = a[i] + b[i]; - } + output[i] = a[i] + b[i]; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs index 017757a140..ca09611eff 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LLz77Type.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal enum Vp8LLz77Type { - internal enum Vp8LLz77Type - { - Lz77Standard = 1, + Lz77Standard = 1, - Lz77Rle = 2, + Lz77Rle = 2, - Lz77Box = 4 - } + Lz77Box = 4 } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs index 59ce2a9dd5..afc7d64fb7 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMetadata.cs @@ -3,26 +3,25 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal class Vp8LMetadata { - internal class Vp8LMetadata - { - public int ColorCacheSize { get; set; } + public int ColorCacheSize { get; set; } - public ColorCache ColorCache { get; set; } + public ColorCache ColorCache { get; set; } - public int HuffmanMask { get; set; } + public int HuffmanMask { get; set; } - public int HuffmanSubSampleBits { get; set; } + public int HuffmanSubSampleBits { get; set; } - public int HuffmanXSize { get; set; } + public int HuffmanXSize { get; set; } - public IMemoryOwner HuffmanImage { get; set; } + public IMemoryOwner HuffmanImage { get; set; } - public int NumHTreeGroups { get; set; } + public int NumHTreeGroups { get; set; } - public HTreeGroup[] HTreeGroups { get; set; } + public HTreeGroup[] HTreeGroups { get; set; } - public HuffmanCode[] HuffmanTables { get; set; } - } + public HuffmanCode[] HuffmanTables { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs index 02689ef439..b0a0fbf6d5 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LMultipliers.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +internal struct Vp8LMultipliers { - internal struct Vp8LMultipliers - { - public byte GreenToRed; + public byte GreenToRed; - public byte GreenToBlue; + public byte GreenToBlue; - public byte RedToBlue; - } + public byte RedToBlue; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs index 2e9432a74a..9a2021df06 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LStreaks.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +internal class Vp8LStreaks { - internal class Vp8LStreaks + /// + /// Initializes a new instance of the class. + /// + public Vp8LStreaks() { - /// - /// Initializes a new instance of the class. - /// - public Vp8LStreaks() - { - this.Counts = new int[2]; - this.Streaks = new int[2][]; - this.Streaks[0] = new int[2]; - this.Streaks[1] = new int[2]; - } + this.Counts = new int[2]; + this.Streaks = new int[2][]; + this.Streaks[0] = new int[2]; + this.Streaks[1] = new int[2]; + } - /// - /// Gets the streak count. - /// index: 0=zero streak, 1=non-zero streak. - /// - public int[] Counts { get; } + /// + /// Gets the streak count. + /// index: 0=zero streak, 1=non-zero streak. + /// + public int[] Counts { get; } - /// - /// Gets the streaks. - /// [zero/non-zero][streak < 3 / streak >= 3]. - /// - public int[][] Streaks { get; } + /// + /// Gets the streaks. + /// [zero/non-zero][streak < 3 / streak >= 3]. + /// + public int[][] Streaks { get; } - public void Clear() - { - this.Counts.AsSpan().Clear(); - this.Streaks[0].AsSpan().Clear(); - this.Streaks[1].AsSpan().Clear(); - } + public void Clear() + { + this.Counts.AsSpan().Clear(); + this.Streaks[0].AsSpan().Clear(); + this.Streaks[1].AsSpan().Clear(); + } - public double FinalHuffmanCost() - { - // The constants in this function are experimental and got rounded from - // their original values in 1/8 when switched to 1/1024. - double retval = InitialHuffmanCost(); + public double FinalHuffmanCost() + { + // The constants in this function are experimental and got rounded from + // their original values in 1/8 when switched to 1/1024. + double retval = InitialHuffmanCost(); - // Second coefficient: Many zeros in the histogram are covered efficiently - // by a run-length encode. Originally 2/8. - retval += (this.Counts[0] * 1.5625) + (0.234375 * this.Streaks[0][1]); + // Second coefficient: Many zeros in the histogram are covered efficiently + // by a run-length encode. Originally 2/8. + retval += (this.Counts[0] * 1.5625) + (0.234375 * this.Streaks[0][1]); - // Second coefficient: Constant values are encoded less efficiently, but still - // RLE'ed. Originally 6/8. - retval += (this.Counts[1] * 2.578125) + (0.703125 * this.Streaks[1][1]); + // Second coefficient: Constant values are encoded less efficiently, but still + // RLE'ed. Originally 6/8. + retval += (this.Counts[1] * 2.578125) + (0.703125 * this.Streaks[1][1]); - // 0s are usually encoded more efficiently than non-0s. - // Originally 15/8. - retval += 1.796875 * this.Streaks[0][0]; + // 0s are usually encoded more efficiently than non-0s. + // Originally 15/8. + retval += 1.796875 * this.Streaks[0][0]; - // Originally 26/8. - retval += 3.28125 * this.Streaks[1][0]; + // Originally 26/8. + retval += 3.28125 * this.Streaks[1][0]; - return retval; - } + return retval; + } - private static double InitialHuffmanCost() - { - // Small bias because Huffman code length is typically not stored in full length. - int huffmanCodeOfHuffmanCodeSize = WebpConstants.CodeLengthCodes * 3; - double smallBias = 9.1; - return huffmanCodeOfHuffmanCodeSize - smallBias; - } + private static double InitialHuffmanCost() + { + // Small bias because Huffman code length is typically not stored in full length. + int huffmanCodeOfHuffmanCodeSize = WebpConstants.CodeLengthCodes * 3; + double smallBias = 9.1; + return huffmanCodeOfHuffmanCodeSize - smallBias; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs index 381c7d07ae..daf5d65a0c 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransform.cs @@ -4,44 +4,43 @@ using System.Buffers; using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Data associated with a VP8L transformation to reduce the entropy. +/// +[DebuggerDisplay("Transformtype: {" + nameof(TransformType) + "}")] +internal class Vp8LTransform { - /// - /// Data associated with a VP8L transformation to reduce the entropy. - /// - [DebuggerDisplay("Transformtype: {" + nameof(TransformType) + "}")] - internal class Vp8LTransform + public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) { - public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize) - { - this.TransformType = transformType; - this.XSize = xSize; - this.YSize = ySize; - } - - /// - /// Gets the transform type. - /// - public Vp8LTransformType TransformType { get; } - - /// - /// Gets or sets the subsampling bits defining the transform window. - /// - public int Bits { get; set; } - - /// - /// Gets or sets the transform window X index. - /// - public int XSize { get; set; } - - /// - /// Gets the transform window Y index. - /// - public int YSize { get; } - - /// - /// Gets or sets the transform data. - /// - public IMemoryOwner Data { get; set; } + this.TransformType = transformType; + this.XSize = xSize; + this.YSize = ySize; } + + /// + /// Gets the transform type. + /// + public Vp8LTransformType TransformType { get; } + + /// + /// Gets or sets the subsampling bits defining the transform window. + /// + public int Bits { get; set; } + + /// + /// Gets or sets the transform window X index. + /// + public int XSize { get; set; } + + /// + /// Gets the transform window Y index. + /// + public int YSize { get; } + + /// + /// Gets or sets the transform data. + /// + public IMemoryOwner Data { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs index 6f64c6b631..f58463b288 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LTransformType.cs @@ -1,37 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Enum for the different transform types. Transformations are reversible manipulations of the image data +/// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. +/// Transformations can make the final compression more dense. +/// +internal enum Vp8LTransformType : uint { /// - /// Enum for the different transform types. Transformations are reversible manipulations of the image data - /// that can reduce the remaining symbolic entropy by modeling spatial and color correlations. - /// Transformations can make the final compression more dense. + /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. /// - internal enum Vp8LTransformType : uint - { - /// - /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated. - /// - PredictorTransform = 0, + PredictorTransform = 0, - /// - /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. - /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. - /// - CrossColorTransform = 1, + /// + /// The goal of the color transform is to de-correlate the R, G and B values of each pixel. + /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. + /// + CrossColorTransform = 1, - /// - /// The subtract green transform subtracts green values from red and blue values of each pixel. - /// When this transform is present, the decoder needs to add the green value to both red and blue. - /// There is no data associated with this transform. - /// - SubtractGreen = 2, + /// + /// The subtract green transform subtracts green values from red and blue values of each pixel. + /// When this transform is present, the decoder needs to add the green value to both red and blue. + /// There is no data associated with this transform. + /// + SubtractGreen = 2, - /// - /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. - /// The color indexing transform achieves this. - /// - ColorIndexingTransform = 3, - } + /// + /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices. + /// The color indexing transform achieves this. + /// + ColorIndexingTransform = 3, } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index b90dbf4dbb..dd04b2eb9a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -1,404 +1,429 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp.Lossless +namespace SixLabors.ImageSharp.Formats.Webp.Lossless; + +/// +/// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp +/// +/// +/// The lossless specification can be found here: +/// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification +/// +internal sealed class WebpLosslessDecoder { /// - /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp + /// A bit reader for reading lossless webp streams. /// - /// - /// The lossless specification can be found here: - /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification - /// - internal sealed class WebpLosslessDecoder - { - /// - /// A bit reader for reading lossless webp streams. - /// - private readonly Vp8LBitReader bitReader; + private readonly Vp8LBitReader bitReader; - /// - /// The global configuration. - /// - private readonly Configuration configuration; + /// + /// The global configuration. + /// + private readonly Configuration configuration; - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; - private const int BitsSpecialMarker = 0x100; + private const int BitsSpecialMarker = 0x100; - private const uint PackedNonLiteralCode = 0; + private const uint PackedNonLiteralCode = 0; - private static readonly int CodeToPlaneCodes = WebpLookupTables.CodeToPlane.Length; + private static readonly int CodeToPlaneCodes = WebpLookupTables.CodeToPlane.Length; - // Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha and distance alphabets are constant (256 for red, blue and alpha, 40 for - // distance) and lookup table sizes for them in worst case are 630 and 410 respectively. Size of green alphabet depends on color cache size and is equal - // to 256 (green component values) + 24 (length prefix values) + color_cache_size (between 0 and 2048). - // All values computed for 8-bit first level lookup with Mark Adler's tool: - // http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c - private const int FixedTableSize = (630 * 3) + 410; + // Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha and distance alphabets are constant (256 for red, blue and alpha, 40 for + // distance) and lookup table sizes for them in worst case are 630 and 410 respectively. Size of green alphabet depends on color cache size and is equal + // to 256 (green component values) + 24 (length prefix values) + color_cache_size (between 0 and 2048). + // All values computed for 8-bit first level lookup with Mark Adler's tool: + // http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c + private const int FixedTableSize = (630 * 3) + 410; - private static readonly int[] TableSize = - { - FixedTableSize + 654, - FixedTableSize + 656, - FixedTableSize + 658, - FixedTableSize + 662, - FixedTableSize + 670, - FixedTableSize + 686, - FixedTableSize + 718, - FixedTableSize + 782, - FixedTableSize + 912, - FixedTableSize + 1168, - FixedTableSize + 1680, - FixedTableSize + 2704 - }; - - private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; + private static readonly int[] TableSize = + { + FixedTableSize + 654, + FixedTableSize + 656, + FixedTableSize + 658, + FixedTableSize + 662, + FixedTableSize + 670, + FixedTableSize + 686, + FixedTableSize + 718, + FixedTableSize + 782, + FixedTableSize + 912, + FixedTableSize + 1168, + FixedTableSize + 1680, + FixedTableSize + 2704 + }; + + private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; - /// - /// Initializes a new instance of the class. - /// - /// Bitreader to read from the stream. - /// Used for allocating memory during processing operations. - /// The configuration. - public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) - { - this.bitReader = bitReader; - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - } + /// + /// Initializes a new instance of the class. + /// + /// Bitreader to read from the stream. + /// Used for allocating memory during processing operations. + /// The configuration. + public WebpLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) + { + this.bitReader = bitReader; + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + } - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan CodeLengthCodeOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan CodeLengthCodeOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; - /// - /// Decodes the lossless webp image from the stream. - /// - /// The pixel format. - /// The pixel buffer to store the decoded data. - /// The width of the image. - /// The height of the image. - public void Decode(Buffer2D pixels, int width, int height) - where TPixel : unmanaged, IPixel + /// + /// Decodes the lossless webp image from the stream. + /// + /// The pixel format. + /// The pixel buffer to store the decoded data. + /// The width of the image. + /// The height of the image. + public void Decode(Buffer2D pixels, int width, int height) + where TPixel : unmanaged, IPixel + { + using (Vp8LDecoder decoder = new(width, height, this.memoryAllocator)) { - using (Vp8LDecoder decoder = new(width, height, this.memoryAllocator)) - { - this.DecodeImageStream(decoder, width, height, true); - this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); - this.DecodePixelValues(decoder, pixels, width, height); - } + this.DecodeImageStream(decoder, width, height, true); + this.DecodeImageData(decoder, decoder.Pixels.Memory.Span); + this.DecodePixelValues(decoder, pixels, width, height); } + } - public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) + public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0) + { + int transformXSize = xSize; + int transformYSize = ySize; + int numberOfTransformsPresent = 0; + if (isLevel0) { - int transformXSize = xSize; - int transformYSize = ySize; - int numberOfTransformsPresent = 0; - if (isLevel0) - { - decoder.Transforms = new List(WebpConstants.MaxNumberOfTransforms); + decoder.Transforms = new List(WebpConstants.MaxNumberOfTransforms); - // Next bit indicates, if a transformation is present. - while (this.bitReader.ReadBit()) + // Next bit indicates, if a transformation is present. + while (this.bitReader.ReadBit()) + { + if (numberOfTransformsPresent > WebpConstants.MaxNumberOfTransforms) { - if (numberOfTransformsPresent > WebpConstants.MaxNumberOfTransforms) - { - WebpThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebpConstants.MaxNumberOfTransforms} was exceeded"); - } - - this.ReadTransformation(transformXSize, transformYSize, decoder); - if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform) - { - transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits); - } - - numberOfTransformsPresent++; + WebpThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebpConstants.MaxNumberOfTransforms} was exceeded"); } - } - else - { - decoder.Metadata = new Vp8LMetadata(); - } - - // Color cache. - bool isColorCachePresent = this.bitReader.ReadBit(); - int colorCacheBits = 0; - int colorCacheSize = 0; - if (isColorCachePresent) - { - colorCacheBits = (int)this.bitReader.ReadValue(4); - // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. - // That is why 11 bits is also considered valid here. - bool colorCacheBitsIsValid = colorCacheBits is >= 1 and <= WebpConstants.MaxColorCacheBits + 1; - if (!colorCacheBitsIsValid) + this.ReadTransformation(transformXSize, transformYSize, decoder); + if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform) { - WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); + transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits); } - } - - // Read the Huffman codes (may recurse). - this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0); - decoder.Metadata.ColorCacheSize = colorCacheSize; - // Finish setting up the color-cache. - if (isColorCachePresent) - { - decoder.Metadata.ColorCache = new ColorCache(); - colorCacheSize = 1 << colorCacheBits; - decoder.Metadata.ColorCacheSize = colorCacheSize; - decoder.Metadata.ColorCache.Init(colorCacheBits); - } - else - { - decoder.Metadata.ColorCacheSize = 0; + numberOfTransformsPresent++; } + } + else + { + decoder.Metadata = new Vp8LMetadata(); + } + + // Color cache. + bool isColorCachePresent = this.bitReader.ReadBit(); + int colorCacheBits = 0; + int colorCacheSize = 0; + if (isColorCachePresent) + { + colorCacheBits = (int)this.bitReader.ReadValue(4); - UpdateDecoder(decoder, transformXSize, transformYSize); - if (isLevel0) + // Note: According to webpinfo color cache bits of 11 are valid, even though 10 is defined in the source code as maximum. + // That is why 11 bits is also considered valid here. + bool colorCacheBitsIsValid = colorCacheBits is >= 1 and <= WebpConstants.MaxColorCacheBits + 1; + if (!colorCacheBitsIsValid) { - // level 0 complete. - return null; + WebpThrowHelper.ThrowImageFormatException("Invalid color cache bits found"); } + } - // Use the Huffman trees to decode the LZ77 encoded data. - IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); - this.DecodeImageData(decoder, pixelData.GetSpan()); + // Read the Huffman codes (may recurse). + this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0); + decoder.Metadata.ColorCacheSize = colorCacheSize; - return pixelData; + // Finish setting up the color-cache. + if (isColorCachePresent) + { + decoder.Metadata.ColorCache = new ColorCache(); + colorCacheSize = 1 << colorCacheBits; + decoder.Metadata.ColorCacheSize = colorCacheSize; + decoder.Metadata.ColorCache.Init(colorCacheBits); + } + else + { + decoder.Metadata.ColorCacheSize = 0; } - private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels, int width, int height) - where TPixel : unmanaged, IPixel + UpdateDecoder(decoder, transformXSize, transformYSize); + if (isLevel0) { - Span pixelData = decoder.Pixels.GetSpan(); + // level 0 complete. + return null; + } - // Apply reverse transformations, if any are present. - ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); + // Use the Huffman trees to decode the LZ77 encoded data. + IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean); + this.DecodeImageData(decoder, pixelData.GetSpan()); - Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); - int bytesPerRow = width * 4; - for (int y = 0; y < height; y++) - { - Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); - Span pixelRow = pixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgra32Bytes( - this.configuration, - rowAsBytes[..bytesPerRow], - pixelRow[..width], - width); - } + return pixelData; + } + + private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels, int width, int height) + where TPixel : unmanaged, IPixel + { + Span pixelData = decoder.Pixels.GetSpan(); + + // Apply reverse transformations, if any are present. + ApplyInverseTransforms(decoder, pixelData, this.memoryAllocator); + + Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData); + int bytesPerRow = width * 4; + for (int y = 0; y < height; y++) + { + Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); + Span pixelRow = pixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromBgra32Bytes( + this.configuration, + rowAsBytes[..bytesPerRow], + pixelRow[..width], + width); } + } - public void DecodeImageData(Vp8LDecoder decoder, Span pixelData) + public void DecodeImageData(Vp8LDecoder decoder, Span pixelData) + { + const int lastPixel = 0; + int width = decoder.Width; + int height = decoder.Height; + int row = lastPixel / width; + int col = lastPixel % width; + const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; + int colorCacheSize = decoder.Metadata.ColorCacheSize; + ColorCache colorCache = decoder.Metadata.ColorCache; + int colorCacheLimit = lenCodeLimit + colorCacheSize; + int mask = decoder.Metadata.HuffmanMask; + Span hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); + + int totalPixels = width * height; + int decodedPixels = 0; + int lastCached = decodedPixels; + while (decodedPixels < totalPixels) { - const int lastPixel = 0; - int width = decoder.Width; - int height = decoder.Height; - int row = lastPixel / width; - int col = lastPixel % width; - const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; - int colorCacheSize = decoder.Metadata.ColorCacheSize; - ColorCache colorCache = decoder.Metadata.ColorCache; - int colorCacheLimit = lenCodeLimit + colorCacheSize; - int mask = decoder.Metadata.HuffmanMask; - Span hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); + int code; + if ((col & mask) == 0) + { + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); + } - int totalPixels = width * height; - int decodedPixels = 0; - int lastCached = decodedPixels; - while (decodedPixels < totalPixels) + if (hTreeGroup[0].IsTrivialCode) { - int code; - if ((col & mask) == 0) + pixelData[decodedPixels] = hTreeGroup[0].LiteralArb; + AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); + continue; + } + + this.bitReader.FillBitWindow(); + if (hTreeGroup[0].UsePackedTable) + { + code = (int)this.ReadPackedSymbols(hTreeGroup, pixelData, decodedPixels); + if (this.bitReader.IsEndOfStream()) { - hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); + break; } - if (hTreeGroup[0].IsTrivialCode) + if (code == PackedNonLiteralCode) { - pixelData[decodedPixels] = hTreeGroup[0].LiteralArb; AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); continue; } + } + else + { + code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + } - this.bitReader.FillBitWindow(); - if (hTreeGroup[0].UsePackedTable) + if (this.bitReader.IsEndOfStream()) + { + break; + } + + // Literal + if (code < WebpConstants.NumLiteralCodes) + { + if (hTreeGroup[0].IsTrivialLiteral) + { + pixelData[decodedPixels] = hTreeGroup[0].LiteralArb | ((uint)code << 8); + } + else { - code = (int)this.ReadPackedSymbols(hTreeGroup, pixelData, decodedPixels); + uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); + this.bitReader.FillBitWindow(); + uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); + uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); if (this.bitReader.IsEndOfStream()) { break; } - if (code == PackedNonLiteralCode) - { - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); - continue; - } - } - else - { - code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]); + pixelData[decodedPixels] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); } + AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); + } + else if (code < lenCodeLimit) + { + // Backward reference is used. + int lengthSym = code - WebpConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance((int)distSymbol); + int dist = PlaneCodeToDistance(width, distCode); if (this.bitReader.IsEndOfStream()) { break; } - // Literal - if (code < WebpConstants.NumLiteralCodes) + CopyBlock(pixelData, decodedPixels, dist, length); + decodedPixels += length; + col += length; + while (col >= width) { - if (hTreeGroup[0].IsTrivialLiteral) - { - pixelData[decodedPixels] = hTreeGroup[0].LiteralArb | ((uint)code << 8); - } - else - { - uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]); - this.bitReader.FillBitWindow(); - uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]); - uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]); - if (this.bitReader.IsEndOfStream()) - { - break; - } - - pixelData[decodedPixels] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue); - } - - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); + col -= width; + row++; } - else if (code < lenCodeLimit) - { - // Backward reference is used. - int lengthSym = code - WebpConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); - uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]); - this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance((int)distSymbol); - int dist = PlaneCodeToDistance(width, distCode); - if (this.bitReader.IsEndOfStream()) - { - break; - } - - CopyBlock(pixelData, decodedPixels, dist, length); - decodedPixels += length; - col += length; - while (col >= width) - { - col -= width; - row++; - } - - if ((col & mask) != 0) - { - hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); - } - if (colorCache != null) - { - while (lastCached < decodedPixels) - { - colorCache.Insert(pixelData[lastCached]); - lastCached++; - } - } + if ((col & mask) != 0) + { + hTreeGroup = GetHTreeGroupForPos(decoder.Metadata, col, row); } - else if (code < colorCacheLimit) + + if (colorCache != null) { - // Color cache should be used. - int key = code - lenCodeLimit; while (lastCached < decodedPixels) { colorCache.Insert(pixelData[lastCached]); lastCached++; } - - pixelData[decodedPixels] = colorCache.Lookup(key); - AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); } - else + } + else if (code < colorCacheLimit) + { + // Color cache should be used. + int key = code - lenCodeLimit; + while (lastCached < decodedPixels) { - WebpThrowHelper.ThrowImageFormatException("Webp parsing error"); + colorCache.Insert(pixelData[lastCached]); + lastCached++; } + + pixelData[decodedPixels] = colorCache.Lookup(key); + AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached); + } + else + { + WebpThrowHelper.ThrowImageFormatException("Webp parsing error"); } } + } - private static void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) + private static void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached) + { + col++; + decodedPixels++; + if (col >= width) { - col++; - decodedPixels++; - if (col >= width) - { - col = 0; - row++; + col = 0; + row++; - if (colorCache != null) + if (colorCache != null) + { + while (lastCached < decodedPixels) { - while (lastCached < decodedPixels) - { - colorCache.Insert(pixelData[lastCached]); - lastCached++; - } + colorCache.Insert(pixelData[lastCached]); + lastCached++; } } } + } - private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion) - { - int maxAlphabetSize = 0; - int numHTreeGroups = 1; - int numHTreeGroupsMax = 1; + private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion) + { + int maxAlphabetSize = 0; + int numHTreeGroups = 1; + int numHTreeGroupsMax = 1; - // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. - // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. - if (allowRecursion && this.bitReader.ReadBit()) - { - // Use meta Huffman codes. - int huffmanPrecision = (int)(this.bitReader.ReadValue(3) + 2); - int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); - int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); - int huffmanPixels = huffmanXSize * huffmanYSize; + // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored. + // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image. + if (allowRecursion && this.bitReader.ReadBit()) + { + // Use meta Huffman codes. + int huffmanPrecision = (int)(this.bitReader.ReadValue(3) + 2); + int huffmanXSize = LosslessUtils.SubSampleSize(xSize, huffmanPrecision); + int huffmanYSize = LosslessUtils.SubSampleSize(ySize, huffmanPrecision); + int huffmanPixels = huffmanXSize * huffmanYSize; - IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); - Span huffmanImageSpan = huffmanImage.GetSpan(); - decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; + IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false); + Span huffmanImageSpan = huffmanImage.GetSpan(); + decoder.Metadata.HuffmanSubSampleBits = huffmanPrecision; - // TODO: Isn't huffmanPixels the length of the span? - for (int i = 0; i < huffmanPixels; i++) + // TODO: Isn't huffmanPixels the length of the span? + for (int i = 0; i < huffmanPixels; i++) + { + // The huffman data is stored in red and green bytes. + uint group = (huffmanImageSpan[i] >> 8) & 0xffff; + huffmanImageSpan[i] = group; + if (group >= numHTreeGroupsMax) { - // The huffman data is stored in red and green bytes. - uint group = (huffmanImageSpan[i] >> 8) & 0xffff; - huffmanImageSpan[i] = group; - if (group >= numHTreeGroupsMax) - { - numHTreeGroupsMax = (int)group + 1; - } + numHTreeGroupsMax = (int)group + 1; } + } + + numHTreeGroups = numHTreeGroupsMax; + decoder.Metadata.HuffmanImage = huffmanImage; + } - numHTreeGroups = numHTreeGroupsMax; - decoder.Metadata.HuffmanImage = huffmanImage; + // Find maximum alphabet size for the hTree group. + for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) + { + int alphabetSize = WebpConstants.AlphabetSize[j]; + if (j == 0 && colorCacheBits > 0) + { + alphabetSize += 1 << colorCacheBits; } - // Find maximum alphabet size for the hTree group. + if (maxAlphabetSize < alphabetSize) + { + maxAlphabetSize = alphabetSize; + } + } + + int tableSize = TableSize[colorCacheBits]; + HuffmanCode[] huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; + HTreeGroup[] hTreeGroups = new HTreeGroup[numHTreeGroups]; + Span huffmanTable = huffmanTables.AsSpan(); + int[] codeLengths = new int[maxAlphabetSize]; + for (int i = 0; i < numHTreeGroupsMax; i++) + { + hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); + HTreeGroup hTreeGroup = hTreeGroups[i]; + int totalSize = 0; + bool isTrivialLiteral = true; + int maxBits = 0; + codeLengths.AsSpan().Clear(); for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) { int alphabetSize = WebpConstants.AlphabetSize[j]; @@ -407,603 +432,575 @@ private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int col alphabetSize += 1 << colorCacheBits; } - if (maxAlphabetSize < alphabetSize) + int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); + if (size == 0) { - maxAlphabetSize = alphabetSize; + WebpThrowHelper.ThrowImageFormatException("Huffman table size is zero"); } - } - - int tableSize = TableSize[colorCacheBits]; - HuffmanCode[] huffmanTables = new HuffmanCode[numHTreeGroups * tableSize]; - HTreeGroup[] hTreeGroups = new HTreeGroup[numHTreeGroups]; - Span huffmanTable = huffmanTables.AsSpan(); - int[] codeLengths = new int[maxAlphabetSize]; - for (int i = 0; i < numHTreeGroupsMax; i++) - { - hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize); - HTreeGroup hTreeGroup = hTreeGroups[i]; - int totalSize = 0; - bool isTrivialLiteral = true; - int maxBits = 0; - codeLengths.AsSpan().Clear(); - for (int j = 0; j < WebpConstants.HuffmanCodesPerMetaCode; j++) - { - int alphabetSize = WebpConstants.AlphabetSize[j]; - if (j == 0 && colorCacheBits > 0) - { - alphabetSize += 1 << colorCacheBits; - } - int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable); - if (size == 0) - { - WebpThrowHelper.ThrowImageFormatException("Huffman table size is zero"); - } + // TODO: Avoid allocation. + hTreeGroup.HTrees.Add(huffmanTable[..size].ToArray()); - // TODO: Avoid allocation. - hTreeGroup.HTrees.Add(huffmanTable[..size].ToArray()); - - HuffmanCode huffTableZero = huffmanTable[0]; - if (isTrivialLiteral && LiteralMap[j] == 1) - { - isTrivialLiteral = huffTableZero.BitsUsed == 0; - } + HuffmanCode huffTableZero = huffmanTable[0]; + if (isTrivialLiteral && LiteralMap[j] == 1) + { + isTrivialLiteral = huffTableZero.BitsUsed == 0; + } - totalSize += huffTableZero.BitsUsed; - huffmanTable = huffmanTable[size..]; + totalSize += huffTableZero.BitsUsed; + huffmanTable = huffmanTable[size..]; - if (j <= HuffIndex.Alpha) + if (j <= HuffIndex.Alpha) + { + int localMaxBits = codeLengths[0]; + int k; + for (k = 1; k < alphabetSize; ++k) { - int localMaxBits = codeLengths[0]; - int k; - for (k = 1; k < alphabetSize; ++k) + int codeLengthK = codeLengths[k]; + if (codeLengthK > localMaxBits) { - int codeLengthK = codeLengths[k]; - if (codeLengthK > localMaxBits) - { - localMaxBits = codeLengthK; - } + localMaxBits = codeLengthK; } - - maxBits += localMaxBits; } - } - hTreeGroup.IsTrivialLiteral = isTrivialLiteral; - hTreeGroup.IsTrivialCode = false; - if (isTrivialLiteral) - { - uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; - uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; - uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; - uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; - hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; - if (totalSize == 0 && green < WebpConstants.NumLiteralCodes) - { - hTreeGroup.IsTrivialCode = true; - hTreeGroup.LiteralArb |= green << 8; - } + maxBits += localMaxBits; } + } - hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; - if (hTreeGroup.UsePackedTable) + hTreeGroup.IsTrivialLiteral = isTrivialLiteral; + hTreeGroup.IsTrivialCode = false; + if (isTrivialLiteral) + { + uint red = hTreeGroup.HTrees[HuffIndex.Red][0].Value; + uint blue = hTreeGroup.HTrees[HuffIndex.Blue][0].Value; + uint green = hTreeGroup.HTrees[HuffIndex.Green][0].Value; + uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha][0].Value; + hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue; + if (totalSize == 0 && green < WebpConstants.NumLiteralCodes) { - BuildPackedTable(hTreeGroup); + hTreeGroup.IsTrivialCode = true; + hTreeGroup.LiteralArb |= green << 8; } } - decoder.Metadata.NumHTreeGroups = numHTreeGroups; - decoder.Metadata.HTreeGroups = hTreeGroups; - decoder.Metadata.HuffmanTables = huffmanTables; + hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits; + if (hTreeGroup.UsePackedTable) + { + BuildPackedTable(hTreeGroup); + } } - private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) + decoder.Metadata.NumHTreeGroups = numHTreeGroups; + decoder.Metadata.HTreeGroups = hTreeGroups; + decoder.Metadata.HuffmanTables = huffmanTables; + } + + private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table) + { + bool simpleCode = this.bitReader.ReadBit(); + for (int i = 0; i < alphabetSize; i++) { - bool simpleCode = this.bitReader.ReadBit(); - for (int i = 0; i < alphabetSize; i++) + codeLengths[i] = 0; + } + + if (simpleCode) + { + // (i) Simple Code Length Code. + // This variant is used in the special case when only 1 or 2 Huffman code lengths are non-zero, + // and are in the range of[0, 255]. All other Huffman code lengths are implicitly zeros. + + // Read symbols, codes & code lengths directly. + uint numSymbols = this.bitReader.ReadValue(1) + 1; + uint firstSymbolLenCode = this.bitReader.ReadValue(1); + + // The first code is either 1 bit or 8 bit code. + uint symbol = this.bitReader.ReadValue(firstSymbolLenCode == 0 ? 1 : 8); + codeLengths[symbol] = 1; + + // The second code (if present), is always 8 bit long. + if (numSymbols == 2) { - codeLengths[i] = 0; + symbol = this.bitReader.ReadValue(8); + codeLengths[symbol] = 1; + } + } + else + { + // (ii) Normal Code Length Code: + // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; + // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. + int[] codeLengthCodeLengths = new int[NumCodeLengthCodes]; + uint numCodes = this.bitReader.ReadValue(4) + 4; + if (numCodes > NumCodeLengthCodes) + { + WebpThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); } - if (simpleCode) + for (int i = 0; i < numCodes; i++) { - // (i) Simple Code Length Code. - // This variant is used in the special case when only 1 or 2 Huffman code lengths are non-zero, - // and are in the range of[0, 255]. All other Huffman code lengths are implicitly zeros. + codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); + } - // Read symbols, codes & code lengths directly. - uint numSymbols = this.bitReader.ReadValue(1) + 1; - uint firstSymbolLenCode = this.bitReader.ReadValue(1); + this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); + } - // The first code is either 1 bit or 8 bit code. - uint symbol = this.bitReader.ReadValue(firstSymbolLenCode == 0 ? 1 : 8); - codeLengths[symbol] = 1; + return HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); + } + + private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) + { + int maxSymbol; + int symbol = 0; + int prevCodeLen = WebpConstants.DefaultCodeLength; + int size = HuffmanUtils.BuildHuffmanTable(table, WebpConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); + if (size == 0) + { + WebpThrowHelper.ThrowImageFormatException("Error building huffman table"); + } - // The second code (if present), is always 8 bit long. - if (numSymbols == 2) + if (this.bitReader.ReadBit()) + { + int lengthNBits = 2 + (2 * (int)this.bitReader.ReadValue(3)); + maxSymbol = 2 + (int)this.bitReader.ReadValue(lengthNBits); + } + else + { + maxSymbol = numSymbols; + } + + while (symbol < numSymbols) + { + if (maxSymbol-- == 0) + { + break; + } + + this.bitReader.FillBitWindow(); + ulong prefetchBits = this.bitReader.PrefetchBits(); + int idx = (int)(prefetchBits & 127); + HuffmanCode huffmanCode = table[idx]; + this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); + uint codeLen = huffmanCode.Value; + if (codeLen < WebpConstants.CodeLengthLiterals) + { + codeLengths[symbol++] = (int)codeLen; + if (codeLen != 0) { - symbol = this.bitReader.ReadValue(8); - codeLengths[symbol] = 1; + prevCodeLen = (int)codeLen; } } else { - // (ii) Normal Code Length Code: - // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths; - // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros. - int[] codeLengthCodeLengths = new int[NumCodeLengthCodes]; - uint numCodes = this.bitReader.ReadValue(4) + 4; - if (numCodes > NumCodeLengthCodes) + bool usePrev = codeLen == WebpConstants.CodeLengthRepeatCode; + uint slot = codeLen - WebpConstants.CodeLengthLiterals; + int extraBits = WebpConstants.CodeLengthExtraBits[slot]; + int repeatOffset = WebpConstants.CodeLengthRepeatOffsets[slot]; + int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); + if (symbol + repeat > numSymbols) { - WebpThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value"); + return; } - for (int i = 0; i < numCodes; i++) + int length = usePrev ? prevCodeLen : 0; + while (repeat-- > 0) { - codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3); + codeLengths[symbol++] = length; } - - this.ReadHuffmanCodeLengths(table, codeLengthCodeLengths, alphabetSize, codeLengths); } - - return HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize); } + } - private void ReadHuffmanCodeLengths(Span table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths) - { - int maxSymbol; - int symbol = 0; - int prevCodeLen = WebpConstants.DefaultCodeLength; - int size = HuffmanUtils.BuildHuffmanTable(table, WebpConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes); - if (size == 0) - { - WebpThrowHelper.ThrowImageFormatException("Error building huffman table"); - } + /// + /// Reads the transformations, if any are present. + /// + /// The width of the image. + /// The height of the image. + /// Vp8LDecoder where the transformations will be stored. + private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) + { + Vp8LTransformType transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); + Vp8LTransform transform = new(transformType, xSize, ySize); - if (this.bitReader.ReadBit()) - { - int lengthNBits = 2 + (2 * (int)this.bitReader.ReadValue(3)); - maxSymbol = 2 + (int)this.bitReader.ReadValue(lengthNBits); - } - else + // Each transform is allowed to be used only once. + foreach (Vp8LTransform decoderTransform in decoder.Transforms) + { + if (decoderTransform.TransformType == transform.TransformType) { - maxSymbol = numSymbols; + WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); } + } - while (symbol < numSymbols) - { - if (maxSymbol-- == 0) + switch (transformType) + { + case Vp8LTransformType.SubtractGreen: + // There is no data associated with this transform. + break; + case Vp8LTransformType.ColorIndexingTransform: + // The transform data contains color table size and the entries in the color table. + // 8 bit value for color table size. + uint numColors = this.bitReader.ReadValue(8) + 1; + if (numColors > 16) { - break; + transform.Bits = 0; } - - this.bitReader.FillBitWindow(); - ulong prefetchBits = this.bitReader.PrefetchBits(); - int idx = (int)(prefetchBits & 127); - HuffmanCode huffmanCode = table[idx]; - this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed); - uint codeLen = huffmanCode.Value; - if (codeLen < WebpConstants.CodeLengthLiterals) + else if (numColors > 4) { - codeLengths[symbol++] = (int)codeLen; - if (codeLen != 0) - { - prevCodeLen = (int)codeLen; - } + transform.Bits = 1; + } + else if (numColors > 2) + { + transform.Bits = 2; } else { - bool usePrev = codeLen == WebpConstants.CodeLengthRepeatCode; - uint slot = codeLen - WebpConstants.CodeLengthLiterals; - int extraBits = WebpConstants.CodeLengthExtraBits[slot]; - int repeatOffset = WebpConstants.CodeLengthRepeatOffsets[slot]; - int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset); - if (symbol + repeat > numSymbols) - { - return; - } - - int length = usePrev ? prevCodeLen : 0; - while (repeat-- > 0) - { - codeLengths[symbol++] = length; - } + transform.Bits = 3; } - } - } - - /// - /// Reads the transformations, if any are present. - /// - /// The width of the image. - /// The height of the image. - /// Vp8LDecoder where the transformations will be stored. - private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder) - { - Vp8LTransformType transformType = (Vp8LTransformType)this.bitReader.ReadValue(2); - Vp8LTransform transform = new(transformType, xSize, ySize); - // Each transform is allowed to be used only once. - foreach (Vp8LTransform decoderTransform in decoder.Transforms) - { - if (decoderTransform.TransformType == transform.TransformType) + using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) { - WebpThrowHelper.ThrowImageFormatException("Each transform can only be present once"); + int finalNumColors = 1 << (8 >> transform.Bits); + IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean); + LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); + transform.Data = newColorMap; } - } - switch (transformType) - { - case Vp8LTransformType.SubtractGreen: - // There is no data associated with this transform. - break; - case Vp8LTransformType.ColorIndexingTransform: - // The transform data contains color table size and the entries in the color table. - // 8 bit value for color table size. - uint numColors = this.bitReader.ReadValue(8) + 1; - if (numColors > 16) - { - transform.Bits = 0; - } - else if (numColors > 4) - { - transform.Bits = 1; - } - else if (numColors > 2) - { - transform.Bits = 2; - } - else - { - transform.Bits = 3; - } + break; + + case Vp8LTransformType.PredictorTransform: + case Vp8LTransformType.CrossColorTransform: - using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false)) + // The first 3 bits of prediction data define the block width and height in number of bits. + transform.Bits = (int)this.bitReader.ReadValue(3) + 2; + int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); + int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); + transform.Data = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); + break; + } + + decoder.Transforms.Add(transform); + } + + /// + /// A Webp lossless image can go through four different types of transformation before being entropy encoded. + /// This will reverse the transformations, if any are present. + /// + /// The decoder holding the transformation infos. + /// The pixel data to apply the transformation. + /// The memory allocator is needed to allocate memory during the predictor transform. + public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator) + { + List transforms = decoder.Transforms; + for (int i = transforms.Count - 1; i >= 0; i--) + { + Vp8LTransform transform = transforms[i]; + switch (transform.TransformType) + { + case Vp8LTransformType.PredictorTransform: + using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) { - int finalNumColors = 1 << (8 >> transform.Bits); - IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean); - LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan()); - transform.Data = newColorMap; + LosslessUtils.PredictorInverseTransform(transform, pixelData, output.GetSpan()); } break; - - case Vp8LTransformType.PredictorTransform: + case Vp8LTransformType.SubtractGreen: + LosslessUtils.AddGreenToBlueAndRed(pixelData); + break; case Vp8LTransformType.CrossColorTransform: - - // The first 3 bits of prediction data define the block width and height in number of bits. - transform.Bits = (int)this.bitReader.ReadValue(3) + 2; - int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits); - int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits); - transform.Data = this.DecodeImageStream(decoder, blockWidth, blockHeight, false); + LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); + break; + case Vp8LTransformType.ColorIndexingTransform: + LosslessUtils.ColorIndexInverseTransform(transform, pixelData); break; } - - decoder.Transforms.Add(transform); } + } - /// - /// A Webp lossless image can go through four different types of transformation before being entropy encoded. - /// This will reverse the transformations, if any are present. - /// - /// The decoder holding the transformation infos. - /// The pixel data to apply the transformation. - /// The memory allocator is needed to allocate memory during the predictor transform. - public static void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData, MemoryAllocator memoryAllocator) + /// + /// The alpha channel of a lossy webp image can be compressed using the lossless webp compression. + /// This method will undo the compression. + /// + /// The alpha decoder. + public void DecodeAlphaData(AlphaDecoder dec) + { + Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; + Span data = MemoryMarshal.Cast(pixelData); + int row = 0; + int col = 0; + Vp8LDecoder vp8LDec = dec.Vp8LDec; + int width = vp8LDec.Width; + int height = vp8LDec.Height; + Vp8LMetadata hdr = vp8LDec.Metadata; + int pos = 0; // Current position. + int end = width * height; // End of data. + int last = end; // Last pixel to decode. + int lastRow = height; + const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; + int mask = hdr.HuffmanMask; + Span htreeGroup = pos < last ? GetHTreeGroupForPos(hdr, col, row) : null; + while (!this.bitReader.Eos && pos < last) { - List transforms = decoder.Transforms; - for (int i = transforms.Count - 1; i >= 0; i--) + // Only update when changing tile. + if ((col & mask) == 0) { - Vp8LTransform transform = transforms[i]; - switch (transform.TransformType) - { - case Vp8LTransformType.PredictorTransform: - using (IMemoryOwner output = memoryAllocator.Allocate(pixelData.Length, AllocationOptions.Clean)) - { - LosslessUtils.PredictorInverseTransform(transform, pixelData, output.GetSpan()); - } - - break; - case Vp8LTransformType.SubtractGreen: - LosslessUtils.AddGreenToBlueAndRed(pixelData); - break; - case Vp8LTransformType.CrossColorTransform: - LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); - break; - case Vp8LTransformType.ColorIndexingTransform: - LosslessUtils.ColorIndexInverseTransform(transform, pixelData); - break; - } + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } - } - /// - /// The alpha channel of a lossy webp image can be compressed using the lossless webp compression. - /// This method will undo the compression. - /// - /// The alpha decoder. - public void DecodeAlphaData(AlphaDecoder dec) - { - Span pixelData = dec.Vp8LDec.Pixels.Memory.Span; - Span data = MemoryMarshal.Cast(pixelData); - int row = 0; - int col = 0; - Vp8LDecoder vp8LDec = dec.Vp8LDec; - int width = vp8LDec.Width; - int height = vp8LDec.Height; - Vp8LMetadata hdr = vp8LDec.Metadata; - int pos = 0; // Current position. - int end = width * height; // End of data. - int last = end; // Last pixel to decode. - int lastRow = height; - const int lenCodeLimit = WebpConstants.NumLiteralCodes + WebpConstants.NumLengthCodes; - int mask = hdr.HuffmanMask; - Span htreeGroup = pos < last ? GetHTreeGroupForPos(hdr, col, row) : null; - while (!this.bitReader.Eos && pos < last) - { - // Only update when changing tile. - if ((col & mask) == 0) - { - htreeGroup = GetHTreeGroupForPos(hdr, col, row); - } + this.bitReader.FillBitWindow(); + int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); + if (code < WebpConstants.NumLiteralCodes) + { + // Literal + data[pos] = (byte)code; + ++pos; + ++col; - this.bitReader.FillBitWindow(); - int code = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Green]); - if (code < WebpConstants.NumLiteralCodes) + if (col >= width) { - // Literal - data[pos] = (byte)code; - ++pos; - ++col; - - if (col >= width) + col = 0; + ++row; + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) { - col = 0; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) - { - dec.ExtractPalettedAlphaRows(row); - } + dec.ExtractPalettedAlphaRows(row); } } - else if (code < lenCodeLimit) + } + else if (code < lenCodeLimit) + { + // Backward reference + int lengthSym = code - WebpConstants.NumLiteralCodes; + int length = this.GetCopyLength(lengthSym); + int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); + this.bitReader.FillBitWindow(); + int distCode = this.GetCopyDistance(distSymbol); + int dist = PlaneCodeToDistance(width, distCode); + if (pos >= dist && end - pos >= length) + { + CopyBlock8B(data, pos, dist, length); + } + else { - // Backward reference - int lengthSym = code - WebpConstants.NumLiteralCodes; - int length = this.GetCopyLength(lengthSym); - int distSymbol = (int)this.ReadSymbol(htreeGroup[0].HTrees[HuffIndex.Dist]); - this.bitReader.FillBitWindow(); - int distCode = this.GetCopyDistance(distSymbol); - int dist = PlaneCodeToDistance(width, distCode); - if (pos >= dist && end - pos >= length) - { - CopyBlock8B(data, pos, dist, length); - } - else - { - WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); - } - - pos += length; - col += length; - while (col >= width) - { - col -= width; - ++row; - if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) - { - dec.ExtractPalettedAlphaRows(row); - } - } + WebpThrowHelper.ThrowImageFormatException("error while decoding alpha data"); + } - if (pos < last && (col & mask) > 0) + pos += length; + col += length; + while (col >= width) + { + col -= width; + ++row; + if (row <= lastRow && row % WebpConstants.NumArgbCacheRows == 0) { - htreeGroup = GetHTreeGroupForPos(hdr, col, row); + dec.ExtractPalettedAlphaRows(row); } } - else + + if (pos < last && (col & mask) > 0) { - WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); + htreeGroup = GetHTreeGroupForPos(hdr, col, row); } - - this.bitReader.Eos = this.bitReader.IsEndOfStream(); + } + else + { + WebpThrowHelper.ThrowImageFormatException("bitstream error while parsing alpha data"); } - // Process the remaining rows corresponding to last row-block. - dec.ExtractPalettedAlphaRows(row > lastRow ? lastRow : row); + this.bitReader.Eos = this.bitReader.IsEndOfStream(); } - private static void UpdateDecoder(Vp8LDecoder decoder, int width, int height) - { - int numBits = decoder.Metadata.HuffmanSubSampleBits; - decoder.Width = width; - decoder.Height = height; - decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); - decoder.Metadata.HuffmanMask = numBits == 0 ? ~0 : (1 << numBits) - 1; - } + // Process the remaining rows corresponding to last row-block. + dec.ExtractPalettedAlphaRows(row > lastRow ? lastRow : row); + } - private uint ReadPackedSymbols(Span group, Span pixelData, int decodedPixels) + private static void UpdateDecoder(Vp8LDecoder decoder, int width, int height) + { + int numBits = decoder.Metadata.HuffmanSubSampleBits; + decoder.Width = width; + decoder.Height = height; + decoder.Metadata.HuffmanXSize = LosslessUtils.SubSampleSize(width, numBits); + decoder.Metadata.HuffmanMask = numBits == 0 ? ~0 : (1 << numBits) - 1; + } + + private uint ReadPackedSymbols(Span group, Span pixelData, int decodedPixels) + { + uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); + HuffmanCode code = group[0].PackedTable[val]; + if (code.BitsUsed < BitsSpecialMarker) { - uint val = (uint)(this.bitReader.PrefetchBits() & (HuffmanUtils.HuffmanPackedTableSize - 1)); - HuffmanCode code = group[0].PackedTable[val]; - if (code.BitsUsed < BitsSpecialMarker) - { - this.bitReader.AdvanceBitPosition(code.BitsUsed); - pixelData[decodedPixels] = code.Value; - return PackedNonLiteralCode; - } + this.bitReader.AdvanceBitPosition(code.BitsUsed); + pixelData[decodedPixels] = code.Value; + return PackedNonLiteralCode; + } - this.bitReader.AdvanceBitPosition(code.BitsUsed - BitsSpecialMarker); + this.bitReader.AdvanceBitPosition(code.BitsUsed - BitsSpecialMarker); - return code.Value; - } + return code.Value; + } - private static void BuildPackedTable(HTreeGroup hTreeGroup) + private static void BuildPackedTable(HTreeGroup hTreeGroup) + { + for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++) { - for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++) + uint bits = code; + ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits]; + HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; + if (hCode.Value >= WebpConstants.NumLiteralCodes) { - uint bits = code; - ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits]; - HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; - if (hCode.Value >= WebpConstants.NumLiteralCodes) - { - huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; - huff.Value = hCode.Value; - } - else - { - huff.BitsUsed = 0; - huff.Value = 0; - bits >>= AccumulateHCode(hCode, 8, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff); - } + huff.BitsUsed = hCode.BitsUsed + BitsSpecialMarker; + huff.Value = hCode.Value; + } + else + { + huff.BitsUsed = 0; + huff.Value = 0; + bits >>= AccumulateHCode(hCode, 8, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff); } } + } - /// - /// Decodes the next Huffman code from the bit-stream. - /// FillBitWindow() needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. - /// - /// The Huffman table. - private uint ReadSymbol(Span table) + /// + /// Decodes the next Huffman code from the bit-stream. + /// FillBitWindow() needs to be called at minimum every second call to ReadSymbol, in order to pre-fetch enough bits. + /// + /// The Huffman table. + private uint ReadSymbol(Span table) + { + uint val = (uint)this.bitReader.PrefetchBits(); + Span tableSpan = table[(int)(val & HuffmanUtils.HuffmanTableMask)..]; + int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; + if (nBits > 0) { - uint val = (uint)this.bitReader.PrefetchBits(); - Span tableSpan = table[(int)(val & HuffmanUtils.HuffmanTableMask)..]; - int nBits = tableSpan[0].BitsUsed - HuffmanUtils.HuffmanTableBits; - if (nBits > 0) - { - this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); - val = (uint)this.bitReader.PrefetchBits(); - tableSpan = tableSpan[(int)tableSpan[0].Value..]; - tableSpan = tableSpan[((int)val & ((1 << nBits) - 1))..]; - } + this.bitReader.AdvanceBitPosition(HuffmanUtils.HuffmanTableBits); + val = (uint)this.bitReader.PrefetchBits(); + tableSpan = tableSpan[(int)tableSpan[0].Value..]; + tableSpan = tableSpan[((int)val & ((1 << nBits) - 1))..]; + } - this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); + this.bitReader.AdvanceBitPosition(tableSpan[0].BitsUsed); - return tableSpan[0].Value; - } + return tableSpan[0].Value; + } - [MethodImpl(InliningOptions.ShortMethod)] - private int GetCopyLength(int lengthSymbol) => - this.GetCopyDistance(lengthSymbol); // Length and distance prefixes are encoded the same way. + [MethodImpl(InliningOptions.ShortMethod)] + private int GetCopyLength(int lengthSymbol) => + this.GetCopyDistance(lengthSymbol); // Length and distance prefixes are encoded the same way. - private int GetCopyDistance(int distanceSymbol) + private int GetCopyDistance(int distanceSymbol) + { + if (distanceSymbol < 4) { - if (distanceSymbol < 4) - { - return distanceSymbol + 1; - } + return distanceSymbol + 1; + } - int extraBits = (distanceSymbol - 2) >> 1; - int offset = (2 + (distanceSymbol & 1)) << extraBits; + int extraBits = (distanceSymbol - 2) >> 1; + int offset = (2 + (distanceSymbol & 1)) << extraBits; - return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); - } + return (int)(offset + this.bitReader.ReadValue(extraBits) + 1); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static Span GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + [MethodImpl(InliningOptions.ShortMethod)] + private static Span GetHTreeGroupForPos(Vp8LMetadata metadata, int x, int y) + { + uint metaIndex = GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); + return metadata.HTreeGroups.AsSpan((int)metaIndex); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) + { + if (bits is 0) { - uint metaIndex = GetMetaIndex(metadata.HuffmanImage, metadata.HuffmanXSize, metadata.HuffmanSubSampleBits, x, y); - return metadata.HTreeGroups.AsSpan((int)metaIndex); + return 0; } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GetMetaIndex(IMemoryOwner huffmanImage, int xSize, int bits, int x, int y) - { - if (bits is 0) - { - return 0; - } + Span huffmanImageSpan = huffmanImage.GetSpan(); + return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; + } - Span huffmanImageSpan = huffmanImage.GetSpan(); - return huffmanImageSpan[(xSize * (y >> bits)) + (x >> bits)]; + private static int PlaneCodeToDistance(int xSize, int planeCode) + { + if (planeCode > CodeToPlaneCodes) + { + return planeCode - CodeToPlaneCodes; } - private static int PlaneCodeToDistance(int xSize, int planeCode) - { - if (planeCode > CodeToPlaneCodes) - { - return planeCode - CodeToPlaneCodes; - } + int distCode = WebpLookupTables.CodeToPlane[planeCode - 1]; + int yOffset = distCode >> 4; + int xOffset = 8 - (distCode & 0xf); + int dist = (yOffset * xSize) + xOffset; - int distCode = WebpLookupTables.CodeToPlane[planeCode - 1]; - int yOffset = distCode >> 4; - int xOffset = 8 - (distCode & 0xf); - int dist = (yOffset * xSize) + xOffset; + // dist < 1 can happen if xSize is very small. + return dist >= 1 ? dist : 1; + } - // dist < 1 can happen if xSize is very small. - return dist >= 1 ? dist : 1; + /// + /// Copies pixels when a backward reference is used. + /// Copy 'length' number of pixels (in scan-line order) from the sequence of pixels prior to them by 'dist' pixels. + /// + /// The pixel data. + /// The number of so far decoded pixels. + /// The backward reference distance prior to the current decoded pixel. + /// The number of pixels to copy. + private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) + { + int start = decodedPixels - dist; + if (start < 0) + { + WebpThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); } - /// - /// Copies pixels when a backward reference is used. - /// Copy 'length' number of pixels (in scan-line order) from the sequence of pixels prior to them by 'dist' pixels. - /// - /// The pixel data. - /// The number of so far decoded pixels. - /// The backward reference distance prior to the current decoded pixel. - /// The number of pixels to copy. - private static void CopyBlock(Span pixelData, int decodedPixels, int dist, int length) + if (dist >= length) { - int start = decodedPixels - dist; - if (start < 0) - { - WebpThrowHelper.ThrowImageFormatException("webp image data seems to be invalid"); - } - - if (dist >= length) - { - // no overlap. - Span src = pixelData.Slice(start, length); - Span dest = pixelData[decodedPixels..]; - src.CopyTo(dest); - } - else + // no overlap. + Span src = pixelData.Slice(start, length); + Span dest = pixelData[decodedPixels..]; + src.CopyTo(dest); + } + else + { + // There is overlap between the backward reference distance and the pixels to copy. + Span src = pixelData[start..]; + Span dest = pixelData[decodedPixels..]; + for (int i = 0; i < length; i++) { - // There is overlap between the backward reference distance and the pixels to copy. - Span src = pixelData[start..]; - Span dest = pixelData[decodedPixels..]; - for (int i = 0; i < length; i++) - { - dest[i] = src[i]; - } + dest[i] = src[i]; } } + } - /// - /// Copies alpha values when a backward reference is used. - /// Copy 'length' number of alpha values from the sequence of alpha values prior to them by 'dist'. - /// - /// The alpha values. - /// The position of the so far decoded pixels. - /// The backward reference distance prior to the current decoded pixel. - /// The number of pixels to copy. - private static void CopyBlock8B(Span data, int pos, int dist, int length) + /// + /// Copies alpha values when a backward reference is used. + /// Copy 'length' number of alpha values from the sequence of alpha values prior to them by 'dist'. + /// + /// The alpha values. + /// The position of the so far decoded pixels. + /// The backward reference distance prior to the current decoded pixel. + /// The number of pixels to copy. + private static void CopyBlock8B(Span data, int pos, int dist, int length) + { + if (dist >= length) { - if (dist >= length) - { - // no overlap. - data.Slice(pos - dist, length).CopyTo(data[pos..]); - } - else + // no overlap. + data.Slice(pos - dist, length).CopyTo(data[pos..]); + } + else + { + Span dst = data[pos..]; + Span src = data[(pos - dist)..]; + for (int i = 0; i < length; i++) { - Span dst = data[pos..]; - Span src = data[(pos - dist)..]; - for (int i = 0; i < length; i++) - { - dst[i] = src[i]; - } + dst[i] = src[i]; } } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff) - { - huff.BitsUsed += hCode.BitsUsed; - huff.Value |= hCode.Value << shift; - return hCode.BitsUsed; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff) + { + huff.BitsUsed += hCode.BitsUsed; + huff.Value |= hCode.Value << shift; + return hCode.BitsUsed; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs b/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs index 5981b4a17d..7e484219fd 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/IntraPredictionMode.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal enum IntraPredictionMode { - internal enum IntraPredictionMode - { - /// - /// Predict DC using row above and column to the left. - /// - DcPrediction = 0, + /// + /// Predict DC using row above and column to the left. + /// + DcPrediction = 0, - /// - /// Propagate second differences a la "True Motion". - /// - TrueMotion = 1, + /// + /// Propagate second differences a la "True Motion". + /// + TrueMotion = 1, - /// - /// Predict rows using row above. - /// - VPrediction = 2, + /// + /// Predict rows using row above. + /// + VPrediction = 2, - /// - /// Predict columns using column to the left. - /// - HPrediction = 3, - } + /// + /// Predict columns using column to the left. + /// + HPrediction = 3, } diff --git a/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs b/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs index 0823e17f4f..c230759723 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LoopFilter.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Enum for the different loop filters used. VP8 supports two types of loop filters. +/// +internal enum LoopFilter { /// - /// Enum for the different loop filters used. VP8 supports two types of loop filters. + /// No filter is used. /// - internal enum LoopFilter - { - /// - /// No filter is used. - /// - None = 0, + None = 0, - /// - /// Simple loop filter. - /// - Simple = 1, + /// + /// Simple loop filter. + /// + Simple = 1, - /// - /// Complex loop filter. - /// - Complex = 2, - } + /// + /// Complex loop filter. + /// + Complex = 2, } diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 210125790c..4756dea867 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,2369 +8,2368 @@ using System.Runtime.Intrinsics.X86; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal static class LossyUtils { - internal static class LossyUtils + // Note: method name in libwebp reference implementation is called VP8SSE16x16. + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8_Sse16X16(Span a, Span b) { - // Note: method name in libwebp reference implementation is called VP8SSE16x16. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X16(Span a, Span b) + if (Avx2.IsSupported) { - if (Avx2.IsSupported) - { - return Vp8_Sse16xN_Avx2(a, b, 4); - } - - if (Sse2.IsSupported) - { - return Vp8_Sse16xN_Sse2(a, b, 8); - } - - return Vp8_SseNxN(a, b, 16, 16); + return Vp8_Sse16xN_Avx2(a, b, 4); } - // Note: method name in libwebp reference implementation is called VP8SSE16x8. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X8(Span a, Span b) + if (Sse2.IsSupported) { - if (Avx2.IsSupported) - { - return Vp8_Sse16xN_Avx2(a, b, 2); - } + return Vp8_Sse16xN_Sse2(a, b, 8); + } - if (Sse2.IsSupported) - { - return Vp8_Sse16xN_Sse2(a, b, 4); - } + return Vp8_SseNxN(a, b, 16, 16); + } - return Vp8_SseNxN(a, b, 16, 8); + // Note: method name in libwebp reference implementation is called VP8SSE16x8. + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8_Sse16X8(Span a, Span b) + { + if (Avx2.IsSupported) + { + return Vp8_Sse16xN_Avx2(a, b, 2); } - // Note: method name in libwebp reference implementation is called VP8SSE4x4. - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse4X4(Span a, Span b) + if (Sse2.IsSupported) { - if (Avx2.IsSupported) - { - // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - var a0 = Vector256.Create( - Unsafe.As>(ref aRef), - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps))); - var a1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)), - Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3))); - var b0 = Vector256.Create( - Unsafe.As>(ref bRef), - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps))); - var b1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)), - Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3))); - - // Combine pair of lines. - Vector256 a01 = Avx2.UnpackLow(a0.AsInt32(), a1.AsInt32()); - Vector256 b01 = Avx2.UnpackLow(b0.AsInt32(), b1.AsInt32()); - - // Convert to 16b. - Vector256 a01s = Avx2.UnpackLow(a01.AsByte(), Vector256.Zero); - Vector256 b01s = Avx2.UnpackLow(b01.AsByte(), Vector256.Zero); - - // subtract, square and accumulate. - Vector256 d0 = Avx2.SubtractSaturate(a01s, b01s); - Vector256 e0 = Avx2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); - - return Numerics.ReduceSum(e0); - } - - if (Sse2.IsSupported) - { - // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - Vector128 a0 = Unsafe.As>(ref aRef); - Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); - Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); - Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); - Vector128 b0 = Unsafe.As>(ref bRef); - Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); - Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); - Vector128 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3)); - - // Combine pair of lines. - Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); - Vector128 a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32()); - Vector128 b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32()); - Vector128 b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32()); - - // Convert to 16b. - Vector128 a01s = Sse2.UnpackLow(a01.AsByte(), Vector128.Zero); - Vector128 a23s = Sse2.UnpackLow(a23.AsByte(), Vector128.Zero); - Vector128 b01s = Sse2.UnpackLow(b01.AsByte(), Vector128.Zero); - Vector128 b23s = Sse2.UnpackLow(b23.AsByte(), Vector128.Zero); - - // subtract, square and accumulate. - Vector128 d0 = Sse2.SubtractSaturate(a01s, b01s); - Vector128 d1 = Sse2.SubtractSaturate(a23s, b23s); - Vector128 e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); - Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); - Vector128 sum = Sse2.Add(e0, e1); - - return Numerics.ReduceSum(sum); - } - - return Vp8_SseNxN(a, b, 4, 4); + return Vp8_Sse16xN_Sse2(a, b, 4); } - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_SseNxN(Span a, Span b, int w, int h) + return Vp8_SseNxN(a, b, 16, 8); + } + + // Note: method name in libwebp reference implementation is called VP8SSE4x4. + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8_Sse4X4(Span a, Span b) + { + if (Avx2.IsSupported) { - int count = 0; - int aOffset = 0; - int bOffset = 0; - for (int y = 0; y < h; y++) - { - for (int x = 0; x < w; x++) - { - int diff = a[aOffset + x] - b[bOffset + x]; - count += diff * diff; - } + // Load values. + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + var a0 = Vector256.Create( + Unsafe.As>(ref aRef), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3))); + var b0 = Vector256.Create( + Unsafe.As>(ref bRef), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3))); - aOffset += WebpConstants.Bps; - bOffset += WebpConstants.Bps; - } + // Combine pair of lines. + Vector256 a01 = Avx2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector256 b01 = Avx2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + + // Convert to 16b. + Vector256 a01s = Avx2.UnpackLow(a01.AsByte(), Vector256.Zero); + Vector256 b01s = Avx2.UnpackLow(b01.AsByte(), Vector256.Zero); - return count; + // subtract, square and accumulate. + Vector256 d0 = Avx2.SubtractSaturate(a01s, b01s); + Vector256 e0 = Avx2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + + return Numerics.ReduceSum(e0); } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8_Sse16xN_Sse2(Span a, Span b, int numPairs) + if (Sse2.IsSupported) { - Vector128 sum = Vector128.Zero; - nint offset = 0; + // Load values. ref byte aRef = ref MemoryMarshal.GetReference(a); ref byte bRef = ref MemoryMarshal.GetReference(b); - for (int i = 0; i < numPairs; i++) - { - // Load values. - Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); - Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); - Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); - Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps)); - - Vector128 sum1 = SubtractAndAccumulate(a0, b0); - Vector128 sum2 = SubtractAndAccumulate(a1, b1); - sum = Sse2.Add(sum, Sse2.Add(sum1, sum2)); - - offset += 2 * WebpConstants.Bps; - } + Vector128 a0 = Unsafe.As>(ref aRef); + Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); + Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); + Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); + Vector128 b0 = Unsafe.As>(ref bRef); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); + Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); + Vector128 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3)); + + // Combine pair of lines. + Vector128 a01 = Sse2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector128 a23 = Sse2.UnpackLow(a2.AsInt32(), a3.AsInt32()); + Vector128 b01 = Sse2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + Vector128 b23 = Sse2.UnpackLow(b2.AsInt32(), b3.AsInt32()); + + // Convert to 16b. + Vector128 a01s = Sse2.UnpackLow(a01.AsByte(), Vector128.Zero); + Vector128 a23s = Sse2.UnpackLow(a23.AsByte(), Vector128.Zero); + Vector128 b01s = Sse2.UnpackLow(b01.AsByte(), Vector128.Zero); + Vector128 b23s = Sse2.UnpackLow(b23.AsByte(), Vector128.Zero); + + // subtract, square and accumulate. + Vector128 d0 = Sse2.SubtractSaturate(a01s, b01s); + Vector128 d1 = Sse2.SubtractSaturate(a23s, b23s); + Vector128 e0 = Sse2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + Vector128 e1 = Sse2.MultiplyAddAdjacent(d1.AsInt16(), d1.AsInt16()); + Vector128 sum = Sse2.Add(e0, e1); return Numerics.ReduceSum(sum); } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Vp8_Sse16xN_Avx2(Span a, Span b, int numPairs) + return Vp8_SseNxN(a, b, 4, 4); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8_SseNxN(Span a, Span b, int w, int h) + { + int count = 0; + int aOffset = 0; + int bOffset = 0; + for (int y = 0; y < h; y++) { - Vector256 sum = Vector256.Zero; - nint offset = 0; - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); - for (int i = 0; i < numPairs; i++) + for (int x = 0; x < w; x++) { - // Load values. - var a0 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); - var b0 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, offset)), - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps))); - var a1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (2 * WebpConstants.Bps))), - Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (3 * WebpConstants.Bps)))); - var b1 = Vector256.Create( - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (2 * WebpConstants.Bps))), - Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (3 * WebpConstants.Bps)))); - - Vector256 sum1 = SubtractAndAccumulate(a0, b0); - Vector256 sum2 = SubtractAndAccumulate(a1, b1); - sum = Avx2.Add(sum, Avx2.Add(sum1, sum2)); - - offset += 4 * WebpConstants.Bps; + int diff = a[aOffset + x] - b[bOffset + x]; + count += diff * diff; } - return Numerics.ReduceSum(sum); + aOffset += WebpConstants.Bps; + bOffset += WebpConstants.Bps; } - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 SubtractAndAccumulate(Vector128 a, Vector128 b) - { - // Take abs(a-b) in 8b. - Vector128 ab = Sse2.SubtractSaturate(a, b); - Vector128 ba = Sse2.SubtractSaturate(b, a); - Vector128 absAb = Sse2.Or(ab, ba); + return count; + } - // Zero-extend to 16b. - Vector128 c0 = Sse2.UnpackLow(absAb, Vector128.Zero); - Vector128 c1 = Sse2.UnpackHigh(absAb, Vector128.Zero); + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Sse2(Span a, Span b, int numPairs) + { + Vector128 sum = Vector128.Zero; + nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + for (int i = 0; i < numPairs; i++) + { + // Load values. + Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); + Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); + Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps)); - // Multiply with self. - Vector128 sum1 = Sse2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); - Vector128 sum2 = Sse2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + Vector128 sum1 = SubtractAndAccumulate(a0, b0); + Vector128 sum2 = SubtractAndAccumulate(a1, b1); + sum = Sse2.Add(sum, Sse2.Add(sum1, sum2)); - return Sse2.Add(sum1, sum2); + offset += 2 * WebpConstants.Bps; } - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector256 SubtractAndAccumulate(Vector256 a, Vector256 b) - { - // Take abs(a-b) in 8b. - Vector256 ab = Avx2.SubtractSaturate(a, b); - Vector256 ba = Avx2.SubtractSaturate(b, a); - Vector256 absAb = Avx2.Or(ab, ba); + return Numerics.ReduceSum(sum); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Avx2(Span a, Span b, int numPairs) + { + Vector256 sum = Vector256.Zero; + nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + for (int i = 0; i < numPairs; i++) + { + // Load values. + var a0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); + var b0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (3 * WebpConstants.Bps)))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (3 * WebpConstants.Bps)))); + + Vector256 sum1 = SubtractAndAccumulate(a0, b0); + Vector256 sum2 = SubtractAndAccumulate(a1, b1); + sum = Avx2.Add(sum, Avx2.Add(sum1, sum2)); + + offset += 4 * WebpConstants.Bps; + } + + return Numerics.ReduceSum(sum); + } - // Zero-extend to 16b. - Vector256 c0 = Avx2.UnpackLow(absAb, Vector256.Zero); - Vector256 c1 = Avx2.UnpackHigh(absAb, Vector256.Zero); + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SubtractAndAccumulate(Vector128 a, Vector128 b) + { + // Take abs(a-b) in 8b. + Vector128 ab = Sse2.SubtractSaturate(a, b); + Vector128 ba = Sse2.SubtractSaturate(b, a); + Vector128 absAb = Sse2.Or(ab, ba); - // Multiply with self. - Vector256 sum1 = Avx2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); - Vector256 sum2 = Avx2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + // Zero-extend to 16b. + Vector128 c0 = Sse2.UnpackLow(absAb, Vector128.Zero); + Vector128 c1 = Sse2.UnpackHigh(absAb, Vector128.Zero); - return Avx2.Add(sum1, sum2); - } + // Multiply with self. + Vector128 sum1 = Sse2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector128 sum2 = Sse2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + + return Sse2.Add(sum1, sum2); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector256 SubtractAndAccumulate(Vector256 a, Vector256 b) + { + // Take abs(a-b) in 8b. + Vector256 ab = Avx2.SubtractSaturate(a, b); + Vector256 ba = Avx2.SubtractSaturate(b, a); + Vector256 absAb = Avx2.Or(ab, ba); - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); + // Zero-extend to 16b. + Vector256 c0 = Avx2.UnpackLow(absAb, Vector256.Zero); + Vector256 c1 = Avx2.UnpackHigh(absAb, Vector256.Zero); - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); + // Multiply with self. + Vector256 sum1 = Avx2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector256 sum2 = Avx2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); - [MethodImpl(InliningOptions.ShortMethod)] - public static void Copy(Span src, Span dst, int w, int h) + return Avx2.Add(sum1, sum2); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Vp8Copy16X8(Span src, Span dst) => Copy(src, dst, 16, 8); + + [MethodImpl(InliningOptions.ShortMethod)] + public static void Copy(Span src, Span dst, int w, int h) + { + int offset = 0; + for (int y = 0; y < h; y++) { - int offset = 0; - for (int y = 0; y < h; y++) - { - src.Slice(offset, w).CopyTo(dst.Slice(offset, w)); - offset += WebpConstants.Bps; - } + src.Slice(offset, w).CopyTo(dst.Slice(offset, w)); + offset += WebpConstants.Bps; } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Disto16X16(Span a, Span b, Span w, Span scratch) + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Disto16X16(Span a, Span b, Span w, Span scratch) + { + int d = 0; + const int dataSize = (4 * WebpConstants.Bps) - 16; + for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) { - int d = 0; - const int dataSize = (4 * WebpConstants.Bps) - 16; - for (int y = 0; y < 16 * WebpConstants.Bps; y += 4 * WebpConstants.Bps) + for (int x = 0; x < 16; x += 4) { - for (int x = 0; x < 16; x += 4) - { - d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w, scratch); - } + d += Vp8Disto4X4(a.Slice(x + y, dataSize), b.Slice(x + y, dataSize), w, scratch); } - - return d; } - [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8Disto4X4(Span a, Span b, Span w, Span scratch) + return d; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Vp8Disto4X4(Span a, Span b, Span w, Span scratch) + { + if (Sse41.IsSupported) { - if (Sse41.IsSupported) - { - int diffSum = TTransformSse41(a, b, w); - return Math.Abs(diffSum) >> 5; - } - else - { - int sum1 = TTransform(a, w, scratch); - int sum2 = TTransform(b, w, scratch); - return Math.Abs(sum2 - sum1) >> 5; - } + int diffSum = TTransformSse41(a, b, w); + return Math.Abs(diffSum) >> 5; } - - public static void DC16(Span dst, Span yuv, int offset) + else { - int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebpConstants.Bps; - int dc = 16; - for (int j = 0; j < 16; j++) - { - // DC += dst[-1 + j * BPS] + dst[j - BPS]; - dc += yuv[offsetMinus1 + (j * WebpConstants.Bps)] + yuv[offsetMinusBps + j]; - } + int sum1 = TTransform(a, w, scratch); + int sum2 = TTransform(b, w, scratch); + return Math.Abs(sum2 - sum1) >> 5; + } + } - Put16(dc >> 5, dst); + public static void DC16(Span dst, Span yuv, int offset) + { + int offsetMinus1 = offset - 1; + int offsetMinusBps = offset - WebpConstants.Bps; + int dc = 16; + for (int j = 0; j < 16; j++) + { + // DC += dst[-1 + j * BPS] + dst[j - BPS]; + dc += yuv[offsetMinus1 + (j * WebpConstants.Bps)] + yuv[offsetMinusBps + j]; } - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM16(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 16); + Put16(dc >> 5, dst); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void TM16(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 16); - public static void VE16(Span dst, Span yuv, int offset) + public static void VE16(Span dst, Span yuv, int offset) + { + // vertical + Span src = yuv.Slice(offset - WebpConstants.Bps, 16); + for (int j = 0; j < 16; j++) { - // vertical - Span src = yuv.Slice(offset - WebpConstants.Bps, 16); - for (int j = 0; j < 16; j++) - { - // memcpy(dst + j * BPS, dst - BPS, 16); - src.CopyTo(dst[(j * WebpConstants.Bps)..]); - } + // memcpy(dst + j * BPS, dst - BPS, 16); + src.CopyTo(dst[(j * WebpConstants.Bps)..]); } + } - public static void HE16(Span dst, Span yuv, int offset) + public static void HE16(Span dst, Span yuv, int offset) + { + // horizontal + offset--; + for (int j = 16; j > 0; j--) { - // horizontal - offset--; - for (int j = 16; j > 0; j--) - { - // memset(dst, dst[-1], 16); - byte v = yuv[offset]; - Memset(dst, v, 0, 16); - offset += WebpConstants.Bps; - dst = dst[WebpConstants.Bps..]; - } + // memset(dst, dst[-1], 16); + byte v = yuv[offset]; + Memset(dst, v, 0, 16); + offset += WebpConstants.Bps; + dst = dst[WebpConstants.Bps..]; } + } - public static void DC16NoTop(Span dst, Span yuv, int offset) + public static void DC16NoTop(Span dst, Span yuv, int offset) + { + // DC with top samples not available. + int dc = 8; + for (int j = 0; j < 16; j++) { - // DC with top samples not available. - int dc = 8; - for (int j = 0; j < 16; j++) - { - // DC += dst[-1 + j * BPS]; - dc += yuv[-1 + (j * WebpConstants.Bps) + offset]; - } - - Put16(dc >> 4, dst); + // DC += dst[-1 + j * BPS]; + dc += yuv[-1 + (j * WebpConstants.Bps) + offset]; } - public static void DC16NoLeft(Span dst, Span yuv, int offset) - { - // DC with left samples not available. - int dc = 8; - for (int i = 0; i < 16; i++) - { - // DC += dst[i - BPS]; - dc += yuv[i - WebpConstants.Bps + offset]; - } + Put16(dc >> 4, dst); + } - Put16(dc >> 4, dst); + public static void DC16NoLeft(Span dst, Span yuv, int offset) + { + // DC with left samples not available. + int dc = 8; + for (int i = 0; i < 16; i++) + { + // DC += dst[i - BPS]; + dc += yuv[i - WebpConstants.Bps + offset]; } - [MethodImpl(InliningOptions.ShortMethod)] - public static void DC16NoTopLeft(Span dst) => - Put16(0x80, dst); // DC with no top and left samples. + Put16(dc >> 4, dst); + } - public static void DC8uv(Span dst, Span yuv, int offset) - { - int dc0 = 8; - int offsetMinus1 = offset - 1; - int offsetMinusBps = offset - WebpConstants.Bps; - for (int i = 0; i < 8; i++) - { - // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; - dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebpConstants.Bps)]; - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void DC16NoTopLeft(Span dst) => + Put16(0x80, dst); // DC with no top and left samples. - Put8x8uv((byte)(dc0 >> 4), dst); + public static void DC8uv(Span dst, Span yuv, int offset) + { + int dc0 = 8; + int offsetMinus1 = offset - 1; + int offsetMinusBps = offset - WebpConstants.Bps; + for (int i = 0; i < 8; i++) + { + // dc0 += dst[i - BPS] + dst[-1 + i * BPS]; + dc0 += yuv[offsetMinusBps + i] + yuv[offsetMinus1 + (i * WebpConstants.Bps)]; } - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM8uv(Span dst, Span yuv, int offset) => - TrueMotion(dst, yuv, offset, 8); // TrueMotion + Put8x8uv((byte)(dc0 >> 4), dst); + } - public static void VE8uv(Span dst, Span yuv, int offset) - { - // vertical - Span src = yuv.Slice(offset - WebpConstants.Bps, 8); + [MethodImpl(InliningOptions.ShortMethod)] + public static void TM8uv(Span dst, Span yuv, int offset) => + TrueMotion(dst, yuv, offset, 8); // TrueMotion - const int endIdx = 8 * WebpConstants.Bps; - for (int j = 0; j < endIdx; j += WebpConstants.Bps) - { - // memcpy(dst + j * BPS, dst - BPS, 8); - src.CopyTo(dst[j..]); - } - } + public static void VE8uv(Span dst, Span yuv, int offset) + { + // vertical + Span src = yuv.Slice(offset - WebpConstants.Bps, 8); - public static void HE8uv(Span dst, Span yuv, int offset) + const int endIdx = 8 * WebpConstants.Bps; + for (int j = 0; j < endIdx; j += WebpConstants.Bps) { - // horizontal - offset--; - for (int j = 0; j < 8; j++) - { - // memset(dst, dst[-1], 8); - // dst += BPS; - byte v = yuv[offset]; - Memset(dst, v, 0, 8); - dst = dst[WebpConstants.Bps..]; - offset += WebpConstants.Bps; - } + // memcpy(dst + j * BPS, dst - BPS, 8); + src.CopyTo(dst[j..]); } + } - public static void DC8uvNoTop(Span dst, Span yuv, int offset) + public static void HE8uv(Span dst, Span yuv, int offset) + { + // horizontal + offset--; + for (int j = 0; j < 8; j++) { - // DC with no top samples. - int dc0 = 4; - int offsetMinusOne = offset - 1; - const int endIdx = 8 * WebpConstants.Bps; - for (int i = 0; i < endIdx; i += WebpConstants.Bps) - { - // dc0 += dst[-1 + i * BPS]; - dc0 += yuv[offsetMinusOne + i]; - } - - Put8x8uv((byte)(dc0 >> 3), dst); + // memset(dst, dst[-1], 8); + // dst += BPS; + byte v = yuv[offset]; + Memset(dst, v, 0, 8); + dst = dst[WebpConstants.Bps..]; + offset += WebpConstants.Bps; } + } - public static void DC8uvNoLeft(Span dst, Span yuv, int offset) + public static void DC8uvNoTop(Span dst, Span yuv, int offset) + { + // DC with no top samples. + int dc0 = 4; + int offsetMinusOne = offset - 1; + const int endIdx = 8 * WebpConstants.Bps; + for (int i = 0; i < endIdx; i += WebpConstants.Bps) { - // DC with no left samples. - int offsetMinusBps = offset - WebpConstants.Bps; - int dc0 = 4; - for (int i = 0; i < 8; i++) - { - // dc0 += dst[i - BPS]; - dc0 += yuv[offsetMinusBps + i]; - } + // dc0 += dst[-1 + i * BPS]; + dc0 += yuv[offsetMinusOne + i]; + } + + Put8x8uv((byte)(dc0 >> 3), dst); + } - Put8x8uv((byte)(dc0 >> 3), dst); + public static void DC8uvNoLeft(Span dst, Span yuv, int offset) + { + // DC with no left samples. + int offsetMinusBps = offset - WebpConstants.Bps; + int dc0 = 4; + for (int i = 0; i < 8; i++) + { + // dc0 += dst[i - BPS]; + dc0 += yuv[offsetMinusBps + i]; } - [MethodImpl(InliningOptions.ShortMethod)] - public static void DC8uvNoTopLeft(Span dst) => - Put8x8uv(0x80, dst); // DC with nothing. + Put8x8uv((byte)(dc0 >> 3), dst); + } - public static void DC4(Span dst, Span yuv, int offset) + [MethodImpl(InliningOptions.ShortMethod)] + public static void DC8uvNoTopLeft(Span dst) => + Put8x8uv(0x80, dst); // DC with nothing. + + public static void DC4(Span dst, Span yuv, int offset) + { + int dc = 4; + int offsetMinusBps = offset - WebpConstants.Bps; + int offsetMinusOne = offset - 1; + for (int i = 0; i < 4; i++) { - int dc = 4; - int offsetMinusBps = offset - WebpConstants.Bps; - int offsetMinusOne = offset - 1; - for (int i = 0; i < 4; i++) - { - dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebpConstants.Bps)]; - } + dc += yuv[offsetMinusBps + i] + yuv[offsetMinusOne + (i * WebpConstants.Bps)]; + } - dc >>= 3; - const int endIndx = 4 * WebpConstants.Bps; - for (int i = 0; i < endIndx; i += WebpConstants.Bps) - { - Memset(dst, (byte)dc, i, 4); - } + dc >>= 3; + const int endIndx = 4 * WebpConstants.Bps; + for (int i = 0; i < endIndx; i += WebpConstants.Bps) + { + Memset(dst, (byte)dc, i, 4); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void TM4(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 4); + [MethodImpl(InliningOptions.ShortMethod)] + public static void TM4(Span dst, Span yuv, int offset) => TrueMotion(dst, yuv, offset, 4); - public static void VE4(Span dst, Span yuv, int offset, Span vals) + public static void VE4(Span dst, Span yuv, int offset, Span vals) + { + // vertical + int topOffset = offset - WebpConstants.Bps; + vals[0] = Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]); + vals[1] = Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]); + vals[2] = Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]); + vals[3] = Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]); + const int endIdx = 4 * WebpConstants.Bps; + for (int i = 0; i < endIdx; i += WebpConstants.Bps) { - // vertical - int topOffset = offset - WebpConstants.Bps; - vals[0] = Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]); - vals[1] = Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]); - vals[2] = Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]); - vals[3] = Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4]); - const int endIdx = 4 * WebpConstants.Bps; - for (int i = 0; i < endIdx; i += WebpConstants.Bps) - { - vals.CopyTo(dst[i..]); - } + vals.CopyTo(dst[i..]); } + } - public static void HE4(Span dst, Span yuv, int offset) - { - // horizontal - int offsetMinusOne = offset - 1; - byte a = yuv[offsetMinusOne - WebpConstants.Bps]; - byte b = yuv[offsetMinusOne]; - byte c = yuv[offsetMinusOne + WebpConstants.Bps]; - byte d = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte e = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; - uint val = 0x01010101U * Avg3(a, b, c); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * Avg3(b, c, d); - BinaryPrimitives.WriteUInt32BigEndian(dst[WebpConstants.Bps..], val); - val = 0x01010101U * Avg3(c, d, e); - BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); - val = 0x01010101U * Avg3(d, e, e); - BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); - } - - public static void RD4(Span dst, Span yuv, int offset) - { - // Down-right - int offsetMinusOne = offset - 1; - byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte l = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - - Dst(dst, 0, 3, Avg3(j, k, l)); - byte ijk = Avg3(i, j, k); - Dst(dst, 1, 3, ijk); - Dst(dst, 0, 2, ijk); - byte xij = Avg3(x, i, j); - Dst(dst, 2, 3, xij); - Dst(dst, 1, 2, xij); - Dst(dst, 0, 1, xij); - byte axi = Avg3(a, x, i); - Dst(dst, 3, 3, axi); - Dst(dst, 2, 2, axi); - Dst(dst, 1, 1, axi); - Dst(dst, 0, 0, axi); - byte bax = Avg3(b, a, x); - Dst(dst, 3, 2, bax); - Dst(dst, 2, 1, bax); - Dst(dst, 1, 0, bax); - byte cba = Avg3(c, b, a); - Dst(dst, 3, 1, cba); - Dst(dst, 2, 0, cba); - Dst(dst, 3, 0, Avg3(d, c, b)); - } - - public static void VR4(Span dst, Span yuv, int offset) - { - // Vertical-Right - int offsetMinusOne = offset - 1; - byte i = yuv[offsetMinusOne]; - byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; - byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; - byte x = yuv[offsetMinusOne - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - - byte xa = Avg2(x, a); - Dst(dst, 0, 0, xa); - Dst(dst, 1, 2, xa); - byte ab = Avg2(a, b); - Dst(dst, 1, 0, ab); - Dst(dst, 2, 2, ab); - byte bc = Avg2(b, c); - Dst(dst, 2, 0, bc); - Dst(dst, 3, 2, bc); - Dst(dst, 3, 0, Avg2(c, d)); - Dst(dst, 0, 3, Avg3(k, j, i)); - Dst(dst, 0, 2, Avg3(j, i, x)); - byte ixa = Avg3(i, x, a); - Dst(dst, 0, 1, ixa); - Dst(dst, 1, 3, ixa); - byte xab = Avg3(x, a, b); - Dst(dst, 1, 1, xab); - Dst(dst, 2, 3, xab); - byte abc = Avg3(a, b, c); - Dst(dst, 2, 1, abc); - Dst(dst, 3, 3, abc); - Dst(dst, 3, 1, Avg3(b, c, d)); - } - - public static void LD4(Span dst, Span yuv, int offset) - { - // Down-Left - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - byte e = yuv[offset + 4 - WebpConstants.Bps]; - byte f = yuv[offset + 5 - WebpConstants.Bps]; - byte g = yuv[offset + 6 - WebpConstants.Bps]; - byte h = yuv[offset + 7 - WebpConstants.Bps]; - - Dst(dst, 0, 0, Avg3(a, b, c)); - byte bcd = Avg3(b, c, d); - Dst(dst, 1, 0, bcd); - Dst(dst, 0, 1, bcd); - byte cde = Avg3(c, d, e); - Dst(dst, 2, 0, cde); - Dst(dst, 1, 1, cde); - Dst(dst, 0, 2, cde); - byte def = Avg3(d, e, f); - Dst(dst, 3, 0, def); - Dst(dst, 2, 1, def); - Dst(dst, 1, 2, def); - Dst(dst, 0, 3, def); - byte efg = Avg3(e, f, g); - Dst(dst, 3, 1, efg); - Dst(dst, 2, 2, efg); - Dst(dst, 1, 3, efg); - byte fgh = Avg3(f, g, h); - Dst(dst, 3, 2, fgh); - Dst(dst, 2, 3, fgh); - Dst(dst, 3, 3, Avg3(g, h, h)); - } - - public static void VL4(Span dst, Span yuv, int offset) - { - // Vertical-Left - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - byte d = yuv[offset + 3 - WebpConstants.Bps]; - byte e = yuv[offset + 4 - WebpConstants.Bps]; - byte f = yuv[offset + 5 - WebpConstants.Bps]; - byte g = yuv[offset + 6 - WebpConstants.Bps]; - byte h = yuv[offset + 7 - WebpConstants.Bps]; - - Dst(dst, 0, 0, Avg2(a, b)); - byte bc = Avg2(b, c); - Dst(dst, 1, 0, bc); - Dst(dst, 0, 2, bc); - byte cd = Avg2(c, d); - Dst(dst, 2, 0, cd); - Dst(dst, 1, 2, cd); - byte de = Avg2(d, e); - Dst(dst, 3, 0, de); - Dst(dst, 2, 2, de); - Dst(dst, 0, 1, Avg3(a, b, c)); - byte bcd = Avg3(b, c, d); - Dst(dst, 1, 1, bcd); - Dst(dst, 0, 3, bcd); - byte cde = Avg3(c, d, e); - Dst(dst, 2, 1, cde); - Dst(dst, 1, 3, cde); - byte def = Avg3(d, e, f); - Dst(dst, 3, 1, def); - Dst(dst, 2, 3, def); - Dst(dst, 3, 2, Avg3(e, f, g)); - Dst(dst, 3, 3, Avg3(f, g, h)); - } - - public static void HD4(Span dst, Span yuv, int offset) - { - // Horizontal-Down - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; - byte x = yuv[offset - 1 - WebpConstants.Bps]; - byte a = yuv[offset - WebpConstants.Bps]; - byte b = yuv[offset + 1 - WebpConstants.Bps]; - byte c = yuv[offset + 2 - WebpConstants.Bps]; - - byte ix = Avg2(i, x); - Dst(dst, 0, 0, ix); - Dst(dst, 2, 1, ix); - byte ji = Avg2(j, i); - Dst(dst, 0, 1, ji); - Dst(dst, 2, 2, ji); - byte kj = Avg2(k, j); - Dst(dst, 0, 2, kj); - Dst(dst, 2, 3, kj); - Dst(dst, 0, 3, Avg2(l, k)); - Dst(dst, 3, 0, Avg3(a, b, c)); - Dst(dst, 2, 0, Avg3(x, a, b)); - byte ixa = Avg3(i, x, a); - Dst(dst, 1, 0, ixa); - Dst(dst, 3, 1, ixa); - byte jix = Avg3(j, i, x); - Dst(dst, 1, 1, jix); - Dst(dst, 3, 2, jix); - byte kji = Avg3(k, j, i); - Dst(dst, 1, 2, kji); - Dst(dst, 3, 3, kji); - Dst(dst, 1, 3, Avg3(l, k, j)); - } - - public static void HU4(Span dst, Span yuv, int offset) - { - // Horizontal-Up - byte i = yuv[offset - 1]; - byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; - byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; - byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; - - Dst(dst, 0, 0, Avg2(i, j)); - byte jk = Avg2(j, k); - Dst(dst, 2, 0, jk); - Dst(dst, 0, 1, jk); - byte kl = Avg2(k, l); - Dst(dst, 2, 1, kl); - Dst(dst, 0, 2, kl); - Dst(dst, 1, 0, Avg3(i, j, k)); - byte jkl = Avg3(j, k, l); - Dst(dst, 3, 0, jkl); - Dst(dst, 1, 1, jkl); - byte kll = Avg3(k, l, l); - Dst(dst, 3, 1, kll); - Dst(dst, 1, 2, kll); - Dst(dst, 3, 2, l); - Dst(dst, 2, 2, l); - Dst(dst, 0, 3, l); - Dst(dst, 1, 3, l); - Dst(dst, 2, 3, l); - Dst(dst, 3, 3, l); - } - - /// - /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. - /// - public static void TransformWht(Span input, Span output, Span scratch) - { - Span tmp = scratch[..16]; - tmp.Clear(); - for (int i = 0; i < 4; i++) - { - int iPlus4 = 4 + i; - int iPlus8 = 8 + i; - int iPlus12 = 12 + i; - int a0 = input[i] + input[iPlus12]; - int a1 = input[iPlus4] + input[iPlus8]; - int a2 = input[iPlus4] - input[iPlus8]; - int a3 = input[i] - input[iPlus12]; - tmp[i] = a0 + a1; - tmp[iPlus8] = a0 - a1; - tmp[iPlus4] = a3 + a2; - tmp[iPlus12] = a3 - a2; - } + public static void HE4(Span dst, Span yuv, int offset) + { + // horizontal + int offsetMinusOne = offset - 1; + byte a = yuv[offsetMinusOne - WebpConstants.Bps]; + byte b = yuv[offsetMinusOne]; + byte c = yuv[offsetMinusOne + WebpConstants.Bps]; + byte d = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte e = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; + uint val = 0x01010101U * Avg3(a, b, c); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * Avg3(b, c, d); + BinaryPrimitives.WriteUInt32BigEndian(dst[WebpConstants.Bps..], val); + val = 0x01010101U * Avg3(c, d, e); + BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); + val = 0x01010101U * Avg3(d, e, e); + BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); + } - int outputOffset = 0; - for (int i = 0; i < 4; i++) - { - int imul4 = i * 4; - int dc = tmp[0 + imul4] + 3; - int a0 = dc + tmp[3 + imul4]; - int a1 = tmp[1 + imul4] + tmp[2 + imul4]; - int a2 = tmp[1 + imul4] - tmp[2 + imul4]; - int a3 = dc - tmp[3 + imul4]; - output[outputOffset + 0] = (short)((a0 + a1) >> 3); - output[outputOffset + 16] = (short)((a3 + a2) >> 3); - output[outputOffset + 32] = (short)((a0 - a1) >> 3); - output[outputOffset + 48] = (short)((a3 - a2) >> 3); - outputOffset += 64; - } + public static void RD4(Span dst, Span yuv, int offset) + { + // Down-right + int offsetMinusOne = offset - 1; + byte i = yuv[offsetMinusOne]; + byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte l = yuv[offsetMinusOne + (3 * WebpConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + + Dst(dst, 0, 3, Avg3(j, k, l)); + byte ijk = Avg3(i, j, k); + Dst(dst, 1, 3, ijk); + Dst(dst, 0, 2, ijk); + byte xij = Avg3(x, i, j); + Dst(dst, 2, 3, xij); + Dst(dst, 1, 2, xij); + Dst(dst, 0, 1, xij); + byte axi = Avg3(a, x, i); + Dst(dst, 3, 3, axi); + Dst(dst, 2, 2, axi); + Dst(dst, 1, 1, axi); + Dst(dst, 0, 0, axi); + byte bax = Avg3(b, a, x); + Dst(dst, 3, 2, bax); + Dst(dst, 2, 1, bax); + Dst(dst, 1, 0, bax); + byte cba = Avg3(c, b, a); + Dst(dst, 3, 1, cba); + Dst(dst, 2, 0, cba); + Dst(dst, 3, 0, Avg3(d, c, b)); + } + + public static void VR4(Span dst, Span yuv, int offset) + { + // Vertical-Right + int offsetMinusOne = offset - 1; + byte i = yuv[offsetMinusOne]; + byte j = yuv[offsetMinusOne + (1 * WebpConstants.Bps)]; + byte k = yuv[offsetMinusOne + (2 * WebpConstants.Bps)]; + byte x = yuv[offsetMinusOne - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + + byte xa = Avg2(x, a); + Dst(dst, 0, 0, xa); + Dst(dst, 1, 2, xa); + byte ab = Avg2(a, b); + Dst(dst, 1, 0, ab); + Dst(dst, 2, 2, ab); + byte bc = Avg2(b, c); + Dst(dst, 2, 0, bc); + Dst(dst, 3, 2, bc); + Dst(dst, 3, 0, Avg2(c, d)); + Dst(dst, 0, 3, Avg3(k, j, i)); + Dst(dst, 0, 2, Avg3(j, i, x)); + byte ixa = Avg3(i, x, a); + Dst(dst, 0, 1, ixa); + Dst(dst, 1, 3, ixa); + byte xab = Avg3(x, a, b); + Dst(dst, 1, 1, xab); + Dst(dst, 2, 3, xab); + byte abc = Avg3(a, b, c); + Dst(dst, 2, 1, abc); + Dst(dst, 3, 3, abc); + Dst(dst, 3, 1, Avg3(b, c, d)); + } + + public static void LD4(Span dst, Span yuv, int offset) + { + // Down-Left + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + byte e = yuv[offset + 4 - WebpConstants.Bps]; + byte f = yuv[offset + 5 - WebpConstants.Bps]; + byte g = yuv[offset + 6 - WebpConstants.Bps]; + byte h = yuv[offset + 7 - WebpConstants.Bps]; + + Dst(dst, 0, 0, Avg3(a, b, c)); + byte bcd = Avg3(b, c, d); + Dst(dst, 1, 0, bcd); + Dst(dst, 0, 1, bcd); + byte cde = Avg3(c, d, e); + Dst(dst, 2, 0, cde); + Dst(dst, 1, 1, cde); + Dst(dst, 0, 2, cde); + byte def = Avg3(d, e, f); + Dst(dst, 3, 0, def); + Dst(dst, 2, 1, def); + Dst(dst, 1, 2, def); + Dst(dst, 0, 3, def); + byte efg = Avg3(e, f, g); + Dst(dst, 3, 1, efg); + Dst(dst, 2, 2, efg); + Dst(dst, 1, 3, efg); + byte fgh = Avg3(f, g, h); + Dst(dst, 3, 2, fgh); + Dst(dst, 2, 3, fgh); + Dst(dst, 3, 3, Avg3(g, h, h)); + } + + public static void VL4(Span dst, Span yuv, int offset) + { + // Vertical-Left + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + byte d = yuv[offset + 3 - WebpConstants.Bps]; + byte e = yuv[offset + 4 - WebpConstants.Bps]; + byte f = yuv[offset + 5 - WebpConstants.Bps]; + byte g = yuv[offset + 6 - WebpConstants.Bps]; + byte h = yuv[offset + 7 - WebpConstants.Bps]; + + Dst(dst, 0, 0, Avg2(a, b)); + byte bc = Avg2(b, c); + Dst(dst, 1, 0, bc); + Dst(dst, 0, 2, bc); + byte cd = Avg2(c, d); + Dst(dst, 2, 0, cd); + Dst(dst, 1, 2, cd); + byte de = Avg2(d, e); + Dst(dst, 3, 0, de); + Dst(dst, 2, 2, de); + Dst(dst, 0, 1, Avg3(a, b, c)); + byte bcd = Avg3(b, c, d); + Dst(dst, 1, 1, bcd); + Dst(dst, 0, 3, bcd); + byte cde = Avg3(c, d, e); + Dst(dst, 2, 1, cde); + Dst(dst, 1, 3, cde); + byte def = Avg3(d, e, f); + Dst(dst, 3, 1, def); + Dst(dst, 2, 3, def); + Dst(dst, 3, 2, Avg3(e, f, g)); + Dst(dst, 3, 3, Avg3(f, g, h)); + } + + public static void HD4(Span dst, Span yuv, int offset) + { + // Horizontal-Down + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; + byte x = yuv[offset - 1 - WebpConstants.Bps]; + byte a = yuv[offset - WebpConstants.Bps]; + byte b = yuv[offset + 1 - WebpConstants.Bps]; + byte c = yuv[offset + 2 - WebpConstants.Bps]; + + byte ix = Avg2(i, x); + Dst(dst, 0, 0, ix); + Dst(dst, 2, 1, ix); + byte ji = Avg2(j, i); + Dst(dst, 0, 1, ji); + Dst(dst, 2, 2, ji); + byte kj = Avg2(k, j); + Dst(dst, 0, 2, kj); + Dst(dst, 2, 3, kj); + Dst(dst, 0, 3, Avg2(l, k)); + Dst(dst, 3, 0, Avg3(a, b, c)); + Dst(dst, 2, 0, Avg3(x, a, b)); + byte ixa = Avg3(i, x, a); + Dst(dst, 1, 0, ixa); + Dst(dst, 3, 1, ixa); + byte jix = Avg3(j, i, x); + Dst(dst, 1, 1, jix); + Dst(dst, 3, 2, jix); + byte kji = Avg3(k, j, i); + Dst(dst, 1, 2, kji); + Dst(dst, 3, 3, kji); + Dst(dst, 1, 3, Avg3(l, k, j)); + } + + public static void HU4(Span dst, Span yuv, int offset) + { + // Horizontal-Up + byte i = yuv[offset - 1]; + byte j = yuv[offset - 1 + (1 * WebpConstants.Bps)]; + byte k = yuv[offset - 1 + (2 * WebpConstants.Bps)]; + byte l = yuv[offset - 1 + (3 * WebpConstants.Bps)]; + + Dst(dst, 0, 0, Avg2(i, j)); + byte jk = Avg2(j, k); + Dst(dst, 2, 0, jk); + Dst(dst, 0, 1, jk); + byte kl = Avg2(k, l); + Dst(dst, 2, 1, kl); + Dst(dst, 0, 2, kl); + Dst(dst, 1, 0, Avg3(i, j, k)); + byte jkl = Avg3(j, k, l); + Dst(dst, 3, 0, jkl); + Dst(dst, 1, 1, jkl); + byte kll = Avg3(k, l, l); + Dst(dst, 3, 1, kll); + Dst(dst, 1, 2, kll); + Dst(dst, 3, 2, l); + Dst(dst, 2, 2, l); + Dst(dst, 0, 3, l); + Dst(dst, 1, 3, l); + Dst(dst, 2, 3, l); + Dst(dst, 3, 3, l); + } + + /// + /// Paragraph 14.3: Implementation of the Walsh-Hadamard transform inversion. + /// + public static void TransformWht(Span input, Span output, Span scratch) + { + Span tmp = scratch[..16]; + tmp.Clear(); + for (int i = 0; i < 4; i++) + { + int iPlus4 = 4 + i; + int iPlus8 = 8 + i; + int iPlus12 = 12 + i; + int a0 = input[i] + input[iPlus12]; + int a1 = input[iPlus4] + input[iPlus8]; + int a2 = input[iPlus4] - input[iPlus8]; + int a3 = input[i] - input[iPlus12]; + tmp[i] = a0 + a1; + tmp[iPlus8] = a0 - a1; + tmp[iPlus4] = a3 + a2; + tmp[iPlus12] = a3 - a2; + } + + int outputOffset = 0; + for (int i = 0; i < 4; i++) + { + int imul4 = i * 4; + int dc = tmp[0 + imul4] + 3; + int a0 = dc + tmp[3 + imul4]; + int a1 = tmp[1 + imul4] + tmp[2 + imul4]; + int a2 = tmp[1 + imul4] - tmp[2 + imul4]; + int a3 = dc - tmp[3 + imul4]; + output[outputOffset + 0] = (short)((a0 + a1) >> 3); + output[outputOffset + 16] = (short)((a3 + a2) >> 3); + output[outputOffset + 32] = (short)((a0 - a1) >> 3); + output[outputOffset + 48] = (short)((a3 - a2) >> 3); + outputOffset += 64; } + } - /// - /// Hadamard transform - /// Returns the weighted sum of the absolute value of transformed coefficients. - /// w[] contains a row-major 4 by 4 symmetric matrix. - /// - public static int TTransform(Span input, Span w, Span scratch) - { - int sum = 0; - Span tmp = scratch[..16]; - tmp.Clear(); + /// + /// Hadamard transform + /// Returns the weighted sum of the absolute value of transformed coefficients. + /// w[] contains a row-major 4 by 4 symmetric matrix. + /// + public static int TTransform(Span input, Span w, Span scratch) + { + int sum = 0; + Span tmp = scratch[..16]; + tmp.Clear(); + + // horizontal pass. + int inputOffset = 0; + for (int i = 0; i < 4; i++) + { + int inputOffsetPlusOne = inputOffset + 1; + int inputOffsetPlusTwo = inputOffset + 2; + int inputOffsetPlusThree = inputOffset + 3; + int a0 = input[inputOffset] + input[inputOffsetPlusTwo]; + int a1 = input[inputOffsetPlusOne] + input[inputOffsetPlusThree]; + int a2 = input[inputOffsetPlusOne] - input[inputOffsetPlusThree]; + int a3 = input[inputOffset] - input[inputOffsetPlusTwo]; + tmp[0 + (i * 4)] = a0 + a1; + tmp[1 + (i * 4)] = a3 + a2; + tmp[2 + (i * 4)] = a3 - a2; + tmp[3 + (i * 4)] = a0 - a1; + + inputOffset += WebpConstants.Bps; + } + + // vertical pass + for (int i = 0; i < 4; i++) + { + int a0 = tmp[0 + i] + tmp[8 + i]; + int a1 = tmp[4 + i] + tmp[12 + i]; + int a2 = tmp[4 + i] - tmp[12 + i]; + int a3 = tmp[0 + i] - tmp[8 + i]; + int b0 = a0 + a1; + int b1 = a3 + a2; + int b2 = a3 - a2; + int b3 = a0 - a1; + + sum += w[0] * Math.Abs(b0); + sum += w[4] * Math.Abs(b1); + sum += w[8] * Math.Abs(b2); + sum += w[12] * Math.Abs(b3); + + w = w[1..]; + } + + return sum; + } - // horizontal pass. - int inputOffset = 0; - for (int i = 0; i < 4; i++) - { - int inputOffsetPlusOne = inputOffset + 1; - int inputOffsetPlusTwo = inputOffset + 2; - int inputOffsetPlusThree = inputOffset + 3; - int a0 = input[inputOffset] + input[inputOffsetPlusTwo]; - int a1 = input[inputOffsetPlusOne] + input[inputOffsetPlusThree]; - int a2 = input[inputOffsetPlusOne] - input[inputOffsetPlusThree]; - int a3 = input[inputOffset] - input[inputOffsetPlusTwo]; - tmp[0 + (i * 4)] = a0 + a1; - tmp[1 + (i * 4)] = a3 + a2; - tmp[2 + (i * 4)] = a3 - a2; - tmp[3 + (i * 4)] = a0 - a1; - - inputOffset += WebpConstants.Bps; - } + /// + /// Hadamard transform + /// Returns the weighted sum of the absolute value of transformed coefficients. + /// w[] contains a row-major 4 by 4 symmetric matrix. + /// + public static int TTransformSse41(Span inputA, Span inputB, Span w) + { + // Load and combine inputs. + Vector128 ina0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA)); + Vector128 ina1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps, 16))); + Vector128 ina2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 2, 16))); + Vector128 ina3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); + Vector128 inb0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB)); + Vector128 inb1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps, 16))); + Vector128 inb2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 2, 16))); + Vector128 inb3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); + + // Combine inA and inB (we'll do two transforms in parallel). + Vector128 inab0 = Sse2.UnpackLow(ina0.AsInt32(), inb0.AsInt32()); + Vector128 inab1 = Sse2.UnpackLow(ina1.AsInt32(), inb1.AsInt32()); + Vector128 inab2 = Sse2.UnpackLow(ina2.AsInt32(), inb2.AsInt32()); + Vector128 inab3 = Sse2.UnpackLow(ina3.AsInt32(), inb3.AsInt32()); + Vector128 tmp0 = Sse41.ConvertToVector128Int16(inab0.AsByte()); + Vector128 tmp1 = Sse41.ConvertToVector128Int16(inab1.AsByte()); + Vector128 tmp2 = Sse41.ConvertToVector128Int16(inab2.AsByte()); + Vector128 tmp3 = Sse41.ConvertToVector128Int16(inab3.AsByte()); + + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent transpose. + // Calculate a and b (two 4x4 at once). + Vector128 a0 = Sse2.Add(tmp0, tmp2); + Vector128 a1 = Sse2.Add(tmp1, tmp3); + Vector128 a2 = Sse2.Subtract(tmp1, tmp3); + Vector128 a3 = Sse2.Subtract(tmp0, tmp2); + Vector128 b0 = Sse2.Add(a0, a1); + Vector128 b1 = Sse2.Add(a3, a2); + Vector128 b2 = Sse2.Subtract(a3, a2); + Vector128 b3 = Sse2.Subtract(a0, a1); + + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(b0, b1, b2, b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3); + + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + // Horizontal pass and difference of weighted sums. + Vector128 w0 = Unsafe.As>(ref MemoryMarshal.GetReference(w)); + Vector128 w8 = Unsafe.As>(ref MemoryMarshal.GetReference(w.Slice(8, 8))); + + // Calculate a and b (two 4x4 at once). + a0 = Sse2.Add(output0.AsInt16(), output2.AsInt16()); + a1 = Sse2.Add(output1.AsInt16(), output3.AsInt16()); + a2 = Sse2.Subtract(output1.AsInt16(), output3.AsInt16()); + a3 = Sse2.Subtract(output0.AsInt16(), output2.AsInt16()); + b0 = Sse2.Add(a0, a1); + b1 = Sse2.Add(a3, a2); + b2 = Sse2.Subtract(a3, a2); + b3 = Sse2.Subtract(a0, a1); + + // Separate the transforms of inA and inB. + Vector128 ab0 = Sse2.UnpackLow(b0.AsInt64(), b1.AsInt64()); + Vector128 ab2 = Sse2.UnpackLow(b2.AsInt64(), b3.AsInt64()); + Vector128 bb0 = Sse2.UnpackHigh(b0.AsInt64(), b1.AsInt64()); + Vector128 bb2 = Sse2.UnpackHigh(b2.AsInt64(), b3.AsInt64()); + + Vector128 ab0Abs = Ssse3.Abs(ab0.AsInt16()); + Vector128 ab2Abs = Ssse3.Abs(ab2.AsInt16()); + Vector128 b0Abs = Ssse3.Abs(bb0.AsInt16()); + Vector128 bb2Abs = Ssse3.Abs(bb2.AsInt16()); + + // weighted sums. + Vector128 ab0mulw0 = Sse2.MultiplyAddAdjacent(ab0Abs.AsInt16(), w0.AsInt16()); + Vector128 ab2mulw8 = Sse2.MultiplyAddAdjacent(ab2Abs.AsInt16(), w8.AsInt16()); + Vector128 b0mulw0 = Sse2.MultiplyAddAdjacent(b0Abs.AsInt16(), w0.AsInt16()); + Vector128 bb2mulw8 = Sse2.MultiplyAddAdjacent(bb2Abs.AsInt16(), w8.AsInt16()); + Vector128 ab0ab2Sum = Sse2.Add(ab0mulw0, ab2mulw8); + Vector128 b0w0bb2w8Sum = Sse2.Add(b0mulw0, bb2mulw8); + + // difference of weighted sums. + Vector128 result = Sse2.Subtract(ab0ab2Sum.AsInt32(), b0w0bb2w8Sum.AsInt32()); + + return Numerics.ReduceSum(result); + } - // vertical pass - for (int i = 0; i < 4; i++) - { - int a0 = tmp[0 + i] + tmp[8 + i]; - int a1 = tmp[4 + i] + tmp[12 + i]; - int a2 = tmp[4 + i] - tmp[12 + i]; - int a3 = tmp[0 + i] - tmp[8 + i]; - int b0 = a0 + a1; - int b1 = a3 + a2; - int b2 = a3 - a2; - int b3 = a0 - a1; - - sum += w[0] * Math.Abs(b0); - sum += w[4] * Math.Abs(b1); - sum += w[8] * Math.Abs(b2); - sum += w[12] * Math.Abs(b3); - - w = w[1..]; - } + // Transpose two 4x4 16b matrices horizontally stored in registers. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Vp8Transpose_2_4x4_16b(Vector128 b0, Vector128 b1, Vector128 b2, Vector128 b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3) + { + // Transpose the two 4x4. + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + Vector128 transpose00 = Sse2.UnpackLow(b0, b1); + Vector128 transpose01 = Sse2.UnpackLow(b2, b3); + Vector128 transpose02 = Sse2.UnpackHigh(b0, b1); + Vector128 transpose03 = Sse2.UnpackHigh(b2, b3); + + // a00 a10 a01 a11 a02 a12 a03 a13 + // a20 a30 a21 a31 a22 a32 a23 a33 + // b00 b10 b01 b11 b02 b12 b03 b13 + // b20 b30 b21 b31 b22 b32 b23 b33 + Vector128 transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32()); + Vector128 transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32()); + Vector128 transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32()); + Vector128 transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32()); + + // a00 a10 a20 a30 a01 a11 a21 a31 + // b00 b10 b20 b30 b01 b11 b21 b31 + // a02 a12 a22 a32 a03 a13 a23 a33 + // b02 b12 a22 b32 b03 b13 b23 b33 + output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64()); + output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64()); + output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64()); + output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64()); + + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + } - return sum; - } - - /// - /// Hadamard transform - /// Returns the weighted sum of the absolute value of transformed coefficients. - /// w[] contains a row-major 4 by 4 symmetric matrix. - /// - public static int TTransformSse41(Span inputA, Span inputB, Span w) - { - // Load and combine inputs. - Vector128 ina0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA)); - Vector128 ina1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps, 16))); - Vector128 ina2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 2, 16))); - Vector128 ina3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputA.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); - Vector128 inb0 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB)); - Vector128 inb1 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps, 16))); - Vector128 inb2 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 2, 16))); - Vector128 inb3 = Unsafe.As>(ref MemoryMarshal.GetReference(inputB.Slice(WebpConstants.Bps * 3, 16))).AsInt64(); - - // Combine inA and inB (we'll do two transforms in parallel). - Vector128 inab0 = Sse2.UnpackLow(ina0.AsInt32(), inb0.AsInt32()); - Vector128 inab1 = Sse2.UnpackLow(ina1.AsInt32(), inb1.AsInt32()); - Vector128 inab2 = Sse2.UnpackLow(ina2.AsInt32(), inb2.AsInt32()); - Vector128 inab3 = Sse2.UnpackLow(ina3.AsInt32(), inb3.AsInt32()); - Vector128 tmp0 = Sse41.ConvertToVector128Int16(inab0.AsByte()); - Vector128 tmp1 = Sse41.ConvertToVector128Int16(inab1.AsByte()); - Vector128 tmp2 = Sse41.ConvertToVector128Int16(inab2.AsByte()); - Vector128 tmp3 = Sse41.ConvertToVector128Int16(inab3.AsByte()); - - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - // Vertical pass first to avoid a transpose (vertical and horizontal passes - // are commutative because w/kWeightY is symmetric) and subsequent transpose. - // Calculate a and b (two 4x4 at once). - Vector128 a0 = Sse2.Add(tmp0, tmp2); - Vector128 a1 = Sse2.Add(tmp1, tmp3); - Vector128 a2 = Sse2.Subtract(tmp1, tmp3); - Vector128 a3 = Sse2.Subtract(tmp0, tmp2); - Vector128 b0 = Sse2.Add(a0, a1); - Vector128 b1 = Sse2.Add(a3, a2); - Vector128 b2 = Sse2.Subtract(a3, a2); - Vector128 b3 = Sse2.Subtract(a0, a1); - - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16b(b0, b1, b2, b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3); + // Transforms (Paragraph 14.4). + // Does two transforms. + public static void TransformTwo(Span src, Span dst, Span scratch) + { + if (Sse2.IsSupported) + { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + + // Load and concatenate the transform coefficients (we'll do two transforms + // in parallel). + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 16)), 0); + var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 20)), 0); + var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 24)), 0); + var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 28)), 0); + + in0 = Sse2.UnpackLow(in0, inb0); + in1 = Sse2.UnpackLow(in1, inb1); + in2 = Sse2.UnpackLow(in2, inb2); + in3 = Sse2.UnpackLow(in3, inb3); // a00 a10 a20 a30 b00 b10 b20 b30 // a01 a11 a21 a31 b01 b11 b21 b31 // a02 a12 a22 a32 b02 b12 b22 b32 // a03 a13 a23 a33 b03 b13 b23 b33 - // Horizontal pass and difference of weighted sums. - Vector128 w0 = Unsafe.As>(ref MemoryMarshal.GetReference(w)); - Vector128 w8 = Unsafe.As>(ref MemoryMarshal.GetReference(w.Slice(8, 8))); - - // Calculate a and b (two 4x4 at once). - a0 = Sse2.Add(output0.AsInt16(), output2.AsInt16()); - a1 = Sse2.Add(output1.AsInt16(), output3.AsInt16()); - a2 = Sse2.Subtract(output1.AsInt16(), output3.AsInt16()); - a3 = Sse2.Subtract(output0.AsInt16(), output2.AsInt16()); - b0 = Sse2.Add(a0, a1); - b1 = Sse2.Add(a3, a2); - b2 = Sse2.Subtract(a3, a2); - b3 = Sse2.Subtract(a0, a1); - - // Separate the transforms of inA and inB. - Vector128 ab0 = Sse2.UnpackLow(b0.AsInt64(), b1.AsInt64()); - Vector128 ab2 = Sse2.UnpackLow(b2.AsInt64(), b3.AsInt64()); - Vector128 bb0 = Sse2.UnpackHigh(b0.AsInt64(), b1.AsInt64()); - Vector128 bb2 = Sse2.UnpackHigh(b2.AsInt64(), b3.AsInt64()); - - Vector128 ab0Abs = Ssse3.Abs(ab0.AsInt16()); - Vector128 ab2Abs = Ssse3.Abs(ab2.AsInt16()); - Vector128 b0Abs = Ssse3.Abs(bb0.AsInt16()); - Vector128 bb2Abs = Ssse3.Abs(bb2.AsInt16()); - - // weighted sums. - Vector128 ab0mulw0 = Sse2.MultiplyAddAdjacent(ab0Abs.AsInt16(), w0.AsInt16()); - Vector128 ab2mulw8 = Sse2.MultiplyAddAdjacent(ab2Abs.AsInt16(), w8.AsInt16()); - Vector128 b0mulw0 = Sse2.MultiplyAddAdjacent(b0Abs.AsInt16(), w0.AsInt16()); - Vector128 bb2mulw8 = Sse2.MultiplyAddAdjacent(bb2Abs.AsInt16(), w8.AsInt16()); - Vector128 ab0ab2Sum = Sse2.Add(ab0mulw0, ab2mulw8); - Vector128 b0w0bb2w8Sum = Sse2.Add(b0mulw0, bb2mulw8); - - // difference of weighted sums. - Vector128 result = Sse2.Subtract(ab0ab2Sum.AsInt32(), b0w0bb2w8Sum.AsInt32()); - - return Numerics.ReduceSum(result); - } - - // Transpose two 4x4 16b matrices horizontally stored in registers. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Vp8Transpose_2_4x4_16b(Vector128 b0, Vector128 b1, Vector128 b2, Vector128 b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3) - { + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + var k1 = Vector128.Create((short)20091); + var k2 = Vector128.Create((short)-30068); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); + // Transpose the two 4x4. - // a00 a01 a02 a03 b00 b01 b02 b03 - // a10 a11 a12 a13 b10 b11 b12 b13 - // a20 a21 a22 a23 b20 b21 b22 b23 - // a30 a31 a32 a33 b30 b31 b32 b33 - Vector128 transpose00 = Sse2.UnpackLow(b0, b1); - Vector128 transpose01 = Sse2.UnpackLow(b2, b3); - Vector128 transpose02 = Sse2.UnpackHigh(b0, b1); - Vector128 transpose03 = Sse2.UnpackHigh(b2, b3); - - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - Vector128 transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32()); - Vector128 transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32()); - - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64()); - output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64()); - output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64()); - output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64()); + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load eight bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Vector128.Create(Unsafe.As(ref dstRef), 0).AsByte(); + Vector128 dst1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte(); + Vector128 dst2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte(); + Vector128 dst3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + Unsafe.As>(ref outputRef) = dst0.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower(); + } + else + { + TransformOne(src, dst, scratch); + TransformOne(src[16..], dst[4..], scratch); } + } - // Transforms (Paragraph 14.4). - // Does two transforms. - public static void TransformTwo(Span src, Span dst, Span scratch) - { - if (Sse2.IsSupported) - { - // This implementation makes use of 16-bit fixed point versions of two - // multiply constants: - // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 - // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 - // - // To be able to use signed 16-bit integers, we use the following trick to - // have constants within range: - // - Associated constants are obtained by subtracting the 16-bit fixed point - // version of one: - // k = K - (1 << 16) => K = k + (1 << 16) - // K1 = 85267 => k1 = 20091 - // K2 = 35468 => k2 = -30068 - // - The multiplication of a variable by a constant become the sum of the - // variable and the multiplication of that variable by the associated - // constant: - // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x - - // Load and concatenate the transform coefficients (we'll do two transforms - // in parallel). - ref short srcRef = ref MemoryMarshal.GetReference(src); - var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); - var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); - var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 16)), 0); - var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 20)), 0); - var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 24)), 0); - var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 28)), 0); - - in0 = Sse2.UnpackLow(in0, inb0); - in1 = Sse2.UnpackLow(in1, inb1); - in2 = Sse2.UnpackLow(in2, inb2); - in3 = Sse2.UnpackLow(in3, inb3); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); - Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); - - var k1 = Vector128.Create((short)20091); - var k2 = Vector128.Create((short)-30068); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); - Vector128 c4 = Sse2.Subtract(c1, c2); - Vector128 c = Sse2.Add(c3.AsInt16(), c4); - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); - Vector128 d4 = Sse2.Add(d1, d2); - Vector128 d = Sse2.Add(d3, d4); - - // Second pass. - Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); - Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); - Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); - Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); - a = Sse2.Add(dc, t2.AsInt16()); - b = Sse2.Subtract(dc, t2.AsInt16()); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); - c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); - c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); - c4 = Sse2.Subtract(c1, c2); - c = Sse2.Add(c3, c4); - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); - d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); - d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); - d4 = Sse2.Add(d1, d2); - d = Sse2.Add(d3, d4); - - // Second pass. - tmp0 = Sse2.Add(a, d); - tmp1 = Sse2.Add(b, c); - tmp2 = Sse2.Subtract(b, c); - tmp3 = Sse2.Subtract(a, d); - Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); - Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); - Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); - Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'dst' and store. - // Load the reference(s). - // Load eight bytes/pixels per line. - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - Vector128 dst0 = Vector128.Create(Unsafe.As(ref dstRef), 0).AsByte(); - Vector128 dst1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte(); - Vector128 dst2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte(); - Vector128 dst3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte(); - - // Convert to 16b. - dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); - dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); - dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); - dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); - - // Add the inverse transform(s). - dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); - dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); - dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); - dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); - - // Unsigned saturate to 8b. - dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); - dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); - dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); - dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); - - // Store the results. - // Store eight bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - Unsafe.As>(ref outputRef) = dst0.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower(); - } - else - { - TransformOne(src, dst, scratch); - TransformOne(src[16..], dst[4..], scratch); - } - } + public static void TransformOne(Span src, Span dst, Span scratch) + { + if (Sse2.IsSupported) + { + // Load and concatenate the transform coefficients. + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + var k1 = Vector128.Create((short)20091); + var k2 = Vector128.Create((short)-30068); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); - public static void TransformOne(Span src, Span dst, Span scratch) + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load four bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref dstRef)).AsByte(); + Vector128 dst1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte(); + Vector128 dst2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte(); + Vector128 dst3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store four bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + int output0 = Sse2.ConvertToInt32(dst0.AsInt32()); + int output1 = Sse2.ConvertToInt32(dst1.AsInt32()); + int output2 = Sse2.ConvertToInt32(dst2.AsInt32()); + int output3 = Sse2.ConvertToInt32(dst3.AsInt32()); + Unsafe.As(ref outputRef) = output0; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; + } + else { - if (Sse2.IsSupported) - { - // Load and concatenate the transform coefficients. - ref short srcRef = ref MemoryMarshal.GetReference(src); - var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); - var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); - var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); - Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); - - var k1 = Vector128.Create((short)20091); - var k2 = Vector128.Create((short)-30068); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); - Vector128 c4 = Sse2.Subtract(c1, c2); - Vector128 c = Sse2.Add(c3.AsInt16(), c4); - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); - Vector128 d4 = Sse2.Add(d1, d2); - Vector128 d = Sse2.Add(d3, d4); - - // Second pass. - Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); - Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); - Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); - Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); - a = Sse2.Add(dc, t2.AsInt16()); - b = Sse2.Subtract(dc, t2.AsInt16()); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); - c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); - c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); - c4 = Sse2.Subtract(c1, c2); - c = Sse2.Add(c3, c4); - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); - d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); - d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); - d4 = Sse2.Add(d1, d2); - d = Sse2.Add(d3, d4); - - // Second pass. - tmp0 = Sse2.Add(a, d); - tmp1 = Sse2.Add(b, c); - tmp2 = Sse2.Subtract(b, c); - tmp3 = Sse2.Subtract(a, d); - Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); - Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); - Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); - Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); - - // Transpose the two 4x4. - Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'dst' and store. - // Load the reference(s). - // Load four bytes/pixels per line. - ref byte dstRef = ref MemoryMarshal.GetReference(dst); - Vector128 dst0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref dstRef)).AsByte(); - Vector128 dst1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte(); - Vector128 dst2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte(); - Vector128 dst3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte(); - - // Convert to 16b. - dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); - dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); - dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); - dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); - - // Add the inverse transform(s). - dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); - dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); - dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); - dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); - - // Unsigned saturate to 8b. - dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); - dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); - dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); - dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); - - // Store the results. - // Store four bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - int output0 = Sse2.ConvertToInt32(dst0.AsInt32()); - int output1 = Sse2.ConvertToInt32(dst1.AsInt32()); - int output2 = Sse2.ConvertToInt32(dst2.AsInt32()); - int output3 = Sse2.ConvertToInt32(dst3.AsInt32()); - Unsafe.As(ref outputRef) = output0; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; - } - else + Span tmp = scratch[..16]; + int tmpOffset = 0; + for (int srcOffset = 0; srcOffset < 4; srcOffset++) + { + // vertical pass + int srcOffsetPlus4 = srcOffset + 4; + int srcOffsetPlus8 = srcOffset + 8; + int srcOffsetPlus12 = srcOffset + 12; + int a = src[srcOffset] + src[srcOffsetPlus8]; + int b = src[srcOffset] - src[srcOffsetPlus8]; + int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); + int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); + tmp[tmpOffset++] = a + d; + tmp[tmpOffset++] = b + c; + tmp[tmpOffset++] = b - c; + tmp[tmpOffset++] = a - d; + } + + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. + tmpOffset = 0; + int dstOffset = 0; + for (int i = 0; i < 4; i++) { - Span tmp = scratch[..16]; - int tmpOffset = 0; - for (int srcOffset = 0; srcOffset < 4; srcOffset++) - { - // vertical pass - int srcOffsetPlus4 = srcOffset + 4; - int srcOffsetPlus8 = srcOffset + 8; - int srcOffsetPlus12 = srcOffset + 12; - int a = src[srcOffset] + src[srcOffsetPlus8]; - int b = src[srcOffset] - src[srcOffsetPlus8]; - int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); - int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); - tmp[tmpOffset++] = a + d; - tmp[tmpOffset++] = b + c; - tmp[tmpOffset++] = b - c; - tmp[tmpOffset++] = a - d; - } + // horizontal pass + int tmpOffsetPlus4 = tmpOffset + 4; + int tmpOffsetPlus8 = tmpOffset + 8; + int tmpOffsetPlus12 = tmpOffset + 12; + int dc = tmp[tmpOffset] + 4; + int a = dc + tmp[tmpOffsetPlus8]; + int b = dc - tmp[tmpOffsetPlus8]; + int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); + int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); + Store(dst[dstOffset..], 0, 0, a + d); + Store(dst[dstOffset..], 1, 0, b + c); + Store(dst[dstOffset..], 2, 0, b - c); + Store(dst[dstOffset..], 3, 0, a - d); + tmpOffset++; - // Each pass is expanding the dynamic range by ~3.85 (upper bound). - // The exact value is (2. + (20091 + 35468) / 65536). - // After the second pass, maximum interval is [-3794, 3794], assuming - // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. - // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. - tmpOffset = 0; - int dstOffset = 0; - for (int i = 0; i < 4; i++) - { - // horizontal pass - int tmpOffsetPlus4 = tmpOffset + 4; - int tmpOffsetPlus8 = tmpOffset + 8; - int tmpOffsetPlus12 = tmpOffset + 12; - int dc = tmp[tmpOffset] + 4; - int a = dc + tmp[tmpOffsetPlus8]; - int b = dc - tmp[tmpOffsetPlus8]; - int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); - int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); - Store(dst[dstOffset..], 0, 0, a + d); - Store(dst[dstOffset..], 1, 0, b + c); - Store(dst[dstOffset..], 2, 0, b - c); - Store(dst[dstOffset..], 3, 0, a - d); - tmpOffset++; - - dstOffset += WebpConstants.Bps; - } + dstOffset += WebpConstants.Bps; } } + } - public static void TransformDc(Span src, Span dst) + public static void TransformDc(Span src, Span dst) + { + int dc = src[0] + 4; + for (int j = 0; j < 4; j++) { - int dc = src[0] + 4; - for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) { - for (int i = 0; i < 4; i++) - { - Store(dst, i, j, dc); - } + Store(dst, i, j, dc); } } + } + + // Simplified transform when only src[0], src[1] and src[4] are non-zero + public static void TransformAc3(Span src, Span dst) + { + int a = src[0] + 4; + int c4 = Mul2(src[4]); + int d4 = Mul1(src[4]); + int c1 = Mul2(src[1]); + int d1 = Mul1(src[1]); + Store2(dst, 0, a + d4, d1, c1); + Store2(dst, 1, a + c4, d1, c1); + Store2(dst, 2, a - c4, d1, c1); + Store2(dst, 3, a - d4, d1, c1); + } - // Simplified transform when only src[0], src[1] and src[4] are non-zero - public static void TransformAc3(Span src, Span dst) + public static void TransformUv(Span src, Span dst, Span scratch) + { + TransformTwo(src[..], dst, scratch); + TransformTwo(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..], scratch); + } + + public static void TransformDcuv(Span src, Span dst) + { + if (src[0 * 16] != 0) { - int a = src[0] + 4; - int c4 = Mul2(src[4]); - int d4 = Mul1(src[4]); - int c1 = Mul2(src[1]); - int d1 = Mul1(src[1]); - Store2(dst, 0, a + d4, d1, c1); - Store2(dst, 1, a + c4, d1, c1); - Store2(dst, 2, a - c4, d1, c1); - Store2(dst, 3, a - d4, d1, c1); + TransformDc(src[..], dst); } - public static void TransformUv(Span src, Span dst, Span scratch) + if (src[1 * 16] != 0) { - TransformTwo(src[..], dst, scratch); - TransformTwo(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..], scratch); + TransformDc(src[(1 * 16)..], dst[4..]); } - public static void TransformDcuv(Span src, Span dst) + if (src[2 * 16] != 0) { - if (src[0 * 16] != 0) - { - TransformDc(src[..], dst); - } - - if (src[1 * 16] != 0) - { - TransformDc(src[(1 * 16)..], dst[4..]); - } - - if (src[2 * 16] != 0) - { - TransformDc(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..]); - } + TransformDc(src[(2 * 16)..], dst[(4 * WebpConstants.Bps)..]); + } - if (src[3 * 16] != 0) - { - TransformDc(src[(3 * 16)..], dst[((4 * WebpConstants.Bps) + 4)..]); - } + if (src[3 * 16] != 0) + { + TransformDc(src[(3 * 16)..], dst[((4 * WebpConstants.Bps) + 4)..]); } + } - // Simple In-loop filtering (Paragraph 15.2) - public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) + // Simple In-loop filtering (Paragraph 15.2) + public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - // Load. - ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + // Load. + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); - Vector128 p1 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, 2 * stride)); - Vector128 p0 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, stride)); - Vector128 q0 = Unsafe.As>(ref pRef); - Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, stride)); + Vector128 p1 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, 2 * stride)); + Vector128 p0 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, stride)); + Vector128 q0 = Unsafe.As>(ref pRef); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, stride)); - DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); - // Store. - ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); - Unsafe.As>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte(); - Unsafe.As>(ref outputRef) = q0.AsSByte(); - } - else + // Store. + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + Unsafe.As>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte(); + Unsafe.As>(ref outputRef) = q0.AsSByte(); + } + else + { + int thresh2 = (2 * thresh) + 1; + int end = 16 + offset; + for (int i = offset; i < end; i++) { - int thresh2 = (2 * thresh) + 1; - int end = 16 + offset; - for (int i = offset; i < end; i++) + if (NeedsFilter(p, i, stride, thresh2)) { - if (NeedsFilter(p, i, stride, thresh2)) - { - DoFilter2(p, i, stride); - } + DoFilter2(p, i, stride); } } } + } - public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) + public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - // Beginning of p1 - ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset - 2); + // Beginning of p1 + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset - 2); - Load16x4(ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); - DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); - Store16x4(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride); - } - else + Load16x4(ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + Store16x4(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride); + } + else + { + int thresh2 = (2 * thresh) + 1; + int end = offset + (16 * stride); + for (int i = offset; i < end; i += stride) { - int thresh2 = (2 * thresh) + 1; - int end = offset + (16 * stride); - for (int i = offset; i < end; i += stride) + if (NeedsFilter(p, i, 1, thresh2)) { - if (NeedsFilter(p, i, 1, thresh2)) - { - DoFilter2(p, i, 1); - } + DoFilter2(p, i, 1); } } } + } - public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) + public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); - } + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); } - else + } + else + { + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); - } + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); } } + } - public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) + public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); - } + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); } - else + } + else + { + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); - } + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); } } + } - // On macroblock edges. - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + // On macroblock edges. + [MethodImpl(InliningOptions.ShortMethod)] + public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Vector128 t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (4 * stride))); - Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (3 * stride))); - Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (2 * stride))); - Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - stride)); - - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(t1, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (4 * stride))); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (3 * stride))); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - stride)); - Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); - Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); - Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); - t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(t1, q2)); - mask = Sse2.Max(mask, Abs(q2, q1)); + Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); - // Store. - ref byte outputRef = ref MemoryMarshal.GetReference(p); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p2.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = q0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = q1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = q2.AsInt32(); - } - else - { - FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); - } + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(p); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p2.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = q0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = q1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = q2.AsInt32(); } + else + { + FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + [MethodImpl(InliningOptions.ShortMethod)] + public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - ref byte bRef = ref Unsafe.Add(ref pRef, offset - 4); - Load16x4(ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte pRef = ref MemoryMarshal.GetReference(p); + ref byte bRef = ref Unsafe.Add(ref pRef, offset - 4); + Load16x4(ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(q3, q2)); - mask = Sse2.Max(mask, Abs(q2, q1)); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - Store16x4(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); - Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride); - } - else - { - FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); - } + Store16x4(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride); + } + else + { + FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); } + } - public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + for (int k = 3; k > 0; k--) { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Vector128 p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); - Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); - Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); - Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + // Beginning of p1. + Span b = p[(offset + (2 * stride))..]; + offset += 4 * stride; - for (int k = 3; k > 0; k--) - { - // Beginning of p1. - Span b = p[(offset + (2 * stride))..]; - offset += 4 * stride; - - Vector128 mask = Abs(p0, p1); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); - - p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); - p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); - Vector128 tmp1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); - Vector128 tmp2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); - - mask = Sse2.Max(mask, Abs(tmp1, tmp2)); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, tmp1)); - - // p3 and p2 are not just temporary variables here: they will be - // re-used for next span. And q2/q3 will become p1/p0 accordingly. - ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); - DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); - - // Store. - ref byte outputRef = ref MemoryMarshal.GetReference(b); - Unsafe.As>(ref outputRef) = p1.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, stride)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 2)) = p3.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 3)) = p2.AsInt32(); - - // Rotate samples. - p1 = tmp1; - p0 = tmp2; - } + Vector128 mask = Abs(p0, p1); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 tmp1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 tmp2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); + + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(b); + Unsafe.As>(ref outputRef) = p1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 2)) = p3.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 3)) = p2.AsInt32(); + + // Rotate samples. + p1 = tmp1; + p0 = tmp2; } - else + } + else + { + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4 * stride; - FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); - } + offset += 4 * stride; + FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); } } + } - public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - ref byte pRef = ref MemoryMarshal.GetReference(p); - Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - Vector128 mask; - for (int k = 3; k > 0; k--) - { - // Beginning of p1. - ref byte bRef = ref Unsafe.Add(ref pRef, offset + 2); + Vector128 mask; + for (int k = 3; k > 0; k--) + { + // Beginning of p1. + ref byte bRef = ref Unsafe.Add(ref pRef, offset + 2); - // Beginning of q0 (and next span). - offset += 4; + // Beginning of q0 (and next span). + offset += 4; - // Compute partial mask. - mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); + // Compute partial mask. + mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); - mask = Sse2.Max(mask, Abs(tmp1, tmp2)); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, tmp1)); + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); - ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); - DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); - Store16x4(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); + Store16x4(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); - // Rotate samples. - p1 = tmp1; - p0 = tmp2; - } + // Rotate samples. + p1 = tmp1; + p0 = tmp2; } - else + } + else + { + for (int k = 3; k > 0; k--) { - for (int k = 3; k > 0; k--) - { - offset += 4; - FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); - } + offset += 4; + FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); } } + } - // 8-pixels wide variant, for chroma filtering. - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + // 8-pixels wide variant, for chroma filtering. + [MethodImpl(InliningOptions.ShortMethod)] + public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - // Load uv h-edges. - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset - (4 * stride)); - Vector128 p2 = LoadUvEdge(ref uRef, ref vRef, offset - (3 * stride)); - Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset - (2 * stride)); - Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset - stride); - - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(t1, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); + // Load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset - (4 * stride)); + Vector128 p2 = LoadUvEdge(ref uRef, ref vRef, offset - (3 * stride)); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset - (2 * stride)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset - stride); - Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); - Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); - Vector128 q2 = LoadUvEdge(ref uRef, ref vRef, offset + (2 * stride)); - t1 = LoadUvEdge(ref uRef, ref vRef, offset + (3 * stride)); + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(t1, q2)); - mask = Sse2.Max(mask, Abs(q2, q1)); + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 q2 = LoadUvEdge(ref uRef, ref vRef, offset + (2 * stride)); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (3 * stride)); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); - // Store. - StoreUv(p2, ref uRef, ref vRef, offset - (3 * stride)); - StoreUv(p1, ref uRef, ref vRef, offset - (2 * stride)); - StoreUv(p0, ref uRef, ref vRef, offset - stride); - StoreUv(q0, ref uRef, ref vRef, offset); - StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride)); - StoreUv(q2, ref uRef, ref vRef, offset + (2 * stride)); - } - else - { - FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); - } + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + StoreUv(p2, ref uRef, ref vRef, offset - (3 * stride)); + StoreUv(p1, ref uRef, ref vRef, offset - (2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset - stride); + StoreUv(q0, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride)); + StoreUv(q2, ref uRef, ref vRef, offset + (2 * stride)); + } + else + { + FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + [MethodImpl(InliningOptions.ShortMethod)] + public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Load16x4(ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(p3, p2)); - mask = Sse2.Max(mask, Abs(p2, p1)); + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(q3, q2)); - mask = Sse2.Max(mask, Abs(q2, q1)); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - Store16x4(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride); - Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); - } - else - { - FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); - } + Store16x4(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); } + else + { + FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); + } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + [MethodImpl(InliningOptions.ShortMethod)] + public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - // Load uv h-edges. - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Vector128 t2 = LoadUvEdge(ref uRef, ref vRef, offset); - Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); - Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); - Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); - - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(t2, t1)); - mask = Sse2.Max(mask, Abs(t1, p1)); + // Load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t2 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); - offset += 4 * stride; + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); - Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); - Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); - t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); - t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + offset += 4 * stride; - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(t2, t1)); - mask = Sse2.Max(mask, Abs(t1, q1)); + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); - // Store. - StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride)); - StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride)); - StoreUv(q0, ref uRef, ref vRef, offset); - StoreUv(q1, ref uRef, ref vRef, offset + stride); - } - else - { - int offset4mulstride = offset + (4 * stride); - FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - } + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + + // Store. + StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride)); + StoreUv(q0, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + stride); + } + else + { + int offset4mulstride = offset + (4 * stride); + FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + [MethodImpl(InliningOptions.ShortMethod)] + public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) + { + if (Sse2.IsSupported) { - if (Sse2.IsSupported) - { - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); - Vector128 mask = Abs(p1, p0); - mask = Sse2.Max(mask, Abs(t2, t1)); - mask = Sse2.Max(mask, Abs(t1, p1)); + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); - // Beginning of q0. - offset += 4; + // Beginning of q0. + offset += 4; - Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); - mask = Sse2.Max(mask, Abs(q1, q0)); - mask = Sse2.Max(mask, Abs(t2, t1)); - mask = Sse2.Max(mask, Abs(t1, q1)); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); - ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); - DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); - // Beginning of p1. - offset -= 2; - Store16x4(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); - } - else - { - int offsetPlus4 = offset + 4; - FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - } + // Beginning of p1. + offset -= 2; + Store16x4(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); } - - public static void Mean16x4(Span input, Span dc) + else { - if (Ssse3.IsSupported) - { - Vector128 mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); - - Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); - Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16))); - Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 3, 16))); - Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte - Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); - Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); - Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); - Vector128 c0 = Sse2.And(a0, mean16x4Mask); // lo byte - Vector128 c1 = Sse2.And(a1, mean16x4Mask); - Vector128 c2 = Sse2.And(a2, mean16x4Mask); - Vector128 c3 = Sse2.And(a3, mean16x4Mask); - Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); - Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); - Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); - Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); - Vector128 e0 = Sse2.Add(d0, d1); - Vector128 e1 = Sse2.Add(d2, d3); - Vector128 f0 = Sse2.Add(e0, e1); - Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); - Vector128 wide = Sse2.UnpackLow(hadd, Vector128.Zero).AsUInt32(); - - ref uint outputRef = ref MemoryMarshal.GetReference(dc); - Unsafe.As>(ref outputRef) = wide; - } - else - { - for (int k = 0; k < 4; k++) + int offsetPlus4 = offset + 4; + FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + } + } + + public static void Mean16x4(Span input, Span dc) + { + if (Ssse3.IsSupported) + { + Vector128 mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); + + Vector128 a0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector128 a1 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps, 16))); + Vector128 a2 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 2, 16))); + Vector128 a3 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(WebpConstants.Bps * 3, 16))); + Vector128 b0 = Sse2.ShiftRightLogical(a0.AsInt16(), 8); // hi byte + Vector128 b1 = Sse2.ShiftRightLogical(a1.AsInt16(), 8); + Vector128 b2 = Sse2.ShiftRightLogical(a2.AsInt16(), 8); + Vector128 b3 = Sse2.ShiftRightLogical(a3.AsInt16(), 8); + Vector128 c0 = Sse2.And(a0, mean16x4Mask); // lo byte + Vector128 c1 = Sse2.And(a1, mean16x4Mask); + Vector128 c2 = Sse2.And(a2, mean16x4Mask); + Vector128 c3 = Sse2.And(a3, mean16x4Mask); + Vector128 d0 = Sse2.Add(b0.AsInt32(), c0.AsInt32()); + Vector128 d1 = Sse2.Add(b1.AsInt32(), c1.AsInt32()); + Vector128 d2 = Sse2.Add(b2.AsInt32(), c2.AsInt32()); + Vector128 d3 = Sse2.Add(b3.AsInt32(), c3.AsInt32()); + Vector128 e0 = Sse2.Add(d0, d1); + Vector128 e1 = Sse2.Add(d2, d3); + Vector128 f0 = Sse2.Add(e0, e1); + Vector128 hadd = Ssse3.HorizontalAdd(f0.AsInt16(), f0.AsInt16()); + Vector128 wide = Sse2.UnpackLow(hadd, Vector128.Zero).AsUInt32(); + + ref uint outputRef = ref MemoryMarshal.GetReference(dc); + Unsafe.As>(ref outputRef) = wide; + } + else + { + for (int k = 0; k < 4; k++) + { + uint avg = 0; + for (int y = 0; y < 4; y++) { - uint avg = 0; - for (int y = 0; y < 4; y++) + for (int x = 0; x < 4; x++) { - for (int x = 0; x < 4; x++) - { - avg += input[x + (y * WebpConstants.Bps)]; - } + avg += input[x + (y * WebpConstants.Bps)]; } - - dc[k] = avg; - input = input[4..]; // go to next 4x4 block. } + + dc[k] = avg; + input = input[4..]; // go to next 4x4 block. } } + } - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg2(byte a, byte b) => (byte)((a + b + 1) >> 1); - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Avg3(byte a, byte b, byte c) => (byte)((a + (2 * b) + c + 2) >> 2); + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Avg3(byte a, byte b, byte c) => (byte)((a + (2 * b) + c + 2) >> 2); - [MethodImpl(InliningOptions.ShortMethod)] - public static void Dst(Span dst, int x, int y, byte v) => dst[x + (y * WebpConstants.Bps)] = v; + [MethodImpl(InliningOptions.ShortMethod)] + public static void Dst(Span dst, int x, int y, byte v) => dst[x + (y * WebpConstants.Bps)] = v; - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : v < 0 ? 0 : 255); + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Clip8B(int v) => (byte)((v & ~0xff) == 0 ? v : v < 0 ? 0 : 255); - // Cost of coding one event with probability 'proba'. - public static int Vp8BitCost(int bit, byte proba) => bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; + // Cost of coding one event with probability 'proba'. + public static int Vp8BitCost(int bit, byte proba) => bit == 0 ? WebpLookupTables.Vp8EntropyCost[proba] : WebpLookupTables.Vp8EntropyCost[255 - proba]; - [MethodImpl(InliningOptions.ShortMethod)] - private static void Put16(int v, Span dst) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Put16(int v, Span dst) + { + for (int j = 0; j < 16; j++) { - for (int j = 0; j < 16; j++) - { - Memset(dst[(j * WebpConstants.Bps)..], (byte)v, 0, 16); - } + Memset(dst[(j * WebpConstants.Bps)..], (byte)v, 0, 16); } + } - private static void TrueMotion(Span dst, Span yuv, int offset, int size) + private static void TrueMotion(Span dst, Span yuv, int offset, int size) + { + // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. + int topOffset = offset - WebpConstants.Bps; + Span top = yuv[topOffset..]; + byte p = yuv[topOffset - 1]; + int leftOffset = offset - 1; + byte left = yuv[leftOffset]; + for (int y = 0; y < size; y++) { - // For information about how true motion works, see rfc6386, page 52. ff and section 20.14. - int topOffset = offset - WebpConstants.Bps; - Span top = yuv[topOffset..]; - byte p = yuv[topOffset - 1]; - int leftOffset = offset - 1; - byte left = yuv[leftOffset]; - for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) { - for (int x = 0; x < size; x++) - { - dst[x] = (byte)Clamp255(left + top[x] - p); - } - - leftOffset += WebpConstants.Bps; - left = yuv[leftOffset]; - dst = dst[WebpConstants.Bps..]; + dst[x] = (byte)Clamp255(left + top[x] - p); } + + leftOffset += WebpConstants.Bps; + left = yuv[leftOffset]; + dst = dst[WebpConstants.Bps..]; } + } - // Complex In-loop filtering (Paragraph 15.3) - private static void FilterLoop24( - Span p, - int offset, - int hStride, - int vStride, - int size, - int thresh, - int ithresh, - int hevThresh) + // Complex In-loop filtering (Paragraph 15.3) + private static void FilterLoop24( + Span p, + int offset, + int hStride, + int vStride, + int size, + int thresh, + int ithresh, + int hevThresh) + { + int thresh2 = (2 * thresh) + 1; + while (size-- > 0) { - int thresh2 = (2 * thresh) + 1; - while (size-- > 0) + if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) { - if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) + if (Hev(p, offset, hStride, hevThresh)) { - if (Hev(p, offset, hStride, hevThresh)) - { - DoFilter2(p, offset, hStride); - } - else - { - DoFilter4(p, offset, hStride); - } + DoFilter2(p, offset, hStride); + } + else + { + DoFilter4(p, offset, hStride); } - - offset += vStride; } + + offset += vStride; } + } - private static void FilterLoop26( - Span p, - int offset, - int hStride, - int vStride, - int size, - int thresh, - int ithresh, - int hevThresh) + private static void FilterLoop26( + Span p, + int offset, + int hStride, + int vStride, + int size, + int thresh, + int ithresh, + int hevThresh) + { + int thresh2 = (2 * thresh) + 1; + while (size-- > 0) { - int thresh2 = (2 * thresh) + 1; - while (size-- > 0) + if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) { - if (NeedsFilter2(p, offset, hStride, thresh2, ithresh)) + if (Hev(p, offset, hStride, hevThresh)) { - if (Hev(p, offset, hStride, hevThresh)) - { - DoFilter2(p, offset, hStride); - } - else - { - DoFilter6(p, offset, hStride); - } + DoFilter2(p, offset, hStride); + } + else + { + DoFilter6(p, offset, hStride); } - - offset += vStride; } - } - // Applies filter on 2 pixels (p0 and q0) - private static void DoFilter2(Span p, int offset, int step) - { - // 4 pixels in, 2 pixels out. - int p1 = p[offset - (2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int a = (3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1); - int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); - int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); - p[offset - step] = WebpLookupTables.Clip1(p0 + a2); - p[offset] = WebpLookupTables.Clip1(q0 - a1); + offset += vStride; } + } - // Applies filter on 2 pixels (p0 and q0) - private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) - { - var signBit = Vector128.Create((byte)0x80); + // Applies filter on 2 pixels (p0 and q0) + private static void DoFilter2(Span p, int offset, int step) + { + // 4 pixels in, 2 pixels out. + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int a = (3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1); + int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); + int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); + p[offset - step] = WebpLookupTables.Clip1(p0 + a2); + p[offset] = WebpLookupTables.Clip1(q0 - a1); + } - // Convert p1/q1 to byte (for GetBaseDelta). - Vector128 p1s = Sse2.Xor(p1, signBit); - Vector128 q1s = Sse2.Xor(q1, signBit); - Vector128 mask = NeedsFilter(p1, p0, q0, q1, thresh); + // Applies filter on 2 pixels (p0 and q0) + private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) + { + var signBit = Vector128.Create((byte)0x80); - // Flip sign. - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); + // Convert p1/q1 to byte (for GetBaseDelta). + Vector128 p1s = Sse2.Xor(p1, signBit); + Vector128 q1s = Sse2.Xor(q1, signBit); + Vector128 mask = NeedsFilter(p1, p0, q0, q1, thresh); - Vector128 a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); + // Flip sign. + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); - // Mask filter values we don't care about. - a = Sse2.And(a, mask); + Vector128 a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); - DoSimpleFilterSse2(ref p0, ref q0, a); + // Mask filter values we don't care about. + a = Sse2.And(a, mask); - // Flip sign. - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); - } + DoSimpleFilterSse2(ref p0, ref q0, a); - // Applies filter on 4 pixels (p1, p0, q0 and q1) - private static void DoFilter4Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, Vector128 mask, int tresh) - { - // Compute hev mask. - Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + // Flip sign. + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + } - var signBit = Vector128.Create((byte)0x80); + // Applies filter on 4 pixels (p1, p0, q0 and q1) + private static void DoFilter4Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + var signBit = Vector128.Create((byte)0x80); + + // Convert to signed values. + p1 = Sse2.Xor(p1, signBit); + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + q1 = Sse2.Xor(q1, signBit); + + Vector128 t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 + t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1) + Vector128 t2 = Sse2.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0 + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) + t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. + + t2 = Sse2.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + Vector128 t3 = Sse2.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + + // This is equivalent to signed (a + 1) >> 1 calculation. + t2 = Sse2.Add(t3, signBit.AsSByte()); + t3 = Sse2.Average(t2.AsByte(), Vector128.Zero).AsSByte(); + t3 = Sse2.Subtract(t3, Vector128.Create((sbyte)64)); + + t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev + q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 + p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 + p1 = Sse2.Xor(p1.AsByte(), signBit); + q1 = Sse2.Xor(q1.AsByte(), signBit); + } - // Convert to signed values. - p1 = Sse2.Xor(p1, signBit); - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); - q1 = Sse2.Xor(q1, signBit); + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) + private static void DoFilter6Sse2(ref Vector128 p2, ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, ref Vector128 q2, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + // Convert to signed values. + var signBit = Vector128.Create((byte)0x80); + p1 = Sse2.Xor(p1, signBit); + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + q1 = Sse2.Xor(q1, signBit); + p2 = Sse2.Xor(p2, signBit); + q2 = Sse2.Xor(q2, signBit); + + Vector128 a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); + + // Do simple filter on pixels with hev. + Vector128 m = Sse2.AndNot(notHev, mask); + Vector128 f = Sse2.And(a.AsByte(), m); + DoSimpleFilterSse2(ref p0, ref q0, f); + + // Do strong filter on pixels with not hev. + m = Sse2.And(notHev, mask); + f = Sse2.And(a.AsByte(), m); + Vector128 flow = Sse2.UnpackLow(Vector128.Zero, f); + Vector128 fhigh = Sse2.UnpackHigh(Vector128.Zero, f); + + var nine = Vector128.Create((short)0x0900); + Vector128 f9Low = Sse2.MultiplyHigh(flow.AsInt16(), nine); // Filter (lo) * 9 + Vector128 f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), nine); // Filter (hi) * 9 + + var sixtyThree = Vector128.Create((short)63); + Vector128 a2Low = Sse2.Add(f9Low, sixtyThree); // Filter * 9 + 63 + Vector128 a2High = Sse2.Add(f9High, sixtyThree); // Filter * 9 + 63 + + Vector128 a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63 + Vector128 a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63 + + Vector128 a0Low = Sse2.Add(a1Low, f9Low); // Filter * 27 + 63 + Vector128 a0High = Sse2.Add(a1High, f9High); // Filter * 27 + 63 + + Update2Pixels(ref p2, ref q2, a2Low, a2High); + Update2Pixels(ref p1, ref q1, a1Low, a1High); + Update2Pixels(ref p0, ref q0, a0Low, a0High); + } - Vector128 t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 - t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1) - Vector128 t2 = Sse2.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0 - t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) - t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) - t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) - t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. + private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) + { + Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte()); + Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte()); - t2 = Sse2.AddSaturate(t1, Vector128.Create((byte)3).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 3 - Vector128 t3 = Sse2.AddSaturate(t1, Vector128.Create((byte)4).AsSByte()); // 3 * (q0 - p0) + hev(p1 - q1) + 4 - t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 - t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 - p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 - q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); + v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 + v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 + p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 + } - // This is equivalent to signed (a + 1) >> 1 calculation. - t2 = Sse2.Add(t3, signBit.AsSByte()); - t3 = Sse2.Average(t2.AsByte(), Vector128.Zero).AsSByte(); - t3 = Sse2.Subtract(t3, Vector128.Create((sbyte)64)); + private static Vector128 GetNotHev(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) + { + Vector128 t1 = Abs(p1, p0); + Vector128 t2 = Abs(q1, q0); - t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev - q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 - p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 - p1 = Sse2.Xor(p1.AsByte(), signBit); - q1 = Sse2.Xor(q1.AsByte(), signBit); - } + var h = Vector128.Create((byte)hevThresh); + Vector128 tMax = Sse2.Max(t1, t2); - // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) - private static void DoFilter6Sse2(ref Vector128 p2, ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, ref Vector128 q2, Vector128 mask, int tresh) - { - // Compute hev mask. - Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + Vector128 tMaxH = Sse2.SubtractSaturate(tMax, h); - // Convert to signed values. - var signBit = Vector128.Create((byte)0x80); - p1 = Sse2.Xor(p1, signBit); - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); - q1 = Sse2.Xor(q1, signBit); - p2 = Sse2.Xor(p2, signBit); - q2 = Sse2.Xor(q2, signBit); + // not_hev <= t1 && not_hev <= t2 + return Sse2.CompareEqual(tMaxH, Vector128.Zero); + } - Vector128 a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); - - // Do simple filter on pixels with hev. - Vector128 m = Sse2.AndNot(notHev, mask); - Vector128 f = Sse2.And(a.AsByte(), m); - DoSimpleFilterSse2(ref p0, ref q0, f); + // Applies filter on 4 pixels (p1, p0, q0 and q1) + private static void DoFilter4(Span p, int offset, int step) + { + // 4 pixels in, 4 pixels out. + int offsetMinus2Step = offset - (2 * step); + int p1 = p[offsetMinus2Step]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int a = 3 * (q0 - p0); + int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); + int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); + int a3 = (a1 + 1) >> 1; + p[offsetMinus2Step] = WebpLookupTables.Clip1(p1 + a3); + p[offset - step] = WebpLookupTables.Clip1(p0 + a2); + p[offset] = WebpLookupTables.Clip1(q0 - a1); + p[offset + step] = WebpLookupTables.Clip1(q1 - a3); + } - // Do strong filter on pixels with not hev. - m = Sse2.And(notHev, mask); - f = Sse2.And(a.AsByte(), m); - Vector128 flow = Sse2.UnpackLow(Vector128.Zero, f); - Vector128 fhigh = Sse2.UnpackHigh(Vector128.Zero, f); + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) + private static void DoFilter6(Span p, int offset, int step) + { + // 6 pixels in, 6 pixels out. + int step2 = 2 * step; + int step3 = 3 * step; + int offsetMinusStep = offset - step; + int p2 = p[offset - step3]; + int p1 = p[offset - step2]; + int p0 = p[offsetMinusStep]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int q2 = p[offset + step2]; + int a = WebpLookupTables.Sclip1((3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1)); + + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[offset - step3] = WebpLookupTables.Clip1(p2 + a3); + p[offset - step2] = WebpLookupTables.Clip1(p1 + a2); + p[offsetMinusStep] = WebpLookupTables.Clip1(p0 + a1); + p[offset] = WebpLookupTables.Clip1(q0 - a1); + p[offset + step] = WebpLookupTables.Clip1(q1 - a2); + p[offset + step2] = WebpLookupTables.Clip1(q2 - a3); + } - var nine = Vector128.Create((short)0x0900); - Vector128 f9Low = Sse2.MultiplyHigh(flow.AsInt16(), nine); // Filter (lo) * 9 - Vector128 f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), nine); // Filter (hi) * 9 + [MethodImpl(InliningOptions.ShortMethod)] + private static bool NeedsFilter(Span p, int offset, int step, int t) + { + int p1 = p[offset + (-2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + return (4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) <= t; + } - var sixtyThree = Vector128.Create((short)63); - Vector128 a2Low = Sse2.Add(f9Low, sixtyThree); // Filter * 9 + 63 - Vector128 a2High = Sse2.Add(f9High, sixtyThree); // Filter * 9 + 63 + private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) + { + int step2 = 2 * step; + int step3 = 3 * step; + int p3 = p[offset - (4 * step)]; + int p2 = p[offset - step3]; + int p1 = p[offset - step2]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + int q2 = p[offset + step2]; + int q3 = p[offset + step3]; + if ((4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) > t) + { + return false; + } + + return WebpLookupTables.Abs0(p3 - p2) <= it && WebpLookupTables.Abs0(p2 - p1) <= it && + WebpLookupTables.Abs0(p1 - p0) <= it && WebpLookupTables.Abs0(q3 - q2) <= it && + WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it; + } - Vector128 a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63 - Vector128 a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63 - - Vector128 a0Low = Sse2.Add(a1Low, f9Low); // Filter * 27 + 63 - Vector128 a0High = Sse2.Add(a1High, f9High); // Filter * 27 + 63 - - Update2Pixels(ref p2, ref q2, a2Low, a2High); - Update2Pixels(ref p1, ref q1, a1Low, a1High); - Update2Pixels(ref p0, ref q0, a0Low, a0High); - } - - private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) - { - Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)3).AsSByte()); - Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), Vector128.Create((byte)4).AsSByte()); - - v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 - v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3 - q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 - p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 - } - - private static Vector128 GetNotHev(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) - { - Vector128 t1 = Abs(p1, p0); - Vector128 t2 = Abs(q1, q0); - - var h = Vector128.Create((byte)hevThresh); - Vector128 tMax = Sse2.Max(t1, t2); - - Vector128 tMaxH = Sse2.SubtractSaturate(tMax, h); - - // not_hev <= t1 && not_hev <= t2 - return Sse2.CompareEqual(tMaxH, Vector128.Zero); - } - - // Applies filter on 4 pixels (p1, p0, q0 and q1) - private static void DoFilter4(Span p, int offset, int step) - { - // 4 pixels in, 4 pixels out. - int offsetMinus2Step = offset - (2 * step); - int p1 = p[offsetMinus2Step]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int a = 3 * (q0 - p0); - int a1 = WebpLookupTables.Sclip2((a + 4) >> 3); - int a2 = WebpLookupTables.Sclip2((a + 3) >> 3); - int a3 = (a1 + 1) >> 1; - p[offsetMinus2Step] = WebpLookupTables.Clip1(p1 + a3); - p[offset - step] = WebpLookupTables.Clip1(p0 + a2); - p[offset] = WebpLookupTables.Clip1(q0 - a1); - p[offset + step] = WebpLookupTables.Clip1(q1 - a3); - } - - // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) - private static void DoFilter6(Span p, int offset, int step) - { - // 6 pixels in, 6 pixels out. - int step2 = 2 * step; - int step3 = 3 * step; - int offsetMinusStep = offset - step; - int p2 = p[offset - step3]; - int p1 = p[offset - step2]; - int p0 = p[offsetMinusStep]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int q2 = p[offset + step2]; - int a = WebpLookupTables.Sclip1((3 * (q0 - p0)) + WebpLookupTables.Sclip1(p1 - q1)); - - // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] - int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 - int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 - int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 - p[offset - step3] = WebpLookupTables.Clip1(p2 + a3); - p[offset - step2] = WebpLookupTables.Clip1(p1 + a2); - p[offsetMinusStep] = WebpLookupTables.Clip1(p0 + a1); - p[offset] = WebpLookupTables.Clip1(q0 - a1); - p[offset + step] = WebpLookupTables.Clip1(q1 - a2); - p[offset + step2] = WebpLookupTables.Clip1(q2 - a3); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool NeedsFilter(Span p, int offset, int step, int t) - { - int p1 = p[offset + (-2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - return (4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) <= t; - } + private static Vector128 NeedsFilter(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) + { + var mthresh = Vector128.Create((byte)thresh); + Vector128 t1 = Abs(p1, q1); // abs(p1 - q1) + var fe = Vector128.Create((byte)0xFE); + Vector128 t2 = Sse2.And(t1, fe); // set lsb of each byte to zero. + Vector128 t3 = Sse2.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2 - private static bool NeedsFilter2(Span p, int offset, int step, int t, int it) - { - int step2 = 2 * step; - int step3 = 3 * step; - int p3 = p[offset - (4 * step)]; - int p2 = p[offset - step3]; - int p1 = p[offset - step2]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - int q2 = p[offset + step2]; - int q3 = p[offset + step3]; - if ((4 * WebpLookupTables.Abs0(p0 - q0)) + WebpLookupTables.Abs0(p1 - q1) > t) - { - return false; - } + Vector128 t4 = Abs(p0, q0); // abs(p0 - q0) + Vector128 t5 = Sse2.AddSaturate(t4, t4); // abs(p0 - q0) * 2 + Vector128 t6 = Sse2.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2 - return WebpLookupTables.Abs0(p3 - p2) <= it && WebpLookupTables.Abs0(p2 - p1) <= it && - WebpLookupTables.Abs0(p1 - p0) <= it && WebpLookupTables.Abs0(q3 - q2) <= it && - WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it; - } + Vector128 t7 = Sse2.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh - private static Vector128 NeedsFilter(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) - { - var mthresh = Vector128.Create((byte)thresh); - Vector128 t1 = Abs(p1, q1); // abs(p1 - q1) - var fe = Vector128.Create((byte)0xFE); - Vector128 t2 = Sse2.And(t1, fe); // set lsb of each byte to zero. - Vector128 t3 = Sse2.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2 + return Sse2.CompareEqual(t7, Vector128.Zero); + } - Vector128 t4 = Abs(p0, q0); // abs(p0 - q0) - Vector128 t5 = Sse2.AddSaturate(t4, t4); // abs(p0 - q0) * 2 - Vector128 t6 = Sse2.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2 + private static void Load16x4(ref byte r0, ref byte r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + { + // Assume the pixels around the edge (|) are numbered as follows + // 00 01 | 02 03 + // 10 11 | 12 13 + // ... | ... + // e0 e1 | e2 e3 + // f0 f1 | f2 f3 + // + // r0 is pointing to the 0th row (00) + // r8 is pointing to the 8th row (80) + + // Load + // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 + // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 + Load8x4(ref r0, stride, out Vector128 t1, out Vector128 t2); + Load8x4(ref r8, stride, out p0, out q1); + + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + p1 = Sse2.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte(); + p0 = Sse2.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte(); + q0 = Sse2.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte(); + q1 = Sse2.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte(); + } - Vector128 t7 = Sse2.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh + // Reads 8 rows across a vertical edge. + private static void Load8x4(ref byte bRef, int stride, out Vector128 p, out Vector128 q) + { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); + uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); + uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); + uint a03 = Unsafe.As(ref Unsafe.Add(ref bRef, 0 * stride)); + Vector128 a0 = Vector128.Create(a03, a02, a01, a00).AsByte(); + uint a10 = Unsafe.As(ref Unsafe.Add(ref bRef, 7 * stride)); + uint a11 = Unsafe.As(ref Unsafe.Add(ref bRef, 3 * stride)); + uint a12 = Unsafe.As(ref Unsafe.Add(ref bRef, 5 * stride)); + uint a13 = Unsafe.As(ref Unsafe.Add(ref bRef, 1 * stride)); + Vector128 a1 = Vector128.Create(a13, a12, a11, a10).AsByte(); + + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + Vector128 b0 = Sse2.UnpackLow(a0.AsSByte(), a1.AsSByte()); + Vector128 b1 = Sse2.UnpackHigh(a0.AsSByte(), a1.AsSByte()); + + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + Vector128 c0 = Sse2.UnpackLow(b0.AsInt16(), b1.AsInt16()); + Vector128 c1 = Sse2.UnpackHigh(b0.AsInt16(), b1.AsInt16()); + + // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + p = Sse2.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte(); + q = Sse2.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte(); + } - return Sse2.CompareEqual(t7, Vector128.Zero); - } + // Transpose back and store + private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, ref byte r0Ref, ref byte r8Ref, int stride) + { + // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 + // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 + Vector128 p0s = Sse2.UnpackLow(p1, p0); + Vector128 p1s = Sse2.UnpackHigh(p1, p0); + + // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 + // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 + Vector128 q0s = Sse2.UnpackLow(q0, q1); + Vector128 q1s = Sse2.UnpackHigh(q0, q1); + + // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 + // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 + Vector128 t1 = p0s; + p0s = Sse2.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte(); + q0s = Sse2.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte(); + + // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 + // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 + t1 = p1s; + p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); + q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); + + Store4x4(p0s, ref r0Ref, stride); + Store4x4(q0s, ref Unsafe.Add(ref r0Ref, 4 * stride), stride); + + Store4x4(p1s, ref r8Ref, stride); + Store4x4(q1s, ref Unsafe.Add(ref r8Ref, 4 * stride), stride); + } - private static void Load16x4(ref byte r0, ref byte r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + private static void Store4x4(Vector128 x, ref byte dstRef, int stride) + { + int offset = 0; + for (int i = 0; i < 4; i++) { - // Assume the pixels around the edge (|) are numbered as follows - // 00 01 | 02 03 - // 10 11 | 12 13 - // ... | ... - // e0 e1 | e2 e3 - // f0 f1 | f2 f3 - // - // r0 is pointing to the 0th row (00) - // r8 is pointing to the 8th row (80) - - // Load - // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 - // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 - // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 - // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 - Load8x4(ref r0, stride, out Vector128 t1, out Vector128 t2); - Load8x4(ref r8, stride, out p0, out q1); - - // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 - // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 - // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 - // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 - p1 = Sse2.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte(); - p0 = Sse2.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte(); - q0 = Sse2.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte(); - q1 = Sse2.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte(); - } - - // Reads 8 rows across a vertical edge. - private static void Load8x4(ref byte bRef, int stride, out Vector128 p, out Vector128 q) - { - // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 - // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 - uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); - uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); - uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); - uint a03 = Unsafe.As(ref Unsafe.Add(ref bRef, 0 * stride)); - Vector128 a0 = Vector128.Create(a03, a02, a01, a00).AsByte(); - uint a10 = Unsafe.As(ref Unsafe.Add(ref bRef, 7 * stride)); - uint a11 = Unsafe.As(ref Unsafe.Add(ref bRef, 3 * stride)); - uint a12 = Unsafe.As(ref Unsafe.Add(ref bRef, 5 * stride)); - uint a13 = Unsafe.As(ref Unsafe.Add(ref bRef, 1 * stride)); - Vector128 a1 = Vector128.Create(a13, a12, a11, a10).AsByte(); - - // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 - // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 - Vector128 b0 = Sse2.UnpackLow(a0.AsSByte(), a1.AsSByte()); - Vector128 b1 = Sse2.UnpackHigh(a0.AsSByte(), a1.AsSByte()); - - // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 - // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 - Vector128 c0 = Sse2.UnpackLow(b0.AsInt16(), b1.AsInt16()); - Vector128 c1 = Sse2.UnpackHigh(b0.AsInt16(), b1.AsInt16()); - - // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 - // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 - p = Sse2.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte(); - q = Sse2.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte(); - } - - // Transpose back and store - private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, ref byte r0Ref, ref byte r8Ref, int stride) - { - // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 - // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 - Vector128 p0s = Sse2.UnpackLow(p1, p0); - Vector128 p1s = Sse2.UnpackHigh(p1, p0); - - // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 - // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 - Vector128 q0s = Sse2.UnpackLow(q0, q1); - Vector128 q1s = Sse2.UnpackHigh(q0, q1); - - // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 - // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 - Vector128 t1 = p0s; - p0s = Sse2.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte(); - q0s = Sse2.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte(); - - // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 - // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 - t1 = p1s; - p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); - q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); - - Store4x4(p0s, ref r0Ref, stride); - Store4x4(q0s, ref Unsafe.Add(ref r0Ref, 4 * stride), stride); - - Store4x4(p1s, ref r8Ref, stride); - Store4x4(q1s, ref Unsafe.Add(ref r8Ref, 4 * stride), stride); - } - - private static void Store4x4(Vector128 x, ref byte dstRef, int stride) - { - int offset = 0; - for (int i = 0; i < 4; i++) - { - Unsafe.As(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32()); - x = Sse2.ShiftRightLogical128BitLane(x, 4); - offset += stride; - } + Unsafe.As(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32()); + x = Sse2.ShiftRightLogical128BitLane(x, 4); + offset += stride; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 GetBaseDelta(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) - { - // Beware of addition order, for saturation! - Vector128 p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 - Vector128 q0p0 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 - Vector128 s1 = Sse2.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0) - Vector128 s2 = Sse2.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0) - Vector128 s3 = Sse2.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0) - - return s3; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 GetBaseDelta(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) + { + // Beware of addition order, for saturation! + Vector128 p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 + Vector128 q0p0 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 + Vector128 s1 = Sse2.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0) + Vector128 s2 = Sse2.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0) + Vector128 s3 = Sse2.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0) + + return s3; + } - // Shift each byte of "x" by 3 bits while preserving by the sign bit. - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 SignedShift8b(Vector128 x) - { - Vector128 low0 = Sse2.UnpackLow(Vector128.Zero, x); - Vector128 high0 = Sse2.UnpackHigh(Vector128.Zero, x); - Vector128 low1 = Sse2.ShiftRightArithmetic(low0.AsInt16(), 3 + 8); - Vector128 high1 = Sse2.ShiftRightArithmetic(high0.AsInt16(), 3 + 8); + // Shift each byte of "x" by 3 bits while preserving by the sign bit. + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SignedShift8b(Vector128 x) + { + Vector128 low0 = Sse2.UnpackLow(Vector128.Zero, x); + Vector128 high0 = Sse2.UnpackHigh(Vector128.Zero, x); + Vector128 low1 = Sse2.ShiftRightArithmetic(low0.AsInt16(), 3 + 8); + Vector128 high1 = Sse2.ShiftRightArithmetic(high0.AsInt16(), 3 + 8); - return Sse2.PackSignedSaturate(low1, high1); - } + return Sse2.PackSignedSaturate(low1, high1); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void ComplexMask(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) - { - var it = Vector128.Create((byte)ithresh); - Vector128 diff = Sse2.SubtractSaturate(mask, it); - Vector128 threshMask = Sse2.CompareEqual(diff, Vector128.Zero); - Vector128 filterMask = NeedsFilter(p1, p0, q0, q1, thresh); + [MethodImpl(InliningOptions.ShortMethod)] + private static void ComplexMask(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) + { + var it = Vector128.Create((byte)ithresh); + Vector128 diff = Sse2.SubtractSaturate(mask, it); + Vector128 threshMask = Sse2.CompareEqual(diff, Vector128.Zero); + Vector128 filterMask = NeedsFilter(p1, p0, q0, q1, thresh); - mask = Sse2.And(threshMask, filterMask); - } + mask = Sse2.And(threshMask, filterMask); + } - // Updates values of 2 pixels at MB edge during complex filtering. - // Update operations: - // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] - // Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). - private static void Update2Pixels(ref Vector128 pi, ref Vector128 qi, Vector128 a0Low, Vector128 a0High) - { - var signBit = Vector128.Create((byte)0x80); - Vector128 a1Low = Sse2.ShiftRightArithmetic(a0Low, 7); - Vector128 a1High = Sse2.ShiftRightArithmetic(a0High, 7); - Vector128 delta = Sse2.PackSignedSaturate(a1Low, a1High); - pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); - qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); - pi = Sse2.Xor(pi, signBit.AsByte()); - qi = Sse2.Xor(qi, signBit.AsByte()); - } + // Updates values of 2 pixels at MB edge during complex filtering. + // Update operations: + // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] + // Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). + private static void Update2Pixels(ref Vector128 pi, ref Vector128 qi, Vector128 a0Low, Vector128 a0High) + { + var signBit = Vector128.Create((byte)0x80); + Vector128 a1Low = Sse2.ShiftRightArithmetic(a0Low, 7); + Vector128 a1High = Sse2.ShiftRightArithmetic(a0High, 7); + Vector128 delta = Sse2.PackSignedSaturate(a1Low, a1High); + pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); + qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); + pi = Sse2.Xor(pi, signBit.AsByte()); + qi = Sse2.Xor(qi, signBit.AsByte()); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 LoadUvEdge(ref byte uRef, ref byte vRef, int offset) - { - var uVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref uRef, offset)), 0); - var vVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref vRef, offset)), 0); - return Sse2.UnpackLow(uVec, vVec).AsByte(); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 LoadUvEdge(ref byte uRef, ref byte vRef, int offset) + { + var uVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref uRef, offset)), 0); + var vVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref vRef, offset)), 0); + return Sse2.UnpackLow(uVec, vVec).AsByte(); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void StoreUv(Vector128 x, ref byte uRef, ref byte vRef, int offset) - { - Unsafe.As>(ref Unsafe.Add(ref uRef, offset)) = x.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref vRef, offset)) = x.GetUpper(); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void StoreUv(Vector128 x, ref byte uRef, ref byte vRef, int offset) + { + Unsafe.As>(ref Unsafe.Add(ref uRef, offset)) = x.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref vRef, offset)) = x.GetUpper(); + } - // Compute abs(p - q) = subs(p - q) OR subs(q - p) - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 Abs(Vector128 p, Vector128 q) - => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); + // Compute abs(p - q) = subs(p - q) OR subs(q - p) + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 Abs(Vector128 p, Vector128 q) + => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); - [MethodImpl(InliningOptions.ShortMethod)] - private static bool Hev(Span p, int offset, int step, int thresh) - { - int p1 = p[offset - (2 * step)]; - int p0 = p[offset - step]; - int q0 = p[offset]; - int q1 = p[offset + step]; - return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static bool Hev(Span p, int offset, int step, int thresh) + { + int p1 = p[offset - (2 * step)]; + int p0 = p[offset - step]; + int q0 = p[offset]; + int q1 = p[offset + step]; + return WebpLookupTables.Abs0(p1 - p0) > thresh || WebpLookupTables.Abs0(q1 - q0) > thresh; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, int x, int y, int v) - { - int index = x + (y * WebpConstants.Bps); - dst[index] = Clip8B(dst[index] + (v >> 3)); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void Store(Span dst, int x, int y, int v) + { + int index = x + (y * WebpConstants.Bps); + dst[index] = Clip8B(dst[index] + (v >> 3)); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store2(Span dst, int y, int dc, int d, int c) - { - Store(dst, 0, y, dc + d); - Store(dst, 1, y, dc + c); - Store(dst, 2, y, dc - c); - Store(dst, 3, y, dc - d); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void Store2(Span dst, int y, int dc, int d, int c) + { + Store(dst, 0, y, dc + d); + Store(dst, 1, y, dc + c); + Store(dst, 2, y, dc - c); + Store(dst, 3, y, dc - d); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul1(int a) => ((a * 20091) >> 16) + a; + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mul1(int a) => ((a * 20091) >> 16) + a; - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul2(int a) => (a * 35468) >> 16; + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mul2(int a) => (a * 35468) >> 16; - [MethodImpl(InliningOptions.ShortMethod)] - private static void Put8x8uv(byte value, Span dst) + [MethodImpl(InliningOptions.ShortMethod)] + private static void Put8x8uv(byte value, Span dst) + { + const int end = 8 * WebpConstants.Bps; + for (int j = 0; j < end; j += WebpConstants.Bps) { - const int end = 8 * WebpConstants.Bps; - for (int j = 0; j < end; j += WebpConstants.Bps) - { - // memset(dst + j * BPS, value, 8); - Memset(dst, value, j, 8); - } + // memset(dst + j * BPS, value, 8); + Memset(dst, value, j, 8); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Memset(Span dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value); + [MethodImpl(InliningOptions.ShortMethod)] + private static void Memset(Span dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value); - [MethodImpl(InliningOptions.ShortMethod)] - private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs b/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs index 06c0fa6e94..ba554866e3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/PassStats.cs @@ -1,76 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Class for organizing convergence in either size or PSNR. +/// +internal class PassStats { - /// - /// Class for organizing convergence in either size or PSNR. - /// - internal class PassStats + public PassStats(long targetSize, float targetPsnr, int qMin, int qMax, int quality) { - public PassStats(long targetSize, float targetPsnr, int qMin, int qMax, int quality) - { - bool doSizeSearch = targetSize != 0; - - this.IsFirst = true; - this.Dq = 10.0f; - this.Qmin = qMin; - this.Qmax = qMax; - this.Q = Numerics.Clamp(quality, qMin, qMax); - this.LastQ = this.Q; - this.Target = doSizeSearch ? targetSize - : targetPsnr > 0.0f ? targetPsnr - : 40.0f; // default, just in case - this.Value = 0.0f; - this.LastValue = 0.0f; - this.DoSizeSearch = doSizeSearch; - } + bool doSizeSearch = targetSize != 0; - public bool IsFirst { get; set; } + this.IsFirst = true; + this.Dq = 10.0f; + this.Qmin = qMin; + this.Qmax = qMax; + this.Q = Numerics.Clamp(quality, qMin, qMax); + this.LastQ = this.Q; + this.Target = doSizeSearch ? targetSize + : targetPsnr > 0.0f ? targetPsnr + : 40.0f; // default, just in case + this.Value = 0.0f; + this.LastValue = 0.0f; + this.DoSizeSearch = doSizeSearch; + } + + public bool IsFirst { get; set; } - public float Dq { get; set; } + public float Dq { get; set; } - public float Q { get; set; } + public float Q { get; set; } - public float LastQ { get; set; } + public float LastQ { get; set; } - public float Qmin { get; } + public float Qmin { get; } - public float Qmax { get; } + public float Qmax { get; } - public double Value { get; set; } // PSNR or size + public double Value { get; set; } // PSNR or size - public double LastValue { get; set; } + public double LastValue { get; set; } - public double Target { get; } + public double Target { get; } - public bool DoSizeSearch { get; } + public bool DoSizeSearch { get; } - public float ComputeNextQ() + public float ComputeNextQ() + { + float dq; + if (this.IsFirst) { - float dq; - if (this.IsFirst) - { - dq = this.Value > this.Target ? -this.Dq : this.Dq; - this.IsFirst = false; - } - else if (this.Value != this.LastValue) - { - double slope = (this.Target - this.Value) / (this.LastValue - this.Value); - dq = (float)(slope * (this.LastQ - this.Q)); - } - else - { - dq = 0.0f; // we're done?! - } - - // Limit variable to avoid large swings. - this.Dq = Numerics.Clamp(dq, -30.0f, 30.0f); - this.LastQ = this.Q; - this.LastValue = this.Value; - this.Q = Numerics.Clamp(this.Q + this.Dq, this.Qmin, this.Qmax); - - return this.Q; + dq = this.Value > this.Target ? -this.Dq : this.Dq; + this.IsFirst = false; } + else if (this.Value != this.LastValue) + { + double slope = (this.Target - this.Value) / (this.LastValue - this.Value); + dq = (float)(slope * (this.LastQ - this.Q)); + } + else + { + dq = 0.0f; // we're done?! + } + + // Limit variable to avoid large swings. + this.Dq = Numerics.Clamp(dq, -30.0f, 30.0f); + this.LastQ = this.Q; + this.LastValue = this.Value; + this.Q = Numerics.Clamp(this.Q + this.Dq, this.Qmin, this.Qmax); + + return this.Q; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index a8e3652709..fed9c16d4d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -1,818 +1,816 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Common.Helpers; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Quantization methods. +/// +internal static unsafe class QuantEnc { - /// - /// Quantization methods. - /// - internal static unsafe class QuantEnc - { - private static readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + private static readonly ushort[] WeightY = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; - private const int MaxLevel = 2047; + private const int MaxLevel = 2047; - // Diffusion weights. We under-correct a bit (15/16th of the error is actually - // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. - private const int C1 = 7; // fraction of error sent to the 4x4 block below - private const int C2 = 8; // fraction of error sent to the 4x4 block on the right - private const int DSHIFT = 4; - private const int DSCALE = 1; // storage descaling, needed to make the error fit byte + // Diffusion weights. We under-correct a bit (15/16th of the error is actually + // diffused) to avoid 'rainbow' chessboard pattern of blocks at q~=0. + private const int C1 = 7; // fraction of error sent to the 4x4 block below + private const int C2 = 8; // fraction of error sent to the 4x4 block on the right + private const int DSHIFT = 4; + private const int DSCALE = 1; // storage descaling, needed to make the error fit byte - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan Zigzag => new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. + private static ReadOnlySpan Zigzag => new byte[] { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; - public static void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + public static void PickBestIntra16(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + { + const int numBlocks = 16; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI16; + int tlambda = dqm.TLambda; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + Span scratch = it.Scratch3; + var rdTmp = new Vp8ModeScore(); + var res = new Vp8Residual(); + Vp8ModeScore rdCur = rdTmp; + Vp8ModeScore rdBest = rd; + int mode; + bool isFlat = IsFlatSource16(src); + rd.ModeI16 = -1; + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { - const int numBlocks = 16; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI16; - int tlambda = dqm.TLambda; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - Span scratch = it.Scratch3; - var rdTmp = new Vp8ModeScore(); - var res = new Vp8Residual(); - Vp8ModeScore rdCur = rdTmp; - Vp8ModeScore rdBest = rd; - int mode; - bool isFlat = IsFlatSource16(src); - rd.ModeI16 = -1; - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - // Scratch buffer. - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - rdCur.ModeI16 = mode; + // Scratch buffer. + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + rdCur.ModeI16 = mode; - // Reconstruct. - rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); + // Reconstruct. + rdCur.Nz = (uint)ReconstructIntra16(it, dqm, rdCur, tmpDst, mode); - // Measure RD-score. - rdCur.D = LossyUtils.Vp8_Sse16X16(src, tmpDst); - rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0; - rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; - rdCur.R = it.GetCostLuma16(rdCur, proba, res); + // Measure RD-score. + rdCur.D = LossyUtils.Vp8_Sse16X16(src, tmpDst); + rdCur.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto16X16(src, tmpDst, WeightY, scratch)) : 0; + rdCur.H = WebpConstants.Vp8FixedCostsI16[mode]; + rdCur.R = it.GetCostLuma16(rdCur, proba, res); + if (isFlat) + { + // Refine the first impression (which was in pixel space). + isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); if (isFlat) { - // Refine the first impression (which was in pixel space). - isFlat = IsFlat(rdCur.YAcLevels, numBlocks, WebpConstants.FlatnessLimitI16); - if (isFlat) - { - // Block is very flat. We put emphasis on the distortion being very low! - rdCur.D *= 2; - rdCur.SD *= 2; - } - } - - // Since we always examine Intra16 first, we can overwrite *rd directly. - rdCur.SetRdScore(lambda); - - if (mode == 0 || rdCur.Score < rdBest.Score) - { - RuntimeUtility.Swap(ref rdBest, ref rdCur); - it.SwapOut(); + // Block is very flat. We put emphasis on the distortion being very low! + rdCur.D *= 2; + rdCur.SD *= 2; } } - if (rdBest != rd) - { - rd = rdBest; - } + // Since we always examine Intra16 first, we can overwrite *rd directly. + rdCur.SetRdScore(lambda); - // Finalize score for mode decision. - rd.SetRdScore(dqm.LambdaMode); - it.SetIntra16Mode(rd.ModeI16); - - // We have a blocky macroblock (only DCs are non-zero) with fairly high - // distortion, record max delta so we can later adjust the minimal filtering - // strength needed to smooth these blocks out. - if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) + if (mode == 0 || rdCur.Score < rdBest.Score) { - dqm.StoreMaxDelta(rd.YDcLevels); + RuntimeUtility.Swap(ref rdBest, ref rdCur); + it.SwapOut(); } } - public static bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba, int maxI4HeaderBits) + if (rdBest != rd) { - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaI4; - int tlambda = dqm.TLambda; - Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); - Span scratch = it.Scratch3; - int totalHeaderBits = 0; - var rdBest = new Vp8ModeScore(); - - if (maxI4HeaderBits == 0) - { - return false; - } - - rdBest.InitScore(); - rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) - rdBest.SetRdScore(dqm.LambdaMode); - it.StartI4(); - var rdi4 = new Vp8ModeScore(); - var rdTmp = new Vp8ModeScore(); - var res = new Vp8Residual(); - Span tmpLevels = new short[16]; - do - { - const int numBlocks = 1; - rdi4.Clear(); - int mode; - int bestMode = -1; - Span src = src0[WebpLookupTables.Vp8Scan[it.I4]..]; - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - Span bestBlock = bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]; - Span tmpDst = it.Scratch.AsSpan(); - tmpDst.Clear(); - - rdi4.InitScore(); - it.MakeIntra4Preds(); - for (mode = 0; mode < WebpConstants.NumBModes; ++mode) - { - rdTmp.Clear(); - tmpLevels.Clear(); + rd = rdBest; + } - // Reconstruct. - rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); + // Finalize score for mode decision. + rd.SetRdScore(dqm.LambdaMode); + it.SetIntra16Mode(rd.ModeI16); - // Compute RD-score. - rdTmp.D = LossyUtils.Vp8_Sse4X4(src, tmpDst); - rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0; - rdTmp.H = modeCosts[mode]; + // We have a blocky macroblock (only DCs are non-zero) with fairly high + // distortion, record max delta so we can later adjust the minimal filtering + // strength needed to smooth these blocks out. + if ((rd.Nz & 0x100ffff) == 0x1000000 && rd.D > dqm.MinDisto) + { + dqm.StoreMaxDelta(rd.YDcLevels); + } + } - // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. - if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) - { - rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; - } - else - { - rdTmp.R = 0; - } + public static bool PickBestIntra4(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba, int maxI4HeaderBits) + { + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaI4; + int tlambda = dqm.TLambda; + Span src0 = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + Span bestBlocks = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc); + Span scratch = it.Scratch3; + int totalHeaderBits = 0; + var rdBest = new Vp8ModeScore(); + + if (maxI4HeaderBits == 0) + { + return false; + } - // Early-out check. - rdTmp.SetRdScore(lambda); - if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) - { - continue; - } + rdBest.InitScore(); + rdBest.H = 211; // '211' is the value of VP8BitCost(0, 145) + rdBest.SetRdScore(dqm.LambdaMode); + it.StartI4(); + var rdi4 = new Vp8ModeScore(); + var rdTmp = new Vp8ModeScore(); + var res = new Vp8Residual(); + Span tmpLevels = new short[16]; + do + { + const int numBlocks = 1; + rdi4.Clear(); + int mode; + int bestMode = -1; + Span src = src0[WebpLookupTables.Vp8Scan[it.I4]..]; + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); + Span bestBlock = bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]; + Span tmpDst = it.Scratch.AsSpan(); + tmpDst.Clear(); + + rdi4.InitScore(); + it.MakeIntra4Preds(); + for (mode = 0; mode < WebpConstants.NumBModes; ++mode) + { + rdTmp.Clear(); + tmpLevels.Clear(); - // Finish computing score. - rdTmp.R += it.GetCostLuma4(tmpLevels, proba, res); - rdTmp.SetRdScore(lambda); + // Reconstruct. + rdTmp.Nz = (uint)ReconstructIntra4(it, dqm, tmpLevels, src, tmpDst, mode); - if (bestMode < 0 || rdTmp.Score < rdi4.Score) - { - rdi4.CopyScore(rdTmp); - bestMode = mode; + // Compute RD-score. + rdTmp.D = LossyUtils.Vp8_Sse4X4(src, tmpDst); + rdTmp.SD = tlambda != 0 ? Mult8B(tlambda, LossyUtils.Vp8Disto4X4(src, tmpDst, WeightY, scratch)) : 0; + rdTmp.H = modeCosts[mode]; - RuntimeUtility.Swap(ref tmpDst, ref bestBlock); - tmpLevels.CopyTo(rdBest.YAcLevels.AsSpan(it.I4 * 16, 16)); - } + // Add flatness penalty, to avoid flat area to be mispredicted by a complex mode. + if (mode > 0 && IsFlat(tmpLevels, numBlocks, WebpConstants.FlatnessLimitI4)) + { + rdTmp.R = WebpConstants.FlatnessPenality * numBlocks; + } + else + { + rdTmp.R = 0; } - rdi4.SetRdScore(dqm.LambdaMode); - rdBest.AddScore(rdi4); - if (rdBest.Score >= rd.Score) + // Early-out check. + rdTmp.SetRdScore(lambda); + if (bestMode >= 0 && rdTmp.Score >= rdi4.Score) { - return false; + continue; } - totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; - if (totalHeaderBits > maxI4HeaderBits) + // Finish computing score. + rdTmp.R += it.GetCostLuma4(tmpLevels, proba, res); + rdTmp.SetRdScore(lambda); + + if (bestMode < 0 || rdTmp.Score < rdi4.Score) { - return false; + rdi4.CopyScore(rdTmp); + bestMode = mode; + + RuntimeUtility.Swap(ref tmpDst, ref bestBlock); + tmpLevels.CopyTo(rdBest.YAcLevels.AsSpan(it.I4 * 16, 16)); } + } - // Copy selected samples to the right place. - LossyUtils.Vp8Copy4X4(bestBlock, bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]); + rdi4.SetRdScore(dqm.LambdaMode); + rdBest.AddScore(rdi4); + if (rdBest.Score >= rd.Score) + { + return false; + } - rd.ModesI4[it.I4] = (byte)bestMode; - it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; + totalHeaderBits += (int)rdi4.H; // <- equal to modeCosts[bestMode]; + if (totalHeaderBits > maxI4HeaderBits) + { + return false; } - while (it.RotateI4(bestBlocks)); - // Finalize state. - rd.CopyScore(rdBest); - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); + // Copy selected samples to the right place. + LossyUtils.Vp8Copy4X4(bestBlock, bestBlocks[WebpLookupTables.Vp8Scan[it.I4]..]); - // Select intra4x4 over intra16x16. - return true; + rd.ModesI4[it.I4] = (byte)bestMode; + it.TopNz[it.I4 & 3] = it.LeftNz[it.I4 >> 2] = rdi4.Nz != 0 ? 1 : 0; } + while (it.RotateI4(bestBlocks)); - public static void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + // Finalize state. + rd.CopyScore(rdBest); + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + rdBest.YAcLevels.AsSpan().CopyTo(rd.YAcLevels); + + // Select intra4x4 over intra16x16. + return true; + } + + public static void PickBestUv(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8SegmentInfo[] segmentInfos, Vp8EncProba proba) + { + const int numBlocks = 8; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + int lambda = dqm.LambdaUv; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); + Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); + Span dst = dst0; + var rdBest = new Vp8ModeScore(); + var rdUv = new Vp8ModeScore(); + var res = new Vp8Residual(); + int mode; + + rd.ModeUv = -1; + rdBest.InitScore(); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { - const int numBlocks = 8; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - int lambda = dqm.LambdaUv; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.UOffEnc); - Span dst0 = it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc); - Span dst = dst0; - var rdBest = new Vp8ModeScore(); - var rdUv = new Vp8ModeScore(); - var res = new Vp8Residual(); - int mode; + rdUv.Clear(); - rd.ModeUv = -1; - rdBest.InitScore(); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - rdUv.Clear(); + // Reconstruct + rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); - // Reconstruct - rdUv.Nz = (uint)ReconstructUv(it, dqm, rdUv, tmpDst, mode); + // Compute RD-score + rdUv.D = LossyUtils.Vp8_Sse16X8(src, tmpDst); + rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. + rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; + rdUv.R = it.GetCostUv(rdUv, proba, res); + if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) + { + rdUv.R += WebpConstants.FlatnessPenality * numBlocks; + } - // Compute RD-score - rdUv.D = LossyUtils.Vp8_Sse16X8(src, tmpDst); - rdUv.SD = 0; // not calling TDisto here: it tends to flatten areas. - rdUv.H = WebpConstants.Vp8FixedCostsUv[mode]; - rdUv.R = it.GetCostUv(rdUv, proba, res); - if (mode > 0 && IsFlat(rdUv.UvLevels, numBlocks, WebpConstants.FlatnessLimitIUv)) + rdUv.SetRdScore(lambda); + if (mode == 0 || rdUv.Score < rdBest.Score) + { + rdBest.CopyScore(rdUv); + rd.ModeUv = mode; + rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); + for (int i = 0; i < 2; i++) { - rdUv.R += WebpConstants.FlatnessPenality * numBlocks; + rd.Derr[i, 0] = rdUv.Derr[i, 0]; + rd.Derr[i, 1] = rdUv.Derr[i, 1]; + rd.Derr[i, 2] = rdUv.Derr[i, 2]; } - rdUv.SetRdScore(lambda); - if (mode == 0 || rdUv.Score < rdBest.Score) - { - rdBest.CopyScore(rdUv); - rd.ModeUv = mode; - rdUv.UvLevels.CopyTo(rd.UvLevels.AsSpan()); - for (int i = 0; i < 2; i++) - { - rd.Derr[i, 0] = rdUv.Derr[i, 0]; - rd.Derr[i, 1] = rdUv.Derr[i, 1]; - rd.Derr[i, 2] = rdUv.Derr[i, 2]; - } - - RuntimeUtility.Swap(ref tmpDst, ref dst); - } + RuntimeUtility.Swap(ref tmpDst, ref dst); } + } - it.SetIntraUvMode(rd.ModeUv); - rd.AddScore(rdBest); - if (dst != dst0) - { - // copy 16x8 block if needed. - LossyUtils.Vp8Copy16X8(dst, dst0); - } + it.SetIntraUvMode(rd.ModeUv); + rd.AddScore(rdBest); + if (dst != dst0) + { + // copy 16x8 block if needed. + LossyUtils.Vp8Copy16X8(dst, dst0); + } - // Store diffusion errors for next block. - it.StoreDiffusionErrors(rd); + // Store diffusion errors for next block. + it.StoreDiffusionErrors(rd); + } + + public static int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + int nz = 0; + int n; + Span shortScratchSpan = it.Scratch2.AsSpan(); + Span scratch = it.Scratch3.AsSpan(0, 16); + shortScratchSpan.Clear(); + scratch.Clear(); + Span dcTmp = shortScratchSpan[..16]; + Span tmp = shortScratchSpan.Slice(16, 16 * 16); + + for (n = 0; n < 16; n += 2) + { + Vp8Encoding.FTransform2( + src[WebpLookupTables.Vp8Scan[n]..], + reference[WebpLookupTables.Vp8Scan[n]..], + tmp.Slice(n * 16, 16), + tmp.Slice((n + 1) * 16, 16), + scratch); } - public static int ReconstructIntra16(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + Vp8Encoding.FTransformWht(tmp, dcTmp, scratch); + nz |= QuantizeBlock(dcTmp, rd.YDcLevels, ref dqm.Y2) << 24; + + for (n = 0; n < 16; n += 2) { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - int nz = 0; - int n; - Span shortScratchSpan = it.Scratch2.AsSpan(); - Span scratch = it.Scratch3.AsSpan(0, 16); - shortScratchSpan.Clear(); - scratch.Clear(); - Span dcTmp = shortScratchSpan[..16]; - Span tmp = shortScratchSpan.Slice(16, 16 * 16); - - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.FTransform2( - src[WebpLookupTables.Vp8Scan[n]..], - reference[WebpLookupTables.Vp8Scan[n]..], - tmp.Slice(n * 16, 16), - tmp.Slice((n + 1) * 16, 16), - scratch); - } + // Zero-out the first coeff, so that: a) nz is correct below, and + // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. + tmp[n * 16] = tmp[(n + 1) * 16] = 0; + nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), ref dqm.Y1) << n; + } - Vp8Encoding.FTransformWht(tmp, dcTmp, scratch); - nz |= QuantizeBlock(dcTmp, rd.YDcLevels, ref dqm.Y2) << 24; + // Transform back. + LossyUtils.TransformWht(dcTmp, tmp, scratch); + for (n = 0; n < 16; n += 2) + { + Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8Scan[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8Scan[n]..], scratch); + } - for (n = 0; n < 16; n += 2) - { - // Zero-out the first coeff, so that: a) nz is correct below, and - // b) finding 'last' non-zero coeffs in SetResidualCoeffs() is simplified. - tmp[n * 16] = tmp[(n + 1) * 16] = 0; - nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.YAcLevels.AsSpan(n * 16, 32), ref dqm.Y1) << n; - } + return nz; + } - // Transform back. - LossyUtils.TransformWht(dcTmp, tmp, scratch); - for (n = 0; n < 16; n += 2) - { - Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8Scan[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8Scan[n]..], scratch); - } + public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); + Span tmp = it.Scratch2.AsSpan(0, 16); + Span scratch = it.Scratch3.AsSpan(0, 16); + Vp8Encoding.FTransform(src, reference, tmp, scratch); + int nz = QuantizeBlock(tmp, levels, ref dqm.Y1); + Vp8Encoding.ITransformOne(reference, tmp, yuvOut, scratch); + + return nz; + } - return nz; + public static int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + int nz = 0; + int n; + Span tmp = it.Scratch2.AsSpan(0, 8 * 16); + Span scratch = it.Scratch3.AsSpan(0, 16); + + for (n = 0; n < 8; n += 2) + { + Vp8Encoding.FTransform2( + src[WebpLookupTables.Vp8ScanUv[n]..], + reference[WebpLookupTables.Vp8ScanUv[n]..], + tmp.Slice(n * 16, 16), + tmp.Slice((n + 1) * 16, 16), + scratch); } - public static int ReconstructIntra4(Vp8EncIterator it, Vp8SegmentInfo dqm, Span levels, Span src, Span yuvOut, int mode) + CorrectDcValues(it, ref dqm.Uv, tmp, rd); + + for (n = 0; n < 8; n += 2) { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - Span tmp = it.Scratch2.AsSpan(0, 16); - Span scratch = it.Scratch3.AsSpan(0, 16); - Vp8Encoding.FTransform(src, reference, tmp, scratch); - int nz = QuantizeBlock(tmp, levels, ref dqm.Y1); - Vp8Encoding.ITransformOne(reference, tmp, yuvOut, scratch); - - return nz; + nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), ref dqm.Uv) << n; } - public static int ReconstructUv(Vp8EncIterator it, Vp8SegmentInfo dqm, Vp8ModeScore rd, Span yuvOut, int mode) + for (n = 0; n < 8; n += 2) { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - int nz = 0; - int n; - Span tmp = it.Scratch2.AsSpan(0, 8 * 16); - Span scratch = it.Scratch3.AsSpan(0, 16); + Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8ScanUv[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8ScanUv[n]..], scratch); + } - for (n = 0; n < 8; n += 2) + return nz << 16; + } + + // Refine intra16/intra4 sub-modes based on distortion only (not rate). + public static void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode, int mbHeaderLimit) + { + long bestScore = Vp8ModeScore.MaxCost; + int nz = 0; + int mode; + bool isI16 = tryBothModes || it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; + Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; + + // Some empiric constants, of approximate order of magnitude. + const int lambdaDi16 = 106; + const int lambdaDi4 = 11; + const int lambdaDuv = 120; + long scoreI4 = dqm.I4Penalty; + long i4BitSum = 0; + long bitLimit = tryBothModes + ? mbHeaderLimit + : Vp8ModeScore.MaxCost; // no early-out allowed. + + if (isI16) + { + int bestMode = -1; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { - Vp8Encoding.FTransform2( - src[WebpLookupTables.Vp8ScanUv[n]..], - reference[WebpLookupTables.Vp8ScanUv[n]..], - tmp.Slice(n * 16, 16), - tmp.Slice((n + 1) * 16, 16), - scratch); - } + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); + long score = (LossyUtils.Vp8_Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); - CorrectDcValues(it, ref dqm.Uv, tmp, rd); + if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) + { + continue; + } - for (n = 0; n < 8; n += 2) - { - nz |= Quantize2Blocks(tmp.Slice(n * 16, 32), rd.UvLevels.AsSpan(n * 16, 32), ref dqm.Uv) << n; + if (score < bestScore) + { + bestMode = mode; + bestScore = score; + } } - for (n = 0; n < 8; n += 2) + if (it.X == 0 || it.Y == 0) { - Vp8Encoding.ITransformTwo(reference[WebpLookupTables.Vp8ScanUv[n]..], tmp.Slice(n * 16, 32), yuvOut[WebpLookupTables.Vp8ScanUv[n]..], scratch); + // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. + if (IsFlatSource16(src)) + { + bestMode = it.X == 0 ? 0 : 2; + tryBothModes = false; // Stick to i16. + } } - return nz << 16; + it.SetIntra16Mode(bestMode); + + // We'll reconstruct later, if i16 mode actually gets selected. } - // Refine intra16/intra4 sub-modes based on distortion only (not rate). - public static void RefineUsingDistortion(Vp8EncIterator it, Vp8SegmentInfo[] segmentInfos, Vp8ModeScore rd, bool tryBothModes, bool refineUvMode, int mbHeaderLimit) + // Next, evaluate Intra4. + if (tryBothModes || !isI16) { - long bestScore = Vp8ModeScore.MaxCost; - int nz = 0; - int mode; - bool isI16 = tryBothModes || it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - Vp8SegmentInfo dqm = segmentInfos[it.CurrentMacroBlockInfo.Segment]; - - // Some empiric constants, of approximate order of magnitude. - const int lambdaDi16 = 106; - const int lambdaDi4 = 11; - const int lambdaDuv = 120; - long scoreI4 = dqm.I4Penalty; - long i4BitSum = 0; - long bitLimit = tryBothModes - ? mbHeaderLimit - : Vp8ModeScore.MaxCost; // no early-out allowed. - - if (isI16) + // We don't evaluate the rate here, but just account for it through a + // constant penalty (i4 mode usually needs more bits compared to i16). + isI16 = false; + it.StartI4(); + do { - int bestMode = -1; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse16X16(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsI16[mode] * lambdaDi16); - - if (mode > 0 && WebpConstants.Vp8FixedCostsI16[mode] > bitLimit) - { - continue; - } + int bestI4Mode = -1; + long bestI4Score = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); + short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - if (score < bestScore) + it.MakeIntra4Preds(); + for (mode = 0; mode < WebpConstants.NumBModes; ++mode) + { + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); + long score = (LossyUtils.Vp8_Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); + if (score < bestI4Score) { - bestMode = mode; - bestScore = score; + bestI4Mode = mode; + bestI4Score = score; } } - if (it.X == 0 || it.Y == 0) + i4BitSum += modeCosts[bestI4Mode]; + rd.ModesI4[it.I4] = (byte)bestI4Mode; + scoreI4 += bestI4Score; + if (scoreI4 >= bestScore || i4BitSum > bitLimit) { - // Avoid starting a checkerboard resonance from the border. See bug #432 of libwebp. - if (IsFlatSource16(src)) - { - bestMode = it.X == 0 ? 0 : 2; - tryBothModes = false; // Stick to i16. - } + // Intra4 won't be better than Intra16. Bail out and pick Intra16. + isI16 = true; + break; } - - it.SetIntra16Mode(bestMode); - - // We'll reconstruct later, if i16 mode actually gets selected. - } - - // Next, evaluate Intra4. - if (tryBothModes || !isI16) - { - // We don't evaluate the rate here, but just account for it through a - // constant penalty (i4 mode usually needs more bits compared to i16). - isI16 = false; - it.StartI4(); - do + else { - int bestI4Mode = -1; - long bestI4Score = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - short[] modeCosts = it.GetCostModeI4(rd.ModesI4); - - it.MakeIntra4Preds(); - for (mode = 0; mode < WebpConstants.NumBModes; ++mode) - { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse4X4(src, reference) * WebpConstants.RdDistoMult) + (modeCosts[mode] * lambdaDi4); - if (score < bestI4Score) - { - bestI4Mode = mode; - bestI4Score = score; - } - } - - i4BitSum += modeCosts[bestI4Mode]; - rd.ModesI4[it.I4] = (byte)bestI4Mode; - scoreI4 += bestI4Score; - if (scoreI4 >= bestScore || i4BitSum > bitLimit) - { - // Intra4 won't be better than Intra16. Bail out and pick Intra16. - isI16 = true; - break; - } - else - { - // Reconstruct partial block inside YuvOut2 buffer - Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); - nz |= ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; - } + // Reconstruct partial block inside YuvOut2 buffer + Span tmpDst = it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc + WebpLookupTables.Vp8Scan[it.I4]); + nz |= ReconstructIntra4(it, dqm, rd.YAcLevels.AsSpan(it.I4 * 16, 16), src, tmpDst, bestI4Mode) << it.I4; } - while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); } + while (it.RotateI4(it.YuvOut2.AsSpan(Vp8EncIterator.YOffEnc))); + } - // Final reconstruction, depending on which mode is selected. - if (!isI16) - { - it.SetIntra4Mode(rd.ModesI4); - it.SwapOut(); - bestScore = scoreI4; - } - else - { - int intra16Mode = it.Preds[it.PredIdx]; - nz = ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); - } + // Final reconstruction, depending on which mode is selected. + if (!isI16) + { + it.SetIntra4Mode(rd.ModesI4); + it.SwapOut(); + bestScore = scoreI4; + } + else + { + int intra16Mode = it.Preds[it.PredIdx]; + nz = ReconstructIntra16(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.YOffEnc), intra16Mode); + } - // ... and UV! - if (refineUvMode) + // ... and UV! + if (refineUvMode) + { + int bestMode = -1; + long bestUvScore = Vp8ModeScore.MaxCost; + Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); + for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) { - int bestMode = -1; - long bestUvScore = Vp8ModeScore.MaxCost; - Span src = it.YuvIn.AsSpan(Vp8EncIterator.UOffEnc); - for (mode = 0; mode < WebpConstants.NumPredModes; ++mode) + Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); + long score = (LossyUtils.Vp8_Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); + if (score < bestUvScore) { - Span reference = it.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]); - long score = (LossyUtils.Vp8_Sse16X8(src, reference) * WebpConstants.RdDistoMult) + (WebpConstants.Vp8FixedCostsUv[mode] * lambdaDuv); - if (score < bestUvScore) - { - bestMode = mode; - bestUvScore = score; - } + bestMode = mode; + bestUvScore = score; } - - it.SetIntraUvMode(bestMode); } - nz |= ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); - - rd.Nz = (uint)nz; - rd.Score = bestScore; + it.SetIntraUvMode(bestMode); } - [MethodImpl(InliningOptions.ShortMethod)] - public static int Quantize2Blocks(Span input, Span output, ref Vp8Matrix mtx) + nz |= ReconstructUv(it, dqm, rd, it.YuvOut.AsSpan(Vp8EncIterator.UOffEnc), it.CurrentMacroBlockInfo.UvMode); + + rd.Nz = (uint)nz; + rd.Score = bestScore; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static int Quantize2Blocks(Span input, Span output, ref Vp8Matrix mtx) + { + int nz = QuantizeBlock(input[..16], output[..16], ref mtx) << 0; + nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), ref mtx) << 1; + return nz; + } + + public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx) + { + if (Avx2.IsSupported) { - int nz = QuantizeBlock(input[..16], output[..16], ref mtx) << 0; - nz |= QuantizeBlock(input.Slice(1 * 16, 16), output.Slice(1 * 16, 16), ref mtx) << 1; - return nz; + // Load all inputs. + Vector256 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector256 iq0 = Unsafe.As>(ref mtx.IQ[0]); + Vector256 q0 = Unsafe.As>(ref mtx.Q[0]); + + // coeff = abs(in) + Vector256 coeff0 = Avx2.Abs(input0); + + // coeff = abs(in) + sharpen + Vector256 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); + Avx2.Add(coeff0.AsInt16(), sharpen0); + + // out = (coeff * iQ + B) >> QFIX + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + Vector256 coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0); + Vector256 coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0); + Vector256 out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H); + Vector256 out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H); + + // out = (coeff * iQ + B) + Vector256 bias00 = Unsafe.As>(ref mtx.Bias[0]); + Vector256 bias08 = Unsafe.As>(ref mtx.Bias[8]); + out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); + out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); + + // out = QUANTDIV(coeff, iQ, B, QFIX) + out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); + out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); + + // Pack result as 16b. + Vector256 out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32()); + + // if (coeff > 2047) coeff = 2047 + out0 = Avx2.Min(out0, Vector256.Create((short)MaxLevel)); + + // Put the sign back. + out0 = Avx2.Sign(out0, input0); + + // in = out * Q + input0 = Avx2.MultiplyLow(out0, q0.AsInt16()); + ref short inputRef = ref MemoryMarshal.GetReference(input); + Unsafe.As>(ref inputRef) = input0; + + // zigzag the output before storing it. + Vector256 tmp256 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); // Cst256 + Vector256 tmp78 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // Cst78 + + // Reverse the order of the 16-byte lanes. + Vector256 tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); + Vector256 outZ = Avx2.Or(tmp256, tmp87).AsInt16(); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = outZ; + + Vector256 packedOutput = Avx2.PackSignedSaturate(outZ, outZ); + + // Detect if all 'out' values are zeros or not. + Vector256 cmpeq = Avx2.CompareEqual(packedOutput, Vector256.Zero); + return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0; } - - public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx) + else if (Sse41.IsSupported) { - if (Avx2.IsSupported) - { - // Load all inputs. - Vector256 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector256 iq0 = Unsafe.As>(ref mtx.IQ[0]); - Vector256 q0 = Unsafe.As>(ref mtx.Q[0]); - - // coeff = abs(in) - Vector256 coeff0 = Avx2.Abs(input0); - - // coeff = abs(in) + sharpen - Vector256 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); - Avx2.Add(coeff0.AsInt16(), sharpen0); - - // out = (coeff * iQ + B) >> QFIX - // doing calculations with 32b precision (QFIX=17) - // out = (coeff * iQ) - Vector256 coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0); - Vector256 coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0); - Vector256 out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H); - Vector256 out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H); - - // out = (coeff * iQ + B) - Vector256 bias00 = Unsafe.As>(ref mtx.Bias[0]); - Vector256 bias08 = Unsafe.As>(ref mtx.Bias[8]); - out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); - out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); - - // out = QUANTDIV(coeff, iQ, B, QFIX) - out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); - out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); - - // Pack result as 16b. - Vector256 out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32()); - - // if (coeff > 2047) coeff = 2047 - out0 = Avx2.Min(out0, Vector256.Create((short)MaxLevel)); - - // Put the sign back. - out0 = Avx2.Sign(out0, input0); - - // in = out * Q - input0 = Avx2.MultiplyLow(out0, q0.AsInt16()); - ref short inputRef = ref MemoryMarshal.GetReference(input); - Unsafe.As>(ref inputRef) = input0; - - // zigzag the output before storing it. - Vector256 tmp256 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); // Cst256 - Vector256 tmp78 = Avx2.Shuffle(out0.AsByte(), Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // Cst78 - - // Reverse the order of the 16-byte lanes. - Vector256 tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); - Vector256 outZ = Avx2.Or(tmp256, tmp87).AsInt16(); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = outZ; - - Vector256 packedOutput = Avx2.PackSignedSaturate(outZ, outZ); - - // Detect if all 'out' values are zeros or not. - Vector256 cmpeq = Avx2.CompareEqual(packedOutput, Vector256.Zero); - return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0; - } - else if (Sse41.IsSupported) - { - // Load all inputs. - Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); - Vector128 input8 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(8, 8))); - Vector128 iq0 = Unsafe.As>(ref mtx.IQ[0]); - Vector128 iq8 = Unsafe.As>(ref mtx.IQ[8]); - Vector128 q0 = Unsafe.As>(ref mtx.Q[0]); - Vector128 q8 = Unsafe.As>(ref mtx.Q[8]); - - // coeff = abs(in) - Vector128 coeff0 = Ssse3.Abs(input0); - Vector128 coeff8 = Ssse3.Abs(input8); - - // coeff = abs(in) + sharpen - Vector128 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); - Vector128 sharpen8 = Unsafe.As>(ref mtx.Sharpen[8]); - Sse2.Add(coeff0.AsInt16(), sharpen0); - Sse2.Add(coeff8.AsInt16(), sharpen8); - - // out = (coeff * iQ + B) >> QFIX - // doing calculations with 32b precision (QFIX=17) - // out = (coeff * iQ) - Vector128 coeffiQ0H = Sse2.MultiplyHigh(coeff0, iq0); - Vector128 coeffiQ0L = Sse2.MultiplyLow(coeff0, iq0); - Vector128 coeffiQ8H = Sse2.MultiplyHigh(coeff8, iq8); - Vector128 coeffiQ8L = Sse2.MultiplyLow(coeff8, iq8); - Vector128 out00 = Sse2.UnpackLow(coeffiQ0L, coeffiQ0H); - Vector128 out04 = Sse2.UnpackHigh(coeffiQ0L, coeffiQ0H); - Vector128 out08 = Sse2.UnpackLow(coeffiQ8L, coeffiQ8H); - Vector128 out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H); - - // out = (coeff * iQ + B) - Vector128 bias00 = Unsafe.As>(ref mtx.Bias[0]); - Vector128 bias04 = Unsafe.As>(ref mtx.Bias[4]); - Vector128 bias08 = Unsafe.As>(ref mtx.Bias[8]); - Vector128 bias12 = Unsafe.As>(ref mtx.Bias[12]); - out00 = Sse2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); - out04 = Sse2.Add(out04.AsInt32(), bias04.AsInt32()).AsUInt16(); - out08 = Sse2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); - out12 = Sse2.Add(out12.AsInt32(), bias12.AsInt32()).AsUInt16(); - - // out = QUANTDIV(coeff, iQ, B, QFIX) - out00 = Sse2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); - out04 = Sse2.ShiftRightArithmetic(out04.AsInt32(), WebpConstants.QFix).AsUInt16(); - out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); - out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16(); - - // Pack result as 16b. - Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32()); - Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32()); - - // if (coeff > 2047) coeff = 2047 - var maxCoeff2047 = Vector128.Create((short)MaxLevel); - out0 = Sse2.Min(out0, maxCoeff2047); - out8 = Sse2.Min(out8, maxCoeff2047); - - // Put the sign back. - out0 = Ssse3.Sign(out0, input0); - out8 = Ssse3.Sign(out8, input8); - - // in = out * Q - input0 = Sse2.MultiplyLow(out0, q0.AsInt16()); - input8 = Sse2.MultiplyLow(out8, q8.AsInt16()); - - // in = out * Q - ref short inputRef = ref MemoryMarshal.GetReference(input); - Unsafe.As>(ref inputRef) = input0; - Unsafe.As>(ref Unsafe.Add(ref inputRef, 8)) = input8; - - // zigzag the output before storing it. The re-ordering is: - // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 - // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15 - // There's only two misplaced entries ([8] and [7]) that are crossing the - // reg's boundaries. - // We use pshufb instead of pshuflo/pshufhi. - Vector128 tmpLo = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13)); - Vector128 tmp7 = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255)); // extract #7 - Vector128 tmpHi = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); - Vector128 tmp8 = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // extract #8 - Vector128 outZ0 = Sse2.Or(tmpLo, tmp8); - Vector128 outZ8 = Sse2.Or(tmpHi, tmp7); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = outZ0.AsInt16(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = outZ8.AsInt16(); - - Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); - - // Detect if all 'out' values are zeros or not. - Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); - return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; - } - else + // Load all inputs. + Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector128 input8 = Unsafe.As>(ref MemoryMarshal.GetReference(input.Slice(8, 8))); + Vector128 iq0 = Unsafe.As>(ref mtx.IQ[0]); + Vector128 iq8 = Unsafe.As>(ref mtx.IQ[8]); + Vector128 q0 = Unsafe.As>(ref mtx.Q[0]); + Vector128 q8 = Unsafe.As>(ref mtx.Q[8]); + + // coeff = abs(in) + Vector128 coeff0 = Ssse3.Abs(input0); + Vector128 coeff8 = Ssse3.Abs(input8); + + // coeff = abs(in) + sharpen + Vector128 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); + Vector128 sharpen8 = Unsafe.As>(ref mtx.Sharpen[8]); + Sse2.Add(coeff0.AsInt16(), sharpen0); + Sse2.Add(coeff8.AsInt16(), sharpen8); + + // out = (coeff * iQ + B) >> QFIX + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + Vector128 coeffiQ0H = Sse2.MultiplyHigh(coeff0, iq0); + Vector128 coeffiQ0L = Sse2.MultiplyLow(coeff0, iq0); + Vector128 coeffiQ8H = Sse2.MultiplyHigh(coeff8, iq8); + Vector128 coeffiQ8L = Sse2.MultiplyLow(coeff8, iq8); + Vector128 out00 = Sse2.UnpackLow(coeffiQ0L, coeffiQ0H); + Vector128 out04 = Sse2.UnpackHigh(coeffiQ0L, coeffiQ0H); + Vector128 out08 = Sse2.UnpackLow(coeffiQ8L, coeffiQ8H); + Vector128 out12 = Sse2.UnpackHigh(coeffiQ8L, coeffiQ8H); + + // out = (coeff * iQ + B) + Vector128 bias00 = Unsafe.As>(ref mtx.Bias[0]); + Vector128 bias04 = Unsafe.As>(ref mtx.Bias[4]); + Vector128 bias08 = Unsafe.As>(ref mtx.Bias[8]); + Vector128 bias12 = Unsafe.As>(ref mtx.Bias[12]); + out00 = Sse2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); + out04 = Sse2.Add(out04.AsInt32(), bias04.AsInt32()).AsUInt16(); + out08 = Sse2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); + out12 = Sse2.Add(out12.AsInt32(), bias12.AsInt32()).AsUInt16(); + + // out = QUANTDIV(coeff, iQ, B, QFIX) + out00 = Sse2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); + out04 = Sse2.ShiftRightArithmetic(out04.AsInt32(), WebpConstants.QFix).AsUInt16(); + out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); + out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16(); + + // Pack result as 16b. + Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32()); + Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32()); + + // if (coeff > 2047) coeff = 2047 + var maxCoeff2047 = Vector128.Create((short)MaxLevel); + out0 = Sse2.Min(out0, maxCoeff2047); + out8 = Sse2.Min(out8, maxCoeff2047); + + // Put the sign back. + out0 = Ssse3.Sign(out0, input0); + out8 = Ssse3.Sign(out8, input8); + + // in = out * Q + input0 = Sse2.MultiplyLow(out0, q0.AsInt16()); + input8 = Sse2.MultiplyLow(out8, q8.AsInt16()); + + // in = out * Q + ref short inputRef = ref MemoryMarshal.GetReference(input); + Unsafe.As>(ref inputRef) = input0; + Unsafe.As>(ref Unsafe.Add(ref inputRef, 8)) = input8; + + // zigzag the output before storing it. The re-ordering is: + // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 + // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15 + // There's only two misplaced entries ([8] and [7]) that are crossing the + // reg's boundaries. + // We use pshufb instead of pshuflo/pshufhi. + Vector128 tmpLo = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13)); + Vector128 tmp7 = Ssse3.Shuffle(out0.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255)); // extract #7 + Vector128 tmpHi = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15)); + Vector128 tmp8 = Ssse3.Shuffle(out8.AsByte(), Vector128.Create(254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255)); // extract #8 + Vector128 outZ0 = Sse2.Or(tmpLo, tmp8); + Vector128 outZ8 = Sse2.Or(tmpHi, tmp7); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = outZ0.AsInt16(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = outZ8.AsInt16(); + + Vector128 packedOutput = Sse2.PackSignedSaturate(outZ0.AsInt16(), outZ8.AsInt16()); + + // Detect if all 'out' values are zeros or not. + Vector128 cmpeq = Sse2.CompareEqual(packedOutput, Vector128.Zero); + return Sse2.MoveMask(cmpeq) != 0xffff ? 1 : 0; + } + else + { + int last = -1; + int n; + for (n = 0; n < 16; ++n) { - int last = -1; - int n; - for (n = 0; n < 16; ++n) + int j = Zigzag[n]; + bool sign = input[j] < 0; + uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); + if (coeff > mtx.ZThresh[j]) { - int j = Zigzag[n]; - bool sign = input[j] < 0; - uint coeff = (uint)((sign ? -input[j] : input[j]) + mtx.Sharpen[j]); - if (coeff > mtx.ZThresh[j]) + uint q = mtx.Q[j]; + uint iQ = mtx.IQ[j]; + uint b = mtx.Bias[j]; + int level = QuantDiv(coeff, iQ, b); + if (level > MaxLevel) { - uint q = mtx.Q[j]; - uint iQ = mtx.IQ[j]; - uint b = mtx.Bias[j]; - int level = QuantDiv(coeff, iQ, b); - if (level > MaxLevel) - { - level = MaxLevel; - } - - if (sign) - { - level = -level; - } - - input[j] = (short)(level * (int)q); - output[n] = (short)level; - if (level != 0) - { - last = n; - } + level = MaxLevel; } - else + + if (sign) { - output[n] = 0; - input[j] = 0; + level = -level; } - } - return last >= 0 ? 1 : 0; + input[j] = (short)(level * (int)q); + output[n] = (short)level; + if (level != 0) + { + last = n; + } + } + else + { + output[n] = 0; + input[j] = 0; + } } + + return last >= 0 ? 1 : 0; } + } - // Quantize as usual, but also compute and return the quantization error. - // Error is already divided by DSHIFT. - public static int QuantizeSingle(Span v, ref Vp8Matrix mtx) + // Quantize as usual, but also compute and return the quantization error. + // Error is already divided by DSHIFT. + public static int QuantizeSingle(Span v, ref Vp8Matrix mtx) + { + int v0 = v[0]; + bool sign = v0 < 0; + if (sign) { - int v0 = v[0]; - bool sign = v0 < 0; - if (sign) - { - v0 = -v0; - } - - if (v0 > (int)mtx.ZThresh[0]) - { - int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; - int err = v0 - qV; - v[0] = (short)(sign ? -qV : qV); - return (sign ? -err : err) >> DSCALE; - } - - v[0] = 0; - return (sign ? -v0 : v0) >> DSCALE; + v0 = -v0; } - public static void CorrectDcValues(Vp8EncIterator it, ref Vp8Matrix mtx, Span tmp, Vp8ModeScore rd) + if (v0 > (int)mtx.ZThresh[0]) { + int qV = QuantDiv((uint)v0, mtx.IQ[0], mtx.Bias[0]) * mtx.Q[0]; + int err = v0 - qV; + v[0] = (short)(sign ? -qV : qV); + return (sign ? -err : err) >> DSCALE; + } + + v[0] = 0; + return (sign ? -v0 : v0) >> DSCALE; + } + + public static void CorrectDcValues(Vp8EncIterator it, ref Vp8Matrix mtx, Span tmp, Vp8ModeScore rd) + { #pragma warning disable SA1005 // Single line comments should begin with single space - // | top[0] | top[1] - // --------+--------+--------- - // left[0] | tmp[0] tmp[1] <-> err0 err1 - // left[1] | tmp[2] tmp[3] err2 err3 - // - // Final errors {err1,err2,err3} are preserved and later restored - // as top[]/left[] on the next block. + // | top[0] | top[1] + // --------+--------+--------- + // left[0] | tmp[0] tmp[1] <-> err0 err1 + // left[1] | tmp[2] tmp[3] err2 err3 + // + // Final errors {err1,err2,err3} are preserved and later restored + // as top[]/left[] on the next block. #pragma warning restore SA1005 // Single line comments should begin with single space - for (int ch = 0; ch <= 1; ++ch) - { - Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); - Span left = it.LeftDerr.AsSpan(ch, 2); - Span c = tmp.Slice(ch * 4 * 16, 4 * 16); - c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); - int err0 = QuantizeSingle(c, ref mtx); - c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); - int err1 = QuantizeSingle(c[(1 * 16)..], ref mtx); - c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); - int err2 = QuantizeSingle(c[(2 * 16)..], ref mtx); - c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); - int err3 = QuantizeSingle(c[(3 * 16)..], ref mtx); - - rd.Derr[ch, 0] = err1; - rd.Derr[ch, 1] = err2; - rd.Derr[ch, 2] = err3; - } + for (int ch = 0; ch <= 1; ++ch) + { + Span top = it.TopDerr.AsSpan((it.X * 4) + ch, 2); + Span left = it.LeftDerr.AsSpan(ch, 2); + Span c = tmp.Slice(ch * 4 * 16, 4 * 16); + c[0] += (short)(((C1 * top[0]) + (C2 * left[0])) >> (DSHIFT - DSCALE)); + int err0 = QuantizeSingle(c, ref mtx); + c[1 * 16] += (short)(((C1 * top[1]) + (C2 * err0)) >> (DSHIFT - DSCALE)); + int err1 = QuantizeSingle(c[(1 * 16)..], ref mtx); + c[2 * 16] += (short)(((C1 * err0) + (C2 * left[1])) >> (DSHIFT - DSCALE)); + int err2 = QuantizeSingle(c[(2 * 16)..], ref mtx); + c[3 * 16] += (short)(((C1 * err1) + (C2 * err2)) >> (DSHIFT - DSCALE)); + int err3 = QuantizeSingle(c[(3 * 16)..], ref mtx); + + rd.Derr[ch, 0] = err1; + rd.Derr[ch, 1] = err2; + rd.Derr[ch, 2] = err3; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlatSource16(Span src) + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsFlatSource16(Span src) + { + uint v = src[0] * 0x01010101u; + Span vSpan = BitConverter.GetBytes(v).AsSpan(); + for (nint i = 0; i < 16; i++) { - uint v = src[0] * 0x01010101u; - Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (nint i = 0; i < 16; i++) + if (!src[..4].SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || + !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) { - if (!src[..4].SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || - !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) - { - return false; - } - - src = src[WebpConstants.Bps..]; + return false; } - return true; + src = src[WebpConstants.Bps..]; } - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsFlat(Span levels, int numBlocks, int thresh) + return true; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsFlat(Span levels, int numBlocks, int thresh) + { + int score = 0; + ref short levelsRef = ref MemoryMarshal.GetReference(levels); + int offset = 0; + while (numBlocks-- > 0) { - int score = 0; - ref short levelsRef = ref MemoryMarshal.GetReference(levels); - int offset = 0; - while (numBlocks-- > 0) + for (nint i = 1; i < 16; i++) { - for (nint i = 1; i < 16; i++) + // omit DC, we're only interested in AC + score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; + if (score > thresh) { - // omit DC, we're only interested in AC - score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; - if (score > thresh) - { - return false; - } + return false; } - - offset += 16; } - return true; + offset += 16; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; - - [MethodImpl(InliningOptions.ShortMethod)] - private static int QuantDiv(uint n, uint iQ, uint b) => (int)(((n * iQ) + b) >> WebpConstants.QFix); + return true; } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mult8B(int a, int b) => ((a * b) + 128) >> 8; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int QuantDiv(uint n, uint iQ, uint b) => (int)(((n * iQ) + b) >> WebpConstants.QFix); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs index 88fc703bc2..90506efb81 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8BandProbas.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// All the probabilities associated to one band. +/// +internal class Vp8BandProbas { /// - /// All the probabilities associated to one band. + /// Initializes a new instance of the class. /// - internal class Vp8BandProbas + public Vp8BandProbas() { - /// - /// Initializes a new instance of the class. - /// - public Vp8BandProbas() + this.Probabilities = new Vp8ProbaArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) { - this.Probabilities = new Vp8ProbaArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Probabilities[i] = new Vp8ProbaArray(); - } + this.Probabilities[i] = new Vp8ProbaArray(); } - - /// - /// Gets the Probabilities. - /// - public Vp8ProbaArray[] Probabilities { get; } } + + /// + /// Gets the Probabilities. + /// + public Vp8ProbaArray[] Probabilities { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs index 05feaced66..2c8d723d74 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8CostArray.cs @@ -1,15 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8CostArray { - internal class Vp8CostArray - { - /// - /// Initializes a new instance of the class. - /// - public Vp8CostArray() => this.Costs = new ushort[67 + 1]; + /// + /// Initializes a new instance of the class. + /// + public Vp8CostArray() => this.Costs = new ushort[67 + 1]; - public ushort[] Costs { get; } - } + public ushort[] Costs { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs index 3db9e7ea55..eee22159e1 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Costs.cs @@ -1,25 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8Costs { - internal class Vp8Costs + /// + /// Initializes a new instance of the class. + /// + public Vp8Costs() { - /// - /// Initializes a new instance of the class. - /// - public Vp8Costs() + this.Costs = new Vp8CostArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) { - this.Costs = new Vp8CostArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Costs[i] = new Vp8CostArray(); - } + this.Costs[i] = new Vp8CostArray(); } - - /// - /// Gets the Costs. - /// - public Vp8CostArray[] Costs { get; } } + + /// + /// Gets the Costs. + /// + public Vp8CostArray[] Costs { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index 7a3ea41631..8a16cb04dc 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -1,345 +1,343 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Holds information for decoding a lossy webp image. +/// +internal class Vp8Decoder : IDisposable { + private Vp8MacroBlock leftMacroBlock; + /// - /// Holds information for decoding a lossy webp image. + /// Initializes a new instance of the class. /// - internal class Vp8Decoder : IDisposable + /// The frame header. + /// The picture header. + /// The segment header. + /// The probabilities. + /// Used for allocating memory for the pixel data output and the temporary buffers. + public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator) { - private Vp8MacroBlock leftMacroBlock; - - /// - /// Initializes a new instance of the class. - /// - /// The frame header. - /// The picture header. - /// The segment header. - /// The probabilities. - /// Used for allocating memory for the pixel data output and the temporary buffers. - public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator) + this.FilterHeader = new Vp8FilterHeader(); + this.FrameHeader = frameHeader; + this.PictureHeader = pictureHeader; + this.SegmentHeader = segmentHeader; + this.Probabilities = probabilities; + this.IntraL = new byte[4]; + this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); + this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); + this.CacheYStride = 16 * this.MbWidth; + this.CacheUvStride = 8 * this.MbWidth; + this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; + this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; + this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; + this.FilterInfo = new Vp8FilterInfo[this.MbWidth]; + for (int i = 0; i < this.MbWidth; i++) { - this.FilterHeader = new Vp8FilterHeader(); - this.FrameHeader = frameHeader; - this.PictureHeader = pictureHeader; - this.SegmentHeader = segmentHeader; - this.Probabilities = probabilities; - this.IntraL = new byte[4]; - this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4); - this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4); - this.CacheYStride = 16 * this.MbWidth; - this.CacheUvStride = 8 * this.MbWidth; - this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1]; - this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth]; - this.YuvTopSamples = new Vp8TopSamples[this.MbWidth]; - this.FilterInfo = new Vp8FilterInfo[this.MbWidth]; - for (int i = 0; i < this.MbWidth; i++) - { - this.MacroBlockInfo[i] = new Vp8MacroBlock(); - this.MacroBlockData[i] = new Vp8MacroBlockData(); - this.YuvTopSamples[i] = new Vp8TopSamples(); - this.FilterInfo[i] = new Vp8FilterInfo(); - } + this.MacroBlockInfo[i] = new Vp8MacroBlock(); + this.MacroBlockData[i] = new Vp8MacroBlockData(); + this.YuvTopSamples[i] = new Vp8TopSamples(); + this.FilterInfo[i] = new Vp8FilterInfo(); + } - this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); + this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock(); - this.DeQuantMatrices = new Vp8QuantMatrix[WebpConstants.NumMbSegments]; - this.FilterStrength = new Vp8FilterInfo[WebpConstants.NumMbSegments, 2]; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) + this.DeQuantMatrices = new Vp8QuantMatrix[WebpConstants.NumMbSegments]; + this.FilterStrength = new Vp8FilterInfo[WebpConstants.NumMbSegments, 2]; + for (int i = 0; i < WebpConstants.NumMbSegments; i++) + { + this.DeQuantMatrices[i] = new Vp8QuantMatrix(); + for (int j = 0; j < 2; j++) { - this.DeQuantMatrices[i] = new Vp8QuantMatrix(); - for (int j = 0; j < 2; j++) - { - this.FilterStrength[i, j] = new Vp8FilterInfo(); - } + this.FilterStrength[i, j] = new Vp8FilterInfo(); } + } - uint width = pictureHeader.Width; - uint height = pictureHeader.Height; - - int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter - int extraY = extraRows * this.CacheYStride; - int extraUv = extraRows / 2 * this.CacheUvStride; - this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); - this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); - int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize); - this.CacheV = memoryAllocator.Allocate(cacheUvSize); - this.TmpYBuffer = memoryAllocator.Allocate((int)width); - this.TmpUBuffer = memoryAllocator.Allocate((int)width); - this.TmpVBuffer = memoryAllocator.Allocate((int)width); - this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); + uint width = pictureHeader.Width; + uint height = pictureHeader.Height; + + int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter + int extraY = extraRows * this.CacheYStride; + int extraUv = extraRows / 2 * this.CacheUvStride; + this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); + this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); + int cacheUvSize = (16 * this.CacheUvStride) + extraUv; + this.CacheU = memoryAllocator.Allocate(cacheUvSize); + this.CacheV = memoryAllocator.Allocate(cacheUvSize); + this.TmpYBuffer = memoryAllocator.Allocate((int)width); + this.TmpUBuffer = memoryAllocator.Allocate((int)width); + this.TmpVBuffer = memoryAllocator.Allocate((int)width); + this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); #if DEBUG - // Filling those buffers with 205, is only useful for debugging, - // so the default values are the same as the reference libwebp implementation. - this.YuvBuffer.Memory.Span.Fill(205); - this.CacheY.Memory.Span.Fill(205); - this.CacheU.Memory.Span.Fill(205); - this.CacheV.Memory.Span.Fill(205); + // Filling those buffers with 205, is only useful for debugging, + // so the default values are the same as the reference libwebp implementation. + this.YuvBuffer.Memory.Span.Fill(205); + this.CacheY.Memory.Span.Fill(205); + this.CacheU.Memory.Span.Fill(205); + this.CacheV.Memory.Span.Fill(205); #endif - this.Vp8BitReaders = new Vp8BitReader[WebpConstants.MaxNumPartitions]; - } + this.Vp8BitReaders = new Vp8BitReader[WebpConstants.MaxNumPartitions]; + } - /// - /// Gets the frame header. - /// - public Vp8FrameHeader FrameHeader { get; } - - /// - /// Gets the picture header. - /// - public Vp8PictureHeader PictureHeader { get; } - - /// - /// Gets the filter header. - /// - public Vp8FilterHeader FilterHeader { get; } - - /// - /// Gets the segment header. - /// - public Vp8SegmentHeader SegmentHeader { get; } - - /// - /// Gets or sets the number of partitions minus one. - /// - public int NumPartsMinusOne { get; set; } - - /// - /// Gets the per-partition boolean decoders. - /// - public Vp8BitReader[] Vp8BitReaders { get; } - - /// - /// Gets the dequantization matrices (one set of DC/AC dequant factor per segment). - /// - public Vp8QuantMatrix[] DeQuantMatrices { get; } - - /// - /// Gets or sets a value indicating whether to use the skip probabilities. - /// - public bool UseSkipProbability { get; set; } - - /// - /// Gets or sets the skip probability. - /// - public byte SkipProbability { get; set; } + /// + /// Gets the frame header. + /// + public Vp8FrameHeader FrameHeader { get; } - /// - /// Gets or sets the Probabilities. - /// - public Vp8Proba Probabilities { get; set; } + /// + /// Gets the picture header. + /// + public Vp8PictureHeader PictureHeader { get; } - /// - /// Gets or sets the top intra modes values: 4 * MbWidth. - /// - public byte[] IntraT { get; set; } + /// + /// Gets the filter header. + /// + public Vp8FilterHeader FilterHeader { get; } - /// - /// Gets the left intra modes values. - /// - public byte[] IntraL { get; } + /// + /// Gets the segment header. + /// + public Vp8SegmentHeader SegmentHeader { get; } - /// - /// Gets the width in macroblock units. - /// - public int MbWidth { get; } + /// + /// Gets or sets the number of partitions minus one. + /// + public int NumPartsMinusOne { get; set; } - /// - /// Gets the height in macroblock units. - /// - public int MbHeight { get; } + /// + /// Gets the per-partition boolean decoders. + /// + public Vp8BitReader[] Vp8BitReaders { get; } - /// - /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbX { get; set; } + /// + /// Gets the dequantization matrices (one set of DC/AC dequant factor per segment). + /// + public Vp8QuantMatrix[] DeQuantMatrices { get; } - /// - /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. - /// - public int TopLeftMbY { get; set; } + /// + /// Gets or sets a value indicating whether to use the skip probabilities. + /// + public bool UseSkipProbability { get; set; } - /// - /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. - /// - public int BottomRightMbX { get; set; } + /// + /// Gets or sets the skip probability. + /// + public byte SkipProbability { get; set; } - /// - /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. - /// - public int BottomRightMbY { get; set; } + /// + /// Gets or sets the Probabilities. + /// + public Vp8Proba Probabilities { get; set; } - /// - /// Gets or sets the current x position in macroblock units. - /// - public int MbX { get; set; } + /// + /// Gets or sets the top intra modes values: 4 * MbWidth. + /// + public byte[] IntraT { get; set; } - /// - /// Gets or sets the current y position in macroblock units. - /// - public int MbY { get; set; } + /// + /// Gets the left intra modes values. + /// + public byte[] IntraL { get; } - /// - /// Gets the parsed reconstruction data. - /// - public Vp8MacroBlockData[] MacroBlockData { get; } + /// + /// Gets the width in macroblock units. + /// + public int MbWidth { get; } - /// - /// Gets the contextual macroblock info. - /// - public Vp8MacroBlock[] MacroBlockInfo { get; } + /// + /// Gets the height in macroblock units. + /// + public int MbHeight { get; } - /// - /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce) - /// visually objectionable artifacts. - /// - public LoopFilter Filter { get; set; } + /// + /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbX { get; set; } - /// - /// Gets the pre-calculated per-segment filter strengths. - /// - public Vp8FilterInfo[,] FilterStrength { get; } + /// + /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered. + /// + public int TopLeftMbY { get; set; } - public IMemoryOwner YuvBuffer { get; } + /// + /// Gets or sets the last bottom-right x index of the macroblock that must be decoded. + /// + public int BottomRightMbX { get; set; } - public Vp8TopSamples[] YuvTopSamples { get; } + /// + /// Gets or sets the last bottom-right y index of the macroblock that must be decoded. + /// + public int BottomRightMbY { get; set; } - public IMemoryOwner CacheY { get; } + /// + /// Gets or sets the current x position in macroblock units. + /// + public int MbX { get; set; } - public IMemoryOwner CacheU { get; } + /// + /// Gets or sets the current y position in macroblock units. + /// + public int MbY { get; set; } + + /// + /// Gets the parsed reconstruction data. + /// + public Vp8MacroBlockData[] MacroBlockData { get; } + + /// + /// Gets the contextual macroblock info. + /// + public Vp8MacroBlock[] MacroBlockInfo { get; } + + /// + /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce) + /// visually objectionable artifacts. + /// + public LoopFilter Filter { get; set; } + + /// + /// Gets the pre-calculated per-segment filter strengths. + /// + public Vp8FilterInfo[,] FilterStrength { get; } + + public IMemoryOwner YuvBuffer { get; } + + public Vp8TopSamples[] YuvTopSamples { get; } + + public IMemoryOwner CacheY { get; } + + public IMemoryOwner CacheU { get; } - public IMemoryOwner CacheV { get; } + public IMemoryOwner CacheV { get; } - public int CacheYOffset { get; set; } + public int CacheYOffset { get; set; } - public int CacheUvOffset { get; set; } + public int CacheUvOffset { get; set; } - public int CacheYStride { get; } + public int CacheYStride { get; } - public int CacheUvStride { get; } + public int CacheUvStride { get; } - public IMemoryOwner TmpYBuffer { get; } + public IMemoryOwner TmpYBuffer { get; } - public IMemoryOwner TmpUBuffer { get; } + public IMemoryOwner TmpUBuffer { get; } - public IMemoryOwner TmpVBuffer { get; } + public IMemoryOwner TmpVBuffer { get; } - /// - /// Gets the pixel buffer where the decoded pixel data will be stored. - /// - public IMemoryOwner Pixels { get; } + /// + /// Gets the pixel buffer where the decoded pixel data will be stored. + /// + public IMemoryOwner Pixels { get; } + + /// + /// Gets or sets filter info. + /// + public Vp8FilterInfo[] FilterInfo { get; set; } - /// - /// Gets or sets filter info. - /// - public Vp8FilterInfo[] FilterInfo { get; set; } + public Vp8MacroBlock CurrentMacroBlock => this.MacroBlockInfo[this.MbX]; - public Vp8MacroBlock CurrentMacroBlock => this.MacroBlockInfo[this.MbX]; + public Vp8MacroBlock LeftMacroBlock => this.leftMacroBlock ??= new Vp8MacroBlock(); - public Vp8MacroBlock LeftMacroBlock => this.leftMacroBlock ??= new Vp8MacroBlock(); + public Vp8MacroBlockData CurrentBlockData => this.MacroBlockData[this.MbX]; - public Vp8MacroBlockData CurrentBlockData => this.MacroBlockData[this.MbX]; + public void PrecomputeFilterStrengths() + { + if (this.Filter == LoopFilter.None) + { + return; + } - public void PrecomputeFilterStrengths() + Vp8FilterHeader hdr = this.FilterHeader; + for (int s = 0; s < WebpConstants.NumMbSegments; ++s) { - if (this.Filter == LoopFilter.None) + int baseLevel; + + // First, compute the initial level. + if (this.SegmentHeader.UseSegment) { - return; + baseLevel = this.SegmentHeader.FilterStrength[s]; + if (!this.SegmentHeader.Delta) + { + baseLevel += hdr.FilterLevel; + } } - - Vp8FilterHeader hdr = this.FilterHeader; - for (int s = 0; s < WebpConstants.NumMbSegments; ++s) + else { - int baseLevel; + baseLevel = hdr.FilterLevel; + } - // First, compute the initial level. - if (this.SegmentHeader.UseSegment) + for (int i4x4 = 0; i4x4 <= 1; i4x4++) + { + Vp8FilterInfo info = this.FilterStrength[s, i4x4]; + int level = baseLevel; + if (hdr.UseLfDelta) { - baseLevel = this.SegmentHeader.FilterStrength[s]; - if (!this.SegmentHeader.Delta) + level += hdr.RefLfDelta[0]; + if (i4x4 > 0) { - baseLevel += hdr.FilterLevel; + level += hdr.ModeLfDelta[0]; } } - else - { - baseLevel = hdr.FilterLevel; - } - for (int i4x4 = 0; i4x4 <= 1; i4x4++) + level = level < 0 ? 0 : level > 63 ? 63 : level; + if (level > 0) { - Vp8FilterInfo info = this.FilterStrength[s, i4x4]; - int level = baseLevel; - if (hdr.UseLfDelta) + int iLevel = level; + if (hdr.Sharpness > 0) { - level += hdr.RefLfDelta[0]; - if (i4x4 > 0) + if (hdr.Sharpness > 4) { - level += hdr.ModeLfDelta[0]; + iLevel >>= 2; } - } - - level = level < 0 ? 0 : level > 63 ? 63 : level; - if (level > 0) - { - int iLevel = level; - if (hdr.Sharpness > 0) + else { - if (hdr.Sharpness > 4) - { - iLevel >>= 2; - } - else - { - iLevel >>= 1; - } - - int iLevelCap = 9 - hdr.Sharpness; - if (iLevel > iLevelCap) - { - iLevel = iLevelCap; - } + iLevel >>= 1; } - if (iLevel < 1) + int iLevelCap = 9 - hdr.Sharpness; + if (iLevel > iLevelCap) { - iLevel = 1; + iLevel = iLevelCap; } - - info.InnerLevel = (byte)iLevel; - info.Limit = (byte)((2 * level) + iLevel); - info.HighEdgeVarianceThreshold = (byte)(level >= 40 ? 2 : level >= 15 ? 1 : 0); } - else + + if (iLevel < 1) { - info.Limit = 0; // no filtering. + iLevel = 1; } - info.UseInnerFiltering = i4x4 == 1; + info.InnerLevel = (byte)iLevel; + info.Limit = (byte)((2 * level) + iLevel); + info.HighEdgeVarianceThreshold = (byte)(level >= 40 ? 2 : level >= 15 ? 1 : 0); + } + else + { + info.Limit = 0; // no filtering. } + + info.UseInnerFiltering = i4x4 == 1; } } + } - /// - public void Dispose() - { - this.YuvBuffer.Dispose(); - this.CacheY.Dispose(); - this.CacheU.Dispose(); - this.CacheV.Dispose(); - this.TmpYBuffer.Dispose(); - this.TmpUBuffer.Dispose(); - this.TmpVBuffer.Dispose(); - this.Pixels.Dispose(); - } + /// + public void Dispose() + { + this.YuvBuffer.Dispose(); + this.CacheY.Dispose(); + this.CacheU.Dispose(); + this.CacheV.Dispose(); + this.TmpYBuffer.Dispose(); + this.TmpUBuffer.Dispose(); + this.TmpVBuffer.Dispose(); + this.Pixels.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index 3092e01e33..fa5fe51c79 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -1,941 +1,938 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +/// +/// Iterator structure to iterate through macroblocks, pointing to the +/// right neighbouring data (samples, predictions, contexts, ...) +/// +internal class Vp8EncIterator { + public const int YOffEnc = 0; + + public const int UOffEnc = 16; + + public const int VOffEnc = 16 + 8; + + private const int MaxIntra16Mode = 2; + + private const int MaxIntra4Mode = 2; + + private const int MaxUvMode = 2; + + private const int DefaultAlpha = -1; + + private readonly int mbw; + + private readonly int mbh; + + /// + /// Stride of the prediction plane(=4*mbw + 1). + /// + private readonly int predsWidth; + /// - /// Iterator structure to iterate through macroblocks, pointing to the - /// right neighbouring data (samples, predictions, contexts, ...) + /// Array to record the position of the top sample to pass to the prediction functions. /// - internal class Vp8EncIterator + private readonly byte[] vp8TopLeftI4 = { - public const int YOffEnc = 0; - - public const int UOffEnc = 16; - - public const int VOffEnc = 16 + 8; - - private const int MaxIntra16Mode = 2; - - private const int MaxIntra4Mode = 2; - - private const int MaxUvMode = 2; - - private const int DefaultAlpha = -1; - - private readonly int mbw; - - private readonly int mbh; - - /// - /// Stride of the prediction plane(=4*mbw + 1). - /// - private readonly int predsWidth; - - /// - /// Array to record the position of the top sample to pass to the prediction functions. - /// - private readonly byte[] vp8TopLeftI4 = - { - 17, 21, 25, 29, - 13, 17, 21, 25, - 9, 13, 17, 21, - 5, 9, 13, 17 - }; - - private int currentMbIdx; - - private int nzIdx; - private int yTopIdx; - - private int uvTopIdx; - - public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, sbyte[] topDerr, int mbw, int mbh) - { - this.YTop = yTop; - this.UvTop = uvTop; - this.Nz = nz; - this.Mb = mb; - this.Preds = preds; - this.TopDerr = topDerr; - this.LeftDerr = new sbyte[2 * 2]; - this.mbw = mbw; - this.mbh = mbh; - this.currentMbIdx = 0; - this.nzIdx = 1; - this.yTopIdx = 0; - this.uvTopIdx = 0; - this.predsWidth = (4 * mbw) + 1; - this.PredIdx = this.predsWidth; - this.YuvIn = new byte[WebpConstants.Bps * 16]; - this.YuvOut = new byte[WebpConstants.Bps * 16]; - this.YuvOut2 = new byte[WebpConstants.Bps * 16]; - this.YuvP = new byte[(32 * WebpConstants.Bps) + (16 * WebpConstants.Bps) + (8 * WebpConstants.Bps)]; // I16+Chroma+I4 preds - this.YLeft = new byte[32]; - this.UvLeft = new byte[32]; - this.TopNz = new int[9]; - this.LeftNz = new int[9]; - this.I4Boundary = new byte[37]; - this.BitCount = new long[4, 3]; - this.Scratch = new byte[WebpConstants.Bps * 16]; - this.Scratch2 = new short[17 * 16]; - this.Scratch3 = new int[16]; - - // To match the C initial values of the reference implementation, initialize all with 204. - const byte defaultInitVal = 204; - this.YuvIn.AsSpan().Fill(defaultInitVal); - this.YuvOut.AsSpan().Fill(defaultInitVal); - this.YuvOut2.AsSpan().Fill(defaultInitVal); - this.YuvP.AsSpan().Fill(defaultInitVal); - this.YLeft.AsSpan().Fill(defaultInitVal); - this.UvLeft.AsSpan().Fill(defaultInitVal); - this.Scratch.AsSpan().Fill(defaultInitVal); - - this.Reset(); - } - - /// - /// Gets or sets the current macroblock X value. - /// - public int X { get; set; } - - /// - /// Gets or sets the current macroblock Y. - /// - public int Y { get; set; } - - /// - /// Gets the input samples. - /// - public byte[] YuvIn { get; } - - /// - /// Gets or sets the output samples. - /// - public byte[] YuvOut { get; set; } - - /// - /// Gets or sets the secondary buffer swapped with YuvOut. - /// - public byte[] YuvOut2 { get; set; } - - /// - /// Gets the scratch buffer for prediction. - /// - public byte[] YuvP { get; } - - /// - /// Gets the left luma samples. - /// - public byte[] YLeft { get; } - - /// - /// Gets the left uv samples. - /// - public byte[] UvLeft { get; } - - /// - /// Gets the left error diffusion (u/v). - /// - public sbyte[] LeftDerr { get; } - - /// - /// Gets the top luma samples at position 'X'. - /// - public byte[] YTop { get; } - - /// - /// Gets the top u/v samples at position 'X', packed as 16 bytes. - /// - public byte[] UvTop { get; } - - /// - /// Gets the intra mode predictors (4x4 blocks). - /// - public byte[] Preds { get; } - - /// - /// Gets the current start index of the intra mode predictors. - /// - public int PredIdx { get; private set; } - - /// - /// Gets the non-zero pattern. - /// - public uint[] Nz { get; } - - /// - /// Gets the top diffusion error. - /// - public sbyte[] TopDerr { get; } - - /// - /// Gets 32+5 boundary samples needed by intra4x4. - /// - public byte[] I4Boundary { get; } - - /// - /// Gets or sets the index to the current top boundary sample. - /// - public int I4BoundaryIdx { get; set; } - - /// - /// Gets or sets the current intra4x4 mode being tested. - /// - public int I4 { get; set; } - - /// - /// Gets the top-non-zero context. - /// - public int[] TopNz { get; } - - /// - /// Gets the left-non-zero. leftNz[8] is independent. - /// - public int[] LeftNz { get; } - - /// - /// Gets or sets the macroblock bit-cost for luma. - /// - public long LumaBits { get; set; } - - /// - /// Gets the bit counters for coded levels. - /// - public long[,] BitCount { get; } - - /// - /// Gets or sets the macroblock bit-cost for chroma. - /// - public long UvBits { get; set; } - - /// - /// Gets or sets the number of mb still to be processed. - /// - public int CountDown { get; set; } - - /// - /// Gets the byte scratch buffer. - /// - public byte[] Scratch { get; } - - /// - /// Gets the short scratch buffer. - /// - public short[] Scratch2 { get; } - - /// - /// Gets the int scratch buffer. - /// - public int[] Scratch3 { get; } - - public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; - - private Vp8MacroBlockInfo[] Mb { get; } - - public void Init() => this.Reset(); - - public static void InitFilter() - { - // TODO: add support for autofilter - } - - public void StartI4() - { - int i; - this.I4 = 0; // first 4x4 sub-block. - this.I4BoundaryIdx = this.vp8TopLeftI4[0]; + 17, 21, 25, 29, + 13, 17, 21, 25, + 9, 13, 17, 21, + 5, 9, 13, 17 + }; - // Import the boundary samples. - for (i = 0; i < 17; i++) - { - // left - this.I4Boundary[i] = this.YLeft[15 - i + 1]; - } + private int currentMbIdx; - Span yTop = this.YTop.AsSpan(this.yTopIdx); - for (i = 0; i < 16; i++) - { - // top - this.I4Boundary[17 + i] = yTop[i]; - } + private int nzIdx; + private int yTopIdx; - // top-right samples have a special case on the far right of the picture. - if (this.X < this.mbw - 1) - { - for (i = 16; i < 16 + 4; i++) - { - this.I4Boundary[17 + i] = yTop[i]; - } - } - else - { - // else, replicate the last valid pixel four times - for (i = 16; i < 16 + 4; i++) - { - this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; - } - } + private int uvTopIdx; - this.NzToBytes(); // import the non-zero context. - } + public Vp8EncIterator(byte[] yTop, byte[] uvTop, uint[] nz, Vp8MacroBlockInfo[] mb, byte[] preds, sbyte[] topDerr, int mbw, int mbh) + { + this.YTop = yTop; + this.UvTop = uvTop; + this.Nz = nz; + this.Mb = mb; + this.Preds = preds; + this.TopDerr = topDerr; + this.LeftDerr = new sbyte[2 * 2]; + this.mbw = mbw; + this.mbh = mbh; + this.currentMbIdx = 0; + this.nzIdx = 1; + this.yTopIdx = 0; + this.uvTopIdx = 0; + this.predsWidth = (4 * mbw) + 1; + this.PredIdx = this.predsWidth; + this.YuvIn = new byte[WebpConstants.Bps * 16]; + this.YuvOut = new byte[WebpConstants.Bps * 16]; + this.YuvOut2 = new byte[WebpConstants.Bps * 16]; + this.YuvP = new byte[(32 * WebpConstants.Bps) + (16 * WebpConstants.Bps) + (8 * WebpConstants.Bps)]; // I16+Chroma+I4 preds + this.YLeft = new byte[32]; + this.UvLeft = new byte[32]; + this.TopNz = new int[9]; + this.LeftNz = new int[9]; + this.I4Boundary = new byte[37]; + this.BitCount = new long[4, 3]; + this.Scratch = new byte[WebpConstants.Bps * 16]; + this.Scratch2 = new short[17 * 16]; + this.Scratch3 = new int[16]; + + // To match the C initial values of the reference implementation, initialize all with 204. + const byte defaultInitVal = 204; + this.YuvIn.AsSpan().Fill(defaultInitVal); + this.YuvOut.AsSpan().Fill(defaultInitVal); + this.YuvOut2.AsSpan().Fill(defaultInitVal); + this.YuvP.AsSpan().Fill(defaultInitVal); + this.YLeft.AsSpan().Fill(defaultInitVal); + this.UvLeft.AsSpan().Fill(defaultInitVal); + this.Scratch.AsSpan().Fill(defaultInitVal); + + this.Reset(); + } - // Import uncompressed samples from source. - public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height, bool importBoundarySamples) - { - int yStartIdx = ((this.Y * yStride) + this.X) * 16; - int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; - Span ySrc = y[yStartIdx..]; - Span uSrc = u[uvStartIdx..]; - Span vSrc = v[uvStartIdx..]; - int w = Math.Min(width - (this.X * 16), 16); - int h = Math.Min(height - (this.Y * 16), 16); - int uvw = (w + 1) >> 1; - int uvh = (h + 1) >> 1; + /// + /// Gets or sets the current macroblock X value. + /// + public int X { get; set; } - Span yuvIn = this.YuvIn.AsSpan(YOffEnc); - Span uIn = this.YuvIn.AsSpan(UOffEnc); - Span vIn = this.YuvIn.AsSpan(VOffEnc); - ImportBlock(ySrc, yStride, yuvIn, w, h, 16); - ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); - ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); + /// + /// Gets or sets the current macroblock Y. + /// + public int Y { get; set; } - if (!importBoundarySamples) - { - return; - } + /// + /// Gets the input samples. + /// + public byte[] YuvIn { get; } - // Import source (uncompressed) samples into boundary. - if (this.X == 0) - { - this.InitLeft(); - } - else - { - Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.UvLeft.AsSpan(0, 16); - Span vLeft = this.UvLeft.AsSpan(16, 16); - if (this.Y == 0) - { - yLeft[0] = 127; - uLeft[0] = 127; - vLeft[0] = 127; - } - else - { - yLeft[0] = y[yStartIdx - 1 - yStride]; - uLeft[0] = u[uvStartIdx - 1 - uvStride]; - vLeft[0] = v[uvStartIdx - 1 - uvStride]; - } + /// + /// Gets or sets the output samples. + /// + public byte[] YuvOut { get; set; } - ImportLine(y[(yStartIdx - 1)..], yStride, yLeft[1..], h, 16); - ImportLine(u[(uvStartIdx - 1)..], uvStride, uLeft[1..], uvh, 8); - ImportLine(v[(uvStartIdx - 1)..], uvStride, vLeft[1..], uvh, 8); - } + /// + /// Gets or sets the secondary buffer swapped with YuvOut. + /// + public byte[] YuvOut2 { get; set; } - Span yTop = this.YTop.AsSpan(this.yTopIdx, 16); - if (this.Y == 0) - { - yTop.Fill(127); - this.UvTop.AsSpan(this.uvTopIdx, 16).Fill(127); - } - else - { - ImportLine(y[(yStartIdx - yStride)..], 1, yTop, w, 16); - ImportLine(u[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx, 8), uvw, 8); - ImportLine(v[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx + 8, 8), uvw, 8); - } + /// + /// Gets the scratch buffer for prediction. + /// + public byte[] YuvP { get; } + + /// + /// Gets the left luma samples. + /// + public byte[] YLeft { get; } + + /// + /// Gets the left uv samples. + /// + public byte[] UvLeft { get; } + + /// + /// Gets the left error diffusion (u/v). + /// + public sbyte[] LeftDerr { get; } + + /// + /// Gets the top luma samples at position 'X'. + /// + public byte[] YTop { get; } + + /// + /// Gets the top u/v samples at position 'X', packed as 16 bytes. + /// + public byte[] UvTop { get; } + + /// + /// Gets the intra mode predictors (4x4 blocks). + /// + public byte[] Preds { get; } + + /// + /// Gets the current start index of the intra mode predictors. + /// + public int PredIdx { get; private set; } + + /// + /// Gets the non-zero pattern. + /// + public uint[] Nz { get; } + + /// + /// Gets the top diffusion error. + /// + public sbyte[] TopDerr { get; } + + /// + /// Gets 32+5 boundary samples needed by intra4x4. + /// + public byte[] I4Boundary { get; } + + /// + /// Gets or sets the index to the current top boundary sample. + /// + public int I4BoundaryIdx { get; set; } + + /// + /// Gets or sets the current intra4x4 mode being tested. + /// + public int I4 { get; set; } + + /// + /// Gets the top-non-zero context. + /// + public int[] TopNz { get; } + + /// + /// Gets the left-non-zero. leftNz[8] is independent. + /// + public int[] LeftNz { get; } + + /// + /// Gets or sets the macroblock bit-cost for luma. + /// + public long LumaBits { get; set; } + + /// + /// Gets the bit counters for coded levels. + /// + public long[,] BitCount { get; } + + /// + /// Gets or sets the macroblock bit-cost for chroma. + /// + public long UvBits { get; set; } + + /// + /// Gets or sets the number of mb still to be processed. + /// + public int CountDown { get; set; } + + /// + /// Gets the byte scratch buffer. + /// + public byte[] Scratch { get; } + + /// + /// Gets the short scratch buffer. + /// + public short[] Scratch2 { get; } + + /// + /// Gets the int scratch buffer. + /// + public int[] Scratch3 { get; } + + public Vp8MacroBlockInfo CurrentMacroBlockInfo => this.Mb[this.currentMbIdx]; + + private Vp8MacroBlockInfo[] Mb { get; } + + public void Init() => this.Reset(); + + public static void InitFilter() + { + // TODO: add support for autofilter + } + + public void StartI4() + { + int i; + this.I4 = 0; // first 4x4 sub-block. + this.I4BoundaryIdx = this.vp8TopLeftI4[0]; + + // Import the boundary samples. + for (i = 0; i < 17; i++) + { + // left + this.I4Boundary[i] = this.YLeft[15 - i + 1]; + } + + Span yTop = this.YTop.AsSpan(this.yTopIdx); + for (i = 0; i < 16; i++) + { + // top + this.I4Boundary[17 + i] = yTop[i]; } - public int FastMbAnalyze(int quality) + // top-right samples have a special case on the far right of the picture. + if (this.X < this.mbw - 1) { - // Empirical cut-off value, should be around 16 (~=block size). We use the - // [8-17] range and favor intra4 at high quality, intra16 for low quality. - int q = quality; - int kThreshold = 8 + ((17 - 8) * q / 100); - int k; - Span dc = stackalloc uint[16]; - uint m; - uint m2; - for (k = 0; k < 16; k += 4) + for (i = 16; i < 16 + 4; i++) { - LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4)); + this.I4Boundary[17 + i] = yTop[i]; } - - for (m = 0, m2 = 0, k = 0; k < 16; k++) + } + else + { + // else, replicate the last valid pixel four times + for (i = 16; i < 16 + 4; i++) { - m += dc[k]; - m2 += dc[k] * dc[k]; + this.I4Boundary[17 + i] = this.I4Boundary[17 + 15]; } + } + + this.NzToBytes(); // import the non-zero context. + } + + // Import uncompressed samples from source. + public void Import(Span y, Span u, Span v, int yStride, int uvStride, int width, int height, bool importBoundarySamples) + { + int yStartIdx = ((this.Y * yStride) + this.X) * 16; + int uvStartIdx = ((this.Y * uvStride) + this.X) * 8; + Span ySrc = y[yStartIdx..]; + Span uSrc = u[uvStartIdx..]; + Span vSrc = v[uvStartIdx..]; + int w = Math.Min(width - (this.X * 16), 16); + int h = Math.Min(height - (this.Y * 16), 16); + int uvw = (w + 1) >> 1; + int uvh = (h + 1) >> 1; + + Span yuvIn = this.YuvIn.AsSpan(YOffEnc); + Span uIn = this.YuvIn.AsSpan(UOffEnc); + Span vIn = this.YuvIn.AsSpan(VOffEnc); + ImportBlock(ySrc, yStride, yuvIn, w, h, 16); + ImportBlock(uSrc, uvStride, uIn, uvw, uvh, 8); + ImportBlock(vSrc, uvStride, vIn, uvw, uvh, 8); + + if (!importBoundarySamples) + { + return; + } - if (kThreshold * m2 < m * m) + // Import source (uncompressed) samples into boundary. + if (this.X == 0) + { + this.InitLeft(); + } + else + { + Span yLeft = this.YLeft.AsSpan(); + Span uLeft = this.UvLeft.AsSpan(0, 16); + Span vLeft = this.UvLeft.AsSpan(16, 16); + if (this.Y == 0) { - this.SetIntra16Mode(0); // DC16 + yLeft[0] = 127; + uLeft[0] = 127; + vLeft[0] = 127; } else { - byte[] modes = new byte[16]; // DC4 - this.SetIntra4Mode(modes); + yLeft[0] = y[yStartIdx - 1 - yStride]; + uLeft[0] = u[uvStartIdx - 1 - uvStride]; + vLeft[0] = v[uvStartIdx - 1 - uvStride]; } - return 0; + ImportLine(y[(yStartIdx - 1)..], yStride, yLeft[1..], h, 16); + ImportLine(u[(uvStartIdx - 1)..], uvStride, uLeft[1..], uvh, 8); + ImportLine(v[(uvStartIdx - 1)..], uvStride, vLeft[1..], uvh, 8); } - public int MbAnalyzeBestIntra16Mode() + Span yTop = this.YTop.AsSpan(this.yTopIdx, 16); + if (this.Y == 0) { - const int maxMode = MaxIntra16Mode; - int mode; - int bestAlpha = DefaultAlpha; - int bestMode = 0; + yTop.Fill(127); + this.UvTop.AsSpan(this.uvTopIdx, 16).Fill(127); + } + else + { + ImportLine(y[(yStartIdx - yStride)..], 1, yTop, w, 16); + ImportLine(u[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx, 8), uvw, 8); + ImportLine(v[(uvStartIdx - uvStride)..], 1, this.UvTop.AsSpan(this.uvTopIdx + 8, 8), uvw, 8); + } + } - this.MakeLuma16Preds(); - for (mode = 0; mode < maxMode; mode++) - { - Vp8Histogram histo = new(); - histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); - int alpha = histo.GetAlpha(); - if (alpha > bestAlpha) - { - bestAlpha = alpha; - bestMode = mode; - } - } + public int FastMbAnalyze(int quality) + { + // Empirical cut-off value, should be around 16 (~=block size). We use the + // [8-17] range and favor intra4 at high quality, intra16 for low quality. + int q = quality; + int kThreshold = 8 + ((17 - 8) * q / 100); + int k; + Span dc = stackalloc uint[16]; + uint m; + uint m2; + for (k = 0; k < 16; k += 4) + { + LossyUtils.Mean16x4(this.YuvIn.AsSpan(YOffEnc + (k * WebpConstants.Bps)), dc.Slice(k, 4)); + } - this.SetIntra16Mode(bestMode); - return bestAlpha; + for (m = 0, m2 = 0, k = 0; k < 16; k++) + { + m += dc[k]; + m2 += dc[k] * dc[k]; } - public int MbAnalyzeBestIntra4Mode(int bestAlpha) + if (kThreshold * m2 < m * m) { - byte[] modes = new byte[16]; - const int maxMode = MaxIntra4Mode; - Vp8Histogram totalHisto = new(); - int curHisto = 0; - this.StartI4(); - do - { - int mode; - int bestModeAlpha = DefaultAlpha; - Vp8Histogram[] histos = new Vp8Histogram[2]; - Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); + this.SetIntra16Mode(0); // DC16 + } + else + { + byte[] modes = new byte[16]; // DC4 + this.SetIntra4Mode(modes); + } - this.MakeIntra4Preds(); - for (mode = 0; mode < maxMode; ++mode) - { - histos[curHisto] = new Vp8Histogram(); - histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); - - int alpha = histos[curHisto].GetAlpha(); - if (alpha > bestModeAlpha) - { - bestModeAlpha = alpha; - modes[this.I4] = (byte)mode; - - // Keep track of best histo so far. - curHisto ^= 1; - } - } + return 0; + } - // Accumulate best histogram. - histos[curHisto ^ 1].Merge(totalHisto); - } - while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. + public int MbAnalyzeBestIntra16Mode() + { + const int maxMode = MaxIntra16Mode; + int mode; + int bestAlpha = DefaultAlpha; + int bestMode = 0; - int i4Alpha = totalHisto.GetAlpha(); - if (i4Alpha > bestAlpha) + this.MakeLuma16Preds(); + for (mode = 0; mode < maxMode; mode++) + { + Vp8Histogram histo = new(); + histo.CollectHistogram(this.YuvIn.AsSpan(YOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8I16ModeOffsets[mode]), 0, 16); + int alpha = histo.GetAlpha(); + if (alpha > bestAlpha) { - this.SetIntra4Mode(modes); - bestAlpha = i4Alpha; + bestAlpha = alpha; + bestMode = mode; } - - return bestAlpha; } - public int MbAnalyzeBestUvMode() + this.SetIntra16Mode(bestMode); + return bestAlpha; + } + + public int MbAnalyzeBestIntra4Mode(int bestAlpha) + { + byte[] modes = new byte[16]; + const int maxMode = MaxIntra4Mode; + Vp8Histogram totalHisto = new(); + int curHisto = 0; + this.StartI4(); + do { - int bestAlpha = DefaultAlpha; - int smallestAlpha = 0; - int bestMode = 0; - const int maxMode = MaxUvMode; int mode; + int bestModeAlpha = DefaultAlpha; + Vp8Histogram[] histos = new Vp8Histogram[2]; + Span src = this.YuvIn.AsSpan(YOffEnc + WebpLookupTables.Vp8Scan[this.I4]); - this.MakeChroma8Preds(); + this.MakeIntra4Preds(); for (mode = 0; mode < maxMode; ++mode) { - Vp8Histogram histo = new(); - histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); - int alpha = histo.GetAlpha(); - if (alpha > bestAlpha) - { - bestAlpha = alpha; - } + histos[curHisto] = new Vp8Histogram(); + histos[curHisto].CollectHistogram(src, this.YuvP.AsSpan(Vp8Encoding.Vp8I4ModeOffsets[mode]), 0, 1); - // The best prediction mode tends to be the one with the smallest alpha. - if (mode == 0 || alpha < smallestAlpha) + int alpha = histos[curHisto].GetAlpha(); + if (alpha > bestModeAlpha) { - smallestAlpha = alpha; - bestMode = mode; + bestModeAlpha = alpha; + modes[this.I4] = (byte)mode; + + // Keep track of best histo so far. + curHisto ^= 1; } } - this.SetIntraUvMode(bestMode); - return bestAlpha; + // Accumulate best histogram. + histos[curHisto ^ 1].Merge(totalHisto); } + while (this.RotateI4(this.YuvIn.AsSpan(YOffEnc))); // Note: we reuse the original samples for predictors. - public void SetIntra16Mode(int mode) + int i4Alpha = totalHisto.GetAlpha(); + if (i4Alpha > bestAlpha) { - Span preds = this.Preds.AsSpan(this.PredIdx); - for (int y = 0; y < 4; y++) - { - preds[..4].Fill((byte)mode); - preds = preds[this.predsWidth..]; - } - - this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; + this.SetIntra4Mode(modes); + bestAlpha = i4Alpha; } - public void SetIntra4Mode(byte[] modes) + return bestAlpha; + } + + public int MbAnalyzeBestUvMode() + { + int bestAlpha = DefaultAlpha; + int smallestAlpha = 0; + int bestMode = 0; + const int maxMode = MaxUvMode; + int mode; + + this.MakeChroma8Preds(); + for (mode = 0; mode < maxMode; ++mode) { - int modesIdx = 0; - int predIdx = this.PredIdx; - for (int y = 4; y > 0; y--) + Vp8Histogram histo = new(); + histo.CollectHistogram(this.YuvIn.AsSpan(UOffEnc), this.YuvP.AsSpan(Vp8Encoding.Vp8UvModeOffsets[mode]), 16, 16 + 4 + 4); + int alpha = histo.GetAlpha(); + if (alpha > bestAlpha) { - modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); - predIdx += this.predsWidth; - modesIdx += 4; + bestAlpha = alpha; } - this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; + // The best prediction mode tends to be the one with the smallest alpha. + if (mode == 0 || alpha < smallestAlpha) + { + smallestAlpha = alpha; + bestMode = mode; + } } - public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) + this.SetIntraUvMode(bestMode); + return bestAlpha; + } + + public void SetIntra16Mode(int mode) + { + Span preds = this.Preds.AsSpan(this.PredIdx); + for (int y = 0; y < 4; y++) { - int r = 0; + preds[..4].Fill((byte)mode); + preds = preds[this.predsWidth..]; + } - // re-import the non-zero context. - this.NzToBytes(); + this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I16X16; + } - // DC - res.Init(0, 1, proba); - res.SetCoeffs(rd.YDcLevels); - r += res.GetResidualCost(this.TopNz[8] + this.LeftNz[8]); + public void SetIntra4Mode(byte[] modes) + { + int modesIdx = 0; + int predIdx = this.PredIdx; + for (int y = 4; y > 0; y--) + { + modes.AsSpan(modesIdx, 4).CopyTo(this.Preds.AsSpan(predIdx)); + predIdx += this.predsWidth; + modesIdx += 4; + } - // AC - res.Init(1, 0, proba); - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int ctx = this.TopNz[x] + this.LeftNz[y]; - res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); - r += res.GetResidualCost(ctx); - this.TopNz[x] = this.LeftNz[y] = res.Last >= 0 ? 1 : 0; - } - } + this.CurrentMacroBlockInfo.MacroBlockType = Vp8MacroBlockType.I4X4; + } - return r; - } + public int GetCostLuma16(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) + { + int r = 0; - public short[] GetCostModeI4(byte[] modes) + // re-import the non-zero context. + this.NzToBytes(); + + // DC + res.Init(0, 1, proba); + res.SetCoeffs(rd.YDcLevels); + r += res.GetResidualCost(this.TopNz[8] + this.LeftNz[8]); + + // AC + res.Init(1, 0, proba); + for (int y = 0; y < 4; y++) { - int predsWidth = this.predsWidth; - int predIdx = this.PredIdx; - int x = this.I4 & 3; - int y = this.I4 >> 2; - int left = x == 0 ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; - int top = y == 0 ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; - return WebpLookupTables.Vp8FixedCostsI4[top, left]; + for (int x = 0; x < 4; x++) + { + int ctx = this.TopNz[x] + this.LeftNz[y]; + res.SetCoeffs(rd.YAcLevels.AsSpan((x + (y * 4)) * 16, 16)); + r += res.GetResidualCost(ctx); + this.TopNz[x] = this.LeftNz[y] = res.Last >= 0 ? 1 : 0; + } } - public int GetCostLuma4(Span levels, Vp8EncProba proba, Vp8Residual res) - { - int x = this.I4 & 3; - int y = this.I4 >> 2; - int r = 0; + return r; + } - res.Init(0, 3, proba); - int ctx = this.TopNz[x] + this.LeftNz[y]; - res.SetCoeffs(levels); - r += res.GetResidualCost(ctx); - return r; - } + public short[] GetCostModeI4(byte[] modes) + { + int predsWidth = this.predsWidth; + int predIdx = this.PredIdx; + int x = this.I4 & 3; + int y = this.I4 >> 2; + int left = x == 0 ? this.Preds[predIdx + (y * predsWidth) - 1] : modes[this.I4 - 1]; + int top = y == 0 ? this.Preds[predIdx - predsWidth + x] : modes[this.I4 - 4]; + return WebpLookupTables.Vp8FixedCostsI4[top, left]; + } - public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) - { - int r = 0; + public int GetCostLuma4(Span levels, Vp8EncProba proba, Vp8Residual res) + { + int x = this.I4 & 3; + int y = this.I4 >> 2; + int r = 0; + + res.Init(0, 3, proba); + int ctx = this.TopNz[x] + this.LeftNz[y]; + res.SetCoeffs(levels); + r += res.GetResidualCost(ctx); + return r; + } + + public int GetCostUv(Vp8ModeScore rd, Vp8EncProba proba, Vp8Residual res) + { + int r = 0; - // re-import the non-zero context. - this.NzToBytes(); + // re-import the non-zero context. + this.NzToBytes(); - res.Init(0, 2, proba); - for (int ch = 0; ch <= 2; ch += 2) + res.Init(0, 2, proba); + for (int ch = 0; ch <= 2; ch += 2) + { + for (int y = 0; y < 2; y++) { - for (int y = 0; y < 2; y++) + for (int x = 0; x < 2; x++) { - for (int x = 0; x < 2; x++) - { - int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; - res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); - r += res.GetResidualCost(ctx); - this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = res.Last >= 0 ? 1 : 0; - } + int ctx = this.TopNz[4 + ch + x] + this.LeftNz[4 + ch + y]; + res.SetCoeffs(rd.UvLevels.AsSpan(((ch * 2) + x + (y * 2)) * 16, 16)); + r += res.GetResidualCost(ctx); + this.TopNz[4 + ch + x] = this.LeftNz[4 + ch + y] = res.Last >= 0 ? 1 : 0; } } - - return r; } - public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode; + return r; + } + + public void SetIntraUvMode(int mode) => this.CurrentMacroBlockInfo.UvMode = mode; - public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip; + public void SetSkip(bool skip) => this.CurrentMacroBlockInfo.Skip = skip; - public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment; + public void SetSegment(int segment) => this.CurrentMacroBlockInfo.Segment = segment; - public void StoreDiffusionErrors(Vp8ModeScore rd) + public void StoreDiffusionErrors(Vp8ModeScore rd) + { + for (int ch = 0; ch <= 1; ++ch) { - for (int ch = 0; ch <= 1; ++ch) - { - Span top = this.TopDerr.AsSpan((this.X * 4) + ch, 2); - Span left = this.LeftDerr.AsSpan(ch, 2); + Span top = this.TopDerr.AsSpan((this.X * 4) + ch, 2); + Span left = this.LeftDerr.AsSpan(ch, 2); - // restore err1 - left[0] = (sbyte)rd.Derr[ch, 0]; + // restore err1 + left[0] = (sbyte)rd.Derr[ch, 0]; - // 3/4th of err3 - left[1] = (sbyte)((3 * rd.Derr[ch, 2]) >> 2); + // 3/4th of err3 + left[1] = (sbyte)((3 * rd.Derr[ch, 2]) >> 2); - // err2 - top[0] = (sbyte)rd.Derr[ch, 1]; + // err2 + top[0] = (sbyte)rd.Derr[ch, 1]; - // 1/4th of err3. - top[1] = (sbyte)(rd.Derr[ch, 2] - left[1]); - } + // 1/4th of err3. + top[1] = (sbyte)(rd.Derr[ch, 2] - left[1]); } + } - /// - /// Returns true if iteration is finished. - /// - /// True if iterator is finished. - public bool IsDone() => this.CountDown <= 0; + /// + /// Returns true if iteration is finished. + /// + /// True if iterator is finished. + public bool IsDone() => this.CountDown <= 0; - /// - /// Go to next macroblock. - /// - /// Returns false if not finished. - public bool Next() + /// + /// Go to next macroblock. + /// + /// Returns false if not finished. + public bool Next() + { + if (++this.X == this.mbw) { - if (++this.X == this.mbw) - { - this.SetRow(++this.Y); - } - else - { - this.currentMbIdx++; - this.nzIdx++; - this.PredIdx += 4; - this.yTopIdx += 16; - this.uvTopIdx += 16; - } - - return --this.CountDown > 0; + this.SetRow(++this.Y); } - - public void SaveBoundary() + else { - int x = this.X; - int y = this.Y; - Span ySrc = this.YuvOut.AsSpan(YOffEnc); - Span uvSrc = this.YuvOut.AsSpan(UOffEnc); - if (x < this.mbw - 1) - { - // left - for (int i = 0; i < 16; i++) - { - this.YLeft[i + 1] = ySrc[15 + (i * WebpConstants.Bps)]; - } - - for (int i = 0; i < 8; i++) - { - this.UvLeft[i + 1] = uvSrc[7 + (i * WebpConstants.Bps)]; - this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebpConstants.Bps)]; - } - - // top-left (before 'top'!) - this.YLeft[0] = this.YTop[this.yTopIdx + 15]; - this.UvLeft[0] = this.UvTop[this.uvTopIdx + 0 + 7]; - this.UvLeft[16] = this.UvTop[this.uvTopIdx + 8 + 7]; - } - - if (y < this.mbh - 1) - { - // top - ySrc.Slice(15 * WebpConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); - uvSrc.Slice(7 * WebpConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); - } + this.currentMbIdx++; + this.nzIdx++; + this.PredIdx += 4; + this.yTopIdx += 16; + this.uvTopIdx += 16; } - public bool RotateI4(Span yuvOut) - { - Span blk = yuvOut[WebpLookupTables.Vp8Scan[this.I4]..]; - Span top = this.I4Boundary.AsSpan(); - int topOffset = this.I4BoundaryIdx; - int i; + return --this.CountDown > 0; + } - // Update the cache with 7 fresh samples. - for (i = 0; i <= 3; i++) + public void SaveBoundary() + { + int x = this.X; + int y = this.Y; + Span ySrc = this.YuvOut.AsSpan(YOffEnc); + Span uvSrc = this.YuvOut.AsSpan(UOffEnc); + if (x < this.mbw - 1) + { + // left + for (int i = 0; i < 16; i++) { - top[topOffset - 4 + i] = blk[i + (3 * WebpConstants.Bps)]; // Store future top samples. + this.YLeft[i + 1] = ySrc[15 + (i * WebpConstants.Bps)]; } - if ((this.I4 & 3) != 3) + for (int i = 0; i < 8; i++) { - // if not on the right sub-blocks #3, #7, #11, #15 - for (i = 0; i <= 2; i++) - { - // store future left samples - top[topOffset + i] = blk[3 + ((2 - i) * WebpConstants.Bps)]; - } - } - else - { - // else replicate top-right samples, as says the specs. - for (i = 0; i <= 3; i++) - { - top[topOffset + i] = top[topOffset + i + 4]; - } + this.UvLeft[i + 1] = uvSrc[7 + (i * WebpConstants.Bps)]; + this.UvLeft[i + 16 + 1] = uvSrc[15 + (i * WebpConstants.Bps)]; } - // move pointers to next sub-block - ++this.I4; - if (this.I4 == 16) - { - // we're done - return false; - } + // top-left (before 'top'!) + this.YLeft[0] = this.YTop[this.yTopIdx + 15]; + this.UvLeft[0] = this.UvTop[this.uvTopIdx + 0 + 7]; + this.UvLeft[16] = this.UvTop[this.uvTopIdx + 8 + 7]; + } + + if (y < this.mbh - 1) + { + // top + ySrc.Slice(15 * WebpConstants.Bps, 16).CopyTo(this.YTop.AsSpan(this.yTopIdx)); + uvSrc.Slice(7 * WebpConstants.Bps, 8 + 8).CopyTo(this.UvTop.AsSpan(this.uvTopIdx)); + } + } - this.I4BoundaryIdx = this.vp8TopLeftI4[this.I4]; + public bool RotateI4(Span yuvOut) + { + Span blk = yuvOut[WebpLookupTables.Vp8Scan[this.I4]..]; + Span top = this.I4Boundary.AsSpan(); + int topOffset = this.I4BoundaryIdx; + int i; - return true; + // Update the cache with 7 fresh samples. + for (i = 0; i <= 3; i++) + { + top[topOffset - 4 + i] = blk[i + (3 * WebpConstants.Bps)]; // Store future top samples. } - public void ResetAfterSkip() + if ((this.I4 & 3) != 3) { - if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) + // if not on the right sub-blocks #3, #7, #11, #15 + for (i = 0; i <= 2; i++) { - // Reset all predictors. - this.Nz[this.nzIdx] = 0; - this.LeftNz[8] = 0; + // store future left samples + top[topOffset + i] = blk[3 + ((2 - i) * WebpConstants.Bps)]; } - else + } + else + { + // else replicate top-right samples, as says the specs. + for (i = 0; i <= 3; i++) { - // Preserve the dc_nz bit. - this.Nz[this.nzIdx] &= 1 << 24; + top[topOffset + i] = top[topOffset + i + 4]; } } - public void MakeLuma16Preds() + // move pointers to next sub-block + ++this.I4; + if (this.I4 == 16) { - Span left = this.X != 0 ? this.YLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; - Vp8Encoding.EncPredLuma16(this.YuvP, left, top); + // we're done + return false; } - public void MakeChroma8Preds() - { - Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; - Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; - Vp8Encoding.EncPredChroma8(this.YuvP, left, top); - } + this.I4BoundaryIdx = this.vp8TopLeftI4[this.I4]; - public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx, this.Scratch.AsSpan(0, 4)); + return true; + } - public void SwapOut() + public void ResetAfterSkip() + { + if (this.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16) { - // Tuple swap uses 2 more IL bytes -#pragma warning disable IDE0180 // Use tuple to swap values - byte[] tmp = this.YuvOut; - this.YuvOut = this.YuvOut2; - this.YuvOut2 = tmp; -#pragma warning restore IDE0180 // Use tuple to swap values + // Reset all predictors. + this.Nz[this.nzIdx] = 0; + this.LeftNz[8] = 0; } - - public void NzToBytes() + else { - Span nz = this.Nz.AsSpan(); - - uint lnz = nz[this.nzIdx - 1]; - uint tnz = nz[this.nzIdx]; - Span topNz = this.TopNz; - Span leftNz = this.LeftNz; + // Preserve the dc_nz bit. + this.Nz[this.nzIdx] &= 1 << 24; + } + } - // Top-Y - topNz[0] = Bit(tnz, 12); - topNz[1] = Bit(tnz, 13); - topNz[2] = Bit(tnz, 14); - topNz[3] = Bit(tnz, 15); + public void MakeLuma16Preds() + { + Span left = this.X != 0 ? this.YLeft.AsSpan() : null; + Span top = this.Y != 0 ? this.YTop.AsSpan(this.yTopIdx) : null; + Vp8Encoding.EncPredLuma16(this.YuvP, left, top); + } - // Top-U - topNz[4] = Bit(tnz, 18); - topNz[5] = Bit(tnz, 19); + public void MakeChroma8Preds() + { + Span left = this.X != 0 ? this.UvLeft.AsSpan() : null; + Span top = this.Y != 0 ? this.UvTop.AsSpan(this.uvTopIdx) : null; + Vp8Encoding.EncPredChroma8(this.YuvP, left, top); + } - // Top-V - topNz[6] = Bit(tnz, 22); - topNz[7] = Bit(tnz, 23); + public void MakeIntra4Preds() => Vp8Encoding.EncPredLuma4(this.YuvP, this.I4Boundary, this.I4BoundaryIdx, this.Scratch.AsSpan(0, 4)); - // DC - topNz[8] = Bit(tnz, 24); + public void SwapOut() + { + // Tuple swap uses 2 more IL bytes +#pragma warning disable IDE0180 // Use tuple to swap values + byte[] tmp = this.YuvOut; + this.YuvOut = this.YuvOut2; + this.YuvOut2 = tmp; +#pragma warning restore IDE0180 // Use tuple to swap values + } - // left-Y - leftNz[0] = Bit(lnz, 3); - leftNz[1] = Bit(lnz, 7); - leftNz[2] = Bit(lnz, 11); - leftNz[3] = Bit(lnz, 15); + public void NzToBytes() + { + Span nz = this.Nz.AsSpan(); - // left-U - leftNz[4] = Bit(lnz, 17); - leftNz[5] = Bit(lnz, 19); + uint lnz = nz[this.nzIdx - 1]; + uint tnz = nz[this.nzIdx]; + Span topNz = this.TopNz; + Span leftNz = this.LeftNz; - // left-V - leftNz[6] = Bit(lnz, 21); - leftNz[7] = Bit(lnz, 23); + // Top-Y + topNz[0] = Bit(tnz, 12); + topNz[1] = Bit(tnz, 13); + topNz[2] = Bit(tnz, 14); + topNz[3] = Bit(tnz, 15); - // left-DC is special, iterated separately. - } + // Top-U + topNz[4] = Bit(tnz, 18); + topNz[5] = Bit(tnz, 19); - public void BytesToNz() - { - uint nz = 0; - int[] topNz = this.TopNz; - int[] leftNz = this.LeftNz; + // Top-V + topNz[6] = Bit(tnz, 22); + topNz[7] = Bit(tnz, 23); - // top - nz |= (uint)((topNz[0] << 12) | (topNz[1] << 13)); - nz |= (uint)((topNz[2] << 14) | (topNz[3] << 15)); - nz |= (uint)((topNz[4] << 18) | (topNz[5] << 19)); - nz |= (uint)((topNz[6] << 22) | (topNz[7] << 23)); - nz |= (uint)(topNz[8] << 24); // we propagate the top bit, esp. for intra4 + // DC + topNz[8] = Bit(tnz, 24); - // left - nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); - nz |= (uint)(leftNz[2] << 11); - nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); + // left-Y + leftNz[0] = Bit(lnz, 3); + leftNz[1] = Bit(lnz, 7); + leftNz[2] = Bit(lnz, 11); + leftNz[3] = Bit(lnz, 15); - this.Nz[this.nzIdx] = nz; - } + // left-U + leftNz[4] = Bit(lnz, 17); + leftNz[5] = Bit(lnz, 19); - private static void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) - { - int dstIdx = 0; - int srcIdx = 0; - for (int i = 0; i < h; i++) - { - // memcpy(dst, src, w); - src.Slice(srcIdx, w).CopyTo(dst[dstIdx..]); - if (w < size) - { - // memset(dst + w, dst[w - 1], size - w); - dst.Slice(dstIdx + w, size - w).Fill(dst[dstIdx + w - 1]); - } + // left-V + leftNz[6] = Bit(lnz, 21); + leftNz[7] = Bit(lnz, 23); - dstIdx += WebpConstants.Bps; - srcIdx += srcStride; - } + // left-DC is special, iterated separately. + } - for (int i = h; i < size; i++) - { - // memcpy(dst, dst - BPS, size); - dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst[dstIdx..]); - dstIdx += WebpConstants.Bps; - } - } + public void BytesToNz() + { + uint nz = 0; + int[] topNz = this.TopNz; + int[] leftNz = this.LeftNz; + + // top + nz |= (uint)((topNz[0] << 12) | (topNz[1] << 13)); + nz |= (uint)((topNz[2] << 14) | (topNz[3] << 15)); + nz |= (uint)((topNz[4] << 18) | (topNz[5] << 19)); + nz |= (uint)((topNz[6] << 22) | (topNz[7] << 23)); + nz |= (uint)(topNz[8] << 24); // we propagate the top bit, esp. for intra4 + + // left + nz |= (uint)((leftNz[0] << 3) | (leftNz[1] << 7)); + nz |= (uint)(leftNz[2] << 11); + nz |= (uint)((leftNz[4] << 17) | (leftNz[6] << 21)); + + this.Nz[this.nzIdx] = nz; + } - private static void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen) + private static void ImportBlock(Span src, int srcStride, Span dst, int w, int h, int size) + { + int dstIdx = 0; + int srcIdx = 0; + for (int i = 0; i < h; i++) { - int i; - int srcIdx = 0; - for (i = 0; i < len; i++) + // memcpy(dst, src, w); + src.Slice(srcIdx, w).CopyTo(dst[dstIdx..]); + if (w < size) { - dst[i] = src[srcIdx]; - srcIdx += srcStride; + // memset(dst + w, dst[w - 1], size - w); + dst.Slice(dstIdx + w, size - w).Fill(dst[dstIdx + w - 1]); } - for (; i < totalLen; i++) - { - dst[i] = dst[len - 1]; - } + dstIdx += WebpConstants.Bps; + srcIdx += srcStride; } - /// - /// Restart a scan. - /// - private void Reset() + for (int i = h; i < size; i++) { - this.SetRow(0); - this.SetCountDown(this.mbw * this.mbh); - this.InitTop(); - - Array.Clear(this.BitCount); + // memcpy(dst, dst - BPS, size); + dst.Slice(dstIdx - WebpConstants.Bps, size).CopyTo(dst[dstIdx..]); + dstIdx += WebpConstants.Bps; } + } - /// - /// Reset iterator position to row 'y'. - /// - /// The y position. - private void SetRow(int y) + private static void ImportLine(Span src, int srcStride, Span dst, int len, int totalLen) + { + int i; + int srcIdx = 0; + for (i = 0; i < len; i++) { - this.X = 0; - this.Y = y; - this.currentMbIdx = y * this.mbw; - this.nzIdx = 1; // note: in reference source nz starts at -1. - this.yTopIdx = 0; - this.uvTopIdx = 0; - this.PredIdx = this.predsWidth + (y * 4 * this.predsWidth); - - this.InitLeft(); + dst[i] = src[srcIdx]; + srcIdx += srcStride; } - private void InitLeft() + for (; i < totalLen; i++) { - Span yLeft = this.YLeft.AsSpan(); - Span uLeft = this.UvLeft.AsSpan(0, 16); - Span vLeft = this.UvLeft.AsSpan(16, 16); - byte val = (byte)(this.Y > 0 ? 129 : 127); - yLeft[0] = val; - uLeft[0] = val; - vLeft[0] = val; + dst[i] = dst[len - 1]; + } + } - yLeft.Slice(1, 16).Fill(129); - uLeft.Slice(1, 8).Fill(129); - vLeft.Slice(1, 8).Fill(129); + /// + /// Restart a scan. + /// + private void Reset() + { + this.SetRow(0); + this.SetCountDown(this.mbw * this.mbh); + this.InitTop(); - this.LeftNz[8] = 0; + Array.Clear(this.BitCount); + } - this.LeftDerr.AsSpan().Clear(); - } + /// + /// Reset iterator position to row 'y'. + /// + /// The y position. + private void SetRow(int y) + { + this.X = 0; + this.Y = y; + this.currentMbIdx = y * this.mbw; + this.nzIdx = 1; // note: in reference source nz starts at -1. + this.yTopIdx = 0; + this.uvTopIdx = 0; + this.PredIdx = this.predsWidth + (y * 4 * this.predsWidth); + + this.InitLeft(); + } - private void InitTop() - { - int topSize = this.mbw * 16; - this.YTop.AsSpan(0, topSize).Fill(127); - this.UvTop.AsSpan().Fill(127); - this.Nz.AsSpan().Clear(); + private void InitLeft() + { + Span yLeft = this.YLeft.AsSpan(); + Span uLeft = this.UvLeft.AsSpan(0, 16); + Span vLeft = this.UvLeft.AsSpan(16, 16); + byte val = (byte)(this.Y > 0 ? 129 : 127); + yLeft[0] = val; + uLeft[0] = val; + vLeft[0] = val; - int predsW = (4 * this.mbw) + 1; - int predsH = (4 * this.mbh) + 1; - int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Clear(); + yLeft.Slice(1, 16).Fill(129); + uLeft.Slice(1, 8).Fill(129); + vLeft.Slice(1, 8).Fill(129); - this.TopDerr.AsSpan().Clear(); - } + this.LeftNz[8] = 0; - private static int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0; + this.LeftDerr.AsSpan().Clear(); + } + + private void InitTop() + { + int topSize = this.mbw * 16; + this.YTop.AsSpan(0, topSize).Fill(127); + this.UvTop.AsSpan().Fill(127); + this.Nz.AsSpan().Clear(); - /// - /// Set count down. - /// - /// Number of iterations to go. - private void SetCountDown(int countDown) => this.CountDown = countDown; + int predsW = (4 * this.mbw) + 1; + int predsH = (4 * this.mbh) + 1; + int predsSize = predsW * predsH; + this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Clear(); + + this.TopDerr.AsSpan().Clear(); } + + private static int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0; + + /// + /// Set count down. + /// + /// Number of iterations to go. + private void SetCountDown(int countDown) => this.CountDown = countDown; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs index 7feffd9988..070e705747 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncProba.cs @@ -1,265 +1,262 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +internal class Vp8EncProba { - internal class Vp8EncProba + /// + /// Last (inclusive) level with variable cost. + /// + private const int MaxVariableLevel = 67; + + /// + /// Value below which using skipProba is OK. + /// + private const int SkipProbaThreshold = 250; + + /// + /// Initializes a new instance of the class. + /// + public Vp8EncProba() { - /// - /// Last (inclusive) level with variable cost. - /// - private const int MaxVariableLevel = 67; - - /// - /// Value below which using skipProba is OK. - /// - private const int SkipProbaThreshold = 250; - - /// - /// Initializes a new instance of the class. - /// - public Vp8EncProba() + this.Dirty = true; + this.UseSkipProba = false; + this.Segments = new byte[3]; + this.Coeffs = new Vp8BandProbas[WebpConstants.NumTypes][]; + for (int i = 0; i < this.Coeffs.Length; i++) { - this.Dirty = true; - this.UseSkipProba = false; - this.Segments = new byte[3]; - this.Coeffs = new Vp8BandProbas[WebpConstants.NumTypes][]; - for (int i = 0; i < this.Coeffs.Length; i++) + this.Coeffs[i] = new Vp8BandProbas[WebpConstants.NumBands]; + for (int j = 0; j < this.Coeffs[i].Length; j++) { - this.Coeffs[i] = new Vp8BandProbas[WebpConstants.NumBands]; - for (int j = 0; j < this.Coeffs[i].Length; j++) - { - this.Coeffs[i][j] = new Vp8BandProbas(); - } + this.Coeffs[i][j] = new Vp8BandProbas(); } + } - this.Stats = new Vp8Stats[WebpConstants.NumTypes][]; - for (int i = 0; i < this.Coeffs.Length; i++) + this.Stats = new Vp8Stats[WebpConstants.NumTypes][]; + for (int i = 0; i < this.Coeffs.Length; i++) + { + this.Stats[i] = new Vp8Stats[WebpConstants.NumBands]; + for (int j = 0; j < this.Stats[i].Length; j++) { - this.Stats[i] = new Vp8Stats[WebpConstants.NumBands]; - for (int j = 0; j < this.Stats[i].Length; j++) - { - this.Stats[i][j] = new Vp8Stats(); - } + this.Stats[i][j] = new Vp8Stats(); } + } - this.LevelCost = new Vp8Costs[WebpConstants.NumTypes][]; - for (int i = 0; i < this.LevelCost.Length; i++) + this.LevelCost = new Vp8Costs[WebpConstants.NumTypes][]; + for (int i = 0; i < this.LevelCost.Length; i++) + { + this.LevelCost[i] = new Vp8Costs[WebpConstants.NumBands]; + for (int j = 0; j < this.LevelCost[i].Length; j++) { - this.LevelCost[i] = new Vp8Costs[WebpConstants.NumBands]; - for (int j = 0; j < this.LevelCost[i].Length; j++) - { - this.LevelCost[i][j] = new Vp8Costs(); - } + this.LevelCost[i][j] = new Vp8Costs(); } + } - this.RemappedCosts = new Vp8Costs[WebpConstants.NumTypes][]; - for (int i = 0; i < this.RemappedCosts.Length; i++) + this.RemappedCosts = new Vp8Costs[WebpConstants.NumTypes][]; + for (int i = 0; i < this.RemappedCosts.Length; i++) + { + this.RemappedCosts[i] = new Vp8Costs[16]; + for (int j = 0; j < this.RemappedCosts[i].Length; j++) { - this.RemappedCosts[i] = new Vp8Costs[16]; - for (int j = 0; j < this.RemappedCosts[i].Length; j++) - { - this.RemappedCosts[i][j] = new Vp8Costs(); - } + this.RemappedCosts[i][j] = new Vp8Costs(); } + } - // Initialize with default probabilities. - this.Segments.AsSpan().Fill(255); - for (int t = 0; t < WebpConstants.NumTypes; ++t) + // Initialize with default probabilities. + this.Segments.AsSpan().Fill(255); + for (int t = 0; t < WebpConstants.NumTypes; ++t) + { + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int b = 0; b < WebpConstants.NumBands; ++b) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int c = 0; c < WebpConstants.NumCtx; ++c) + Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - Vp8ProbaArray dst = this.Coeffs[t][b].Probabilities[c]; - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - dst.Probabilities[p] = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - } + dst.Probabilities[p] = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; } } } } + } - /// - /// Gets the probabilities for segment tree. - /// - public byte[] Segments { get; } + /// + /// Gets the probabilities for segment tree. + /// + public byte[] Segments { get; } - /// - /// Gets or sets the final probability of being skipped. - /// - public byte SkipProba { get; set; } + /// + /// Gets or sets the final probability of being skipped. + /// + public byte SkipProba { get; set; } - /// - /// Gets or sets a value indicating whether to use the skip probability. - /// - public bool UseSkipProba { get; set; } + /// + /// Gets or sets a value indicating whether to use the skip probability. + /// + public bool UseSkipProba { get; set; } - public Vp8BandProbas[][] Coeffs { get; } + public Vp8BandProbas[][] Coeffs { get; } - public Vp8Stats[][] Stats { get; } + public Vp8Stats[][] Stats { get; } - public Vp8Costs[][] LevelCost { get; } + public Vp8Costs[][] LevelCost { get; } - public Vp8Costs[][] RemappedCosts { get; } + public Vp8Costs[][] RemappedCosts { get; } - /// - /// Gets or sets the number of skipped blocks. - /// - public int NbSkip { get; set; } + /// + /// Gets or sets the number of skipped blocks. + /// + public int NbSkip { get; set; } - /// - /// Gets or sets a value indicating whether CalculateLevelCosts() needs to be called. - /// - public bool Dirty { get; set; } + /// + /// Gets or sets a value indicating whether CalculateLevelCosts() needs to be called. + /// + public bool Dirty { get; set; } - public void CalculateLevelCosts() + public void CalculateLevelCosts() + { + if (!this.Dirty) { - if (!this.Dirty) - { - return; // Nothing to do. - } + return; // Nothing to do. + } - for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype) + for (int ctype = 0; ctype < WebpConstants.NumTypes; ++ctype) + { + for (int band = 0; band < WebpConstants.NumBands; ++band) { - for (int band = 0; band < WebpConstants.NumBands; ++band) + for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { - for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) + Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; + Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx]; + int cost0 = ctx > 0 ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; + int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; + int v; + table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); + for (v = 1; v <= MaxVariableLevel; ++v) { - Vp8ProbaArray p = this.Coeffs[ctype][band].Probabilities[ctx]; - Vp8CostArray table = this.LevelCost[ctype][band].Costs[ctx]; - int cost0 = ctx > 0 ? LossyUtils.Vp8BitCost(1, p.Probabilities[0]) : 0; - int costBase = LossyUtils.Vp8BitCost(1, p.Probabilities[1]) + cost0; - int v; - table.Costs[0] = (ushort)(LossyUtils.Vp8BitCost(0, p.Probabilities[1]) + cost0); - for (v = 1; v <= MaxVariableLevel; ++v) - { - table.Costs[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); - } - - // Starting at level 67 and up, the variable part of the cost is actually constant + table.Costs[v] = (ushort)(costBase + VariableLevelCost(v, p.Probabilities)); } + + // Starting at level 67 and up, the variable part of the cost is actually constant } + } - for (int n = 0; n < 16; ++n) + for (int n = 0; n < 16; ++n) + { + for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) { - for (int ctx = 0; ctx < WebpConstants.NumCtx; ++ctx) - { - Vp8CostArray dst = this.RemappedCosts[ctype][n].Costs[ctx]; - Vp8CostArray src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs[ctx]; - src.Costs.CopyTo(dst.Costs.AsSpan()); - } + Vp8CostArray dst = this.RemappedCosts[ctype][n].Costs[ctx]; + Vp8CostArray src = this.LevelCost[ctype][WebpConstants.Vp8EncBands[n]].Costs[ctx]; + src.Costs.CopyTo(dst.Costs.AsSpan()); } } - - this.Dirty = false; } - public int FinalizeTokenProbas() + this.Dirty = false; + } + + public int FinalizeTokenProbas() + { + bool hasChanged = false; + int size = 0; + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - bool hasChanged = false; - int size = 0; - for (int t = 0; t < WebpConstants.NumTypes; ++t) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int b = 0; b < WebpConstants.NumBands; ++b) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int c = 0; c < WebpConstants.NumCtx; ++c) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - for (int p = 0; p < WebpConstants.NumProbas; ++p) + uint stats = this.Stats[t][b].Stats[c].Stats[p]; + int nb = (int)((stats >> 0) & 0xffff); + int total = (int)((stats >> 16) & 0xffff); + int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; + int oldP = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; + int newP = CalcTokenProba(nb, total); + int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba); + int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256); + bool useNewP = oldCost > newCost; + size += LossyUtils.Vp8BitCost(useNewP ? 1 : 0, (byte)updateProba); + if (useNewP) { - uint stats = this.Stats[t][b].Stats[c].Stats[p]; - int nb = (int)((stats >> 0) & 0xffff); - int total = (int)((stats >> 16) & 0xffff); - int updateProba = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; - int oldP = WebpLookupTables.DefaultCoeffsProba[t, b, c, p]; - int newP = CalcTokenProba(nb, total); - int oldCost = BranchCost(nb, total, oldP) + LossyUtils.Vp8BitCost(0, (byte)updateProba); - int newCost = BranchCost(nb, total, newP) + LossyUtils.Vp8BitCost(1, (byte)updateProba) + (8 * 256); - bool useNewP = oldCost > newCost; - size += LossyUtils.Vp8BitCost(useNewP ? 1 : 0, (byte)updateProba); - if (useNewP) - { - // Only use proba that seem meaningful enough. - this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)newP; - hasChanged |= newP != oldP; - size += 8 * 256; - } - else - { - this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)oldP; - } + // Only use proba that seem meaningful enough. + this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)newP; + hasChanged |= newP != oldP; + size += 8 * 256; + } + else + { + this.Coeffs[t][b].Probabilities[c].Probabilities[p] = (byte)oldP; } } } } - - this.Dirty = hasChanged; - return size; } - public int FinalizeSkipProba(int mbw, int mbh) - { - int nbMbs = mbw * mbh; - int nbEvents = this.NbSkip; - this.SkipProba = (byte)CalcSkipProba(nbEvents, nbMbs); - this.UseSkipProba = this.SkipProba < SkipProbaThreshold; + this.Dirty = hasChanged; + return size; + } - int size = 256; - if (this.UseSkipProba) - { - size += (nbEvents * LossyUtils.Vp8BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * LossyUtils.Vp8BitCost(0, this.SkipProba)); - size += 8 * 256; // cost of signaling the skipProba itself. - } + public int FinalizeSkipProba(int mbw, int mbh) + { + int nbMbs = mbw * mbh; + int nbEvents = this.NbSkip; + this.SkipProba = (byte)CalcSkipProba(nbEvents, nbMbs); + this.UseSkipProba = this.SkipProba < SkipProbaThreshold; - return size; + int size = 256; + if (this.UseSkipProba) + { + size += (nbEvents * LossyUtils.Vp8BitCost(1, this.SkipProba)) + ((nbMbs - nbEvents) * LossyUtils.Vp8BitCost(0, this.SkipProba)); + size += 8 * 256; // cost of signaling the skipProba itself. } - public void ResetTokenStats() + return size; + } + + public void ResetTokenStats() + { + for (int t = 0; t < WebpConstants.NumTypes; ++t) { - for (int t = 0; t < WebpConstants.NumTypes; ++t) + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int b = 0; b < WebpConstants.NumBands; ++b) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int c = 0; c < WebpConstants.NumCtx; ++c) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - this.Stats[t][b].Stats[c].Stats[p] = 0; - } + this.Stats[t][b].Stats[c].Stats[p] = 0; } } } } + } - private static int CalcSkipProba(long nb, long total) => (int)(total != 0 ? (total - nb) * 255 / total : 255); + private static int CalcSkipProba(long nb, long total) => (int)(total != 0 ? (total - nb) * 255 / total : 255); - private static int VariableLevelCost(int level, Span probas) + private static int VariableLevelCost(int level, Span probas) + { + int pattern = WebpLookupTables.Vp8LevelCodes[level - 1][0]; + int bits = WebpLookupTables.Vp8LevelCodes[level - 1][1]; + int cost = 0; + for (int i = 2; pattern != 0; i++) { - int pattern = WebpLookupTables.Vp8LevelCodes[level - 1][0]; - int bits = WebpLookupTables.Vp8LevelCodes[level - 1][1]; - int cost = 0; - for (int i = 2; pattern != 0; i++) + if ((pattern & 1) != 0) { - if ((pattern & 1) != 0) - { - cost += LossyUtils.Vp8BitCost(bits & 1, probas[i]); - } - - bits >>= 1; - pattern >>= 1; + cost += LossyUtils.Vp8BitCost(bits & 1, probas[i]); } - return cost; + bits >>= 1; + pattern >>= 1; } - // Collect statistics and deduce probabilities for next coding pass. - // Return the total bit-cost for coding the probability updates. - private static int CalcTokenProba(int nb, int total) => nb != 0 ? (255 - (nb * 255 / total)) : 255; - - // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. - private static int BranchCost(int nb, int total, int proba) => (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); + return cost; } + + // Collect statistics and deduce probabilities for next coding pass. + // Return the total bit-cost for coding the probability updates. + private static int CalcTokenProba(int nb, int total) => nb != 0 ? (255 - (nb * 255 / total)) : 255; + + // Cost of coding 'nb' 1's and 'total-nb' 0's using 'proba' probability. + private static int BranchCost(int nb, int total, int proba) => (nb * LossyUtils.Vp8BitCost(1, (byte)proba)) + ((total - nb) * LossyUtils.Vp8BitCost(0, (byte)proba)); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs index ba91c479d0..07bfe25b7d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncSegmentHeader.cs @@ -1,34 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8EncSegmentHeader { - internal class Vp8EncSegmentHeader + /// + /// Initializes a new instance of the class. + /// + /// Number of segments. + public Vp8EncSegmentHeader(int numSegments) { - /// - /// Initializes a new instance of the class. - /// - /// Number of segments. - public Vp8EncSegmentHeader(int numSegments) - { - this.NumSegments = numSegments; - this.UpdateMap = this.NumSegments > 1; - this.Size = 0; - } + this.NumSegments = numSegments; + this.UpdateMap = this.NumSegments > 1; + this.Size = 0; + } - /// - /// Gets the actual number of segments. 1 segment only = unused. - /// - public int NumSegments { get; } + /// + /// Gets the actual number of segments. 1 segment only = unused. + /// + public int NumSegments { get; } - /// - /// Gets or sets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. - /// - public bool UpdateMap { get; set; } + /// + /// Gets or sets a value indicating whether to update the segment map or not. Must be false if there's only 1 segment. + /// + public bool UpdateMap { get; set; } - /// - /// Gets or sets the bit-cost for transmitting the segment map. - /// - public int Size { get; set; } - } + /// + /// Gets or sets the bit-cost for transmitting the segment map. + /// + public int Size { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 12b2ea7dc2..b15ccc052b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -1,1164 +1,1161 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Encoder for lossy webp images. +/// +internal class Vp8Encoder : IDisposable { /// - /// Encoder for lossy webp images. + /// The to use for buffer allocations. /// - internal class Vp8Encoder : IDisposable - { - /// - /// The to use for buffer allocations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// The quality, that will be used to encode the image. - /// - private readonly int quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// Number of entropy-analysis passes (in [1..10]). - /// - private readonly int entropyPasses; - - /// - /// Specify the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). A value of 0 will turn off any filtering. - /// - private readonly int filterStrength; - - /// - /// The spatial noise shaping. 0=off, 100=maximum. - /// - private readonly int spatialNoiseShaping; - - /// - /// A bit writer for writing lossy webp streams. - /// - private Vp8BitWriter bitWriter; - - private readonly Vp8RdLevel rdOptLevel; - - private int maxI4HeaderBits; - - /// - /// Global susceptibility. - /// - private int alpha; - - /// - /// U/V quantization susceptibility. - /// - private int uvAlpha; - - private readonly bool alphaCompression; - - private const int NumMbSegments = 4; - - private const int MaxItersKMeans = 6; - - // Convergence is considered reached if dq < DqLimit - private const float DqLimit = 0.4f; - - private const ulong Partition0SizeLimit = (WebpConstants.Vp8MaxPartition0Size - 2048UL) << 11; - - private const long HeaderSizeEstimate = WebpConstants.RiffHeaderSize + WebpConstants.ChunkHeaderSize + WebpConstants.Vp8FrameHeaderSize; - - private const int QMin = 0; - - private const int QMax = 100; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The width of the input image. - /// The height of the input image. - /// The encoding quality. - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// Number of entropy-analysis passes (in [1..10]). - /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// The spatial noise shaping. 0=off, 100=maximum. - /// If true, the alpha channel will be compressed with the lossless compression. - public Vp8Encoder( - MemoryAllocator memoryAllocator, - Configuration configuration, - int width, - int height, - int quality, - WebpEncodingMethod method, - int entropyPasses, - int filterStrength, - int spatialNoiseShaping, - bool alphaCompression) - { - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.Width = width; - this.Height = height; - this.quality = Numerics.Clamp(quality, 0, 100); - this.method = method; - this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); - this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); - this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); - this.alphaCompression = alphaCompression; - if (method is WebpEncodingMethod.BestQuality) - { - this.rdOptLevel = Vp8RdLevel.RdOptTrellisAll; - } - else if (method >= WebpEncodingMethod.Level5) - { - this.rdOptLevel = Vp8RdLevel.RdOptTrellis; - } - else if (method >= WebpEncodingMethod.Level3) - { - this.rdOptLevel = Vp8RdLevel.RdOptBasic; - } - else - { - this.rdOptLevel = Vp8RdLevel.RdOptNone; - } + private readonly MemoryAllocator memoryAllocator; - int pixelCount = width * height; - this.Mbw = (width + 15) >> 4; - this.Mbh = (height + 15) >> 4; - int uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); - this.Y = this.memoryAllocator.Allocate(pixelCount); - this.U = this.memoryAllocator.Allocate(uvSize); - this.V = this.memoryAllocator.Allocate(uvSize); - this.YTop = new byte[this.Mbw * 16]; - this.UvTop = new byte[this.Mbw * 16 * 2]; - this.Nz = new uint[this.Mbw + 1]; - this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.Mbw * this.Mbh); - this.TopDerr = new sbyte[this.Mbw * 4]; - - // TODO: make partition_limit configurable? - const int limit = 100; // original code: limit = 100 - config->partition_limit; - this.maxI4HeaderBits = - 256 * 16 * 16 * limit * limit / (100 * 100); // ... modulated with a quadratic curve. - - this.MbInfo = new Vp8MacroBlockInfo[this.Mbw * this.Mbh]; - for (int i = 0; i < this.MbInfo.Length; i++) - { - this.MbInfo[i] = new Vp8MacroBlockInfo(); - } + /// + /// The global configuration. + /// + private readonly Configuration configuration; - this.SegmentInfos = new Vp8SegmentInfo[4]; - for (int i = 0; i < 4; i++) - { - this.SegmentInfos[i] = new Vp8SegmentInfo(); - } + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; - this.FilterHeader = new Vp8FilterHeader(); - int predSize = (((4 * this.Mbw) + 1) * ((4 * this.Mbh) + 1)) + this.PredsWidth + 1; - this.PredsWidth = (4 * this.Mbw) + 1; - this.Proba = new Vp8EncProba(); - this.Preds = new byte[predSize + this.PredsWidth + this.Mbw]; + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly WebpEncodingMethod method; - // Initialize with default values, which the reference c implementation uses, - // to be able to compare to the original and spot differences. - this.Preds.AsSpan().Fill(205); - this.Nz.AsSpan().Fill(3452816845); + /// + /// Number of entropy-analysis passes (in [1..10]). + /// + private readonly int entropyPasses; - this.ResetBoundaryPredictions(); - } + /// + /// Specify the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). A value of 0 will turn off any filtering. + /// + private readonly int filterStrength; - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan AverageBytesPerMb => new byte[] { 50, 24, 16, 9, 7, 5, 3, 2 }; - - public int BaseQuant { get; set; } + /// + /// The spatial noise shaping. 0=off, 100=maximum. + /// + private readonly int spatialNoiseShaping; - /// - /// Gets the probabilities. - /// - public Vp8EncProba Proba { get; } + /// + /// A bit writer for writing lossy webp streams. + /// + private Vp8BitWriter bitWriter; - /// - /// Gets the segment features. - /// - public Vp8EncSegmentHeader SegmentHeader { get; private set; } - - /// - /// Gets the segment infos. - /// - public Vp8SegmentInfo[] SegmentInfos { get; } - - /// - /// Gets the macro block info's. - /// - public Vp8MacroBlockInfo[] MbInfo { get; } - - /// - /// Gets the filter header. - /// - public Vp8FilterHeader FilterHeader { get; } - - /// - /// Gets or sets the global susceptibility. - /// - public int Alpha { get; set; } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets the stride of the prediction plane (=4*mb_w + 1) - /// - public int PredsWidth { get; } - - /// - /// Gets the macroblock width. - /// - public int Mbw { get; } - - /// - /// Gets the macroblock height. - /// - public int Mbh { get; } - - public int DqY1Dc { get; private set; } - - public int DqY2Ac { get; private set; } - - public int DqY2Dc { get; private set; } - - public int DqUvAc { get; private set; } - - public int DqUvDc { get; private set; } - - /// - /// Gets the luma component. - /// - private IMemoryOwner Y { get; } - - /// - /// Gets the chroma U component. - /// - private IMemoryOwner U { get; } - - /// - /// Gets the chroma U component. - /// - private IMemoryOwner V { get; } - - /// - /// Gets the top luma samples. - /// - public byte[] YTop { get; } - - /// - /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). - /// - public byte[] UvTop { get; } - - /// - /// Gets the non-zero pattern. - /// - public uint[] Nz { get; } - - /// - /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). - /// - public byte[] Preds { get; } - - /// - /// Gets the diffusion error. - /// - public sbyte[] TopDerr { get; } - - /// - /// Gets a rough limit for header bits per MB. - /// - private int MbHeaderLimit { get; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - int width = image.Width; - int height = image.Height; - int pixelCount = width * height; - Span y = this.Y.GetSpan(); - Span u = this.U.GetSpan(); - Span v = this.V.GetSpan(); - bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v); - - int yStride = width; - int uvStride = (yStride + 1) >> 1; - - Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); - int[] alphas = new int[WebpConstants.MaxAlpha + 1]; - this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); - int totalMb = this.Mbw * this.Mbw; - this.alpha /= totalMb; - this.uvAlpha /= totalMb; - - // Analysis is done, proceed to actual encoding. - this.SegmentHeader = new Vp8EncSegmentHeader(4); - this.AssignSegments(alphas); - this.SetLoopParams(this.quality); - - // Initialize the bitwriter. - int averageBytesPerMacroBlock = AverageBytesPerMb[this.BaseQuant >> 4]; - int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; - this.bitWriter = new Vp8BitWriter(expectedSize, this); - - // Extract and encode alpha channel data, if present. - int alphaDataSize = 0; - bool alphaCompressionSucceeded = false; - using AlphaEncoder alphaEncoder = new(); - Span alphaData = Span.Empty; - if (hasAlpha) - { - // TODO: This can potentially run in an separate task. - IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize); - alphaData = encodedAlphaData.GetSpan(); - if (alphaDataSize < pixelCount) - { - // Only use compressed data, if the compressed data is actually smaller then the uncompressed data. - alphaCompressionSucceeded = true; - } - } + private readonly Vp8RdLevel rdOptLevel; - // Stats-collection loop. - this.StatLoop(width, height, yStride, uvStride); - it.Init(); - Vp8EncIterator.InitFilter(); - Vp8ModeScore info = new(); - Vp8Residual residual = new(); - do - { - bool dontUseSkip = !this.Proba.UseSkipProba; - info.Clear(); - it.Import(y, u, v, yStride, uvStride, width, height, false); + private int maxI4HeaderBits; - // Warning! order is important: first call VP8Decimate() and - // *then* decide how to code the skip decision if there's one. - if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip) - { - this.CodeResiduals(it, info, residual); - } - else - { - it.ResetAfterSkip(); - } + /// + /// Global susceptibility. + /// + private int alpha; - it.SaveBoundary(); - } - while (it.Next()); + /// + /// U/V quantization susceptibility. + /// + private int uvAlpha; + + private readonly bool alphaCompression; + + private const int NumMbSegments = 4; + + private const int MaxItersKMeans = 6; + + // Convergence is considered reached if dq < DqLimit + private const float DqLimit = 0.4f; + + private const ulong Partition0SizeLimit = (WebpConstants.Vp8MaxPartition0Size - 2048UL) << 11; - // Store filter stats. - this.AdjustFilterStrength(); - - // Write bytes from the bitwriter buffer to the stream. - ImageMetadata metadata = image.Metadata; - metadata.SyncProfiles(); - this.bitWriter.WriteEncodedImageToStream( - stream, - metadata.ExifProfile, - metadata.XmpProfile, - metadata.IccProfile, - (uint)width, - (uint)height, - hasAlpha, - alphaData[..alphaDataSize], - this.alphaCompression && alphaCompressionSucceeded); + private const long HeaderSizeEstimate = WebpConstants.RiffHeaderSize + WebpConstants.ChunkHeaderSize + WebpConstants.Vp8FrameHeaderSize; + + private const int QMin = 0; + + private const int QMax = 100; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The global configuration. + /// The width of the input image. + /// The height of the input image. + /// The encoding quality. + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// Number of entropy-analysis passes (in [1..10]). + /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// The spatial noise shaping. 0=off, 100=maximum. + /// If true, the alpha channel will be compressed with the lossless compression. + public Vp8Encoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + int width, + int height, + int quality, + WebpEncodingMethod method, + int entropyPasses, + int filterStrength, + int spatialNoiseShaping, + bool alphaCompression) + { + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.Width = width; + this.Height = height; + this.quality = Numerics.Clamp(quality, 0, 100); + this.method = method; + this.entropyPasses = Numerics.Clamp(entropyPasses, 1, 10); + this.filterStrength = Numerics.Clamp(filterStrength, 0, 100); + this.spatialNoiseShaping = Numerics.Clamp(spatialNoiseShaping, 0, 100); + this.alphaCompression = alphaCompression; + if (method is WebpEncodingMethod.BestQuality) + { + this.rdOptLevel = Vp8RdLevel.RdOptTrellisAll; + } + else if (method >= WebpEncodingMethod.Level5) + { + this.rdOptLevel = Vp8RdLevel.RdOptTrellis; + } + else if (method >= WebpEncodingMethod.Level3) + { + this.rdOptLevel = Vp8RdLevel.RdOptBasic; + } + else + { + this.rdOptLevel = Vp8RdLevel.RdOptNone; } - /// - public void Dispose() + int pixelCount = width * height; + this.Mbw = (width + 15) >> 4; + this.Mbh = (height + 15) >> 4; + int uvSize = ((width + 1) >> 1) * ((height + 1) >> 1); + this.Y = this.memoryAllocator.Allocate(pixelCount); + this.U = this.memoryAllocator.Allocate(uvSize); + this.V = this.memoryAllocator.Allocate(uvSize); + this.YTop = new byte[this.Mbw * 16]; + this.UvTop = new byte[this.Mbw * 16 * 2]; + this.Nz = new uint[this.Mbw + 1]; + this.MbHeaderLimit = 256 * 510 * 8 * 1024 / (this.Mbw * this.Mbh); + this.TopDerr = new sbyte[this.Mbw * 4]; + + // TODO: make partition_limit configurable? + const int limit = 100; // original code: limit = 100 - config->partition_limit; + this.maxI4HeaderBits = + 256 * 16 * 16 * limit * limit / (100 * 100); // ... modulated with a quadratic curve. + + this.MbInfo = new Vp8MacroBlockInfo[this.Mbw * this.Mbh]; + for (int i = 0; i < this.MbInfo.Length; i++) { - this.Y.Dispose(); - this.U.Dispose(); - this.V.Dispose(); + this.MbInfo[i] = new Vp8MacroBlockInfo(); } - /// - /// Only collect statistics(number of skips, token usage, ...). - /// This is used for deciding optimal probabilities. It also modifies the - /// quantizer value if some target (size, PSNR) was specified. - /// - /// The image width. - /// The image height. - /// The y-luminance stride. - /// The uv stride. - private void StatLoop(int width, int height, int yStride, int uvStride) + this.SegmentInfos = new Vp8SegmentInfo[4]; + for (int i = 0; i < 4; i++) { - const int targetSize = 0; // TODO: target size is hardcoded. - const float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. - const bool doSearch = targetSize > 0 || targetPsnr > 0; - bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; - int numPassLeft = this.entropyPasses; - Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; - int nbMbs = this.Mbw * this.Mbh; - - PassStats stats = new(targetSize, targetPsnr, QMin, QMax, this.quality); - this.Proba.ResetTokenStats(); - - // Fast mode: quick analysis pass over few mbs. Better than nothing. - if (fastProbe) - { - if (this.method == WebpEncodingMethod.Level3) - { - // We need more stats for method 3 to be reliable. - nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; - } - else - { - nbMbs = nbMbs > 200 ? nbMbs >> 2 : 50; - } - } + this.SegmentInfos[i] = new Vp8SegmentInfo(); + } - while (numPassLeft-- > 0) - { - bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); - long sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); - if (sizeP0 == 0) - { - return; - } + this.FilterHeader = new Vp8FilterHeader(); + int predSize = (((4 * this.Mbw) + 1) * ((4 * this.Mbh) + 1)) + this.PredsWidth + 1; + this.PredsWidth = (4 * this.Mbw) + 1; + this.Proba = new Vp8EncProba(); + this.Preds = new byte[predSize + this.PredsWidth + this.Mbw]; - if (this.maxI4HeaderBits > 0 && sizeP0 > (long)Partition0SizeLimit) - { - ++numPassLeft; - this.maxI4HeaderBits >>= 1; // strengthen header bit limitation... - continue; // ...and start over - } + // Initialize with default values, which the reference c implementation uses, + // to be able to compare to the original and spot differences. + this.Preds.AsSpan().Fill(205); + this.Nz.AsSpan().Fill(3452816845); - if (isLastPass) - { - break; - } + this.ResetBoundaryPredictions(); + } - // If no target size: just do several pass without changing 'q' - if (doSearch) - { - // Unreachable due to hardcoding above. -#pragma warning disable CS0162 // Unreachable code detected - stats.ComputeNextQ(); -#pragma warning restore CS0162 // Unreachable code detected - if (MathF.Abs(stats.Dq) <= DqLimit) - { - break; - } - } - } + // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. + private static ReadOnlySpan AverageBytesPerMb => new byte[] { 50, 24, 16, 9, 7, 5, 3, 2 }; - if (!doSearch || !stats.DoSizeSearch) - { - // Need to finalize probas now, since it wasn't done during the search. - this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); - this.Proba.FinalizeTokenProbas(); - } + public int BaseQuant { get; set; } - // Finalize costs. - this.Proba.CalculateLevelCosts(); - } + /// + /// Gets the probabilities. + /// + public Vp8EncProba Proba { get; } - private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) - { - Span y = this.Y.GetSpan(); - Span u = this.U.GetSpan(); - Span v = this.V.GetSpan(); - Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); - long size = 0; - long sizeP0 = 0; - long distortion = 0; - long pixelCount = nbMbs * 384; - - it.Init(); - this.SetLoopParams(stats.Q); - Vp8ModeScore info = new(); - do - { - info.Clear(); - it.Import(y, u, v, yStride, uvStride, width, height, false); - if (this.Decimate(it, ref info, rdOpt)) - { - // Just record the number of skips and act like skipProba is not used. - ++this.Proba.NbSkip; - } + /// + /// Gets the segment features. + /// + public Vp8EncSegmentHeader SegmentHeader { get; private set; } - this.RecordResiduals(it, info); - size += info.R + info.H; - sizeP0 += info.H; - distortion += info.D; + /// + /// Gets the segment infos. + /// + public Vp8SegmentInfo[] SegmentInfos { get; } - it.SaveBoundary(); - } - while (it.Next() && --nbMbs > 0); + /// + /// Gets the macro block info's. + /// + public Vp8MacroBlockInfo[] MbInfo { get; } - sizeP0 += this.SegmentHeader.Size; - if (stats.DoSizeSearch) - { - size += this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); - size += this.Proba.FinalizeTokenProbas(); - size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; - stats.Value = size; - } - else - { - stats.Value = GetPsnr(distortion, pixelCount); - } + /// + /// Gets the filter header. + /// + public Vp8FilterHeader FilterHeader { get; } - return sizeP0; - } + /// + /// Gets or sets the global susceptibility. + /// + public int Alpha { get; set; } - private void SetLoopParams(float q) - { - // Setup segment quantizations and filters. - this.SetSegmentParams(q); + /// + /// Gets the width of the image. + /// + public int Width { get; } - // Compute segment probabilities. - this.SetSegmentProbas(); + /// + /// Gets the height of the image. + /// + public int Height { get; } - this.ResetStats(); - } + /// + /// Gets the stride of the prediction plane (=4*mb_w + 1) + /// + public int PredsWidth { get; } - private unsafe void AdjustFilterStrength() - { - if (this.filterStrength > 0) - { - int maxLevel = 0; - for (int s = 0; s < WebpConstants.NumMbSegments; s++) - { - Vp8SegmentInfo dqm = this.SegmentInfos[s]; + /// + /// Gets the macroblock width. + /// + public int Mbw { get; } - // this '>> 3' accounts for some inverse WHT scaling - int delta = (dqm.MaxEdge * dqm.Y2.Q[1]) >> 3; - int level = FilterStrengthFromDelta(this.FilterHeader.Sharpness, delta); - if (level > dqm.FStrength) - { - dqm.FStrength = level; - } + /// + /// Gets the macroblock height. + /// + public int Mbh { get; } - if (maxLevel < dqm.FStrength) - { - maxLevel = dqm.FStrength; - } - } + public int DqY1Dc { get; private set; } + + public int DqY2Ac { get; private set; } + + public int DqY2Dc { get; private set; } + + public int DqUvAc { get; private set; } + + public int DqUvDc { get; private set; } + + /// + /// Gets the luma component. + /// + private IMemoryOwner Y { get; } + + /// + /// Gets the chroma U component. + /// + private IMemoryOwner U { get; } - this.FilterHeader.FilterLevel = maxLevel; + /// + /// Gets the chroma U component. + /// + private IMemoryOwner V { get; } + + /// + /// Gets the top luma samples. + /// + public byte[] YTop { get; } + + /// + /// Gets the top u/v samples. U and V are packed into 16 bytes (8 U + 8 V). + /// + public byte[] UvTop { get; } + + /// + /// Gets the non-zero pattern. + /// + public uint[] Nz { get; } + + /// + /// Gets the prediction modes: (4*mbw+1) * (4*mbh+1). + /// + public byte[] Preds { get; } + + /// + /// Gets the diffusion error. + /// + public sbyte[] TopDerr { get; } + + /// + /// Gets a rough limit for header bits per MB. + /// + private int MbHeaderLimit { get; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + int width = image.Width; + int height = image.Height; + int pixelCount = width * height; + Span y = this.Y.GetSpan(); + Span u = this.U.GetSpan(); + Span v = this.V.GetSpan(); + bool hasAlpha = YuvConversion.ConvertRgbToYuv(image, this.configuration, this.memoryAllocator, y, u, v); + + int yStride = width; + int uvStride = (yStride + 1) >> 1; + + Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); + int[] alphas = new int[WebpConstants.MaxAlpha + 1]; + this.alpha = this.MacroBlockAnalysis(width, height, it, y, u, v, yStride, uvStride, alphas, out this.uvAlpha); + int totalMb = this.Mbw * this.Mbw; + this.alpha /= totalMb; + this.uvAlpha /= totalMb; + + // Analysis is done, proceed to actual encoding. + this.SegmentHeader = new Vp8EncSegmentHeader(4); + this.AssignSegments(alphas); + this.SetLoopParams(this.quality); + + // Initialize the bitwriter. + int averageBytesPerMacroBlock = AverageBytesPerMb[this.BaseQuant >> 4]; + int expectedSize = this.Mbw * this.Mbh * averageBytesPerMacroBlock; + this.bitWriter = new Vp8BitWriter(expectedSize, this); + + // Extract and encode alpha channel data, if present. + int alphaDataSize = 0; + bool alphaCompressionSucceeded = false; + using AlphaEncoder alphaEncoder = new(); + Span alphaData = Span.Empty; + if (hasAlpha) + { + // TODO: This can potentially run in an separate task. + IMemoryOwner encodedAlphaData = alphaEncoder.EncodeAlpha(image, this.configuration, this.memoryAllocator, this.alphaCompression, out alphaDataSize); + alphaData = encodedAlphaData.GetSpan(); + if (alphaDataSize < pixelCount) + { + // Only use compressed data, if the compressed data is actually smaller then the uncompressed data. + alphaCompressionSucceeded = true; } } - private void ResetBoundaryPredictions() + // Stats-collection loop. + this.StatLoop(width, height, yStride, uvStride); + it.Init(); + Vp8EncIterator.InitFilter(); + Vp8ModeScore info = new(); + Vp8Residual residual = new(); + do { - Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ - Span left = this.Preds.AsSpan(this.PredsWidth - 1); - for (int i = 0; i < 4 * this.Mbw; i++) + bool dontUseSkip = !this.Proba.UseSkipProba; + info.Clear(); + it.Import(y, u, v, yStride, uvStride, width, height, false); + + // Warning! order is important: first call VP8Decimate() and + // *then* decide how to code the skip decision if there's one. + if (!this.Decimate(it, ref info, this.rdOptLevel) || dontUseSkip) { - top[i] = (int)IntraPredictionMode.DcPrediction; + this.CodeResiduals(it, info, residual); } - - for (int i = 0; i < 4 * this.Mbh; i++) + else { - left[i * this.PredsWidth] = (int)IntraPredictionMode.DcPrediction; + it.ResetAfterSkip(); } - int predsW = (4 * this.Mbw) + 1; - int predsH = (4 * this.Mbh) + 1; - int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Clear(); - - this.Nz[0] = 0; // constant + it.SaveBoundary(); } + while (it.Next()); + + // Store filter stats. + this.AdjustFilterStrength(); + + // Write bytes from the bitwriter buffer to the stream. + ImageMetadata metadata = image.Metadata; + metadata.SyncProfiles(); + this.bitWriter.WriteEncodedImageToStream( + stream, + metadata.ExifProfile, + metadata.XmpProfile, + metadata.IccProfile, + (uint)width, + (uint)height, + hasAlpha, + alphaData[..alphaDataSize], + this.alphaCompression && alphaCompressionSucceeded); + } - // Simplified k-Means, to assign Nb segments based on alpha-histogram. - private void AssignSegments(int[] alphas) + /// + public void Dispose() + { + this.Y.Dispose(); + this.U.Dispose(); + this.V.Dispose(); + } + + /// + /// Only collect statistics(number of skips, token usage, ...). + /// This is used for deciding optimal probabilities. It also modifies the + /// quantizer value if some target (size, PSNR) was specified. + /// + /// The image width. + /// The image height. + /// The y-luminance stride. + /// The uv stride. + private void StatLoop(int width, int height, int yStride, int uvStride) + { + const int targetSize = 0; // TODO: target size is hardcoded. + const float targetPsnr = 0.0f; // TODO: targetPsnr is hardcoded. + const bool doSearch = targetSize > 0 || targetPsnr > 0; + bool fastProbe = (this.method == 0 || this.method == WebpEncodingMethod.Level3) && !doSearch; + int numPassLeft = this.entropyPasses; + Vp8RdLevel rdOpt = this.method >= WebpEncodingMethod.Level3 || doSearch ? Vp8RdLevel.RdOptBasic : Vp8RdLevel.RdOptNone; + int nbMbs = this.Mbw * this.Mbh; + + PassStats stats = new(targetSize, targetPsnr, QMin, QMax, this.quality); + this.Proba.ResetTokenStats(); + + // Fast mode: quick analysis pass over few mbs. Better than nothing. + if (fastProbe) { - int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments; - int[] centers = new int[NumMbSegments]; - int weightedAverage = 0; - int[] map = new int[WebpConstants.MaxAlpha + 1]; - int n, k; - int[] accum = new int[NumMbSegments]; - int[] distAccum = new int[NumMbSegments]; - - // Bracket the input. - for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) + if (this.method == WebpEncodingMethod.Level3) + { + // We need more stats for method 3 to be reliable. + nbMbs = nbMbs > 200 ? nbMbs >> 1 : 100; + } + else { + nbMbs = nbMbs > 200 ? nbMbs >> 2 : 50; } + } - int minA = n; - for (n = WebpConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) + while (numPassLeft-- > 0) + { + bool isLastPass = (MathF.Abs(stats.Dq) <= DqLimit) || (numPassLeft == 0) || (this.maxI4HeaderBits == 0); + long sizeP0 = this.OneStatPass(width, height, yStride, uvStride, rdOpt, nbMbs, stats); + if (sizeP0 == 0) { + return; } - int maxA = n; - int rangeA = maxA - minA; + if (this.maxI4HeaderBits > 0 && sizeP0 > (long)Partition0SizeLimit) + { + ++numPassLeft; + this.maxI4HeaderBits >>= 1; // strengthen header bit limitation... + continue; // ...and start over + } - // Spread initial centers evenly. - for (k = 0, n = 1; k < nb; ++k, n += 2) + if (isLastPass) { - centers[k] = minA + (n * rangeA / (2 * nb)); + break; } - for (k = 0; k < MaxItersKMeans; ++k) + // If no target size: just do several pass without changing 'q' + if (doSearch) { - // Reset stats. - for (n = 0; n < nb; ++n) + // Unreachable due to hardcoding above. +#pragma warning disable CS0162 // Unreachable code detected + stats.ComputeNextQ(); +#pragma warning restore CS0162 // Unreachable code detected + if (MathF.Abs(stats.Dq) <= DqLimit) { - accum[n] = 0; - distAccum[n] = 0; + break; } + } + } - // Assign nearest center for each 'a' - n = 0; // track the nearest center for current 'a' - int a; - for (a = minA; a <= maxA; ++a) - { - if (alphas[a] != 0) - { - while (n + 1 < nb && Math.Abs(a - centers[n + 1]) < Math.Abs(a - centers[n])) - { - n++; - } + if (!doSearch || !stats.DoSizeSearch) + { + // Need to finalize probas now, since it wasn't done during the search. + this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); + this.Proba.FinalizeTokenProbas(); + } - map[a] = n; + // Finalize costs. + this.Proba.CalculateLevelCosts(); + } - // Accumulate contribution into best centroid. - distAccum[n] += a * alphas[a]; - accum[n] += alphas[a]; - } - } + private long OneStatPass(int width, int height, int yStride, int uvStride, Vp8RdLevel rdOpt, int nbMbs, PassStats stats) + { + Span y = this.Y.GetSpan(); + Span u = this.U.GetSpan(); + Span v = this.V.GetSpan(); + Vp8EncIterator it = new(this.YTop, this.UvTop, this.Nz, this.MbInfo, this.Preds, this.TopDerr, this.Mbw, this.Mbh); + long size = 0; + long sizeP0 = 0; + long distortion = 0; + long pixelCount = nbMbs * 384; + + it.Init(); + this.SetLoopParams(stats.Q); + Vp8ModeScore info = new(); + do + { + info.Clear(); + it.Import(y, u, v, yStride, uvStride, width, height, false); + if (this.Decimate(it, ref info, rdOpt)) + { + // Just record the number of skips and act like skipProba is not used. + ++this.Proba.NbSkip; + } + + this.RecordResiduals(it, info); + size += info.R + info.H; + sizeP0 += info.H; + distortion += info.D; + + it.SaveBoundary(); + } + while (it.Next() && --nbMbs > 0); + + sizeP0 += this.SegmentHeader.Size; + if (stats.DoSizeSearch) + { + size += this.Proba.FinalizeSkipProba(this.Mbw, this.Mbh); + size += this.Proba.FinalizeTokenProbas(); + size = ((size + sizeP0 + 1024) >> 11) + HeaderSizeEstimate; + stats.Value = size; + } + else + { + stats.Value = GetPsnr(distortion, pixelCount); + } + + return sizeP0; + } + + private void SetLoopParams(float q) + { + // Setup segment quantizations and filters. + this.SetSegmentParams(q); + + // Compute segment probabilities. + this.SetSegmentProbas(); + + this.ResetStats(); + } + + private unsafe void AdjustFilterStrength() + { + if (this.filterStrength > 0) + { + int maxLevel = 0; + for (int s = 0; s < WebpConstants.NumMbSegments; s++) + { + Vp8SegmentInfo dqm = this.SegmentInfos[s]; - // All point are classified. Move the centroids to the center of their respective cloud. - int displaced = 0; - weightedAverage = 0; - int totalWeight = 0; - for (n = 0; n < nb; ++n) + // this '>> 3' accounts for some inverse WHT scaling + int delta = (dqm.MaxEdge * dqm.Y2.Q[1]) >> 3; + int level = FilterStrengthFromDelta(this.FilterHeader.Sharpness, delta); + if (level > dqm.FStrength) { - if (accum[n] != 0) - { - int newCenter = (distAccum[n] + (accum[n] / 2)) / accum[n]; - displaced += Math.Abs(centers[n] - newCenter); - centers[n] = newCenter; - weightedAverage += newCenter * accum[n]; - totalWeight += accum[n]; - } + dqm.FStrength = level; } - weightedAverage = (weightedAverage + (totalWeight / 2)) / totalWeight; - if (displaced < 5) + if (maxLevel < dqm.FStrength) { - break; // no need to keep on looping... + maxLevel = dqm.FStrength; } } - // Map each original value to the closest centroid - for (n = 0; n < this.Mbw * this.Mbh; ++n) - { - Vp8MacroBlockInfo mb = this.MbInfo[n]; - int alpha = mb.Alpha; - mb.Segment = map[alpha]; - mb.Alpha = centers[map[alpha]]; - } + this.FilterHeader.FilterLevel = maxLevel; + } + } - // TODO: add possibility for SmoothSegmentMap - this.SetSegmentAlphas(centers, weightedAverage); + private void ResetBoundaryPredictions() + { + Span top = this.Preds.AsSpan(); // original source top starts at: enc->preds_ - enc->preds_w_ + Span left = this.Preds.AsSpan(this.PredsWidth - 1); + for (int i = 0; i < 4 * this.Mbw; i++) + { + top[i] = (int)IntraPredictionMode.DcPrediction; } - private void SetSegmentAlphas(int[] centers, int mid) + for (int i = 0; i < 4 * this.Mbh; i++) { - int nb = this.SegmentHeader.NumSegments; - Vp8SegmentInfo[] dqm = this.SegmentInfos; - int min = centers[0], max = centers[0]; - int n; + left[i * this.PredsWidth] = (int)IntraPredictionMode.DcPrediction; + } + + int predsW = (4 * this.Mbw) + 1; + int predsH = (4 * this.Mbh) + 1; + int predsSize = predsW * predsH; + this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Clear(); - if (nb > 1) + this.Nz[0] = 0; // constant + } + + // Simplified k-Means, to assign Nb segments based on alpha-histogram. + private void AssignSegments(int[] alphas) + { + int nb = this.SegmentHeader.NumSegments < NumMbSegments ? this.SegmentHeader.NumSegments : NumMbSegments; + int[] centers = new int[NumMbSegments]; + int weightedAverage = 0; + int[] map = new int[WebpConstants.MaxAlpha + 1]; + int n, k; + int[] accum = new int[NumMbSegments]; + int[] distAccum = new int[NumMbSegments]; + + // Bracket the input. + for (n = 0; n <= WebpConstants.MaxAlpha && alphas[n] == 0; ++n) + { + } + + int minA = n; + for (n = WebpConstants.MaxAlpha; n > minA && alphas[n] == 0; --n) + { + } + + int maxA = n; + int rangeA = maxA - minA; + + // Spread initial centers evenly. + for (k = 0, n = 1; k < nb; ++k, n += 2) + { + centers[k] = minA + (n * rangeA / (2 * nb)); + } + + for (k = 0; k < MaxItersKMeans; ++k) + { + // Reset stats. + for (n = 0; n < nb; ++n) + { + accum[n] = 0; + distAccum[n] = 0; + } + + // Assign nearest center for each 'a' + n = 0; // track the nearest center for current 'a' + int a; + for (a = minA; a <= maxA; ++a) { - for (n = 0; n < nb; ++n) + if (alphas[a] != 0) { - if (min > centers[n]) + while (n + 1 < nb && Math.Abs(a - centers[n + 1]) < Math.Abs(a - centers[n])) { - min = centers[n]; + n++; } - if (max < centers[n]) - { - max = centers[n]; - } + map[a] = n; + + // Accumulate contribution into best centroid. + distAccum[n] += a * alphas[a]; + accum[n] += alphas[a]; } } - if (max == min) + // All point are classified. Move the centroids to the center of their respective cloud. + int displaced = 0; + weightedAverage = 0; + int totalWeight = 0; + for (n = 0; n < nb; ++n) { - max = min + 1; + if (accum[n] != 0) + { + int newCenter = (distAccum[n] + (accum[n] / 2)) / accum[n]; + displaced += Math.Abs(centers[n] - newCenter); + centers[n] = newCenter; + weightedAverage += newCenter * accum[n]; + totalWeight += accum[n]; + } } - for (n = 0; n < nb; ++n) + weightedAverage = (weightedAverage + (totalWeight / 2)) / totalWeight; + if (displaced < 5) { - int alpha = 255 * (centers[n] - mid) / (max - min); - int beta = 255 * (centers[n] - min) / (max - min); - dqm[n].Alpha = Numerics.Clamp(alpha, -127, 127); - dqm[n].Beta = Numerics.Clamp(beta, 0, 255); + break; // no need to keep on looping... } } - private void SetSegmentParams(float quality) + // Map each original value to the closest centroid + for (n = 0; n < this.Mbw * this.Mbh; ++n) + { + Vp8MacroBlockInfo mb = this.MbInfo[n]; + int alpha = mb.Alpha; + mb.Segment = map[alpha]; + mb.Alpha = centers[map[alpha]]; + } + + // TODO: add possibility for SmoothSegmentMap + this.SetSegmentAlphas(centers, weightedAverage); + } + + private void SetSegmentAlphas(int[] centers, int mid) + { + int nb = this.SegmentHeader.NumSegments; + Vp8SegmentInfo[] dqm = this.SegmentInfos; + int min = centers[0], max = centers[0]; + int n; + + if (nb > 1) { - int nb = this.SegmentHeader.NumSegments; - Vp8SegmentInfo[] dqm = this.SegmentInfos; - double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d; - double cBase = QualityToCompression(quality / 100.0d); - for (int i = 0; i < nb; i++) + for (n = 0; n < nb; ++n) { - // We modulate the base coefficient to accommodate for the quantization - // susceptibility and allow denser segments to be quantized more. - double expn = 1.0d - (amp * dqm[i].Alpha); - double c = Math.Pow(cBase, expn); - int q = (int)(127.0d * (1.0d - c)); - dqm[i].Quant = Numerics.Clamp(q, 0, 127); - } + if (min > centers[n]) + { + min = centers[n]; + } - // Purely indicative in the bitstream (except for the 1-segment case). - this.BaseQuant = dqm[0].Quant; + if (max < centers[n]) + { + max = centers[n]; + } + } + } - // uvAlpha is normally spread around ~60. The useful range is - // typically ~30 (quite bad) to ~100 (ok to decimate UV more). - // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. - this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); + if (max == min) + { + max = min + 1; + } - // We rescale by the user-defined strength of adaptation. - this.DqUvAc = this.DqUvAc * this.spatialNoiseShaping / 100; + for (n = 0; n < nb; ++n) + { + int alpha = 255 * (centers[n] - mid) / (max - min); + int beta = 255 * (centers[n] - min) / (max - min); + dqm[n].Alpha = Numerics.Clamp(alpha, -127, 127); + dqm[n].Beta = Numerics.Clamp(beta, 0, 255); + } + } - // and make it safe. - this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); + private void SetSegmentParams(float quality) + { + int nb = this.SegmentHeader.NumSegments; + Vp8SegmentInfo[] dqm = this.SegmentInfos; + double amp = WebpConstants.SnsToDq * this.spatialNoiseShaping / 100.0d / 128.0d; + double cBase = QualityToCompression(quality / 100.0d); + for (int i = 0; i < nb; i++) + { + // We modulate the base coefficient to accommodate for the quantization + // susceptibility and allow denser segments to be quantized more. + double expn = 1.0d - (amp * dqm[i].Alpha); + double c = Math.Pow(cBase, expn); + int q = (int)(127.0d * (1.0d - c)); + dqm[i].Quant = Numerics.Clamp(q, 0, 127); + } - // We also boost the dc-uv-quant a little, based on sns-strength, since - // U/V channels are quite more reactive to high quants (flat DC-blocks tend to appear, and are unpleasant). - this.DqUvDc = -4 * this.spatialNoiseShaping / 100; - this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed. + // Purely indicative in the bitstream (except for the 1-segment case). + this.BaseQuant = dqm[0].Quant; - this.DqY1Dc = 0; - this.DqY2Dc = 0; - this.DqY2Ac = 0; + // uvAlpha is normally spread around ~60. The useful range is + // typically ~30 (quite bad) to ~100 (ok to decimate UV more). + // We map it to the safe maximal range of MAX/MIN_DQ_UV for dq_uv. + this.DqUvAc = (this.uvAlpha - WebpConstants.QuantEncMidAlpha) * (WebpConstants.QuantEncMaxDqUv - WebpConstants.QuantEncMinDqUv) / (WebpConstants.QuantEncMaxAlpha - WebpConstants.QuantEncMinAlpha); - // Initialize segments' filtering. - this.SetupFilterStrength(); + // We rescale by the user-defined strength of adaptation. + this.DqUvAc = this.DqUvAc * this.spatialNoiseShaping / 100; - this.SetupMatrices(dqm); - } + // and make it safe. + this.DqUvAc = Numerics.Clamp(this.DqUvAc, WebpConstants.QuantEncMinDqUv, WebpConstants.QuantEncMaxDqUv); - private void SetupFilterStrength() - { - const int filterSharpness = 0; // TODO: filterSharpness is hardcoded - const int filterType = 1; // TODO: filterType is hardcoded + // We also boost the dc-uv-quant a little, based on sns-strength, since + // U/V channels are quite more reactive to high quants (flat DC-blocks tend to appear, and are unpleasant). + this.DqUvDc = -4 * this.spatialNoiseShaping / 100; + this.DqUvDc = Numerics.Clamp(this.DqUvDc, -15, 15); // 4bit-signed max allowed. - // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. - int level0 = 5 * this.filterStrength; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) - { - Vp8SegmentInfo m = this.SegmentInfos[i]; + this.DqY1Dc = 0; + this.DqY2Dc = 0; + this.DqY2Ac = 0; - // We focus on the quantization of AC coeffs. - int qstep = WebpLookupTables.AcTable[Numerics.Clamp(m.Quant, 0, 127)] >> 2; - int baseStrength = FilterStrengthFromDelta(this.FilterHeader.Sharpness, qstep); + // Initialize segments' filtering. + this.SetupFilterStrength(); - // Segments with lower complexity ('beta') will be less filtered. - int f = baseStrength * level0 / (256 + m.Beta); - if (f < WebpConstants.FilterStrengthCutoff) - { - m.FStrength = 0; - } - else if (f > 63) - { - m.FStrength = 63; - } - else - { - m.FStrength = f; - } - } + this.SetupMatrices(dqm); + } - // We record the initial strength (mainly for the case of 1-segment only). - this.FilterHeader.FilterLevel = this.SegmentInfos[0].FStrength; - this.FilterHeader.Simple = filterType == 0; - this.FilterHeader.Sharpness = filterSharpness; - } + private void SetupFilterStrength() + { + const int filterSharpness = 0; // TODO: filterSharpness is hardcoded + const int filterType = 1; // TODO: filterType is hardcoded - private void SetSegmentProbas() + // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. + int level0 = 5 * this.filterStrength; + for (int i = 0; i < WebpConstants.NumMbSegments; i++) { - int[] p = new int[NumMbSegments]; - int n; + Vp8SegmentInfo m = this.SegmentInfos[i]; + + // We focus on the quantization of AC coeffs. + int qstep = WebpLookupTables.AcTable[Numerics.Clamp(m.Quant, 0, 127)] >> 2; + int baseStrength = FilterStrengthFromDelta(this.FilterHeader.Sharpness, qstep); - for (n = 0; n < this.Mbw * this.Mbh; ++n) + // Segments with lower complexity ('beta') will be less filtered. + int f = baseStrength * level0 / (256 + m.Beta); + if (f < WebpConstants.FilterStrengthCutoff) { - Vp8MacroBlockInfo mb = this.MbInfo[n]; - ++p[mb.Segment]; + m.FStrength = 0; } - - if (this.SegmentHeader.NumSegments > 1) + else if (f > 63) { - byte[] probas = this.Proba.Segments; - probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]); - probas[1] = (byte)GetProba(p[0], p[1]); - probas[2] = (byte)GetProba(p[2], p[3]); - - this.SegmentHeader.UpdateMap = probas[0] != 255 || probas[1] != 255 || probas[2] != 255; - if (!this.SegmentHeader.UpdateMap) - { - this.ResetSegments(); - } - - this.SegmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + - (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) + - (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) + - (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2]))); + m.FStrength = 63; } else { - this.SegmentHeader.UpdateMap = false; - this.SegmentHeader.Size = 0; + m.FStrength = f; } } - private void ResetSegments() + // We record the initial strength (mainly for the case of 1-segment only). + this.FilterHeader.FilterLevel = this.SegmentInfos[0].FStrength; + this.FilterHeader.Simple = filterType == 0; + this.FilterHeader.Sharpness = filterSharpness; + } + + private void SetSegmentProbas() + { + int[] p = new int[NumMbSegments]; + int n; + + for (n = 0; n < this.Mbw * this.Mbh; ++n) + { + Vp8MacroBlockInfo mb = this.MbInfo[n]; + ++p[mb.Segment]; + } + + if (this.SegmentHeader.NumSegments > 1) { - int n; - for (n = 0; n < this.Mbw * this.Mbh; ++n) + byte[] probas = this.Proba.Segments; + probas[0] = (byte)GetProba(p[0] + p[1], p[2] + p[3]); + probas[1] = (byte)GetProba(p[0], p[1]); + probas[2] = (byte)GetProba(p[2], p[3]); + + this.SegmentHeader.UpdateMap = probas[0] != 255 || probas[1] != 255 || probas[2] != 255; + if (!this.SegmentHeader.UpdateMap) { - this.MbInfo[n].Segment = 0; + this.ResetSegments(); } + + this.SegmentHeader.Size = (p[0] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(0, probas[1]))) + + (p[1] * (LossyUtils.Vp8BitCost(0, probas[0]) + LossyUtils.Vp8BitCost(1, probas[1]))) + + (p[2] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(0, probas[2]))) + + (p[3] * (LossyUtils.Vp8BitCost(1, probas[0]) + LossyUtils.Vp8BitCost(1, probas[2]))); } + else + { + this.SegmentHeader.UpdateMap = false; + this.SegmentHeader.Size = 0; + } + } - private void ResetStats() + private void ResetSegments() + { + int n; + for (n = 0; n < this.Mbw * this.Mbh; ++n) { - Vp8EncProba proba = this.Proba; - proba.CalculateLevelCosts(); - proba.NbSkip = 0; + this.MbInfo[n].Segment = 0; } + } - private unsafe void SetupMatrices(Vp8SegmentInfo[] dqm) + private void ResetStats() + { + Vp8EncProba proba = this.Proba; + proba.CalculateLevelCosts(); + proba.NbSkip = 0; + } + + private unsafe void SetupMatrices(Vp8SegmentInfo[] dqm) + { + int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0; + for (int i = 0; i < dqm.Length; i++) { - int tlambdaScale = this.method >= WebpEncodingMethod.Default ? this.spatialNoiseShaping : 0; - for (int i = 0; i < dqm.Length; i++) - { - Vp8SegmentInfo m = dqm[i]; - int q = m.Quant; + Vp8SegmentInfo m = dqm[i]; + int q = m.Quant; - m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)]; - m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; + m.Y1.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY1Dc, 0, 127)]; + m.Y1.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q, 0, 127)]; - m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY2Dc, 0, 127)] * 2); - m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q + this.DqY2Ac, 0, 127)]; + m.Y2.Q[0] = (ushort)(WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqY2Dc, 0, 127)] * 2); + m.Y2.Q[1] = WebpLookupTables.AcTable2[Numerics.Clamp(q + this.DqY2Ac, 0, 127)]; - m.Uv.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqUvDc, 0, 117)]; - m.Uv.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q + this.DqUvAc, 0, 127)]; + m.Uv.Q[0] = WebpLookupTables.DcTable[Numerics.Clamp(q + this.DqUvDc, 0, 117)]; + m.Uv.Q[1] = WebpLookupTables.AcTable[Numerics.Clamp(q + this.DqUvAc, 0, 127)]; - int qi4 = m.Y1.Expand(0); - int qi16 = m.Y2.Expand(1); - int quv = m.Uv.Expand(2); + int qi4 = m.Y1.Expand(0); + int qi16 = m.Y2.Expand(1); + int quv = m.Uv.Expand(2); - m.LambdaI16 = 3 * qi16 * qi16; - m.LambdaI4 = (3 * qi4 * qi4) >> 7; - m.LambdaUv = (3 * quv * quv) >> 6; - m.LambdaMode = (1 * qi4 * qi4) >> 7; - m.TLambda = (tlambdaScale * qi4) >> 5; + m.LambdaI16 = 3 * qi16 * qi16; + m.LambdaI4 = (3 * qi4 * qi4) >> 7; + m.LambdaUv = (3 * quv * quv) >> 6; + m.LambdaMode = (1 * qi4 * qi4) >> 7; + m.TLambda = (tlambdaScale * qi4) >> 5; - // none of these constants should be < 1. - m.LambdaI16 = m.LambdaI16 < 1 ? 1 : m.LambdaI16; - m.LambdaI4 = m.LambdaI4 < 1 ? 1 : m.LambdaI4; - m.LambdaUv = m.LambdaUv < 1 ? 1 : m.LambdaUv; - m.LambdaMode = m.LambdaMode < 1 ? 1 : m.LambdaMode; - m.TLambda = m.TLambda < 1 ? 1 : m.TLambda; + // none of these constants should be < 1. + m.LambdaI16 = m.LambdaI16 < 1 ? 1 : m.LambdaI16; + m.LambdaI4 = m.LambdaI4 < 1 ? 1 : m.LambdaI4; + m.LambdaUv = m.LambdaUv < 1 ? 1 : m.LambdaUv; + m.LambdaMode = m.LambdaMode < 1 ? 1 : m.LambdaMode; + m.TLambda = m.TLambda < 1 ? 1 : m.TLambda; - m.MinDisto = 20 * m.Y1.Q[0]; - m.MaxEdge = 0; + m.MinDisto = 20 * m.Y1.Q[0]; + m.MaxEdge = 0; - m.I4Penalty = 1000 * qi4 * qi4; - } + m.I4Penalty = 1000 * qi4 * qi4; } + } - private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha) + private int MacroBlockAnalysis(int width, int height, Vp8EncIterator it, Span y, Span u, Span v, int yStride, int uvStride, int[] alphas, out int uvAlpha) + { + int alpha = 0; + uvAlpha = 0; + if (!it.IsDone()) { - int alpha = 0; - uvAlpha = 0; - if (!it.IsDone()) + do { - do - { - it.Import(y, u, v, yStride, uvStride, width, height, true); - int bestAlpha = this.MbAnalyze(it, alphas, out int bestUvAlpha); + it.Import(y, u, v, yStride, uvStride, width, height, true); + int bestAlpha = this.MbAnalyze(it, alphas, out int bestUvAlpha); - // Accumulate for later complexity analysis. - alpha += bestAlpha; - uvAlpha += bestUvAlpha; - } - while (it.Next()); + // Accumulate for later complexity analysis. + alpha += bestAlpha; + uvAlpha += bestUvAlpha; } - - return alpha; + while (it.Next()); } - private int MbAnalyze(Vp8EncIterator it, int[] alphas, out int bestUvAlpha) - { - it.SetIntra16Mode(0); // default: Intra16, DC_PRED - it.SetSkip(false); // not skipped. - it.SetSegment(0); // default segment, spec-wise. + return alpha; + } - int bestAlpha; - if (this.method <= WebpEncodingMethod.Level1) - { - bestAlpha = it.FastMbAnalyze(this.quality); - } - else + private int MbAnalyze(Vp8EncIterator it, int[] alphas, out int bestUvAlpha) + { + it.SetIntra16Mode(0); // default: Intra16, DC_PRED + it.SetSkip(false); // not skipped. + it.SetSegment(0); // default segment, spec-wise. + + int bestAlpha; + if (this.method <= WebpEncodingMethod.Level1) + { + bestAlpha = it.FastMbAnalyze(this.quality); + } + else + { + bestAlpha = it.MbAnalyzeBestIntra16Mode(); + if (this.method >= WebpEncodingMethod.Level5) { - bestAlpha = it.MbAnalyzeBestIntra16Mode(); - if (this.method >= WebpEncodingMethod.Level5) - { - // We go and make a fast decision for intra4/intra16. - // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. - bestAlpha = it.MbAnalyzeBestIntra4Mode(bestAlpha); - } + // We go and make a fast decision for intra4/intra16. + // It's usually not a good and definitive pick, but helps seeding the stats about level bit-cost. + bestAlpha = it.MbAnalyzeBestIntra4Mode(bestAlpha); } + } - bestUvAlpha = it.MbAnalyzeBestUvMode(); - - // Final susceptibility mix. - bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2; - bestAlpha = FinalAlphaValue(bestAlpha); - alphas[bestAlpha]++; - it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. + bestUvAlpha = it.MbAnalyzeBestUvMode(); - return bestAlpha; // Mixed susceptibility (not just luma). - } + // Final susceptibility mix. + bestAlpha = ((3 * bestAlpha) + bestUvAlpha + 2) >> 2; + bestAlpha = FinalAlphaValue(bestAlpha); + alphas[bestAlpha]++; + it.CurrentMacroBlockInfo.Alpha = bestAlpha; // For later remapping. - private bool Decimate(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8RdLevel rdOpt) - { - rd.InitScore(); + return bestAlpha; // Mixed susceptibility (not just luma). + } - // We can perform predictions for Luma16x16 and Chroma8x8 already. - // Luma4x4 predictions needs to be done as-we-go. - it.MakeLuma16Preds(); - it.MakeChroma8Preds(); + private bool Decimate(Vp8EncIterator it, ref Vp8ModeScore rd, Vp8RdLevel rdOpt) + { + rd.InitScore(); - if (rdOpt > Vp8RdLevel.RdOptNone) - { - QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); - if (this.method >= WebpEncodingMethod.Level2) - { - QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); - } + // We can perform predictions for Luma16x16 and Chroma8x8 already. + // Luma4x4 predictions needs to be done as-we-go. + it.MakeLuma16Preds(); + it.MakeChroma8Preds(); - QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); - } - else + if (rdOpt > Vp8RdLevel.RdOptNone) + { + QuantEnc.PickBestIntra16(it, ref rd, this.SegmentInfos, this.Proba); + if (this.method >= WebpEncodingMethod.Level2) { - // At this point we have heuristically decided intra16 / intra4. - // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). - // For method <= 1, we don't re-examine the decision but just go ahead with - // quantization/reconstruction. - QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= WebpEncodingMethod.Level2, this.method >= WebpEncodingMethod.Level1, this.MbHeaderLimit); + QuantEnc.PickBestIntra4(it, ref rd, this.SegmentInfos, this.Proba, this.maxI4HeaderBits); } - bool isSkipped = rd.Nz == 0; - it.SetSkip(isSkipped); - - return isSkipped; + QuantEnc.PickBestUv(it, ref rd, this.SegmentInfos, this.Proba); } - - private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd, Vp8Residual residual) + else { - int x, y, ch; - bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - int segment = it.CurrentMacroBlockInfo.Segment; + // At this point we have heuristically decided intra16 / intra4. + // For method >= 2, pick the best intra4/intra16 based on SSE (~tad slower). + // For method <= 1, we don't re-examine the decision but just go ahead with + // quantization/reconstruction. + QuantEnc.RefineUsingDistortion(it, this.SegmentInfos, rd, this.method >= WebpEncodingMethod.Level2, this.method >= WebpEncodingMethod.Level1, this.MbHeaderLimit); + } - it.NzToBytes(); + bool isSkipped = rd.Nz == 0; + it.SetSkip(isSkipped); - int pos1 = this.bitWriter.NumBytes(); - if (i16) - { - residual.Init(0, 1, this.Proba); - residual.SetCoeffs(rd.YDcLevels); - int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); - it.TopNz[8] = it.LeftNz[8] = res; - residual.Init(1, 0, this.Proba); - } - else + return isSkipped; + } + + private void CodeResiduals(Vp8EncIterator it, Vp8ModeScore rd, Vp8Residual residual) + { + int x, y, ch; + bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; + int segment = it.CurrentMacroBlockInfo.Segment; + + it.NzToBytes(); + + int pos1 = this.bitWriter.NumBytes(); + if (i16) + { + residual.Init(0, 1, this.Proba); + residual.SetCoeffs(rd.YDcLevels); + int res = this.bitWriter.PutCoeffs(it.TopNz[8] + it.LeftNz[8], residual); + it.TopNz[8] = it.LeftNz[8] = res; + residual.Init(1, 0, this.Proba); + } + else + { + residual.Init(0, 3, this.Proba); + } + + // luma-AC + for (y = 0; y < 4; y++) + { + for (x = 0; x < 4; x++) { - residual.Init(0, 3, this.Proba); + int ctx = it.TopNz[x] + it.LeftNz[y]; + Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); + residual.SetCoeffs(coeffs); + int res = this.bitWriter.PutCoeffs(ctx, residual); + it.TopNz[x] = it.LeftNz[y] = res; } + } + + int pos2 = this.bitWriter.NumBytes(); - // luma-AC - for (y = 0; y < 4; y++) + // U/V + residual.Init(0, 2, this.Proba); + for (ch = 0; ch <= 2; ch += 2) + { + for (y = 0; y < 2; y++) { - for (x = 0; x < 4; x++) + for (x = 0; x < 2; x++) { - int ctx = it.TopNz[x] + it.LeftNz[y]; - Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); - residual.SetCoeffs(coeffs); + int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; + residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); int res = this.bitWriter.PutCoeffs(ctx, residual); - it.TopNz[x] = it.LeftNz[y] = res; + it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; } } + } - int pos2 = this.bitWriter.NumBytes(); + int pos3 = this.bitWriter.NumBytes(); + it.LumaBits = pos2 - pos1; + it.UvBits = pos3 - pos2; + it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits; + it.BitCount[segment, 2] += it.UvBits; + it.BytesToNz(); + } - // U/V - residual.Init(0, 2, this.Proba); - for (ch = 0; ch <= 2; ch += 2) - { - for (y = 0; y < 2; y++) - { - for (x = 0; x < 2; x++) - { - int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - int res = this.bitWriter.PutCoeffs(ctx, residual); - it.TopNz[4 + ch + x] = it.LeftNz[4 + ch + y] = res; - } - } - } + /// + /// Same as CodeResiduals, but doesn't actually write anything. + /// Instead, it just records the event distribution. + /// + /// The iterator. + /// The score accumulator. + private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd) + { + int x, y, ch; + Vp8Residual residual = new(); + bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - int pos3 = this.bitWriter.NumBytes(); - it.LumaBits = pos2 - pos1; - it.UvBits = pos3 - pos2; - it.BitCount[segment, i16 ? 1 : 0] += it.LumaBits; - it.BitCount[segment, 2] += it.UvBits; - it.BytesToNz(); - } + it.NzToBytes(); - /// - /// Same as CodeResiduals, but doesn't actually write anything. - /// Instead, it just records the event distribution. - /// - /// The iterator. - /// The score accumulator. - private void RecordResiduals(Vp8EncIterator it, Vp8ModeScore rd) + if (i16) { - int x, y, ch; - Vp8Residual residual = new(); - bool i16 = it.CurrentMacroBlockInfo.MacroBlockType == Vp8MacroBlockType.I16X16; - - it.NzToBytes(); + // i16x16 + residual.Init(0, 1, this.Proba); + residual.SetCoeffs(rd.YDcLevels); + int res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); + it.TopNz[8] = res; + it.LeftNz[8] = res; + residual.Init(1, 0, this.Proba); + } + else + { + residual.Init(0, 3, this.Proba); + } - if (i16) - { - // i16x16 - residual.Init(0, 1, this.Proba); - residual.SetCoeffs(rd.YDcLevels); - int res = residual.RecordCoeffs(it.TopNz[8] + it.LeftNz[8]); - it.TopNz[8] = res; - it.LeftNz[8] = res; - residual.Init(1, 0, this.Proba); - } - else + // luma-AC + for (y = 0; y < 4; y++) + { + for (x = 0; x < 4; x++) { - residual.Init(0, 3, this.Proba); + int ctx = it.TopNz[x] + it.LeftNz[y]; + Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); + residual.SetCoeffs(coeffs); + int res = residual.RecordCoeffs(ctx); + it.TopNz[x] = res; + it.LeftNz[y] = res; } + } - // luma-AC - for (y = 0; y < 4; y++) + // U/V + residual.Init(0, 2, this.Proba); + for (ch = 0; ch <= 2; ch += 2) + { + for (y = 0; y < 2; y++) { - for (x = 0; x < 4; x++) + for (x = 0; x < 2; x++) { - int ctx = it.TopNz[x] + it.LeftNz[y]; - Span coeffs = rd.YAcLevels.AsSpan(16 * (x + (y * 4)), 16); - residual.SetCoeffs(coeffs); + int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; + residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); int res = residual.RecordCoeffs(ctx); - it.TopNz[x] = res; - it.LeftNz[y] = res; - } - } - - // U/V - residual.Init(0, 2, this.Proba); - for (ch = 0; ch <= 2; ch += 2) - { - for (y = 0; y < 2; y++) - { - for (x = 0; x < 2; x++) - { - int ctx = it.TopNz[4 + ch + x] + it.LeftNz[4 + ch + y]; - residual.SetCoeffs(rd.UvLevels.AsSpan(16 * ((ch * 2) + x + (y * 2)), 16)); - int res = residual.RecordCoeffs(ctx); - it.TopNz[4 + ch + x] = res; - it.LeftNz[4 + ch + y] = res; - } + it.TopNz[4 + ch + x] = res; + it.LeftNz[4 + ch + y] = res; } } - - it.BytesToNz(); } - [MethodImpl(InliningOptions.ShortMethod)] - private static int FinalAlphaValue(int alpha) - { - alpha = WebpConstants.MaxAlpha - alpha; - return Numerics.Clamp(alpha, 0, WebpConstants.MaxAlpha); - } + it.BytesToNz(); + } - /// - /// We want to emulate jpeg-like behaviour where the expected "good" quality - /// is around q=75. Internally, our "good" middle is around c=50. So we - /// map accordingly using linear piece-wise function - /// - /// The compression level. - [MethodImpl(InliningOptions.ShortMethod)] - private static double QualityToCompression(double c) - { - double linearC = c < 0.75 ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; - - // The file size roughly scales as pow(quantizer, 3.). Actually, the - // exponent is somewhere between 2.8 and 3.2, but we're mostly interested - // in the mid-quant range. So we scale the compressibility inversely to - // this power-law: quant ~= compression ^ 1/3. This law holds well for - // low quant. Finer modeling for high-quant would make use of AcTable[] - // more explicitly. - return (double)Math.Pow(linearC, 1 / 3.0d); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int FinalAlphaValue(int alpha) + { + alpha = WebpConstants.MaxAlpha - alpha; + return Numerics.Clamp(alpha, 0, WebpConstants.MaxAlpha); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int FilterStrengthFromDelta(int sharpness, int delta) - { - int pos = delta < WebpConstants.MaxDelzaSize ? delta : WebpConstants.MaxDelzaSize - 1; - return WebpLookupTables.LevelsFromDelta[sharpness, pos]; - } + /// + /// We want to emulate jpeg-like behaviour where the expected "good" quality + /// is around q=75. Internally, our "good" middle is around c=50. So we + /// map accordingly using linear piece-wise function + /// + /// The compression level. + [MethodImpl(InliningOptions.ShortMethod)] + private static double QualityToCompression(double c) + { + double linearC = c < 0.75 ? c * (2.0d / 3.0d) : (2.0d * c) - 1.0d; + + // The file size roughly scales as pow(quantizer, 3.). Actually, the + // exponent is somewhere between 2.8 and 3.2, but we're mostly interested + // in the mid-quant range. So we scale the compressibility inversely to + // this power-law: quant ~= compression ^ 1/3. This law holds well for + // low quant. Finer modeling for high-quant would make use of AcTable[] + // more explicitly. + return (double)Math.Pow(linearC, 1 / 3.0d); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static double GetPsnr(long mse, long size) => mse > 0 && size > 0 ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; + [MethodImpl(InliningOptions.ShortMethod)] + private static int FilterStrengthFromDelta(int sharpness, int delta) + { + int pos = delta < WebpConstants.MaxDelzaSize ? delta : WebpConstants.MaxDelzaSize - 1; + return WebpLookupTables.LevelsFromDelta[sharpness, pos]; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetProba(int a, int b) - { - int total = a + b; - return total == 0 ? 255 // that's the default probability. - : ((255 * a) + (total / 2)) / total; // rounded proba - } + [MethodImpl(InliningOptions.ShortMethod)] + private static double GetPsnr(long mse, long size) => mse > 0 && size > 0 ? 10.0f * Math.Log10(255.0f * 255.0f * size / mse) : 99; + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetProba(int a, int b) + { + int total = a + b; + return total == 0 ? 255 // that's the default probability. + : ((255 * a) + (total / 2)) / total; // rounded proba } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 32d96cba9e..cc263657f1 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -1,1106 +1,1104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy -{ - /// - /// Methods for encoding a VP8 frame. - /// - internal static unsafe class Vp8Encoding - { - private const int KC1 = 20091 + (1 << 16); +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; - private const int KC2 = 35468; +/// +/// Methods for encoding a VP8 frame. +/// +internal static unsafe class Vp8Encoding +{ + private const int KC1 = 20091 + (1 << 16); - private static readonly byte[] Clip1 = GetClip1(); // clips [-255,510] to [0,255] + private const int KC2 = 35468; - private const int I16DC16 = 0 * 16 * WebpConstants.Bps; + private static readonly byte[] Clip1 = GetClip1(); // clips [-255,510] to [0,255] - private const int I16TM16 = I16DC16 + 16; + private const int I16DC16 = 0 * 16 * WebpConstants.Bps; - private const int I16VE16 = 1 * 16 * WebpConstants.Bps; + private const int I16TM16 = I16DC16 + 16; - private const int I16HE16 = I16VE16 + 16; + private const int I16VE16 = 1 * 16 * WebpConstants.Bps; - private const int C8DC8 = 2 * 16 * WebpConstants.Bps; + private const int I16HE16 = I16VE16 + 16; - private const int C8TM8 = C8DC8 + (1 * 16); + private const int C8DC8 = 2 * 16 * WebpConstants.Bps; - private const int C8VE8 = (2 * 16 * WebpConstants.Bps) + (8 * WebpConstants.Bps); + private const int C8TM8 = C8DC8 + (1 * 16); - private const int C8HE8 = C8VE8 + (1 * 16); + private const int C8VE8 = (2 * 16 * WebpConstants.Bps) + (8 * WebpConstants.Bps); - public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; + private const int C8HE8 = C8VE8 + (1 * 16); - public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; + public static readonly int[] Vp8I16ModeOffsets = { I16DC16, I16TM16, I16VE16, I16HE16 }; - private const int I4DC4 = (3 * 16 * WebpConstants.Bps) + 0; + public static readonly int[] Vp8UvModeOffsets = { C8DC8, C8TM8, C8VE8, C8HE8 }; - private const int I4TM4 = I4DC4 + 4; + private const int I4DC4 = (3 * 16 * WebpConstants.Bps) + 0; - private const int I4VE4 = I4DC4 + 8; + private const int I4TM4 = I4DC4 + 4; - private const int I4HE4 = I4DC4 + 12; + private const int I4VE4 = I4DC4 + 8; - private const int I4RD4 = I4DC4 + 16; + private const int I4HE4 = I4DC4 + 12; - private const int I4VR4 = I4DC4 + 20; + private const int I4RD4 = I4DC4 + 16; - private const int I4LD4 = I4DC4 + 24; + private const int I4VR4 = I4DC4 + 20; - private const int I4VL4 = I4DC4 + 28; + private const int I4LD4 = I4DC4 + 24; - private const int I4HD4 = (3 * 16 * WebpConstants.Bps) + (4 * WebpConstants.Bps); + private const int I4VL4 = I4DC4 + 28; - private const int I4HU4 = I4HD4 + 4; + private const int I4HD4 = (3 * 16 * WebpConstants.Bps) + (4 * WebpConstants.Bps); - public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; + private const int I4HU4 = I4HD4 + 4; - private static byte[] GetClip1() - { - byte[] clip1 = new byte[255 + 510 + 1]; - - for (int i = -255; i <= 255 + 255; i++) - { - clip1[255 + i] = Clip8b(i); - } + public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; - return clip1; - } + private static byte[] GetClip1() + { + byte[] clip1 = new byte[255 + 510 + 1]; - // Transforms (Paragraph 14.4) - // Does two inverse transforms. - public static void ITransformTwo(Span reference, Span input, Span dst, Span scratch) + for (int i = -255; i <= 255 + 255; i++) { - if (Sse2.IsSupported) - { - // This implementation makes use of 16-bit fixed point versions of two - // multiply constants: - // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 - // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 - // - // To be able to use signed 16-bit integers, we use the following trick to - // have constants within range: - // - Associated constants are obtained by subtracting the 16-bit fixed point - // version of one: - // k = K - (1 << 16) => K = k + (1 << 16) - // K1 = 85267 => k1 = 20091 - // K2 = 35468 => k2 = -30068 - // - The multiplication of a variable by a constant become the sum of the - // variable and the multiplication of that variable by the associated - // constant: - // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x - - // Load and concatenate the transform coefficients (we'll do two inverse - // transforms in parallel). In the case of only one inverse transform, the - // second half of the vectors will just contain random value we'll never - // use nor store. - ref short inputRef = ref MemoryMarshal.GetReference(input); - Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - Vector128 inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); - Vector128 inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); - Vector128 inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); - Vector128 inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 28)), 0); - - in0 = Sse2.UnpackLow(in0, inb0); - in1 = Sse2.UnpackLow(in1, inb1); - in2 = Sse2.UnpackLow(in2, inb2); - in3 = Sse2.UnpackLow(in3, inb3); - - // a00 a10 a20 a30 b00 b10 b20 b30 - // a01 a11 a21 a31 b01 b11 b21 b31 - // a02 a12 a22 a32 b02 b12 b22 b32 - // a03 a13 a23 a33 b03 b13 b23 b33 - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'ref' and store. - // Load the reference(s). - Vector128 ref0 = Vector128.Zero; - Vector128 ref1 = Vector128.Zero; - Vector128 ref2 = Vector128.Zero; - Vector128 ref3 = Vector128.Zero; - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load eight bytes/pixels per line. - ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); - ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); - ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); - ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); - - // Convert to 16b. - ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); - ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); - ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); - ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); - - // Add the inverse transform(s). - Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); - Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); - Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); - Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); - - // Unsigned saturate to 8b. - ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); - ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); - ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); - ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - - // Store eight bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - Unsafe.As>(ref outputRef) = ref0.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); - } - else - { - ITransformOne(reference, input, dst, scratch); - ITransformOne(reference[4..], input[16..], dst[4..], scratch); - } + clip1[255 + i] = Clip8b(i); } - public static void ITransformOne(Span reference, Span input, Span dst, Span scratch) - { - if (Sse2.IsSupported) - { - // Load and concatenate the transform coefficients (we'll do two inverse - // transforms in parallel). In the case of only one inverse transform, the - // second half of the vectors will just contain random value we'll never - // use nor store. - ref short inputRef = ref MemoryMarshal.GetReference(input); - Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); - Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); - Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); - Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); - - // a00 a10 a20 a30 x x x x - // a01 a11 a21 a31 x x x x - // a02 a12 a22 a32 x x x x - // a03 a13 a23 a33 x x x x - - // Vertical pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); - - // Horizontal pass and subsequent transpose. - // First pass, c and d calculations are longer because of the "trick" multiplications. - InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); - - // Transpose the two 4x4. - LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); - - // Add inverse transform to 'ref' and store. - // Load the reference(s). - Vector128 ref0 = Vector128.Zero; - Vector128 ref1 = Vector128.Zero; - Vector128 ref2 = Vector128.Zero; - Vector128 ref3 = Vector128.Zero; - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load four bytes/pixels per line. - ref0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref referenceRef)).AsByte(); - ref1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); - ref2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); - ref3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); - - // Convert to 16b. - ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); - ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); - ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); - ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); - - // Add the inverse transform(s). - Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); - Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); - Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); - Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); - - // Unsigned saturate to 8b. - ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); - ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); - ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); - ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - - // Unsigned saturate to 8b. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - - // Store four bytes/pixels per line. - int output0 = Sse2.ConvertToInt32(ref0.AsInt32()); - int output1 = Sse2.ConvertToInt32(ref1.AsInt32()); - int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); - int output3 = Sse2.ConvertToInt32(ref3.AsInt32()); - - Unsafe.As(ref outputRef) = output0; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; - Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; - } - else - { - int i; - Span tmp = scratch[..16]; - for (i = 0; i < 4; i++) - { - // vertical pass. - int a = input[0] + input[8]; - int b = input[0] - input[8]; - int c = Mul(input[4], KC2) - Mul(input[12], KC1); - int d = Mul(input[4], KC1) + Mul(input[12], KC2); - tmp[0] = a + d; - tmp[1] = b + c; - tmp[2] = b - c; - tmp[3] = a - d; - tmp = tmp[4..]; - input = input[1..]; - } + return clip1; + } - tmp = scratch; - for (i = 0; i < 4; i++) - { - // horizontal pass. - int dc = tmp[0] + 4; - int a = dc + tmp[8]; - int b = dc - tmp[8]; - int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); - int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, a + d); - Store(dst, reference, 1, i, b + c); - Store(dst, reference, 2, i, b - c); - Store(dst, reference, 3, i, a - d); - tmp = tmp[1..]; - } - } + // Transforms (Paragraph 14.4) + // Does two inverse transforms. + public static void ITransformTwo(Span reference, Span input, Span dst, Span scratch) + { + if (Sse2.IsSupported) + { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + + // Load and concatenate the transform coefficients (we'll do two inverse + // transforms in parallel). In the case of only one inverse transform, the + // second half of the vectors will just contain random value we'll never + // use nor store. + ref short inputRef = ref MemoryMarshal.GetReference(input); + Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); + Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); + Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); + Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + Vector128 inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); + Vector128 inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); + Vector128 inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); + Vector128 inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 28)), 0); + + in0 = Sse2.UnpackLow(in0, inb0); + in1 = Sse2.UnpackLow(in1, inb1); + in2 = Sse2.UnpackLow(in2, inb2); + in3 = Sse2.UnpackLow(in3, inb3); + + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); + + // Transpose the two 4x4. + LossyUtils.Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); + + // Transpose the two 4x4. + LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'ref' and store. + // Load the reference(s). + Vector128 ref0 = Vector128.Zero; + Vector128 ref1 = Vector128.Zero; + Vector128 ref2 = Vector128.Zero; + Vector128 ref3 = Vector128.Zero; + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load eight bytes/pixels per line. + ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); + ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); + ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); + ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); + + // Convert to 16b. + ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); + ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); + ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); + ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); + + // Add the inverse transform(s). + Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); + Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); + Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); + Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); + + // Unsigned saturate to 8b. + ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); + ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); + ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); + ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); + + // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + Unsafe.As>(ref outputRef) = ref0.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); } - - private static void InverseTransformVerticalPass(Vector128 in0, Vector128 in2, Vector128 in1, Vector128 in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3) + else { - Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); - Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); - - Vector128 k1 = Vector128.Create((short)20091).AsInt16(); - Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); - - // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 - Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); - Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); - Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); - Vector128 c4 = Sse2.Subtract(c1, c2); - Vector128 c = Sse2.Add(c3, c4); - - // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); - Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); - Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); - Vector128 d4 = Sse2.Add(d1, d2); - Vector128 d = Sse2.Add(d3, d4); - - // Second pass. - tmp0 = Sse2.Add(a, d); - tmp1 = Sse2.Add(b, c); - tmp2 = Sse2.Subtract(b, c); - tmp3 = Sse2.Subtract(a, d); + ITransformOne(reference, input, dst, scratch); + ITransformOne(reference[4..], input[16..], dst[4..], scratch); } + } - private static void InverseTransformHorizontalPass(Vector128 t0, Vector128 t2, Vector128 t1, Vector128 t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3) + public static void ITransformOne(Span reference, Span input, Span dst, Span scratch) + { + if (Sse2.IsSupported) { - Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); - Vector128 a = Sse2.Add(dc, t2.AsInt16()); - Vector128 b = Sse2.Subtract(dc, t2.AsInt16()); - - Vector128 k1 = Vector128.Create((short)20091).AsInt16(); - Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); - - // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 - Vector128 c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); - Vector128 c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); - Vector128 c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); - Vector128 c4 = Sse2.Subtract(c1, c2); - Vector128 c = Sse2.Add(c3, c4); - - // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 - Vector128 d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); - Vector128 d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); - Vector128 d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); - Vector128 d4 = Sse2.Add(d1, d2); - Vector128 d = Sse2.Add(d3, d4); - - // Second pass. - Vector128 tmp0 = Sse2.Add(a, d); - Vector128 tmp1 = Sse2.Add(b, c); - Vector128 tmp2 = Sse2.Subtract(b, c); - Vector128 tmp3 = Sse2.Subtract(a, d); - shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); - shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); - shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); - shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + // Load and concatenate the transform coefficients (we'll do two inverse + // transforms in parallel). In the case of only one inverse transform, the + // second half of the vectors will just contain random value we'll never + // use nor store. + ref short inputRef = ref MemoryMarshal.GetReference(input); + Vector128 in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); + Vector128 in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); + Vector128 in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); + Vector128 in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); + + // Transpose the two 4x4. + LossyUtils.Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); + + // Transpose the two 4x4. + LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'ref' and store. + // Load the reference(s). + Vector128 ref0 = Vector128.Zero; + Vector128 ref1 = Vector128.Zero; + Vector128 ref2 = Vector128.Zero; + Vector128 ref3 = Vector128.Zero; + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load four bytes/pixels per line. + ref0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref referenceRef)).AsByte(); + ref1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); + ref2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); + ref3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); + + // Convert to 16b. + ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); + ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); + ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); + ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); + + // Add the inverse transform(s). + Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); + Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); + Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); + Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); + + // Unsigned saturate to 8b. + ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); + ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); + ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); + ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); + + // Unsigned saturate to 8b. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + + // Store four bytes/pixels per line. + int output0 = Sse2.ConvertToInt32(ref0.AsInt32()); + int output1 = Sse2.ConvertToInt32(ref1.AsInt32()); + int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); + int output3 = Sse2.ConvertToInt32(ref3.AsInt32()); + + Unsafe.As(ref outputRef) = output0; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; } - - public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) + else { - if (Sse2.IsSupported) + int i; + Span tmp = scratch[..16]; + for (i = 0; i < 4; i++) { - ref byte srcRef = ref MemoryMarshal.GetReference(src); - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load src. - Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); - Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); - Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); - - // Load ref. - Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); - Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); - Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); - Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); - - // Convert both to 16 bit. - Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); - Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); - Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); - Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); - Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); - Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); - Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); - Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); - - // Compute difference. -> 00 01 02 03 00' 01' 02' 03' - Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); - Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); - Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); - Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); - - // Unpack and shuffle. - // 00 01 02 03 0 0 0 0 - // 10 11 12 13 0 0 0 0 - // 20 21 22 23 0 0 0 0 - // 30 31 32 33 0 0 0 0 - Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); - Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); - - // First pass. - FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); - FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); - - // Second pass. - FTransformPass2SSE2(v01l, v32l, output); - FTransformPass2SSE2(v01h, v32h, output2); + // vertical pass. + int a = input[0] + input[8]; + int b = input[0] - input[8]; + int c = Mul(input[4], KC2) - Mul(input[12], KC1); + int d = Mul(input[4], KC1) + Mul(input[12], KC2); + tmp[0] = a + d; + tmp[1] = b + c; + tmp[2] = b - c; + tmp[3] = a - d; + tmp = tmp[4..]; + input = input[1..]; } - else + + tmp = scratch; + for (i = 0; i < 4; i++) { - FTransform(src, reference, output, scratch); - FTransform(src[4..], reference[4..], output2, scratch); + // horizontal pass. + int dc = tmp[0] + 4; + int a = dc + tmp[8]; + int b = dc - tmp[8]; + int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); + int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); + Store(dst, reference, 0, i, a + d); + Store(dst, reference, 1, i, b + c); + Store(dst, reference, 2, i, b - c); + Store(dst, reference, 3, i, a - d); + tmp = tmp[1..]; } } + } - public static void FTransform(Span src, Span reference, Span output, Span scratch) - { - if (Sse2.IsSupported) - { - ref byte srcRef = ref MemoryMarshal.GetReference(src); - ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - - // Load src. - Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); - Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); - Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); - Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); - - // Load ref. - Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); - Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); - Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); - Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); - - // 00 01 02 03 * - // 10 11 12 13 * - // 20 21 22 23 * - // 30 31 32 33 * - // Shuffle. - Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); - Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); - Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); - Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); - - // 00 01 10 11 02 03 12 13 * * ... - // 20 21 30 31 22 22 32 33 * * ... - - // Convert both to 16 bit. - Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); - Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); - Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); - Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); - - // Compute the difference. - Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); - Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); - - // First pass. - FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); - - // Second pass. - FTransformPass2SSE2(v01, v32, output); - } - else - { - int i; - Span tmp = scratch[..16]; + private static void InverseTransformVerticalPass(Vector128 in0, Vector128 in2, Vector128 in1, Vector128 in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3) + { + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + Vector128 k1 = Vector128.Create((short)20091).AsInt16(); + Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), k2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), k1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3, c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), k1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), k2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + } - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; i++) - { - int d3 = src[srcIdx + 3] - reference[refIdx + 3]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - - srcIdx += WebpConstants.Bps; - refIdx += WebpConstants.Bps; - } + private static void InverseTransformHorizontalPass(Vector128 t0, Vector128 t2, Vector128 t1, Vector128 t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3) + { + Vector128 dc = Sse2.Add(t0.AsInt16(), Vector128.Create((short)4)); + Vector128 a = Sse2.Add(dc, t2.AsInt16()); + Vector128 b = Sse2.Subtract(dc, t2.AsInt16()); + + Vector128 k1 = Vector128.Create((short)20091).AsInt16(); + Vector128 k2 = Vector128.Create((short)-30068).AsInt16(); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + Vector128 c1 = Sse2.MultiplyHigh(t1.AsInt16(), k2); + Vector128 c2 = Sse2.MultiplyHigh(t3.AsInt16(), k1); + Vector128 c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + Vector128 d1 = Sse2.MultiplyHigh(t1.AsInt16(), k1); + Vector128 d2 = Sse2.MultiplyHigh(t3.AsInt16(), k2); + Vector128 d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a, d); + Vector128 tmp1 = Sse2.Add(b, c); + Vector128 tmp2 = Sse2.Subtract(b, c); + Vector128 tmp3 = Sse2.Subtract(a, d); + shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + } - for (i = 0; i < 4; i++) - { - int t12 = tmp[12 + i]; // 15b - int t8 = tmp[8 + i]; - - int a1 = tmp[4 + i] + t8; - int a2 = tmp[4 + i] - t8; - int a0 = tmp[0 + i] + t12; // 15b - int a3 = tmp[0 + i] - t12; - - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - } - } - } + public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) + { + if (Sse2.IsSupported) + { + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load src. + Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // Convert both to 16 bit. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); + Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); + Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); + Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); + Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); + Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); + Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); + + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); + Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); + Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); + Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); + + // Unpack and shuffle. + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); + Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); + + // First pass. + FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); + FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); - public static void FTransformPass1SSE2(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) + // Second pass. + FTransformPass2SSE2(v01l, v32l, output); + FTransformPass2SSE2(v01h, v32h, output2); + } + else { - // *in01 = 00 01 10 11 02 03 12 13 - // *in23 = 20 21 30 31 22 23 32 33 - const byte mmShuffle_2301 = 0b_10_11_00_01; - Vector128 shuf01_p = Sse2.ShuffleHigh(row01, mmShuffle_2301); - Vector128 shuf32_p = Sse2.ShuffleHigh(row23, mmShuffle_2301); - - // 00 01 10 11 03 02 13 12 - // 20 21 30 31 23 22 33 32 - Vector128 s01 = Sse2.UnpackLow(shuf01_p.AsInt64(), shuf32_p.AsInt64()); - Vector128 s32 = Sse2.UnpackHigh(shuf01_p.AsInt64(), shuf32_p.AsInt64()); - - // 00 01 10 11 20 21 30 31 - // 03 02 13 12 23 22 33 32 - Vector128 a01 = Sse2.Add(s01.AsInt16(), s32.AsInt16()); - Vector128 a32 = Sse2.Subtract(s01.AsInt16(), s32.AsInt16()); - - // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] - // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] - - // [ (a0 + a1) << 3, ... ] - Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16()); // K88p - - // [ (a0 - a1) << 3, ... ] - Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16()); // K88m - Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16()); // K5352_2217p - Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16()); // K5352_2217m - Vector128 tmp12 = Sse2.Add(tmp11, Vector128.Create(1812)); - Vector128 tmp32 = Sse2.Add(tmp31, Vector128.Create(937)); - Vector128 tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9); - Vector128 tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); - Vector128 s03 = Sse2.PackSignedSaturate(tmp0, tmp2); - Vector128 s12 = Sse2.PackSignedSaturate(tmp1, tmp3); - Vector128 slo = Sse2.UnpackLow(s03, s12); // 0 1 0 1 0 1... - Vector128 shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3 - Vector128 v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); - out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32()); - - const byte mmShuffle_1032 = 0b_01_00_11_10; - out32 = Sse2.Shuffle(v23, mmShuffle_1032); + FTransform(src, reference, output, scratch); + FTransform(src[4..], reference[4..], output2, scratch); } + } - public static void FTransformPass2SSE2(Vector128 v01, Vector128 v32, Span output) + public static void FTransform(Span src, Span reference, Span output, Span scratch) + { + if (Sse2.IsSupported) { - // Same operations are done on the (0,3) and (1,2) pairs. - // a3 = v0 - v3 - // a2 = v1 - v2 - Vector128 a32 = Sse2.Subtract(v01.AsInt16(), v32.AsInt16()); - Vector128 a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64()); - - Vector128 b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); - Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16()); // K5352_2217 - Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16()); // K2217_5352 - Vector128 d1 = Sse2.Add(c1, Vector128.Create(12000 + (1 << 16))); // K12000PlusOne - Vector128 d3 = Sse2.Add(c3, Vector128.Create(51000)); - Vector128 e1 = Sse2.ShiftRightArithmetic(d1, 16); - Vector128 e3 = Sse2.ShiftRightArithmetic(d3, 16); - - // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) - // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) - Vector128 f1 = Sse2.PackSignedSaturate(e1, e1); - Vector128 f3 = Sse2.PackSignedSaturate(e3, e3); - - // g1 = f1 + (a3 != 0); - // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the - // desired (0, 1), we add one earlier through k12000_plus_one. - // -> g1 = f1 + 1 - (a3 == 0) - Vector128 g1 = Sse2.Add(f1, Sse2.CompareEqual(a32, Vector128.Zero)); - - // a0 = v0 + v3 - // a1 = v1 + v2 - Vector128 a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16()); - Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), Vector128.Create((short)7)); - Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); - Vector128 c0 = Sse2.Add(a01Plus7, a11); - Vector128 c2 = Sse2.Subtract(a01Plus7, a11); - - // d0 = (a0 + a1 + 7) >> 4; - // d2 = (a0 - a1 + 7) >> 4; - Vector128 d0 = Sse2.ShiftRightArithmetic(c0, 4); - Vector128 d2 = Sse2.ShiftRightArithmetic(c2, 4); - - Vector128 d0g1 = Sse2.UnpackLow(d0.AsInt64(), g1.AsInt64()); - Vector128 d2f3 = Sse2.UnpackLow(d2.AsInt64(), f3.AsInt64()); - - ref short outputRef = ref MemoryMarshal.GetReference(output); - Unsafe.As>(ref outputRef) = d0g1.AsInt16(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = d2f3.AsInt16(); - } + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load src. + Vector128 src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + Vector128 src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + Vector128 src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + Vector128 src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + Vector128 ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + Vector128 ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + Vector128 ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + Vector128 ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); + Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); + Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); + + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Convert both to 16 bit. + Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); + Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); + Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); + Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); + + // Compute the difference. + Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); + Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); + + // First pass. + FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); - public static void FTransformWht(Span input, Span output, Span scratch) + // Second pass. + FTransformPass2SSE2(v01, v32, output); + } + else { + int i; Span tmp = scratch[..16]; - int i; - int inputIdx = 0; + int srcIdx = 0; + int refIdx = 0; for (i = 0; i < 4; i++) { - int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; - int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b - int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; - tmp[3 + (i * 4)] = a0 - a1; - tmp[2 + (i * 4)] = a3 - a2; - tmp[1 + (i * 4)] = a3 + a2; - tmp[0 + (i * 4)] = a0 + a1; // 14b - - inputIdx += 64; + int d3 = src[srcIdx + 3] - reference[refIdx + 3]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + + srcIdx += WebpConstants.Bps; + refIdx += WebpConstants.Bps; } for (i = 0; i < 4; i++) { - int t12 = tmp[12 + i]; + int t12 = tmp[12 + i]; // 15b int t8 = tmp[8 + i]; - int a1 = tmp[4 + i] + t12; - int a2 = tmp[4 + i] - t12; - int a0 = tmp[0 + i] + t8; // 15b - int a3 = tmp[0 + i] - t8; - - int b0 = a0 + a1; // 16b - int b1 = a3 + a2; - int b2 = a3 - a2; - int b3 = a0 - a1; + int a1 = tmp[4 + i] + t8; + int a2 = tmp[4 + i] - t8; + int a0 = tmp[0 + i] + t12; // 15b + int a3 = tmp[0 + i] - t12; - output[12 + i] = (short)(b3 >> 1); - output[8 + i] = (short)(b2 >> 1); - output[4 + i] = (short)(b1 >> 1); - output[0 + i] = (short)(b0 >> 1); // 15b + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b } } + } + + public static void FTransformPass1SSE2(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) + { + // *in01 = 00 01 10 11 02 03 12 13 + // *in23 = 20 21 30 31 22 23 32 33 + const byte mmShuffle_2301 = 0b_10_11_00_01; + Vector128 shuf01_p = Sse2.ShuffleHigh(row01, mmShuffle_2301); + Vector128 shuf32_p = Sse2.ShuffleHigh(row23, mmShuffle_2301); + + // 00 01 10 11 03 02 13 12 + // 20 21 30 31 23 22 33 32 + Vector128 s01 = Sse2.UnpackLow(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + Vector128 s32 = Sse2.UnpackHigh(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + + // 00 01 10 11 20 21 30 31 + // 03 02 13 12 23 22 33 32 + Vector128 a01 = Sse2.Add(s01.AsInt16(), s32.AsInt16()); + Vector128 a32 = Sse2.Subtract(s01.AsInt16(), s32.AsInt16()); + + // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] + // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] + + // [ (a0 + a1) << 3, ... ] + Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16()); // K88p + + // [ (a0 - a1) << 3, ... ] + Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16()); // K88m + Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16()); // K5352_2217p + Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16()); // K5352_2217m + Vector128 tmp12 = Sse2.Add(tmp11, Vector128.Create(1812)); + Vector128 tmp32 = Sse2.Add(tmp31, Vector128.Create(937)); + Vector128 tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9); + Vector128 tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); + Vector128 s03 = Sse2.PackSignedSaturate(tmp0, tmp2); + Vector128 s12 = Sse2.PackSignedSaturate(tmp1, tmp3); + Vector128 slo = Sse2.UnpackLow(s03, s12); // 0 1 0 1 0 1... + Vector128 shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3 + Vector128 v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); + out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32()); + + const byte mmShuffle_1032 = 0b_01_00_11_10; + out32 = Sse2.Shuffle(v23, mmShuffle_1032); + } + + public static void FTransformPass2SSE2(Vector128 v01, Vector128 v32, Span output) + { + // Same operations are done on the (0,3) and (1,2) pairs. + // a3 = v0 - v3 + // a2 = v1 - v2 + Vector128 a32 = Sse2.Subtract(v01.AsInt16(), v32.AsInt16()); + Vector128 a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64()); + + Vector128 b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); + Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16()); // K5352_2217 + Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16()); // K2217_5352 + Vector128 d1 = Sse2.Add(c1, Vector128.Create(12000 + (1 << 16))); // K12000PlusOne + Vector128 d3 = Sse2.Add(c3, Vector128.Create(51000)); + Vector128 e1 = Sse2.ShiftRightArithmetic(d1, 16); + Vector128 e3 = Sse2.ShiftRightArithmetic(d3, 16); + + // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) + // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) + Vector128 f1 = Sse2.PackSignedSaturate(e1, e1); + Vector128 f3 = Sse2.PackSignedSaturate(e3, e3); + + // g1 = f1 + (a3 != 0); + // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the + // desired (0, 1), we add one earlier through k12000_plus_one. + // -> g1 = f1 + 1 - (a3 == 0) + Vector128 g1 = Sse2.Add(f1, Sse2.CompareEqual(a32, Vector128.Zero)); + + // a0 = v0 + v3 + // a1 = v1 + v2 + Vector128 a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16()); + Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), Vector128.Create((short)7)); + Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); + Vector128 c0 = Sse2.Add(a01Plus7, a11); + Vector128 c2 = Sse2.Subtract(a01Plus7, a11); + + // d0 = (a0 + a1 + 7) >> 4; + // d2 = (a0 - a1 + 7) >> 4; + Vector128 d0 = Sse2.ShiftRightArithmetic(c0, 4); + Vector128 d2 = Sse2.ShiftRightArithmetic(c2, 4); + + Vector128 d0g1 = Sse2.UnpackLow(d0.AsInt64(), g1.AsInt64()); + Vector128 d2f3 = Sse2.UnpackLow(d2.AsInt64(), f3.AsInt64()); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = d0g1.AsInt16(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = d2f3.AsInt16(); + } + + public static void FTransformWht(Span input, Span output, Span scratch) + { + Span tmp = scratch[..16]; - // luma 16x16 prediction (paragraph 12.3). - public static void EncPredLuma16(Span dst, Span left, Span top) + int i; + int inputIdx = 0; + for (i = 0; i < 4; i++) { - DcMode(dst, left, top, 16, 16, 5); - VerticalPred(dst[I16VE16..], top, 16); - HorizontalPred(dst[I16HE16..], left, 16); - TrueMotion(dst[I16TM16..], left, top, 16); + int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; + int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b + int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; + tmp[3 + (i * 4)] = a0 - a1; + tmp[2 + (i * 4)] = a3 - a2; + tmp[1 + (i * 4)] = a3 + a2; + tmp[0 + (i * 4)] = a0 + a1; // 14b + + inputIdx += 64; } - // Chroma 8x8 prediction (paragraph 12.2). - public static void EncPredChroma8(Span dst, Span left, Span top) + for (i = 0; i < 4; i++) { - // U block. - DcMode(dst[C8DC8..], left, top, 8, 8, 4); - VerticalPred(dst[C8VE8..], top, 8); - HorizontalPred(dst[C8HE8..], left, 8); - TrueMotion(dst[C8TM8..], left, top, 8); - - // V block. - dst = dst[8..]; - if (top != default) - { - top = top[8..]; - } + int t12 = tmp[12 + i]; + int t8 = tmp[8 + i]; + + int a1 = tmp[4 + i] + t12; + int a2 = tmp[4 + i] - t12; + int a0 = tmp[0 + i] + t8; // 15b + int a3 = tmp[0 + i] - t8; + + int b0 = a0 + a1; // 16b + int b1 = a3 + a2; + int b2 = a3 - a2; + int b3 = a0 - a1; + + output[12 + i] = (short)(b3 >> 1); + output[8 + i] = (short)(b2 >> 1); + output[4 + i] = (short)(b1 >> 1); + output[0 + i] = (short)(b0 >> 1); // 15b + } + } - if (left != default) - { - left = left[16..]; - } + // luma 16x16 prediction (paragraph 12.3). + public static void EncPredLuma16(Span dst, Span left, Span top) + { + DcMode(dst, left, top, 16, 16, 5); + VerticalPred(dst[I16VE16..], top, 16); + HorizontalPred(dst[I16HE16..], left, 16); + TrueMotion(dst[I16TM16..], left, top, 16); + } - DcMode(dst[C8DC8..], left, top, 8, 8, 4); - VerticalPred(dst[C8VE8..], top, 8); - HorizontalPred(dst[C8HE8..], left, 8); - TrueMotion(dst[C8TM8..], left, top, 8); + // Chroma 8x8 prediction (paragraph 12.2). + public static void EncPredChroma8(Span dst, Span left, Span top) + { + // U block. + DcMode(dst[C8DC8..], left, top, 8, 8, 4); + VerticalPred(dst[C8VE8..], top, 8); + HorizontalPred(dst[C8HE8..], left, 8); + TrueMotion(dst[C8TM8..], left, top, 8); + + // V block. + dst = dst[8..]; + if (top != default) + { + top = top[8..]; } - // Left samples are top[-5 .. -2], top_left is top[-1], top are - // located at top[0..3], and top right is top[4..7] - public static void EncPredLuma4(Span dst, Span top, int topOffset, Span vals) + if (left != default) { - Dc4(dst[I4DC4..], top, topOffset); - Tm4(dst[I4TM4..], top, topOffset); - Ve4(dst[I4VE4..], top, topOffset, vals); - He4(dst[I4HE4..], top, topOffset); - Rd4(dst[I4RD4..], top, topOffset); - Vr4(dst[I4VR4..], top, topOffset); - Ld4(dst[I4LD4..], top, topOffset); - Vl4(dst[I4VL4..], top, topOffset); - Hd4(dst[I4HD4..], top, topOffset); - Hu4(dst[I4HU4..], top, topOffset); + left = left[16..]; } - private static void VerticalPred(Span dst, Span top, int size) + DcMode(dst[C8DC8..], left, top, 8, 8, 4); + VerticalPred(dst[C8VE8..], top, 8); + HorizontalPred(dst[C8HE8..], left, 8); + TrueMotion(dst[C8TM8..], left, top, 8); + } + + // Left samples are top[-5 .. -2], top_left is top[-1], top are + // located at top[0..3], and top right is top[4..7] + public static void EncPredLuma4(Span dst, Span top, int topOffset, Span vals) + { + Dc4(dst[I4DC4..], top, topOffset); + Tm4(dst[I4TM4..], top, topOffset); + Ve4(dst[I4VE4..], top, topOffset, vals); + He4(dst[I4HE4..], top, topOffset); + Rd4(dst[I4RD4..], top, topOffset); + Vr4(dst[I4VR4..], top, topOffset); + Ld4(dst[I4LD4..], top, topOffset); + Vl4(dst[I4VL4..], top, topOffset); + Hd4(dst[I4HD4..], top, topOffset); + Hu4(dst[I4HU4..], top, topOffset); + } + + private static void VerticalPred(Span dst, Span top, int size) + { + if (top != default) { - if (top != default) + for (int j = 0; j < size; j++) { - for (int j = 0; j < size; j++) - { - top[..size].CopyTo(dst[(j * WebpConstants.Bps)..]); - } + top[..size].CopyTo(dst[(j * WebpConstants.Bps)..]); } - else + } + else + { + Fill(dst, 127, size); + } + } + + public static void HorizontalPred(Span dst, Span left, int size) + { + if (left != default) + { + left = left[1..]; // in the reference implementation, left starts at - 1. + for (int j = 0; j < size; j++) { - Fill(dst, 127, size); + dst.Slice(j * WebpConstants.Bps, size).Fill(left[j]); } } + else + { + Fill(dst, 129, size); + } + } - public static void HorizontalPred(Span dst, Span left, int size) + public static void TrueMotion(Span dst, Span left, Span top, int size) + { + if (left != default) { - if (left != default) + if (top != default) { - left = left[1..]; // in the reference implementation, left starts at - 1. - for (int j = 0; j < size; j++) + Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 + for (int y = 0; y < size; y++) { - dst.Slice(j * WebpConstants.Bps, size).Fill(left[j]); + Span clipTable = clip[left[y + 1]..]; // left[y] + for (int x = 0; x < size; x++) + { + dst[x] = clipTable[top[x]]; + } + + dst = dst[WebpConstants.Bps..]; } } else { - Fill(dst, 129, size); + HorizontalPred(dst, left, size); } } - - public static void TrueMotion(Span dst, Span left, Span top, int size) + else { - if (left != default) + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != default) { - if (top != default) - { - Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 - for (int y = 0; y < size; y++) - { - Span clipTable = clip[left[y + 1]..]; // left[y] - for (int x = 0; x < size; x++) - { - dst[x] = clipTable[top[x]]; - } - - dst = dst[WebpConstants.Bps..]; - } - } - else - { - HorizontalPred(dst, left, size); - } + VerticalPred(dst, top, size); } else { - // true motion without left samples (hence: with default 129 value) - // is equivalent to VE prediction where you just copy the top samples. - // Note that if top samples are not available, the default value is - // then 129, and not 127 as in the VerticalPred case. - if (top != default) - { - VerticalPred(dst, top, size); - } - else - { - Fill(dst, 129, size); - } + Fill(dst, 129, size); } } + } - private static void DcMode(Span dst, Span left, Span top, int size, int round, int shift) + private static void DcMode(Span dst, Span left, Span top, int size, int round, int shift) + { + int dc = 0; + int j; + if (top != default) { - int dc = 0; - int j; - if (top != default) + for (j = 0; j < size; j++) { - for (j = 0; j < size; j++) - { - dc += top[j]; - } - - if (left != default) - { - // top and left present. - left = left[1..]; // in the reference implementation, left starts at -1. - for (j = 0; j < size; j++) - { - dc += left[j]; - } - } - else - { - // top, but no left. - dc += dc; - } - - dc = (dc + round) >> shift; + dc += top[j]; } - else if (left != default) + + if (left != default) { - // left but no top. + // top and left present. left = left[1..]; // in the reference implementation, left starts at -1. for (j = 0; j < size; j++) { dc += left[j]; } - - dc += dc; - dc = (dc + round) >> shift; } else { - // no top, no left, nothing. - dc = 0x80; + // top, but no left. + dc += dc; } - Fill(dst, dc, size); + dc = (dc + round) >> shift; } - - private static void Dc4(Span dst, Span top, int topOffset) + else if (left != default) { - uint dc = 4; - int i; - for (i = 0; i < 4; i++) + // left but no top. + left = left[1..]; // in the reference implementation, left starts at -1. + for (j = 0; j < size; j++) { - dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); + dc += left[j]; } - Fill(dst, (int)(dc >> 3), 4); + dc += dc; + dc = (dc + round) >> shift; } - - private static void Tm4(Span dst, Span top, int topOffset) + else { - Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); - for (int y = 0; y < 4; y++) - { - Span clipTable = clip[top[topOffset - 2 - y]..]; - for (int x = 0; x < 4; x++) - { - dst[x] = clipTable[top[topOffset + x]]; - } + // no top, no left, nothing. + dc = 0x80; + } - dst = dst[WebpConstants.Bps..]; - } + Fill(dst, dc, size); + } + + private static void Dc4(Span dst, Span top, int topOffset) + { + uint dc = 4; + int i; + for (i = 0; i < 4; i++) + { + dc += (uint)(top[topOffset + i] + top[topOffset - 5 + i]); } - private static void Ve4(Span dst, Span top, int topOffset, Span vals) + Fill(dst, (int)(dc >> 3), 4); + } + + private static void Tm4(Span dst, Span top, int topOffset) + { + Span clip = Clip1.AsSpan(255 - top[topOffset - 1]); + for (int y = 0; y < 4; y++) { - // vertical - vals[0] = LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]); - vals[1] = LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]); - vals[2] = LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]); - vals[3] = LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]); - for (int i = 0; i < 4; i++) + Span clipTable = clip[top[topOffset - 2 - y]..]; + for (int x = 0; x < 4; x++) { - vals.CopyTo(dst[(i * WebpConstants.Bps)..]); + dst[x] = clipTable[top[topOffset + x]]; } - } - private static void He4(Span dst, Span top, int topOffset) - { - // horizontal - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); - BinaryPrimitives.WriteUInt32BigEndian(dst, val); - val = 0x01010101U * LossyUtils.Avg3(i, j, k); - BinaryPrimitives.WriteUInt32BigEndian(dst[(1 * WebpConstants.Bps)..], val); - val = 0x01010101U * LossyUtils.Avg3(j, k, l); - BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); - val = 0x01010101U * LossyUtils.Avg3(k, l, l); - BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); + dst = dst[WebpConstants.Bps..]; } + } - private static void Rd4(Span dst, Span top, int topOffset) + private static void Ve4(Span dst, Span top, int topOffset, Span vals) + { + // vertical + vals[0] = LossyUtils.Avg3(top[topOffset - 1], top[topOffset], top[topOffset + 1]); + vals[1] = LossyUtils.Avg3(top[topOffset], top[topOffset + 1], top[topOffset + 2]); + vals[2] = LossyUtils.Avg3(top[topOffset + 1], top[topOffset + 2], top[topOffset + 3]); + vals[3] = LossyUtils.Avg3(top[topOffset + 2], top[topOffset + 3], top[topOffset + 4]); + for (int i = 0; i < 4; i++) { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); - byte ijk = LossyUtils.Avg3(i, j, k); - LossyUtils.Dst(dst, 0, 2, ijk); - LossyUtils.Dst(dst, 1, 3, ijk); - byte xij = LossyUtils.Avg3(x, i, j); - LossyUtils.Dst(dst, 0, 1, xij); - LossyUtils.Dst(dst, 1, 2, xij); - LossyUtils.Dst(dst, 2, 3, xij); - byte axi = LossyUtils.Avg3(a, x, i); - LossyUtils.Dst(dst, 0, 0, axi); - LossyUtils.Dst(dst, 1, 1, axi); - LossyUtils.Dst(dst, 2, 2, axi); - LossyUtils.Dst(dst, 3, 3, axi); - byte bax = LossyUtils.Avg3(b, a, x); - LossyUtils.Dst(dst, 1, 0, bax); - LossyUtils.Dst(dst, 2, 1, bax); - LossyUtils.Dst(dst, 3, 2, bax); - byte cba = LossyUtils.Avg3(c, b, a); - LossyUtils.Dst(dst, 2, 0, cba); - LossyUtils.Dst(dst, 3, 1, cba); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); + vals.CopyTo(dst[(i * WebpConstants.Bps)..]); } + } - private static void Vr4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - - byte xa = LossyUtils.Avg2(x, a); - LossyUtils.Dst(dst, 0, 0, xa); - LossyUtils.Dst(dst, 1, 2, xa); - byte ab = LossyUtils.Avg2(a, b); - LossyUtils.Dst(dst, 1, 0, ab); - LossyUtils.Dst(dst, 2, 2, ab); - byte bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 2, 0, bc); - LossyUtils.Dst(dst, 3, 2, bc); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); - LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); - byte ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 0, 1, ixa); - LossyUtils.Dst(dst, 1, 3, ixa); - byte xab = LossyUtils.Avg3(x, a, b); - LossyUtils.Dst(dst, 1, 1, xab); - LossyUtils.Dst(dst, 2, 3, xab); - byte abc = LossyUtils.Avg3(a, b, c); - LossyUtils.Dst(dst, 2, 1, abc); - LossyUtils.Dst(dst, 3, 3, abc); - LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); - } + private static void He4(Span dst, Span top, int topOffset) + { + // horizontal + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + uint val = 0x01010101U * LossyUtils.Avg3(x, i, j); + BinaryPrimitives.WriteUInt32BigEndian(dst, val); + val = 0x01010101U * LossyUtils.Avg3(i, j, k); + BinaryPrimitives.WriteUInt32BigEndian(dst[(1 * WebpConstants.Bps)..], val); + val = 0x01010101U * LossyUtils.Avg3(j, k, l); + BinaryPrimitives.WriteUInt32BigEndian(dst[(2 * WebpConstants.Bps)..], val); + val = 0x01010101U * LossyUtils.Avg3(k, l, l); + BinaryPrimitives.WriteUInt32BigEndian(dst[(3 * WebpConstants.Bps)..], val); + } - private static void Ld4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); - byte bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 0, bcd); - LossyUtils.Dst(dst, 0, 1, bcd); - byte cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 0, cde); - LossyUtils.Dst(dst, 1, 1, cde); - LossyUtils.Dst(dst, 0, 2, cde); - byte def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 0, def); - LossyUtils.Dst(dst, 2, 1, def); - LossyUtils.Dst(dst, 1, 2, def); - LossyUtils.Dst(dst, 0, 3, def); - byte efg = LossyUtils.Avg3(e, f, g); - LossyUtils.Dst(dst, 3, 1, efg); - LossyUtils.Dst(dst, 2, 2, efg); - LossyUtils.Dst(dst, 1, 3, efg); - byte fgh = LossyUtils.Avg3(f, g, h); - LossyUtils.Dst(dst, 3, 2, fgh); - LossyUtils.Dst(dst, 2, 3, fgh); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); - } + private static void Rd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(j, k, l)); + byte ijk = LossyUtils.Avg3(i, j, k); + LossyUtils.Dst(dst, 0, 2, ijk); + LossyUtils.Dst(dst, 1, 3, ijk); + byte xij = LossyUtils.Avg3(x, i, j); + LossyUtils.Dst(dst, 0, 1, xij); + LossyUtils.Dst(dst, 1, 2, xij); + LossyUtils.Dst(dst, 2, 3, xij); + byte axi = LossyUtils.Avg3(a, x, i); + LossyUtils.Dst(dst, 0, 0, axi); + LossyUtils.Dst(dst, 1, 1, axi); + LossyUtils.Dst(dst, 2, 2, axi); + LossyUtils.Dst(dst, 3, 3, axi); + byte bax = LossyUtils.Avg3(b, a, x); + LossyUtils.Dst(dst, 1, 0, bax); + LossyUtils.Dst(dst, 2, 1, bax); + LossyUtils.Dst(dst, 3, 2, bax); + byte cba = LossyUtils.Avg3(c, b, a); + LossyUtils.Dst(dst, 2, 0, cba); + LossyUtils.Dst(dst, 3, 1, cba); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(d, c, b)); + } - private static void Vl4(Span dst, Span top, int topOffset) - { - byte a = top[topOffset + 0]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - byte d = top[topOffset + 3]; - byte e = top[topOffset + 4]; - byte f = top[topOffset + 5]; - byte g = top[topOffset + 6]; - byte h = top[topOffset + 7]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); - byte bc = LossyUtils.Avg2(b, c); - LossyUtils.Dst(dst, 1, 0, bc); - LossyUtils.Dst(dst, 0, 2, bc); - byte cd = LossyUtils.Avg2(c, d); - LossyUtils.Dst(dst, 2, 0, cd); - LossyUtils.Dst(dst, 1, 2, cd); - byte de = LossyUtils.Avg2(d, e); - LossyUtils.Dst(dst, 3, 0, de); - LossyUtils.Dst(dst, 2, 2, de); - LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); - byte bcd = LossyUtils.Avg3(b, c, d); - LossyUtils.Dst(dst, 1, 1, bcd); - LossyUtils.Dst(dst, 0, 3, bcd); - byte cde = LossyUtils.Avg3(c, d, e); - LossyUtils.Dst(dst, 2, 1, cde); - LossyUtils.Dst(dst, 1, 3, cde); - byte def = LossyUtils.Avg3(d, e, f); - LossyUtils.Dst(dst, 3, 1, def); - LossyUtils.Dst(dst, 2, 3, def); - LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); - LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); - } + private static void Vr4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + + byte xa = LossyUtils.Avg2(x, a); + LossyUtils.Dst(dst, 0, 0, xa); + LossyUtils.Dst(dst, 1, 2, xa); + byte ab = LossyUtils.Avg2(a, b); + LossyUtils.Dst(dst, 1, 0, ab); + LossyUtils.Dst(dst, 2, 2, ab); + byte bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 2, 0, bc); + LossyUtils.Dst(dst, 3, 2, bc); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg2(c, d)); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg3(k, j, i)); + LossyUtils.Dst(dst, 0, 2, LossyUtils.Avg3(j, i, x)); + byte ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 0, 1, ixa); + LossyUtils.Dst(dst, 1, 3, ixa); + byte xab = LossyUtils.Avg3(x, a, b); + LossyUtils.Dst(dst, 1, 1, xab); + LossyUtils.Dst(dst, 2, 3, xab); + byte abc = LossyUtils.Avg3(a, b, c); + LossyUtils.Dst(dst, 2, 1, abc); + LossyUtils.Dst(dst, 3, 3, abc); + LossyUtils.Dst(dst, 3, 1, LossyUtils.Avg3(b, c, d)); + } - private static void Hd4(Span dst, Span top, int topOffset) - { - byte x = top[topOffset - 1]; - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - byte a = top[topOffset]; - byte b = top[topOffset + 1]; - byte c = top[topOffset + 2]; - - byte ix = LossyUtils.Avg2(i, x); - LossyUtils.Dst(dst, 0, 0, ix); - LossyUtils.Dst(dst, 2, 1, ix); - byte ji = LossyUtils.Avg2(j, i); - LossyUtils.Dst(dst, 0, 1, ji); - LossyUtils.Dst(dst, 2, 2, ji); - byte kj = LossyUtils.Avg2(k, j); - LossyUtils.Dst(dst, 0, 2, kj); - LossyUtils.Dst(dst, 2, 3, kj); - LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); - LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); - LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); - byte ixa = LossyUtils.Avg3(i, x, a); - LossyUtils.Dst(dst, 1, 0, ixa); - LossyUtils.Dst(dst, 3, 1, ixa); - byte jix = LossyUtils.Avg3(j, i, x); - LossyUtils.Dst(dst, 1, 1, jix); - LossyUtils.Dst(dst, 3, 2, jix); - byte kji = LossyUtils.Avg3(k, j, i); - LossyUtils.Dst(dst, 1, 2, kji); - LossyUtils.Dst(dst, 3, 3, kji); - LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); - } + private static void Ld4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg3(a, b, c)); + byte bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 0, bcd); + LossyUtils.Dst(dst, 0, 1, bcd); + byte cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 0, cde); + LossyUtils.Dst(dst, 1, 1, cde); + LossyUtils.Dst(dst, 0, 2, cde); + byte def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 0, def); + LossyUtils.Dst(dst, 2, 1, def); + LossyUtils.Dst(dst, 1, 2, def); + LossyUtils.Dst(dst, 0, 3, def); + byte efg = LossyUtils.Avg3(e, f, g); + LossyUtils.Dst(dst, 3, 1, efg); + LossyUtils.Dst(dst, 2, 2, efg); + LossyUtils.Dst(dst, 1, 3, efg); + byte fgh = LossyUtils.Avg3(f, g, h); + LossyUtils.Dst(dst, 3, 2, fgh); + LossyUtils.Dst(dst, 2, 3, fgh); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(g, h, h)); + } - private static void Hu4(Span dst, Span top, int topOffset) - { - byte i = top[topOffset - 2]; - byte j = top[topOffset - 3]; - byte k = top[topOffset - 4]; - byte l = top[topOffset - 5]; - - LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); - byte jk = LossyUtils.Avg2(j, k); - LossyUtils.Dst(dst, 2, 0, jk); - LossyUtils.Dst(dst, 0, 1, jk); - byte kl = LossyUtils.Avg2(k, l); - LossyUtils.Dst(dst, 2, 1, kl); - LossyUtils.Dst(dst, 0, 2, kl); - LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); - byte jkl = LossyUtils.Avg3(j, k, l); - LossyUtils.Dst(dst, 3, 0, jkl); - LossyUtils.Dst(dst, 1, 1, jkl); - byte kll = LossyUtils.Avg3(k, l, l); - LossyUtils.Dst(dst, 3, 1, kll); - LossyUtils.Dst(dst, 1, 2, kll); - LossyUtils.Dst(dst, 3, 2, l); - LossyUtils.Dst(dst, 2, 2, l); - LossyUtils.Dst(dst, 0, 3, l); - LossyUtils.Dst(dst, 1, 3, l); - LossyUtils.Dst(dst, 2, 3, l); - LossyUtils.Dst(dst, 3, 3, l); - } + private static void Vl4(Span dst, Span top, int topOffset) + { + byte a = top[topOffset + 0]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + byte d = top[topOffset + 3]; + byte e = top[topOffset + 4]; + byte f = top[topOffset + 5]; + byte g = top[topOffset + 6]; + byte h = top[topOffset + 7]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(a, b)); + byte bc = LossyUtils.Avg2(b, c); + LossyUtils.Dst(dst, 1, 0, bc); + LossyUtils.Dst(dst, 0, 2, bc); + byte cd = LossyUtils.Avg2(c, d); + LossyUtils.Dst(dst, 2, 0, cd); + LossyUtils.Dst(dst, 1, 2, cd); + byte de = LossyUtils.Avg2(d, e); + LossyUtils.Dst(dst, 3, 0, de); + LossyUtils.Dst(dst, 2, 2, de); + LossyUtils.Dst(dst, 0, 1, LossyUtils.Avg3(a, b, c)); + byte bcd = LossyUtils.Avg3(b, c, d); + LossyUtils.Dst(dst, 1, 1, bcd); + LossyUtils.Dst(dst, 0, 3, bcd); + byte cde = LossyUtils.Avg3(c, d, e); + LossyUtils.Dst(dst, 2, 1, cde); + LossyUtils.Dst(dst, 1, 3, cde); + byte def = LossyUtils.Avg3(d, e, f); + LossyUtils.Dst(dst, 3, 1, def); + LossyUtils.Dst(dst, 2, 3, def); + LossyUtils.Dst(dst, 3, 2, LossyUtils.Avg3(e, f, g)); + LossyUtils.Dst(dst, 3, 3, LossyUtils.Avg3(f, g, h)); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Fill(Span dst, int value, int size) + private static void Hd4(Span dst, Span top, int topOffset) + { + byte x = top[topOffset - 1]; + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + byte a = top[topOffset]; + byte b = top[topOffset + 1]; + byte c = top[topOffset + 2]; + + byte ix = LossyUtils.Avg2(i, x); + LossyUtils.Dst(dst, 0, 0, ix); + LossyUtils.Dst(dst, 2, 1, ix); + byte ji = LossyUtils.Avg2(j, i); + LossyUtils.Dst(dst, 0, 1, ji); + LossyUtils.Dst(dst, 2, 2, ji); + byte kj = LossyUtils.Avg2(k, j); + LossyUtils.Dst(dst, 0, 2, kj); + LossyUtils.Dst(dst, 2, 3, kj); + LossyUtils.Dst(dst, 0, 3, LossyUtils.Avg2(l, k)); + LossyUtils.Dst(dst, 3, 0, LossyUtils.Avg3(a, b, c)); + LossyUtils.Dst(dst, 2, 0, LossyUtils.Avg3(x, a, b)); + byte ixa = LossyUtils.Avg3(i, x, a); + LossyUtils.Dst(dst, 1, 0, ixa); + LossyUtils.Dst(dst, 3, 1, ixa); + byte jix = LossyUtils.Avg3(j, i, x); + LossyUtils.Dst(dst, 1, 1, jix); + LossyUtils.Dst(dst, 3, 2, jix); + byte kji = LossyUtils.Avg3(k, j, i); + LossyUtils.Dst(dst, 1, 2, kji); + LossyUtils.Dst(dst, 3, 3, kji); + LossyUtils.Dst(dst, 1, 3, LossyUtils.Avg3(l, k, j)); + } + + private static void Hu4(Span dst, Span top, int topOffset) + { + byte i = top[topOffset - 2]; + byte j = top[topOffset - 3]; + byte k = top[topOffset - 4]; + byte l = top[topOffset - 5]; + + LossyUtils.Dst(dst, 0, 0, LossyUtils.Avg2(i, j)); + byte jk = LossyUtils.Avg2(j, k); + LossyUtils.Dst(dst, 2, 0, jk); + LossyUtils.Dst(dst, 0, 1, jk); + byte kl = LossyUtils.Avg2(k, l); + LossyUtils.Dst(dst, 2, 1, kl); + LossyUtils.Dst(dst, 0, 2, kl); + LossyUtils.Dst(dst, 1, 0, LossyUtils.Avg3(i, j, k)); + byte jkl = LossyUtils.Avg3(j, k, l); + LossyUtils.Dst(dst, 3, 0, jkl); + LossyUtils.Dst(dst, 1, 1, jkl); + byte kll = LossyUtils.Avg3(k, l, l); + LossyUtils.Dst(dst, 3, 1, kll); + LossyUtils.Dst(dst, 1, 2, kll); + LossyUtils.Dst(dst, 3, 2, l); + LossyUtils.Dst(dst, 2, 2, l); + LossyUtils.Dst(dst, 0, 3, l); + LossyUtils.Dst(dst, 1, 3, l); + LossyUtils.Dst(dst, 2, 3, l); + LossyUtils.Dst(dst, 3, 3, l); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void Fill(Span dst, int value, int size) + { + for (int j = 0; j < size; j++) { - for (int j = 0; j < size; j++) - { - dst.Slice(j * WebpConstants.Bps, size).Fill((byte)value); - } + dst.Slice(j * WebpConstants.Bps, size).Fill((byte)value); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8b(int v) => (v & ~0xff) == 0 ? (byte)v : v < 0 ? (byte)0 : (byte)255; + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Clip8b(int v) => (v & ~0xff) == 0 ? (byte)v : v < 0 ? (byte)0 : (byte)255; - [MethodImpl(InliningOptions.ShortMethod)] - private static void Store(Span dst, Span reference, int x, int y, int v) => dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); + [MethodImpl(InliningOptions.ShortMethod)] + private static void Store(Span dst, Span reference, int x, int y, int v) => dst[x + (y * WebpConstants.Bps)] = LossyUtils.Clip8B(reference[x + (y * WebpConstants.Bps)] + (v >> 3)); - [MethodImpl(InliningOptions.ShortMethod)] - private static int Mul(int a, int b) => (a * b) >> 16; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Mul(int a, int b) => (a * b) >> 16; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs index 5afaffcd9e..6c4f68a47e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterHeader.cs @@ -1,72 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8FilterHeader { - internal class Vp8FilterHeader - { - private const int NumRefLfDeltas = 4; + private const int NumRefLfDeltas = 4; - private const int NumModeLfDeltas = 4; + private const int NumModeLfDeltas = 4; - private int filterLevel; + private int filterLevel; - private int sharpness; + private int sharpness; - /// - /// Initializes a new instance of the class. - /// - public Vp8FilterHeader() - { - this.RefLfDelta = new int[NumRefLfDeltas]; - this.ModeLfDelta = new int[NumModeLfDeltas]; - } + /// + /// Initializes a new instance of the class. + /// + public Vp8FilterHeader() + { + this.RefLfDelta = new int[NumRefLfDeltas]; + this.ModeLfDelta = new int[NumModeLfDeltas]; + } - /// - /// Gets or sets the loop filter. - /// - public LoopFilter LoopFilter { get; set; } + /// + /// Gets or sets the loop filter. + /// + public LoopFilter LoopFilter { get; set; } - /// - /// Gets or sets the filter level. Valid values are [0..63]. - /// - public int FilterLevel + /// + /// Gets or sets the filter level. Valid values are [0..63]. + /// + public int FilterLevel + { + get => this.filterLevel; + set { - get => this.filterLevel; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 63, nameof(this.FilterLevel)); - this.filterLevel = value; - } + Guard.MustBeBetweenOrEqualTo(value, 0, 63, nameof(this.FilterLevel)); + this.filterLevel = value; } + } - /// - /// Gets or sets the filter sharpness. Valid values are [0..7]. - /// - public int Sharpness + /// + /// Gets or sets the filter sharpness. Valid values are [0..7]. + /// + public int Sharpness + { + get => this.sharpness; + set { - get => this.sharpness; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 7, nameof(this.Sharpness)); - this.sharpness = value; - } + Guard.MustBeBetweenOrEqualTo(value, 0, 7, nameof(this.Sharpness)); + this.sharpness = value; } + } - /// - /// Gets or sets a value indicating whether the filtering type is: 0=complex, 1=simple. - /// - public bool Simple { get; set; } + /// + /// Gets or sets a value indicating whether the filtering type is: 0=complex, 1=simple. + /// + public bool Simple { get; set; } - /// - /// Gets or sets delta filter level for i4x4 relative to i16x16. - /// - public int I4x4LfDelta { get; set; } + /// + /// Gets or sets delta filter level for i4x4 relative to i16x16. + /// + public int I4x4LfDelta { get; set; } - public bool UseLfDelta { get; set; } + public bool UseLfDelta { get; set; } - public int[] RefLfDelta { get; } + public int[] RefLfDelta { get; } - public int[] ModeLfDelta { get; } - } + public int[] ModeLfDelta { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs index 6f63d44a40..e5169a41d2 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8FilterInfo.cs @@ -1,83 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Filter information. +/// +internal class Vp8FilterInfo : IDeepCloneable { - /// - /// Filter information. - /// - internal class Vp8FilterInfo : IDeepCloneable - { - private byte limit; + private byte limit; - private byte innerLevel; + private byte innerLevel; - private byte highEdgeVarianceThreshold; + private byte highEdgeVarianceThreshold; - /// - /// Initializes a new instance of the class. - /// - public Vp8FilterInfo() - { - } + /// + /// Initializes a new instance of the class. + /// + public Vp8FilterInfo() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The filter info to create a copy from. - public Vp8FilterInfo(Vp8FilterInfo other) - { - this.Limit = other.Limit; - this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold; - this.InnerLevel = other.InnerLevel; - this.UseInnerFiltering = other.UseInnerFiltering; - } + /// + /// Initializes a new instance of the class. + /// + /// The filter info to create a copy from. + public Vp8FilterInfo(Vp8FilterInfo other) + { + this.Limit = other.Limit; + this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold; + this.InnerLevel = other.InnerLevel; + this.UseInnerFiltering = other.UseInnerFiltering; + } - /// - /// Gets or sets the filter limit in [3..189], or 0 if no filtering. - /// - public byte Limit + /// + /// Gets or sets the filter limit in [3..189], or 0 if no filtering. + /// + public byte Limit + { + get => this.limit; + set { - get => this.limit; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)189, nameof(this.Limit)); - this.limit = value; - } + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)189, nameof(this.Limit)); + this.limit = value; } + } - /// - /// Gets or sets the inner limit in [1..63], or 0 if no filtering. - /// - public byte InnerLevel + /// + /// Gets or sets the inner limit in [1..63], or 0 if no filtering. + /// + public byte InnerLevel + { + get => this.innerLevel; + set { - get => this.innerLevel; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)63, nameof(this.InnerLevel)); - this.innerLevel = value; - } + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)63, nameof(this.InnerLevel)); + this.innerLevel = value; } + } - /// - /// Gets or sets a value indicating whether to do inner filtering. - /// - public bool UseInnerFiltering { get; set; } + /// + /// Gets or sets a value indicating whether to do inner filtering. + /// + public bool UseInnerFiltering { get; set; } - /// - /// Gets or sets the high edge variance threshold in [0..2]. - /// - public byte HighEdgeVarianceThreshold + /// + /// Gets or sets the high edge variance threshold in [0..2]. + /// + public byte HighEdgeVarianceThreshold + { + get => this.highEdgeVarianceThreshold; + set { - get => this.highEdgeVarianceThreshold; - set - { - Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)2, nameof(this.HighEdgeVarianceThreshold)); - this.highEdgeVarianceThreshold = value; - } + Guard.MustBeBetweenOrEqualTo(value, (byte)0, (byte)2, nameof(this.HighEdgeVarianceThreshold)); + this.highEdgeVarianceThreshold = value; } - - /// - public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); } + + /// + public IDeepCloneable DeepClone() => new Vp8FilterInfo(this); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs index e6dd072411..2bae892a61 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8FrameHeader.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Vp8 frame header information. +/// +internal class Vp8FrameHeader { /// - /// Vp8 frame header information. + /// Gets or sets a value indicating whether this is a key frame. /// - internal class Vp8FrameHeader - { - /// - /// Gets or sets a value indicating whether this is a key frame. - /// - public bool KeyFrame { get; set; } + public bool KeyFrame { get; set; } - /// - /// Gets or sets Vp8 profile [0..3]. - /// - public sbyte Profile { get; set; } + /// + /// Gets or sets Vp8 profile [0..3]. + /// + public sbyte Profile { get; set; } - /// - /// Gets or sets the partition length. - /// - public uint PartitionLength { get; set; } - } + /// + /// Gets or sets the partition length. + /// + public uint PartitionLength { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index 1ba359d85d..4036fb2844 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -1,132 +1,130 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal sealed class Vp8Histogram { - internal sealed class Vp8Histogram - { - private readonly int[] scratch = new int[16]; + private readonly int[] scratch = new int[16]; - private readonly short[] output = new short[16]; + private readonly short[] output = new short[16]; - private readonly int[] distribution = new int[MaxCoeffThresh + 1]; + private readonly int[] distribution = new int[MaxCoeffThresh + 1]; - /// - /// Size of histogram used by CollectHistogram. - /// - private const int MaxCoeffThresh = 31; + /// + /// Size of histogram used by CollectHistogram. + /// + private const int MaxCoeffThresh = 31; - private int maxValue; + private int maxValue; - private int lastNonZero; + private int lastNonZero; - /// - /// Initializes a new instance of the class. - /// - public Vp8Histogram() - { - this.maxValue = 0; - this.lastNonZero = 1; - } + /// + /// Initializes a new instance of the class. + /// + public Vp8Histogram() + { + this.maxValue = 0; + this.lastNonZero = 1; + } - public int GetAlpha() - { - // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer - // values which happen to be mostly noise. This leaves the maximum precision - // for handling the useful small values which contribute most. - int maxValue = this.maxValue; - int lastNonZero = this.lastNonZero; - int alpha = maxValue > 1 ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; - return alpha; - } + public int GetAlpha() + { + // 'alpha' will later be clipped to [0..MAX_ALPHA] range, clamping outer + // values which happen to be mostly noise. This leaves the maximum precision + // for handling the useful small values which contribute most. + int maxValue = this.maxValue; + int lastNonZero = this.lastNonZero; + int alpha = maxValue > 1 ? WebpConstants.AlphaScale * lastNonZero / maxValue : 0; + return alpha; + } - public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) + public void CollectHistogram(Span reference, Span pred, int startBlock, int endBlock) + { + int j; + this.distribution.AsSpan().Clear(); + for (j = startBlock; j < endBlock; j++) { - int j; - this.distribution.AsSpan().Clear(); - for (j = startBlock; j < endBlock; j++) - { - Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], this.output, this.scratch); + Vp8Encoding.FTransform(reference[WebpLookupTables.Vp8DspScan[j]..], pred[WebpLookupTables.Vp8DspScan[j]..], this.output, this.scratch); - // Convert coefficients to bin. - if (Avx2.IsSupported) - { - // Load. - ref short outputRef = ref MemoryMarshal.GetReference(this.output); - Vector256 out0 = Unsafe.As>(ref outputRef); + // Convert coefficients to bin. + if (Avx2.IsSupported) + { + // Load. + ref short outputRef = ref MemoryMarshal.GetReference(this.output); + Vector256 out0 = Unsafe.As>(ref outputRef); - // v = abs(out) >> 3 - Vector256 abs0 = Avx2.Abs(out0.AsInt16()); - Vector256 v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); + // v = abs(out) >> 3 + Vector256 abs0 = Avx2.Abs(out0.AsInt16()); + Vector256 v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); - // bin = min(v, MAX_COEFF_THRESH) - Vector256 min0 = Avx2.Min(v0, Vector256.Create((short)MaxCoeffThresh)); + // bin = min(v, MAX_COEFF_THRESH) + Vector256 min0 = Avx2.Min(v0, Vector256.Create((short)MaxCoeffThresh)); - // Store. - Unsafe.As>(ref outputRef) = min0; + // Store. + Unsafe.As>(ref outputRef) = min0; - // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) - { - ++this.distribution[this.output[k]]; - } + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + ++this.distribution[this.output[k]]; } - else + } + else + { + for (int k = 0; k < 16; ++k) { - for (int k = 0; k < 16; ++k) - { - int v = Math.Abs(this.output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++this.distribution[clippedValue]; - } + int v = Math.Abs(this.output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++this.distribution[clippedValue]; } } - - this.SetHistogramData(this.distribution); } - public void Merge(Vp8Histogram other) + this.SetHistogramData(this.distribution); + } + + public void Merge(Vp8Histogram other) + { + if (this.maxValue > other.maxValue) { - if (this.maxValue > other.maxValue) - { - other.maxValue = this.maxValue; - } + other.maxValue = this.maxValue; + } - if (this.lastNonZero > other.lastNonZero) - { - other.lastNonZero = this.lastNonZero; - } + if (this.lastNonZero > other.lastNonZero) + { + other.lastNonZero = this.lastNonZero; } + } - private void SetHistogramData(int[] distribution) + private void SetHistogramData(int[] distribution) + { + int maxValue = 0; + int lastNonZero = 1; + for (int k = 0; k <= MaxCoeffThresh; ++k) { - int maxValue = 0; - int lastNonZero = 1; - for (int k = 0; k <= MaxCoeffThresh; ++k) + int value = distribution[k]; + if (value > 0) { - int value = distribution[k]; - if (value > 0) + if (value > maxValue) { - if (value > maxValue) - { - maxValue = value; - } - - lastNonZero = k; + maxValue = value; } - } - this.maxValue = maxValue; - this.lastNonZero = lastNonZero; + lastNonZero = k; + } } - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipMax(int v, int max) => v > max ? max : v; + this.maxValue = maxValue; + this.lastNonZero = lastNonZero; } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipMax(int v, int max) => v > max ? max : v; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs index b6fd0f5e5a..fdfca0c3d8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Io.cs @@ -1,68 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +internal ref struct Vp8Io { - internal ref struct Vp8Io - { - /// - /// Gets or sets the picture width in pixels (invariable). - /// The actual area passed to put() is stored in /> field. - /// - public int Width { get; set; } + /// + /// Gets or sets the picture width in pixels (invariable). + /// The actual area passed to put() is stored in /> field. + /// + public int Width { get; set; } - /// - /// Gets or sets the picture height in pixels (invariable). - /// The actual area passed to put() is stored in /> field. - /// - public int Height { get; set; } + /// + /// Gets or sets the picture height in pixels (invariable). + /// The actual area passed to put() is stored in /> field. + /// + public int Height { get; set; } - /// - /// Gets or sets the y-position of the current macroblock. - /// - public int MbY { get; set; } + /// + /// Gets or sets the y-position of the current macroblock. + /// + public int MbY { get; set; } - /// - /// Gets or sets number of columns in the sample. - /// - public int MbW { get; set; } + /// + /// Gets or sets number of columns in the sample. + /// + public int MbW { get; set; } - /// - /// Gets or sets number of rows in the sample. - /// - public int MbH { get; set; } + /// + /// Gets or sets number of rows in the sample. + /// + public int MbH { get; set; } - /// - /// Gets or sets the luma component. - /// - public Span Y { get; set; } + /// + /// Gets or sets the luma component. + /// + public Span Y { get; set; } - /// - /// Gets or sets the U chroma component. - /// - public Span U { get; set; } + /// + /// Gets or sets the U chroma component. + /// + public Span U { get; set; } - /// - /// Gets or sets the V chroma component. - /// - public Span V { get; set; } + /// + /// Gets or sets the V chroma component. + /// + public Span V { get; set; } - /// - /// Gets or sets the row stride for luma. - /// - public int YStride { get; set; } + /// + /// Gets or sets the row stride for luma. + /// + public int YStride { get; set; } - /// - /// Gets or sets the row stride for chroma. - /// - public int UvStride { get; set; } + /// + /// Gets or sets the row stride for chroma. + /// + public int UvStride { get; set; } - public bool UseScaling { get; set; } + public bool UseScaling { get; set; } - public int ScaledWidth { get; set; } + public int ScaledWidth { get; set; } - public int ScaledHeight { get; set; } - } + public int ScaledHeight { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs index 0a392c6358..03e5526a7c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlock.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Contextual macroblock information. +/// +internal class Vp8MacroBlock { /// - /// Contextual macroblock information. + /// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma). /// - internal class Vp8MacroBlock - { - /// - /// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma). - /// - public uint NoneZeroAcDcCoeffs { get; set; } + public uint NoneZeroAcDcCoeffs { get; set; } - /// - /// Gets or sets non-zero DC coeff (1bit). - /// - public uint NoneZeroDcCoeffs { get; set; } - } + /// + /// Gets or sets non-zero DC coeff (1bit). + /// + public uint NoneZeroDcCoeffs { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs index dea810b1fb..f233e178c6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockData.cs @@ -1,66 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Data needed to reconstruct a macroblock. +/// +internal class Vp8MacroBlockData { /// - /// Data needed to reconstruct a macroblock. + /// Initializes a new instance of the class. /// - internal class Vp8MacroBlockData + public Vp8MacroBlockData() { - /// - /// Initializes a new instance of the class. - /// - public Vp8MacroBlockData() - { - this.Modes = new byte[16]; - this.Coeffs = new short[384]; - } + this.Modes = new byte[16]; + this.Coeffs = new short[384]; + } - /// - /// Gets or sets the coefficients. 384 coeffs = (16+4+4) * 4*4. - /// - public short[] Coeffs { get; set; } + /// + /// Gets or sets the coefficients. 384 coeffs = (16+4+4) * 4*4. + /// + public short[] Coeffs { get; set; } - /// - /// Gets or sets a value indicating whether its intra4x4. - /// - public bool IsI4x4 { get; set; } + /// + /// Gets or sets a value indicating whether its intra4x4. + /// + public bool IsI4x4 { get; set; } - /// - /// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. - /// - public byte[] Modes { get; } + /// + /// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes. + /// + public byte[] Modes { get; } - /// - /// Gets or sets the chroma prediction mode. - /// - public byte UvMode { get; set; } + /// + /// Gets or sets the chroma prediction mode. + /// + public byte UvMode { get; set; } - /// - /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). - /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: - /// code=0 -> no coefficient - /// code=1 -> only DC - /// code=2 -> first three coefficients are non-zero - /// code=3 -> more than three coefficients are non-zero - /// This allows to call specialized transform functions. - /// - public uint NonZeroY { get; set; } + /// + /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). + /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + /// code=0 -> no coefficient + /// code=1 -> only DC + /// code=2 -> first three coefficients are non-zero + /// code=3 -> more than three coefficients are non-zero + /// This allows to call specialized transform functions. + /// + public uint NonZeroY { get; set; } - /// - /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). - /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: - /// code=0 -> no coefficient - /// code=1 -> only DC - /// code=2 -> first three coefficients are non-zero - /// code=3 -> more than three coefficients are non-zero - /// This allows to call specialized transform functions. - /// - public uint NonZeroUv { get; set; } + /// + /// Gets or sets bit-wise info about the content of each sub-4x4 blocks (in decoding order). + /// Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + /// code=0 -> no coefficient + /// code=1 -> only DC + /// code=2 -> first three coefficients are non-zero + /// code=3 -> more than three coefficients are non-zero + /// This allows to call specialized transform functions. + /// + public uint NonZeroUv { get; set; } - public bool Skip { get; set; } + public bool Skip { get; set; } - public byte Segment { get; set; } - } + public byte Segment { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs index c8cfd8b5ea..f2fa852f80 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockInfo.cs @@ -3,19 +3,18 @@ using System.Diagnostics; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +[DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] +internal class Vp8MacroBlockInfo { - [DebuggerDisplay("Type: {MacroBlockType}, Alpha: {Alpha}, UvMode: {UvMode}")] - internal class Vp8MacroBlockInfo - { - public Vp8MacroBlockType MacroBlockType { get; set; } + public Vp8MacroBlockType MacroBlockType { get; set; } - public int UvMode { get; set; } + public int UvMode { get; set; } - public bool Skip { get; set; } + public bool Skip { get; set; } - public int Segment { get; set; } + public int Segment { get; set; } - public int Alpha { get; set; } - } + public int Alpha { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs index 56fef4fbde..99d8941b83 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8MacroBlockType.cs @@ -1,12 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal enum Vp8MacroBlockType { - internal enum Vp8MacroBlockType - { - I4X4 = 0, + I4X4 = 0, - I16X16 = 1 - } + I16X16 = 1 } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs index 4457409c3b..7ba15a6d61 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs @@ -1,102 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +internal unsafe struct Vp8Matrix { - internal unsafe struct Vp8Matrix + private static readonly int[][] BiasMatrices = { - private static readonly int[][] BiasMatrices = + // [luma-ac,luma-dc,chroma][dc,ac] + new[] { 96, 110 }, + new[] { 96, 108 }, + new[] { 110, 115 } + }; + + /// + /// Number of descaling bits for sharpening bias. + /// + private const int SharpenBits = 11; + + /// + /// The quantizer steps. + /// + public fixed ushort Q[16]; + + /// + /// The reciprocals, fixed point. + /// + public fixed ushort IQ[16]; + + /// + /// The rounding bias. + /// + public fixed uint Bias[16]; + + /// + /// The value below which a coefficient is zeroed. + /// + public fixed uint ZThresh[16]; + + /// + /// The frequency boosters for slight sharpening. + /// + public fixed short Sharpen[16]; + + // Sharpening by (slightly) raising the hi-frequency coeffs. + // Hack-ish but helpful for mid-bitrate range. Use with care. + // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. + private static ReadOnlySpan FreqSharpening => new byte[] { 0, 30, 60, 90, 30, 60, 90, 90, 60, 90, 90, 90, 90, 90, 90, 90 }; + + /// + /// Returns the average quantizer. + /// + /// The average quantizer. + public int Expand(int type) + { + int sum; + int i; + for (i = 0; i < 2; i++) { - // [luma-ac,luma-dc,chroma][dc,ac] - new[] { 96, 110 }, - new[] { 96, 108 }, - new[] { 110, 115 } - }; - - /// - /// Number of descaling bits for sharpening bias. - /// - private const int SharpenBits = 11; - - /// - /// The quantizer steps. - /// - public fixed ushort Q[16]; - - /// - /// The reciprocals, fixed point. - /// - public fixed ushort IQ[16]; - - /// - /// The rounding bias. - /// - public fixed uint Bias[16]; - - /// - /// The value below which a coefficient is zeroed. - /// - public fixed uint ZThresh[16]; - - /// - /// The frequency boosters for slight sharpening. - /// - public fixed short Sharpen[16]; - - // Sharpening by (slightly) raising the hi-frequency coeffs. - // Hack-ish but helpful for mid-bitrate range. Use with care. - // This uses C#'s optimization to refer to the static data segment of the assembly, no allocation occurs. - private static ReadOnlySpan FreqSharpening => new byte[] { 0, 30, 60, 90, 30, 60, 90, 90, 60, 90, 90, 90, 90, 90, 90, 90 }; - - /// - /// Returns the average quantizer. - /// - /// The average quantizer. - public int Expand(int type) + int isAcCoeff = i > 0 ? 1 : 0; + int bias = BiasMatrices[type][isAcCoeff]; + this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); + this.Bias[i] = (uint)BIAS(bias); + + // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: + // * zero if coeff <= zthresh + // * non-zero if coeff > zthresh + this.ZThresh[i] = ((1 << WebpConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; + } + + for (i = 2; i < 16; i++) { - int sum; - int i; - for (i = 0; i < 2; i++) - { - int isAcCoeff = i > 0 ? 1 : 0; - int bias = BiasMatrices[type][isAcCoeff]; - this.IQ[i] = (ushort)((1 << WebpConstants.QFix) / this.Q[i]); - this.Bias[i] = (uint)BIAS(bias); - - // zthresh is the exact value such that QUANTDIV(coeff, iQ, B) is: - // * zero if coeff <= zthresh - // * non-zero if coeff > zthresh - this.ZThresh[i] = ((1 << WebpConstants.QFix) - 1 - this.Bias[i]) / this.IQ[i]; - } + this.Q[i] = this.Q[1]; + this.IQ[i] = this.IQ[1]; + this.Bias[i] = this.Bias[1]; + this.ZThresh[i] = this.ZThresh[1]; + } - for (i = 2; i < 16; i++) + for (sum = 0, i = 0; i < 16; i++) + { + if (type == 0) { - this.Q[i] = this.Q[1]; - this.IQ[i] = this.IQ[1]; - this.Bias[i] = this.Bias[1]; - this.ZThresh[i] = this.ZThresh[1]; + // We only use sharpening for AC luma coeffs. + this.Sharpen[i] = (short)((FreqSharpening[i] * this.Q[i]) >> SharpenBits); } - - for (sum = 0, i = 0; i < 16; i++) + else { - if (type == 0) - { - // We only use sharpening for AC luma coeffs. - this.Sharpen[i] = (short)((FreqSharpening[i] * this.Q[i]) >> SharpenBits); - } - else - { - this.Sharpen[i] = 0; - } - - sum += this.Q[i]; + this.Sharpen[i] = 0; } - return (sum + 8) >> 4; + sum += this.Q[i]; } - private static int BIAS(int b) => b << (WebpConstants.QFix - 8); + return (sum + 8) >> 4; } + + private static int BIAS(int b) => b << (WebpConstants.QFix - 8); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs index c59b4e0377..4f52db0fe9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8ModeScore.cs @@ -1,139 +1,136 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +/// +/// Class to accumulate score and info during RD-optimization and mode evaluation. +/// +internal class Vp8ModeScore { + public const long MaxCost = 0x7fffffffffffffL; + + /// + /// Distortion multiplier (equivalent of lambda). + /// + private const int RdDistoMult = 256; + /// - /// Class to accumulate score and info during RD-optimization and mode evaluation. + /// Initializes a new instance of the class. /// - internal class Vp8ModeScore + public Vp8ModeScore() { - public const long MaxCost = 0x7fffffffffffffL; - - /// - /// Distortion multiplier (equivalent of lambda). - /// - private const int RdDistoMult = 256; - - /// - /// Initializes a new instance of the class. - /// - public Vp8ModeScore() - { - this.YDcLevels = new short[16]; - this.YAcLevels = new short[16 * 16]; - this.UvLevels = new short[(4 + 4) * 16]; - - this.ModesI4 = new byte[16]; - this.Derr = new int[2, 3]; - } - - /// - /// Gets or sets the distortion. - /// - public long D { get; set; } - - /// - /// Gets or sets the spectral distortion. - /// - public long SD { get; set; } - - /// - /// Gets or sets the header bits. - /// - public long H { get; set; } - - /// - /// Gets or sets the rate. - /// - public long R { get; set; } - - /// - /// Gets or sets the score. - /// - public long Score { get; set; } - - /// - /// Gets the quantized levels for luma-DC. - /// - public short[] YDcLevels { get; } - - /// - /// Gets the quantized levels for luma-AC. - /// - public short[] YAcLevels { get; } - - /// - /// Gets the quantized levels for chroma. - /// - public short[] UvLevels { get; } - - /// - /// Gets or sets the mode number for intra16 prediction. - /// - public int ModeI16 { get; set; } - - /// - /// Gets the mode numbers for intra4 predictions. - /// - public byte[] ModesI4 { get; } - - /// - /// Gets or sets the mode number of chroma prediction. - /// - public int ModeUv { get; set; } - - /// - /// Gets or sets the Non-zero blocks. - /// - public uint Nz { get; set; } - - /// - /// Gets the diffusion errors. - /// - public int[,] Derr { get; } - - public void Clear() - { - Array.Clear(this.YDcLevels); - Array.Clear(this.YAcLevels); - Array.Clear(this.UvLevels); - Array.Clear(this.ModesI4); - Array.Clear(this.Derr); - } - - public void InitScore() - { - this.D = 0; - this.SD = 0; - this.R = 0; - this.H = 0; - this.Nz = 0; - this.Score = MaxCost; - } - - public void CopyScore(Vp8ModeScore other) - { - this.D = other.D; - this.SD = other.SD; - this.R = other.R; - this.H = other.H; - this.Nz = other.Nz; // note that nz is not accumulated, but just copied. - this.Score = other.Score; - } - - public void AddScore(Vp8ModeScore other) - { - this.D += other.D; - this.SD += other.SD; - this.R += other.R; - this.H += other.H; - this.Nz |= other.Nz; // here, new nz bits are accumulated. - this.Score += other.Score; - } - - public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); + this.YDcLevels = new short[16]; + this.YAcLevels = new short[16 * 16]; + this.UvLevels = new short[(4 + 4) * 16]; + + this.ModesI4 = new byte[16]; + this.Derr = new int[2, 3]; } + + /// + /// Gets or sets the distortion. + /// + public long D { get; set; } + + /// + /// Gets or sets the spectral distortion. + /// + public long SD { get; set; } + + /// + /// Gets or sets the header bits. + /// + public long H { get; set; } + + /// + /// Gets or sets the rate. + /// + public long R { get; set; } + + /// + /// Gets or sets the score. + /// + public long Score { get; set; } + + /// + /// Gets the quantized levels for luma-DC. + /// + public short[] YDcLevels { get; } + + /// + /// Gets the quantized levels for luma-AC. + /// + public short[] YAcLevels { get; } + + /// + /// Gets the quantized levels for chroma. + /// + public short[] UvLevels { get; } + + /// + /// Gets or sets the mode number for intra16 prediction. + /// + public int ModeI16 { get; set; } + + /// + /// Gets the mode numbers for intra4 predictions. + /// + public byte[] ModesI4 { get; } + + /// + /// Gets or sets the mode number of chroma prediction. + /// + public int ModeUv { get; set; } + + /// + /// Gets or sets the Non-zero blocks. + /// + public uint Nz { get; set; } + + /// + /// Gets the diffusion errors. + /// + public int[,] Derr { get; } + + public void Clear() + { + Array.Clear(this.YDcLevels); + Array.Clear(this.YAcLevels); + Array.Clear(this.UvLevels); + Array.Clear(this.ModesI4); + Array.Clear(this.Derr); + } + + public void InitScore() + { + this.D = 0; + this.SD = 0; + this.R = 0; + this.H = 0; + this.Nz = 0; + this.Score = MaxCost; + } + + public void CopyScore(Vp8ModeScore other) + { + this.D = other.D; + this.SD = other.SD; + this.R = other.R; + this.H = other.H; + this.Nz = other.Nz; // note that nz is not accumulated, but just copied. + this.Score = other.Score; + } + + public void AddScore(Vp8ModeScore other) + { + this.D += other.D; + this.SD += other.SD; + this.R += other.R; + this.H += other.H; + this.Nz |= other.Nz; // here, new nz bits are accumulated. + this.Score += other.Score; + } + + public void SetRdScore(int lambda) => this.Score = ((this.R + this.H) * lambda) + (RdDistoMult * (this.D + this.SD)); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs index 66a7e4f176..627f036cec 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8PictureHeader.cs @@ -1,42 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8PictureHeader { - internal class Vp8PictureHeader - { - /// - /// Gets or sets the width of the image. - /// - public uint Width { get; set; } + /// + /// Gets or sets the width of the image. + /// + public uint Width { get; set; } - /// - /// Gets or sets the Height of the image. - /// - public uint Height { get; set; } + /// + /// Gets or sets the Height of the image. + /// + public uint Height { get; set; } - /// - /// Gets or sets the horizontal scale. - /// - public sbyte XScale { get; set; } + /// + /// Gets or sets the horizontal scale. + /// + public sbyte XScale { get; set; } - /// - /// Gets or sets the vertical scale. - /// - public sbyte YScale { get; set; } + /// + /// Gets or sets the vertical scale. + /// + public sbyte YScale { get; set; } - /// - /// Gets or sets the colorspace. - /// 0 - YUV color space similar to the YCrCb color space defined in. - /// 1 - Reserved for future use. - /// - public sbyte ColorSpace { get; set; } + /// + /// Gets or sets the colorspace. + /// 0 - YUV color space similar to the YCrCb color space defined in. + /// 1 - Reserved for future use. + /// + public sbyte ColorSpace { get; set; } - /// - /// Gets or sets the clamp type. - /// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive). - /// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary. - /// - public sbyte ClampType { get; set; } - } + /// + /// Gets or sets the clamp type. + /// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive). + /// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary. + /// + public sbyte ClampType { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs index f9545fcd77..0da6dfcad4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Proba.cs @@ -1,42 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Data for all frame-persistent probabilities. +/// +internal class Vp8Proba { + private const int MbFeatureTreeProbs = 3; + /// - /// Data for all frame-persistent probabilities. + /// Initializes a new instance of the class. /// - internal class Vp8Proba + public Vp8Proba() { - private const int MbFeatureTreeProbs = 3; + this.Segments = new uint[MbFeatureTreeProbs]; + this.Bands = new Vp8BandProbas[WebpConstants.NumTypes, WebpConstants.NumBands]; + this.BandsPtr = new Vp8BandProbas[WebpConstants.NumTypes][]; - /// - /// Initializes a new instance of the class. - /// - public Vp8Proba() + for (int i = 0; i < WebpConstants.NumTypes; i++) { - this.Segments = new uint[MbFeatureTreeProbs]; - this.Bands = new Vp8BandProbas[WebpConstants.NumTypes, WebpConstants.NumBands]; - this.BandsPtr = new Vp8BandProbas[WebpConstants.NumTypes][]; - - for (int i = 0; i < WebpConstants.NumTypes; i++) + for (int j = 0; j < WebpConstants.NumBands; j++) { - for (int j = 0; j < WebpConstants.NumBands; j++) - { - this.Bands[i, j] = new Vp8BandProbas(); - } + this.Bands[i, j] = new Vp8BandProbas(); } + } - for (int i = 0; i < WebpConstants.NumTypes; i++) - { - this.BandsPtr[i] = new Vp8BandProbas[16 + 1]; - } + for (int i = 0; i < WebpConstants.NumTypes; i++) + { + this.BandsPtr[i] = new Vp8BandProbas[16 + 1]; } + } - public uint[] Segments { get; } + public uint[] Segments { get; } - public Vp8BandProbas[,] Bands { get; } + public Vp8BandProbas[,] Bands { get; } - public Vp8BandProbas[][] BandsPtr { get; } - } + public Vp8BandProbas[][] BandsPtr { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs index 286590eef5..3375275424 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8ProbaArray.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Probabilities associated to one of the contexts. +/// +internal class Vp8ProbaArray { /// - /// Probabilities associated to one of the contexts. + /// Initializes a new instance of the class. /// - internal class Vp8ProbaArray - { - /// - /// Initializes a new instance of the class. - /// - public Vp8ProbaArray() => this.Probabilities = new byte[WebpConstants.NumProbas]; + public Vp8ProbaArray() => this.Probabilities = new byte[WebpConstants.NumProbas]; - /// - /// Gets the probabilities. - /// - public byte[] Probabilities { get; } - } + /// + /// Gets the probabilities. + /// + public byte[] Probabilities { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs index 680b92e135..27fc29f6e3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8QuantMatrix.cs @@ -1,34 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8QuantMatrix { - internal class Vp8QuantMatrix - { - private int dither; + private int dither; - public int[] Y1Mat { get; } = new int[2]; + public int[] Y1Mat { get; } = new int[2]; - public int[] Y2Mat { get; } = new int[2]; + public int[] Y2Mat { get; } = new int[2]; - public int[] UvMat { get; } = new int[2]; + public int[] UvMat { get; } = new int[2]; - /// - /// Gets or sets the U/V quantizer value. - /// - public int UvQuant { get; set; } + /// + /// Gets or sets the U/V quantizer value. + /// + public int UvQuant { get; set; } - /// - /// Gets or sets the dithering amplitude (0 = off, max=255). - /// - public int Dither + /// + /// Gets or sets the dithering amplitude (0 = off, max=255). + /// + public int Dither + { + get => this.dither; + set { - get => this.dither; - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.Dither)); - this.dither = value; - } + Guard.MustBeBetweenOrEqualTo(value, 0, 255, nameof(this.Dither)); + this.dither = value; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs index c503949938..30e5e77eeb 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8RDLevel.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Rate-distortion optimization levels +/// +internal enum Vp8RdLevel { /// - /// Rate-distortion optimization levels + /// No rd-opt. /// - internal enum Vp8RdLevel - { - /// - /// No rd-opt. - /// - RdOptNone = 0, + RdOptNone = 0, - /// - /// Basic scoring (no trellis). - /// - RdOptBasic = 1, + /// + /// Basic scoring (no trellis). + /// + RdOptBasic = 1, - /// - /// Perform trellis-quant on the final decision only. - /// - RdOptTrellis = 2, + /// + /// Perform trellis-quant on the final decision only. + /// + RdOptTrellis = 2, - /// - /// Trellis-quant for every scoring (much slower). - /// - RdOptTrellisAll = 3 - } + /// + /// Trellis-quant for every scoring (much slower). + /// + RdOptTrellisAll = 3 } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs index 9541ee1f7b..a6c3879bb7 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs @@ -1,255 +1,253 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// On-the-fly info about the current set of residuals. +/// +internal class Vp8Residual { - /// - /// On-the-fly info about the current set of residuals. - /// - internal class Vp8Residual - { - private readonly byte[] scratch = new byte[32]; + private readonly byte[] scratch = new byte[32]; - private readonly ushort[] scratchUShort = new ushort[16]; + private readonly ushort[] scratchUShort = new ushort[16]; - public int First { get; set; } + public int First { get; set; } - public int Last { get; set; } + public int Last { get; set; } - public int CoeffType { get; set; } + public int CoeffType { get; set; } - public short[] Coeffs { get; } = new short[16]; + public short[] Coeffs { get; } = new short[16]; - public Vp8BandProbas[] Prob { get; set; } + public Vp8BandProbas[] Prob { get; set; } - public Vp8Stats[] Stats { get; set; } + public Vp8Stats[] Stats { get; set; } - public Vp8Costs[] Costs { get; set; } + public Vp8Costs[] Costs { get; set; } + + public void Init(int first, int coeffType, Vp8EncProba prob) + { + this.First = first; + this.CoeffType = coeffType; + this.Prob = prob.Coeffs[this.CoeffType]; + this.Stats = prob.Stats[this.CoeffType]; + this.Costs = prob.RemappedCosts[this.CoeffType]; + this.Coeffs.AsSpan().Clear(); + } - public void Init(int first, int coeffType, Vp8EncProba prob) + public void SetCoeffs(Span coeffs) + { + if (Sse2.IsSupported) { - this.First = first; - this.CoeffType = coeffType; - this.Prob = prob.Coeffs[this.CoeffType]; - this.Stats = prob.Stats[this.CoeffType]; - this.Costs = prob.RemappedCosts[this.CoeffType]; - this.Coeffs.AsSpan().Clear(); + ref short coeffsRef = ref MemoryMarshal.GetReference(coeffs); + Vector128 c0 = Unsafe.As>(ref coeffsRef); + Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref coeffsRef, 8)); + + // Use SSE2 to compare 16 values with a single instruction. + Vector128 m0 = Sse2.PackSignedSaturate(c0.AsInt16(), c1.AsInt16()); + Vector128 m1 = Sse2.CompareEqual(m0, Vector128.Zero); + + // Get the comparison results as a bitmask into 16bits. Negate the mask to get + // the position of entries that are not equal to zero. We don't need to mask + // out least significant bits according to res->first, since coeffs[0] is 0 + // if res->first > 0. + uint mask = 0x0000ffffu ^ (uint)Sse2.MoveMask(m1); + + // The position of the most significant non-zero bit indicates the position of + // the last non-zero value. + this.Last = mask != 0 ? BitOperations.Log2(mask) : -1; } - - public void SetCoeffs(Span coeffs) + else { - if (Sse2.IsSupported) - { - ref short coeffsRef = ref MemoryMarshal.GetReference(coeffs); - Vector128 c0 = Unsafe.As>(ref coeffsRef); - Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref coeffsRef, 8)); - - // Use SSE2 to compare 16 values with a single instruction. - Vector128 m0 = Sse2.PackSignedSaturate(c0.AsInt16(), c1.AsInt16()); - Vector128 m1 = Sse2.CompareEqual(m0, Vector128.Zero); - - // Get the comparison results as a bitmask into 16bits. Negate the mask to get - // the position of entries that are not equal to zero. We don't need to mask - // out least significant bits according to res->first, since coeffs[0] is 0 - // if res->first > 0. - uint mask = 0x0000ffffu ^ (uint)Sse2.MoveMask(m1); - - // The position of the most significant non-zero bit indicates the position of - // the last non-zero value. - this.Last = mask != 0 ? BitOperations.Log2(mask) : -1; - } - else + int n; + this.Last = -1; + for (n = 15; n >= 0; --n) { - int n; - this.Last = -1; - for (n = 15; n >= 0; --n) + if (coeffs[n] != 0) { - if (coeffs[n] != 0) - { - this.Last = n; - break; - } + this.Last = n; + break; } } + } + + coeffs[..16].CopyTo(this.Coeffs); + } - coeffs[..16].CopyTo(this.Coeffs); + // Simulate block coding, but only record statistics. + // Note: no need to record the fixed probas. + public int RecordCoeffs(int ctx) + { + int n = this.First; + Vp8StatsArray s = this.Stats[n].Stats[ctx]; + if (this.Last < 0) + { + RecordStats(0, s, 0); + return 0; } - // Simulate block coding, but only record statistics. - // Note: no need to record the fixed probas. - public int RecordCoeffs(int ctx) + while (n <= this.Last) { - int n = this.First; - Vp8StatsArray s = this.Stats[n].Stats[ctx]; - if (this.Last < 0) + int v; + RecordStats(1, s, 0); // order of record doesn't matter + while ((v = this.Coeffs[n++]) == 0) { - RecordStats(0, s, 0); - return 0; + RecordStats(0, s, 1); + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[0]; } - while (n <= this.Last) + RecordStats(1, s, 1); + bool bit = (uint)(v + 1) > 2u; + if (RecordStats(bit ? 1 : 0, s, 2) == 0) + { + // v = -1 or 1 + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[1]; + } + else { - int v; - RecordStats(1, s, 0); // order of record doesn't matter - while ((v = this.Coeffs[n++]) == 0) + v = Math.Abs(v); + if (v > WebpConstants.MaxVariableLevel) { - RecordStats(0, s, 1); - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[0]; + v = WebpConstants.MaxVariableLevel; } - RecordStats(1, s, 1); - bool bit = (uint)(v + 1) > 2u; - if (RecordStats(bit ? 1 : 0, s, 2) == 0) + int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; + int pattern = WebpLookupTables.Vp8LevelCodes[v - 1][0]; + int i; + for (i = 0; (pattern >>= 1) != 0; i++) { - // v = -1 or 1 - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[1]; - } - else - { - v = Math.Abs(v); - if (v > WebpConstants.MaxVariableLevel) - { - v = WebpConstants.MaxVariableLevel; - } - - int bits = WebpLookupTables.Vp8LevelCodes[v - 1][1]; - int pattern = WebpLookupTables.Vp8LevelCodes[v - 1][0]; - int i; - for (i = 0; (pattern >>= 1) != 0; i++) + int mask = 2 << i; + if ((pattern & 1) != 0) { - int mask = 2 << i; - if ((pattern & 1) != 0) - { - RecordStats((bits & mask) != 0 ? 1 : 0, s, 3 + i); - } + RecordStats((bits & mask) != 0 ? 1 : 0, s, 3 + i); } - - s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[2]; } - } - if (n < 16) - { - RecordStats(0, s, 0); + s = this.Stats[WebpConstants.Vp8EncBands[n]].Stats[2]; } - - return 1; } - public int GetResidualCost(int ctx0) + if (n < 16) { - int n = this.First; - int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0]; - Vp8Costs[] costs = this.Costs; - Vp8CostArray t = costs[n].Costs[ctx0]; + RecordStats(0, s, 0); + } - // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 - // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll - // be missing during the loop. - int cost = ctx0 == 0 ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; + return 1; + } - if (this.Last < 0) - { - return LossyUtils.Vp8BitCost(0, (byte)p0); - } + public int GetResidualCost(int ctx0) + { + int n = this.First; + int p0 = this.Prob[n].Probabilities[ctx0].Probabilities[0]; + Vp8Costs[] costs = this.Costs; + Vp8CostArray t = costs[n].Costs[ctx0]; - if (Avx2.IsSupported) - { - Span ctxs = this.scratch.AsSpan(0, 16); - Span levels = this.scratch.AsSpan(16, 16); - Span absLevels = this.scratchUShort.AsSpan(); - - // Precompute clamped levels and contexts, packed to 8b. - ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); - Vector256 c0 = Unsafe.As>(ref outputRef).AsInt16(); - Vector256 d0 = Avx2.Subtract(Vector256.Zero, c0); - Vector256 e0 = Avx2.Max(c0, d0); // abs(v), 16b - Vector256 f = Avx2.PackSignedSaturate(e0, e0); - Vector256 g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2)); - Vector256 h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67] - - ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); - ref byte levelsRef = ref MemoryMarshal.GetReference(levels); - ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels); - Unsafe.As>(ref ctxsRef) = g.GetLower(); - Unsafe.As>(ref levelsRef) = h.GetLower(); - Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); - - int level; - int flevel; - for (; n < this.Last; ++n) - { - int ctx = ctxs[n]; - level = levels[n]; - flevel = absLevels[n]; - cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; - t = costs[n + 1].Costs[ctx]; - } + // bitCost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = ctx0 == 0 ? LossyUtils.Vp8BitCost(1, (byte)p0) : 0; + + if (this.Last < 0) + { + return LossyUtils.Vp8BitCost(0, (byte)p0); + } - // Last coefficient is always non-zero. + if (Avx2.IsSupported) + { + Span ctxs = this.scratch.AsSpan(0, 16); + Span levels = this.scratch.AsSpan(16, 16); + Span absLevels = this.scratchUShort.AsSpan(); + + // Precompute clamped levels and contexts, packed to 8b. + ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); + Vector256 c0 = Unsafe.As>(ref outputRef).AsInt16(); + Vector256 d0 = Avx2.Subtract(Vector256.Zero, c0); + Vector256 e0 = Avx2.Max(c0, d0); // abs(v), 16b + Vector256 f = Avx2.PackSignedSaturate(e0, e0); + Vector256 g = Avx2.Min(f.AsByte(), Vector256.Create((byte)2)); + Vector256 h = Avx2.Min(f.AsByte(), Vector256.Create((byte)67)); // clampLevel in [0..67] + + ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); + ref byte levelsRef = ref MemoryMarshal.GetReference(levels); + ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels); + Unsafe.As>(ref ctxsRef) = g.GetLower(); + Unsafe.As>(ref levelsRef) = h.GetLower(); + Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); + + int level; + int flevel; + for (; n < this.Last; ++n) + { + int ctx = ctxs[n]; level = levels[n]; flevel = absLevels[n]; cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; - if (n < 15) - { - int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = ctxs[n]; - int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); - } - - return cost; + t = costs[n + 1].Costs[ctx]; } + // Last coefficient is always non-zero. + level = levels[n]; + flevel = absLevels[n]; + cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; + if (n < 15) { - int v; - for (; n < this.Last; ++n) - { - v = Math.Abs(this.Coeffs[n]); - int ctx = v >= 2 ? 2 : v; - cost += LevelCost(t.Costs, v); - t = costs[n + 1].Costs[ctx]; - } + int b = WebpConstants.Vp8EncBands[n + 1]; + int ctx = ctxs[n]; + int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); + } - // Last coefficient is always non-zero + return cost; + } + + { + int v; + for (; n < this.Last; ++n) + { v = Math.Abs(this.Coeffs[n]); + int ctx = v >= 2 ? 2 : v; cost += LevelCost(t.Costs, v); - if (n < 15) - { - int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = v == 1 ? 1 : 2; - int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); - } + t = costs[n + 1].Costs[ctx]; + } - return cost; + // Last coefficient is always non-zero + v = Math.Abs(this.Coeffs[n]); + cost += LevelCost(t.Costs, v); + if (n < 15) + { + int b = WebpConstants.Vp8EncBands[n + 1]; + int ctx = v == 1 ? 1 : 2; + int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); } + + return cost; } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int LevelCost(Span table, int level) - => WebpLookupTables.Vp8LevelFixedCosts[level] + table[level > WebpConstants.MaxVariableLevel ? WebpConstants.MaxVariableLevel : level]; + [MethodImpl(InliningOptions.ShortMethod)] + private static int LevelCost(Span table, int level) + => WebpLookupTables.Vp8LevelFixedCosts[level] + table[level > WebpConstants.MaxVariableLevel ? WebpConstants.MaxVariableLevel : level]; - private static int RecordStats(int bit, Vp8StatsArray statsArr, int idx) + private static int RecordStats(int bit, Vp8StatsArray statsArr, int idx) + { + // An overflow is inbound. Note we handle this at 0xfffe0000u instead of + // 0xffff0000u to make sure p + 1u does not overflow. + if (statsArr.Stats[idx] >= 0xfffe0000u) { - // An overflow is inbound. Note we handle this at 0xfffe0000u instead of - // 0xffff0000u to make sure p + 1u does not overflow. - if (statsArr.Stats[idx] >= 0xfffe0000u) - { - statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. - } + statsArr.Stats[idx] = ((statsArr.Stats[idx] + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. + } - // Record bit count (lower 16 bits) and increment total count (upper 16 bits). - statsArr.Stats[idx] += 0x00010000u + (uint)bit; + // Record bit count (lower 16 bits) and increment total count (upper 16 bits). + statsArr.Stats[idx] += 0x00010000u + (uint)bit; - return bit; - } + return bit; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs index c8026dcd12..ed67fecb3e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentHeader.cs @@ -1,45 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Segment features. +/// +internal class Vp8SegmentHeader { + private const int NumMbSegments = 4; + /// - /// Segment features. + /// Initializes a new instance of the class. /// - internal class Vp8SegmentHeader + public Vp8SegmentHeader() { - private const int NumMbSegments = 4; - - /// - /// Initializes a new instance of the class. - /// - public Vp8SegmentHeader() - { - this.Quantizer = new byte[NumMbSegments]; - this.FilterStrength = new byte[NumMbSegments]; - } - - public bool UseSegment { get; set; } - - /// - /// Gets or sets a value indicating whether to update the segment map or not. - /// - public bool UpdateMap { get; set; } - - /// - /// Gets or sets a value indicating whether to use delta values for quantizer and filter. - /// If this value is false, absolute values are used. - /// - public bool Delta { get; set; } - - /// - /// Gets quantization changes. - /// - public byte[] Quantizer { get; } - - /// - /// Gets the filter strength for segments. - /// - public byte[] FilterStrength { get; } + this.Quantizer = new byte[NumMbSegments]; + this.FilterStrength = new byte[NumMbSegments]; } + + public bool UseSegment { get; set; } + + /// + /// Gets or sets a value indicating whether to update the segment map or not. + /// + public bool UpdateMap { get; set; } + + /// + /// Gets or sets a value indicating whether to use delta values for quantizer and filter. + /// If this value is false, absolute values are used. + /// + public bool Delta { get; set; } + + /// + /// Gets quantization changes. + /// + public byte[] Quantizer { get; } + + /// + /// Gets the filter strength for segments. + /// + public byte[] FilterStrength { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs index 98b2af9852..5a48911206 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8SegmentInfo.cs @@ -1,87 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +internal class Vp8SegmentInfo { - internal class Vp8SegmentInfo - { - /// - /// Gets the quantization matrix y1. - /// + /// + /// Gets the quantization matrix y1. + /// #pragma warning disable SA1401 // Fields should be private - public Vp8Matrix Y1; + public Vp8Matrix Y1; - /// - /// Gets the quantization matrix y2. - /// - public Vp8Matrix Y2; + /// + /// Gets the quantization matrix y2. + /// + public Vp8Matrix Y2; - /// - /// Gets the quantization matrix uv. - /// - public Vp8Matrix Uv; + /// + /// Gets the quantization matrix uv. + /// + public Vp8Matrix Uv; #pragma warning restore SA1401 // Fields should be private - /// - /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. - /// - public int Alpha { get; set; } + /// + /// Gets or sets the quant-susceptibility, range [-127,127]. Zero is neutral. Lower values indicate a lower risk of blurriness. + /// + public int Alpha { get; set; } - /// - /// Gets or sets the filter-susceptibility, range [0,255]. - /// - public int Beta { get; set; } + /// + /// Gets or sets the filter-susceptibility, range [0,255]. + /// + public int Beta { get; set; } - /// - /// Gets or sets the final segment quantizer. - /// - public int Quant { get; set; } + /// + /// Gets or sets the final segment quantizer. + /// + public int Quant { get; set; } - /// - /// Gets or sets the final in-loop filtering strength. - /// - public int FStrength { get; set; } + /// + /// Gets or sets the final in-loop filtering strength. + /// + public int FStrength { get; set; } - /// - /// Gets or sets the max edge delta (for filtering strength). - /// - public int MaxEdge { get; set; } + /// + /// Gets or sets the max edge delta (for filtering strength). + /// + public int MaxEdge { get; set; } - /// - /// Gets or sets the penalty for using Intra4. - /// - public long I4Penalty { get; set; } + /// + /// Gets or sets the penalty for using Intra4. + /// + public long I4Penalty { get; set; } - /// - /// Gets or sets the minimum distortion required to trigger filtering record. - /// - public int MinDisto { get; set; } + /// + /// Gets or sets the minimum distortion required to trigger filtering record. + /// + public int MinDisto { get; set; } - public int LambdaI16 { get; set; } + public int LambdaI16 { get; set; } - public int LambdaI4 { get; set; } + public int LambdaI4 { get; set; } - public int TLambda { get; set; } + public int TLambda { get; set; } - public int LambdaUv { get; set; } + public int LambdaUv { get; set; } - public int LambdaMode { get; set; } + public int LambdaMode { get; set; } - public void StoreMaxDelta(Span dcs) + public void StoreMaxDelta(Span dcs) + { + // We look at the first three AC coefficients to determine what is the average + // delta between each sub-4x4 block. + int v0 = Math.Abs(dcs[1]); + int v1 = Math.Abs(dcs[2]); + int v2 = Math.Abs(dcs[4]); + int maxV = v1 > v0 ? v1 : v0; + maxV = v2 > maxV ? v2 : maxV; + if (maxV > this.MaxEdge) { - // We look at the first three AC coefficients to determine what is the average - // delta between each sub-4x4 block. - int v0 = Math.Abs(dcs[1]); - int v1 = Math.Abs(dcs[2]); - int v2 = Math.Abs(dcs[4]); - int maxV = v1 > v0 ? v1 : v0; - maxV = v2 > maxV ? v2 : maxV; - if (maxV > this.MaxEdge) - { - this.MaxEdge = maxV; - } + this.MaxEdge = maxV; } } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs index 52d8de28b1..dda921a7c7 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Stats.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8Stats { - internal class Vp8Stats + /// + /// Initializes a new instance of the class. + /// + public Vp8Stats() { - /// - /// Initializes a new instance of the class. - /// - public Vp8Stats() + this.Stats = new Vp8StatsArray[WebpConstants.NumCtx]; + for (int i = 0; i < WebpConstants.NumCtx; i++) { - this.Stats = new Vp8StatsArray[WebpConstants.NumCtx]; - for (int i = 0; i < WebpConstants.NumCtx; i++) - { - this.Stats[i] = new Vp8StatsArray(); - } + this.Stats[i] = new Vp8StatsArray(); } - - public Vp8StatsArray[] Stats { get; } } + + public Vp8StatsArray[] Stats { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs index 30294c3c7e..2fbba6996d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8StatsArray.cs @@ -1,15 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8StatsArray { - internal class Vp8StatsArray - { - /// - /// Initializes a new instance of the class. - /// - public Vp8StatsArray() => this.Stats = new uint[WebpConstants.NumProbas]; + /// + /// Initializes a new instance of the class. + /// + public Vp8StatsArray() => this.Stats = new uint[WebpConstants.NumProbas]; - public uint[] Stats { get; } - } + public uint[] Stats { get; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs index fc8b46f2b2..5b87d7914b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8TopSamples.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal class Vp8TopSamples { - internal class Vp8TopSamples - { - public byte[] Y { get; } = new byte[16]; + public byte[] Y { get; } = new byte[16]; - public byte[] U { get; } = new byte[8]; + public byte[] U { get; } = new byte[8]; - public byte[] V { get; } = new byte[8]; - } + public byte[] V { get; } = new byte[8]; } diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index e913e98989..95c97c45b3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,1370 +8,1369 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +/// +/// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp +/// +/// +/// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386 +/// +internal sealed class WebpLossyDecoder { /// - /// Decoder for lossy webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp + /// A bit reader for reading lossy webp streams. + /// + private readonly Vp8BitReader bitReader; + + /// + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// The global configuration. + /// + private readonly Configuration configuration; + + /// + /// Scratch buffer to reduce allocations. + /// + private readonly int[] scratch = new int[16]; + + /// + /// Another scratch buffer to reduce allocations. + /// + private readonly byte[] scratchBytes = new byte[4]; + + /// + /// Initializes a new instance of the class. /// - /// - /// The lossy specification can be found here: https://tools.ietf.org/html/rfc6386 - /// - internal sealed class WebpLossyDecoder + /// Bitreader to read from the stream. + /// Used for allocating memory during processing operations. + /// The configuration. + public WebpLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) { - /// - /// A bit reader for reading lossy webp streams. - /// - private readonly Vp8BitReader bitReader; - - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// Scratch buffer to reduce allocations. - /// - private readonly int[] scratch = new int[16]; - - /// - /// Another scratch buffer to reduce allocations. - /// - private readonly byte[] scratchBytes = new byte[4]; - - /// - /// Initializes a new instance of the class. - /// - /// Bitreader to read from the stream. - /// Used for allocating memory during processing operations. - /// The configuration. - public WebpLossyDecoder(Vp8BitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration) - { - this.bitReader = bitReader; - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - } - - /// - /// Decodes the lossless webp image from the stream. - /// - /// The pixel format. - /// The pixel buffer to store the decoded data. - /// The width of the image. - /// The height of the image. - /// Information about the image. - /// The ALPH chunk data. - public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info, IMemoryOwner alphaData) - where TPixel : unmanaged, IPixel - { - // Paragraph 9.2: color space and clamp type follow. - sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); - sbyte clampType = (sbyte)this.bitReader.ReadValue(1); - Vp8PictureHeader pictureHeader = new() - { - Width = (uint)width, - Height = (uint)height, - XScale = info.XScale, - YScale = info.YScale, - ColorSpace = colorSpace, - ClampType = clampType - }; - - // Paragraph 9.3: Parse the segment header. - Vp8Proba proba = new(); - Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); - - using (Vp8Decoder decoder = new(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba, this.memoryAllocator)) - { - Vp8Io io = InitializeVp8Io(decoder, pictureHeader); + this.bitReader = bitReader; + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + } + + /// + /// Decodes the lossless webp image from the stream. + /// + /// The pixel format. + /// The pixel buffer to store the decoded data. + /// The width of the image. + /// The height of the image. + /// Information about the image. + /// The ALPH chunk data. + public void Decode(Buffer2D pixels, int width, int height, WebpImageInfo info, IMemoryOwner alphaData) + where TPixel : unmanaged, IPixel + { + // Paragraph 9.2: color space and clamp type follow. + sbyte colorSpace = (sbyte)this.bitReader.ReadValue(1); + sbyte clampType = (sbyte)this.bitReader.ReadValue(1); + Vp8PictureHeader pictureHeader = new() + { + Width = (uint)width, + Height = (uint)height, + XScale = info.XScale, + YScale = info.YScale, + ColorSpace = colorSpace, + ClampType = clampType + }; + + // Paragraph 9.3: Parse the segment header. + Vp8Proba proba = new(); + Vp8SegmentHeader vp8SegmentHeader = this.ParseSegmentHeader(proba); + + using (Vp8Decoder decoder = new(info.Vp8FrameHeader, pictureHeader, vp8SegmentHeader, proba, this.memoryAllocator)) + { + Vp8Io io = InitializeVp8Io(decoder, pictureHeader); - // Paragraph 9.4: Parse the filter specs. - this.ParseFilterHeader(decoder); - decoder.PrecomputeFilterStrengths(); + // Paragraph 9.4: Parse the filter specs. + this.ParseFilterHeader(decoder); + decoder.PrecomputeFilterStrengths(); - // Paragraph 9.5: Parse partitions. - this.ParsePartitions(decoder); + // Paragraph 9.5: Parse partitions. + this.ParsePartitions(decoder); - // Paragraph 9.6: Dequantization Indices. - this.ParseDequantizationIndices(decoder); + // Paragraph 9.6: Dequantization Indices. + this.ParseDequantizationIndices(decoder); - // Ignore the value of update probabilities. - this.bitReader.ReadBool(); + // Ignore the value of update probabilities. + this.bitReader.ReadBool(); - // Paragraph 13.4: Parse probabilities. - this.ParseProbabilities(decoder); + // Paragraph 13.4: Parse probabilities. + this.ParseProbabilities(decoder); - // Decode image data. - this.ParseFrame(decoder, io); + // Decode image data. + this.ParseFrame(decoder, io); - if (info.Features?.Alpha == true) - { - using (AlphaDecoder alphaDecoder = new( - width, - height, - alphaData, - info.Features.AlphaChunkHeader, - this.memoryAllocator, - this.configuration)) - { - alphaDecoder.Decode(); - DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); - } - } - else + if (info.Features?.Alpha == true) + { + using (AlphaDecoder alphaDecoder = new( + width, + height, + alphaData, + info.Features.AlphaChunkHeader, + this.memoryAllocator, + this.configuration)) { - this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); + alphaDecoder.Decode(); + DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels, alphaDecoder.Alpha); } } + else + { + this.DecodePixelValues(width, height, decoder.Pixels.Memory.Span, pixels); + } } + } - private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels) - where TPixel : unmanaged, IPixel + private void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels) + where TPixel : unmanaged, IPixel + { + int widthMul3 = width * 3; + for (int y = 0; y < height; y++) { - int widthMul3 = width * 3; - for (int y = 0; y < height; y++) - { - Span row = pixelData.Slice(y * widthMul3, widthMul3); - Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); - PixelOperations.Instance.FromBgr24Bytes( - this.configuration, - row, - decodedPixelRow, - width); - } + Span row = pixelData.Slice(y * widthMul3, widthMul3); + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); + PixelOperations.Instance.FromBgr24Bytes( + this.configuration, + row, + decodedPixelRow, + width); } + } - private static void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels, IMemoryOwner alpha) - where TPixel : unmanaged, IPixel + private static void DecodePixelValues(int width, int height, Span pixelData, Buffer2D decodedPixels, IMemoryOwner alpha) + where TPixel : unmanaged, IPixel + { + TPixel color = default; + Span alphaSpan = alpha.Memory.Span; + Span pixelsBgr = MemoryMarshal.Cast(pixelData); + for (int y = 0; y < height; y++) { - TPixel color = default; - Span alphaSpan = alpha.Memory.Span; - Span pixelsBgr = MemoryMarshal.Cast(pixelData); - for (int y = 0; y < height; y++) + int yMulWidth = y * width; + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); + for (int x = 0; x < width; x++) { - int yMulWidth = y * width; - Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); - for (int x = 0; x < width; x++) - { - int offset = yMulWidth + x; - Bgr24 bgr = pixelsBgr[offset]; - color.FromBgra32(new Bgra32(bgr.R, bgr.G, bgr.B, alphaSpan[offset])); - decodedPixelRow[x] = color; - } + int offset = yMulWidth + x; + Bgr24 bgr = pixelsBgr[offset]; + color.FromBgra32(new Bgra32(bgr.R, bgr.G, bgr.B, alphaSpan[offset])); + decodedPixelRow[x] = color; } } + } - private void ParseFrame(Vp8Decoder dec, Vp8Io io) + private void ParseFrame(Vp8Decoder dec, Vp8Io io) + { + for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) { - for (dec.MbY = 0; dec.MbY < dec.BottomRightMbY; ++dec.MbY) + // Parse bitstream for this row. + long bitreaderIdx = dec.MbY & dec.NumPartsMinusOne; + Vp8BitReader bitreader = dec.Vp8BitReaders[bitreaderIdx]; + + // Parse intra mode mode row. + for (int mbX = 0; mbX < dec.MbWidth; ++mbX) { - // Parse bitstream for this row. - long bitreaderIdx = dec.MbY & dec.NumPartsMinusOne; - Vp8BitReader bitreader = dec.Vp8BitReaders[bitreaderIdx]; + this.ParseIntraMode(dec, mbX); + } - // Parse intra mode mode row. - for (int mbX = 0; mbX < dec.MbWidth; ++mbX) - { - this.ParseIntraMode(dec, mbX); - } + while (dec.MbX < dec.MbWidth) + { + this.DecodeMacroBlock(dec, bitreader); + ++dec.MbX; + } - while (dec.MbX < dec.MbWidth) - { - this.DecodeMacroBlock(dec, bitreader); - ++dec.MbX; - } + // Prepare for next scanline. + InitScanline(dec); - // Prepare for next scanline. - InitScanline(dec); + // Reconstruct, filter and emit the row. + this.ProcessRow(dec, io); + } + } - // Reconstruct, filter and emit the row. - this.ProcessRow(dec, io); - } + private void ParseIntraMode(Vp8Decoder dec, int mbX) + { + Vp8MacroBlockData block = dec.MacroBlockData[mbX]; + Span top = dec.IntraT.AsSpan(4 * mbX, 4); + byte[] left = dec.IntraL; + + if (dec.SegmentHeader.UpdateMap) + { + // Hardcoded tree parsing. + block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) == 0 + ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) + : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); + } + else + { + // default for intra + block.Segment = 0; } - private void ParseIntraMode(Vp8Decoder dec, int mbX) + if (dec.UseSkipProbability) { - Vp8MacroBlockData block = dec.MacroBlockData[mbX]; - Span top = dec.IntraT.AsSpan(4 * mbX, 4); - byte[] left = dec.IntraL; + block.Skip = this.bitReader.GetBit(dec.SkipProbability) == 1; + } - if (dec.SegmentHeader.UpdateMap) + block.IsI4x4 = this.bitReader.GetBit(145) == 0; + if (!block.IsI4x4) + { + // Hardcoded 16x16 intra-mode decision tree. + int yMode; + if (this.bitReader.GetBit(156) != 0) + { + if (this.bitReader.GetBit(128) != 0) + { + yMode = (int)IntraPredictionMode.TrueMotion; + } + else + { + yMode = (int)IntraPredictionMode.HPrediction; + } + } + else if (this.bitReader.GetBit(163) != 0) { - // Hardcoded tree parsing. - block.Segment = this.bitReader.GetBit((int)dec.Probabilities.Segments[0]) == 0 - ? (byte)this.bitReader.GetBit((int)dec.Probabilities.Segments[1]) - : (byte)(this.bitReader.GetBit((int)dec.Probabilities.Segments[2]) + 2); + yMode = (int)IntraPredictionMode.VPrediction; } else { - // default for intra - block.Segment = 0; + yMode = (int)IntraPredictionMode.DcPrediction; } - if (dec.UseSkipProbability) + block.Modes[0] = (byte)yMode; + for (int i = 0; i < left.Length; i++) { - block.Skip = this.bitReader.GetBit(dec.SkipProbability) == 1; + left[i] = (byte)yMode; + top[i] = (byte)yMode; } - - block.IsI4x4 = this.bitReader.GetBit(145) == 0; - if (!block.IsI4x4) + } + else + { + Span modes = block.Modes.AsSpan(); + for (int y = 0; y < 4; y++) { - // Hardcoded 16x16 intra-mode decision tree. - int yMode; - if (this.bitReader.GetBit(156) != 0) + int yMode = left[y]; + for (int x = 0; x < 4; x++) { - if (this.bitReader.GetBit(128) != 0) - { - yMode = (int)IntraPredictionMode.TrueMotion; - } - else + byte[] prob = WebpLookupTables.ModesProba[top[x], yMode]; + int i = WebpConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; + while (i > 0) { - yMode = (int)IntraPredictionMode.HPrediction; + i = WebpConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; } - } - else if (this.bitReader.GetBit(163) != 0) - { - yMode = (int)IntraPredictionMode.VPrediction; - } - else - { - yMode = (int)IntraPredictionMode.DcPrediction; - } - block.Modes[0] = (byte)yMode; - for (int i = 0; i < left.Length; i++) - { - left[i] = (byte)yMode; - top[i] = (byte)yMode; + yMode = -i; + top[x] = (byte)yMode; } - } - else - { - Span modes = block.Modes.AsSpan(); - for (int y = 0; y < 4; y++) - { - int yMode = left[y]; - for (int x = 0; x < 4; x++) - { - byte[] prob = WebpLookupTables.ModesProba[top[x], yMode]; - int i = WebpConstants.YModesIntra4[this.bitReader.GetBit(prob[0])]; - while (i > 0) - { - i = WebpConstants.YModesIntra4[(2 * i) + this.bitReader.GetBit(prob[i])]; - } - - yMode = -i; - top[x] = (byte)yMode; - } - top.CopyTo(modes); - modes = modes[4..]; - left[y] = (byte)yMode; - } + top.CopyTo(modes); + modes = modes[4..]; + left[y] = (byte)yMode; } + } + // Hardcoded UVMode decision tree. + if (this.bitReader.GetBit(142) == 0) + { // Hardcoded UVMode decision tree. - if (this.bitReader.GetBit(142) == 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 0; - } - else if (this.bitReader.GetBit(114) == 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 2; - } - else if (this.bitReader.GetBit(183) != 0) - { - // Hardcoded UVMode decision tree. - block.UvMode = 1; - } - else - { - // Hardcoded UVMode decision tree. - block.UvMode = 3; - } + block.UvMode = 0; } - - private static void InitScanline(Vp8Decoder dec) + else if (this.bitReader.GetBit(114) == 0) { - Vp8MacroBlock left = dec.LeftMacroBlock; - left.NoneZeroAcDcCoeffs = 0; - left.NoneZeroDcCoeffs = 0; - for (int i = 0; i < dec.IntraL.Length; i++) - { - dec.IntraL[i] = 0; - } - - dec.MbX = 0; + // Hardcoded UVMode decision tree. + block.UvMode = 2; + } + else if (this.bitReader.GetBit(183) != 0) + { + // Hardcoded UVMode decision tree. + block.UvMode = 1; + } + else + { + // Hardcoded UVMode decision tree. + block.UvMode = 3; } + } - private void ProcessRow(Vp8Decoder dec, Vp8Io io) + private static void InitScanline(Vp8Decoder dec) + { + Vp8MacroBlock left = dec.LeftMacroBlock; + left.NoneZeroAcDcCoeffs = 0; + left.NoneZeroDcCoeffs = 0; + for (int i = 0; i < dec.IntraL.Length; i++) { - this.ReconstructRow(dec); - FinishRow(dec, io); + dec.IntraL[i] = 0; } - private void ReconstructRow(Vp8Decoder dec) + dec.MbX = 0; + } + + private void ProcessRow(Vp8Decoder dec, Vp8Io io) + { + this.ReconstructRow(dec); + FinishRow(dec, io); + } + + private void ReconstructRow(Vp8Decoder dec) + { + int mby = dec.MbY; + const int yOff = (WebpConstants.Bps * 1) + 8; + const int uOff = yOff + (WebpConstants.Bps * 16) + WebpConstants.Bps; + const int vOff = uOff + 16; + + Span yuv = dec.YuvBuffer.Memory.Span; + Span yDst = yuv[yOff..]; + Span uDst = yuv[uOff..]; + Span vDst = yuv[vOff..]; + + // Initialize left-most block. + int end = 16 * WebpConstants.Bps; + for (int i = 0; i < end; i += WebpConstants.Bps) { - int mby = dec.MbY; - const int yOff = (WebpConstants.Bps * 1) + 8; - const int uOff = yOff + (WebpConstants.Bps * 16) + WebpConstants.Bps; - const int vOff = uOff + 16; + yuv[i - 1 + yOff] = 129; + } - Span yuv = dec.YuvBuffer.Memory.Span; - Span yDst = yuv[yOff..]; - Span uDst = yuv[uOff..]; - Span vDst = yuv[vOff..]; + end = 8 * WebpConstants.Bps; + for (int i = 0; i < end; i += WebpConstants.Bps) + { + yuv[i - 1 + uOff] = 129; + yuv[i - 1 + vOff] = 129; + } - // Initialize left-most block. - int end = 16 * WebpConstants.Bps; - for (int i = 0; i < end; i += WebpConstants.Bps) + // Init top-left sample on left column too. + if (mby > 0) + { + yuv[yOff - 1 - WebpConstants.Bps] = yuv[uOff - 1 - WebpConstants.Bps] = yuv[vOff - 1 - WebpConstants.Bps] = 129; + } + else + { + // We only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + Span tmp = yuv.Slice(yOff - WebpConstants.Bps - 1, 16 + 4 + 1); + for (int i = 0; i < tmp.Length; i++) { - yuv[i - 1 + yOff] = 129; + tmp[i] = 127; } - end = 8 * WebpConstants.Bps; - for (int i = 0; i < end; i += WebpConstants.Bps) + tmp = yuv.Slice(uOff - WebpConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; i++) { - yuv[i - 1 + uOff] = 129; - yuv[i - 1 + vOff] = 129; + tmp[i] = 127; } - // Init top-left sample on left column too. - if (mby > 0) + tmp = yuv.Slice(vOff - WebpConstants.Bps - 1, 8 + 1); + for (int i = 0; i < tmp.Length; i++) { - yuv[yOff - 1 - WebpConstants.Bps] = yuv[uOff - 1 - WebpConstants.Bps] = yuv[vOff - 1 - WebpConstants.Bps] = 129; + tmp[i] = 127; } - else - { - // We only need to do this init once at block (0,0). - // Afterward, it remains valid for the whole topmost row. - Span tmp = yuv.Slice(yOff - WebpConstants.Bps - 1, 16 + 4 + 1); - for (int i = 0; i < tmp.Length; i++) - { - tmp[i] = 127; - } + } + + // Reconstruct one row. + for (int mbx = 0; mbx < dec.MbWidth; mbx++) + { + Vp8MacroBlockData block = dec.MacroBlockData[mbx]; - tmp = yuv.Slice(uOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; i++) + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mbx > 0) + { + for (int i = -1; i < 16; i++) { - tmp[i] = 127; + int srcIdx = (i * WebpConstants.Bps) + 12 + yOff; + int dstIdx = (i * WebpConstants.Bps) - 4 + yOff; + yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); } - tmp = yuv.Slice(vOff - WebpConstants.Bps - 1, 8 + 1); - for (int i = 0; i < tmp.Length; i++) + for (int i = -1; i < 8; i++) { - tmp[i] = 127; + int srcIdx = (i * WebpConstants.Bps) + 4 + uOff; + int dstIdx = (i * WebpConstants.Bps) - 4 + uOff; + yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); + srcIdx = (i * WebpConstants.Bps) + 4 + vOff; + dstIdx = (i * WebpConstants.Bps) - 4 + vOff; + yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); } } - // Reconstruct one row. - for (int mbx = 0; mbx < dec.MbWidth; mbx++) + // Bring top samples into the cache. + Vp8TopSamples topYuv = dec.YuvTopSamples[mbx]; + short[] coeffs = block.Coeffs; + uint bits = block.NonZeroY; + if (mby > 0) { - Vp8MacroBlockData block = dec.MacroBlockData[mbx]; + topYuv.Y.CopyTo(yuv[(yOff - WebpConstants.Bps)..]); + topYuv.U.CopyTo(yuv[(uOff - WebpConstants.Bps)..]); + topYuv.V.CopyTo(yuv[(vOff - WebpConstants.Bps)..]); + } - // Rotate in the left samples from previously decoded block. We move four - // pixels at a time for alignment reason, and because of in-loop filter. - if (mbx > 0) + // Predict and add residuals. + if (block.IsI4x4) + { + Span topRight = yuv[(yOff - WebpConstants.Bps + 16)..]; + if (mby > 0) { - for (int i = -1; i < 16; i++) + if (mbx >= dec.MbWidth - 1) { - int srcIdx = (i * WebpConstants.Bps) + 12 + yOff; - int dstIdx = (i * WebpConstants.Bps) - 4 + yOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); + // On rightmost border. + byte topYuv15 = topYuv.Y[15]; + topRight[0] = topYuv15; + topRight[1] = topYuv15; + topRight[2] = topYuv15; + topRight[3] = topYuv15; } - - for (int i = -1; i < 8; i++) + else { - int srcIdx = (i * WebpConstants.Bps) + 4 + uOff; - int dstIdx = (i * WebpConstants.Bps) - 4 + uOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); - srcIdx = (i * WebpConstants.Bps) + 4 + vOff; - dstIdx = (i * WebpConstants.Bps) - 4 + vOff; - yuv.Slice(srcIdx, 4).CopyTo(yuv[dstIdx..]); + dec.YuvTopSamples[mbx + 1].Y.AsSpan(0, 4).CopyTo(topRight); } } - // Bring top samples into the cache. - Vp8TopSamples topYuv = dec.YuvTopSamples[mbx]; - short[] coeffs = block.Coeffs; - uint bits = block.NonZeroY; - if (mby > 0) - { - topYuv.Y.CopyTo(yuv[(yOff - WebpConstants.Bps)..]); - topYuv.U.CopyTo(yuv[(uOff - WebpConstants.Bps)..]); - topYuv.V.CopyTo(yuv[(vOff - WebpConstants.Bps)..]); - } - - // Predict and add residuals. - if (block.IsI4x4) - { - Span topRight = yuv[(yOff - WebpConstants.Bps + 16)..]; - if (mby > 0) - { - if (mbx >= dec.MbWidth - 1) - { - // On rightmost border. - byte topYuv15 = topYuv.Y[15]; - topRight[0] = topYuv15; - topRight[1] = topYuv15; - topRight[2] = topYuv15; - topRight[3] = topYuv15; - } - else - { - dec.YuvTopSamples[mbx + 1].Y.AsSpan(0, 4).CopyTo(topRight); - } - } - - // Replicate the top-right pixels below. - Span topRightUint = MemoryMarshal.Cast(yuv[(yOff - WebpConstants.Bps + 16)..]); - topRightUint[WebpConstants.Bps] = topRightUint[2 * WebpConstants.Bps] = topRightUint[3 * WebpConstants.Bps] = topRightUint[0]; + // Replicate the top-right pixels below. + Span topRightUint = MemoryMarshal.Cast(yuv[(yOff - WebpConstants.Bps + 16)..]); + topRightUint[WebpConstants.Bps] = topRightUint[2 * WebpConstants.Bps] = topRightUint[3 * WebpConstants.Bps] = topRightUint[0]; - // Predict and add residuals for all 4x4 blocks in turn. - for (int n = 0; n < 16; ++n, bits <<= 2) - { - int offset = yOff + WebpConstants.Scan[n]; - Span dst = yuv[offset..]; - switch (block.Modes[n]) - { - case 0: - LossyUtils.DC4(dst, yuv, offset); - break; - case 1: - LossyUtils.TM4(dst, yuv, offset); - break; - case 2: - LossyUtils.VE4(dst, yuv, offset, this.scratchBytes); - break; - case 3: - LossyUtils.HE4(dst, yuv, offset); - break; - case 4: - LossyUtils.RD4(dst, yuv, offset); - break; - case 5: - LossyUtils.VR4(dst, yuv, offset); - break; - case 6: - LossyUtils.LD4(dst, yuv, offset); - break; - case 7: - LossyUtils.VL4(dst, yuv, offset); - break; - case 8: - LossyUtils.HD4(dst, yuv, offset); - break; - case 9: - LossyUtils.HU4(dst, yuv, offset); - break; - } - - DoTransform(bits, coeffs.AsSpan(n * 16), dst, this.scratch); - } - } - else + // Predict and add residuals for all 4x4 blocks in turn. + for (int n = 0; n < 16; ++n, bits <<= 2) { - // 16x16 - switch (CheckMode(mbx, mby, block.Modes[0])) + int offset = yOff + WebpConstants.Scan[n]; + Span dst = yuv[offset..]; + switch (block.Modes[n]) { case 0: - LossyUtils.DC16(yDst, yuv, yOff); + LossyUtils.DC4(dst, yuv, offset); break; case 1: - LossyUtils.TM16(yDst, yuv, yOff); + LossyUtils.TM4(dst, yuv, offset); break; case 2: - LossyUtils.VE16(yDst, yuv, yOff); + LossyUtils.VE4(dst, yuv, offset, this.scratchBytes); break; case 3: - LossyUtils.HE16(yDst, yuv, yOff); + LossyUtils.HE4(dst, yuv, offset); break; case 4: - LossyUtils.DC16NoTop(yDst, yuv, yOff); + LossyUtils.RD4(dst, yuv, offset); break; case 5: - LossyUtils.DC16NoLeft(yDst, yuv, yOff); + LossyUtils.VR4(dst, yuv, offset); break; case 6: - LossyUtils.DC16NoTopLeft(yDst); + LossyUtils.LD4(dst, yuv, offset); + break; + case 7: + LossyUtils.VL4(dst, yuv, offset); + break; + case 8: + LossyUtils.HD4(dst, yuv, offset); + break; + case 9: + LossyUtils.HU4(dst, yuv, offset); break; } - if (bits != 0) - { - for (int n = 0; n < 16; ++n, bits <<= 2) - { - DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], this.scratch); - } - } + DoTransform(bits, coeffs.AsSpan(n * 16), dst, this.scratch); } - - // Chroma - uint bitsUv = block.NonZeroUv; - switch (CheckMode(mbx, mby, block.UvMode)) + } + else + { + // 16x16 + switch (CheckMode(mbx, mby, block.Modes[0])) { case 0: - LossyUtils.DC8uv(uDst, yuv, uOff); - LossyUtils.DC8uv(vDst, yuv, vOff); + LossyUtils.DC16(yDst, yuv, yOff); break; case 1: - LossyUtils.TM8uv(uDst, yuv, uOff); - LossyUtils.TM8uv(vDst, yuv, vOff); + LossyUtils.TM16(yDst, yuv, yOff); break; case 2: - LossyUtils.VE8uv(uDst, yuv, uOff); - LossyUtils.VE8uv(vDst, yuv, vOff); + LossyUtils.VE16(yDst, yuv, yOff); break; case 3: - LossyUtils.HE8uv(uDst, yuv, uOff); - LossyUtils.HE8uv(vDst, yuv, vOff); + LossyUtils.HE16(yDst, yuv, yOff); break; case 4: - LossyUtils.DC8uvNoTop(uDst, yuv, uOff); - LossyUtils.DC8uvNoTop(vDst, yuv, vOff); + LossyUtils.DC16NoTop(yDst, yuv, yOff); break; case 5: - LossyUtils.DC8uvNoLeft(uDst, yuv, uOff); - LossyUtils.DC8uvNoLeft(vDst, yuv, vOff); + LossyUtils.DC16NoLeft(yDst, yuv, yOff); break; case 6: - LossyUtils.DC8uvNoTopLeft(uDst); - LossyUtils.DC8uvNoTopLeft(vDst); + LossyUtils.DC16NoTopLeft(yDst); break; } - DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, this.scratch); - DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, this.scratch); - - // Stash away top samples for next block. - if (mby < dec.MbHeight - 1) - { - yDst.Slice(15 * WebpConstants.Bps, 16).CopyTo(topYuv.Y); - uDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.U); - vDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.V); - } - - // Transfer reconstructed samples from yuv_buffer cache to final destination. - Span yOut = dec.CacheY.Memory.Span[(dec.CacheYOffset + (mbx * 16))..]; - Span uOut = dec.CacheU.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; - Span vOut = dec.CacheV.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; - for (int j = 0; j < 16; j++) - { - yDst.Slice(j * WebpConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut[(j * dec.CacheYStride)..]); - } - - for (int j = 0; j < 8; j++) + if (bits != 0) { - int jUvStride = j * dec.CacheUvStride; - uDst.Slice(j * WebpConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut[jUvStride..]); - vDst.Slice(j * WebpConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut[jUvStride..]); + for (int n = 0; n < 16; ++n, bits <<= 2) + { + DoTransform(bits, coeffs.AsSpan(n * 16), yDst[WebpConstants.Scan[n]..], this.scratch); + } } } - } - private static void FilterRow(Vp8Decoder dec) - { - int mby = dec.MbY; - for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) + // Chroma + uint bitsUv = block.NonZeroUv; + switch (CheckMode(mbx, mby, block.UvMode)) { - DoFilter(dec, mbx, mby); + case 0: + LossyUtils.DC8uv(uDst, yuv, uOff); + LossyUtils.DC8uv(vDst, yuv, vOff); + break; + case 1: + LossyUtils.TM8uv(uDst, yuv, uOff); + LossyUtils.TM8uv(vDst, yuv, vOff); + break; + case 2: + LossyUtils.VE8uv(uDst, yuv, uOff); + LossyUtils.VE8uv(vDst, yuv, vOff); + break; + case 3: + LossyUtils.HE8uv(uDst, yuv, uOff); + LossyUtils.HE8uv(vDst, yuv, vOff); + break; + case 4: + LossyUtils.DC8uvNoTop(uDst, yuv, uOff); + LossyUtils.DC8uvNoTop(vDst, yuv, vOff); + break; + case 5: + LossyUtils.DC8uvNoLeft(uDst, yuv, uOff); + LossyUtils.DC8uvNoLeft(vDst, yuv, vOff); + break; + case 6: + LossyUtils.DC8uvNoTopLeft(uDst); + LossyUtils.DC8uvNoTopLeft(vDst); + break; } - } - private static void DoFilter(Vp8Decoder dec, int mbx, int mby) - { - int yBps = dec.CacheYStride; - Vp8FilterInfo filterInfo = dec.FilterInfo[mbx]; - int iLevel = filterInfo.InnerLevel; - int limit = filterInfo.Limit; + DoUVTransform(bitsUv, coeffs.AsSpan(16 * 16), uDst, this.scratch); + DoUVTransform(bitsUv >> 8, coeffs.AsSpan(20 * 16), vDst, this.scratch); - if (limit == 0) + // Stash away top samples for next block. + if (mby < dec.MbHeight - 1) { - return; + yDst.Slice(15 * WebpConstants.Bps, 16).CopyTo(topYuv.Y); + uDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.U); + vDst.Slice(7 * WebpConstants.Bps, 8).CopyTo(topYuv.V); } - if (dec.Filter == LoopFilter.Simple) + // Transfer reconstructed samples from yuv_buffer cache to final destination. + Span yOut = dec.CacheY.Memory.Span[(dec.CacheYOffset + (mbx * 16))..]; + Span uOut = dec.CacheU.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; + Span vOut = dec.CacheV.Memory.Span[(dec.CacheUvOffset + (mbx * 8))..]; + for (int j = 0; j < 16; j++) { - int offset = dec.CacheYOffset + (mbx * 16); - if (mbx > 0) - { - LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); - } - - if (mby > 0) - { - LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); - } + yDst.Slice(j * WebpConstants.Bps, Math.Min(16, yOut.Length)).CopyTo(yOut[(j * dec.CacheYStride)..]); } - else if (dec.Filter == LoopFilter.Complex) - { - int uvBps = dec.CacheUvStride; - int yOffset = dec.CacheYOffset + (mbx * 16); - int uvOffset = dec.CacheUvOffset + (mbx * 8); - int hevThresh = filterInfo.HighEdgeVarianceThreshold; - if (mbx > 0) - { - LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } - - if (filterInfo.UseInnerFiltering) - { - LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); - } - - if (mby > 0) - { - LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); - LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); - } - if (filterInfo.UseInnerFiltering) - { - LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); - LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); - } + for (int j = 0; j < 8; j++) + { + int jUvStride = j * dec.CacheUvStride; + uDst.Slice(j * WebpConstants.Bps, Math.Min(8, uOut.Length)).CopyTo(uOut[jUvStride..]); + vDst.Slice(j * WebpConstants.Bps, Math.Min(8, vOut.Length)).CopyTo(vOut[jUvStride..]); } } + } - private static void FinishRow(Vp8Decoder dec, Vp8Io io) + private static void FilterRow(Vp8Decoder dec) + { + int mby = dec.MbY; + for (int mbx = dec.TopLeftMbX; mbx < dec.BottomRightMbX; ++mbx) { - int extraYRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; - int ySize = extraYRows * dec.CacheYStride; - int uvSize = extraYRows / 2 * dec.CacheUvStride; - Span yDst = dec.CacheY.Memory.Span; - Span uDst = dec.CacheU.Memory.Span; - Span vDst = dec.CacheV.Memory.Span; - int mby = dec.MbY; - bool isFirstRow = mby == 0; - bool isLastRow = mby >= dec.BottomRightMbY - 1; - bool filterRow = dec.Filter != LoopFilter.None && dec.MbY >= dec.TopLeftMbY && dec.MbY <= dec.BottomRightMbY; + DoFilter(dec, mbx, mby); + } + } - if (filterRow) - { - FilterRow(dec); - } + private static void DoFilter(Vp8Decoder dec, int mbx, int mby) + { + int yBps = dec.CacheYStride; + Vp8FilterInfo filterInfo = dec.FilterInfo[mbx]; + int iLevel = filterInfo.InnerLevel; + int limit = filterInfo.Limit; - int yStart = mby * 16; - int yEnd = (mby + 1) * 16; - if (!isFirstRow) - { - yStart -= extraYRows; - io.Y = yDst; - io.U = uDst; - io.V = vDst; - } - else - { - io.Y = dec.CacheY.Memory.Span[dec.CacheYOffset..]; - io.U = dec.CacheU.Memory.Span[dec.CacheUvOffset..]; - io.V = dec.CacheV.Memory.Span[dec.CacheUvOffset..]; - } + if (limit == 0) + { + return; + } - if (!isLastRow) + if (dec.Filter == LoopFilter.Simple) + { + int offset = dec.CacheYOffset + (mbx * 16); + if (mbx > 0) { - yEnd -= extraYRows; + LossyUtils.SimpleHFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } - if (yEnd > io.Height) + if (filterInfo.UseInnerFiltering) { - yEnd = io.Height; // make sure we don't overflow on last row. + LossyUtils.SimpleHFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } - if (yStart < yEnd) + if (mby > 0) { - io.MbY = yStart; - io.MbW = io.Width; - io.MbH = yEnd - yStart; - EmitRgb(dec, io); + LossyUtils.SimpleVFilter16(dec.CacheY.Memory.Span, offset, yBps, limit + 4); } - // Rotate top samples if needed. - if (!isLastRow) + if (filterInfo.UseInnerFiltering) { - yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.Memory.Span); - uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.Memory.Span); - vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.Memory.Span); + LossyUtils.SimpleVFilter16i(dec.CacheY.Memory.Span, offset, yBps, limit); } } - - private static int EmitRgb(Vp8Decoder dec, Vp8Io io) - { - Span buf = dec.Pixels.Memory.Span; - int numLinesOut = io.MbH; // a priori guess. - Span curY = io.Y; - Span curU = io.U; - Span curV = io.V; - Span tmpYBuffer = dec.TmpYBuffer.Memory.Span; - Span tmpUBuffer = dec.TmpUBuffer.Memory.Span; - Span tmpVBuffer = dec.TmpVBuffer.Memory.Span; - Span topU = tmpUBuffer; - Span topV = tmpVBuffer; - const int bpp = 3; - int bufferStride = bpp * io.Width; - int dstStartIdx = io.MbY * bufferStride; - Span dst = buf[dstStartIdx..]; - int yEnd = io.MbY + io.MbH; - int mbw = io.MbW; - int uvw = (mbw + 1) / 2; - int y = io.MbY; - byte[] uvBuffer = new byte[(14 * 32) + 15]; - - if (y == 0) - { - // First line is special cased. We mirror the u/v samples at boundary. - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw, uvBuffer); - } - else + else if (dec.Filter == LoopFilter.Complex) + { + int uvBps = dec.CacheUvStride; + int yOffset = dec.CacheYOffset + (mbx * 16); + int uvOffset = dec.CacheUvOffset + (mbx * 8); + int hevThresh = filterInfo.HighEdgeVarianceThreshold; + if (mbx > 0) { - // We can finish the left-over line from previous call. - YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf[(dstStartIdx - bufferStride)..], dst, mbw, uvBuffer); - numLinesOut++; + LossyUtils.HFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.HFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } - // Loop over each output pairs of row. - int bufferStride2 = 2 * bufferStride; - int ioStride2 = 2 * io.YStride; - for (; y + 2 < yEnd; y += 2) + if (filterInfo.UseInnerFiltering) { - topU = curU; - topV = curV; - curU = curU[io.UvStride..]; - curV = curV[io.UvStride..]; - YuvConversion.UpSample(curY[io.YStride..], curY[ioStride2..], topU, topV, curU, curV, dst[bufferStride..], dst[bufferStride2..], mbw, uvBuffer); - curY = curY[ioStride2..]; - dst = dst[bufferStride2..]; + LossyUtils.HFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.HFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); } - // Move to last row. - curY = curY[io.YStride..]; - if (yEnd < io.Height) + if (mby > 0) { - // Save the unfinished samples for next call (as we're not done yet). - curY[..mbw].CopyTo(tmpYBuffer); - curU[..uvw].CopyTo(tmpUBuffer); - curV[..uvw].CopyTo(tmpVBuffer); - - // The upsampler leaves a row unfinished behind (except for the very last row). - numLinesOut--; + LossyUtils.VFilter16(dec.CacheY.Memory.Span, yOffset, yBps, limit + 4, iLevel, hevThresh); + LossyUtils.VFilter8(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit + 4, iLevel, hevThresh); } - else + + if (filterInfo.UseInnerFiltering) { - // Process the very last row of even-sized picture. - if ((yEnd & 1) == 0) - { - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst[bufferStride..], default, mbw, uvBuffer); - } + LossyUtils.VFilter16i(dec.CacheY.Memory.Span, yOffset, yBps, limit, iLevel, hevThresh); + LossyUtils.VFilter8i(dec.CacheU.Memory.Span, dec.CacheV.Memory.Span, uvOffset, uvBps, limit, iLevel, hevThresh); } + } + } - return numLinesOut; + private static void FinishRow(Vp8Decoder dec, Vp8Io io) + { + int extraYRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; + int ySize = extraYRows * dec.CacheYStride; + int uvSize = extraYRows / 2 * dec.CacheUvStride; + Span yDst = dec.CacheY.Memory.Span; + Span uDst = dec.CacheU.Memory.Span; + Span vDst = dec.CacheV.Memory.Span; + int mby = dec.MbY; + bool isFirstRow = mby == 0; + bool isLastRow = mby >= dec.BottomRightMbY - 1; + bool filterRow = dec.Filter != LoopFilter.None && dec.MbY >= dec.TopLeftMbY && dec.MbY <= dec.BottomRightMbY; + + if (filterRow) + { + FilterRow(dec); } - private static void DoTransform(uint bits, Span src, Span dst, Span scratch) + int yStart = mby * 16; + int yEnd = (mby + 1) * 16; + if (!isFirstRow) { - switch (bits >> 30) - { - case 3: - LossyUtils.TransformOne(src, dst, scratch); - break; - case 2: - LossyUtils.TransformAc3(src, dst); - break; - case 1: - LossyUtils.TransformDc(src, dst); - break; - } + yStart -= extraYRows; + io.Y = yDst; + io.U = uDst; + io.V = vDst; + } + else + { + io.Y = dec.CacheY.Memory.Span[dec.CacheYOffset..]; + io.U = dec.CacheU.Memory.Span[dec.CacheUvOffset..]; + io.V = dec.CacheV.Memory.Span[dec.CacheUvOffset..]; } - private static void DoUVTransform(uint bits, Span src, Span dst, Span scratch) + if (!isLastRow) { - // any non-zero coeff at all? - if ((bits & 0xff) > 0) - { - // any non-zero AC coefficient? - if ((bits & 0xaa) > 0) - { - LossyUtils.TransformUv(src, dst, scratch); // note we don't use the AC3 variant for U/V. - } - else - { - LossyUtils.TransformDcuv(src, dst); - } + yEnd -= extraYRows; + } + + if (yEnd > io.Height) + { + yEnd = io.Height; // make sure we don't overflow on last row. + } + + if (yStart < yEnd) + { + io.MbY = yStart; + io.MbW = io.Width; + io.MbH = yEnd - yStart; + EmitRgb(dec, io); + } + + // Rotate top samples if needed. + if (!isLastRow) + { + yDst.Slice(16 * dec.CacheYStride, ySize).CopyTo(dec.CacheY.Memory.Span); + uDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheU.Memory.Span); + vDst.Slice(8 * dec.CacheUvStride, uvSize).CopyTo(dec.CacheV.Memory.Span); + } + } + + private static int EmitRgb(Vp8Decoder dec, Vp8Io io) + { + Span buf = dec.Pixels.Memory.Span; + int numLinesOut = io.MbH; // a priori guess. + Span curY = io.Y; + Span curU = io.U; + Span curV = io.V; + Span tmpYBuffer = dec.TmpYBuffer.Memory.Span; + Span tmpUBuffer = dec.TmpUBuffer.Memory.Span; + Span tmpVBuffer = dec.TmpVBuffer.Memory.Span; + Span topU = tmpUBuffer; + Span topV = tmpVBuffer; + const int bpp = 3; + int bufferStride = bpp * io.Width; + int dstStartIdx = io.MbY * bufferStride; + Span dst = buf[dstStartIdx..]; + int yEnd = io.MbY + io.MbH; + int mbw = io.MbW; + int uvw = (mbw + 1) / 2; + int y = io.MbY; + byte[] uvBuffer = new byte[(14 * 32) + 15]; + + if (y == 0) + { + // First line is special cased. We mirror the u/v samples at boundary. + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw, uvBuffer); + } + else + { + // We can finish the left-over line from previous call. + YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf[(dstStartIdx - bufferStride)..], dst, mbw, uvBuffer); + numLinesOut++; + } + + // Loop over each output pairs of row. + int bufferStride2 = 2 * bufferStride; + int ioStride2 = 2 * io.YStride; + for (; y + 2 < yEnd; y += 2) + { + topU = curU; + topV = curV; + curU = curU[io.UvStride..]; + curV = curV[io.UvStride..]; + YuvConversion.UpSample(curY[io.YStride..], curY[ioStride2..], topU, topV, curU, curV, dst[bufferStride..], dst[bufferStride2..], mbw, uvBuffer); + curY = curY[ioStride2..]; + dst = dst[bufferStride2..]; + } + + // Move to last row. + curY = curY[io.YStride..]; + if (yEnd < io.Height) + { + // Save the unfinished samples for next call (as we're not done yet). + curY[..mbw].CopyTo(tmpYBuffer); + curU[..uvw].CopyTo(tmpUBuffer); + curV[..uvw].CopyTo(tmpVBuffer); + + // The upsampler leaves a row unfinished behind (except for the very last row). + numLinesOut--; + } + else + { + // Process the very last row of even-sized picture. + if ((yEnd & 1) == 0) + { + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst[bufferStride..], default, mbw, uvBuffer); } } - private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) + return numLinesOut; + } + + private static void DoTransform(uint bits, Span src, Span dst, Span scratch) + { + switch (bits >> 30) { - Vp8MacroBlock left = dec.LeftMacroBlock; - Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; - Vp8MacroBlockData blockData = dec.CurrentBlockData; - bool skip = dec.UseSkipProbability && blockData.Skip; + case 3: + LossyUtils.TransformOne(src, dst, scratch); + break; + case 2: + LossyUtils.TransformAc3(src, dst); + break; + case 1: + LossyUtils.TransformDc(src, dst); + break; + } + } - if (!skip) + private static void DoUVTransform(uint bits, Span src, Span dst, Span scratch) + { + // any non-zero coeff at all? + if ((bits & 0xff) > 0) + { + // any non-zero AC coefficient? + if ((bits & 0xaa) > 0) { - skip = this.ParseResiduals(dec, bitreader, macroBlock); + LossyUtils.TransformUv(src, dst, scratch); // note we don't use the AC3 variant for U/V. } else { - left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0; - if (!blockData.IsI4x4) - { - left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0; - } - - blockData.NonZeroY = 0; - blockData.NonZeroUv = 0; + LossyUtils.TransformDcuv(src, dst); } + } + } + + private void DecodeMacroBlock(Vp8Decoder dec, Vp8BitReader bitreader) + { + Vp8MacroBlock left = dec.LeftMacroBlock; + Vp8MacroBlock macroBlock = dec.CurrentMacroBlock; + Vp8MacroBlockData blockData = dec.CurrentBlockData; + bool skip = dec.UseSkipProbability && blockData.Skip; - // Store filter info. - if (dec.Filter != LoopFilter.None) + if (!skip) + { + skip = this.ParseResiduals(dec, bitreader, macroBlock); + } + else + { + left.NoneZeroAcDcCoeffs = macroBlock.NoneZeroAcDcCoeffs = 0; + if (!blockData.IsI4x4) { - Vp8FilterInfo precomputedFilterInfo = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; - dec.FilterInfo[dec.MbX] = (Vp8FilterInfo)precomputedFilterInfo.DeepClone(); - dec.FilterInfo[dec.MbX].UseInnerFiltering |= !skip; + left.NoneZeroDcCoeffs = macroBlock.NoneZeroDcCoeffs = 0; } + + blockData.NonZeroY = 0; + blockData.NonZeroUv = 0; } - private bool ParseResiduals(Vp8Decoder dec, Vp8BitReader br, Vp8MacroBlock mb) + // Store filter info. + if (dec.Filter != LoopFilter.None) { - uint nonZeroY = 0; - uint nonZeroUv = 0; - int first; - int dstOffset = 0; - Vp8MacroBlockData block = dec.CurrentBlockData; - Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; - Vp8BandProbas[][] bands = dec.Probabilities.BandsPtr; - Vp8BandProbas[] acProba; - Vp8MacroBlock leftMb = dec.LeftMacroBlock; - short[] dst = block.Coeffs; - for (int i = 0; i < dst.Length; i++) - { - dst[i] = 0; - } + Vp8FilterInfo precomputedFilterInfo = dec.FilterStrength[blockData.Segment, blockData.IsI4x4 ? 1 : 0]; + dec.FilterInfo[dec.MbX] = (Vp8FilterInfo)precomputedFilterInfo.DeepClone(); + dec.FilterInfo[dec.MbX].UseInnerFiltering |= !skip; + } + } - if (block.IsI4x4) + private bool ParseResiduals(Vp8Decoder dec, Vp8BitReader br, Vp8MacroBlock mb) + { + uint nonZeroY = 0; + uint nonZeroUv = 0; + int first; + int dstOffset = 0; + Vp8MacroBlockData block = dec.CurrentBlockData; + Vp8QuantMatrix q = dec.DeQuantMatrices[block.Segment]; + Vp8BandProbas[][] bands = dec.Probabilities.BandsPtr; + Vp8BandProbas[] acProba; + Vp8MacroBlock leftMb = dec.LeftMacroBlock; + short[] dst = block.Coeffs; + for (int i = 0; i < dst.Length; i++) + { + dst[i] = 0; + } + + if (block.IsI4x4) + { + first = 0; + acProba = bands[3]; + } + else + { + // Parse DC + short[] dc = new short[16]; + int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); + int nz = GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); + mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); + if (nz > 1) { - first = 0; - acProba = bands[3]; + // More than just the DC -> perform the full transform. + LossyUtils.TransformWht(dc, dst, this.scratch); } else { - // Parse DC - short[] dc = new short[16]; - int ctx = (int)(mb.NoneZeroDcCoeffs + leftMb.NoneZeroDcCoeffs); - int nz = GetCoeffs(br, bands[1], ctx, q.Y2Mat, 0, dc); - mb.NoneZeroDcCoeffs = leftMb.NoneZeroDcCoeffs = (uint)(nz > 0 ? 1 : 0); - if (nz > 1) - { - // More than just the DC -> perform the full transform. - LossyUtils.TransformWht(dc, dst, this.scratch); - } - else + // Only DC is non-zero -> inlined simplified transform. + int dc0 = (dc[0] + 3) >> 3; + for (int i = 0; i < 16 * 16; i += 16) { - // Only DC is non-zero -> inlined simplified transform. - int dc0 = (dc[0] + 3) >> 3; - for (int i = 0; i < 16 * 16; i += 16) - { - dst[i] = (short)dc0; - } + dst[i] = (short)dc0; } + } + + first = 1; + acProba = bands[0]; + } - first = 1; - acProba = bands[0]; + byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); + byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); + + for (int y = 0; y < 4; y++) + { + int l = lnz & 1; + uint nzCoeffs = 0; + for (int x = 0; x < 4; x++) + { + int ctx = l + (tnz & 1); + int nz = GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); + l = nz > first ? 1 : 0; + tnz = (byte)((tnz >> 1) | (l << 7)); + nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); + dstOffset += 16; } - byte tnz = (byte)(mb.NoneZeroAcDcCoeffs & 0x0f); - byte lnz = (byte)(leftMb.NoneZeroAcDcCoeffs & 0x0f); + tnz >>= 4; + lnz = (byte)((lnz >> 1) | (l << 7)); + nonZeroY = (nonZeroY << 8) | nzCoeffs; + } + + uint outTnz = tnz; + uint outLnz = (uint)(lnz >> 4); - for (int y = 0; y < 4; y++) + for (int ch = 0; ch < 4; ch += 2) + { + uint nzCoeffs = 0; + int chPlus4 = 4 + ch; + tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); + lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); + for (int y = 0; y < 2; y++) { int l = lnz & 1; - uint nzCoeffs = 0; - for (int x = 0; x < 4; x++) + for (int x = 0; x < 2; x++) { int ctx = l + (tnz & 1); - int nz = GetCoeffs(br, acProba, ctx, q.Y1Mat, first, dst.AsSpan(dstOffset)); - l = nz > first ? 1 : 0; - tnz = (byte)((tnz >> 1) | (l << 7)); + int nz = GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); + l = nz > 0 ? 1 : 0; + tnz = (byte)((tnz >> 1) | (l << 3)); nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); dstOffset += 16; } - tnz >>= 4; - lnz = (byte)((lnz >> 1) | (l << 7)); - nonZeroY = (nonZeroY << 8) | nzCoeffs; + tnz >>= 2; + lnz = (byte)((lnz >> 1) | (l << 5)); } - uint outTnz = tnz; - uint outLnz = (uint)(lnz >> 4); - - for (int ch = 0; ch < 4; ch += 2) - { - uint nzCoeffs = 0; - int chPlus4 = 4 + ch; - tnz = (byte)(mb.NoneZeroAcDcCoeffs >> chPlus4); - lnz = (byte)(leftMb.NoneZeroAcDcCoeffs >> chPlus4); - for (int y = 0; y < 2; y++) - { - int l = lnz & 1; - for (int x = 0; x < 2; x++) - { - int ctx = l + (tnz & 1); - int nz = GetCoeffs(br, bands[2], ctx, q.UvMat, 0, dst.AsSpan(dstOffset)); - l = nz > 0 ? 1 : 0; - tnz = (byte)((tnz >> 1) | (l << 3)); - nzCoeffs = NzCodeBits(nzCoeffs, nz, dst[dstOffset] != 0 ? 1 : 0); - dstOffset += 16; - } - - tnz >>= 2; - lnz = (byte)((lnz >> 1) | (l << 5)); - } - - // Note: we don't really need the per-4x4 details for U/V blocks. - nonZeroUv |= nzCoeffs << (4 * ch); - outTnz |= (uint)(tnz << 4 << ch); - outLnz |= (uint)((lnz & 0xf0) << ch); - } + // Note: we don't really need the per-4x4 details for U/V blocks. + nonZeroUv |= nzCoeffs << (4 * ch); + outTnz |= (uint)(tnz << 4 << ch); + outLnz |= (uint)((lnz & 0xf0) << ch); + } - mb.NoneZeroAcDcCoeffs = outTnz; - leftMb.NoneZeroAcDcCoeffs = outLnz; + mb.NoneZeroAcDcCoeffs = outTnz; + leftMb.NoneZeroAcDcCoeffs = outLnz; - block.NonZeroY = nonZeroY; - block.NonZeroUv = nonZeroUv; + block.NonZeroY = nonZeroY; + block.NonZeroUv = nonZeroUv; - return (nonZeroY | nonZeroUv) == 0; - } + return (nonZeroY | nonZeroUv) == 0; + } - private static int GetCoeffs(Vp8BitReader br, Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) + private static int GetCoeffs(Vp8BitReader br, Vp8BandProbas[] prob, int ctx, int[] dq, int n, Span coeffs) + { + // Returns the position of the last non-zero coeff plus one. + Vp8ProbaArray p = prob[n].Probabilities[ctx]; + for (; n < 16; ++n) { - // Returns the position of the last non-zero coeff plus one. - Vp8ProbaArray p = prob[n].Probabilities[ctx]; - for (; n < 16; ++n) + if (br.GetBit(p.Probabilities[0]) == 0) { - if (br.GetBit(p.Probabilities[0]) == 0) - { - // Previous coeff was last non-zero coeff. - return n; - } - - // Sequence of zero coeffs. - while (br.GetBit(p.Probabilities[1]) == 0) - { - p = prob[++n].Probabilities[0]; - if (n == 16) - { - return 16; - } - } + // Previous coeff was last non-zero coeff. + return n; + } - // Non zero coeffs. - int v; - if (br.GetBit(p.Probabilities[2]) == 0) - { - v = 1; - p = prob[n + 1].Probabilities[1]; - } - else + // Sequence of zero coeffs. + while (br.GetBit(p.Probabilities[1]) == 0) + { + p = prob[++n].Probabilities[0]; + if (n == 16) { - v = GetLargeValue(br, p.Probabilities); - p = prob[n + 1].Probabilities[2]; + return 16; } + } - int idx = n > 0 ? 1 : 0; - coeffs[WebpConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); + // Non zero coeffs. + int v; + if (br.GetBit(p.Probabilities[2]) == 0) + { + v = 1; + p = prob[n + 1].Probabilities[1]; + } + else + { + v = GetLargeValue(br, p.Probabilities); + p = prob[n + 1].Probabilities[2]; } - return 16; + int idx = n > 0 ? 1 : 0; + coeffs[WebpConstants.Zigzag[n]] = (short)(br.GetSigned(v) * dq[idx]); } - private static int GetLargeValue(Vp8BitReader br, byte[] p) + return 16; + } + + private static int GetLargeValue(Vp8BitReader br, byte[] p) + { + // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 + int v; + if (br.GetBit(p[3]) == 0) { - // See section 13 - 2: http://tools.ietf.org/html/rfc6386#section-13.2 - int v; - if (br.GetBit(p[3]) == 0) + if (br.GetBit(p[4]) == 0) { - if (br.GetBit(p[4]) == 0) - { - v = 2; - } - else - { - v = 3 + br.GetBit(p[5]); - } + v = 2; } - else if (br.GetBit(p[6]) == 0) + else { - if (br.GetBit(p[7]) == 0) - { - v = 5 + br.GetBit(159); - } - else - { - v = 7 + (2 * br.GetBit(165)); - v += br.GetBit(145); - } + v = 3 + br.GetBit(p[5]); + } + } + else if (br.GetBit(p[6]) == 0) + { + if (br.GetBit(p[7]) == 0) + { + v = 5 + br.GetBit(159); } else { - int bit1 = br.GetBit(p[8]); - int bit0 = br.GetBit(p[9 + bit1]); - int cat = (2 * bit1) + bit0; - v = 0; - byte[] tab = null; - switch (cat) - { - case 0: - tab = WebpConstants.Cat3; - break; - case 1: - tab = WebpConstants.Cat4; - break; - case 2: - tab = WebpConstants.Cat5; - break; - case 3: - tab = WebpConstants.Cat6; - break; - default: - WebpThrowHelper.ThrowImageFormatException("VP8 parsing error"); - break; - } - - for (int i = 0; i < tab.Length; i++) - { - v += v + br.GetBit(tab[i]); - } + v = 7 + (2 * br.GetBit(165)); + v += br.GetBit(145); + } + } + else + { + int bit1 = br.GetBit(p[8]); + int bit0 = br.GetBit(p[9 + bit1]); + int cat = (2 * bit1) + bit0; + v = 0; + byte[] tab = null; + switch (cat) + { + case 0: + tab = WebpConstants.Cat3; + break; + case 1: + tab = WebpConstants.Cat4; + break; + case 2: + tab = WebpConstants.Cat5; + break; + case 3: + tab = WebpConstants.Cat6; + break; + default: + WebpThrowHelper.ThrowImageFormatException("VP8 parsing error"); + break; + } - v += 3 + (8 << cat); + for (int i = 0; i < tab.Length; i++) + { + v += v + br.GetBit(tab[i]); } - return v; + v += 3 + (8 << cat); } - private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) + return v; + } + + private Vp8SegmentHeader ParseSegmentHeader(Vp8Proba proba) + { + Vp8SegmentHeader vp8SegmentHeader = new() { - Vp8SegmentHeader vp8SegmentHeader = new() - { - UseSegment = this.bitReader.ReadBool() - }; - if (vp8SegmentHeader.UseSegment) + UseSegment = this.bitReader.ReadBool() + }; + if (vp8SegmentHeader.UseSegment) + { + vp8SegmentHeader.UpdateMap = this.bitReader.ReadBool(); + bool updateData = this.bitReader.ReadBool(); + if (updateData) { - vp8SegmentHeader.UpdateMap = this.bitReader.ReadBool(); - bool updateData = this.bitReader.ReadBool(); - if (updateData) + vp8SegmentHeader.Delta = this.bitReader.ReadBool(); + bool hasValue; + for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) { - vp8SegmentHeader.Delta = this.bitReader.ReadBool(); - bool hasValue; - for (int i = 0; i < vp8SegmentHeader.Quantizer.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - vp8SegmentHeader.Quantizer[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(7) : 0); - } + hasValue = this.bitReader.ReadBool(); + vp8SegmentHeader.Quantizer[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(7) : 0); + } - for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) - { - hasValue = this.bitReader.ReadBool(); - vp8SegmentHeader.FilterStrength[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(6) : 0); - } + for (int i = 0; i < vp8SegmentHeader.FilterStrength.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + vp8SegmentHeader.FilterStrength[i] = (byte)(hasValue ? this.bitReader.ReadSignedValue(6) : 0); + } - if (vp8SegmentHeader.UpdateMap) + if (vp8SegmentHeader.UpdateMap) + { + for (int s = 0; s < proba.Segments.Length; ++s) { - for (int s = 0; s < proba.Segments.Length; ++s) - { - hasValue = this.bitReader.ReadBool(); - proba.Segments[s] = hasValue ? this.bitReader.ReadValue(8) : 255; - } + hasValue = this.bitReader.ReadBool(); + proba.Segments[s] = hasValue ? this.bitReader.ReadValue(8) : 255; } } } - else - { - vp8SegmentHeader.UpdateMap = false; - } - - return vp8SegmentHeader; } - - private void ParseFilterHeader(Vp8Decoder dec) + else { - Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; - vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; - vp8FilterHeader.FilterLevel = (int)this.bitReader.ReadValue(6); - vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); - vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); + vp8SegmentHeader.UpdateMap = false; + } + + return vp8SegmentHeader; + } - dec.Filter = vp8FilterHeader.FilterLevel == 0 ? LoopFilter.None : vp8FilterHeader.LoopFilter; - if (vp8FilterHeader.UseLfDelta) + private void ParseFilterHeader(Vp8Decoder dec) + { + Vp8FilterHeader vp8FilterHeader = dec.FilterHeader; + vp8FilterHeader.LoopFilter = this.bitReader.ReadBool() ? LoopFilter.Simple : LoopFilter.Complex; + vp8FilterHeader.FilterLevel = (int)this.bitReader.ReadValue(6); + vp8FilterHeader.Sharpness = (int)this.bitReader.ReadValue(3); + vp8FilterHeader.UseLfDelta = this.bitReader.ReadBool(); + + dec.Filter = vp8FilterHeader.FilterLevel == 0 ? LoopFilter.None : vp8FilterHeader.LoopFilter; + if (vp8FilterHeader.UseLfDelta) + { + // Update lf-delta? + if (this.bitReader.ReadBool()) { - // Update lf-delta? - if (this.bitReader.ReadBool()) + bool hasValue; + for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) { - bool hasValue; - for (int i = 0; i < vp8FilterHeader.RefLfDelta.Length; i++) + hasValue = this.bitReader.ReadBool(); + if (hasValue) { - hasValue = this.bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.RefLfDelta[i] = this.bitReader.ReadSignedValue(6); - } + vp8FilterHeader.RefLfDelta[i] = this.bitReader.ReadSignedValue(6); } + } - for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + for (int i = 0; i < vp8FilterHeader.ModeLfDelta.Length; i++) + { + hasValue = this.bitReader.ReadBool(); + if (hasValue) { - hasValue = this.bitReader.ReadBool(); - if (hasValue) - { - vp8FilterHeader.ModeLfDelta[i] = this.bitReader.ReadSignedValue(6); - } + vp8FilterHeader.ModeLfDelta[i] = this.bitReader.ReadSignedValue(6); } } } - - int extraRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; - int extraY = extraRows * dec.CacheYStride; - int extraUv = extraRows / 2 * dec.CacheUvStride; - dec.CacheYOffset = extraY; - dec.CacheUvOffset = extraUv; } - private void ParsePartitions(Vp8Decoder dec) - { - uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; - int startIdx = (int)this.bitReader.PartitionLength; - Span sz = this.bitReader.Data.Slice(startIdx); - int sizeLeft = (int)size; - dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; - int lastPart = dec.NumPartsMinusOne; + int extraRows = WebpConstants.FilterExtraRows[(int)dec.Filter]; + int extraY = extraRows * dec.CacheYStride; + int extraUv = extraRows / 2 * dec.CacheUvStride; + dec.CacheYOffset = extraY; + dec.CacheUvOffset = extraUv; + } - int lastPartMul3 = lastPart * 3; - int partStart = startIdx + lastPartMul3; - sizeLeft -= lastPartMul3; - for (int p = 0; p < lastPart; ++p) + private void ParsePartitions(Vp8Decoder dec) + { + uint size = this.bitReader.Remaining - this.bitReader.PartitionLength; + int startIdx = (int)this.bitReader.PartitionLength; + Span sz = this.bitReader.Data.Slice(startIdx); + int sizeLeft = (int)size; + dec.NumPartsMinusOne = (1 << (int)this.bitReader.ReadValue(2)) - 1; + int lastPart = dec.NumPartsMinusOne; + + int lastPartMul3 = lastPart * 3; + int partStart = startIdx + lastPartMul3; + sizeLeft -= lastPartMul3; + for (int p = 0; p < lastPart; ++p) + { + int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (pSize > sizeLeft) { - int pSize = sz[0] | (sz[1] << 8) | (sz[2] << 16); - if (pSize > sizeLeft) - { - pSize = sizeLeft; - } - - dec.Vp8BitReaders[p] = new Vp8BitReader(this.bitReader.Data, (uint)pSize, partStart); - partStart += pSize; - sizeLeft -= pSize; - sz = sz[3..]; + pSize = sizeLeft; } - dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); + dec.Vp8BitReaders[p] = new Vp8BitReader(this.bitReader.Data, (uint)pSize, partStart); + partStart += pSize; + sizeLeft -= pSize; + sz = sz[3..]; } - private void ParseDequantizationIndices(Vp8Decoder decoder) - { - Vp8SegmentHeader vp8SegmentHeader = decoder.SegmentHeader; + dec.Vp8BitReaders[lastPart] = new Vp8BitReader(this.bitReader.Data, (uint)sizeLeft, partStart); + } - int baseQ0 = (int)this.bitReader.ReadValue(7); - bool hasValue = this.bitReader.ReadBool(); - int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dqy2Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dqy2Ac = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - hasValue = this.bitReader.ReadBool(); - int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; - for (int i = 0; i < WebpConstants.NumMbSegments; i++) + private void ParseDequantizationIndices(Vp8Decoder decoder) + { + Vp8SegmentHeader vp8SegmentHeader = decoder.SegmentHeader; + + int baseQ0 = (int)this.bitReader.ReadValue(7); + bool hasValue = this.bitReader.ReadBool(); + int dqy1Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Dc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dqy2Ac = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvDc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + hasValue = this.bitReader.ReadBool(); + int dquvAc = hasValue ? this.bitReader.ReadSignedValue(4) : 0; + for (int i = 0; i < WebpConstants.NumMbSegments; i++) + { + int q; + if (vp8SegmentHeader.UseSegment) { - int q; - if (vp8SegmentHeader.UseSegment) + q = vp8SegmentHeader.Quantizer[i]; + if (!vp8SegmentHeader.Delta) { - q = vp8SegmentHeader.Quantizer[i]; - if (!vp8SegmentHeader.Delta) - { - q += baseQ0; - } + q += baseQ0; } - else + } + else + { + if (i > 0) { - if (i > 0) - { - decoder.DeQuantMatrices[i] = decoder.DeQuantMatrices[0]; - continue; - } - - q = baseQ0; + decoder.DeQuantMatrices[i] = decoder.DeQuantMatrices[0]; + continue; } - Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; - m.Y1Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; - m.Y1Mat[1] = WebpLookupTables.AcTable[Clip(q + 0, 127)]; - m.Y2Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; - - // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. - // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. - m.Y2Mat[1] = (WebpLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; - if (m.Y2Mat[1] < 8) - { - m.Y2Mat[1] = 8; - } + q = baseQ0; + } - m.UvMat[0] = WebpLookupTables.DcTable[Clip(q + dquvDc, 117)]; - m.UvMat[1] = WebpLookupTables.AcTable[Clip(q + dquvAc, 127)]; + Vp8QuantMatrix m = decoder.DeQuantMatrices[i]; + m.Y1Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy1Dc, 127)]; + m.Y1Mat[1] = WebpLookupTables.AcTable[Clip(q + 0, 127)]; + m.Y2Mat[0] = WebpLookupTables.DcTable[Clip(q + dqy2Dc, 127)] * 2; - // For dithering strength evaluation. - m.UvQuant = q + dquvAc; + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good word size. + m.Y2Mat[1] = (WebpLookupTables.AcTable[Clip(q + dqy2Ac, 127)] * 101581) >> 16; + if (m.Y2Mat[1] < 8) + { + m.Y2Mat[1] = 8; } + + m.UvMat[0] = WebpLookupTables.DcTable[Clip(q + dquvDc, 117)]; + m.UvMat[1] = WebpLookupTables.AcTable[Clip(q + dquvAc, 127)]; + + // For dithering strength evaluation. + m.UvQuant = q + dquvAc; } + } - private void ParseProbabilities(Vp8Decoder dec) - { - Vp8Proba proba = dec.Probabilities; + private void ParseProbabilities(Vp8Decoder dec) + { + Vp8Proba proba = dec.Probabilities; - for (int t = 0; t < WebpConstants.NumTypes; ++t) + for (int t = 0; t < WebpConstants.NumTypes; ++t) + { + for (int b = 0; b < WebpConstants.NumBands; ++b) { - for (int b = 0; b < WebpConstants.NumBands; ++b) + for (int c = 0; c < WebpConstants.NumCtx; ++c) { - for (int c = 0; c < WebpConstants.NumCtx; ++c) + for (int p = 0; p < WebpConstants.NumProbas; ++p) { - for (int p = 0; p < WebpConstants.NumProbas; ++p) - { - byte prob = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; - proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)(this.bitReader.GetBit(prob) != 0 - ? this.bitReader.ReadValue(8) - : WebpLookupTables.DefaultCoeffsProba[t, b, c, p]); - } + byte prob = WebpLookupTables.CoeffsUpdateProba[t, b, c, p]; + proba.Bands[t, b].Probabilities[c].Probabilities[p] = (byte)(this.bitReader.GetBit(prob) != 0 + ? this.bitReader.ReadValue(8) + : WebpLookupTables.DefaultCoeffsProba[t, b, c, p]); } } - - for (int b = 0; b < 16 + 1; ++b) - { - proba.BandsPtr[t][b] = proba.Bands[t, WebpConstants.Vp8EncBands[b]]; - } } - dec.UseSkipProbability = this.bitReader.ReadBool(); - if (dec.UseSkipProbability) + for (int b = 0; b < 16 + 1; ++b) { - dec.SkipProbability = (byte)this.bitReader.ReadValue(8); + proba.BandsPtr[t][b] = proba.Bands[t, WebpConstants.Vp8EncBands[b]]; } } - private static Vp8Io InitializeVp8Io(Vp8Decoder dec, Vp8PictureHeader pictureHeader) + dec.UseSkipProbability = this.bitReader.ReadBool(); + if (dec.UseSkipProbability) { - Vp8Io io = default; - io.Width = (int)pictureHeader.Width; - io.Height = (int)pictureHeader.Height; - io.UseScaling = false; - io.ScaledWidth = io.Width; - io.ScaledHeight = io.ScaledHeight; - io.MbW = io.Width; - io.MbH = io.Height; - uint strideLength = (pictureHeader.Width + 15) >> 4; - io.YStride = (int)(16 * strideLength); - io.UvStride = (int)(8 * strideLength); - - int intraPredModeSize = 4 * dec.MbWidth; - dec.IntraT = new byte[intraPredModeSize]; + dec.SkipProbability = (byte)this.bitReader.ReadValue(8); + } + } - int extraPixels = WebpConstants.FilterExtraRows[(int)dec.Filter]; - if (dec.Filter == LoopFilter.Complex) + private static Vp8Io InitializeVp8Io(Vp8Decoder dec, Vp8PictureHeader pictureHeader) + { + Vp8Io io = default; + io.Width = (int)pictureHeader.Width; + io.Height = (int)pictureHeader.Height; + io.UseScaling = false; + io.ScaledWidth = io.Width; + io.ScaledHeight = io.ScaledHeight; + io.MbW = io.Width; + io.MbH = io.Height; + uint strideLength = (pictureHeader.Width + 15) >> 4; + io.YStride = (int)(16 * strideLength); + io.UvStride = (int)(8 * strideLength); + + int intraPredModeSize = 4 * dec.MbWidth; + dec.IntraT = new byte[intraPredModeSize]; + + int extraPixels = WebpConstants.FilterExtraRows[(int)dec.Filter]; + if (dec.Filter == LoopFilter.Complex) + { + // For complex filter, we need to preserve the dependency chain. + dec.TopLeftMbX = 0; + dec.TopLeftMbY = 0; + } + else + { + // For simple filter, we include 'extraPixels' on the other side of the boundary, + // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. + int extraShift4 = -extraPixels >> 4; + dec.TopLeftMbX = extraShift4; + dec.TopLeftMbY = extraShift4; + if (dec.TopLeftMbX < 0) { - // For complex filter, we need to preserve the dependency chain. dec.TopLeftMbX = 0; - dec.TopLeftMbY = 0; } - else - { - // For simple filter, we include 'extraPixels' on the other side of the boundary, - // since vertical or horizontal filtering of the previous macroblock can modify some abutting pixels. - int extraShift4 = -extraPixels >> 4; - dec.TopLeftMbX = extraShift4; - dec.TopLeftMbY = extraShift4; - if (dec.TopLeftMbX < 0) - { - dec.TopLeftMbX = 0; - } - if (dec.TopLeftMbY < 0) - { - dec.TopLeftMbY = 0; - } - } - - // We need some 'extra' pixels on the right/bottom. - dec.BottomRightMbY = (io.Height + 15 + extraPixels) >> 4; - dec.BottomRightMbX = (io.Width + 15 + extraPixels) >> 4; - if (dec.BottomRightMbX > dec.MbWidth) + if (dec.TopLeftMbY < 0) { - dec.BottomRightMbX = dec.MbWidth; - } - - if (dec.BottomRightMbY > dec.MbHeight) - { - dec.BottomRightMbY = dec.MbHeight; + dec.TopLeftMbY = 0; } + } - return io; + // We need some 'extra' pixels on the right/bottom. + dec.BottomRightMbY = (io.Height + 15 + extraPixels) >> 4; + dec.BottomRightMbX = (io.Width + 15 + extraPixels) >> 4; + if (dec.BottomRightMbX > dec.MbWidth) + { + dec.BottomRightMbX = dec.MbWidth; } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) + if (dec.BottomRightMbY > dec.MbHeight) { - nzCoeffs <<= 2; - if (nz > 3) - { - nzCoeffs |= 3; - } - else if (nz > 1) - { - nzCoeffs |= 2; - } - else - { - nzCoeffs |= (uint)dcNz; - } + dec.BottomRightMbY = dec.MbHeight; + } + + return io; + } - return nzCoeffs; + [MethodImpl(InliningOptions.ShortMethod)] + private static uint NzCodeBits(uint nzCoeffs, int nz, int dcNz) + { + nzCoeffs <<= 2; + if (nz > 3) + { + nzCoeffs |= 3; + } + else if (nz > 1) + { + nzCoeffs |= 2; + } + else + { + nzCoeffs |= (uint)dcNz; } - [MethodImpl(InliningOptions.ShortMethod)] - private static int CheckMode(int mbx, int mby, int mode) + return nzCoeffs; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int CheckMode(int mbx, int mby, int mode) + { + // B_DC_PRED + if (mode == 0) { - // B_DC_PRED - if (mode == 0) + if (mbx == 0) { - if (mbx == 0) - { - return mby == 0 - ? 6 // B_DC_PRED_NOTOPLEFT - : 5; // B_DC_PRED_NOLEFT - } - return mby == 0 - ? 4 // B_DC_PRED_NOTOP - : 0; // B_DC_PRED + ? 6 // B_DC_PRED_NOTOPLEFT + : 5; // B_DC_PRED_NOLEFT } - return mode; + return mby == 0 + ? 4 // B_DC_PRED_NOTOP + : 0; // B_DC_PRED } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Clip(int value, int max) => Math.Clamp(value, 0, max); + return mode; } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int Clip(int value, int max) => Math.Clamp(value, 0, max); } diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index ea2b82c2c4..5c6dde6224 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,749 +9,748 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp.Lossy +namespace SixLabors.ImageSharp.Formats.Webp.Lossy; + +internal static class YuvConversion { - internal static class YuvConversion + /// + /// Fixed-point precision for RGB->YUV. + /// + private const int YuvFix = 16; + + private const int YuvHalf = 1 << (YuvFix - 1); + + // UpSample from YUV to RGB. + // Given samples laid out in a square as: + // [a b] + // [c d] + // we interpolate u/v as: + // ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 + // ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 + public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) { - /// - /// Fixed-point precision for RGB->YUV. - /// - private const int YuvFix = 16; - - private const int YuvHalf = 1 << (YuvFix - 1); - - // UpSample from YUV to RGB. - // Given samples laid out in a square as: - // [a b] - // [c d] - // we interpolate u/v as: - // ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 - // ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 - public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) + if (Sse41.IsSupported) { - if (Sse41.IsSupported) - { - UpSampleSse41(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len, uvBuffer); - } - else - { - UpSampleScalar(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); - } + UpSampleSse41(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len, uvBuffer); } + else + { + UpSampleScalar(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); + } + } - private static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + private static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + { + const int xStep = 3; + int lastPixelPair = (len - 1) >> 1; + uint tluv = LoadUv(topU[0], topV[0]); // top-left sample + uint luv = LoadUv(curU[0], curV[0]); // left-sample + uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); + + if (bottomY != default) { - const int xStep = 3; - int lastPixelPair = (len - 1) >> 1; - uint tluv = LoadUv(topU[0], topV[0]); // top-left sample - uint luv = LoadUv(curU[0], curV[0]); // left-sample - uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); + } + + for (int x = 1; x <= lastPixelPair; x++) + { + uint tuv = LoadUv(topU[x], topV[x]); // top sample + uint uv = LoadUv(curU[x], curV[x]); // sample + + // Precompute invariant values associated with first and second diagonals. + uint avg = tluv + tuv + luv + uv + 0x00080008u; + uint diag12 = (avg + (2 * (tuv + luv))) >> 3; + uint diag03 = (avg + (2 * (tluv + uv))) >> 3; + uv0 = (diag12 + tluv) >> 1; + uint uv1 = (diag03 + tuv) >> 1; + int xMul2 = x * 2; + YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((xMul2 - 1) * xStep)..]); + YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst[((xMul2 - 0) * xStep)..]); if (bottomY != default) { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); + uv0 = (diag03 + luv) >> 1; + uv1 = (diag12 + uv) >> 1; + YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((xMul2 - 1) * xStep)..]); + YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst[((xMul2 + 0) * xStep)..]); } - for (int x = 1; x <= lastPixelPair; x++) + tluv = tuv; + luv = uv; + } + + if ((len & 1) == 0) + { + uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((len - 1) * xStep)..]); + if (bottomY != default) { - uint tuv = LoadUv(topU[x], topV[x]); // top sample - uint uv = LoadUv(curU[x], curV[x]); // sample - - // Precompute invariant values associated with first and second diagonals. - uint avg = tluv + tuv + luv + uv + 0x00080008u; - uint diag12 = (avg + (2 * (tuv + luv))) >> 3; - uint diag03 = (avg + (2 * (tluv + uv))) >> 3; - uv0 = (diag12 + tluv) >> 1; - uint uv1 = (diag03 + tuv) >> 1; - int xMul2 = x * 2; - YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((xMul2 - 1) * xStep)..]); - YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst[((xMul2 - 0) * xStep)..]); - - if (bottomY != default) - { - uv0 = (diag03 + luv) >> 1; - uv1 = (diag12 + uv) >> 1; - YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((xMul2 - 1) * xStep)..]); - YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst[((xMul2 + 0) * xStep)..]); - } - - tluv = tuv; - luv = uv; + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((len - 1) * xStep)..]); } + } + } - if ((len & 1) == 0) + // We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows + // u = (9*a + 3*b + 3*c + d + 8) / 16 + // = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 + // = (a + m + 1) / 2 + // where m = (a + 3*b + 3*c + d) / 8 + // = ((a + b + c + d) / 2 + b + c) / 4 + // + // Let's say k = (a + b + c + d) / 4. + // We can compute k as + // k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 + // where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 + // + // Then m can be written as + // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + private static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) + { + const int xStep = 3; + Array.Clear(uvBuffer); + Span ru = uvBuffer.AsSpan(15); + Span rv = ru[32..]; + + // Treat the first pixel in regular way. + int uDiag = ((topU[0] + curU[0]) >> 1) + 1; + int vDiag = ((topV[0] + curV[0]) >> 1) + 1; + int u0t = (topU[0] + uDiag) >> 1; + int v0t = (topV[0] + vDiag) >> 1; + YuvToBgr(topY[0], u0t, v0t, topDst); + if (bottomY != default) + { + int u0b = (curU[0] + uDiag) >> 1; + int v0b = (curV[0] + vDiag) >> 1; + YuvToBgr(bottomY[0], u0b, v0b, bottomDst); + } + + // For UpSample32Pixels, 17 u/v values must be read-able for each block. + int pos; + int uvPos; + ref byte topURef = ref MemoryMarshal.GetReference(topU); + ref byte topVRef = ref MemoryMarshal.GetReference(topV); + ref byte curURef = ref MemoryMarshal.GetReference(curU); + ref byte curVRef = ref MemoryMarshal.GetReference(curV); + if (bottomY != default) + { + for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) { - uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((len - 1) * xStep)..]); - if (bottomY != default) - { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((len - 1) * xStep)..]); - } + UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); + UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); + ConvertYuvToBgrWithBottomYSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); } } - - // We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows - // u = (9*a + 3*b + 3*c + d + 8) / 16 - // = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 - // = (a + m + 1) / 2 - // where m = (a + 3*b + 3*c + d) / 8 - // = ((a + b + c + d) / 2 + b + c) / 4 - // - // Let's say k = (a + b + c + d) / 4. - // We can compute k as - // k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 - // where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 - // - // Then m can be written as - // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 - private static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) + else { - const int xStep = 3; - Array.Clear(uvBuffer); - Span ru = uvBuffer.AsSpan(15); - Span rv = ru[32..]; - - // Treat the first pixel in regular way. - int uDiag = ((topU[0] + curU[0]) >> 1) + 1; - int vDiag = ((topV[0] + curV[0]) >> 1) + 1; - int u0t = (topU[0] + uDiag) >> 1; - int v0t = (topV[0] + vDiag) >> 1; - YuvToBgr(topY[0], u0t, v0t, topDst); - if (bottomY != default) + for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) { - int u0b = (curU[0] + uDiag) >> 1; - int v0b = (curV[0] + vDiag) >> 1; - YuvToBgr(bottomY[0], u0b, v0b, bottomDst); + UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); + UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); + ConvertYuvToBgrSse41(topY, topDst, ru, rv, pos, xStep); } + } - // For UpSample32Pixels, 17 u/v values must be read-able for each block. - int pos; - int uvPos; - ref byte topURef = ref MemoryMarshal.GetReference(topU); - ref byte topVRef = ref MemoryMarshal.GetReference(topV); - ref byte curURef = ref MemoryMarshal.GetReference(curU); - ref byte curVRef = ref MemoryMarshal.GetReference(curV); + // Process last block. + if (len > 1) + { + int leftOver = ((len + 1) >> 1) - (pos >> 1); + Span tmpTopDst = ru[(4 * 32)..]; + Span tmpBottomDst = tmpTopDst[(4 * 32)..]; + Span tmpTop = tmpBottomDst[(4 * 32)..]; + Span tmpBottom = (bottomY == default) ? null : tmpTop[32..]; + UpSampleLastBlock(topU[uvPos..], curU[uvPos..], leftOver, ru); + UpSampleLastBlock(topV[uvPos..], curV[uvPos..], leftOver, rv); + + topY[pos..len].CopyTo(tmpTop); if (bottomY != default) { - for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) - { - UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); - UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); - ConvertYuvToBgrWithBottomYSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); - } + bottomY[pos..len].CopyTo(tmpBottom); + ConvertYuvToBgrWithBottomYSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); } else { - for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) - { - UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); - UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); - ConvertYuvToBgrSse41(topY, topDst, ru, rv, pos, xStep); - } + ConvertYuvToBgrSse41(tmpTop, tmpTopDst, ru, rv, 0, xStep); } - // Process last block. - if (len > 1) + tmpTopDst[..((len - pos) * xStep)].CopyTo(topDst[(pos * xStep)..]); + if (bottomY != default) { - int leftOver = ((len + 1) >> 1) - (pos >> 1); - Span tmpTopDst = ru[(4 * 32)..]; - Span tmpBottomDst = tmpTopDst[(4 * 32)..]; - Span tmpTop = tmpBottomDst[(4 * 32)..]; - Span tmpBottom = (bottomY == default) ? null : tmpTop[32..]; - UpSampleLastBlock(topU[uvPos..], curU[uvPos..], leftOver, ru); - UpSampleLastBlock(topV[uvPos..], curV[uvPos..], leftOver, rv); - - topY[pos..len].CopyTo(tmpTop); - if (bottomY != default) - { - bottomY[pos..len].CopyTo(tmpBottom); - ConvertYuvToBgrWithBottomYSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); - } - else - { - ConvertYuvToBgrSse41(tmpTop, tmpTopDst, ru, rv, 0, xStep); - } - - tmpTopDst[..((len - pos) * xStep)].CopyTo(topDst[(pos * xStep)..]); - if (bottomY != default) - { - tmpBottomDst[..((len - pos) * xStep)].CopyTo(bottomDst[(pos * xStep)..]); - } + tmpBottomDst[..((len - pos) * xStep)].CopyTo(bottomDst[(pos * xStep)..]); } } + } - // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. - private static void UpSample32Pixels(ref byte r1, ref byte r2, Span output) + // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. + private static void UpSample32Pixels(ref byte r1, ref byte r2, Span output) + { + // Load inputs. + Vector128 a = Unsafe.As>(ref r1); + Vector128 b = Unsafe.As>(ref Unsafe.Add(ref r1, 1)); + Vector128 c = Unsafe.As>(ref r2); + Vector128 d = Unsafe.As>(ref Unsafe.Add(ref r2, 1)); + + Vector128 s = Sse2.Average(a, d); // s = (a + d + 1) / 2 + Vector128 t = Sse2.Average(b, c); // t = (b + c + 1) / 2 + Vector128 st = Sse2.Xor(s, t); // st = s^t + + Vector128 ad = Sse2.Xor(a, d); // ad = a^d + Vector128 bc = Sse2.Xor(b, c); // bc = b^c + + Vector128 t1 = Sse2.Or(ad, bc); // (a^d) | (b^c) + Vector128 t2 = Sse2.Or(t1, st); // (a^d) | (b^c) | (s^t) + Vector128 t3 = Sse2.And(t2, Vector128.Create((byte)1)); // (a^d) | (b^c) | (s^t) & 1 + Vector128 t4 = Sse2.Average(s, t); + Vector128 k = Sse2.Subtract(t4, t3); // k = (a + b + c + d) / 4 + + Vector128 diag1 = GetM(k, st, bc, t); + Vector128 diag2 = GetM(k, st, ad, s); + + // Pack the alternate pixels. + PackAndStore(a, b, diag1, diag2, output); // store top. + PackAndStore(c, d, diag2, diag1, output[(2 * 32)..]); + } + + private static void UpSampleLastBlock(Span tb, Span bb, int numPixels, Span output) + { + Span r1 = stackalloc byte[17]; + Span r2 = stackalloc byte[17]; + tb[..numPixels].CopyTo(r1); + bb[..numPixels].CopyTo(r2); + + // Replicate last byte. + int length = 17 - numPixels; + if (length > 0) { - // Load inputs. - Vector128 a = Unsafe.As>(ref r1); - Vector128 b = Unsafe.As>(ref Unsafe.Add(ref r1, 1)); - Vector128 c = Unsafe.As>(ref r2); - Vector128 d = Unsafe.As>(ref Unsafe.Add(ref r2, 1)); - - Vector128 s = Sse2.Average(a, d); // s = (a + d + 1) / 2 - Vector128 t = Sse2.Average(b, c); // t = (b + c + 1) / 2 - Vector128 st = Sse2.Xor(s, t); // st = s^t - - Vector128 ad = Sse2.Xor(a, d); // ad = a^d - Vector128 bc = Sse2.Xor(b, c); // bc = b^c - - Vector128 t1 = Sse2.Or(ad, bc); // (a^d) | (b^c) - Vector128 t2 = Sse2.Or(t1, st); // (a^d) | (b^c) | (s^t) - Vector128 t3 = Sse2.And(t2, Vector128.Create((byte)1)); // (a^d) | (b^c) | (s^t) & 1 - Vector128 t4 = Sse2.Average(s, t); - Vector128 k = Sse2.Subtract(t4, t3); // k = (a + b + c + d) / 4 - - Vector128 diag1 = GetM(k, st, bc, t); - Vector128 diag2 = GetM(k, st, ad, s); - - // Pack the alternate pixels. - PackAndStore(a, b, diag1, diag2, output); // store top. - PackAndStore(c, d, diag2, diag1, output[(2 * 32)..]); + r1.Slice(numPixels, length).Fill(r1[numPixels - 1]); + r2.Slice(numPixels, length).Fill(r2[numPixels - 1]); } - private static void UpSampleLastBlock(Span tb, Span bb, int numPixels, Span output) + ref byte r1Ref = ref MemoryMarshal.GetReference(r1); + ref byte r2Ref = ref MemoryMarshal.GetReference(r2); + UpSample32Pixels(ref r1Ref, ref r2Ref, output); + } + + // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 + private static Vector128 GetM(Vector128 k, Vector128 st, Vector128 ij, Vector128 input) + { + Vector128 tmp0 = Sse2.Average(k, input); // (k + in + 1) / 2 + Vector128 tmp1 = Sse2.And(ij, st); // (ij) & (s^t) + Vector128 tmp2 = Sse2.Xor(k, input); // (k^in) + Vector128 tmp3 = Sse2.Or(tmp1, tmp2); // ((ij) & (s^t)) | (k^in) + Vector128 tmp4 = Sse2.And(tmp3, Vector128.Create((byte)1)); // & 1 -> lsb_correction + + return Sse2.Subtract(tmp0, tmp4); // (k + in + 1) / 2 - lsb_correction + } + + private static void PackAndStore(Vector128 a, Vector128 b, Vector128 da, Vector128 db, Span output) + { + Vector128 ta = Sse2.Average(a, da); // (9a + 3b + 3c + d + 8) / 16 + Vector128 tb = Sse2.Average(b, db); // (3a + 9b + c + 3d + 8) / 16 + Vector128 t1 = Sse2.UnpackLow(ta, tb); + Vector128 t2 = Sse2.UnpackHigh(ta, tb); + + ref byte output0Ref = ref MemoryMarshal.GetReference(output); + ref byte output1Ref = ref Unsafe.Add(ref output0Ref, 16); + Unsafe.As>(ref output0Ref) = t1; + Unsafe.As>(ref output1Ref) = t2; + } + + /// + /// Converts the RGB values of the image to YUV. + /// + /// The pixel type of the image. + /// The image to convert. + /// The global configuration. + /// The memory allocator. + /// Span to store the luma component of the image. + /// Span to store the u component of the image. + /// Span to store the v component of the image. + /// true, if the image contains alpha data. + public static bool ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) + where TPixel : unmanaged, IPixel + { + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + int width = imageBuffer.Width; + int height = imageBuffer.Height; + int uvWidth = (width + 1) >> 1; + + // Temporary storage for accumulated R/G/B values during conversion to U/V. + using IMemoryOwner tmpRgb = memoryAllocator.Allocate(4 * uvWidth); + using IMemoryOwner bgraRow0Buffer = memoryAllocator.Allocate(width); + using IMemoryOwner bgraRow1Buffer = memoryAllocator.Allocate(width); + Span tmpRgbSpan = tmpRgb.GetSpan(); + Span bgraRow0 = bgraRow0Buffer.GetSpan(); + Span bgraRow1 = bgraRow1Buffer.GetSpan(); + int uvRowIndex = 0; + int rowIndex; + bool hasAlpha = false; + for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - Span r1 = stackalloc byte[17]; - Span r2 = stackalloc byte[17]; - tb[..numPixels].CopyTo(r1); - bb[..numPixels].CopyTo(r2); - - // Replicate last byte. - int length = 17 - numPixels; - if (length > 0) + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); + Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1); + PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); + PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); + + bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1); + if (rowsHaveAlpha) { - r1.Slice(numPixels, length).Fill(r1[numPixels - 1]); - r2.Slice(numPixels, length).Fill(r2[numPixels - 1]); + hasAlpha = true; } - ref byte r1Ref = ref MemoryMarshal.GetReference(r1); - ref byte r2Ref = ref MemoryMarshal.GetReference(r2); - UpSample32Pixels(ref r1Ref, ref r2Ref, output); - } + // Downsample U/V planes, two rows at a time. + if (!rowsHaveAlpha) + { + AccumulateRgb(bgraRow0, bgraRow1, tmpRgbSpan, width); + } + else + { + AccumulateRgba(bgraRow0, bgraRow1, tmpRgbSpan, width); + } - // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 - private static Vector128 GetM(Vector128 k, Vector128 st, Vector128 ij, Vector128 input) - { - Vector128 tmp0 = Sse2.Average(k, input); // (k + in + 1) / 2 - Vector128 tmp1 = Sse2.And(ij, st); // (ij) & (s^t) - Vector128 tmp2 = Sse2.Xor(k, input); // (k^in) - Vector128 tmp3 = Sse2.Or(tmp1, tmp2); // ((ij) & (s^t)) | (k^in) - Vector128 tmp4 = Sse2.And(tmp3, Vector128.Create((byte)1)); // & 1 -> lsb_correction + ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); + uvRowIndex++; - return Sse2.Subtract(tmp0, tmp4); // (k + in + 1) / 2 - lsb_correction + ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); + ConvertRgbaToY(bgraRow1, y[((rowIndex + 1) * width)..], width); } - private static void PackAndStore(Vector128 a, Vector128 b, Vector128 da, Vector128 db, Span output) + // Extra last row. + if ((height & 1) != 0) { - Vector128 ta = Sse2.Average(a, da); // (9a + 3b + 3c + d + 8) / 16 - Vector128 tb = Sse2.Average(b, db); // (3a + 9b + c + 3d + 8) / 16 - Vector128 t1 = Sse2.UnpackLow(ta, tb); - Vector128 t2 = Sse2.UnpackHigh(ta, tb); - - ref byte output0Ref = ref MemoryMarshal.GetReference(output); - ref byte output1Ref = ref Unsafe.Add(ref output0Ref, 16); - Unsafe.As>(ref output0Ref) = t1; - Unsafe.As>(ref output1Ref) = t2; - } + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); + PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); + ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); - /// - /// Converts the RGB values of the image to YUV. - /// - /// The pixel type of the image. - /// The image to convert. - /// The global configuration. - /// The memory allocator. - /// Span to store the luma component of the image. - /// Span to store the u component of the image. - /// Span to store the v component of the image. - /// true, if the image contains alpha data. - public static bool ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) - where TPixel : unmanaged, IPixel - { - Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; - int width = imageBuffer.Width; - int height = imageBuffer.Height; - int uvWidth = (width + 1) >> 1; - - // Temporary storage for accumulated R/G/B values during conversion to U/V. - using IMemoryOwner tmpRgb = memoryAllocator.Allocate(4 * uvWidth); - using IMemoryOwner bgraRow0Buffer = memoryAllocator.Allocate(width); - using IMemoryOwner bgraRow1Buffer = memoryAllocator.Allocate(width); - Span tmpRgbSpan = tmpRgb.GetSpan(); - Span bgraRow0 = bgraRow0Buffer.GetSpan(); - Span bgraRow1 = bgraRow1Buffer.GetSpan(); - int uvRowIndex = 0; - int rowIndex; - bool hasAlpha = false; - for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) + if (!WebpCommonUtils.CheckNonOpaque(bgraRow0)) { - Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); - Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1); - PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); - PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); - - bool rowsHaveAlpha = WebpCommonUtils.CheckNonOpaque(bgraRow0) && WebpCommonUtils.CheckNonOpaque(bgraRow1); - if (rowsHaveAlpha) - { - hasAlpha = true; - } - - // Downsample U/V planes, two rows at a time. - if (!rowsHaveAlpha) - { - AccumulateRgb(bgraRow0, bgraRow1, tmpRgbSpan, width); - } - else - { - AccumulateRgba(bgraRow0, bgraRow1, tmpRgbSpan, width); - } - - ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); - uvRowIndex++; - - ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); - ConvertRgbaToY(bgraRow1, y[((rowIndex + 1) * width)..], width); + AccumulateRgb(bgraRow0, bgraRow0, tmpRgbSpan, width); } - - // Extra last row. - if ((height & 1) != 0) + else { - Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); - PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); - ConvertRgbaToY(bgraRow0, y[(rowIndex * width)..], width); - - if (!WebpCommonUtils.CheckNonOpaque(bgraRow0)) - { - AccumulateRgb(bgraRow0, bgraRow0, tmpRgbSpan, width); - } - else - { - AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width); - hasAlpha = true; - } - - ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); + AccumulateRgba(bgraRow0, bgraRow0, tmpRgbSpan, width); + hasAlpha = true; } - return hasAlpha; + ConvertRgbaToUv(tmpRgbSpan, u[(uvRowIndex * uvWidth)..], v[(uvRowIndex * uvWidth)..], uvWidth); } - /// - /// Converts a rgba pixel row to Y. - /// - /// The row span to convert. - /// The destination span for y. - /// The width. - [MethodImpl(InliningOptions.ShortMethod)] - public static void ConvertRgbaToY(Span rowSpan, Span y, int width) + return hasAlpha; + } + + /// + /// Converts a rgba pixel row to Y. + /// + /// The row span to convert. + /// The destination span for y. + /// The width. + [MethodImpl(InliningOptions.ShortMethod)] + public static void ConvertRgbaToY(Span rowSpan, Span y, int width) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - y[x] = (byte)RgbToY(rowSpan[x].R, rowSpan[x].G, rowSpan[x].B, YuvHalf); - } + y[x] = (byte)RgbToY(rowSpan[x].R, rowSpan[x].G, rowSpan[x].B, YuvHalf); } + } - /// - /// Converts a rgb row of pixels to UV. - /// - /// The RGB pixel row. - /// The destination span for u. - /// The destination span for v. - /// The width. - public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) + /// + /// Converts a rgb row of pixels to UV. + /// + /// The RGB pixel row. + /// The destination span for u. + /// The destination span for v. + /// The width. + public static void ConvertRgbaToUv(Span rgb, Span u, Span v, int width) + { + int rgbIdx = 0; + for (int i = 0; i < width; i += 1, rgbIdx += 4) { - int rgbIdx = 0; - for (int i = 0; i < width; i += 1, rgbIdx += 4) - { - int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; - u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); - v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); - } + int r = rgb[rgbIdx], g = rgb[rgbIdx + 1], b = rgb[rgbIdx + 2]; + u[i] = (byte)RgbToU(r, g, b, YuvHalf << 2); + v[i] = (byte)RgbToV(r, g, b, YuvHalf << 2); } + } - public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + public static void AccumulateRgb(Span rowSpan, Span nextRowSpan, Span dst, int width) + { + Bgra32 bgra0; + Bgra32 bgra1; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) { - Bgra32 bgra0; - Bgra32 bgra1; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < (width >> 1); i += 1, j += 2, dstIdx += 4) - { - bgra0 = rowSpan[j]; - bgra1 = rowSpan[j + 1]; - Bgra32 bgra2 = nextRowSpan[j]; - Bgra32 bgra3 = nextRowSpan[j + 1]; + bgra0 = rowSpan[j]; + bgra1 = rowSpan[j + 1]; + Bgra32 bgra2 = nextRowSpan[j]; + Bgra32 bgra3 = nextRowSpan[j + 1]; + + dst[dstIdx] = (ushort)LinearToGamma( + GammaToLinear(bgra0.R) + + GammaToLinear(bgra1.R) + + GammaToLinear(bgra2.R) + + GammaToLinear(bgra3.R), + 0); + dst[dstIdx + 1] = (ushort)LinearToGamma( + GammaToLinear(bgra0.G) + + GammaToLinear(bgra1.G) + + GammaToLinear(bgra2.G) + + GammaToLinear(bgra3.G), + 0); + dst[dstIdx + 2] = (ushort)LinearToGamma( + GammaToLinear(bgra0.B) + + GammaToLinear(bgra1.B) + + GammaToLinear(bgra2.B) + + GammaToLinear(bgra3.B), + 0); + } - dst[dstIdx] = (ushort)LinearToGamma( + if ((width & 1) != 0) + { + bgra0 = rowSpan[j]; + bgra1 = nextRowSpan[j]; + + dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); + dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); + dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); + } + } + + public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + { + Bgra32 bgra0; + Bgra32 bgra1; + int i, j; + int dstIdx = 0; + for (i = 0, j = 0; i < width >> 1; i += 1, j += 2, dstIdx += 4) + { + bgra0 = rowSpan[j]; + bgra1 = rowSpan[j + 1]; + Bgra32 bgra2 = nextRowSpan[j]; + Bgra32 bgra3 = nextRowSpan[j + 1]; + uint a = (uint)(bgra0.A + bgra1.A + bgra2.A + bgra3.A); + int r, g, b; + if (a is 4 * 0xff or 0) + { + r = (ushort)LinearToGamma( GammaToLinear(bgra0.R) + - GammaToLinear(bgra1.R) + - GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), + GammaToLinear(bgra1.R) + + GammaToLinear(bgra2.R) + + GammaToLinear(bgra3.R), 0); - dst[dstIdx + 1] = (ushort)LinearToGamma( + g = (ushort)LinearToGamma( GammaToLinear(bgra0.G) + - GammaToLinear(bgra1.G) + - GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), + GammaToLinear(bgra1.G) + + GammaToLinear(bgra2.G) + + GammaToLinear(bgra3.G), 0); - dst[dstIdx + 2] = (ushort)LinearToGamma( + b = (ushort)LinearToGamma( GammaToLinear(bgra0.B) + - GammaToLinear(bgra1.B) + - GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), + GammaToLinear(bgra1.B) + + GammaToLinear(bgra2.B) + + GammaToLinear(bgra3.B), 0); } - - if ((width & 1) != 0) + else { - bgra0 = rowSpan[j]; - bgra1 = nextRowSpan[j]; - - dst[dstIdx] = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); - dst[dstIdx + 1] = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); - dst[dstIdx + 2] = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); + r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra2.R, bgra3.R, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); + g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra2.G, bgra3.G, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); + b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra2.B, bgra3.B, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); } + + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; } - public static void AccumulateRgba(Span rowSpan, Span nextRowSpan, Span dst, int width) + if ((width & 1) != 0) { - Bgra32 bgra0; - Bgra32 bgra1; - int i, j; - int dstIdx = 0; - for (i = 0, j = 0; i < width >> 1; i += 1, j += 2, dstIdx += 4) + bgra0 = rowSpan[j]; + bgra1 = nextRowSpan[j]; + uint a = (uint)(2u * (bgra0.A + bgra1.A)); + int r, g, b; + if (a is 4 * 0xff or 0) { - bgra0 = rowSpan[j]; - bgra1 = rowSpan[j + 1]; - Bgra32 bgra2 = nextRowSpan[j]; - Bgra32 bgra3 = nextRowSpan[j + 1]; - uint a = (uint)(bgra0.A + bgra1.A + bgra2.A + bgra3.A); - int r, g, b; - if (a is 4 * 0xff or 0) - { - r = (ushort)LinearToGamma( - GammaToLinear(bgra0.R) + - GammaToLinear(bgra1.R) + - GammaToLinear(bgra2.R) + - GammaToLinear(bgra3.R), - 0); - g = (ushort)LinearToGamma( - GammaToLinear(bgra0.G) + - GammaToLinear(bgra1.G) + - GammaToLinear(bgra2.G) + - GammaToLinear(bgra3.G), - 0); - b = (ushort)LinearToGamma( - GammaToLinear(bgra0.B) + - GammaToLinear(bgra1.B) + - GammaToLinear(bgra2.B) + - GammaToLinear(bgra3.B), - 0); - } - else - { - r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra2.R, bgra3.R, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra2.G, bgra3.G, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra2.B, bgra3.B, bgra0.A, bgra1.A, bgra2.A, bgra3.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; + r = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); + g = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); + b = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); } - - if ((width & 1) != 0) + else { - bgra0 = rowSpan[j]; - bgra1 = nextRowSpan[j]; - uint a = (uint)(2u * (bgra0.A + bgra1.A)); - int r, g, b; - if (a is 4 * 0xff or 0) - { - r = (ushort)LinearToGamma(GammaToLinear(bgra0.R) + GammaToLinear(bgra1.R), 1); - g = (ushort)LinearToGamma(GammaToLinear(bgra0.G) + GammaToLinear(bgra1.G), 1); - b = (ushort)LinearToGamma(GammaToLinear(bgra0.B) + GammaToLinear(bgra1.B), 1); - } - else - { - r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra0.R, bgra1.R, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra0.G, bgra1.G, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra0.B, bgra1.B, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); - } - - dst[dstIdx] = (ushort)r; - dst[dstIdx + 1] = (ushort)g; - dst[dstIdx + 2] = (ushort)b; - dst[dstIdx + 3] = (ushort)a; + r = LinearToGammaWeighted(bgra0.R, bgra1.R, bgra0.R, bgra1.R, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); + g = LinearToGammaWeighted(bgra0.G, bgra1.G, bgra0.G, bgra1.G, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); + b = LinearToGammaWeighted(bgra0.B, bgra1.B, bgra0.B, bgra1.B, bgra0.A, bgra1.A, bgra0.A, bgra1.A, a); } - } - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) - { - uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); - return LinearToGamma((sum * WebpLookupTables.InvAlpha[totalA]) >> (WebpConstants.AlphaFix - 2), 0); + dst[dstIdx] = (ushort)r; + dst[dstIdx + 1] = (ushort)g; + dst[dstIdx + 2] = (ushort)b; + dst[dstIdx + 3] = (ushort)a; } + } - // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision - // U/V value, suitable for RGBToU/V calls. - [MethodImpl(InliningOptions.ShortMethod)] - private static int LinearToGamma(uint baseValue, int shift) - { - int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. - return (y + WebpConstants.GammaTabRounder) >> WebpConstants.GammaTabFix; // Descale. - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int LinearToGammaWeighted(byte rgb0, byte rgb1, byte rgb2, byte rgb3, byte a0, byte a1, byte a2, byte a3, uint totalA) + { + uint sum = (a0 * GammaToLinear(rgb0)) + (a1 * GammaToLinear(rgb1)) + (a2 * GammaToLinear(rgb2)) + (a3 * GammaToLinear(rgb3)); + return LinearToGamma((sum * WebpLookupTables.InvAlpha[totalA]) >> (WebpConstants.AlphaFix - 2), 0); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static uint GammaToLinear(byte v) => WebpLookupTables.GammaToLinearTab[v]; + // Convert a linear value 'v' to YUV_FIX+2 fixed-point precision + // U/V value, suitable for RGBToU/V calls. + [MethodImpl(InliningOptions.ShortMethod)] + private static int LinearToGamma(uint baseValue, int shift) + { + int y = Interpolate((int)(baseValue << shift)); // Final uplifted value. + return (y + WebpConstants.GammaTabRounder) >> WebpConstants.GammaTabFix; // Descale. + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int Interpolate(int v) - { - int tabPos = v >> (WebpConstants.GammaTabFix + 2); // integer part. - int x = v & ((WebpConstants.GammaTabScale << 2) - 1); // fractional part. - int v0 = WebpLookupTables.LinearToGammaTab[tabPos]; - int v1 = WebpLookupTables.LinearToGammaTab[tabPos + 1]; - int y = (v1 * x) + (v0 * ((WebpConstants.GammaTabScale << 2) - x)); // interpolate + [MethodImpl(InliningOptions.ShortMethod)] + private static uint GammaToLinear(byte v) => WebpLookupTables.GammaToLinearTab[v]; - return y; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Interpolate(int v) + { + int tabPos = v >> (WebpConstants.GammaTabFix + 2); // integer part. + int x = v & ((WebpConstants.GammaTabScale << 2) - 1); // fractional part. + int v0 = WebpLookupTables.LinearToGammaTab[tabPos]; + int v1 = WebpLookupTables.LinearToGammaTab[tabPos + 1]; + int y = (v1 * x) + (v0 * ((WebpConstants.GammaTabScale << 2) - x)); // interpolate - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToY(byte r, byte g, byte b, int rounding) - { - int luma = (16839 * r) + (33059 * g) + (6420 * b); - return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. - } + return y; + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToU(int r, int g, int b, int rounding) - { - int u = (-9719 * r) - (19081 * g) + (28800 * b); - return ClipUv(u, rounding); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToY(byte r, byte g, byte b, int rounding) + { + int luma = (16839 * r) + (33059 * g) + (6420 * b); + return (luma + rounding + (16 << YuvFix)) >> YuvFix; // No need to clip. + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int RgbToV(int r, int g, int b, int rounding) - { - int v = (+28800 * r) - (24116 * g) - (4684 * b); - return ClipUv(v, rounding); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToU(int r, int g, int b, int rounding) + { + int u = (-9719 * r) - (19081 * g) + (28800 * b); + return ClipUv(u, rounding); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static int ClipUv(int uv, int rounding) - { - uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); - return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; - } + [MethodImpl(InliningOptions.ShortMethod)] + private static int RgbToV(int r, int g, int b, int rounding) + { + int v = (+28800 * r) - (24116 * g) - (4684 * b); + return ClipUv(v, rounding); + } - [MethodImpl(InliningOptions.ShortMethod)] - public static uint LoadUv(byte u, byte v) => - (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). + [MethodImpl(InliningOptions.ShortMethod)] + private static int ClipUv(int uv, int rounding) + { + uv = (uv + rounding + (128 << (YuvFix + 2))) >> (YuvFix + 2); + return (uv & ~0xff) == 0 ? uv : uv < 0 ? 0 : 255; + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void YuvToBgr(int y, int u, int v, Span bgr) - { - bgr[2] = (byte)YuvToR(y, v); - bgr[1] = (byte)YuvToG(y, u, v); - bgr[0] = (byte)YuvToB(y, u); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static uint LoadUv(byte u, byte v) => + (uint)(u | (v << 16)); // We process u and v together stashed into 32bit(16bit each). - [MethodImpl(InliningOptions.ShortMethod)] - private static void ConvertYuvToBgrSse41(Span topY, Span topDst, Span ru, Span rv, int curX, int step) => YuvToBgrSse41(topY[curX..], ru, rv, topDst[(curX * step)..]); + [MethodImpl(InliningOptions.ShortMethod)] + public static void YuvToBgr(int y, int u, int v, Span bgr) + { + bgr[2] = (byte)YuvToR(y, v); + bgr[1] = (byte)YuvToG(y, u, v); + bgr[0] = (byte)YuvToB(y, u); + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void ConvertYuvToBgrWithBottomYSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) - { - YuvToBgrSse41(topY[curX..], ru, rv, topDst[(curX * step)..]); - YuvToBgrSse41(bottomY[curX..], ru[64..], rv[64..], bottomDst[(curX * step)..]); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvertYuvToBgrSse41(Span topY, Span topDst, Span ru, Span rv, int curX, int step) => YuvToBgrSse41(topY[curX..], ru, rv, topDst[(curX * step)..]); - private static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) - { - ref byte yRef = ref MemoryMarshal.GetReference(y); - ref byte uRef = ref MemoryMarshal.GetReference(u); - ref byte vRef = ref MemoryMarshal.GetReference(v); - ConvertYuv444ToBgrSse41(ref yRef, ref uRef, ref vRef, out Vector128 r0, out Vector128 g0, out Vector128 b0); - ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 8), ref Unsafe.Add(ref uRef, 8), ref Unsafe.Add(ref vRef, 8), out Vector128 r1, out Vector128 g1, out Vector128 b1); - ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 16), ref Unsafe.Add(ref uRef, 16), ref Unsafe.Add(ref vRef, 16), out Vector128 r2, out Vector128 g2, out Vector128 b2); - ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 24), ref Unsafe.Add(ref uRef, 24), ref Unsafe.Add(ref vRef, 24), out Vector128 r3, out Vector128 g3, out Vector128 b3); - - // Cast to 8b and store as BBBBGGGGRRRR. - Vector128 bgr0 = Sse2.PackUnsignedSaturate(b0, b1); - Vector128 bgr1 = Sse2.PackUnsignedSaturate(b2, b3); - Vector128 bgr2 = Sse2.PackUnsignedSaturate(g0, g1); - Vector128 bgr3 = Sse2.PackUnsignedSaturate(g2, g3); - Vector128 bgr4 = Sse2.PackUnsignedSaturate(r0, r1); - Vector128 bgr5 = Sse2.PackUnsignedSaturate(r2, r3); - - // Pack as BGRBGRBGRBGR. - PlanarTo24bSse41(bgr0, bgr1, bgr2, bgr3, bgr4, bgr5, dst); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvertYuvToBgrWithBottomYSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) + { + YuvToBgrSse41(topY[curX..], ru, rv, topDst[(curX * step)..]); + YuvToBgrSse41(bottomY[curX..], ru[64..], rv[64..], bottomDst[(curX * step)..]); + } - // Pack the planar buffers - // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... - // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... - private static void PlanarTo24bSse41(Vector128 input0, Vector128 input1, Vector128 input2, Vector128 input3, Vector128 input4, Vector128 input5, Span rgb) - { - // The input is 6 registers of sixteen 8b but for the sake of explanation, - // let's take 6 registers of four 8b values. - // To pack, we will keep taking one every two 8b integer and move it - // around as follows: - // Input: - // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 - // Split the 6 registers in two sets of 3 registers: the first set as the even - // 8b bytes, the second the odd ones: - // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 - // Repeat the same permutations twice more: - // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 - // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 - - // Process R. - ChannelMixing( - input0, - input1, - Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5), // PlanarTo24Shuffle0 - Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255), // PlanarTo24Shuffle1 - Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255), // PlanarTo24Shuffle2 - out Vector128 r0, - out Vector128 r1, - out Vector128 r2, - out Vector128 r3, - out Vector128 r4, - out Vector128 r5); - - // Process G. - // Same as before, just shifted to the left by one and including the right padding. - ChannelMixing( - input2, - input3, - Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255), // PlanarTo24Shuffle3 - Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10), // PlanarTo24Shuffle4 - Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255), // PlanarTo24Shuffle5 - out Vector128 g0, - out Vector128 g1, - out Vector128 g2, - out Vector128 g3, - out Vector128 g4, - out Vector128 g5); - - // Process B. - ChannelMixing( - input4, - input5, - Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255), // PlanarTo24Shuffle6 - Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255), // PlanarTo24Shuffle7 - Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15), // PlanarTo24Shuffle8 - out Vector128 b0, - out Vector128 b1, - out Vector128 b2, - out Vector128 b3, - out Vector128 b4, - out Vector128 b5); - - // OR the different channels. - Vector128 rg0 = Sse2.Or(r0, g0); - Vector128 rg1 = Sse2.Or(r1, g1); - Vector128 rg2 = Sse2.Or(r2, g2); - Vector128 rg3 = Sse2.Or(r3, g3); - Vector128 rg4 = Sse2.Or(r4, g4); - Vector128 rg5 = Sse2.Or(r5, g5); - - ref byte outputRef = ref MemoryMarshal.GetReference(rgb); - Unsafe.As>(ref outputRef) = Sse2.Or(rg0, b0); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 16)) = Sse2.Or(rg1, b1); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 32)) = Sse2.Or(rg2, b2); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 48)) = Sse2.Or(rg3, b3); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 64)) = Sse2.Or(rg4, b4); - Unsafe.As>(ref Unsafe.Add(ref outputRef, 80)) = Sse2.Or(rg5, b5); - } + private static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) + { + ref byte yRef = ref MemoryMarshal.GetReference(y); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + ConvertYuv444ToBgrSse41(ref yRef, ref uRef, ref vRef, out Vector128 r0, out Vector128 g0, out Vector128 b0); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 8), ref Unsafe.Add(ref uRef, 8), ref Unsafe.Add(ref vRef, 8), out Vector128 r1, out Vector128 g1, out Vector128 b1); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 16), ref Unsafe.Add(ref uRef, 16), ref Unsafe.Add(ref vRef, 16), out Vector128 r2, out Vector128 g2, out Vector128 b2); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 24), ref Unsafe.Add(ref uRef, 24), ref Unsafe.Add(ref vRef, 24), out Vector128 r3, out Vector128 g3, out Vector128 b3); + + // Cast to 8b and store as BBBBGGGGRRRR. + Vector128 bgr0 = Sse2.PackUnsignedSaturate(b0, b1); + Vector128 bgr1 = Sse2.PackUnsignedSaturate(b2, b3); + Vector128 bgr2 = Sse2.PackUnsignedSaturate(g0, g1); + Vector128 bgr3 = Sse2.PackUnsignedSaturate(g2, g3); + Vector128 bgr4 = Sse2.PackUnsignedSaturate(r0, r1); + Vector128 bgr5 = Sse2.PackUnsignedSaturate(r2, r3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24bSse41(bgr0, bgr1, bgr2, bgr3, bgr4, bgr5, dst); + } - // Shuffles the input buffer as A0 0 0 A1 0 0 A2 - private static void ChannelMixing( - Vector128 input0, - Vector128 input1, - Vector128 shuffle0, - Vector128 shuffle1, - Vector128 shuffle2, - out Vector128 output0, - out Vector128 output1, - out Vector128 output2, - out Vector128 output3, - out Vector128 output4, - out Vector128 output5) - { - output0 = Ssse3.Shuffle(input0, shuffle0); - output1 = Ssse3.Shuffle(input0, shuffle1); - output2 = Ssse3.Shuffle(input0, shuffle2); - output3 = Ssse3.Shuffle(input1, shuffle0); - output4 = Ssse3.Shuffle(input1, shuffle1); - output5 = Ssse3.Shuffle(input1, shuffle2); - } + // Pack the planar buffers + // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... + // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... + private static void PlanarTo24bSse41(Vector128 input0, Vector128 input1, Vector128 input2, Vector128 input3, Vector128 input4, Vector128 input5, Span rgb) + { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + + // Process R. + ChannelMixing( + input0, + input1, + Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5), // PlanarTo24Shuffle0 + Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255), // PlanarTo24Shuffle1 + Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255), // PlanarTo24Shuffle2 + out Vector128 r0, + out Vector128 r1, + out Vector128 r2, + out Vector128 r3, + out Vector128 r4, + out Vector128 r5); + + // Process G. + // Same as before, just shifted to the left by one and including the right padding. + ChannelMixing( + input2, + input3, + Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255), // PlanarTo24Shuffle3 + Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10), // PlanarTo24Shuffle4 + Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255), // PlanarTo24Shuffle5 + out Vector128 g0, + out Vector128 g1, + out Vector128 g2, + out Vector128 g3, + out Vector128 g4, + out Vector128 g5); + + // Process B. + ChannelMixing( + input4, + input5, + Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255), // PlanarTo24Shuffle6 + Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255), // PlanarTo24Shuffle7 + Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15), // PlanarTo24Shuffle8 + out Vector128 b0, + out Vector128 b1, + out Vector128 b2, + out Vector128 b3, + out Vector128 b4, + out Vector128 b5); + + // OR the different channels. + Vector128 rg0 = Sse2.Or(r0, g0); + Vector128 rg1 = Sse2.Or(r1, g1); + Vector128 rg2 = Sse2.Or(r2, g2); + Vector128 rg3 = Sse2.Or(r3, g3); + Vector128 rg4 = Sse2.Or(r4, g4); + Vector128 rg5 = Sse2.Or(r5, g5); + + ref byte outputRef = ref MemoryMarshal.GetReference(rgb); + Unsafe.As>(ref outputRef) = Sse2.Or(rg0, b0); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 16)) = Sse2.Or(rg1, b1); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 32)) = Sse2.Or(rg2, b2); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 48)) = Sse2.Or(rg3, b3); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 64)) = Sse2.Or(rg4, b4); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 80)) = Sse2.Or(rg5, b5); + } - // Convert 32 samples of YUV444 to B/G/R - private static void ConvertYuv444ToBgrSse41(ref byte y, ref byte u, ref byte v, out Vector128 r, out Vector128 g, out Vector128 b) - { - // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. - Vector128 y0 = Unsafe.As>(ref y); - Vector128 u0 = Unsafe.As>(ref u); - Vector128 v0 = Unsafe.As>(ref v); - y0 = Sse2.UnpackLow(Vector128.Zero, y0); - u0 = Sse2.UnpackLow(Vector128.Zero, u0); - v0 = Sse2.UnpackLow(Vector128.Zero, v0); - - // These constants are 14b fixed-point version of ITU-R BT.601 constants. - // R = (19077 * y + 26149 * v - 14234) >> 6 - // G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 - // B = (19077 * y + 33050 * u - 17685) >> 6 - var k19077 = Vector128.Create((ushort)19077); - var k26149 = Vector128.Create((ushort)26149); - var k14234 = Vector128.Create((ushort)14234); - - Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), k19077); - Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), k26149); - Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create((ushort)6419)); - Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), Vector128.Create((ushort)13320)); - - Vector128 r1 = Sse2.Subtract(y1.AsUInt16(), k14234); - Vector128 r2 = Sse2.Add(r1, r0); - - Vector128 g2 = Sse2.Add(y1.AsUInt16(), Vector128.Create((ushort)8708)); - Vector128 g3 = Sse2.Add(g0, g1); - Vector128 g4 = Sse2.Subtract(g2, g3); - - Vector128 b0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129).AsUInt16()); - Vector128 b1 = Sse2.AddSaturate(b0, y1); - Vector128 b2 = Sse2.SubtractSaturate(b1, Vector128.Create((ushort)17685)); - - // Use logical shift for B2, which can be larger than 32767. - r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] - g = Sse2.ShiftRightArithmetic(g4.AsInt16(), 6); // range: [-10953, 27710] - b = Sse2.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] - } + // Shuffles the input buffer as A0 0 0 A1 0 0 A2 + private static void ChannelMixing( + Vector128 input0, + Vector128 input1, + Vector128 shuffle0, + Vector128 shuffle1, + Vector128 shuffle2, + out Vector128 output0, + out Vector128 output1, + out Vector128 output2, + out Vector128 output3, + out Vector128 output4, + out Vector128 output5) + { + output0 = Ssse3.Shuffle(input0, shuffle0); + output1 = Ssse3.Shuffle(input0, shuffle1); + output2 = Ssse3.Shuffle(input0, shuffle2); + output3 = Ssse3.Shuffle(input1, shuffle0); + output4 = Ssse3.Shuffle(input1, shuffle1); + output5 = Ssse3.Shuffle(input1, shuffle2); + } + + // Convert 32 samples of YUV444 to B/G/R + private static void ConvertYuv444ToBgrSse41(ref byte y, ref byte u, ref byte v, out Vector128 r, out Vector128 g, out Vector128 b) + { + // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. + Vector128 y0 = Unsafe.As>(ref y); + Vector128 u0 = Unsafe.As>(ref u); + Vector128 v0 = Unsafe.As>(ref v); + y0 = Sse2.UnpackLow(Vector128.Zero, y0); + u0 = Sse2.UnpackLow(Vector128.Zero, u0); + v0 = Sse2.UnpackLow(Vector128.Zero, v0); + + // These constants are 14b fixed-point version of ITU-R BT.601 constants. + // R = (19077 * y + 26149 * v - 14234) >> 6 + // G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 + // B = (19077 * y + 33050 * u - 17685) >> 6 + var k19077 = Vector128.Create((ushort)19077); + var k26149 = Vector128.Create((ushort)26149); + var k14234 = Vector128.Create((ushort)14234); + + Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), k19077); + Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), k26149); + Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create((ushort)6419)); + Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), Vector128.Create((ushort)13320)); + + Vector128 r1 = Sse2.Subtract(y1.AsUInt16(), k14234); + Vector128 r2 = Sse2.Add(r1, r0); + + Vector128 g2 = Sse2.Add(y1.AsUInt16(), Vector128.Create((ushort)8708)); + Vector128 g3 = Sse2.Add(g0, g1); + Vector128 g4 = Sse2.Subtract(g2, g3); + + Vector128 b0 = Sse2.MultiplyHigh(u0.AsUInt16(), Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129).AsUInt16()); + Vector128 b1 = Sse2.AddSaturate(b0, y1); + Vector128 b2 = Sse2.SubtractSaturate(b1, Vector128.Create((ushort)17685)); + + // Use logical shift for B2, which can be larger than 32767. + r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] + g = Sse2.ShiftRightArithmetic(g4.AsInt16(), 6); // range: [-10953, 27710] + b = Sse2.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] + } - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToG(int y, int u, int v) => Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); - [MethodImpl(InliningOptions.ShortMethod)] - public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); + [MethodImpl(InliningOptions.ShortMethod)] + public static int YuvToR(int y, int v) => Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); - [MethodImpl(InliningOptions.ShortMethod)] - private static int MultHi(int v, int coeff) => (v * coeff) >> 8; + [MethodImpl(InliningOptions.ShortMethod)] + private static int MultHi(int v, int coeff) => (v * coeff) >> 8; - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Clip8(int v) - { - const int yuvMask = (256 << 6) - 1; - return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Clip8(int v) + { + const int yuvMask = (256 << 6) - 1; + return (byte)((v & ~yuvMask) == 0 ? v >> 6 : v < 0 ? 0 : 255); } } diff --git a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs index 94063fae90..7f0920f2dd 100644 --- a/src/ImageSharp/Formats/Webp/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Webp/MetadataExtensions.cs @@ -4,25 +4,24 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions { /// - /// Extension methods for the type. + /// Gets the webp format specific metadata for the image. /// - public static partial class MetadataExtensions - { - /// - /// Gets the webp format specific metadata for the image. - /// - /// The metadata this method extends. - /// The . - public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); + /// The metadata this method extends. + /// The . + public static WebpMetadata GetWebpMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); - /// - /// Gets the webp format specific metadata for the image frame. - /// - /// The metadata this method extends. - /// The . - public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); - } + /// + /// Gets the webp format specific metadata for the image frame. + /// + /// The metadata this method extends. + /// The . + public static WebpFrameMetadata GetWebpMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(WebpFormat.Instance); } diff --git a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs index 2b5916357c..bbd355374d 100644 --- a/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpAlphaCompressionMethod.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal enum WebpAlphaCompressionMethod { - internal enum WebpAlphaCompressionMethod - { - /// - /// No compression. - /// - NoCompression = 0, + /// + /// No compression. + /// + NoCompression = 0, - /// - /// Compressed using the Webp lossless format. - /// - WebpLosslessCompression = 1 - } + /// + /// Compressed using the Webp lossless format. + /// + WebpLosslessCompression = 1 } diff --git a/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs b/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs index 1c6f8c65d9..e2fd43d05e 100644 --- a/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs +++ b/src/ImageSharp/Formats/Webp/WebpAlphaFilterType.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Enum for the different alpha filter types. +/// +internal enum WebpAlphaFilterType { /// - /// Enum for the different alpha filter types. + /// No filtering. /// - internal enum WebpAlphaFilterType - { - /// - /// No filtering. - /// - None = 0, + None = 0, - /// - /// Horizontal filter. - /// - Horizontal = 1, + /// + /// Horizontal filter. + /// + Horizontal = 1, - /// - /// Vertical filter. - /// - Vertical = 2, + /// + /// Vertical filter. + /// + Vertical = 2, - /// - /// Gradient filter. - /// - Gradient = 3, - } + /// + /// Gradient filter. + /// + Gradient = 3, } diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index 2be64492ee..6c45e26614 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Webp.Lossless; @@ -11,377 +10,376 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Decoder for animated webp images. +/// +internal class WebpAnimationDecoder : IDisposable { /// - /// Decoder for animated webp images. + /// Reusable buffer. /// - internal class WebpAnimationDecoder : IDisposable - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// Used for allocating memory during the decoding operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The global configuration. - /// - private readonly Configuration configuration; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// The area to restore. - /// - private Rectangle? restoreArea; - - /// - /// The abstract metadata. - /// - private ImageMetadata metadata; - - /// - /// The gif specific metadata. - /// - private WebpMetadata webpMetadata; - - /// - /// The alpha data, if an ALPH chunk is present. - /// - private IMemoryOwner alphaData; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - /// The global configuration. - /// The maximum number of frames to decode. Inclusive. - public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames) - { - this.memoryAllocator = memoryAllocator; - this.configuration = configuration; - this.maxFrames = maxFrames; - } + private readonly byte[] buffer = new byte[4]; - /// - /// Decodes the animated webp image from the specified stream. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The webp features. - /// The width of the image. - /// The height of the image. - /// The size of the image data in bytes. - public Image Decode(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize) - where TPixel : unmanaged, IPixel - { - Image image = null; - ImageFrame previousFrame = null; + /// + /// Used for allocating memory during the decoding operations. + /// + private readonly MemoryAllocator memoryAllocator; - this.metadata = new ImageMetadata(); - this.webpMetadata = this.metadata.GetWebpMetadata(); - this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; + /// + /// The global configuration. + /// + private readonly Configuration configuration; - uint frameCount = 0; - int remainingBytes = (int)completeDataSize; - while (remainingBytes > 0) - { - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); - remainingBytes -= 4; - switch (chunkType) - { - case WebpChunkType.Animation: - uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor.Value); - remainingBytes -= (int)dataSize; - break; - case WebpChunkType.Xmp: - case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer); - break; - default: - WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data"); - break; - } - - if (stream.Position == stream.Length || ++frameCount == this.maxFrames) - { - break; - } - } + /// + /// The maximum number of frames to decode. Inclusive. + /// + private readonly uint maxFrames; - return image; - } + /// + /// The area to restore. + /// + private Rectangle? restoreArea; - /// - /// Reads an individual webp frame. - /// - /// The pixel format. - /// The stream, where the image should be decoded from. Cannot be null. - /// The image to decode the information to. - /// The previous frame. - /// The width of the image. - /// The height of the image. - /// The default background color of the canvas in. - private uint ReadFrame(BufferedReadStream stream, ref Image image, ref ImageFrame previousFrame, uint width, uint height, Color backgroundColor) - where TPixel : unmanaged, IPixel - { - AnimationFrameData frameData = this.ReadFrameHeader(stream); - long streamStartPosition = stream.Position; + /// + /// The abstract metadata. + /// + private ImageMetadata metadata; - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); - bool hasAlpha = false; - byte alphaChunkHeader = 0; - if (chunkType is WebpChunkType.Alpha) - { - alphaChunkHeader = this.ReadAlphaData(stream); - hasAlpha = true; - chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); - } + /// + /// The gif specific metadata. + /// + private WebpMetadata webpMetadata; + + /// + /// The alpha data, if an ALPH chunk is present. + /// + private IMemoryOwner alphaData; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + /// The global configuration. + /// The maximum number of frames to decode. Inclusive. + public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames) + { + this.memoryAllocator = memoryAllocator; + this.configuration = configuration; + this.maxFrames = maxFrames; + } + + /// + /// Decodes the animated webp image from the specified stream. + /// + /// The pixel format. + /// The stream, where the image should be decoded from. Cannot be null. + /// The webp features. + /// The width of the image. + /// The height of the image. + /// The size of the image data in bytes. + public Image Decode(BufferedReadStream stream, WebpFeatures features, uint width, uint height, uint completeDataSize) + where TPixel : unmanaged, IPixel + { + Image image = null; + ImageFrame previousFrame = null; + + this.metadata = new ImageMetadata(); + this.webpMetadata = this.metadata.GetWebpMetadata(); + this.webpMetadata.AnimationLoopCount = features.AnimationLoopCount; - WebpImageInfo webpInfo = null; - WebpFeatures features = new(); + uint frameCount = 0; + int remainingBytes = (int)completeDataSize; + while (remainingBytes > 0) + { + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + remainingBytes -= 4; switch (chunkType) { - case WebpChunkType.Vp8: - webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); - features.Alpha = hasAlpha; - features.AlphaChunkHeader = alphaChunkHeader; + case WebpChunkType.Animation: + uint dataSize = this.ReadFrame(stream, ref image, ref previousFrame, width, height, features.AnimationBackgroundColor.Value); + remainingBytes -= (int)dataSize; break; - case WebpChunkType.Vp8L: - webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); + case WebpChunkType.Xmp: + case WebpChunkType.Exif: + WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image.Metadata, false, this.buffer); break; default: - WebpThrowHelper.ThrowImageFormatException("Read unexpected chunk type, should be VP8 or VP8L"); + WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data"); break; } - ImageFrame currentFrame = null; - ImageFrame imageFrame; - if (previousFrame is null) + if (stream.Position == stream.Length || ++frameCount == this.maxFrames) { - image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata); - - SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration); - - imageFrame = image.Frames.RootFrame; + break; } - else - { - currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. + } - SetFrameMetadata(currentFrame.Metadata, frameData.Duration); + return image; + } - imageFrame = currentFrame; - } + /// + /// Reads an individual webp frame. + /// + /// The pixel format. + /// The stream, where the image should be decoded from. Cannot be null. + /// The image to decode the information to. + /// The previous frame. + /// The width of the image. + /// The height of the image. + /// The default background color of the canvas in. + private uint ReadFrame(BufferedReadStream stream, ref Image image, ref ImageFrame previousFrame, uint width, uint height, Color backgroundColor) + where TPixel : unmanaged, IPixel + { + AnimationFrameData frameData = this.ReadFrameHeader(stream); + long streamStartPosition = stream.Position; - int frameX = (int)(frameData.X * 2); - int frameY = (int)(frameData.Y * 2); - int frameWidth = (int)frameData.Width; - int frameHeight = (int)frameData.Height; - Rectangle regionRectangle = Rectangle.FromLTRB(frameX, frameY, frameX + frameWidth, frameY + frameHeight); + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + bool hasAlpha = false; + byte alphaChunkHeader = 0; + if (chunkType is WebpChunkType.Alpha) + { + alphaChunkHeader = this.ReadAlphaData(stream); + hasAlpha = true; + chunkType = WebpChunkParsingUtils.ReadChunkType(stream, this.buffer); + } - if (frameData.DisposalMethod is AnimationDisposalMethod.Dispose) - { - this.RestoreToBackground(imageFrame, backgroundColor); - } + WebpImageInfo webpInfo = null; + WebpFeatures features = new(); + switch (chunkType) + { + case WebpChunkType.Vp8: + webpInfo = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, stream, this.buffer, features); + features.Alpha = hasAlpha; + features.AlphaChunkHeader = alphaChunkHeader; + break; + case WebpChunkType.Vp8L: + webpInfo = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, stream, this.buffer, features); + break; + default: + WebpThrowHelper.ThrowImageFormatException("Read unexpected chunk type, should be VP8 or VP8L"); + break; + } - using Buffer2D decodedImage = this.DecodeImageData(frameData, webpInfo); - DrawDecodedImageOnCanvas(decodedImage, imageFrame, frameX, frameY, frameWidth, frameHeight); + ImageFrame currentFrame = null; + ImageFrame imageFrame; + if (previousFrame is null) + { + image = new Image(this.configuration, (int)width, (int)height, backgroundColor.ToPixel(), this.metadata); - if (previousFrame != null && frameData.BlendingMethod is AnimationBlendingMethod.AlphaBlending) - { - this.AlphaBlend(previousFrame, imageFrame, frameX, frameY, frameWidth, frameHeight); - } + SetFrameMetadata(image.Frames.RootFrame.Metadata, frameData.Duration); + + imageFrame = image.Frames.RootFrame; + } + else + { + currentFrame = image.Frames.AddFrame(previousFrame); // This clones the frame and adds it the collection. - previousFrame = currentFrame ?? image.Frames.RootFrame; - this.restoreArea = regionRectangle; + SetFrameMetadata(currentFrame.Metadata, frameData.Duration); - return (uint)(stream.Position - streamStartPosition); + imageFrame = currentFrame; } - /// - /// Sets the frames metadata. - /// - /// The metadata. - /// The frame duration. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetFrameMetadata(ImageFrameMetadata meta, uint duration) + int frameX = (int)(frameData.X * 2); + int frameY = (int)(frameData.Y * 2); + int frameWidth = (int)frameData.Width; + int frameHeight = (int)frameData.Height; + Rectangle regionRectangle = Rectangle.FromLTRB(frameX, frameY, frameX + frameWidth, frameY + frameHeight); + + if (frameData.DisposalMethod is AnimationDisposalMethod.Dispose) { - WebpFrameMetadata frameMetadata = meta.GetWebpMetadata(); - frameMetadata.FrameDuration = duration; + this.RestoreToBackground(imageFrame, backgroundColor); } - /// - /// Reads the ALPH chunk data. - /// - /// The stream to read from. - private byte ReadAlphaData(BufferedReadStream stream) + using Buffer2D decodedImage = this.DecodeImageData(frameData, webpInfo); + DrawDecodedImageOnCanvas(decodedImage, imageFrame, frameX, frameY, frameWidth, frameHeight); + + if (previousFrame != null && frameData.BlendingMethod is AnimationBlendingMethod.AlphaBlending) { - this.alphaData?.Dispose(); + this.AlphaBlend(previousFrame, imageFrame, frameX, frameY, frameWidth, frameHeight); + } - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); - int alphaDataSize = (int)(alphaChunkSize - 1); - this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); + previousFrame = currentFrame ?? image.Frames.RootFrame; + this.restoreArea = regionRectangle; - byte alphaChunkHeader = (byte)stream.ReadByte(); - Span alphaData = this.alphaData.GetSpan(); - stream.Read(alphaData, 0, alphaDataSize); + return (uint)(stream.Position - streamStartPosition); + } - return alphaChunkHeader; - } + /// + /// Sets the frames metadata. + /// + /// The metadata. + /// The frame duration. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetFrameMetadata(ImageFrameMetadata meta, uint duration) + { + WebpFrameMetadata frameMetadata = meta.GetWebpMetadata(); + frameMetadata.FrameDuration = duration; + } - /// - /// Decodes the either lossy or lossless webp image data. - /// - /// The pixel format. - /// The frame data. - /// The webp information. - /// A decoded image. - private Buffer2D DecodeImageData(AnimationFrameData frameData, WebpImageInfo webpInfo) - where TPixel : unmanaged, IPixel - { - Image decodedImage = new((int)frameData.Width, (int)frameData.Height); + /// + /// Reads the ALPH chunk data. + /// + /// The stream to read from. + private byte ReadAlphaData(BufferedReadStream stream) + { + this.alphaData?.Dispose(); - try - { - Buffer2D pixelBufferDecoded = decodedImage.Frames.RootFrame.PixelBuffer; - if (webpInfo.IsLossless) - { - WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); - losslessDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height); - } - else - { - WebpLossyDecoder lossyDecoder = new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); - } - - return pixelBufferDecoded; - } - catch + uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer); + int alphaDataSize = (int)(alphaChunkSize - 1); + this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); + + byte alphaChunkHeader = (byte)stream.ReadByte(); + Span alphaData = this.alphaData.GetSpan(); + stream.Read(alphaData, 0, alphaDataSize); + + return alphaChunkHeader; + } + + /// + /// Decodes the either lossy or lossless webp image data. + /// + /// The pixel format. + /// The frame data. + /// The webp information. + /// A decoded image. + private Buffer2D DecodeImageData(AnimationFrameData frameData, WebpImageInfo webpInfo) + where TPixel : unmanaged, IPixel + { + Image decodedImage = new((int)frameData.Width, (int)frameData.Height); + + try + { + Buffer2D pixelBufferDecoded = decodedImage.Frames.RootFrame.PixelBuffer; + if (webpInfo.IsLossless) { - decodedImage?.Dispose(); - throw; + WebpLosslessDecoder losslessDecoder = new(webpInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + losslessDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height); } - finally + else { - webpInfo.Dispose(); + WebpLossyDecoder lossyDecoder = new(webpInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + lossyDecoder.Decode(pixelBufferDecoded, (int)webpInfo.Width, (int)webpInfo.Height, webpInfo, this.alphaData); } - } - /// - /// Draws the decoded image on canvas. The decoded image can be smaller the canvas. - /// - /// The type of the pixel. - /// The decoded image. - /// The image frame to draw into. - /// The frame x coordinate. - /// The frame y coordinate. - /// The width of the frame. - /// The height of the frame. - private static void DrawDecodedImageOnCanvas(Buffer2D decodedImage, ImageFrame imageFrame, int frameX, int frameY, int frameWidth, int frameHeight) - where TPixel : unmanaged, IPixel + return pixelBufferDecoded; + } + catch { - Buffer2D imageFramePixels = imageFrame.PixelBuffer; - int decodedRowIdx = 0; - for (int y = frameY; y < frameY + frameHeight; y++) - { - Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); - Span decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..frameWidth]; - decodedPixelRow.TryCopyTo(framePixelRow[frameX..]); - } + decodedImage?.Dispose(); + throw; } - - /// - /// After disposing of the previous frame, render the current frame on the canvas using alpha-blending. - /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. - /// - /// The pixel format. - /// The source image. - /// The destination image. - /// The frame x coordinate. - /// The frame y coordinate. - /// The width of the frame. - /// The height of the frame. - private void AlphaBlend(ImageFrame src, ImageFrame dst, int frameX, int frameY, int frameWidth, int frameHeight) - where TPixel : unmanaged, IPixel + finally { - Buffer2D srcPixels = src.PixelBuffer; - Buffer2D dstPixels = dst.PixelBuffer; - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - for (int y = frameY; y < frameY + frameHeight; y++) - { - Span srcPixelRow = srcPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); - Span dstPixelRow = dstPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); + webpInfo.Dispose(); + } + } - blender.Blend(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1.0f); - } + /// + /// Draws the decoded image on canvas. The decoded image can be smaller the canvas. + /// + /// The type of the pixel. + /// The decoded image. + /// The image frame to draw into. + /// The frame x coordinate. + /// The frame y coordinate. + /// The width of the frame. + /// The height of the frame. + private static void DrawDecodedImageOnCanvas(Buffer2D decodedImage, ImageFrame imageFrame, int frameX, int frameY, int frameWidth, int frameHeight) + where TPixel : unmanaged, IPixel + { + Buffer2D imageFramePixels = imageFrame.PixelBuffer; + int decodedRowIdx = 0; + for (int y = frameY; y < frameY + frameHeight; y++) + { + Span framePixelRow = imageFramePixels.DangerousGetRowSpan(y); + Span decodedPixelRow = decodedImage.DangerousGetRowSpan(decodedRowIdx++)[..frameWidth]; + decodedPixelRow.TryCopyTo(framePixelRow[frameX..]); } + } - /// - /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame - /// with background color specified in the ANIM chunk. - /// - /// The pixel format. - /// The image frame. - /// Color of the background. - private void RestoreToBackground(ImageFrame imageFrame, Color backgroundColor) - where TPixel : unmanaged, IPixel + /// + /// After disposing of the previous frame, render the current frame on the canvas using alpha-blending. + /// If the current frame does not have an alpha channel, assume alpha value of 255, effectively replacing the rectangle. + /// + /// The pixel format. + /// The source image. + /// The destination image. + /// The frame x coordinate. + /// The frame y coordinate. + /// The width of the frame. + /// The height of the frame. + private void AlphaBlend(ImageFrame src, ImageFrame dst, int frameX, int frameY, int frameWidth, int frameHeight) + where TPixel : unmanaged, IPixel + { + Buffer2D srcPixels = src.PixelBuffer; + Buffer2D dstPixels = dst.PixelBuffer; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + for (int y = frameY; y < frameY + frameHeight; y++) { - if (!this.restoreArea.HasValue) - { - return; - } + Span srcPixelRow = srcPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); + Span dstPixelRow = dstPixels.DangerousGetRowSpan(y).Slice(frameX, frameWidth); - Rectangle interest = Rectangle.Intersect(imageFrame.Bounds(), this.restoreArea.Value); - Buffer2DRegion pixelRegion = imageFrame.PixelBuffer.GetRegion(interest); - TPixel backgroundPixel = backgroundColor.ToPixel(); - pixelRegion.Fill(backgroundPixel); + blender.Blend(this.configuration, dstPixelRow, srcPixelRow, dstPixelRow, 1.0f); } + } - /// - /// Reads the animation frame header. - /// - /// The stream to read from. - /// Animation frame data. - private AnimationFrameData ReadFrameHeader(BufferedReadStream stream) + /// + /// Dispose to background color. Fill the rectangle on the canvas covered by the current frame + /// with background color specified in the ANIM chunk. + /// + /// The pixel format. + /// The image frame. + /// Color of the background. + private void RestoreToBackground(ImageFrame imageFrame, Color backgroundColor) + where TPixel : unmanaged, IPixel + { + if (!this.restoreArea.HasValue) { - AnimationFrameData data = new() - { - DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer), + return; + } - // 3 bytes for the X coordinate of the upper left corner of the frame. - X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), + Rectangle interest = Rectangle.Intersect(imageFrame.Bounds(), this.restoreArea.Value); + Buffer2DRegion pixelRegion = imageFrame.PixelBuffer.GetRegion(interest); + TPixel backgroundPixel = backgroundColor.ToPixel(); + pixelRegion.Fill(backgroundPixel); + } - // 3 bytes for the Y coordinate of the upper left corner of the frame. - Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), + /// + /// Reads the animation frame header. + /// + /// The stream to read from. + /// Animation frame data. + private AnimationFrameData ReadFrameHeader(BufferedReadStream stream) + { + AnimationFrameData data = new() + { + DataSize = WebpChunkParsingUtils.ReadChunkSize(stream, this.buffer), - // Frame width Minus One. - Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, + // 3 bytes for the X coordinate of the upper left corner of the frame. + X = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), - // Frame height Minus One. - Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, + // 3 bytes for the Y coordinate of the upper left corner of the frame. + Y = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer), - // Frame duration. - Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) - }; + // Frame width Minus One. + Width = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, - byte flags = (byte)stream.ReadByte(); - data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose; - data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending; + // Frame height Minus One. + Height = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + 1, - return data; - } + // Frame duration. + Duration = WebpChunkParsingUtils.ReadUnsignedInt24Bit(stream, this.buffer) + }; + + byte flags = (byte)stream.ReadByte(); + data.DisposalMethod = (flags & 1) == 1 ? AnimationDisposalMethod.Dispose : AnimationDisposalMethod.DoNotDispose; + data.BlendingMethod = (flags & (1 << 1)) != 0 ? AnimationBlendingMethod.DoNotBlend : AnimationBlendingMethod.AlphaBlending; - /// - public void Dispose() => this.alphaData?.Dispose(); + return data; } + + /// + public void Dispose() => this.alphaData?.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs index 9ebca0d328..529c4bafb9 100644 --- a/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Webp/WebpBitsPerPixel.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Enumerates the available bits per pixel the webp image uses. +/// +public enum WebpBitsPerPixel : short { /// - /// Enumerates the available bits per pixel the webp image uses. + /// 24 bits per pixel. Each pixel consists of 3 bytes. /// - public enum WebpBitsPerPixel : short - { - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Pixel24 = 24, + Pixel24 = 24, - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present). - /// - Pixel32 = 32 - } + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present). + /// + Pixel32 = 32 } diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index cd8ee8d818..e4acdf311c 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.Lossy; @@ -11,365 +10,364 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal static class WebpChunkParsingUtils { - internal static class WebpChunkParsingUtils + /// + /// Reads the header of a lossy webp image. + /// + /// Information about this webp image. + public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) { - /// - /// Reads the header of a lossy webp image. - /// - /// Information about this webp image. - public static WebpImageInfo ReadVp8Header(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) + // VP8 data size (not including this 4 bytes). + int bytesRead = stream.Read(buffer, 0, 4); + if (bytesRead != 4) { - // VP8 data size (not including this 4 bytes). - int bytesRead = stream.Read(buffer, 0, 4); - if (bytesRead != 4) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); - } - - uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - - // Remaining counts the available image data payload. - uint remaining = dataSize; - - // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 - // Frame tag that contains four fields: - // - A 1-bit frame type (0 for key frames, 1 for interframes). - // - A 3-bit version number. - // - A 1-bit show_frame flag. - // - A 19-bit field containing the size of the first data partition in bytes. - bytesRead = stream.Read(buffer, 0, 3); - if (bytesRead != 3) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); - } + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); + } - uint frameTag = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16)); - remaining -= 3; - bool isNoKeyFrame = (frameTag & 0x1) == 1; - if (isNoKeyFrame) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); - } + uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - uint version = (frameTag >> 1) & 0x7; - if (version > 3) - { - WebpThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); - } + // Remaining counts the available image data payload. + uint remaining = dataSize; - bool invisibleFrame = ((frameTag >> 4) & 0x1) == 0; - if (invisibleFrame) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); - } + // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30 + // Frame tag that contains four fields: + // - A 1-bit frame type (0 for key frames, 1 for interframes). + // - A 3-bit version number. + // - A 1-bit show_frame flag. + // - A 19-bit field containing the size of the first data partition in bytes. + bytesRead = stream.Read(buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header"); + } - uint partitionLength = frameTag >> 5; - if (partitionLength > dataSize) - { - WebpThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); - } + uint frameTag = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16)); + remaining -= 3; + bool isNoKeyFrame = (frameTag & 0x1) == 1; + if (isNoKeyFrame) + { + WebpThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame"); + } - // Check for VP8 magic bytes. - bytesRead = stream.Read(buffer, 0, 3); - if (bytesRead != 3) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes"); - } + uint version = (frameTag >> 1) & 0x7; + if (version > 3) + { + WebpThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}"); + } - if (!buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) - { - WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); - } + bool invisibleFrame = ((frameTag >> 4) & 0x1) == 0; + if (invisibleFrame) + { + WebpThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible"); + } - bytesRead = stream.Read(buffer, 0, 4); - if (bytesRead != 4) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header, could not read width and height"); - } + uint partitionLength = frameTag >> 5; + if (partitionLength > dataSize) + { + WebpThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information"); + } - uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer); - uint width = tmp & 0x3fff; - sbyte xScale = (sbyte)(tmp >> 6); - tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2)); - uint height = tmp & 0x3fff; - sbyte yScale = (sbyte)(tmp >> 6); - remaining -= 7; - if (width == 0 || height == 0) - { - WebpThrowHelper.ThrowImageFormatException("width or height can not be zero"); - } + // Check for VP8 magic bytes. + bytesRead = stream.Read(buffer, 0, 3); + if (bytesRead != 3) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes"); + } - if (partitionLength > remaining) - { - WebpThrowHelper.ThrowImageFormatException("bad partition length"); - } + if (!buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) + { + WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); + } - var vp8FrameHeader = new Vp8FrameHeader() - { - KeyFrame = true, - Profile = (sbyte)version, - PartitionLength = partitionLength - }; - - var bitReader = new Vp8BitReader( - stream, - remaining, - memoryAllocator, - partitionLength) - { - Remaining = remaining - }; + bytesRead = stream.Read(buffer, 0, 4); + if (bytesRead != 4) + { + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 header, could not read width and height"); + } - return new WebpImageInfo() - { - Width = width, - Height = height, - XScale = xScale, - YScale = yScale, - BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Pixel32 : WebpBitsPerPixel.Pixel24, - IsLossless = false, - Features = features, - Vp8Profile = (sbyte)version, - Vp8FrameHeader = vp8FrameHeader, - Vp8BitReader = bitReader - }; + uint tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer); + uint width = tmp & 0x3fff; + sbyte xScale = (sbyte)(tmp >> 6); + tmp = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(2)); + uint height = tmp & 0x3fff; + sbyte yScale = (sbyte)(tmp >> 6); + remaining -= 7; + if (width == 0 || height == 0) + { + WebpThrowHelper.ThrowImageFormatException("width or height can not be zero"); } - /// - /// Reads the header of a lossless webp image. - /// - /// Information about this image. - public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) + if (partitionLength > remaining) { - // VP8 data size. - uint imageDataSize = ReadChunkSize(stream, buffer); + WebpThrowHelper.ThrowImageFormatException("bad partition length"); + } - var bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator); + var vp8FrameHeader = new Vp8FrameHeader() + { + KeyFrame = true, + Profile = (sbyte)version, + PartitionLength = partitionLength + }; - // One byte signature, should be 0x2f. - uint signature = bitReader.ReadValue(8); - if (signature != WebpConstants.Vp8LHeaderMagicByte) - { - WebpThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); - } + var bitReader = new Vp8BitReader( + stream, + remaining, + memoryAllocator, + partitionLength) + { + Remaining = remaining + }; - // The first 28 bits of the bitstream specify the width and height of the image. - uint width = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; - uint height = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; - if (width == 0 || height == 0) - { - WebpThrowHelper.ThrowImageFormatException("invalid width or height read"); - } + return new WebpImageInfo() + { + Width = width, + Height = height, + XScale = xScale, + YScale = yScale, + BitsPerPixel = features?.Alpha == true ? WebpBitsPerPixel.Pixel32 : WebpBitsPerPixel.Pixel24, + IsLossless = false, + Features = features, + Vp8Profile = (sbyte)version, + Vp8FrameHeader = vp8FrameHeader, + Vp8BitReader = bitReader + }; + } - // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. - // TODO: this flag value is not used yet - bool alphaIsUsed = bitReader.ReadBit(); + /// + /// Reads the header of a lossless webp image. + /// + /// Information about this image. + public static WebpImageInfo ReadVp8LHeader(MemoryAllocator memoryAllocator, BufferedReadStream stream, byte[] buffer, WebpFeatures features) + { + // VP8 data size. + uint imageDataSize = ReadChunkSize(stream, buffer); - // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0. - // Any other value should be treated as an error. - uint version = bitReader.ReadValue(WebpConstants.Vp8LVersionBits); - if (version != 0) - { - WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); - } + var bitReader = new Vp8LBitReader(stream, imageDataSize, memoryAllocator); - return new WebpImageInfo() - { - Width = width, - Height = height, - BitsPerPixel = WebpBitsPerPixel.Pixel32, - IsLossless = true, - Features = features, - Vp8LBitReader = bitReader - }; + // One byte signature, should be 0x2f. + uint signature = bitReader.ReadValue(8); + if (signature != WebpConstants.Vp8LHeaderMagicByte) + { + WebpThrowHelper.ThrowImageFormatException("Invalid VP8L signature"); } - /// - /// Reads an the extended webp file header. An extended file header consists of: - /// - A 'VP8X' chunk with information about features used in the file. - /// - An optional 'ICCP' chunk with color profile. - /// - An optional 'XMP' chunk with metadata. - /// - An optional 'ANIM' chunk with animation control data. - /// - An optional 'ALPH' chunk with alpha channel data. - /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. - /// - /// Information about this webp image. - public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features) + // The first 28 bits of the bitstream specify the width and height of the image. + uint width = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; + uint height = bitReader.ReadValue(WebpConstants.Vp8LImageSizeBits) + 1; + if (width == 0 || height == 0) { - uint fileSize = ReadChunkSize(stream, buffer); + WebpThrowHelper.ThrowImageFormatException("invalid width or height read"); + } - // The first byte contains information about the image features used. - byte imageFeatures = (byte)stream.ReadByte(); + // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise. + // TODO: this flag value is not used yet + bool alphaIsUsed = bitReader.ReadBit(); - // The first two bit of it are reserved and should be 0. - if (imageFeatures >> 6 != 0) - { - WebpThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); - } + // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0. + // Any other value should be treated as an error. + uint version = bitReader.ReadValue(WebpConstants.Vp8LVersionBits); + if (version != 0) + { + WebpThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header"); + } - // If bit 3 is set, a ICC Profile Chunk should be present. - features.IccProfile = (imageFeatures & (1 << 5)) != 0; + return new WebpImageInfo() + { + Width = width, + Height = height, + BitsPerPixel = WebpBitsPerPixel.Pixel32, + IsLossless = true, + Features = features, + Vp8LBitReader = bitReader + }; + } - // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk). - features.Alpha = (imageFeatures & (1 << 4)) != 0; + /// + /// Reads an the extended webp file header. An extended file header consists of: + /// - A 'VP8X' chunk with information about features used in the file. + /// - An optional 'ICCP' chunk with color profile. + /// - An optional 'XMP' chunk with metadata. + /// - An optional 'ANIM' chunk with animation control data. + /// - An optional 'ALPH' chunk with alpha channel data. + /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. + /// + /// Information about this webp image. + public static WebpImageInfo ReadVp8XHeader(BufferedReadStream stream, byte[] buffer, WebpFeatures features) + { + uint fileSize = ReadChunkSize(stream, buffer); - // If bit 5 is set, a EXIF metadata should be present. - features.ExifProfile = (imageFeatures & (1 << 3)) != 0; + // The first byte contains information about the image features used. + byte imageFeatures = (byte)stream.ReadByte(); - // If bit 6 is set, XMP metadata should be present. - features.XmpMetaData = (imageFeatures & (1 << 2)) != 0; + // The first two bit of it are reserved and should be 0. + if (imageFeatures >> 6 != 0) + { + WebpThrowHelper.ThrowImageFormatException("first two bits of the VP8X header are expected to be zero"); + } - // If bit 7 is set, animation should be present. - features.Animation = (imageFeatures & (1 << 1)) != 0; + // If bit 3 is set, a ICC Profile Chunk should be present. + features.IccProfile = (imageFeatures & (1 << 5)) != 0; - // 3 reserved bytes should follow which are supposed to be zero. - stream.Read(buffer, 0, 3); - if (buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0) - { - WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); - } + // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk). + features.Alpha = (imageFeatures & (1 << 4)) != 0; - // 3 bytes for the width. - uint width = ReadUnsignedInt24Bit(stream, buffer) + 1; + // If bit 5 is set, a EXIF metadata should be present. + features.ExifProfile = (imageFeatures & (1 << 3)) != 0; - // 3 bytes for the height. - uint height = ReadUnsignedInt24Bit(stream, buffer) + 1; + // If bit 6 is set, XMP metadata should be present. + features.XmpMetaData = (imageFeatures & (1 << 2)) != 0; - // Read all the chunks in the order they occur. - var info = new WebpImageInfo() - { - Width = width, - Height = height, - Features = features - }; + // If bit 7 is set, animation should be present. + features.Animation = (imageFeatures & (1 << 1)) != 0; - return info; + // 3 reserved bytes should follow which are supposed to be zero. + stream.Read(buffer, 0, 3); + if (buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0) + { + WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); } - /// - /// Reads a unsigned 24 bit integer. - /// - /// The stream to read from. - /// The buffer to store the read data into. - /// A unsigned 24 bit integer. - public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer) - { - if (stream.Read(buffer, 0, 3) == 3) - { - buffer[3] = 0; - return BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } + // 3 bytes for the width. + uint width = ReadUnsignedInt24Bit(stream, buffer) + 1; - throw new ImageFormatException("Invalid Webp data, could not read unsigned integer."); - } + // 3 bytes for the height. + uint height = ReadUnsignedInt24Bit(stream, buffer) + 1; - /// - /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, - /// so the chunk size will be increased by 1 in those cases. - /// - /// The stream to read the data from. - /// Buffer to store the data read from the stream. - /// The chunk size in bytes. - public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer) + // Read all the chunks in the order they occur. + var info = new WebpImageInfo() { - if (stream.Read(buffer, 0, 4) == 4) - { - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); - return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; - } + Width = width, + Height = height, + Features = features + }; + + return info; + } - throw new ImageFormatException("Invalid Webp data, could not read chunk size."); + /// + /// Reads a unsigned 24 bit integer. + /// + /// The stream to read from. + /// The buffer to store the read data into. + /// A unsigned 24 bit integer. + public static uint ReadUnsignedInt24Bit(BufferedReadStream stream, byte[] buffer) + { + if (stream.Read(buffer, 0, 3) == 3) + { + buffer[3] = 0; + return BinaryPrimitives.ReadUInt32LittleEndian(buffer); } - /// - /// Identifies the chunk type from the chunk. - /// - /// The stream to read the data from. - /// Buffer to store the data read from the stream. - /// - /// Thrown if the input stream is not valid. - /// - public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer) + throw new ImageFormatException("Invalid Webp data, could not read unsigned integer."); + } + + /// + /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, + /// so the chunk size will be increased by 1 in those cases. + /// + /// The stream to read the data from. + /// Buffer to store the data read from the stream. + /// The chunk size in bytes. + public static uint ReadChunkSize(BufferedReadStream stream, byte[] buffer) + { + if (stream.Read(buffer, 0, 4) == 4) { - if (stream.Read(buffer, 0, 4) == 4) - { - var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); - return chunkType; - } + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(buffer); + return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; + } + + throw new ImageFormatException("Invalid Webp data, could not read chunk size."); + } - throw new ImageFormatException("Invalid Webp data, could not read chunk type."); + /// + /// Identifies the chunk type from the chunk. + /// + /// The stream to read the data from. + /// Buffer to store the data read from the stream. + /// + /// Thrown if the input stream is not valid. + /// + public static WebpChunkType ReadChunkType(BufferedReadStream stream, byte[] buffer) + { + if (stream.Read(buffer, 0, 4) == 4) + { + var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer); + return chunkType; } - /// - /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). - /// If there are more such chunks, readers MAY ignore all except the first one. - /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. - /// - public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, byte[] buffer) + throw new ImageFormatException("Invalid Webp data, could not read chunk type."); + } + + /// + /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP '). + /// If there are more such chunks, readers MAY ignore all except the first one. + /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks. + /// + public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType chunkType, ImageMetadata metadata, bool ignoreMetaData, byte[] buffer) + { + long streamLength = stream.Length; + while (stream.Position < streamLength) { - long streamLength = stream.Length; - while (stream.Position < streamLength) + uint chunkLength = ReadChunkSize(stream, buffer); + + if (ignoreMetaData) { - uint chunkLength = ReadChunkSize(stream, buffer); + stream.Skip((int)chunkLength); + } - if (ignoreMetaData) - { + int bytesRead; + switch (chunkType) + { + case WebpChunkType.Exif: + byte[] exifData = new byte[chunkLength]; + bytesRead = stream.Read(exifData, 0, (int)chunkLength); + if (bytesRead != chunkLength) + { + WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); + } + + if (metadata.ExifProfile != null) + { + metadata.ExifProfile = new ExifProfile(exifData); + } + + break; + case WebpChunkType.Xmp: + byte[] xmpData = new byte[chunkLength]; + bytesRead = stream.Read(xmpData, 0, (int)chunkLength); + if (bytesRead != chunkLength) + { + WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); + } + + if (metadata.XmpProfile != null) + { + metadata.XmpProfile = new XmpProfile(xmpData); + } + + break; + default: stream.Skip((int)chunkLength); - } - - int bytesRead; - switch (chunkType) - { - case WebpChunkType.Exif: - byte[] exifData = new byte[chunkLength]; - bytesRead = stream.Read(exifData, 0, (int)chunkLength); - if (bytesRead != chunkLength) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); - } - - if (metadata.ExifProfile != null) - { - metadata.ExifProfile = new ExifProfile(exifData); - } - - break; - case WebpChunkType.Xmp: - byte[] xmpData = new byte[chunkLength]; - bytesRead = stream.Read(xmpData, 0, (int)chunkLength); - if (bytesRead != chunkLength) - { - WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); - } - - if (metadata.XmpProfile != null) - { - metadata.XmpProfile = new XmpProfile(xmpData); - } - - break; - default: - stream.Skip((int)chunkLength); - break; - } + break; } } - - /// - /// Determines if the chunk type is an optional VP8X chunk. - /// - /// The chunk type. - /// True, if its an optional chunk type. - public static bool IsOptionalVp8XChunk(WebpChunkType chunkType) => chunkType switch - { - WebpChunkType.Alpha => true, - WebpChunkType.AnimationParameter => true, - WebpChunkType.Exif => true, - WebpChunkType.Iccp => true, - WebpChunkType.Xmp => true, - _ => false - }; } + + /// + /// Determines if the chunk type is an optional VP8X chunk. + /// + /// The chunk type. + /// True, if its an optional chunk type. + public static bool IsOptionalVp8XChunk(WebpChunkType chunkType) => chunkType switch + { + WebpChunkType.Alpha => true, + WebpChunkType.AnimationParameter => true, + WebpChunkType.Exif => true, + WebpChunkType.Iccp => true, + WebpChunkType.Xmp => true, + _ => false + }; } diff --git a/src/ImageSharp/Formats/Webp/WebpChunkType.cs b/src/ImageSharp/Formats/Webp/WebpChunkType.cs index ee22e3c1e6..802d7f7288 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkType.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkType.cs @@ -1,57 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Contains a list of different webp chunk types. +/// +/// See Webp Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container +internal enum WebpChunkType : uint { /// - /// Contains a list of different webp chunk types. - /// - /// See Webp Container Specification for more details: https://developers.google.com/speed/webp/docs/riff_container - internal enum WebpChunkType : uint - { - /// - /// Header signaling the use of the VP8 format. - /// - Vp8 = 0x56503820U, - - /// - /// Header signaling the image uses lossless encoding. - /// - Vp8L = 0x5650384CU, - - /// - /// Header for a extended-VP8 chunk. - /// - Vp8X = 0x56503858U, - - /// - /// Chunk contains information about the alpha channel. - /// - Alpha = 0x414C5048U, - - /// - /// Chunk which contains a color profile. - /// - Iccp = 0x49434350U, - - /// - /// Chunk which contains EXIF metadata about the image. - /// - Exif = 0x45584946U, - - /// - /// Chunk contains XMP metadata about the image. - /// - Xmp = 0x584D5020U, - - /// - /// For an animated image, this chunk contains the global parameters of the animation. - /// - AnimationParameter = 0x414E494D, - - /// - /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. - /// - Animation = 0x414E4D46, - } + /// Header signaling the use of the VP8 format. + /// + Vp8 = 0x56503820U, + + /// + /// Header signaling the image uses lossless encoding. + /// + Vp8L = 0x5650384CU, + + /// + /// Header for a extended-VP8 chunk. + /// + Vp8X = 0x56503858U, + + /// + /// Chunk contains information about the alpha channel. + /// + Alpha = 0x414C5048U, + + /// + /// Chunk which contains a color profile. + /// + Iccp = 0x49434350U, + + /// + /// Chunk which contains EXIF metadata about the image. + /// + Exif = 0x45584946U, + + /// + /// Chunk contains XMP metadata about the image. + /// + Xmp = 0x584D5020U, + + /// + /// For an animated image, this chunk contains the global parameters of the animation. + /// + AnimationParameter = 0x414E494D, + + /// + /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present. + /// + Animation = 0x414E4D46, } diff --git a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs index c5d8c30ed4..1a8fcbafc9 100644 --- a/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpCommonUtils.cs @@ -1,161 +1,159 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Utility methods for lossy and lossless webp format. +/// +internal static class WebpCommonUtils { /// - /// Utility methods for lossy and lossless webp format. + /// Checks if the pixel row is not opaque. /// - internal static class WebpCommonUtils + /// The row to check. + /// Returns true if alpha has non-0xff values. + public static unsafe bool CheckNonOpaque(Span row) { - /// - /// Checks if the pixel row is not opaque. - /// - /// The row to check. - /// Returns true if alpha has non-0xff values. - public static unsafe bool CheckNonOpaque(Span row) + if (Avx2.IsSupported) { - if (Avx2.IsSupported) + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) - { - var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); + var alphaMaskVector256 = Vector256.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector256 all0x80Vector256 = Vector256.Create((byte)0x80).AsByte(); - for (; i + 128 <= length; i += 128) + for (; i + 128 <= length; i += 128) + { + Vector256 a0 = Avx.LoadVector256(src + i).AsByte(); + Vector256 a1 = Avx.LoadVector256(src + i + 32).AsByte(); + Vector256 a2 = Avx.LoadVector256(src + i + 64).AsByte(); + Vector256 a3 = Avx.LoadVector256(src + i + 96).AsByte(); + Vector256 b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); + Vector256 b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); + Vector256 b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); + Vector256 b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); + Vector256 c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); + Vector256 c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); + Vector256 d = Avx2.PackSignedSaturate(c0, c1).AsByte(); + Vector256 bits = Avx2.CompareEqual(d, all0x80Vector256); + int mask = Avx2.MoveMask(bits); + if (mask != -1) { - Vector256 a0 = Avx.LoadVector256(src + i).AsByte(); - Vector256 a1 = Avx.LoadVector256(src + i + 32).AsByte(); - Vector256 a2 = Avx.LoadVector256(src + i + 64).AsByte(); - Vector256 a3 = Avx.LoadVector256(src + i + 96).AsByte(); - Vector256 b0 = Avx2.And(a0, alphaMaskVector256).AsInt32(); - Vector256 b1 = Avx2.And(a1, alphaMaskVector256).AsInt32(); - Vector256 b2 = Avx2.And(a2, alphaMaskVector256).AsInt32(); - Vector256 b3 = Avx2.And(a3, alphaMaskVector256).AsInt32(); - Vector256 c0 = Avx2.PackSignedSaturate(b0, b1).AsInt16(); - Vector256 c1 = Avx2.PackSignedSaturate(b2, b3).AsInt16(); - Vector256 d = Avx2.PackSignedSaturate(c0, c1).AsByte(); - Vector256 bits = Avx2.CompareEqual(d, all0x80Vector256); - int mask = Avx2.MoveMask(bits); - if (mask != -1) - { - return true; - } + return true; } + } - for (; i + 64 <= length; i += 64) + for (; i + 64 <= length; i += 64) + { + if (IsNoneOpaque64Bytes(src, i)) { - if (IsNoneOpaque64Bytes(src, i)) - { - return true; - } + return true; } + } - for (; i + 32 <= length; i += 32) + for (; i + 32 <= length; i += 32) + { + if (IsNoneOpaque32Bytes(src, i)) { - if (IsNoneOpaque32Bytes(src, i)) - { - return true; - } + return true; } + } - for (; i <= length; i += 4) + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) { - if (src[i + 3] != 0xFF) - { - return true; - } + return true; } } } - else if (Sse2.IsSupported) + } + else if (Sse2.IsSupported) + { + ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); + int i = 0; + int length = (row.Length * 4) - 3; + fixed (byte* src = rowBytes) { - ReadOnlySpan rowBytes = MemoryMarshal.AsBytes(row); - int i = 0; - int length = (row.Length * 4) - 3; - fixed (byte* src = rowBytes) + for (; i + 64 <= length; i += 64) { - for (; i + 64 <= length; i += 64) + if (IsNoneOpaque64Bytes(src, i)) { - if (IsNoneOpaque64Bytes(src, i)) - { - return true; - } + return true; } + } - for (; i + 32 <= length; i += 32) + for (; i + 32 <= length; i += 32) + { + if (IsNoneOpaque32Bytes(src, i)) { - if (IsNoneOpaque32Bytes(src, i)) - { - return true; - } + return true; } + } - for (; i <= length; i += 4) + for (; i <= length; i += 4) + { + if (src[i + 3] != 0xFF) { - if (src[i + 3] != 0xFF) - { - return true; - } + return true; } } } - else + } + else + { + for (int x = 0; x < row.Length; x++) { - for (int x = 0; x < row.Length; x++) + if (row[x].A != 0xFF) { - if (row[x].A != 0xFF) - { - return true; - } + return true; } } - - return false; } - private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i) - { - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + return false; + } - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); - Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); - Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); - Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); - int mask = Sse2.MoveMask(bits); - return mask != 0xFFFF; - } + private static unsafe bool IsNoneOpaque64Bytes(byte* src, int i) + { + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); - private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i) - { - var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 a2 = Sse2.LoadVector128(src + i + 32).AsByte(); + Vector128 a3 = Sse2.LoadVector128(src + i + 48).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 b2 = Sse2.And(a2, alphaMask).AsInt32(); + Vector128 b3 = Sse2.And(a3, alphaMask).AsInt32(); + Vector128 c0 = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 c1 = Sse2.PackSignedSaturate(b2, b3).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c0, c1).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); + int mask = Sse2.MoveMask(bits); + return mask != 0xFFFF; + } - Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); - Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); - Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); - Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); - Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); - Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); - Vector128 bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); - int mask = Sse2.MoveMask(bits); - return mask != 0xFFFF; - } + private static unsafe bool IsNoneOpaque32Bytes(byte* src, int i) + { + var alphaMask = Vector128.Create(0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255); + + Vector128 a0 = Sse2.LoadVector128(src + i).AsByte(); + Vector128 a1 = Sse2.LoadVector128(src + i + 16).AsByte(); + Vector128 b0 = Sse2.And(a0, alphaMask).AsInt32(); + Vector128 b1 = Sse2.And(a1, alphaMask).AsInt32(); + Vector128 c = Sse2.PackSignedSaturate(b0, b1).AsInt16(); + Vector128 d = Sse2.PackSignedSaturate(c, c).AsByte(); + Vector128 bits = Sse2.CompareEqual(d, Vector128.Create((byte)0x80).AsByte()); + int mask = Sse2.MoveMask(bits); + return mask != 0xFFFF; } } diff --git a/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs b/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs index c026b9f338..f266cde32f 100644 --- a/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Webp/WebpConfigurationModule.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Registers the image encoders, decoders and mime type detectors for the webp format. +/// +public sealed class WebpConfigurationModule : IConfigurationModule { - /// - /// Registers the image encoders, decoders and mime type detectors for the webp format. - /// - public sealed class WebpConfigurationModule : IConfigurationModule + /// + public void Configure(Configuration configuration) { - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); - configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); - } + configuration.ImageFormatsManager.SetDecoder(WebpFormat.Instance, new WebpDecoder()); + configuration.ImageFormatsManager.SetEncoder(WebpFormat.Instance, new WebpEncoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new WebpImageFormatDetector()); } } diff --git a/src/ImageSharp/Formats/Webp/WebpConstants.cs b/src/ImageSharp/Formats/Webp/WebpConstants.cs index 98740a88ad..d105d8dd62 100644 --- a/src/ImageSharp/Formats/Webp/WebpConstants.cs +++ b/src/ImageSharp/Formats/Webp/WebpConstants.cs @@ -1,358 +1,355 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Webp; -namespace SixLabors.ImageSharp.Formats.Webp +/// +/// Constants used for encoding and decoding VP8 and VP8L bitstreams. +/// +internal static class WebpConstants { /// - /// Constants used for encoding and decoding VP8 and VP8L bitstreams. + /// The list of file extensions that equate to Webp. /// - internal static class WebpConstants + public static readonly IEnumerable FileExtensions = new[] { "webp" }; + + /// + /// The list of mimetypes that equate to a jpeg. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/webp", }; + + /// + /// Signature which identifies a VP8 header. + /// + public static readonly byte[] Vp8HeaderMagicBytes = + { + 0x9D, + 0x01, + 0x2A + }; + + /// + /// Signature byte which identifies a VP8L header. + /// + public const byte Vp8LHeaderMagicByte = 0x2F; + + /// + /// Signature bytes identifying a lossy image. + /// + public static readonly byte[] Vp8MagicBytes = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x20 // ' ' + }; + + /// + /// Signature bytes identifying a lossless image. + /// + public static readonly byte[] Vp8LMagicBytes = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x4C // L + }; + + /// + /// Signature bytes identifying a VP8X header. + /// + public static readonly byte[] Vp8XMagicBytes = + { + 0x56, // V + 0x50, // P + 0x38, // 8 + 0x58 // X + }; + + /// + /// The header bytes identifying RIFF file. + /// + public static readonly byte[] RiffFourCc = + { + 0x52, // R + 0x49, // I + 0x46, // F + 0x46 // F + }; + + /// + /// The header bytes identifying a Webp. + /// + public static readonly byte[] WebpHeader = { - /// - /// The list of file extensions that equate to Webp. - /// - public static readonly IEnumerable FileExtensions = new[] { "webp" }; - - /// - /// The list of mimetypes that equate to a jpeg. - /// - public static readonly IEnumerable MimeTypes = new[] { "image/webp", }; - - /// - /// Signature which identifies a VP8 header. - /// - public static readonly byte[] Vp8HeaderMagicBytes = - { - 0x9D, - 0x01, - 0x2A - }; - - /// - /// Signature byte which identifies a VP8L header. - /// - public const byte Vp8LHeaderMagicByte = 0x2F; - - /// - /// Signature bytes identifying a lossy image. - /// - public static readonly byte[] Vp8MagicBytes = - { - 0x56, // V - 0x50, // P - 0x38, // 8 - 0x20 // ' ' - }; - - /// - /// Signature bytes identifying a lossless image. - /// - public static readonly byte[] Vp8LMagicBytes = - { - 0x56, // V - 0x50, // P - 0x38, // 8 - 0x4C // L - }; - - /// - /// Signature bytes identifying a VP8X header. - /// - public static readonly byte[] Vp8XMagicBytes = - { - 0x56, // V - 0x50, // P - 0x38, // 8 - 0x58 // X - }; - - /// - /// The header bytes identifying RIFF file. - /// - public static readonly byte[] RiffFourCc = - { - 0x52, // R - 0x49, // I - 0x46, // F - 0x46 // F - }; - - /// - /// The header bytes identifying a Webp. - /// - public static readonly byte[] WebpHeader = - { - 0x57, // W - 0x45, // E - 0x42, // B - 0x50 // P - }; - - /// - /// 3 bits reserved for version. - /// - public const int Vp8LVersionBits = 3; - - /// - /// Bits for width and height infos of a VPL8 image. - /// - public const int Vp8LImageSizeBits = 14; - - /// - /// Size of the frame header within VP8 data. - /// - public const int Vp8FrameHeaderSize = 10; - - /// - /// Size of a VP8X chunk in bytes. - /// - public const int Vp8XChunkSize = 10; - - /// - /// Size of a chunk header. - /// - public const int ChunkHeaderSize = 8; - - /// - /// Size of the RIFF header ("RIFFnnnnWEBP"). - /// - public const int RiffHeaderSize = 12; - - /// - /// Size of a chunk tag (e.g. "VP8L"). - /// - public const int TagSize = 4; - - /// - /// The Vp8L version 0. - /// - public const int Vp8LVersion = 0; - - /// - /// Maximum number of histogram images (sub-blocks). - /// - public const int MaxHuffImageSize = 2600; + 0x57, // W + 0x45, // E + 0x42, // B + 0x50 // P + }; + + /// + /// 3 bits reserved for version. + /// + public const int Vp8LVersionBits = 3; + + /// + /// Bits for width and height infos of a VPL8 image. + /// + public const int Vp8LImageSizeBits = 14; + + /// + /// Size of the frame header within VP8 data. + /// + public const int Vp8FrameHeaderSize = 10; + + /// + /// Size of a VP8X chunk in bytes. + /// + public const int Vp8XChunkSize = 10; + + /// + /// Size of a chunk header. + /// + public const int ChunkHeaderSize = 8; + + /// + /// Size of the RIFF header ("RIFFnnnnWEBP"). + /// + public const int RiffHeaderSize = 12; + + /// + /// Size of a chunk tag (e.g. "VP8L"). + /// + public const int TagSize = 4; + + /// + /// The Vp8L version 0. + /// + public const int Vp8LVersion = 0; + + /// + /// Maximum number of histogram images (sub-blocks). + /// + public const int MaxHuffImageSize = 2600; - /// - /// Minimum number of Huffman bits. - /// - public const int MinHuffmanBits = 2; + /// + /// Minimum number of Huffman bits. + /// + public const int MinHuffmanBits = 2; - /// - /// Maximum number of Huffman bits. - /// - public const int MaxHuffmanBits = 9; + /// + /// Maximum number of Huffman bits. + /// + public const int MaxHuffmanBits = 9; - /// - /// The maximum number of colors for a paletted images. - /// - public const int MaxPaletteSize = 256; + /// + /// The maximum number of colors for a paletted images. + /// + public const int MaxPaletteSize = 256; - /// - /// Maximum number of color cache bits is 10. - /// - public const int MaxColorCacheBits = 10; + /// + /// Maximum number of color cache bits is 10. + /// + public const int MaxColorCacheBits = 10; - /// - /// The maximum number of allowed transforms in a VP8L bitstream. - /// - public const int MaxNumberOfTransforms = 4; + /// + /// The maximum number of allowed transforms in a VP8L bitstream. + /// + public const int MaxNumberOfTransforms = 4; - /// - /// Maximum value of transformBits in VP8LEncoder. - /// - public const int MaxTransformBits = 6; + /// + /// Maximum value of transformBits in VP8LEncoder. + /// + public const int MaxTransformBits = 6; - /// - /// The bit to be written when next data to be read is a transform. - /// - public const int TransformPresent = 1; + /// + /// The bit to be written when next data to be read is a transform. + /// + public const int TransformPresent = 1; - /// - /// The maximum allowed width or height of a webp image. - /// - public const int MaxDimension = 16383; + /// + /// The maximum allowed width or height of a webp image. + /// + public const int MaxDimension = 16383; - public const int MaxAllowedCodeLength = 15; + public const int MaxAllowedCodeLength = 15; - public const int DefaultCodeLength = 8; + public const int DefaultCodeLength = 8; - public const int HuffmanCodesPerMetaCode = 5; + public const int HuffmanCodesPerMetaCode = 5; - public const uint ArgbBlack = 0xff000000; + public const uint ArgbBlack = 0xff000000; - public const int NumArgbCacheRows = 16; + public const int NumArgbCacheRows = 16; - public const int NumLiteralCodes = 256; + public const int NumLiteralCodes = 256; - public const int NumLengthCodes = 24; + public const int NumLengthCodes = 24; - public const int NumDistanceCodes = 40; + public const int NumDistanceCodes = 40; - public const int CodeLengthCodes = 19; + public const int CodeLengthCodes = 19; - public const int LengthTableBits = 7; + public const int LengthTableBits = 7; - public const uint CodeLengthLiterals = 16; + public const uint CodeLengthLiterals = 16; - public const int CodeLengthRepeatCode = 16; + public const int CodeLengthRepeatCode = 16; - public static readonly int[] CodeLengthExtraBits = { 2, 3, 7 }; + public static readonly int[] CodeLengthExtraBits = { 2, 3, 7 }; - public static readonly int[] CodeLengthRepeatOffsets = { 3, 3, 11 }; + public static readonly int[] CodeLengthRepeatOffsets = { 3, 3, 11 }; - public static readonly int[] AlphabetSize = - { - NumLiteralCodes + NumLengthCodes, - NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, - NumDistanceCodes - }; + public static readonly int[] AlphabetSize = + { + NumLiteralCodes + NumLengthCodes, + NumLiteralCodes, NumLiteralCodes, NumLiteralCodes, + NumDistanceCodes + }; - public const int NumMbSegments = 4; + public const int NumMbSegments = 4; - public const int MaxNumPartitions = 8; + public const int MaxNumPartitions = 8; - public const int NumTypes = 4; + public const int NumTypes = 4; - public const int NumBands = 8; + public const int NumBands = 8; - public const int NumProbas = 11; + public const int NumProbas = 11; - public const int NumPredModes = 4; + public const int NumPredModes = 4; - public const int NumBModes = 10; + public const int NumBModes = 10; - public const int NumCtx = 3; + public const int NumCtx = 3; - public const int MaxVariableLevel = 67; + public const int MaxVariableLevel = 67; - public const int FlatnessLimitI16 = 0; + public const int FlatnessLimitI16 = 0; - public const int FlatnessLimitIUv = 2; + public const int FlatnessLimitIUv = 2; - public const int FlatnessLimitI4 = 3; + public const int FlatnessLimitI4 = 3; - public const int FlatnessPenality = 140; + public const int FlatnessPenality = 140; - // This is the common stride for enc/dec. - public const int Bps = 32; + // This is the common stride for enc/dec. + public const int Bps = 32; - // gamma-compensates loss of resolution during chroma subsampling. - public const double Gamma = 0.80d; + // gamma-compensates loss of resolution during chroma subsampling. + public const double Gamma = 0.80d; - public const int GammaFix = 12; // Fixed-point precision for linear values. + public const int GammaFix = 12; // Fixed-point precision for linear values. - public const int GammaScale = (1 << GammaFix) - 1; + public const int GammaScale = (1 << GammaFix) - 1; - public const int GammaTabFix = 7; // Fixed-point fractional bits precision. + public const int GammaTabFix = 7; // Fixed-point fractional bits precision. - public const int GammaTabSize = 1 << (GammaFix - GammaTabFix); + public const int GammaTabSize = 1 << (GammaFix - GammaTabFix); - public const int GammaTabScale = 1 << GammaTabFix; + public const int GammaTabScale = 1 << GammaTabFix; - public const int GammaTabRounder = GammaTabScale >> 1; + public const int GammaTabRounder = GammaTabScale >> 1; - public const int AlphaFix = 19; + public const int AlphaFix = 19; - /// - /// 8b of precision for susceptibilities. - /// - public const int MaxAlpha = 255; + /// + /// 8b of precision for susceptibilities. + /// + public const int MaxAlpha = 255; - /// - /// Scaling factor for alpha. - /// - public const int AlphaScale = 2 * MaxAlpha; + /// + /// Scaling factor for alpha. + /// + public const int AlphaScale = 2 * MaxAlpha; - /// - /// Neutral value for susceptibility. - /// - public const int QuantEncMidAlpha = 64; + /// + /// Neutral value for susceptibility. + /// + public const int QuantEncMidAlpha = 64; - /// - /// Lowest usable value for susceptibility. - /// - public const int QuantEncMinAlpha = 30; + /// + /// Lowest usable value for susceptibility. + /// + public const int QuantEncMinAlpha = 30; - /// - /// Higher meaningful value for susceptibility. - /// - public const int QuantEncMaxAlpha = 100; + /// + /// Higher meaningful value for susceptibility. + /// + public const int QuantEncMaxAlpha = 100; - /// - /// Scaling constant between the sns (Spatial Noise Shaping) value and the QP power-law modulation. Must be strictly less than 1. - /// - public const double SnsToDq = 0.9; + /// + /// Scaling constant between the sns (Spatial Noise Shaping) value and the QP power-law modulation. Must be strictly less than 1. + /// + public const double SnsToDq = 0.9; - public const int QuantEncMaxDqUv = 6; + public const int QuantEncMaxDqUv = 6; - public const int QuantEncMinDqUv = -4; + public const int QuantEncMinDqUv = -4; - public const int QFix = 17; + public const int QFix = 17; - public const int MaxDelzaSize = 64; + public const int MaxDelzaSize = 64; - /// - /// Very small filter-strength values have close to no visual effect. So we can - /// save a little decoding-CPU by turning filtering off for these. - /// - public const int FilterStrengthCutoff = 2; + /// + /// Very small filter-strength values have close to no visual effect. So we can + /// save a little decoding-CPU by turning filtering off for these. + /// + public const int FilterStrengthCutoff = 2; - /// - /// Max size of mode partition. - /// - public const int Vp8MaxPartition0Size = 1 << 19; + /// + /// Max size of mode partition. + /// + public const int Vp8MaxPartition0Size = 1 << 19; - public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; + public static readonly short[] Vp8FixedCostsUv = { 302, 984, 439, 642 }; - public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; + public static readonly short[] Vp8FixedCostsI16 = { 663, 919, 872, 919 }; - /// - /// Distortion multiplier (equivalent of lambda). - /// - public const int RdDistoMult = 256; + /// + /// Distortion multiplier (equivalent of lambda). + /// + public const int RdDistoMult = 256; - /// - /// How many extra lines are needed on the MB boundary for caching, given a filtering level. - /// Simple filter(1): up to 2 luma samples are read and 1 is written. - /// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). - /// - public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; + /// + /// How many extra lines are needed on the MB boundary for caching, given a filtering level. + /// Simple filter(1): up to 2 luma samples are read and 1 is written. + /// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling). + /// + public static readonly byte[] FilterExtraRows = { 0, 2, 8 }; - // Paragraph 9.9 - public static readonly int[] Vp8EncBands = - { - 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 - }; + // Paragraph 9.9 + public static readonly int[] Vp8EncBands = + { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0 + }; - public static readonly short[] Scan = - { - 0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps), - 0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps), - 0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps), - 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) - }; - - // Residual decoding (Paragraph 13.2 / 13.3) - public static readonly byte[] Cat3 = { 173, 148, 140 }; - public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; - public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 }; - public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; - public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; - - public static readonly sbyte[] YModesIntra4 = - { - -0, 1, - -1, 2, - -2, 3, - 4, 6, - -3, 5, - -4, -5, - -6, 7, - -7, 8, - -8, -9 - }; - } + public static readonly short[] Scan = + { + 0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps), + 0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps), + 0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps), + 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps) + }; + + // Residual decoding (Paragraph 13.2 / 13.3) + public static readonly byte[] Cat3 = { 173, 148, 140 }; + public static readonly byte[] Cat4 = { 176, 155, 140, 135 }; + public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 }; + public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 }; + public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + + public static readonly sbyte[] YModesIntra4 = + { + -0, 1, + -1, 2, + -2, 3, + 4, 6, + -3, 5, + -4, -5, + -6, 7, + -7, 8, + -8, -9 + }; } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoder.cs b/src/ImageSharp/Formats/Webp/WebpDecoder.cs index 9b05de5ea3..86c868d7e8 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoder.cs @@ -1,43 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Image decoder for generating an image out of a webp stream. +/// +public sealed class WebpDecoder : IImageDecoder { - /// - /// Image decoder for generating an image out of a webp stream. - /// - public sealed class WebpDecoder : IImageDecoder + /// + IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - /// - IImageInfo IImageInfoDetector.Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - using WebpDecoderCore decoder = new(options); - return decoder.Identify(options.Configuration, stream, cancellationToken); - } + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + using WebpDecoderCore decoder = new(options); + return decoder.Identify(options.Configuration, stream, cancellationToken); + } - using WebpDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - ImageDecoderUtilities.Resize(options, image); + using WebpDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - return image; - } + ImageDecoderUtilities.Resize(options, image); - /// - Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoder)this).Decode(options, stream, cancellationToken); + return image; } + + /// + Image IImageDecoder.Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoder)this).Decode(options, stream, cancellationToken); } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 75f5f88f8b..4632200f4b 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -1,10 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Threading; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.IO; @@ -15,455 +13,454 @@ using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Performs the webp decoding operation. +/// +internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable { /// - /// Performs the webp decoding operation. + /// Reusable buffer. /// - internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable - { - /// - /// Reusable buffer. - /// - private readonly byte[] buffer = new byte[4]; - - /// - /// General configuration options. - /// - private readonly Configuration configuration; - - /// - /// A value indicating whether the metadata should be ignored when the image is being decoded. - /// - private readonly bool skipMetadata; - - /// - /// The maximum number of frames to decode. Inclusive. - /// - private readonly uint maxFrames; - - /// - /// Gets the decoded by this decoder instance. - /// - private ImageMetadata metadata; - - /// - /// Gets or sets the alpha data, if an ALPH chunk is present. - /// - private IMemoryOwner alphaData; - - /// - /// Used for allocating memory during the decoding operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// The stream to decode from. - /// - private BufferedReadStream currentStream; - - /// - /// The webp specific metadata. - /// - private WebpMetadata webpMetadata; - - /// - /// Information about the webp image. - /// - private WebpImageInfo webImageInfo; - - /// - /// Initializes a new instance of the class. - /// - /// The decoder options. - public WebpDecoderCore(DecoderOptions options) - { - this.Options = options; - this.configuration = options.Configuration; - this.skipMetadata = options.SkipMetadata; - this.maxFrames = options.MaxFrames; - this.memoryAllocator = this.configuration.MemoryAllocator; - } + private readonly byte[] buffer = new byte[4]; - /// - public DecoderOptions Options { get; } + /// + /// General configuration options. + /// + private readonly Configuration configuration; - /// - public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + /// + /// A value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool skipMetadata; - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Image image = null; - try - { - this.metadata = new ImageMetadata(); - this.currentStream = stream; + /// + /// The maximum number of frames to decode. Inclusive. + /// + private readonly uint maxFrames; - uint fileSize = this.ReadImageHeader(); + /// + /// Gets the decoded by this decoder instance. + /// + private ImageMetadata metadata; - using (this.webImageInfo = this.ReadVp8Info()) - { - if (this.webImageInfo.Features is { Animation: true }) - { - using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames); - return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); - } + /// + /// Gets or sets the alpha data, if an ALPH chunk is present. + /// + private IMemoryOwner alphaData; - if (this.webImageInfo.Features is { Animation: true }) - { - WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); - } + /// + /// Used for allocating memory during the decoding operations. + /// + private readonly MemoryAllocator memoryAllocator; - image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - if (this.webImageInfo.IsLossless) - { - var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); - losslessDecoder.Decode(pixels, image.Width, image.Height); - } - else - { - var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); - lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); - } + /// + /// The stream to decode from. + /// + private BufferedReadStream currentStream; - // There can be optional chunks after the image data, like EXIF and XMP. - if (this.webImageInfo.Features != null) - { - this.ParseOptionalChunks(this.webImageInfo.Features); - } + /// + /// The webp specific metadata. + /// + private WebpMetadata webpMetadata; - return image; - } - } - catch - { - image?.Dispose(); - throw; - } - } + /// + /// Information about the webp image. + /// + private WebpImageInfo webImageInfo; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public WebpDecoderCore(DecoderOptions options) + { + this.Options = options; + this.configuration = options.Configuration; + this.skipMetadata = options.SkipMetadata; + this.maxFrames = options.MaxFrames; + this.memoryAllocator = this.configuration.MemoryAllocator; + } + + /// + public DecoderOptions Options { get; } - /// - public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + /// + public Size Dimensions => new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height); + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Image image = null; + try { + this.metadata = new ImageMetadata(); this.currentStream = stream; - this.ReadImageHeader(); - using (this.webImageInfo = this.ReadVp8Info(true)) + uint fileSize = this.ReadImageHeader(); + + using (this.webImageInfo = this.ReadVp8Info()) { - return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); + if (this.webImageInfo.Features is { Animation: true }) + { + using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.configuration, this.maxFrames); + return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); + } + + if (this.webImageInfo.Features is { Animation: true }) + { + WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); + } + + image = new Image(this.configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (this.webImageInfo.IsLossless) + { + var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + losslessDecoder.Decode(pixels, image.Width, image.Height); + } + else + { + var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); + } + + // There can be optional chunks after the image data, like EXIF and XMP. + if (this.webImageInfo.Features != null) + { + this.ParseOptionalChunks(this.webImageInfo.Features); + } + + return image; } } + catch + { + image?.Dispose(); + throw; + } + } - /// - /// Reads and skips over the image header. - /// - /// The file size in bytes. - private uint ReadImageHeader() + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.currentStream = stream; + + this.ReadImageHeader(); + using (this.webImageInfo = this.ReadVp8Info(true)) { - // Skip FourCC header, we already know its a RIFF file at this point. - this.currentStream.Skip(4); + return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata); + } + } - // Read file size. - // The size of the file in bytes starting at offset 8. - // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. - uint fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); + /// + /// Reads and skips over the image header. + /// + /// The file size in bytes. + private uint ReadImageHeader() + { + // Skip FourCC header, we already know its a RIFF file at this point. + this.currentStream.Skip(4); - // Skip 'WEBP' from the header. - this.currentStream.Skip(4); + // Read file size. + // The size of the file in bytes starting at offset 8. + // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC. + uint fileSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); - return fileSize; - } + // Skip 'WEBP' from the header. + this.currentStream.Skip(4); - /// - /// Reads information present in the image header, about the image content and how to decode the image. - /// - /// For identify, the alpha data should not be read. - /// Information about the webp image. - private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) - { - this.metadata = new ImageMetadata(); - this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance); + return fileSize; + } + + /// + /// Reads information present in the image header, about the image content and how to decode the image. + /// + /// For identify, the alpha data should not be read. + /// Information about the webp image. + private WebpImageInfo ReadVp8Info(bool ignoreAlpha = false) + { + this.metadata = new ImageMetadata(); + this.webpMetadata = this.metadata.GetFormatMetadata(WebpFormat.Instance); - WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); - var features = new WebpFeatures(); - switch (chunkType) - { - case WebpChunkType.Vp8: - this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; - return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); - case WebpChunkType.Vp8L: - this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; - return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); - case WebpChunkType.Vp8X: - WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features); - while (this.currentStream.Position < this.currentStream.Length) + var features = new WebpFeatures(); + switch (chunkType) + { + case WebpChunkType.Vp8: + this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; + return WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); + case WebpChunkType.Vp8L: + this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; + return WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); + case WebpChunkType.Vp8X: + WebpImageInfo webpInfos = WebpChunkParsingUtils.ReadVp8XHeader(this.currentStream, this.buffer, features); + while (this.currentStream.Position < this.currentStream.Length) + { + chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); + if (chunkType == WebpChunkType.Vp8) { - chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); - if (chunkType == WebpChunkType.Vp8) - { - this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; - webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); - } - else if (chunkType == WebpChunkType.Vp8L) - { - this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; - webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); - } - else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) - { - bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha); - if (isAnimationChunk) - { - return webpInfos; - } - } - else + this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; + webpInfos = WebpChunkParsingUtils.ReadVp8Header(this.memoryAllocator, this.currentStream, this.buffer, features); + } + else if (chunkType == WebpChunkType.Vp8L) + { + this.webpMetadata.FileFormat = WebpFileFormatType.Lossless; + webpInfos = WebpChunkParsingUtils.ReadVp8LHeader(this.memoryAllocator, this.currentStream, this.buffer, features); + } + else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) + { + bool isAnimationChunk = this.ParseOptionalExtendedChunks(chunkType, features, ignoreAlpha); + if (isAnimationChunk) { - WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + return webpInfos; } } + else + { + WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + } + } - return webpInfos; - default: - WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); - return - new WebpImageInfo(); // this return will never be reached, because throw helper will throw an exception. - } + return webpInfos; + default: + WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); + return + new WebpImageInfo(); // this return will never be reached, because throw helper will throw an exception. } + } - /// - /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. - /// - /// The chunk type. - /// The webp image features. - /// For identify, the alpha data should not be read. - /// true, if its a alpha chunk. - private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) + /// + /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. + /// + /// The chunk type. + /// The webp image features. + /// For identify, the alpha data should not be read. + /// true, if its a alpha chunk. + private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) + { + switch (chunkType) { - switch (chunkType) - { - case WebpChunkType.Iccp: - this.ReadIccProfile(); - break; - - case WebpChunkType.Exif: - this.ReadExifProfile(); - break; - - case WebpChunkType.Xmp: - this.ReadXmpProfile(); - break; - - case WebpChunkType.AnimationParameter: - this.ReadAnimationParameters(features); - return true; - - case WebpChunkType.Alpha: - this.ReadAlphaData(features, ignoreAlpha); - break; - default: - WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); - break; - } - - return false; + case WebpChunkType.Iccp: + this.ReadIccProfile(); + break; + + case WebpChunkType.Exif: + this.ReadExifProfile(); + break; + + case WebpChunkType.Xmp: + this.ReadXmpProfile(); + break; + + case WebpChunkType.AnimationParameter: + this.ReadAnimationParameters(features); + return true; + + case WebpChunkType.Alpha: + this.ReadAlphaData(features, ignoreAlpha); + break; + default: + WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + break; } - /// - /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. - /// - /// The webp features. - private void ParseOptionalChunks(WebpFeatures features) - { - if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) - { - return; - } + return false; + } - long streamLength = this.currentStream.Length; - while (this.currentStream.Position < streamLength) - { - // Read chunk header. - WebpChunkType chunkType = this.ReadChunkType(); - if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null) - { - this.ReadExifProfile(); - } - else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) - { - this.ReadXmpProfile(); - } - else - { - // Skip duplicate XMP or EXIF chunk. - uint chunkLength = this.ReadChunkSize(); - this.currentStream.Skip((int)chunkLength); - } - } + /// + /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. + /// + /// The webp features. + private void ParseOptionalChunks(WebpFeatures features) + { + if (this.skipMetadata || (!features.ExifProfile && !features.XmpMetaData)) + { + return; } - /// - /// Reads the EXIF profile from the stream. - /// - private void ReadExifProfile() + long streamLength = this.currentStream.Length; + while (this.currentStream.Position < streamLength) { - uint exifChunkSize = this.ReadChunkSize(); - if (this.skipMetadata) - { - this.currentStream.Skip((int)exifChunkSize); - } - else + // Read chunk header. + WebpChunkType chunkType = this.ReadChunkType(); + if (chunkType == WebpChunkType.Exif && this.metadata.ExifProfile == null) { - byte[] exifData = new byte[exifChunkSize]; - int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize); - if (bytesRead != exifChunkSize) - { - // Ignore invalid chunk. - return; - } - - var profile = new ExifProfile(exifData); - this.metadata.ExifProfile = profile; + this.ReadExifProfile(); } - } - - /// - /// Reads the XMP profile the stream. - /// - private void ReadXmpProfile() - { - uint xmpChunkSize = this.ReadChunkSize(); - if (this.skipMetadata) + else if (chunkType == WebpChunkType.Xmp && this.metadata.XmpProfile == null) { - this.currentStream.Skip((int)xmpChunkSize); + this.ReadXmpProfile(); } else { - byte[] xmpData = new byte[xmpChunkSize]; - int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); - if (bytesRead != xmpChunkSize) - { - // Ignore invalid chunk. - return; - } - - var profile = new XmpProfile(xmpData); - this.metadata.XmpProfile = profile; + // Skip duplicate XMP or EXIF chunk. + uint chunkLength = this.ReadChunkSize(); + this.currentStream.Skip((int)chunkLength); } } + } - /// - /// Reads the ICCP chunk from the stream. - /// - private void ReadIccProfile() + /// + /// Reads the EXIF profile from the stream. + /// + private void ReadExifProfile() + { + uint exifChunkSize = this.ReadChunkSize(); + if (this.skipMetadata) + { + this.currentStream.Skip((int)exifChunkSize); + } + else { - uint iccpChunkSize = this.ReadChunkSize(); - if (this.skipMetadata) + byte[] exifData = new byte[exifChunkSize]; + int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize); + if (bytesRead != exifChunkSize) { - this.currentStream.Skip((int)iccpChunkSize); + // Ignore invalid chunk. + return; } - else - { - byte[] iccpData = new byte[iccpChunkSize]; - int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); - if (bytesRead != iccpChunkSize) - { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); - } - var profile = new IccProfile(iccpData); - if (profile.CheckIsValid()) - { - this.metadata.IccProfile = profile; - } - } + var profile = new ExifProfile(exifData); + this.metadata.ExifProfile = profile; } + } - /// - /// Reads the animation parameters chunk from the stream. - /// - /// The webp features. - private void ReadAnimationParameters(WebpFeatures features) + /// + /// Reads the XMP profile the stream. + /// + private void ReadXmpProfile() + { + uint xmpChunkSize = this.ReadChunkSize(); + if (this.skipMetadata) + { + this.currentStream.Skip((int)xmpChunkSize); + } + else { - features.Animation = true; - uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); - byte blue = (byte)this.currentStream.ReadByte(); - byte green = (byte)this.currentStream.ReadByte(); - byte red = (byte)this.currentStream.ReadByte(); - byte alpha = (byte)this.currentStream.ReadByte(); - features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha)); - int bytesRead = this.currentStream.Read(this.buffer, 0, 2); - if (bytesRead != 2) + byte[] xmpData = new byte[xmpChunkSize]; + int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); + if (bytesRead != xmpChunkSize) { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); + // Ignore invalid chunk. + return; } - features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer); + var profile = new XmpProfile(xmpData); + this.metadata.XmpProfile = profile; } + } - /// - /// Reads the alpha data chunk data from the stream. - /// - /// The features. - /// if set to true, skips the chunk data. - private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha) + /// + /// Reads the ICCP chunk from the stream. + /// + private void ReadIccProfile() + { + uint iccpChunkSize = this.ReadChunkSize(); + if (this.skipMetadata) { - uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); - if (ignoreAlpha) + this.currentStream.Skip((int)iccpChunkSize); + } + else + { + byte[] iccpData = new byte[iccpChunkSize]; + int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize); + if (bytesRead != iccpChunkSize) { - this.currentStream.Skip((int)alphaChunkSize); - return; + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk"); } - features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); - int alphaDataSize = (int)(alphaChunkSize - 1); - this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); - Span alphaData = this.alphaData.GetSpan(); - int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize); - if (bytesRead != alphaDataSize) + var profile = new IccProfile(iccpData); + if (profile.CheckIsValid()) { - WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream"); + this.metadata.IccProfile = profile; } } + } - /// - /// Identifies the chunk type from the chunk. - /// - /// - /// Thrown if the input stream is not valid. - /// - private WebpChunkType ReadChunkType() + /// + /// Reads the animation parameters chunk from the stream. + /// + /// The webp features. + private void ReadAnimationParameters(WebpFeatures features) + { + features.Animation = true; + uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); + byte blue = (byte)this.currentStream.ReadByte(); + byte green = (byte)this.currentStream.ReadByte(); + byte red = (byte)this.currentStream.ReadByte(); + byte alpha = (byte)this.currentStream.ReadByte(); + features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha)); + int bytesRead = this.currentStream.Read(this.buffer, 0, 2); + if (bytesRead != 2) { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) - { - var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); - return chunkType; - } + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count"); + } + + features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer); + } - throw new ImageFormatException("Invalid Webp data."); + /// + /// Reads the alpha data chunk data from the stream. + /// + /// The features. + /// if set to true, skips the chunk data. + private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha) + { + uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); + if (ignoreAlpha) + { + this.currentStream.Skip((int)alphaChunkSize); + return; } - /// - /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, - /// so the chunk size will be increased by 1 in those cases. - /// - /// The chunk size in bytes. - private uint ReadChunkSize() + features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); + int alphaDataSize = (int)(alphaChunkSize - 1); + this.alphaData = this.memoryAllocator.Allocate(alphaDataSize); + Span alphaData = this.alphaData.GetSpan(); + int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize); + if (bytesRead != alphaDataSize) { - if (this.currentStream.Read(this.buffer, 0, 4) == 4) - { - uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); - return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; - } + WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream"); + } + } + + /// + /// Identifies the chunk type from the chunk. + /// + /// + /// Thrown if the input stream is not valid. + /// + private WebpChunkType ReadChunkType() + { + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer); + return chunkType; + } + + throw new ImageFormatException("Invalid Webp data."); + } - throw new ImageFormatException("Invalid Webp data."); + /// + /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload, + /// so the chunk size will be increased by 1 in those cases. + /// + /// The chunk size in bytes. + private uint ReadChunkSize() + { + if (this.currentStream.Read(this.buffer, 0, 4) == 4) + { + uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); + return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1; } - /// - public void Dispose() => this.alphaData?.Dispose(); + throw new ImageFormatException("Invalid Webp data."); } + + /// + public void Dispose() => this.alphaData?.Dispose(); } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoder.cs b/src/ImageSharp/Formats/Webp/WebpEncoder.cs index 14310b1ed9..b6a45555d8 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoder.cs @@ -1,63 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Image encoder for writing an image to a stream in the Webp format. +/// +public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions { - /// - /// Image encoder for writing an image to a stream in the Webp format. - /// - public sealed class WebpEncoder : IImageEncoder, IWebpEncoderOptions - { - /// - public WebpFileFormatType? FileFormat { get; set; } + /// + public WebpFileFormatType? FileFormat { get; set; } - /// - public int Quality { get; set; } = 75; + /// + public int Quality { get; set; } = 75; - /// - public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default; + /// + public WebpEncodingMethod Method { get; set; } = WebpEncodingMethod.Default; - /// - public bool UseAlphaCompression { get; set; } = true; + /// + public bool UseAlphaCompression { get; set; } = true; - /// - public int EntropyPasses { get; set; } = 1; + /// + public int EntropyPasses { get; set; } = 1; - /// - public int SpatialNoiseShaping { get; set; } = 50; + /// + public int SpatialNoiseShaping { get; set; } = 50; - /// - public int FilterStrength { get; set; } = 60; + /// + public int FilterStrength { get; set; } = 60; - /// - public WebpTransparentColorMode TransparentColorMode { get; set; } = WebpTransparentColorMode.Clear; + /// + public WebpTransparentColorMode TransparentColorMode { get; set; } = WebpTransparentColorMode.Clear; - /// - public bool NearLossless { get; set; } + /// + public bool NearLossless { get; set; } - /// - public int NearLosslessQuality { get; set; } = 100; + /// + public int NearLosslessQuality { get; set; } = 100; - /// - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); - encoder.Encode(image, stream); - } + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); + encoder.Encode(image, stream); + } - /// - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); - return encoder.EncodeAsync(image, stream, cancellationToken); - } + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoderCore(this, image.GetMemoryAllocator()); + return encoder.EncodeAsync(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs index 967e18cd16..f9ceaf3098 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncoderCore.cs @@ -1,158 +1,155 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Image encoder for writing an image to a stream in the Webp format. +/// +internal sealed class WebpEncoderCore : IImageEncoderInternals { /// - /// Image encoder for writing an image to a stream in the Webp format. + /// Used for allocating memory during processing operations. + /// + private readonly MemoryAllocator memoryAllocator; + + /// + /// Indicating whether the alpha plane should be compressed with Webp lossless format. + /// Defaults to true. + /// + private readonly bool alphaCompression; + + /// + /// Compression quality. Between 0 and 100. + /// + private readonly int quality; + + /// + /// Quality/speed trade-off (0=fast, 6=slower-better). + /// + private readonly WebpEncodingMethod method; + + /// + /// The number of entropy-analysis passes (in [1..10]). + /// + private readonly int entropyPasses; + + /// + /// Spatial Noise Shaping. 0=off, 100=maximum. + /// + private readonly int spatialNoiseShaping; + + /// + /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). + /// + private readonly int filterStrength; + + /// + /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible + /// RGB information for better compression. /// - internal sealed class WebpEncoderCore : IImageEncoderInternals + private readonly WebpTransparentColorMode transparentColorMode; + + /// + /// Indicating whether near lossless mode should be used. + /// + private readonly bool nearLossless; + + /// + /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). + /// + private readonly int nearLosslessQuality; + + /// + /// Indicating what file format compression should be used. + /// Defaults to lossy. + /// + private readonly WebpFileFormatType? fileFormat; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The encoder options. + /// The memory manager. + public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAllocator) + { + this.memoryAllocator = memoryAllocator; + this.alphaCompression = options.UseAlphaCompression; + this.fileFormat = options.FileFormat; + this.quality = options.Quality; + this.method = options.Method; + this.entropyPasses = options.EntropyPasses; + this.spatialNoiseShaping = options.SpatialNoiseShaping; + this.filterStrength = options.FilterStrength; + this.transparentColorMode = options.TransparentColorMode; + this.nearLossless = options.NearLossless; + this.nearLosslessQuality = options.NearLosslessQuality; + } + + /// + /// Encodes the image as webp to the specified stream. + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - /// - /// Used for allocating memory during processing operations. - /// - private readonly MemoryAllocator memoryAllocator; - - /// - /// Indicating whether the alpha plane should be compressed with Webp lossless format. - /// Defaults to true. - /// - private readonly bool alphaCompression; - - /// - /// Compression quality. Between 0 and 100. - /// - private readonly int quality; - - /// - /// Quality/speed trade-off (0=fast, 6=slower-better). - /// - private readonly WebpEncodingMethod method; - - /// - /// The number of entropy-analysis passes (in [1..10]). - /// - private readonly int entropyPasses; - - /// - /// Spatial Noise Shaping. 0=off, 100=maximum. - /// - private readonly int spatialNoiseShaping; - - /// - /// The filter the strength of the deblocking filter, between 0 (no filtering) and 100 (maximum filtering). - /// - private readonly int filterStrength; - - /// - /// Flag indicating whether to preserve the exact RGB values under transparent area. Otherwise, discard this invisible - /// RGB information for better compression. - /// - private readonly WebpTransparentColorMode transparentColorMode; - - /// - /// Indicating whether near lossless mode should be used. - /// - private readonly bool nearLossless; - - /// - /// The near lossless quality. The range is 0 (maximum preprocessing) to 100 (no preprocessing, the default). - /// - private readonly int nearLosslessQuality; - - /// - /// Indicating what file format compression should be used. - /// Defaults to lossy. - /// - private readonly WebpFileFormatType? fileFormat; - - /// - /// The global configuration. - /// - private Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The encoder options. - /// The memory manager. - public WebpEncoderCore(IWebpEncoderOptions options, MemoryAllocator memoryAllocator) + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.configuration = image.GetConfiguration(); + bool lossless; + if (this.fileFormat is not null) + { + lossless = this.fileFormat == WebpFileFormatType.Lossless; + } + else { - this.memoryAllocator = memoryAllocator; - this.alphaCompression = options.UseAlphaCompression; - this.fileFormat = options.FileFormat; - this.quality = options.Quality; - this.method = options.Method; - this.entropyPasses = options.EntropyPasses; - this.spatialNoiseShaping = options.SpatialNoiseShaping; - this.filterStrength = options.FilterStrength; - this.transparentColorMode = options.TransparentColorMode; - this.nearLossless = options.NearLossless; - this.nearLosslessQuality = options.NearLosslessQuality; + WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); + lossless = webpMetadata.FileFormat == WebpFileFormatType.Lossless; } - /// - /// Encodes the image as webp to the specified stream. - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + if (lossless) + { + using var enc = new Vp8LEncoder( + this.memoryAllocator, + this.configuration, + image.Width, + image.Height, + this.quality, + this.method, + this.transparentColorMode, + this.nearLossless, + this.nearLosslessQuality); + enc.Encode(image, stream); + } + else { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.configuration = image.GetConfiguration(); - bool lossless; - if (this.fileFormat is not null) - { - lossless = this.fileFormat == WebpFileFormatType.Lossless; - } - else - { - WebpMetadata webpMetadata = image.Metadata.GetWebpMetadata(); - lossless = webpMetadata.FileFormat == WebpFileFormatType.Lossless; - } - - if (lossless) - { - using var enc = new Vp8LEncoder( - this.memoryAllocator, - this.configuration, - image.Width, - image.Height, - this.quality, - this.method, - this.transparentColorMode, - this.nearLossless, - this.nearLosslessQuality); - enc.Encode(image, stream); - } - else - { - using var enc = new Vp8Encoder( - this.memoryAllocator, - this.configuration, - image.Width, - image.Height, - this.quality, - this.method, - this.entropyPasses, - this.filterStrength, - this.spatialNoiseShaping, - this.alphaCompression); - enc.Encode(image, stream); - } + using var enc = new Vp8Encoder( + this.memoryAllocator, + this.configuration, + image.Width, + image.Height, + this.quality, + this.method, + this.entropyPasses, + this.filterStrength, + this.spatialNoiseShaping, + this.alphaCompression); + enc.Encode(image, stream); } } } diff --git a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs index cdf3b7a552..c901384bb5 100644 --- a/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs +++ b/src/ImageSharp/Formats/Webp/WebpEncodingMethod.cs @@ -1,61 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Quality/speed trade-off for the encoding process (0=fast, 6=slower-better). +/// +public enum WebpEncodingMethod { /// - /// Quality/speed trade-off for the encoding process (0=fast, 6=slower-better). - /// - public enum WebpEncodingMethod - { - /// - /// Fastest, but quality compromise. Equivalent to . - /// - Level0 = 0, - - /// - /// Fastest, but quality compromise. - /// - Fastest = Level0, - - /// - /// Level1. - /// - Level1 = 1, - - /// - /// Level 2. - /// - Level2 = 2, - - /// - /// Level 3. - /// - Level3 = 3, - - /// - /// Level 4. Equivalent to . - /// - Level4 = 4, - - /// - /// BestQuality trade off between speed and quality. - /// - Default = Level4, - - /// - /// Level 5. - /// - Level5 = 5, - - /// - /// Slowest option, but best quality. Equivalent to . - /// - Level6 = 6, - - /// - /// Slowest option, but best quality. - /// - BestQuality = Level6 - } + /// Fastest, but quality compromise. Equivalent to . + /// + Level0 = 0, + + /// + /// Fastest, but quality compromise. + /// + Fastest = Level0, + + /// + /// Level1. + /// + Level1 = 1, + + /// + /// Level 2. + /// + Level2 = 2, + + /// + /// Level 3. + /// + Level3 = 3, + + /// + /// Level 4. Equivalent to . + /// + Level4 = 4, + + /// + /// BestQuality trade off between speed and quality. + /// + Default = Level4, + + /// + /// Level 5. + /// + Level5 = 5, + + /// + /// Slowest option, but best quality. Equivalent to . + /// + Level6 = 6, + + /// + /// Slowest option, but best quality. + /// + BestQuality = Level6 } diff --git a/src/ImageSharp/Formats/Webp/WebpFeatures.cs b/src/ImageSharp/Formats/Webp/WebpFeatures.cs index ceaedb58bb..5cd040139d 100644 --- a/src/ImageSharp/Formats/Webp/WebpFeatures.cs +++ b/src/ImageSharp/Formats/Webp/WebpFeatures.cs @@ -1,52 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Image features of a VP8X image. +/// +internal class WebpFeatures { /// - /// Image features of a VP8X image. - /// - internal class WebpFeatures - { - /// - /// Gets or sets a value indicating whether this image has an ICC Profile. - /// - public bool IccProfile { get; set; } - - /// - /// Gets or sets a value indicating whether this image has an alpha channel. - /// - public bool Alpha { get; set; } - - /// - /// Gets or sets the alpha chunk header. - /// - public byte AlphaChunkHeader { get; set; } - - /// - /// Gets or sets a value indicating whether this image has an EXIF Profile. - /// - public bool ExifProfile { get; set; } - - /// - /// Gets or sets a value indicating whether this image has XMP Metadata. - /// - public bool XmpMetaData { get; set; } - - /// - /// Gets or sets a value indicating whether this image is an animation. - /// - public bool Animation { get; set; } - - /// - /// Gets or sets the animation loop count. 0 means infinitely. - /// - public ushort AnimationLoopCount { get; set; } - - /// - /// Gets or sets default background color of the animation frame canvas. - /// This color MAY be used to fill the unused space on the canvas around the frames, as well as the transparent pixels of the first frame.. - /// - public Color? AnimationBackgroundColor { get; set; } - } + /// Gets or sets a value indicating whether this image has an ICC Profile. + /// + public bool IccProfile { get; set; } + + /// + /// Gets or sets a value indicating whether this image has an alpha channel. + /// + public bool Alpha { get; set; } + + /// + /// Gets or sets the alpha chunk header. + /// + public byte AlphaChunkHeader { get; set; } + + /// + /// Gets or sets a value indicating whether this image has an EXIF Profile. + /// + public bool ExifProfile { get; set; } + + /// + /// Gets or sets a value indicating whether this image has XMP Metadata. + /// + public bool XmpMetaData { get; set; } + + /// + /// Gets or sets a value indicating whether this image is an animation. + /// + public bool Animation { get; set; } + + /// + /// Gets or sets the animation loop count. 0 means infinitely. + /// + public ushort AnimationLoopCount { get; set; } + + /// + /// Gets or sets default background color of the animation frame canvas. + /// This color MAY be used to fill the unused space on the canvas around the frames, as well as the transparent pixels of the first frame.. + /// + public Color? AnimationBackgroundColor { get; set; } } diff --git a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs index 4c4d2789a3..1ed9bbb431 100644 --- a/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs +++ b/src/ImageSharp/Formats/Webp/WebpFileFormatType.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Info about the webp file format used. +/// +public enum WebpFileFormatType { /// - /// Info about the webp file format used. + /// The lossless webp format. /// - public enum WebpFileFormatType - { - /// - /// The lossless webp format. - /// - Lossless, + Lossless, - /// - /// The lossy webp format. - /// - Lossy, - } + /// + /// The lossy webp format. + /// + Lossy, } diff --git a/src/ImageSharp/Formats/Webp/WebpFormat.cs b/src/ImageSharp/Formats/Webp/WebpFormat.cs index a934a61493..cc2073bc08 100644 --- a/src/ImageSharp/Formats/Webp/WebpFormat.cs +++ b/src/ImageSharp/Formats/Webp/WebpFormat.cs @@ -1,40 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Formats.Webp; -namespace SixLabors.ImageSharp.Formats.Webp +/// +/// Registers the image encoders, decoders and mime type detectors for the Webp format. +/// +public sealed class WebpFormat : IImageFormat { - /// - /// Registers the image encoders, decoders and mime type detectors for the Webp format. - /// - public sealed class WebpFormat : IImageFormat + private WebpFormat() { - private WebpFormat() - { - } + } - /// - /// Gets the current instance. - /// - public static WebpFormat Instance { get; } = new(); + /// + /// Gets the current instance. + /// + public static WebpFormat Instance { get; } = new(); - /// - public string Name => "Webp"; + /// + public string Name => "Webp"; - /// - public string DefaultMimeType => "image/webp"; + /// + public string DefaultMimeType => "image/webp"; - /// - public IEnumerable MimeTypes => WebpConstants.MimeTypes; + /// + public IEnumerable MimeTypes => WebpConstants.MimeTypes; - /// - public IEnumerable FileExtensions => WebpConstants.FileExtensions; + /// + public IEnumerable FileExtensions => WebpConstants.FileExtensions; - /// - public WebpMetadata CreateDefaultFormatMetadata() => new(); + /// + public WebpMetadata CreateDefaultFormatMetadata() => new(); - /// - public WebpFrameMetadata CreateDefaultFormatFrameMetadata() => new(); - } + /// + public WebpFrameMetadata CreateDefaultFormatFrameMetadata() => new(); } diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs index b1007ece67..bce1b09d6f 100644 --- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs @@ -1,33 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Provides webp specific metadata information for the image frame. +/// +public class WebpFrameMetadata : IDeepCloneable { /// - /// Provides webp specific metadata information for the image frame. + /// Initializes a new instance of the class. /// - public class WebpFrameMetadata : IDeepCloneable + public WebpFrameMetadata() { - /// - /// Initializes a new instance of the class. - /// - public WebpFrameMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private WebpFrameMetadata(WebpFrameMetadata other) => this.FrameDuration = other.FrameDuration; + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private WebpFrameMetadata(WebpFrameMetadata other) => this.FrameDuration = other.FrameDuration; - /// - /// Gets or sets the frame duration. The time to wait before displaying the next frame, - /// in 1 millisecond units. Note the interpretation of frame duration of 0 (and often smaller and equal to 10) is implementation defined. - /// - public uint FrameDuration { get; set; } + /// + /// Gets or sets the frame duration. The time to wait before displaying the next frame, + /// in 1 millisecond units. Note the interpretation of frame duration of 0 (and often smaller and equal to 10) is implementation defined. + /// + public uint FrameDuration { get; set; } - /// - public IDeepCloneable DeepClone() => new WebpFrameMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new WebpFrameMetadata(this); } diff --git a/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs b/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs index cdb6e56627..50820fd61c 100644 --- a/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Webp/WebpImageFormatDetector.cs @@ -1,39 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Formats.Webp; -namespace SixLabors.ImageSharp.Formats.Webp +/// +/// Detects Webp file headers. +/// +public sealed class WebpImageFormatDetector : IImageFormatDetector { - /// - /// Detects Webp file headers. - /// - public sealed class WebpImageFormatDetector : IImageFormatDetector - { - /// - public int HeaderSize => 12; + /// + public int HeaderSize => 12; - /// - public IImageFormat DetectFormat(ReadOnlySpan header) - => this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; + /// + public IImageFormat DetectFormat(ReadOnlySpan header) + => this.IsSupportedFileFormat(header) ? WebpFormat.Instance : null; - private bool IsSupportedFileFormat(ReadOnlySpan header) - => header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header); + private bool IsSupportedFileFormat(ReadOnlySpan header) + => header.Length >= this.HeaderSize && IsRiffContainer(header) && IsWebpFile(header); - /// - /// Checks, if the header starts with a valid RIFF FourCC. - /// - /// The header bytes. - /// True, if its a valid RIFF FourCC. - private static bool IsRiffContainer(ReadOnlySpan header) - => header[..4].SequenceEqual(WebpConstants.RiffFourCc); + /// + /// Checks, if the header starts with a valid RIFF FourCC. + /// + /// The header bytes. + /// True, if its a valid RIFF FourCC. + private static bool IsRiffContainer(ReadOnlySpan header) + => header[..4].SequenceEqual(WebpConstants.RiffFourCc); - /// - /// Checks if 'WEBP' is present in the header. - /// - /// The header bytes. - /// True, if its a webp file. - private static bool IsWebpFile(ReadOnlySpan header) - => header.Slice(8, 4).SequenceEqual(WebpConstants.WebpHeader); - } + /// + /// Checks if 'WEBP' is present in the header. + /// + /// The header bytes. + /// True, if its a webp file. + private static bool IsWebpFile(ReadOnlySpan header) + => header.Slice(8, 4).SequenceEqual(WebpConstants.WebpHeader); } diff --git a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs index 3c9eea38c5..2f420f453e 100644 --- a/src/ImageSharp/Formats/Webp/WebpImageInfo.cs +++ b/src/ImageSharp/Formats/Webp/WebpImageInfo.cs @@ -1,68 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.Lossy; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal class WebpImageInfo : IDisposable { - internal class WebpImageInfo : IDisposable - { - /// - /// Gets or sets the bitmap width in pixels. - /// - public uint Width { get; set; } + /// + /// Gets or sets the bitmap width in pixels. + /// + public uint Width { get; set; } - /// - /// Gets or sets the bitmap height in pixels. - /// - public uint Height { get; set; } + /// + /// Gets or sets the bitmap height in pixels. + /// + public uint Height { get; set; } - public sbyte XScale { get; set; } + public sbyte XScale { get; set; } - public sbyte YScale { get; set; } + public sbyte YScale { get; set; } - /// - /// Gets or sets the bits per pixel. - /// - public WebpBitsPerPixel BitsPerPixel { get; set; } + /// + /// Gets or sets the bits per pixel. + /// + public WebpBitsPerPixel BitsPerPixel { get; set; } - /// - /// Gets or sets a value indicating whether this image uses lossless compression. - /// - public bool IsLossless { get; set; } + /// + /// Gets or sets a value indicating whether this image uses lossless compression. + /// + public bool IsLossless { get; set; } - /// - /// Gets or sets additional features present in a VP8X image. - /// - public WebpFeatures Features { get; set; } + /// + /// Gets or sets additional features present in a VP8X image. + /// + public WebpFeatures Features { get; set; } - /// - /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. - /// - public int Vp8Profile { get; set; } = -1; + /// + /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1. + /// + public int Vp8Profile { get; set; } = -1; - /// - /// Gets or sets the VP8 frame header. - /// - public Vp8FrameHeader Vp8FrameHeader { get; set; } + /// + /// Gets or sets the VP8 frame header. + /// + public Vp8FrameHeader Vp8FrameHeader { get; set; } - /// - /// Gets or sets the VP8L bitreader. Will be , if its not a lossless image. - /// - public Vp8LBitReader Vp8LBitReader { get; set; } + /// + /// Gets or sets the VP8L bitreader. Will be , if its not a lossless image. + /// + public Vp8LBitReader Vp8LBitReader { get; set; } - /// - /// Gets or sets the VP8 bitreader. Will be , if its not a lossy image. - /// - public Vp8BitReader Vp8BitReader { get; set; } + /// + /// Gets or sets the VP8 bitreader. Will be , if its not a lossy image. + /// + public Vp8BitReader Vp8BitReader { get; set; } - /// - public void Dispose() - { - this.Vp8BitReader?.Dispose(); - this.Vp8LBitReader?.Dispose(); - } + /// + public void Dispose() + { + this.Vp8BitReader?.Dispose(); + this.Vp8LBitReader?.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs index 9eb813a9e0..a69bcfd006 100644 --- a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs @@ -1,1671 +1,1669 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Webp -{ -#pragma warning disable SA1201 // Elements should appear in the correct order - internal static class WebpLookupTables - { - public static readonly byte[,][] ModesProba = new byte[10, 10][]; +namespace SixLabors.ImageSharp.Formats.Webp; - public static readonly ushort[] GammaToLinearTab = new ushort[256]; +#pragma warning disable SA1201 // Elements should appear in the correct order +internal static class WebpLookupTables +{ + public static readonly byte[,][] ModesProba = new byte[10, 10][]; - public static readonly int[] LinearToGammaTab = new int[WebpConstants.GammaTabSize + 1]; + public static readonly ushort[] GammaToLinearTab = new ushort[256]; - public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; + public static readonly int[] LinearToGammaTab = new int[WebpConstants.GammaTabSize + 1]; - // Compute susceptibility based on DCT-coeff histograms: - // the higher, the "easier" the macroblock is to compress. - public static readonly int[] Vp8DspScan = - { - // Luma - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), - 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), - 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), - 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), + public static readonly short[,][] Vp8FixedCostsI4 = new short[10, 10][]; - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U - 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V - }; + // Compute susceptibility based on DCT-coeff histograms: + // the higher, the "easier" the macroblock is to compress. + public static readonly int[] Vp8DspScan = + { + // Luma + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), + 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), + 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), + 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), - public static readonly short[] Vp8Scan = - { - // Luma - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), - 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), - 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), - 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), - }; + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U + 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V + }; - public static readonly short[] Vp8ScanUv = - { - 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U - 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V - }; + public static readonly short[] Vp8Scan = + { + // Luma + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), + 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps), + 0 + (8 * WebpConstants.Bps), 4 + (8 * WebpConstants.Bps), 8 + (8 * WebpConstants.Bps), 12 + (8 * WebpConstants.Bps), + 0 + (12 * WebpConstants.Bps), 4 + (12 * WebpConstants.Bps), 8 + (12 * WebpConstants.Bps), 12 + (12 * WebpConstants.Bps), + }; - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Abs0(int x) => Abs0Table[x + 255]; + public static readonly short[] Vp8ScanUv = + { + 0 + (0 * WebpConstants.Bps), 4 + (0 * WebpConstants.Bps), 0 + (4 * WebpConstants.Bps), 4 + (4 * WebpConstants.Bps), // U + 8 + (0 * WebpConstants.Bps), 12 + (0 * WebpConstants.Bps), 8 + (4 * WebpConstants.Bps), 12 + (4 * WebpConstants.Bps) // V + }; - [MethodImpl(InliningOptions.ShortMethod)] - public static sbyte Sclip1(int x) => Sclip1Table[x + 1020]; + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Abs0(int x) => Abs0Table[x + 255]; - [MethodImpl(InliningOptions.ShortMethod)] - public static sbyte Sclip2(int x) => Sclip2Table[x + 112]; + [MethodImpl(InliningOptions.ShortMethod)] + public static sbyte Sclip1(int x) => Sclip1Table[x + 1020]; - [MethodImpl(InliningOptions.ShortMethod)] - public static byte Clip1(int x) => Clip1Table[x + 255]; + [MethodImpl(InliningOptions.ShortMethod)] + public static sbyte Sclip2(int x) => Sclip2Table[x + 112]; - // fixed costs for coding levels, deduce from the coding tree. - // This is only the part that doesn't depend on the probability state. - public static readonly short[] Vp8LevelFixedCosts = - { - 0, 256, 256, 256, 256, 432, 618, 630, 731, 640, 640, 828, 901, 948, 1021, 1101, 1174, 1221, 1294, 1042, - 1085, 1115, 1158, 1202, 1245, 1275, 1318, 1337, 1380, 1410, 1453, 1497, 1540, 1570, 1613, 1280, 1295, - 1317, 1332, 1358, 1373, 1395, 1410, 1454, 1469, 1491, 1506, 1532, 1547, 1569, 1584, 1601, 1616, 1638, - 1653, 1679, 1694, 1716, 1731, 1775, 1790, 1812, 1827, 1853, 1868, 1890, 1905, 1727, 1733, 1742, 1748, - 1759, 1765, 1774, 1780, 1800, 1806, 1815, 1821, 1832, 1838, 1847, 1853, 1878, 1884, 1893, 1899, 1910, - 1916, 1925, 1931, 1951, 1957, 1966, 1972, 1983, 1989, 1998, 2004, 2027, 2033, 2042, 2048, 2059, 2065, - 2074, 2080, 2100, 2106, 2115, 2121, 2132, 2138, 2147, 2153, 2178, 2184, 2193, 2199, 2210, 2216, 2225, - 2231, 2251, 2257, 2266, 2272, 2283, 2289, 2298, 2304, 2168, 2174, 2183, 2189, 2200, 2206, 2215, 2221, - 2241, 2247, 2256, 2262, 2273, 2279, 2288, 2294, 2319, 2325, 2334, 2340, 2351, 2357, 2366, 2372, 2392, - 2398, 2407, 2413, 2424, 2430, 2439, 2445, 2468, 2474, 2483, 2489, 2500, 2506, 2515, 2521, 2541, 2547, - 2556, 2562, 2573, 2579, 2588, 2594, 2619, 2625, 2634, 2640, 2651, 2657, 2666, 2672, 2692, 2698, 2707, - 2713, 2724, 2730, 2739, 2745, 2540, 2546, 2555, 2561, 2572, 2578, 2587, 2593, 2613, 2619, 2628, 2634, - 2645, 2651, 2660, 2666, 2691, 2697, 2706, 2712, 2723, 2729, 2738, 2744, 2764, 2770, 2779, 2785, 2796, - 2802, 2811, 2817, 2840, 2846, 2855, 2861, 2872, 2878, 2887, 2893, 2913, 2919, 2928, 2934, 2945, 2951, - 2960, 2966, 2991, 2997, 3006, 3012, 3023, 3029, 3038, 3044, 3064, 3070, 3079, 3085, 3096, 3102, 3111, - 3117, 2981, 2987, 2996, 3002, 3013, 3019, 3028, 3034, 3054, 3060, 3069, 3075, 3086, 3092, 3101, 3107, - 3132, 3138, 3147, 3153, 3164, 3170, 3179, 3185, 3205, 3211, 3220, 3226, 3237, 3243, 3252, 3258, 3281, - 3287, 3296, 3302, 3313, 3319, 3328, 3334, 3354, 3360, 3369, 3375, 3386, 3392, 3401, 3407, 3432, 3438, - 3447, 3453, 3464, 3470, 3479, 3485, 3505, 3511, 3520, 3526, 3537, 3543, 3552, 3558, 2816, 2822, 2831, - 2837, 2848, 2854, 2863, 2869, 2889, 2895, 2904, 2910, 2921, 2927, 2936, 2942, 2967, 2973, 2982, 2988, - 2999, 3005, 3014, 3020, 3040, 3046, 3055, 3061, 3072, 3078, 3087, 3093, 3116, 3122, 3131, 3137, 3148, - 3154, 3163, 3169, 3189, 3195, 3204, 3210, 3221, 3227, 3236, 3242, 3267, 3273, 3282, 3288, 3299, 3305, - 3314, 3320, 3340, 3346, 3355, 3361, 3372, 3378, 3387, 3393, 3257, 3263, 3272, 3278, 3289, 3295, 3304, - 3310, 3330, 3336, 3345, 3351, 3362, 3368, 3377, 3383, 3408, 3414, 3423, 3429, 3440, 3446, 3455, 3461, - 3481, 3487, 3496, 3502, 3513, 3519, 3528, 3534, 3557, 3563, 3572, 3578, 3589, 3595, 3604, 3610, 3630, - 3636, 3645, 3651, 3662, 3668, 3677, 3683, 3708, 3714, 3723, 3729, 3740, 3746, 3755, 3761, 3781, 3787, - 3796, 3802, 3813, 3819, 3828, 3834, 3629, 3635, 3644, 3650, 3661, 3667, 3676, 3682, 3702, 3708, 3717, - 3723, 3734, 3740, 3749, 3755, 3780, 3786, 3795, 3801, 3812, 3818, 3827, 3833, 3853, 3859, 3868, 3874, - 3885, 3891, 3900, 3906, 3929, 3935, 3944, 3950, 3961, 3967, 3976, 3982, 4002, 4008, 4017, 4023, 4034, - 4040, 4049, 4055, 4080, 4086, 4095, 4101, 4112, 4118, 4127, 4133, 4153, 4159, 4168, 4174, 4185, 4191, - 4200, 4206, 4070, 4076, 4085, 4091, 4102, 4108, 4117, 4123, 4143, 4149, 4158, 4164, 4175, 4181, 4190, - 4196, 4221, 4227, 4236, 4242, 4253, 4259, 4268, 4274, 4294, 4300, 4309, 4315, 4326, 4332, 4341, 4347, - 4370, 4376, 4385, 4391, 4402, 4408, 4417, 4423, 4443, 4449, 4458, 4464, 4475, 4481, 4490, 4496, 4521, - 4527, 4536, 4542, 4553, 4559, 4568, 4574, 4594, 4600, 4609, 4615, 4626, 4632, 4641, 4647, 3515, 3521, - 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, 3672, 3681, - 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, 3830, 3836, - 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, - 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, 3994, - 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, 4145, 4154, - 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, 4303, 4309, - 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, 4460, 4480, - 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, 4401, 4407, - 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, 4558, 4567, - 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, 4716, 4722, - 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, - 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, 4880, - 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, 5031, 5040, - 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, 5189, 5195, - 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, 5346, 4604, - 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, 4755, 4761, - 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, 4910, 4919, - 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, 5070, 5076, - 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, - 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, 5234, - 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, 5383, 5392, - 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, 5543, 5549, - 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, 5470, 5490, - 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, 5641, 5647, - 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, 5796, 5805, - 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, 5956, 5962, - 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, - 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, 6120, - 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, 6269, 6278, - 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, 6429, 6435, - 3515, 3521, 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, - 3672, 3681, 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, - 3830, 3836, 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, - 3987, 3998, 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, - 3988, 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, - 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, - 4303, 4309, 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, - 4460, 4480, 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, - 4401, 4407, 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, - 4558, 4567, 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, - 4716, 4722, 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, - 4873, 4884, 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, - 4874, 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, - 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, - 5189, 5195, 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, - 5346, 4604, 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, - 4755, 4761, 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, - 4910, 4919, 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, - 5070, 5076, 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, - 5066, 5077, 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, - 5228, 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, - 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, - 5543, 5549, 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, - 5470, 5490, 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, - 5641, 5647, 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, - 5796, 5805, 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, - 5956, 5962, 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, - 5952, 5963, 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, - 6114, 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, - 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, - 6429, 6435, 5303, 5309, 5318, 5324, 5335, 5341, 5350, 5356, 5376, 5382, 5391, 5397, 5408, 5414, 5423, - 5429, 5454, 5460, 5469, 5475, 5486, 5492, 5501, 5507, 5527, 5533, 5542, 5548, 5559, 5565, 5574, 5580, - 5603, 5609, 5618, 5624, 5635, 5641, 5650, 5656, 5676, 5682, 5691, 5697, 5708, 5714, 5723, 5729, 5754, - 5760, 5769, 5775, 5786, 5792, 5801, 5807, 5827, 5833, 5842, 5848, 5859, 5865, 5874, 5880, 5744, 5750, - 5759, 5765, 5776, 5782, 5791, 5797, 5817, 5823, 5832, 5838, 5849, 5855, 5864, 5870, 5895, 5901, 5910, - 5916, 5927, 5933, 5942, 5948, 5968, 5974, 5983, 5989, 6000, 6006, 6015, 6021, 6044, 6050, 6059, 6065, - 6076, 6082, 6091, 6097, 6117, 6123, 6132, 6138, 6149, 6155, 6164, 6170, 6195, 6201, 6210, 6216, 6227, - 6233, 6242, 6248, 6268, 6274, 6283, 6289, 6300, 6306, 6315, 6321, 6116, 6122, 6131, 6137, 6148, 6154, - 6163, 6169, 6189, 6195, 6204, 6210, 6221, 6227, 6236, 6242, 6267, 6273, 6282, 6288, 6299, 6305, 6314, - 6320, 6340, 6346, 6355, 6361, 6372, 6378, 6387, 6393, 6416, 6422, 6431, 6437, 6448, 6454, 6463, 6469, - 6489, 6495, 6504, 6510, 6521, 6527, 6536, 6542, 6567, 6573, 6582, 6588, 6599, 6605, 6614, 6620, 6640, - 6646, 6655, 6661, 6672, 6678, 6687, 6693, 6557, 6563, 6572, 6578, 6589, 6595, 6604, 6610, 6630, 6636, - 6645, 6651, 6662, 6668, 6677, 6683, 6708, 6714, 6723, 6729, 6740, 6746, 6755, 6761, 6781, 6787, 6796, - 6802, 6813, 6819, 6828, 6834, 6857, 6863, 6872, 6878, 6889, 6895, 6904, 6910, 6930, 6936, 6945, 6951, - 6962, 6968, 6977, 6983, 7008, 7014, 7023, 7029, 7040, 7046, 7055, 7061, 7081, 7087, 7096, 7102, 7113, - 7119, 7128, 7134, 6392, 6398, 6407, 6413, 6424, 6430, 6439, 6445, 6465, 6471, 6480, 6486, 6497, 6503, - 6512, 6518, 6543, 6549, 6558, 6564, 6575, 6581, 6590, 6596, 6616, 6622, 6631, 6637, 6648, 6654, 6663, - 6669, 6692, 6698, 6707, 6713, 6724, 6730, 6739, 6745, 6765, 6771, 6780, 6786, 6797, 6803, 6812, 6818, - 6843, 6849, 6858, 6864, 6875, 6881, 6890, 6896, 6916, 6922, 6931, 6937, 6948, 6954, 6963, 6969, 6833, - 6839, 6848, 6854, 6865, 6871, 6880, 6886, 6906, 6912, 6921, 6927, 6938, 6944, 6953, 6959, 6984, 6990, - 6999, 7005, 7016, 7022, 7031, 7037, 7057, 7063, 7072, 7078, 7089, 7095, 7104, 7110, 7133, 7139, 7148, - 7154, 7165, 7171, 7180, 7186, 7206, 7212, 7221, 7227, 7238, 7244, 7253, 7259, 7284, 7290, 7299, 7305, - 7316, 7322, 7331, 7337, 7357, 7363, 7372, 7378, 7389, 7395, 7404, 7410, 7205, 7211, 7220, 7226, 7237, - 7243, 7252, 7258, 7278, 7284, 7293, 7299, 7310, 7316, 7325, 7331, 7356, 7362, 7371, 7377, 7388, 7394, - 7403, 7409, 7429, 7435, 7444, 7450, 7461, 7467, 7476, 7482, 7505, 7511, 7520, 7526, 7537, 7543, 7552, - 7558, 7578, 7584, 7593, 7599, 7610, 7616, 7625, 7631, 7656, 7662, 7671, 7677, 7688, 7694, 7703, 7709, - 7729, 7735, 7744, 7750, 7761 - }; + [MethodImpl(InliningOptions.ShortMethod)] + public static byte Clip1(int x) => Clip1Table[x + 255]; - // This table gives, for a given sharpness, the filtering strength to be - // used (at least) in order to filter a given edge step delta. - public static readonly byte[,] LevelsFromDelta = - { - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, - 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, - 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, - 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, - 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, - 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, - 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, - 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, - 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, - 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, - 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, - 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, - 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - }, - { - 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, - 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, - 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 - } - }; + // fixed costs for coding levels, deduce from the coding tree. + // This is only the part that doesn't depend on the probability state. + public static readonly short[] Vp8LevelFixedCosts = + { + 0, 256, 256, 256, 256, 432, 618, 630, 731, 640, 640, 828, 901, 948, 1021, 1101, 1174, 1221, 1294, 1042, + 1085, 1115, 1158, 1202, 1245, 1275, 1318, 1337, 1380, 1410, 1453, 1497, 1540, 1570, 1613, 1280, 1295, + 1317, 1332, 1358, 1373, 1395, 1410, 1454, 1469, 1491, 1506, 1532, 1547, 1569, 1584, 1601, 1616, 1638, + 1653, 1679, 1694, 1716, 1731, 1775, 1790, 1812, 1827, 1853, 1868, 1890, 1905, 1727, 1733, 1742, 1748, + 1759, 1765, 1774, 1780, 1800, 1806, 1815, 1821, 1832, 1838, 1847, 1853, 1878, 1884, 1893, 1899, 1910, + 1916, 1925, 1931, 1951, 1957, 1966, 1972, 1983, 1989, 1998, 2004, 2027, 2033, 2042, 2048, 2059, 2065, + 2074, 2080, 2100, 2106, 2115, 2121, 2132, 2138, 2147, 2153, 2178, 2184, 2193, 2199, 2210, 2216, 2225, + 2231, 2251, 2257, 2266, 2272, 2283, 2289, 2298, 2304, 2168, 2174, 2183, 2189, 2200, 2206, 2215, 2221, + 2241, 2247, 2256, 2262, 2273, 2279, 2288, 2294, 2319, 2325, 2334, 2340, 2351, 2357, 2366, 2372, 2392, + 2398, 2407, 2413, 2424, 2430, 2439, 2445, 2468, 2474, 2483, 2489, 2500, 2506, 2515, 2521, 2541, 2547, + 2556, 2562, 2573, 2579, 2588, 2594, 2619, 2625, 2634, 2640, 2651, 2657, 2666, 2672, 2692, 2698, 2707, + 2713, 2724, 2730, 2739, 2745, 2540, 2546, 2555, 2561, 2572, 2578, 2587, 2593, 2613, 2619, 2628, 2634, + 2645, 2651, 2660, 2666, 2691, 2697, 2706, 2712, 2723, 2729, 2738, 2744, 2764, 2770, 2779, 2785, 2796, + 2802, 2811, 2817, 2840, 2846, 2855, 2861, 2872, 2878, 2887, 2893, 2913, 2919, 2928, 2934, 2945, 2951, + 2960, 2966, 2991, 2997, 3006, 3012, 3023, 3029, 3038, 3044, 3064, 3070, 3079, 3085, 3096, 3102, 3111, + 3117, 2981, 2987, 2996, 3002, 3013, 3019, 3028, 3034, 3054, 3060, 3069, 3075, 3086, 3092, 3101, 3107, + 3132, 3138, 3147, 3153, 3164, 3170, 3179, 3185, 3205, 3211, 3220, 3226, 3237, 3243, 3252, 3258, 3281, + 3287, 3296, 3302, 3313, 3319, 3328, 3334, 3354, 3360, 3369, 3375, 3386, 3392, 3401, 3407, 3432, 3438, + 3447, 3453, 3464, 3470, 3479, 3485, 3505, 3511, 3520, 3526, 3537, 3543, 3552, 3558, 2816, 2822, 2831, + 2837, 2848, 2854, 2863, 2869, 2889, 2895, 2904, 2910, 2921, 2927, 2936, 2942, 2967, 2973, 2982, 2988, + 2999, 3005, 3014, 3020, 3040, 3046, 3055, 3061, 3072, 3078, 3087, 3093, 3116, 3122, 3131, 3137, 3148, + 3154, 3163, 3169, 3189, 3195, 3204, 3210, 3221, 3227, 3236, 3242, 3267, 3273, 3282, 3288, 3299, 3305, + 3314, 3320, 3340, 3346, 3355, 3361, 3372, 3378, 3387, 3393, 3257, 3263, 3272, 3278, 3289, 3295, 3304, + 3310, 3330, 3336, 3345, 3351, 3362, 3368, 3377, 3383, 3408, 3414, 3423, 3429, 3440, 3446, 3455, 3461, + 3481, 3487, 3496, 3502, 3513, 3519, 3528, 3534, 3557, 3563, 3572, 3578, 3589, 3595, 3604, 3610, 3630, + 3636, 3645, 3651, 3662, 3668, 3677, 3683, 3708, 3714, 3723, 3729, 3740, 3746, 3755, 3761, 3781, 3787, + 3796, 3802, 3813, 3819, 3828, 3834, 3629, 3635, 3644, 3650, 3661, 3667, 3676, 3682, 3702, 3708, 3717, + 3723, 3734, 3740, 3749, 3755, 3780, 3786, 3795, 3801, 3812, 3818, 3827, 3833, 3853, 3859, 3868, 3874, + 3885, 3891, 3900, 3906, 3929, 3935, 3944, 3950, 3961, 3967, 3976, 3982, 4002, 4008, 4017, 4023, 4034, + 4040, 4049, 4055, 4080, 4086, 4095, 4101, 4112, 4118, 4127, 4133, 4153, 4159, 4168, 4174, 4185, 4191, + 4200, 4206, 4070, 4076, 4085, 4091, 4102, 4108, 4117, 4123, 4143, 4149, 4158, 4164, 4175, 4181, 4190, + 4196, 4221, 4227, 4236, 4242, 4253, 4259, 4268, 4274, 4294, 4300, 4309, 4315, 4326, 4332, 4341, 4347, + 4370, 4376, 4385, 4391, 4402, 4408, 4417, 4423, 4443, 4449, 4458, 4464, 4475, 4481, 4490, 4496, 4521, + 4527, 4536, 4542, 4553, 4559, 4568, 4574, 4594, 4600, 4609, 4615, 4626, 4632, 4641, 4647, 3515, 3521, + 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, 3672, 3681, + 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, 3830, 3836, + 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, + 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, 3994, + 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, 4145, 4154, + 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, 4303, 4309, + 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, 4460, 4480, + 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, 4401, 4407, + 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, 4558, 4567, + 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, 4716, 4722, + 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, + 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, 4880, + 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, 5031, 5040, + 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, 5189, 5195, + 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, 5346, 4604, + 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, 4755, 4761, + 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, 4910, 4919, + 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, 5070, 5076, + 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, + 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, 5234, + 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, 5383, 5392, + 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, 5543, 5549, + 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, 5470, 5490, + 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, 5641, 5647, + 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, 5796, 5805, + 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, 5956, 5962, + 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, + 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, 6120, + 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, 6269, 6278, + 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, 6429, 6435, + 3515, 3521, 3530, 3536, 3547, 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, 3626, 3635, 3641, 3666, + 3672, 3681, 3687, 3698, 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, 3777, 3786, 3792, 3815, 3821, + 3830, 3836, 3847, 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, 3926, 3935, 3941, 3966, 3972, 3981, + 3987, 3998, 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, 4077, 4086, 4092, 3956, 3962, 3971, 3977, + 3988, 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, + 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, 4294, + 4303, 4309, 4329, 4335, 4344, 4350, 4361, 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, 4445, 4454, + 4460, 4480, 4486, 4495, 4501, 4512, 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, 4366, 4375, 4381, + 4401, 4407, 4416, 4422, 4433, 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, 4517, 4526, 4532, 4552, + 4558, 4567, 4573, 4584, 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, 4666, 4675, 4681, 4701, 4707, + 4716, 4722, 4733, 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, 4817, 4826, 4832, 4852, 4858, 4867, + 4873, 4884, 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, 4807, 4816, 4822, 4842, 4848, 4857, 4863, + 4874, 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, + 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, 5180, + 5189, 5195, 5220, 5226, 5235, 5241, 5252, 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, 5331, 5340, + 5346, 4604, 4610, 4619, 4625, 4636, 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, 4715, 4724, 4730, + 4755, 4761, 4770, 4776, 4787, 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, 4866, 4875, 4881, 4904, + 4910, 4919, 4925, 4936, 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, 5015, 5024, 5030, 5055, 5061, + 5070, 5076, 5087, 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, 5166, 5175, 5181, 5045, 5051, 5060, + 5066, 5077, 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, 5156, 5165, 5171, 5196, 5202, 5211, 5217, + 5228, 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, + 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, 5534, + 5543, 5549, 5569, 5575, 5584, 5590, 5601, 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, 5455, 5464, + 5470, 5490, 5496, 5505, 5511, 5522, 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, 5606, 5615, 5621, + 5641, 5647, 5656, 5662, 5673, 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, 5755, 5764, 5770, 5790, + 5796, 5805, 5811, 5822, 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, 5906, 5915, 5921, 5941, 5947, + 5956, 5962, 5973, 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, 5896, 5905, 5911, 5931, 5937, 5946, + 5952, 5963, 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, 6047, 6056, 6062, 6082, 6088, 6097, 6103, + 6114, 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, + 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, 6420, + 6429, 6435, 5303, 5309, 5318, 5324, 5335, 5341, 5350, 5356, 5376, 5382, 5391, 5397, 5408, 5414, 5423, + 5429, 5454, 5460, 5469, 5475, 5486, 5492, 5501, 5507, 5527, 5533, 5542, 5548, 5559, 5565, 5574, 5580, + 5603, 5609, 5618, 5624, 5635, 5641, 5650, 5656, 5676, 5682, 5691, 5697, 5708, 5714, 5723, 5729, 5754, + 5760, 5769, 5775, 5786, 5792, 5801, 5807, 5827, 5833, 5842, 5848, 5859, 5865, 5874, 5880, 5744, 5750, + 5759, 5765, 5776, 5782, 5791, 5797, 5817, 5823, 5832, 5838, 5849, 5855, 5864, 5870, 5895, 5901, 5910, + 5916, 5927, 5933, 5942, 5948, 5968, 5974, 5983, 5989, 6000, 6006, 6015, 6021, 6044, 6050, 6059, 6065, + 6076, 6082, 6091, 6097, 6117, 6123, 6132, 6138, 6149, 6155, 6164, 6170, 6195, 6201, 6210, 6216, 6227, + 6233, 6242, 6248, 6268, 6274, 6283, 6289, 6300, 6306, 6315, 6321, 6116, 6122, 6131, 6137, 6148, 6154, + 6163, 6169, 6189, 6195, 6204, 6210, 6221, 6227, 6236, 6242, 6267, 6273, 6282, 6288, 6299, 6305, 6314, + 6320, 6340, 6346, 6355, 6361, 6372, 6378, 6387, 6393, 6416, 6422, 6431, 6437, 6448, 6454, 6463, 6469, + 6489, 6495, 6504, 6510, 6521, 6527, 6536, 6542, 6567, 6573, 6582, 6588, 6599, 6605, 6614, 6620, 6640, + 6646, 6655, 6661, 6672, 6678, 6687, 6693, 6557, 6563, 6572, 6578, 6589, 6595, 6604, 6610, 6630, 6636, + 6645, 6651, 6662, 6668, 6677, 6683, 6708, 6714, 6723, 6729, 6740, 6746, 6755, 6761, 6781, 6787, 6796, + 6802, 6813, 6819, 6828, 6834, 6857, 6863, 6872, 6878, 6889, 6895, 6904, 6910, 6930, 6936, 6945, 6951, + 6962, 6968, 6977, 6983, 7008, 7014, 7023, 7029, 7040, 7046, 7055, 7061, 7081, 7087, 7096, 7102, 7113, + 7119, 7128, 7134, 6392, 6398, 6407, 6413, 6424, 6430, 6439, 6445, 6465, 6471, 6480, 6486, 6497, 6503, + 6512, 6518, 6543, 6549, 6558, 6564, 6575, 6581, 6590, 6596, 6616, 6622, 6631, 6637, 6648, 6654, 6663, + 6669, 6692, 6698, 6707, 6713, 6724, 6730, 6739, 6745, 6765, 6771, 6780, 6786, 6797, 6803, 6812, 6818, + 6843, 6849, 6858, 6864, 6875, 6881, 6890, 6896, 6916, 6922, 6931, 6937, 6948, 6954, 6963, 6969, 6833, + 6839, 6848, 6854, 6865, 6871, 6880, 6886, 6906, 6912, 6921, 6927, 6938, 6944, 6953, 6959, 6984, 6990, + 6999, 7005, 7016, 7022, 7031, 7037, 7057, 7063, 7072, 7078, 7089, 7095, 7104, 7110, 7133, 7139, 7148, + 7154, 7165, 7171, 7180, 7186, 7206, 7212, 7221, 7227, 7238, 7244, 7253, 7259, 7284, 7290, 7299, 7305, + 7316, 7322, 7331, 7337, 7357, 7363, 7372, 7378, 7389, 7395, 7404, 7410, 7205, 7211, 7220, 7226, 7237, + 7243, 7252, 7258, 7278, 7284, 7293, 7299, 7310, 7316, 7325, 7331, 7356, 7362, 7371, 7377, 7388, 7394, + 7403, 7409, 7429, 7435, 7444, 7450, 7461, 7467, 7476, 7482, 7505, 7511, 7520, 7526, 7537, 7543, 7552, + 7558, 7578, 7584, 7593, 7599, 7610, 7616, 7625, 7631, 7656, 7662, 7671, 7677, 7688, 7694, 7703, 7709, + 7729, 7735, 7744, 7750, 7761 + }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan Norm => new byte[] - { - // renorm_sizes[i] = 8 - log2(i) - 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0 - }; + // This table gives, for a given sharpness, the filtering strength to be + // used (at least) in order to filter a given edge step delta. + public static readonly byte[,] LevelsFromDelta = + { + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, + 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, + 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, + 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, + 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, + 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, + 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, + 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, + 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, + 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + }, + { + 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, + 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, + 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 + } + }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan NewRange => new byte[] - { - // range = ((range + 1) << kVP8Log2Range[range]) - 1 - 127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239, - 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, - 247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, - 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, - 243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, - 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, - 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, - 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, - 241, 243, 245, 247, 249, 251, 253, 127 - }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + public static ReadOnlySpan Norm => new byte[] + { + // renorm_sizes[i] = 8 - log2(i) + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 + }; - public static readonly ushort[] Vp8EntropyCost = - { - 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, - 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, - 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, - 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, - 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, - 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, - 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, - 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, - 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, - 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, - 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, - 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, - 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, - 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, - 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, - 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, - 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, - 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, - 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, - 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, - 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, - 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, - 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, - 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, - 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, - 10, 9, 7, 6, 4, 3 - }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + public static ReadOnlySpan NewRange => new byte[] + { + // range = ((range + 1) << kVP8Log2Range[range]) - 1 + 127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239, + 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, + 247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, + 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, + 243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, + 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, + 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, + 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 + }; - public static readonly ushort[][] Vp8LevelCodes = - { - new ushort[] { 0x001, 0x000 }, new ushort[] { 0x007, 0x001 }, new ushort[] { 0x00f, 0x005 }, - new ushort[] { 0x00f, 0x00d }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x023 }, - new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x0d3, 0x013 }, - new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, - new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x093 }, - new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, - new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, - new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, - new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, - new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x153 }, - }; + public static readonly ushort[] Vp8EntropyCost = + { + 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, + 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, + 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, + 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, + 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, + 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, + 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, + 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, + 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, + 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, + 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, + 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, + 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, + 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, + 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, + 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, + 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, + 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, + 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, + 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, + 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, + 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, + 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, + 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, + 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, + 10, 9, 7, 6, 4, 3 + }; - /// - /// Lookup table for small values of log2(int). - /// - public static readonly float[] Log2Table = - { - 0.0000000000000000f, 0.0000000000000000f, - 1.0000000000000000f, 1.5849625007211560f, - 2.0000000000000000f, 2.3219280948873621f, - 2.5849625007211560f, 2.8073549220576041f, - 3.0000000000000000f, 3.1699250014423121f, - 3.3219280948873621f, 3.4594316186372973f, - 3.5849625007211560f, 3.7004397181410921f, - 3.8073549220576041f, 3.9068905956085187f, - 4.0000000000000000f, 4.0874628412503390f, - 4.1699250014423121f, 4.2479275134435852f, - 4.3219280948873626f, 4.3923174227787606f, - 4.4594316186372973f, 4.5235619560570130f, - 4.5849625007211560f, 4.6438561897747243f, - 4.7004397181410917f, 4.7548875021634682f, - 4.8073549220576037f, 4.8579809951275718f, - 4.9068905956085187f, 4.9541963103868749f, - 5.0000000000000000f, 5.0443941193584533f, - 5.0874628412503390f, 5.1292830169449663f, - 5.1699250014423121f, 5.2094533656289501f, - 5.2479275134435852f, 5.2854022188622487f, - 5.3219280948873626f, 5.3575520046180837f, - 5.3923174227787606f, 5.4262647547020979f, - 5.4594316186372973f, 5.4918530963296747f, - 5.5235619560570130f, 5.5545888516776376f, - 5.5849625007211560f, 5.6147098441152083f, - 5.6438561897747243f, 5.6724253419714951f, - 5.7004397181410917f, 5.7279204545631987f, - 5.7548875021634682f, 5.7813597135246599f, - 5.8073549220576037f, 5.8328900141647412f, - 5.8579809951275718f, 5.8826430493618415f, - 5.9068905956085187f, 5.9307373375628866f, - 5.9541963103868749f, 5.9772799234999167f, - 6.0000000000000000f, 6.0223678130284543f, - 6.0443941193584533f, 6.0660891904577720f, - 6.0874628412503390f, 6.1085244567781691f, - 6.1292830169449663f, 6.1497471195046822f, - 6.1699250014423121f, 6.1898245588800175f, - 6.2094533656289501f, 6.2288186904958804f, - 6.2479275134435852f, 6.2667865406949010f, - 6.2854022188622487f, 6.3037807481771030f, - 6.3219280948873626f, 6.3398500028846243f, - 6.3575520046180837f, 6.3750394313469245f, - 6.3923174227787606f, 6.4093909361377017f, - 6.4262647547020979f, 6.4429434958487279f, - 6.4594316186372973f, 6.4757334309663976f, - 6.4918530963296747f, 6.5077946401986963f, - 6.5235619560570130f, 6.5391588111080309f, - 6.5545888516776376f, 6.5698556083309478f, - 6.5849625007211560f, 6.5999128421871278f, - 6.6147098441152083f, 6.6293566200796094f, - 6.6438561897747243f, 6.6582114827517946f, - 6.6724253419714951f, 6.6865005271832185f, - 6.7004397181410917f, 6.7142455176661224f, - 6.7279204545631987f, 6.7414669864011464f, - 6.7548875021634682f, 6.7681843247769259f, - 6.7813597135246599f, 6.7944158663501061f, - 6.8073549220576037f, 6.8201789624151878f, - 6.8328900141647412f, 6.8454900509443747f, - 6.8579809951275718f, 6.8703647195834047f, - 6.8826430493618415f, 6.8948177633079437f, - 6.9068905956085187f, 6.9188632372745946f, - 6.9307373375628866f, 6.9425145053392398f, - 6.9541963103868749f, 6.9657842846620869f, - 6.9772799234999167f, 6.9886846867721654f, - 7.0000000000000000f, 7.0112272554232539f, - 7.0223678130284543f, 7.0334230015374501f, - 7.0443941193584533f, 7.0552824355011898f, - 7.0660891904577720f, 7.0768155970508308f, - 7.0874628412503390f, 7.0980320829605263f, - 7.1085244567781691f, 7.1189410727235076f, - 7.1292830169449663f, 7.1395513523987936f, - 7.1497471195046822f, 7.1598713367783890f, - 7.1699250014423121f, 7.1799090900149344f, - 7.1898245588800175f, 7.1996723448363644f, - 7.2094533656289501f, 7.2191685204621611f, - 7.2288186904958804f, 7.2384047393250785f, - 7.2479275134435852f, 7.2573878426926521f, - 7.2667865406949010f, 7.2761244052742375f, - 7.2854022188622487f, 7.2946207488916270f, - 7.3037807481771030f, 7.3128829552843557f, - 7.3219280948873626f, 7.3309168781146167f, - 7.3398500028846243f, 7.3487281542310771f, - 7.3575520046180837f, 7.3663222142458160f, - 7.3750394313469245f, 7.3837042924740519f, - 7.3923174227787606f, 7.4008794362821843f, - 7.4093909361377017f, 7.4178525148858982f, - 7.4262647547020979f, 7.4346282276367245f, - 7.4429434958487279f, 7.4512111118323289f, - 7.4594316186372973f, 7.4676055500829976f, - 7.4757334309663976f, 7.4838157772642563f, - 7.4918530963296747f, 7.4998458870832056f, - 7.5077946401986963f, 7.5156998382840427f, - 7.5235619560570130f, 7.5313814605163118f, - 7.5391588111080309f, 7.5468944598876364f, - 7.5545888516776376f, 7.5622424242210728f, - 7.5698556083309478f, 7.5774288280357486f, - 7.5849625007211560f, 7.5924570372680806f, - 7.5999128421871278f, 7.6073303137496104f, - 7.6147098441152083f, 7.6220518194563764f, - 7.6293566200796094f, 7.6366246205436487f, - 7.6438561897747243f, 7.6510516911789281f, - 7.6582114827517946f, 7.6653359171851764f, - 7.6724253419714951f, 7.6794800995054464f, - 7.6865005271832185f, 7.6934869574993252f, - 7.7004397181410917f, 7.7073591320808825f, - 7.7142455176661224f, 7.7210991887071855f, - 7.7279204545631987f, 7.7347096202258383f, - 7.7414669864011464f, 7.7481928495894605f, - 7.7548875021634682f, 7.7615512324444795f, - 7.7681843247769259f, 7.7747870596011736f, - 7.7813597135246599f, 7.7879025593914317f, - 7.7944158663501061f, 7.8008998999203047f, - 7.8073549220576037f, 7.8137811912170374f, - 7.8201789624151878f, 7.8265484872909150f, - 7.8328900141647412f, 7.8392037880969436f, - 7.8454900509443747f, 7.8517490414160571f, - 7.8579809951275718f, 7.8641861446542797f, - 7.8703647195834047f, 7.8765169465649993f, - 7.8826430493618415f, 7.8887432488982591f, - 7.8948177633079437f, 7.9008668079807486f, - 7.9068905956085187f, 7.9128893362299619f, - 7.9188632372745946f, 7.9248125036057812f, - 7.9307373375628866f, 7.9366379390025709f, - 7.9425145053392398f, 7.9483672315846778f, - 7.9541963103868749f, 7.9600019320680805f, - 7.9657842846620869f, 7.9715435539507719f, - 7.9772799234999167f, 7.9829935746943103f, - 7.9886846867721654f, 7.9943534368588577f - }; + public static readonly ushort[][] Vp8LevelCodes = + { + new ushort[] { 0x001, 0x000 }, new ushort[] { 0x007, 0x001 }, new ushort[] { 0x00f, 0x005 }, + new ushort[] { 0x00f, 0x00d }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x003 }, new ushort[] { 0x033, 0x023 }, + new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x033, 0x023 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, + new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x013 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, + new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x0d3, 0x093 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, + new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x053 }, new ushort[] { 0x153, 0x153 }, + }; - public static readonly float[] SLog2Table = - { - 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, - 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, - 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, - 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, - 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, - 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, - 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, - 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, - 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, - 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, - 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, - 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, - 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, - 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, - 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, - 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, - 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, - 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, - 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, - 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, - 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, - 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, - 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, - 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, - 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, - 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, - 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, - 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, - 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, - 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, - 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, - 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, - 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, - 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, - 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, - 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, - 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, - 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, - 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, - 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, - 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, - 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, - 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, - 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, - 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, - 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, - 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, - 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, - 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, - 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, - 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, - 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, - 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, - 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, - 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, - 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, - 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, - 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, - 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, - 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, - 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, - 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, - 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, - 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f - }; + /// + /// Lookup table for small values of log2(int). + /// + public static readonly float[] Log2Table = + { + 0.0000000000000000f, 0.0000000000000000f, + 1.0000000000000000f, 1.5849625007211560f, + 2.0000000000000000f, 2.3219280948873621f, + 2.5849625007211560f, 2.8073549220576041f, + 3.0000000000000000f, 3.1699250014423121f, + 3.3219280948873621f, 3.4594316186372973f, + 3.5849625007211560f, 3.7004397181410921f, + 3.8073549220576041f, 3.9068905956085187f, + 4.0000000000000000f, 4.0874628412503390f, + 4.1699250014423121f, 4.2479275134435852f, + 4.3219280948873626f, 4.3923174227787606f, + 4.4594316186372973f, 4.5235619560570130f, + 4.5849625007211560f, 4.6438561897747243f, + 4.7004397181410917f, 4.7548875021634682f, + 4.8073549220576037f, 4.8579809951275718f, + 4.9068905956085187f, 4.9541963103868749f, + 5.0000000000000000f, 5.0443941193584533f, + 5.0874628412503390f, 5.1292830169449663f, + 5.1699250014423121f, 5.2094533656289501f, + 5.2479275134435852f, 5.2854022188622487f, + 5.3219280948873626f, 5.3575520046180837f, + 5.3923174227787606f, 5.4262647547020979f, + 5.4594316186372973f, 5.4918530963296747f, + 5.5235619560570130f, 5.5545888516776376f, + 5.5849625007211560f, 5.6147098441152083f, + 5.6438561897747243f, 5.6724253419714951f, + 5.7004397181410917f, 5.7279204545631987f, + 5.7548875021634682f, 5.7813597135246599f, + 5.8073549220576037f, 5.8328900141647412f, + 5.8579809951275718f, 5.8826430493618415f, + 5.9068905956085187f, 5.9307373375628866f, + 5.9541963103868749f, 5.9772799234999167f, + 6.0000000000000000f, 6.0223678130284543f, + 6.0443941193584533f, 6.0660891904577720f, + 6.0874628412503390f, 6.1085244567781691f, + 6.1292830169449663f, 6.1497471195046822f, + 6.1699250014423121f, 6.1898245588800175f, + 6.2094533656289501f, 6.2288186904958804f, + 6.2479275134435852f, 6.2667865406949010f, + 6.2854022188622487f, 6.3037807481771030f, + 6.3219280948873626f, 6.3398500028846243f, + 6.3575520046180837f, 6.3750394313469245f, + 6.3923174227787606f, 6.4093909361377017f, + 6.4262647547020979f, 6.4429434958487279f, + 6.4594316186372973f, 6.4757334309663976f, + 6.4918530963296747f, 6.5077946401986963f, + 6.5235619560570130f, 6.5391588111080309f, + 6.5545888516776376f, 6.5698556083309478f, + 6.5849625007211560f, 6.5999128421871278f, + 6.6147098441152083f, 6.6293566200796094f, + 6.6438561897747243f, 6.6582114827517946f, + 6.6724253419714951f, 6.6865005271832185f, + 6.7004397181410917f, 6.7142455176661224f, + 6.7279204545631987f, 6.7414669864011464f, + 6.7548875021634682f, 6.7681843247769259f, + 6.7813597135246599f, 6.7944158663501061f, + 6.8073549220576037f, 6.8201789624151878f, + 6.8328900141647412f, 6.8454900509443747f, + 6.8579809951275718f, 6.8703647195834047f, + 6.8826430493618415f, 6.8948177633079437f, + 6.9068905956085187f, 6.9188632372745946f, + 6.9307373375628866f, 6.9425145053392398f, + 6.9541963103868749f, 6.9657842846620869f, + 6.9772799234999167f, 6.9886846867721654f, + 7.0000000000000000f, 7.0112272554232539f, + 7.0223678130284543f, 7.0334230015374501f, + 7.0443941193584533f, 7.0552824355011898f, + 7.0660891904577720f, 7.0768155970508308f, + 7.0874628412503390f, 7.0980320829605263f, + 7.1085244567781691f, 7.1189410727235076f, + 7.1292830169449663f, 7.1395513523987936f, + 7.1497471195046822f, 7.1598713367783890f, + 7.1699250014423121f, 7.1799090900149344f, + 7.1898245588800175f, 7.1996723448363644f, + 7.2094533656289501f, 7.2191685204621611f, + 7.2288186904958804f, 7.2384047393250785f, + 7.2479275134435852f, 7.2573878426926521f, + 7.2667865406949010f, 7.2761244052742375f, + 7.2854022188622487f, 7.2946207488916270f, + 7.3037807481771030f, 7.3128829552843557f, + 7.3219280948873626f, 7.3309168781146167f, + 7.3398500028846243f, 7.3487281542310771f, + 7.3575520046180837f, 7.3663222142458160f, + 7.3750394313469245f, 7.3837042924740519f, + 7.3923174227787606f, 7.4008794362821843f, + 7.4093909361377017f, 7.4178525148858982f, + 7.4262647547020979f, 7.4346282276367245f, + 7.4429434958487279f, 7.4512111118323289f, + 7.4594316186372973f, 7.4676055500829976f, + 7.4757334309663976f, 7.4838157772642563f, + 7.4918530963296747f, 7.4998458870832056f, + 7.5077946401986963f, 7.5156998382840427f, + 7.5235619560570130f, 7.5313814605163118f, + 7.5391588111080309f, 7.5468944598876364f, + 7.5545888516776376f, 7.5622424242210728f, + 7.5698556083309478f, 7.5774288280357486f, + 7.5849625007211560f, 7.5924570372680806f, + 7.5999128421871278f, 7.6073303137496104f, + 7.6147098441152083f, 7.6220518194563764f, + 7.6293566200796094f, 7.6366246205436487f, + 7.6438561897747243f, 7.6510516911789281f, + 7.6582114827517946f, 7.6653359171851764f, + 7.6724253419714951f, 7.6794800995054464f, + 7.6865005271832185f, 7.6934869574993252f, + 7.7004397181410917f, 7.7073591320808825f, + 7.7142455176661224f, 7.7210991887071855f, + 7.7279204545631987f, 7.7347096202258383f, + 7.7414669864011464f, 7.7481928495894605f, + 7.7548875021634682f, 7.7615512324444795f, + 7.7681843247769259f, 7.7747870596011736f, + 7.7813597135246599f, 7.7879025593914317f, + 7.7944158663501061f, 7.8008998999203047f, + 7.8073549220576037f, 7.8137811912170374f, + 7.8201789624151878f, 7.8265484872909150f, + 7.8328900141647412f, 7.8392037880969436f, + 7.8454900509443747f, 7.8517490414160571f, + 7.8579809951275718f, 7.8641861446542797f, + 7.8703647195834047f, 7.8765169465649993f, + 7.8826430493618415f, 7.8887432488982591f, + 7.8948177633079437f, 7.9008668079807486f, + 7.9068905956085187f, 7.9128893362299619f, + 7.9188632372745946f, 7.9248125036057812f, + 7.9307373375628866f, 7.9366379390025709f, + 7.9425145053392398f, 7.9483672315846778f, + 7.9541963103868749f, 7.9600019320680805f, + 7.9657842846620869f, 7.9715435539507719f, + 7.9772799234999167f, 7.9829935746943103f, + 7.9886846867721654f, 7.9943534368588577f + }; - public static readonly int[] CodeToPlane = - { - 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, - 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, - 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, - 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, - 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, - 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, - 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, - 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, - 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, - 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, - 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, - 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 - }; + public static readonly float[] SLog2Table = + { + 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, + 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, + 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, + 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, + 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, + 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, + 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, + 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, + 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, + 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, + 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, + 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, + 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, + 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, + 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, + 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, + 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, + 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, + 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, + 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, + 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, + 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, + 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, + 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, + 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, + 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, + 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, + 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, + 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, + 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, + 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, + 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, + 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, + 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, + 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, + 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, + 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, + 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, + 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, + 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, + 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, + 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, + 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, + 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, + 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, + 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, + 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, + 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, + 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, + 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, + 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, + 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, + 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, + 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, + 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, + 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, + 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, + 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, + 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, + 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, + 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, + 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, + 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, + 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f + }; - public static readonly uint[] PlaneToCodeLut = - { - 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, - 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, - 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, - 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, - 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, - 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, - 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, - 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 - }; + public static readonly int[] CodeToPlane = + { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 + }; - // 31 ^ clz(i) - public static ReadOnlySpan LogTable8Bit => new byte[] - { - 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - }; + public static readonly uint[] PlaneToCodeLut = + { + 96, 73, 55, 39, 23, 13, 5, 1, 255, 255, 255, 255, 255, 255, 255, 255, + 101, 78, 58, 42, 26, 16, 8, 2, 0, 3, 9, 17, 27, 43, 59, 79, + 102, 86, 62, 46, 32, 20, 10, 6, 4, 7, 11, 21, 33, 47, 63, 87, + 105, 90, 70, 52, 37, 28, 18, 14, 12, 15, 19, 29, 38, 53, 71, 91, + 110, 99, 82, 66, 48, 35, 30, 24, 22, 25, 31, 36, 49, 67, 83, 100, + 115, 108, 94, 76, 64, 50, 44, 40, 34, 41, 45, 51, 65, 77, 95, 109, + 118, 113, 103, 92, 80, 68, 60, 56, 54, 57, 61, 69, 81, 93, 104, 114, + 119, 116, 111, 106, 97, 88, 84, 74, 72, 75, 85, 89, 98, 107, 112, 117 + }; - // Paragraph 14.1 - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan DcTable => new byte[] - { - 4, 5, 6, 7, 8, 9, 10, 10, - 11, 12, 13, 14, 15, 16, 17, 17, - 18, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 25, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, 36, - 37, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, - 91, 93, 95, 96, 98, 100, 101, 102, - 104, 106, 108, 110, 112, 114, 116, 118, - 122, 124, 126, 128, 130, 132, 134, 136, - 138, 140, 143, 145, 148, 151, 154, 157 - }; + // 31 ^ clz(i) + public static ReadOnlySpan LogTable8Bit => new byte[] + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; - // Paragraph 14.1 - public static readonly ushort[] AcTable = - { - 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 60, - 62, 64, 66, 68, 70, 72, 74, 76, - 78, 80, 82, 84, 86, 88, 90, 92, - 94, 96, 98, 100, 102, 104, 106, 108, - 110, 112, 114, 116, 119, 122, 125, 128, - 131, 134, 137, 140, 143, 146, 149, 152, - 155, 158, 161, 164, 167, 170, 173, 177, - 181, 185, 189, 193, 197, 201, 205, 209, - 213, 217, 221, 225, 229, 234, 239, 245, - 249, 254, 259, 264, 269, 274, 279, 284 - }; + // Paragraph 14.1 + // This uses C#'s compiler optimization to refer to assembly's static data directly. + public static ReadOnlySpan DcTable => new byte[] + { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 + }; - public static readonly ushort[] AcTable2 = - { - 8, 8, 9, 10, 12, 13, 15, 17, - 18, 20, 21, 23, 24, 26, 27, 29, - 31, 32, 34, 35, 37, 38, 40, 41, - 43, 44, 46, 48, 49, 51, 52, 54, - 55, 57, 58, 60, 62, 63, 65, 66, - 68, 69, 71, 72, 74, 75, 77, 79, - 80, 82, 83, 85, 86, 88, 89, 93, - 96, 99, 102, 105, 108, 111, 114, 117, - 120, 124, 127, 130, 133, 136, 139, 142, - 145, 148, 151, 155, 158, 161, 164, 167, - 170, 173, 176, 179, 184, 189, 193, 198, - 203, 207, 212, 217, 221, 226, 230, 235, - 240, 244, 249, 254, 258, 263, 268, 274, - 280, 286, 292, 299, 305, 311, 317, 323, - 330, 336, 342, 348, 354, 362, 370, 379, - 385, 393, 401, 409, 416, 424, 432, 440 - }; + // Paragraph 14.1 + public static readonly ushort[] AcTable = + { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 + }; - // Paragraph 13 - public static readonly byte[,,,] CoeffsUpdateProba = - { - { - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, - { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } - }, - { - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } - }, - { - { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - }, - { - { - { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - }, - { - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, - { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } - } - } - }; + public static readonly ushort[] AcTable2 = + { + 8, 8, 9, 10, 12, 13, 15, 17, + 18, 20, 21, 23, 24, 26, 27, 29, + 31, 32, 34, 35, 37, 38, 40, 41, + 43, 44, 46, 48, 49, 51, 52, 54, + 55, 57, 58, 60, 62, 63, 65, 66, + 68, 69, 71, 72, 74, 75, 77, 79, + 80, 82, 83, 85, 86, 88, 89, 93, + 96, 99, 102, 105, 108, 111, 114, 117, + 120, 124, 127, 130, 133, 136, 139, 142, + 145, 148, 151, 155, 158, 161, 164, 167, + 170, 173, 176, 179, 184, 189, 193, 198, + 203, 207, 212, 217, 221, 226, 230, 235, + 240, 244, 249, 254, 258, 263, 268, 274, + 280, 286, 292, 299, 305, 311, 317, 323, + 330, 336, 342, 348, 354, 362, 370, 379, + 385, 393, 401, 409, 416, 424, 432, 440 + }; - // Paragraph 13.5: Default Token Probability Table. - public static readonly byte[,,,] DefaultCoeffsProba = - { - { - { - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, - { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, - { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } - }, - { - { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, - { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, - { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, - }, - { - { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, - { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, - { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, - }, - { - { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, - { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, - { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } - }, - { - { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, - { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, - { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } - }, - { - { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, - { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, - { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } - }, - { - { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { - { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, - { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, - { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } - }, - { - { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, - { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, - { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } - }, - { - { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, - { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, - { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } - }, - { - { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, - { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, - { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } - }, - { - { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, - { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, - { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } - }, - { - { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, - { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, - { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } - }, - { - { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, - { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, - { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } - }, - { - { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } - } - }, - { - { - { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, - { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, - { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } - }, - { - { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, - { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, - { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } - }, - { - { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, - { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, - { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } - }, - { - { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, - { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, - { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, - { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - }, - { - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } - } - }, - { - { - { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, - { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, - { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } - }, - { - { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, - { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, - { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } - }, - { - { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, - { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, - { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } - }, - { - { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, - { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, - { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } - }, - { - { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, - { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, - { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } - }, - { - { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, - { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, - { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } - }, - { - { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, - { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, - { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } - }, - { - { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, - { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } - } - } - }; + // Paragraph 13 + public static readonly byte[,,,] CoeffsUpdateProba = + { + { + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { + { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { + { + { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } + }; - public static readonly (int Code, int ExtraBits)[] PrefixEncodeCode = - { - (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), - (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), - (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), - (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), - (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), - (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), - (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), - (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), - (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), - (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), - (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), - (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), - }; + // Paragraph 13.5: Default Token Probability Table. + public static readonly byte[,,,] DefaultCoeffsProba = + { + { + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { + { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { + { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { + { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { + { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { + { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { + { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { + { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { + { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { + { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { + { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { + { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { + { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { + { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { + { + { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { + { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { + { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { + { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { + { + { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { + { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { + { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { + { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { + { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { + { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { + { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { + { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } + }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - public static ReadOnlySpan PrefixEncodeExtraBitsValue => new byte[] - { - 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, - 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, - 123, 124, 125, 126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, - 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, - 118, 119, 120, 121, 122, 123, 124, 125, 126 - }; + public static readonly (int Code, int ExtraBits)[] PrefixEncodeCode = + { + (0, 0), (0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 1), (5, 1), + (5, 1), (6, 2), (6, 2), (6, 2), (6, 2), (7, 2), (7, 2), (7, 2), + (7, 2), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), (8, 3), + (8, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), (9, 3), + (9, 3), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), (10, 4), + (10, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), (11, 4), + (11, 4), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), (12, 5), + (12, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), (13, 5), + (13, 5), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), (14, 6), + (14, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), (15, 6), + (15, 6), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), (16, 7), + (16, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), (17, 7), + }; - // Following table is (1 << AlphaFix) / a. The (v * InvAlpha[a]) >> AlphaFix - // formula is then equal to v / a in most (99.6%) cases. Note that this table - // and constant are adjusted very tightly to fit 32b arithmetic. - // In particular, they use the fact that the operands for 'v / a' are actually - // derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3 - // with ai in [0..255] and pi in [0..1< PrefixEncodeExtraBitsValue => new byte[] + { + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 125, 126, 127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126 + }; - static WebpLookupTables() - { - double scale = (double)(1 << WebpConstants.GammaTabFix) / WebpConstants.GammaScale; - double norm = 1.0d / 255.0d; - for (int v = 0; v < 256; ++v) - { - GammaToLinearTab[v] = (ushort)((Math.Pow(norm * v, WebpConstants.Gamma) * WebpConstants.GammaScale) + .5); - } + // Following table is (1 << AlphaFix) / a. The (v * InvAlpha[a]) >> AlphaFix + // formula is then equal to v / a in most (99.6%) cases. Note that this table + // and constant are adjusted very tightly to fit 32b arithmetic. + // In particular, they use the fact that the operands for 'v / a' are actually + // derived as v = (a0.p0 + a1.p1 + a2.p2 + a3.p3) and a = a0 + a1 + a2 + a3 + // with ai in [0..255] and pi in [0..1< Abs0Table => new byte[] - { - 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, - 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, - 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, 0xcf, 0xce, 0xcd, - 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, - 0xbb, 0xba, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, 0xab, - 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, - 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, - 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, - 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, - 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, - 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, - 0x44, 0x43, 0x42, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, - 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, - 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, - 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, - 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, - 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, - 0xff - }; + InitializeModesProbabilities(); + InitializeFixedCostsI4(); + } - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Clip1Table => new byte[] - { - 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, - 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, - 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, - 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, - 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, - 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, - 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff - }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan Abs0Table => new byte[] + { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0, 0xef, + 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, + 0xdd, 0xdc, 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, 0xcf, 0xce, 0xcd, + 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, + 0xbb, 0xba, 0xb9, 0xb8, 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, 0xab, + 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, + 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, + 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, + 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, + 0x66, 0x65, 0x64, 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, 0x57, 0x56, + 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, + 0x44, 0x43, 0x42, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, + 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, + 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, + 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0xff + }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Sclip1Table => new sbyte[] - { - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, - -128, -128, -128, -128, -128, -128, -128, -128, -128, -127, -126, -125, -124, -123, -122, -121, -120, - -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, - -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, - -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, - -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, - -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, - -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, - 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, - 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127 - }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan Clip1Table => new byte[] + { + 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, 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, 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, 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, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff + }; - // This uses C#'s compiler optimization to refer to assembly's static data directly. - private static ReadOnlySpan Sclip2Table => new sbyte[] - { - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, - -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -15, -14, -13, -12, -11, -10, -9, -8, - -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 - }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan Sclip1Table => new sbyte[] + { + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, + -128, -128, -128, -128, -128, -128, -128, -128, -128, -127, -126, -125, -124, -123, -122, -121, -120, + -119, -118, -117, -116, -115, -114, -113, -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, + -102, -101, -100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, + -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, + -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, + -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, + -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, + 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127 + }; - private static void InitializeModesProbabilities() - { - // Paragraph 11.5 - ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; - ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; - ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; - ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; - ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; - ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; - ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; - ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; - ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; - ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; - ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; - ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; - ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; - ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; - ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; - ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; - ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; - ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; - ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; - ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; - ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; - ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; - ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; - ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; - ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; - ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; - ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; - ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; - ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; - ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; - ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; - ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; - ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; - ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; - ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; - ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; - ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; - ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; - ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; - ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; - ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; - ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; - ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; - ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; - ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; - ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; - ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; - ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; - ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; - ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; - ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; - ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; - ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; - ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; - ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; - ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; - ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; - ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; - ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; - ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; - ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; - ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; - ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; - ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; - ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; - ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; - ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; - ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; - ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; - ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; - ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; - ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; - ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; - ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; - ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; - ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; - ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; - ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; - ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; - ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; - ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; - ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; - ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; - ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; - ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; - ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; - ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; - ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; - ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; - ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; - ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; - ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; - ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; - ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; - ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; - ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; - ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; - ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; - ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; - ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; - } + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan Sclip2Table => new sbyte[] + { + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -16, -15, -14, -13, -12, -11, -10, -9, -8, + -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 + }; - private static void InitializeFixedCostsI4() - { - Vp8FixedCostsI4[0, 0] = new short[] { 40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137 }; - Vp8FixedCostsI4[0, 1] = new short[] { 192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522 }; - Vp8FixedCostsI4[0, 2] = new short[] { 142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657 }; - Vp8FixedCostsI4[0, 3] = new short[] { 559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007 }; - Vp8FixedCostsI4[0, 4] = new short[] { 299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142 }; - Vp8FixedCostsI4[0, 5] = new short[] { 277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291 }; - Vp8FixedCostsI4[0, 6] = new short[] { 214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918 }; - Vp8FixedCostsI4[0, 7] = new short[] { 152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390 }; - Vp8FixedCostsI4[0, 8] = new short[] { 512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207 }; - Vp8FixedCostsI4[0, 9] = new short[] { 424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538 }; - Vp8FixedCostsI4[1, 0] = new short[] { 240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099 }; - Vp8FixedCostsI4[1, 1] = new short[] { 467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391 }; - Vp8FixedCostsI4[1, 2] = new short[] { 500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114 }; - Vp8FixedCostsI4[1, 3] = new short[] { 672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876 }; - Vp8FixedCostsI4[1, 4] = new short[] { 458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033 }; - Vp8FixedCostsI4[1, 5] = new short[] { 504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268 }; - Vp8FixedCostsI4[1, 6] = new short[] { 333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598 }; - Vp8FixedCostsI4[1, 7] = new short[] { 399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015 }; - Vp8FixedCostsI4[1, 8] = new short[] { 622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971 }; - Vp8FixedCostsI4[1, 9] = new short[] { 500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523 }; - Vp8FixedCostsI4[2, 0] = new short[] { 394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181 }; - Vp8FixedCostsI4[2, 1] = new short[] { 655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553 }; - Vp8FixedCostsI4[2, 2] = new short[] { 690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232 }; - Vp8FixedCostsI4[2, 3] = new short[] { 559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784 }; - Vp8FixedCostsI4[2, 4] = new short[] { 690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126 }; - Vp8FixedCostsI4[2, 5] = new short[] { 740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513 }; - Vp8FixedCostsI4[2, 6] = new short[] { 323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655 }; - Vp8FixedCostsI4[2, 7] = new short[] { 488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461 }; - Vp8FixedCostsI4[2, 8] = new short[] { 740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721 }; - Vp8FixedCostsI4[2, 9] = new short[] { 522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697 }; - Vp8FixedCostsI4[3, 0] = new short[] { 105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579 }; - Vp8FixedCostsI4[3, 1] = new short[] { 534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170 }; - Vp8FixedCostsI4[3, 2] = new short[] { 305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242 }; - Vp8FixedCostsI4[3, 3] = new short[] { 683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947 }; - Vp8FixedCostsI4[3, 4] = new short[] { 394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047 }; - Vp8FixedCostsI4[3, 5] = new short[] { 528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358 }; - Vp8FixedCostsI4[3, 6] = new short[] { 347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667 }; - Vp8FixedCostsI4[3, 7] = new short[] { 218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617 }; - Vp8FixedCostsI4[3, 8] = new short[] { 672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087 }; - Vp8FixedCostsI4[3, 9] = new short[] { 592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822 }; - Vp8FixedCostsI4[4, 0] = new short[] { 228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782 }; - Vp8FixedCostsI4[4, 1] = new short[] { 494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363 }; - Vp8FixedCostsI4[4, 2] = new short[] { 512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463 }; - Vp8FixedCostsI4[4, 3] = new short[] { 683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939 }; - Vp8FixedCostsI4[4, 4] = new short[] { 622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219 }; - Vp8FixedCostsI4[4, 5] = new short[] { 631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170 }; - Vp8FixedCostsI4[4, 6] = new short[] { 555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429 }; - Vp8FixedCostsI4[4, 7] = new short[] { 504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406 }; - Vp8FixedCostsI4[4, 8] = new short[] { 683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122 }; - Vp8FixedCostsI4[4, 9] = new short[] { 399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678 }; - Vp8FixedCostsI4[5, 0] = new short[] { 333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219 }; - Vp8FixedCostsI4[5, 1] = new short[] { 512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169 }; - Vp8FixedCostsI4[5, 2] = new short[] { 572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995 }; - Vp8FixedCostsI4[5, 3] = new short[] { 786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768 }; - Vp8FixedCostsI4[5, 4] = new short[] { 690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897 }; - Vp8FixedCostsI4[5, 5] = new short[] { 768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453 }; - Vp8FixedCostsI4[5, 6] = new short[] { 452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813 }; - Vp8FixedCostsI4[5, 7] = new short[] { 394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109 }; - Vp8FixedCostsI4[5, 8] = new short[] { 559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028 }; - Vp8FixedCostsI4[5, 9] = new short[] { 564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764 }; - Vp8FixedCostsI4[6, 0] = new short[] { 266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461 }; - Vp8FixedCostsI4[6, 1] = new short[] { 366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054 }; - Vp8FixedCostsI4[6, 2] = new short[] { 452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150 }; - Vp8FixedCostsI4[6, 3] = new short[] { 555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496 }; - Vp8FixedCostsI4[6, 4] = new short[] { 704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578 }; - Vp8FixedCostsI4[6, 5] = new short[] { 672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949 }; - Vp8FixedCostsI4[6, 6] = new short[] { 296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722 }; - Vp8FixedCostsI4[6, 7] = new short[] { 342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052 }; - Vp8FixedCostsI4[6, 8] = new short[] { 555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494 }; - Vp8FixedCostsI4[6, 9] = new short[] { 288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509 }; - Vp8FixedCostsI4[7, 0] = new short[] { 342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151 }; - Vp8FixedCostsI4[7, 1] = new short[] { 483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266 }; - Vp8FixedCostsI4[7, 2] = new short[] { 488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109 }; - Vp8FixedCostsI4[7, 3] = new short[] { 522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605 }; - Vp8FixedCostsI4[7, 4] = new short[] { 709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057 }; - Vp8FixedCostsI4[7, 5] = new short[] { 512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350 }; - Vp8FixedCostsI4[7, 6] = new short[] { 452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922 }; - Vp8FixedCostsI4[7, 7] = new short[] { 403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547 }; - Vp8FixedCostsI4[7, 8] = new short[] { 559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984 }; - Vp8FixedCostsI4[7, 9] = new short[] { 547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651 }; - Vp8FixedCostsI4[8, 0] = new short[] { 165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612 }; - Vp8FixedCostsI4[8, 1] = new short[] { 592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041 }; - Vp8FixedCostsI4[8, 2] = new short[] { 403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407 }; - Vp8FixedCostsI4[8, 3] = new short[] { 896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035 }; - Vp8FixedCostsI4[8, 4] = new short[] { 640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865 }; - Vp8FixedCostsI4[8, 5] = new short[] { 559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435 }; - Vp8FixedCostsI4[8, 6] = new short[] { 415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522 }; - Vp8FixedCostsI4[8, 7] = new short[] { 406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277 }; - Vp8FixedCostsI4[8, 8] = new short[] { 968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403 }; - Vp8FixedCostsI4[8, 9] = new short[] { 732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755 }; - Vp8FixedCostsI4[9, 0] = new short[] { 111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307 }; - Vp8FixedCostsI4[9, 1] = new short[] { 406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793 }; - Vp8FixedCostsI4[9, 2] = new short[] { 342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502 }; - Vp8FixedCostsI4[9, 3] = new short[] { 559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802 }; - Vp8FixedCostsI4[9, 4] = new short[] { 473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782 }; - Vp8FixedCostsI4[9, 5] = new short[] { 342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057 }; - Vp8FixedCostsI4[9, 6] = new short[] { 208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712 }; - Vp8FixedCostsI4[9, 7] = new short[] { 228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318 }; - Vp8FixedCostsI4[9, 8] = new short[] { 768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825 }; - Vp8FixedCostsI4[9, 9] = new short[] { 305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501 }; - } + private static void InitializeModesProbabilities() + { + // Paragraph 11.5 + ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 }; + ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 }; + ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 }; + ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 }; + ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 }; + ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 }; + ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 }; + ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 }; + ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 }; + ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 }; + ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 }; + ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 }; + ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 }; + ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 }; + ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 }; + ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 }; + ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 }; + ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 }; + ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 }; + ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 }; + ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 }; + ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 }; + ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 }; + ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 }; + ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 }; + ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 }; + ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 }; + ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 }; + ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 }; + ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 }; + ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 }; + ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 }; + ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 }; + ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 }; + ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 }; + ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 }; + ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 }; + ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 }; + ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 }; + ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 }; + ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 }; + ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 }; + ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 }; + ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 }; + ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 }; + ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 }; + ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 }; + ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 }; + ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 }; + ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 }; + ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 }; + ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 }; + ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 }; + ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 }; + ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 }; + ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 }; + ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 }; + ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 }; + ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 }; + ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 }; + ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 }; + ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 }; + ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 }; + ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 }; + ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 }; + ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 }; + ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 }; + ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 }; + ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 }; + ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 }; + ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 }; + ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 }; + ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 }; + ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 }; + ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 }; + ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 }; + ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 }; + ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 }; + ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 }; + ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 }; + ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 }; + ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 }; + ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 }; + ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 }; + ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 }; + ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 }; + ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 }; + ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 }; + ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 }; + ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 }; + ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 }; + ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 }; + ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 }; + ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 }; + ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 }; + ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 }; + ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 }; + ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 }; + ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 }; + ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 }; + } + + private static void InitializeFixedCostsI4() + { + Vp8FixedCostsI4[0, 0] = new short[] { 40, 1151, 1723, 1874, 2103, 2019, 1628, 1777, 2226, 2137 }; + Vp8FixedCostsI4[0, 1] = new short[] { 192, 469, 1296, 1308, 1849, 1794, 1781, 1703, 1713, 1522 }; + Vp8FixedCostsI4[0, 2] = new short[] { 142, 910, 762, 1684, 1849, 1576, 1460, 1305, 1801, 1657 }; + Vp8FixedCostsI4[0, 3] = new short[] { 559, 641, 1370, 421, 1182, 1569, 1612, 1725, 863, 1007 }; + Vp8FixedCostsI4[0, 4] = new short[] { 299, 1059, 1256, 1108, 636, 1068, 1581, 1883, 869, 1142 }; + Vp8FixedCostsI4[0, 5] = new short[] { 277, 1111, 707, 1362, 1089, 672, 1603, 1541, 1545, 1291 }; + Vp8FixedCostsI4[0, 6] = new short[] { 214, 781, 1609, 1303, 1632, 2229, 726, 1560, 1713, 918 }; + Vp8FixedCostsI4[0, 7] = new short[] { 152, 1037, 1046, 1759, 1983, 2174, 1358, 742, 1740, 1390 }; + Vp8FixedCostsI4[0, 8] = new short[] { 512, 1046, 1420, 753, 752, 1297, 1486, 1613, 460, 1207 }; + Vp8FixedCostsI4[0, 9] = new short[] { 424, 827, 1362, 719, 1462, 1202, 1199, 1476, 1199, 538 }; + Vp8FixedCostsI4[1, 0] = new short[] { 240, 402, 1134, 1491, 1659, 1505, 1517, 1555, 1979, 2099 }; + Vp8FixedCostsI4[1, 1] = new short[] { 467, 242, 960, 1232, 1714, 1620, 1834, 1570, 1676, 1391 }; + Vp8FixedCostsI4[1, 2] = new short[] { 500, 455, 463, 1507, 1699, 1282, 1564, 982, 2114, 2114 }; + Vp8FixedCostsI4[1, 3] = new short[] { 672, 643, 1372, 331, 1589, 1667, 1453, 1938, 996, 876 }; + Vp8FixedCostsI4[1, 4] = new short[] { 458, 783, 1037, 911, 738, 968, 1165, 1518, 859, 1033 }; + Vp8FixedCostsI4[1, 5] = new short[] { 504, 815, 504, 1139, 1219, 719, 1506, 1085, 1268, 1268 }; + Vp8FixedCostsI4[1, 6] = new short[] { 333, 630, 1445, 1239, 1883, 3672, 799, 1548, 1865, 598 }; + Vp8FixedCostsI4[1, 7] = new short[] { 399, 644, 746, 1342, 1856, 1350, 1493, 613, 1855, 1015 }; + Vp8FixedCostsI4[1, 8] = new short[] { 622, 749, 1205, 608, 1066, 1408, 1290, 1406, 546, 971 }; + Vp8FixedCostsI4[1, 9] = new short[] { 500, 753, 1041, 668, 1230, 1617, 1297, 1425, 1383, 523 }; + Vp8FixedCostsI4[2, 0] = new short[] { 394, 553, 523, 1502, 1536, 981, 1608, 1142, 1666, 2181 }; + Vp8FixedCostsI4[2, 1] = new short[] { 655, 430, 375, 1411, 1861, 1220, 1677, 1135, 1978, 1553 }; + Vp8FixedCostsI4[2, 2] = new short[] { 690, 640, 245, 1954, 2070, 1194, 1528, 982, 1972, 2232 }; + Vp8FixedCostsI4[2, 3] = new short[] { 559, 834, 741, 867, 1131, 980, 1225, 852, 1092, 784 }; + Vp8FixedCostsI4[2, 4] = new short[] { 690, 875, 516, 959, 673, 894, 1056, 1190, 1528, 1126 }; + Vp8FixedCostsI4[2, 5] = new short[] { 740, 951, 384, 1277, 1177, 492, 1579, 1155, 1846, 1513 }; + Vp8FixedCostsI4[2, 6] = new short[] { 323, 775, 1062, 1776, 3062, 1274, 813, 1188, 1372, 655 }; + Vp8FixedCostsI4[2, 7] = new short[] { 488, 971, 484, 1767, 1515, 1775, 1115, 503, 1539, 1461 }; + Vp8FixedCostsI4[2, 8] = new short[] { 740, 1006, 998, 709, 851, 1230, 1337, 788, 741, 721 }; + Vp8FixedCostsI4[2, 9] = new short[] { 522, 1073, 573, 1045, 1346, 887, 1046, 1146, 1203, 697 }; + Vp8FixedCostsI4[3, 0] = new short[] { 105, 864, 1442, 1009, 1934, 1840, 1519, 1920, 1673, 1579 }; + Vp8FixedCostsI4[3, 1] = new short[] { 534, 305, 1193, 683, 1388, 2164, 1802, 1894, 1264, 1170 }; + Vp8FixedCostsI4[3, 2] = new short[] { 305, 518, 877, 1108, 1426, 3215, 1425, 1064, 1320, 1242 }; + Vp8FixedCostsI4[3, 3] = new short[] { 683, 732, 1927, 257, 1493, 2048, 1858, 1552, 1055, 947 }; + Vp8FixedCostsI4[3, 4] = new short[] { 394, 814, 1024, 660, 959, 1556, 1282, 1289, 893, 1047 }; + Vp8FixedCostsI4[3, 5] = new short[] { 528, 615, 996, 940, 1201, 635, 1094, 2515, 803, 1358 }; + Vp8FixedCostsI4[3, 6] = new short[] { 347, 614, 1609, 1187, 3133, 1345, 1007, 1339, 1017, 667 }; + Vp8FixedCostsI4[3, 7] = new short[] { 218, 740, 878, 1605, 3650, 3650, 1345, 758, 1357, 1617 }; + Vp8FixedCostsI4[3, 8] = new short[] { 672, 750, 1541, 558, 1257, 1599, 1870, 2135, 402, 1087 }; + Vp8FixedCostsI4[3, 9] = new short[] { 592, 684, 1161, 430, 1092, 1497, 1475, 1489, 1095, 822 }; + Vp8FixedCostsI4[4, 0] = new short[] { 228, 1056, 1059, 1368, 752, 982, 1512, 1518, 987, 1782 }; + Vp8FixedCostsI4[4, 1] = new short[] { 494, 514, 818, 942, 965, 892, 1610, 1356, 1048, 1363 }; + Vp8FixedCostsI4[4, 2] = new short[] { 512, 648, 591, 1042, 761, 991, 1196, 1454, 1309, 1463 }; + Vp8FixedCostsI4[4, 3] = new short[] { 683, 749, 1043, 676, 841, 1396, 1133, 1138, 654, 939 }; + Vp8FixedCostsI4[4, 4] = new short[] { 622, 1101, 1126, 994, 361, 1077, 1203, 1318, 877, 1219 }; + Vp8FixedCostsI4[4, 5] = new short[] { 631, 1068, 857, 1650, 651, 477, 1650, 1419, 828, 1170 }; + Vp8FixedCostsI4[4, 6] = new short[] { 555, 727, 1068, 1335, 3127, 1339, 820, 1331, 1077, 429 }; + Vp8FixedCostsI4[4, 7] = new short[] { 504, 879, 624, 1398, 889, 889, 1392, 808, 891, 1406 }; + Vp8FixedCostsI4[4, 8] = new short[] { 683, 1602, 1289, 977, 578, 983, 1280, 1708, 406, 1122 }; + Vp8FixedCostsI4[4, 9] = new short[] { 399, 865, 1433, 1070, 1072, 764, 968, 1477, 1223, 678 }; + Vp8FixedCostsI4[5, 0] = new short[] { 333, 760, 935, 1638, 1010, 529, 1646, 1410, 1472, 2219 }; + Vp8FixedCostsI4[5, 1] = new short[] { 512, 494, 750, 1160, 1215, 610, 1870, 1868, 1628, 1169 }; + Vp8FixedCostsI4[5, 2] = new short[] { 572, 646, 492, 1934, 1208, 603, 1580, 1099, 1398, 1995 }; + Vp8FixedCostsI4[5, 3] = new short[] { 786, 789, 942, 581, 1018, 951, 1599, 1207, 731, 768 }; + Vp8FixedCostsI4[5, 4] = new short[] { 690, 1015, 672, 1078, 582, 504, 1693, 1438, 1108, 2897 }; + Vp8FixedCostsI4[5, 5] = new short[] { 768, 1267, 571, 2005, 1243, 244, 2881, 1380, 1786, 1453 }; + Vp8FixedCostsI4[5, 6] = new short[] { 452, 899, 1293, 903, 1311, 3100, 465, 1311, 1319, 813 }; + Vp8FixedCostsI4[5, 7] = new short[] { 394, 927, 942, 1103, 1358, 1104, 946, 593, 1363, 1109 }; + Vp8FixedCostsI4[5, 8] = new short[] { 559, 1005, 1007, 1016, 658, 1173, 1021, 1164, 623, 1028 }; + Vp8FixedCostsI4[5, 9] = new short[] { 564, 796, 632, 1005, 1014, 863, 2316, 1268, 938, 764 }; + Vp8FixedCostsI4[6, 0] = new short[] { 266, 606, 1098, 1228, 1497, 1243, 948, 1030, 1734, 1461 }; + Vp8FixedCostsI4[6, 1] = new short[] { 366, 585, 901, 1060, 1407, 1247, 876, 1134, 1620, 1054 }; + Vp8FixedCostsI4[6, 2] = new short[] { 452, 565, 542, 1729, 1479, 1479, 1016, 886, 2938, 1150 }; + Vp8FixedCostsI4[6, 3] = new short[] { 555, 1088, 1533, 950, 1354, 895, 834, 1019, 1021, 496 }; + Vp8FixedCostsI4[6, 4] = new short[] { 704, 815, 1193, 971, 973, 640, 1217, 2214, 832, 578 }; + Vp8FixedCostsI4[6, 5] = new short[] { 672, 1245, 579, 871, 875, 774, 872, 1273, 1027, 949 }; + Vp8FixedCostsI4[6, 6] = new short[] { 296, 1134, 2050, 1784, 1636, 3425, 442, 1550, 2076, 722 }; + Vp8FixedCostsI4[6, 7] = new short[] { 342, 982, 1259, 1846, 1848, 1848, 622, 568, 1847, 1052 }; + Vp8FixedCostsI4[6, 8] = new short[] { 555, 1064, 1304, 828, 746, 1343, 1075, 1329, 1078, 494 }; + Vp8FixedCostsI4[6, 9] = new short[] { 288, 1167, 1285, 1174, 1639, 1639, 833, 2254, 1304, 509 }; + Vp8FixedCostsI4[7, 0] = new short[] { 342, 719, 767, 1866, 1757, 1270, 1246, 550, 1746, 2151 }; + Vp8FixedCostsI4[7, 1] = new short[] { 483, 653, 694, 1509, 1459, 1410, 1218, 507, 1914, 1266 }; + Vp8FixedCostsI4[7, 2] = new short[] { 488, 757, 447, 2979, 1813, 1268, 1654, 539, 1849, 2109 }; + Vp8FixedCostsI4[7, 3] = new short[] { 522, 1097, 1085, 851, 1365, 1111, 851, 901, 961, 605 }; + Vp8FixedCostsI4[7, 4] = new short[] { 709, 716, 841, 728, 736, 945, 941, 862, 2845, 1057 }; + Vp8FixedCostsI4[7, 5] = new short[] { 512, 1323, 500, 1336, 1083, 681, 1342, 717, 1604, 1350 }; + Vp8FixedCostsI4[7, 6] = new short[] { 452, 1155, 1372, 1900, 1501, 3290, 311, 944, 1919, 922 }; + Vp8FixedCostsI4[7, 7] = new short[] { 403, 1520, 977, 2132, 1733, 3522, 1076, 276, 3335, 1547 }; + Vp8FixedCostsI4[7, 8] = new short[] { 559, 1374, 1101, 615, 673, 2462, 974, 795, 984, 984 }; + Vp8FixedCostsI4[7, 9] = new short[] { 547, 1122, 1062, 812, 1410, 951, 1140, 622, 1268, 651 }; + Vp8FixedCostsI4[8, 0] = new short[] { 165, 982, 1235, 938, 1334, 1366, 1659, 1578, 964, 1612 }; + Vp8FixedCostsI4[8, 1] = new short[] { 592, 422, 925, 847, 1139, 1112, 1387, 2036, 861, 1041 }; + Vp8FixedCostsI4[8, 2] = new short[] { 403, 837, 732, 770, 941, 1658, 1250, 809, 1407, 1407 }; + Vp8FixedCostsI4[8, 3] = new short[] { 896, 874, 1071, 381, 1568, 1722, 1437, 2192, 480, 1035 }; + Vp8FixedCostsI4[8, 4] = new short[] { 640, 1098, 1012, 1032, 684, 1382, 1581, 2106, 416, 865 }; + Vp8FixedCostsI4[8, 5] = new short[] { 559, 1005, 819, 914, 710, 770, 1418, 920, 838, 1435 }; + Vp8FixedCostsI4[8, 6] = new short[] { 415, 1258, 1245, 870, 1278, 3067, 770, 1021, 1287, 522 }; + Vp8FixedCostsI4[8, 7] = new short[] { 406, 990, 601, 1009, 1265, 1265, 1267, 759, 1017, 1277 }; + Vp8FixedCostsI4[8, 8] = new short[] { 968, 1182, 1329, 788, 1032, 1292, 1705, 1714, 203, 1403 }; + Vp8FixedCostsI4[8, 9] = new short[] { 732, 877, 1279, 471, 901, 1161, 1545, 1294, 755, 755 }; + Vp8FixedCostsI4[9, 0] = new short[] { 111, 931, 1378, 1185, 1933, 1648, 1148, 1714, 1873, 1307 }; + Vp8FixedCostsI4[9, 1] = new short[] { 406, 414, 1030, 1023, 1910, 1404, 1313, 1647, 1509, 793 }; + Vp8FixedCostsI4[9, 2] = new short[] { 342, 640, 575, 1088, 1241, 1349, 1161, 1350, 1756, 1502 }; + Vp8FixedCostsI4[9, 3] = new short[] { 559, 766, 1185, 357, 1682, 1428, 1329, 1897, 1219, 802 }; + Vp8FixedCostsI4[9, 4] = new short[] { 473, 909, 1164, 771, 719, 2508, 1427, 1432, 722, 782 }; + Vp8FixedCostsI4[9, 5] = new short[] { 342, 892, 785, 1145, 1150, 794, 1296, 1550, 973, 1057 }; + Vp8FixedCostsI4[9, 6] = new short[] { 208, 1036, 1326, 1343, 1606, 3395, 815, 1455, 1618, 712 }; + Vp8FixedCostsI4[9, 7] = new short[] { 228, 928, 890, 1046, 3499, 1711, 994, 829, 1720, 1318 }; + Vp8FixedCostsI4[9, 8] = new short[] { 768, 724, 1058, 636, 991, 1075, 1319, 1324, 616, 825 }; + Vp8FixedCostsI4[9, 9] = new short[] { 305, 1167, 1358, 899, 1587, 1587, 987, 1988, 1332, 501 }; } } diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs index 91d0ada093..5d1051c751 100644 --- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs +++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs @@ -1,41 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Provides Webp specific metadata information for the image. +/// +public class WebpMetadata : IDeepCloneable { /// - /// Provides Webp specific metadata information for the image. + /// Initializes a new instance of the class. /// - public class WebpMetadata : IDeepCloneable + public WebpMetadata() { - /// - /// Initializes a new instance of the class. - /// - public WebpMetadata() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The metadata to create an instance from. - private WebpMetadata(WebpMetadata other) - { - this.FileFormat = other.FileFormat; - this.AnimationLoopCount = other.AnimationLoopCount; - } + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private WebpMetadata(WebpMetadata other) + { + this.FileFormat = other.FileFormat; + this.AnimationLoopCount = other.AnimationLoopCount; + } - /// - /// Gets or sets the webp file format used. Either lossless or lossy. - /// - public WebpFileFormatType? FileFormat { get; set; } + /// + /// Gets or sets the webp file format used. Either lossless or lossy. + /// + public WebpFileFormatType? FileFormat { get; set; } - /// - /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely. - /// - public ushort AnimationLoopCount { get; set; } = 1; + /// + /// Gets or sets the loop count. The number of times to loop the animation. 0 means infinitely. + /// + public ushort AnimationLoopCount { get; set; } = 1; - /// - public IDeepCloneable DeepClone() => new WebpMetadata(this); - } + /// + public IDeepCloneable DeepClone() => new WebpMetadata(this); } diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs index dde78cfcdb..122580df67 100644 --- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs +++ b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +internal static class WebpThrowHelper { - internal static class WebpThrowHelper - { - /// - /// Cold path optimization for throwing 's. - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); + /// + /// Cold path optimization for throwing 's. + /// + /// The error message for the exception. + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - /// - /// Cold path optimization for throwing -s - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); - /// - /// Cold path optimization for throwing -s - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage); - /// - /// Cold path optimization for throwing -s - /// - /// The error message for the exception. - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); - } + /// + /// Cold path optimization for throwing -s + /// + /// The error message for the exception. + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidImageDimensions(string errorMessage) => throw new InvalidImageContentException(errorMessage); } diff --git a/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs b/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs index 8f629935d0..c12b8ed976 100644 --- a/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs +++ b/src/ImageSharp/Formats/Webp/WebpTransparentColorMode.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Webp +namespace SixLabors.ImageSharp.Formats.Webp; + +/// +/// Enum indicating how the transparency should be handled on encoding. +/// +public enum WebpTransparentColorMode { /// - /// Enum indicating how the transparency should be handled on encoding. + /// Discard the transparency information for better compression. /// - public enum WebpTransparentColorMode - { - /// - /// Discard the transparency information for better compression. - /// - Clear = 0, + Clear = 0, - /// - /// The transparency will be kept as is. - /// - Preserve = 1, - } + /// + /// The transparency will be kept as is. + /// + Preserve = 1, } diff --git a/src/ImageSharp/GeometryUtilities.cs b/src/ImageSharp/GeometryUtilities.cs index 238b24858d..ba534a90a1 100644 --- a/src/ImageSharp/GeometryUtilities.cs +++ b/src/ImageSharp/GeometryUtilities.cs @@ -1,34 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Utility class for common geometric functions. +/// +public static class GeometryUtilities { /// - /// Utility class for common geometric functions. + /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. /// - public static class GeometryUtilities - { - /// - /// Converts a degree (360-periodic) angle to a radian (2*Pi-periodic) angle. - /// - /// The angle in degrees. - /// - /// The representing the degree as radians. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float DegreeToRadian(float degree) => degree * (MathF.PI / 180F); + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreeToRadian(float degree) => degree * (MathF.PI / 180F); - /// - /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. - /// - /// The angle in radians. - /// - /// The representing the degree as radians. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float RadianToDegree(float radian) => radian / (MathF.PI / 180F); - } + /// + /// Converts a radian (2*Pi-periodic) angle to a degree (360-periodic) angle. + /// + /// The angle in radians. + /// + /// The representing the degree as radians. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RadianToDegree(float radian) => radian / (MathF.PI / 180F); } diff --git a/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs index bd5b266fde..4220b3df77 100644 --- a/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs +++ b/src/ImageSharp/GraphicOptionsDefaultsExtensions.cs @@ -1,98 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds extensions that allow the processing of images to the type. +/// +public static class GraphicOptionsDefaultsExtensions { /// - /// Adds extensions that allow the processing of images to the type. + /// Sets the default options against the image processing context. /// - public static class GraphicOptionsDefaultsExtensions + /// The image processing context to store default against. + /// The action to update instance of the default options used. + /// The passed in to allow chaining. + public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action optionsBuilder) { - /// - /// Sets the default options against the image processing context. - /// - /// The image processing context to store default against. - /// The action to update instance of the default options used. - /// The passed in to allow chaining. - public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, Action optionsBuilder) - { - var cloned = context.GetGraphicsOptions().DeepClone(); - optionsBuilder(cloned); - context.Properties[typeof(GraphicsOptions)] = cloned; - return context; - } + var cloned = context.GetGraphicsOptions().DeepClone(); + optionsBuilder(cloned); + context.Properties[typeof(GraphicsOptions)] = cloned; + return context; + } - /// - /// Sets the default options against the configuration. - /// - /// The configuration to store default against. - /// The default options to use. - public static void SetGraphicsOptions(this Configuration configuration, Action optionsBuilder) - { - var cloned = configuration.GetGraphicsOptions().DeepClone(); - optionsBuilder(cloned); - configuration.Properties[typeof(GraphicsOptions)] = cloned; - } + /// + /// Sets the default options against the configuration. + /// + /// The configuration to store default against. + /// The default options to use. + public static void SetGraphicsOptions(this Configuration configuration, Action optionsBuilder) + { + var cloned = configuration.GetGraphicsOptions().DeepClone(); + optionsBuilder(cloned); + configuration.Properties[typeof(GraphicsOptions)] = cloned; + } - /// - /// Sets the default options against the image processing context. - /// - /// The image processing context to store default against. - /// The default options to use. - /// The passed in to allow chaining. - public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options) - { - context.Properties[typeof(GraphicsOptions)] = options; - return context; - } + /// + /// Sets the default options against the image processing context. + /// + /// The image processing context to store default against. + /// The default options to use. + /// The passed in to allow chaining. + public static IImageProcessingContext SetGraphicsOptions(this IImageProcessingContext context, GraphicsOptions options) + { + context.Properties[typeof(GraphicsOptions)] = options; + return context; + } - /// - /// Sets the default options against the configuration. - /// - /// The configuration to store default against. - /// The default options to use. - public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options) - { - configuration.Properties[typeof(GraphicsOptions)] = options; - } + /// + /// Sets the default options against the configuration. + /// + /// The configuration to store default against. + /// The default options to use. + public static void SetGraphicsOptions(this Configuration configuration, GraphicsOptions options) + { + configuration.Properties[typeof(GraphicsOptions)] = options; + } - /// - /// Gets the default options against the image processing context. - /// - /// The image processing context to retrieve defaults from. - /// The globaly configued default options. - public static GraphicsOptions GetGraphicsOptions(this IImageProcessingContext context) + /// + /// Gets the default options against the image processing context. + /// + /// The image processing context to retrieve defaults from. + /// The globaly configued default options. + public static GraphicsOptions GetGraphicsOptions(this IImageProcessingContext context) + { + if (context.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) { - if (context.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) - { - return go; - } - - // do not cache the fall back to config into the the processing context - // in case someone want to change the value on the config and expects it re trflow thru - return context.Configuration.GetGraphicsOptions(); + return go; } - /// - /// Gets the default options against the image processing context. - /// - /// The configuration to retrieve defaults from. - /// The globaly configued default options. - public static GraphicsOptions GetGraphicsOptions(this Configuration configuration) + // do not cache the fall back to config into the the processing context + // in case someone want to change the value on the config and expects it re trflow thru + return context.Configuration.GetGraphicsOptions(); + } + + /// + /// Gets the default options against the image processing context. + /// + /// The configuration to retrieve defaults from. + /// The globaly configued default options. + public static GraphicsOptions GetGraphicsOptions(this Configuration configuration) + { + if (configuration.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) { - if (configuration.Properties.TryGetValue(typeof(GraphicsOptions), out var options) && options is GraphicsOptions go) - { - return go; - } + return go; + } - var configOptions = new GraphicsOptions(); + var configOptions = new GraphicsOptions(); - // capture the fallback so the same instance will always be returned in case its mutated - configuration.Properties[typeof(GraphicsOptions)] = configOptions; - return configOptions; - } + // capture the fallback so the same instance will always be returned in case its mutated + configuration.Properties[typeof(GraphicsOptions)] = configOptions; + return configOptions; } } diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index 3dfbe84412..4ac8585c3c 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -3,87 +3,86 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Options for influencing the drawing functions. +/// +public class GraphicsOptions : IDeepCloneable { + private int antialiasSubpixelDepth = 16; + private float blendPercentage = 1F; + /// - /// Options for influencing the drawing functions. + /// Initializes a new instance of the class. /// - public class GraphicsOptions : IDeepCloneable + public GraphicsOptions() { - private int antialiasSubpixelDepth = 16; - private float blendPercentage = 1F; + } - /// - /// Initializes a new instance of the class. - /// - public GraphicsOptions() + private GraphicsOptions(GraphicsOptions source) + { + this.AlphaCompositionMode = source.AlphaCompositionMode; + this.Antialias = source.Antialias; + this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; + this.BlendPercentage = source.BlendPercentage; + this.ColorBlendingMode = source.ColorBlendingMode; + } + + /// + /// Gets or sets a value indicating whether antialiasing should be applied. + /// Defaults to true. + /// + public bool Antialias { get; set; } = true; + + /// + /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// Defaults to 16. + /// + public int AntialiasSubpixelDepth + { + get { + return this.antialiasSubpixelDepth; } - private GraphicsOptions(GraphicsOptions source) + set { - this.AlphaCompositionMode = source.AlphaCompositionMode; - this.Antialias = source.Antialias; - this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; - this.BlendPercentage = source.BlendPercentage; - this.ColorBlendingMode = source.ColorBlendingMode; + Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); + this.antialiasSubpixelDepth = value; } + } - /// - /// Gets or sets a value indicating whether antialiasing should be applied. - /// Defaults to true. - /// - public bool Antialias { get; set; } = true; - - /// - /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. - /// Defaults to 16. - /// - public int AntialiasSubpixelDepth + /// + /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation. + /// Range 0..1; Defaults to 1. + /// + public float BlendPercentage + { + get { - get - { - return this.antialiasSubpixelDepth; - } - - set - { - Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); - this.antialiasSubpixelDepth = value; - } + return this.blendPercentage; } - /// - /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation. - /// Range 0..1; Defaults to 1. - /// - public float BlendPercentage + set { - get - { - return this.blendPercentage; - } - - set - { - Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); - this.blendPercentage = value; - } + Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); + this.blendPercentage = value; } + } - /// - /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. - /// Defaults to . - /// - public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; + /// + /// Gets or sets a value indicating the color blending mode to apply to the drawing operation. + /// Defaults to . + /// + public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; - /// - /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation - /// Defaults to . - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; + /// + /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation + /// Defaults to . + /// + public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; - /// - public GraphicsOptions DeepClone() => new GraphicsOptions(this); - } + /// + public GraphicsOptions DeepClone() => new GraphicsOptions(this); } diff --git a/src/ImageSharp/IConfigurationModule.cs b/src/ImageSharp/IConfigurationModule.cs index 6d7d8bf031..e2b221a080 100644 --- a/src/ImageSharp/IConfigurationModule.cs +++ b/src/ImageSharp/IConfigurationModule.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents an interface that can register image encoders, decoders and image format detectors. +/// +public interface IConfigurationModule { /// - /// Represents an interface that can register image encoders, decoders and image format detectors. + /// Called when loaded into a configuration object so the module can register items into the configuration. /// - public interface IConfigurationModule - { - /// - /// Called when loaded into a configuration object so the module can register items into the configuration. - /// - /// The configuration that will retain the encoders, decodes and mime type detectors. - void Configure(Configuration configuration); - } + /// The configuration that will retain the encoders, decodes and mime type detectors. + void Configure(Configuration configuration); } diff --git a/src/ImageSharp/IDeepCloneable.cs b/src/ImageSharp/IDeepCloneable.cs index 26c48d1af5..3d00d627e0 100644 --- a/src/ImageSharp/IDeepCloneable.cs +++ b/src/ImageSharp/IDeepCloneable.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// A generic interface for a deeply cloneable type. +/// +/// The type of object to clone. +public interface IDeepCloneable + where T : class { /// - /// A generic interface for a deeply cloneable type. + /// Creates a new that is a deep copy of the current instance. /// - /// The type of object to clone. - public interface IDeepCloneable - where T : class - { - /// - /// Creates a new that is a deep copy of the current instance. - /// - /// The . - T DeepClone(); - } + /// The . + T DeepClone(); +} +/// +/// An interface for objects that can be cloned. This creates a deep copy of the object. +/// +public interface IDeepCloneable +{ /// - /// An interface for objects that can be cloned. This creates a deep copy of the object. + /// Creates a new object that is a deep copy of the current instance. /// - public interface IDeepCloneable - { - /// - /// Creates a new object that is a deep copy of the current instance. - /// - /// The . - IDeepCloneable DeepClone(); - } + /// The . + IDeepCloneable DeepClone(); } diff --git a/src/ImageSharp/IImage.cs b/src/ImageSharp/IImage.cs index 93a8990115..9940a7954c 100644 --- a/src/ImageSharp/IImage.cs +++ b/src/ImageSharp/IImage.cs @@ -1,14 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Encapsulates the properties and methods that describe an image. +/// +public interface IImage : IImageInfo, IDisposable { - /// - /// Encapsulates the properties and methods that describe an image. - /// - public interface IImage : IImageInfo, IDisposable - { - } } diff --git a/src/ImageSharp/IImageInfo.cs b/src/ImageSharp/IImageInfo.cs index 371fb6c872..32ca9a9a21 100644 --- a/src/ImageSharp/IImageInfo.cs +++ b/src/ImageSharp/IImageInfo.cs @@ -4,32 +4,31 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Encapsulates properties that describe basic image information including dimensions, pixel type information +/// and additional metadata. +/// +public interface IImageInfo { /// - /// Encapsulates properties that describe basic image information including dimensions, pixel type information - /// and additional metadata. + /// Gets information about the image pixels. /// - public interface IImageInfo - { - /// - /// Gets information about the image pixels. - /// - PixelTypeInfo PixelType { get; } + PixelTypeInfo PixelType { get; } - /// - /// Gets the width. - /// - int Width { get; } + /// + /// Gets the width. + /// + int Width { get; } - /// - /// Gets the height. - /// - int Height { get; } + /// + /// Gets the height. + /// + int Height { get; } - /// - /// Gets the metadata of the image. - /// - ImageMetadata Metadata { get; } - } + /// + /// Gets the metadata of the image. + /// + ImageMetadata Metadata { get; } } diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index e6aeadea23..7e233c9655 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -1,429 +1,426 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.IO +namespace SixLabors.ImageSharp.IO; + +/// +/// A readonly stream that add a secondary level buffer in addition to native stream +/// buffered reading to reduce the overhead of small incremental reads. +/// +internal sealed class BufferedReadStream : Stream { - /// - /// A readonly stream that add a secondary level buffer in addition to native stream - /// buffered reading to reduce the overhead of small incremental reads. - /// - internal sealed class BufferedReadStream : Stream - { - private readonly int maxBufferIndex; + private readonly int maxBufferIndex; - private readonly byte[] readBuffer; + private readonly byte[] readBuffer; - private MemoryHandle readBufferHandle; + private MemoryHandle readBufferHandle; - private readonly unsafe byte* pinnedReadBuffer; + private readonly unsafe byte* pinnedReadBuffer; - // Index within our buffer, not reader position. - private int readBufferIndex; + // Index within our buffer, not reader position. + private int readBufferIndex; - // Matches what the stream position would be without buffering - private long readerPosition; + // Matches what the stream position would be without buffering + private long readerPosition; - private bool isDisposed; + private bool isDisposed; - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The input stream. - public BufferedReadStream(Configuration configuration, Stream stream) + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The input stream. + public BufferedReadStream(Configuration configuration, Stream stream) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); + Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); + + // Ensure all underlying buffers have been flushed before we attempt to read the stream. + // User streams may have opted to throw from Flush if CanWrite is false + // (although the abstract Stream does not do so). + if (stream.CanWrite) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); - Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); - - // Ensure all underlying buffers have been flushed before we attempt to read the stream. - // User streams may have opted to throw from Flush if CanWrite is false - // (although the abstract Stream does not do so). - if (stream.CanWrite) - { - stream.Flush(); - } - - this.BaseStream = stream; - this.Length = stream.Length; - this.Position = (int)stream.Position; - this.BufferSize = configuration.StreamProcessingBufferSize; - this.maxBufferIndex = this.BufferSize - 1; - this.readBuffer = ArrayPool.Shared.Rent(this.BufferSize); - this.readBufferHandle = new Memory(this.readBuffer).Pin(); - unsafe - { - this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; - } - - // This triggers a full read on first attempt. - this.readBufferIndex = this.BufferSize; + stream.Flush(); } - /// - /// Gets the size, in bytes, of the underlying buffer. - /// - public int BufferSize + this.BaseStream = stream; + this.Length = stream.Length; + this.Position = (int)stream.Position; + this.BufferSize = configuration.StreamProcessingBufferSize; + this.maxBufferIndex = this.BufferSize - 1; + this.readBuffer = ArrayPool.Shared.Rent(this.BufferSize); + this.readBufferHandle = new Memory(this.readBuffer).Pin(); + unsafe { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; + this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; } - /// - public override long Length { get; } + // This triggers a full read on first attempt. + this.readBufferIndex = this.BufferSize; + } + + /// + /// Gets the size, in bytes, of the underlying buffer. + /// + public int BufferSize + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + /// + public override long Length { get; } - /// - public override long Position + /// + public override long Position + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.readerPosition; + + [MethodImpl(MethodImplOptions.NoInlining)] + set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.readerPosition; + Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position)); - [MethodImpl(MethodImplOptions.NoInlining)] - set + // Only reset readBufferIndex if we are out of bounds of our working buffer + // otherwise we should simply move the value by the diff. + if (this.IsInReadBuffer(value, out long index)) + { + this.readBufferIndex = (int)index; + this.readerPosition = value; + } + else { - Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position)); - - // Only reset readBufferIndex if we are out of bounds of our working buffer - // otherwise we should simply move the value by the diff. - if (this.IsInReadBuffer(value, out long index)) - { - this.readBufferIndex = (int)index; - this.readerPosition = value; - } - else - { - // Base stream seek will throw for us if invalid. - this.BaseStream.Seek(value, SeekOrigin.Begin); - this.readerPosition = value; - this.readBufferIndex = this.BufferSize; - } + // Base stream seek will throw for us if invalid. + this.BaseStream.Seek(value, SeekOrigin.Begin); + this.readerPosition = value; + this.readBufferIndex = this.BufferSize; } } + } + + /// + public override bool CanRead { get; } = true; + + /// + public override bool CanSeek { get; } = true; - /// - public override bool CanRead { get; } = true; + /// + public override bool CanWrite { get; } - /// - public override bool CanSeek { get; } = true; + /// + /// Gets remaining byte count available to read. + /// + public long RemainingBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.Length - this.Position; + } - /// - public override bool CanWrite { get; } + /// + /// Gets the underlying stream. + /// + public Stream BaseStream + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - /// - /// Gets remaining byte count available to read. - /// - public long RemainingBytes + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int ReadByte() + { + if (this.readerPosition >= this.Length) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.Length - this.Position; + return -1; } - /// - /// Gets the underlying stream. - /// - public Stream BaseStream + // Our buffer has been read. + // We need to refill and start again. + if (this.readBufferIndex > this.maxBufferIndex) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; + this.FillReadBuffer(); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int ReadByte() + this.readerPosition++; + unsafe { - if (this.readerPosition >= this.Length) - { - return -1; - } - - // Our buffer has been read. - // We need to refill and start again. - if (this.readBufferIndex > this.maxBufferIndex) - { - this.FillReadBuffer(); - } - - this.readerPosition++; - unsafe - { - return this.pinnedReadBuffer[this.readBufferIndex++]; - } + return this.pinnedReadBuffer[this.readBufferIndex++]; } + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(byte[] buffer, int offset, int count) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(byte[] buffer, int offset, int count) + { + // Too big for our buffer. Read directly from the stream. + if (count > this.BufferSize) { - // Too big for our buffer. Read directly from the stream. - if (count > this.BufferSize) - { - return this.ReadToBufferDirectSlow(buffer, offset, count); - } - - // Too big for remaining buffer but less than entire buffer length - // Copy to buffer then read from there. - if (count + this.readBufferIndex > this.BufferSize) - { - return this.ReadToBufferViaCopySlow(buffer, offset, count); - } - - return this.ReadToBufferViaCopyFast(buffer, offset, count); + return this.ReadToBufferDirectSlow(buffer, offset, count); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(Span buffer) + // Too big for remaining buffer but less than entire buffer length + // Copy to buffer then read from there. + if (count + this.readBufferIndex > this.BufferSize) { - // Too big for our buffer. Read directly from the stream. - int count = buffer.Length; - if (count > this.BufferSize) - { - return this.ReadToBufferDirectSlow(buffer); - } + return this.ReadToBufferViaCopySlow(buffer, offset, count); + } - // Too big for remaining buffer but less than entire buffer length - // Copy to buffer then read from there. - if (count + this.readBufferIndex > this.BufferSize) - { - return this.ReadToBufferViaCopySlow(buffer); - } + return this.ReadToBufferViaCopyFast(buffer, offset, count); + } - return this.ReadToBufferViaCopyFast(buffer); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(Span buffer) + { + // Too big for our buffer. Read directly from the stream. + int count = buffer.Length; + if (count > this.BufferSize) + { + return this.ReadToBufferDirectSlow(buffer); } - /// - public override void Flush() + // Too big for remaining buffer but less than entire buffer length + // Copy to buffer then read from there. + if (count + this.readBufferIndex > this.BufferSize) { - // Reset the stream position to match reader position. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - this.readerPosition = (int)baseStream.Position; - } - - // Reset to trigger full read on next attempt. - this.readBufferIndex = this.BufferSize; + return this.ReadToBufferViaCopySlow(buffer); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override long Seek(long offset, SeekOrigin origin) + return this.ReadToBufferViaCopyFast(buffer); + } + + /// + public override void Flush() + { + // Reset the stream position to match reader position. + Stream baseStream = this.BaseStream; + if (this.readerPosition != baseStream.Position) { - switch (origin) - { - case SeekOrigin.Begin: - this.Position = offset; - break; + baseStream.Seek(this.readerPosition, SeekOrigin.Begin); + this.readerPosition = (int)baseStream.Position; + } - case SeekOrigin.Current: - this.Position += offset; - break; + // Reset to trigger full read on next attempt. + this.readBufferIndex = this.BufferSize; + } - case SeekOrigin.End: - this.Position = this.Length - offset; - break; - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + this.Position = offset; + break; + + case SeekOrigin.Current: + this.Position += offset; + break; - return this.readerPosition; + case SeekOrigin.End: + this.Position = this.Length - offset; + break; } - /// - /// - /// This operation is not supported in . - /// - public override void SetLength(long value) - => throw new NotSupportedException(); - - /// - /// - /// This operation is not supported in . - /// - public override void Write(byte[] buffer, int offset, int count) - => throw new NotSupportedException(); - - /// - protected override void Dispose(bool disposing) + return this.readerPosition; + } + + /// + /// + /// This operation is not supported in . + /// + public override void SetLength(long value) + => throw new NotSupportedException(); + + /// + /// + /// This operation is not supported in . + /// + public override void Write(byte[] buffer, int offset, int count) + => throw new NotSupportedException(); + + /// + protected override void Dispose(bool disposing) + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.isDisposed = true; - this.readBufferHandle.Dispose(); - ArrayPool.Shared.Return(this.readBuffer); - this.Flush(); + this.isDisposed = true; + this.readBufferHandle.Dispose(); + ArrayPool.Shared.Return(this.readBuffer); + this.Flush(); - base.Dispose(true); - } + base.Dispose(true); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsInReadBuffer(long newPosition, out long index) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsInReadBuffer(long newPosition, out long index) + { + index = newPosition - this.readerPosition + this.readBufferIndex; + return index > -1 && index < this.BufferSize; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void FillReadBuffer() + { + Stream baseStream = this.BaseStream; + if (this.readerPosition != baseStream.Position) { - index = newPosition - this.readerPosition + this.readBufferIndex; - return index > -1 && index < this.BufferSize; + baseStream.Seek(this.readerPosition, SeekOrigin.Begin); } - [MethodImpl(MethodImplOptions.NoInlining)] - private void FillReadBuffer() + // Read doesn't always guarantee the full returned length so read a byte + // at a time until we get either our count or hit the end of the stream. + int n = 0; + int i; + do { - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } + i = baseStream.Read(this.readBuffer, n, this.BufferSize - n); + n += i; + } + while (n < this.BufferSize && i > 0); - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do - { - i = baseStream.Read(this.readBuffer, n, this.BufferSize - n); - n += i; - } - while (n < this.BufferSize && i > 0); + this.readBufferIndex = 0; + } - this.readBufferIndex = 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopyFast(Span buffer) + { + int n = this.GetCopyCount(buffer.Length); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(Span buffer) - { - int n = this.GetCopyCount(buffer.Length); + // Just straight copy. MemoryStream does the same so should be fast enough. + this.readBuffer.AsSpan(this.readBufferIndex, n).CopyTo(buffer); - // Just straight copy. MemoryStream does the same so should be fast enough. - this.readBuffer.AsSpan(this.readBufferIndex, n).CopyTo(buffer); + this.readerPosition += n; + this.readBufferIndex += n; - this.readerPosition += n; - this.readBufferIndex += n; + return n; + } - return n; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) + { + int n = this.GetCopyCount(count); + this.CopyBytes(buffer, offset, n); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) - { - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); + this.readerPosition += n; + this.readBufferIndex += n; - this.readerPosition += n; - this.readBufferIndex += n; + return n; + } - return n; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopySlow(Span buffer) + { + // Refill our buffer then copy. + this.FillReadBuffer(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(Span buffer) - { - // Refill our buffer then copy. - this.FillReadBuffer(); + return this.ReadToBufferViaCopyFast(buffer); + } - return this.ReadToBufferViaCopyFast(buffer); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) + { + // Refill our buffer then copy. + this.FillReadBuffer(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - this.FillReadBuffer(); + return this.ReadToBufferViaCopyFast(buffer, offset, count); + } - return this.ReadToBufferViaCopyFast(buffer, offset, count); + [MethodImpl(MethodImplOptions.NoInlining)] + private int ReadToBufferDirectSlow(Span buffer) + { + // Read to target but don't copy to our read buffer. + Stream baseStream = this.BaseStream; + if (this.readerPosition != baseStream.Position) + { + baseStream.Seek(this.readerPosition, SeekOrigin.Begin); } - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(Span buffer) + // Read doesn't always guarantee the full returned length so read a byte + // at a time until we get either our count or hit the end of the stream. + int count = buffer.Length; + int n = 0; + int i; + do { - // Read to target but don't copy to our read buffer. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } + i = baseStream.Read(buffer[n..count]); + n += i; + } + while (n < count && i > 0); - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int count = buffer.Length; - int n = 0; - int i; - do - { - i = baseStream.Read(buffer[n..count]); - n += i; - } - while (n < count && i > 0); + this.Position += n; - this.Position += n; + return n; + } - return n; + [MethodImpl(MethodImplOptions.NoInlining)] + private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) + { + // Read to target but don't copy to our read buffer. + Stream baseStream = this.BaseStream; + if (this.readerPosition != baseStream.Position) + { + baseStream.Seek(this.readerPosition, SeekOrigin.Begin); } - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) + // Read doesn't always guarantee the full returned length so read a byte + // at a time until we get either our count or hit the end of the stream. + int n = 0; + int i; + do { - // Read to target but don't copy to our read buffer. - Stream baseStream = this.BaseStream; - if (this.readerPosition != baseStream.Position) - { - baseStream.Seek(this.readerPosition, SeekOrigin.Begin); - } + i = baseStream.Read(buffer, n + offset, count - n); + n += i; + } + while (n < count && i > 0); - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do - { - i = baseStream.Read(buffer, n + offset, count - n); - n += i; - } - while (n < count && i > 0); + this.Position += n; - this.Position += n; + return n; + } - return n; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetCopyCount(int count) + { + long n = this.Length - this.readerPosition; + if (n > count) + { + return count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetCopyCount(int count) + if (n < 0) { - long n = this.Length - this.readerPosition; - if (n > count) - { - return count; - } - - if (n < 0) - { - return 0; - } - - return (int)n; + return 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void CopyBytes(byte[] buffer, int offset, int count) + return (int)n; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void CopyBytes(byte[] buffer, int offset, int count) + { + // Same as MemoryStream. + if (count < 9) { - // Same as MemoryStream. - if (count < 9) - { - int byteCount = count; - int read = this.readBufferIndex; - byte* pinned = this.pinnedReadBuffer; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = pinned[read + byteCount]; - } - } - else + int byteCount = count; + int read = this.readBufferIndex; + byte* pinned = this.pinnedReadBuffer; + + while (--byteCount > -1) { - Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); + buffer[offset + byteCount] = pinned[read + byteCount]; } } + else + { + Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); + } } } diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index 837af618ec..ecb299ca31 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -1,585 +1,582 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.IO +namespace SixLabors.ImageSharp.IO; + +/// +/// Provides an in-memory stream composed of non-contiguous chunks that doesn't need to be resized. +/// Chunks are allocated by the assigned via the constructor +/// and is designed to take advantage of buffer pooling when available. +/// +internal sealed class ChunkedMemoryStream : Stream { - /// - /// Provides an in-memory stream composed of non-contiguous chunks that doesn't need to be resized. - /// Chunks are allocated by the assigned via the constructor - /// and is designed to take advantage of buffer pooling when available. - /// - internal sealed class ChunkedMemoryStream : Stream - { - // The memory allocator. - private readonly MemoryAllocator allocator; + // The memory allocator. + private readonly MemoryAllocator allocator; - // Data - private MemoryChunk memoryChunk; + // Data + private MemoryChunk memoryChunk; - // The total number of allocated chunks - private int chunkCount; + // The total number of allocated chunks + private int chunkCount; - // The length of the largest contiguous buffer that can be handled by the allocator. - private readonly int allocatorCapacity; + // The length of the largest contiguous buffer that can be handled by the allocator. + private readonly int allocatorCapacity; - // Has the stream been disposed. - private bool isDisposed; + // Has the stream been disposed. + private bool isDisposed; - // Current chunk to write to - private MemoryChunk writeChunk; + // Current chunk to write to + private MemoryChunk writeChunk; - // Offset into chunk to write to - private int writeOffset; + // Offset into chunk to write to + private int writeOffset; - // Current chunk to read from - private MemoryChunk readChunk; + // Current chunk to read from + private MemoryChunk readChunk; - // Offset into chunk to read from - private int readOffset; + // Offset into chunk to read from + private int readOffset; - /// - /// Initializes a new instance of the class. - /// - public ChunkedMemoryStream(MemoryAllocator allocator) - { - Guard.NotNull(allocator, nameof(allocator)); + /// + /// Initializes a new instance of the class. + /// + public ChunkedMemoryStream(MemoryAllocator allocator) + { + Guard.NotNull(allocator, nameof(allocator)); - this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); - this.allocator = allocator; - } + this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); + this.allocator = allocator; + } - /// - public override bool CanRead => !this.isDisposed; + /// + public override bool CanRead => !this.isDisposed; - /// - public override bool CanSeek => !this.isDisposed; + /// + public override bool CanSeek => !this.isDisposed; - /// - public override bool CanWrite => !this.isDisposed; + /// + public override bool CanWrite => !this.isDisposed; - /// - public override long Length + /// + public override long Length + { + get { - get - { - this.EnsureNotDisposed(); - - int length = 0; - MemoryChunk chunk = this.memoryChunk; - while (chunk != null) - { - MemoryChunk next = chunk.Next; - if (next != null) - { - length += chunk.Length; - } - else - { - length += this.writeOffset; - } - - chunk = next; - } - - return length; - } - } + this.EnsureNotDisposed(); - /// - public override long Position - { - get + int length = 0; + MemoryChunk chunk = this.memoryChunk; + while (chunk != null) { - this.EnsureNotDisposed(); - - if (this.readChunk is null) + MemoryChunk next = chunk.Next; + if (next != null) { - return 0; + length += chunk.Length; } - - int pos = 0; - MemoryChunk chunk = this.memoryChunk; - while (chunk != this.readChunk) + else { - pos += chunk.Length; - chunk = chunk.Next; + length += this.writeOffset; } - pos += this.readOffset; - - return pos; + chunk = next; } - set - { - this.EnsureNotDisposed(); - - if (value < 0) - { - ThrowArgumentOutOfRange(nameof(value)); - } - - // Back up current position in case new position is out of range - MemoryChunk backupReadChunk = this.readChunk; - int backupReadOffset = this.readOffset; - - this.readChunk = null; - this.readOffset = 0; - - int leftUntilAtPos = (int)value; - MemoryChunk chunk = this.memoryChunk; - while (chunk != null) - { - if ((leftUntilAtPos < chunk.Length) - || ((leftUntilAtPos == chunk.Length) - && (chunk.Next is null))) - { - // The desired position is in this chunk - this.readChunk = chunk; - this.readOffset = leftUntilAtPos; - break; - } - - leftUntilAtPos -= chunk.Length; - chunk = chunk.Next; - } - - if (this.readChunk is null) - { - // Position is out of range - this.readChunk = backupReadChunk; - this.readOffset = backupReadOffset; - } - } + return length; } + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override long Seek(long offset, SeekOrigin origin) + /// + public override long Position + { + get { this.EnsureNotDisposed(); - switch (origin) + if (this.readChunk is null) { - case SeekOrigin.Begin: - this.Position = offset; - break; - - case SeekOrigin.Current: - this.Position += offset; - break; + return 0; + } - case SeekOrigin.End: - this.Position = this.Length + offset; - break; - default: - ThrowInvalidSeek(); - break; + int pos = 0; + MemoryChunk chunk = this.memoryChunk; + while (chunk != this.readChunk) + { + pos += chunk.Length; + chunk = chunk.Next; } - return this.Position; - } + pos += this.readOffset; - /// - public override void SetLength(long value) - => throw new NotSupportedException(); + return pos; + } - /// - protected override void Dispose(bool disposing) + set { - if (this.isDisposed) + this.EnsureNotDisposed(); + + if (value < 0) { - return; + ThrowArgumentOutOfRange(nameof(value)); } - try + // Back up current position in case new position is out of range + MemoryChunk backupReadChunk = this.readChunk; + int backupReadOffset = this.readOffset; + + this.readChunk = null; + this.readOffset = 0; + + int leftUntilAtPos = (int)value; + MemoryChunk chunk = this.memoryChunk; + while (chunk != null) { - this.isDisposed = true; - if (disposing) + if ((leftUntilAtPos < chunk.Length) + || ((leftUntilAtPos == chunk.Length) + && (chunk.Next is null))) { - ReleaseMemoryChunks(this.memoryChunk); + // The desired position is in this chunk + this.readChunk = chunk; + this.readOffset = leftUntilAtPos; + break; } - this.memoryChunk = null; - this.writeChunk = null; - this.readChunk = null; - this.chunkCount = 0; + leftUntilAtPos -= chunk.Length; + chunk = chunk.Next; } - finally + + if (this.readChunk is null) { - base.Dispose(disposing); + // Position is out of range + this.readChunk = backupReadChunk; + this.readOffset = backupReadOffset; } } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override long Seek(long offset, SeekOrigin origin) + { + this.EnsureNotDisposed(); - /// - public override void Flush() + switch (origin) { + case SeekOrigin.Begin: + this.Position = offset; + break; + + case SeekOrigin.Current: + this.Position += offset; + break; + + case SeekOrigin.End: + this.Position = this.Length + offset; + break; + default: + ThrowInvalidSeek(); + break; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(byte[] buffer, int offset, int count) - { - Guard.NotNull(buffer, nameof(buffer)); - Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); - Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); + return this.Position; + } - const string bufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); + /// + public override void SetLength(long value) + => throw new NotSupportedException(); - return this.ReadImpl(buffer.AsSpan(offset, count)); + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(Span buffer) => this.ReadImpl(buffer); - - private int ReadImpl(Span buffer) + try { - this.EnsureNotDisposed(); - - if (this.readChunk is null) + this.isDisposed = true; + if (disposing) { - if (this.memoryChunk is null) - { - return 0; - } - - this.readChunk = this.memoryChunk; - this.readOffset = 0; + ReleaseMemoryChunks(this.memoryChunk); } - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } + this.memoryChunk = null; + this.writeChunk = null; + this.readChunk = null; + this.chunkCount = 0; + } + finally + { + base.Dispose(disposing); + } + } - int bytesRead = 0; - int offset = 0; - int count = buffer.Length; - while (count > 0) - { - if (this.readOffset == chunkSize) - { - // Exit if no more chunks are currently available - if (this.readChunk.Next is null) - { - break; - } - - this.readChunk = this.readChunk.Next; - this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer; - chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } - } + /// + public override void Flush() + { + } - int readCount = Math.Min(count, chunkSize - this.readOffset); - chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer[offset..]); - offset += readCount; - count -= readCount; - this.readOffset += readCount; - bytesRead += readCount; - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(byte[] buffer, int offset, int count) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); + Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - return bytesRead; - } + const string bufferMessage = "Offset subtracted from the buffer length is less than count."; + Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int ReadByte() - { - this.EnsureNotDisposed(); + return this.ReadImpl(buffer.AsSpan(offset, count)); + } - if (this.readChunk is null) - { - if (this.memoryChunk is null) - { - return 0; - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int Read(Span buffer) => this.ReadImpl(buffer); - this.readChunk = this.memoryChunk; - this.readOffset = 0; - } + private int ReadImpl(Span buffer) + { + this.EnsureNotDisposed(); - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) + if (this.readChunk is null) + { + if (this.memoryChunk is null) { - chunkSize = this.writeOffset; + return 0; } + this.readChunk = this.memoryChunk; + this.readOffset = 0; + } + + IMemoryOwner chunkBuffer = this.readChunk.Buffer; + int chunkSize = this.readChunk.Length; + if (this.readChunk.Next is null) + { + chunkSize = this.writeOffset; + } + + int bytesRead = 0; + int offset = 0; + int count = buffer.Length; + while (count > 0) + { if (this.readOffset == chunkSize) { // Exit if no more chunks are currently available if (this.readChunk.Next is null) { - return -1; + break; } this.readChunk = this.readChunk.Next; this.readOffset = 0; chunkBuffer = this.readChunk.Buffer; + chunkSize = this.readChunk.Length; + if (this.readChunk.Next is null) + { + chunkSize = this.writeOffset; + } } - return chunkBuffer.GetSpan()[this.readOffset++]; + int readCount = Math.Min(count, chunkSize - this.readOffset); + chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer[offset..]); + offset += readCount; + count -= readCount; + this.readOffset += readCount; + bytesRead += readCount; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Write(byte[] buffer, int offset, int count) - { - Guard.NotNull(buffer, nameof(buffer)); - Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); - Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - - const string bufferMessage = "Offset subtracted from the buffer length is less than count."; - Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - - this.WriteImpl(buffer.AsSpan(offset, count)); - } + return bytesRead; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Write(ReadOnlySpan buffer) => this.WriteImpl(buffer); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int ReadByte() + { + this.EnsureNotDisposed(); - private void WriteImpl(ReadOnlySpan buffer) + if (this.readChunk is null) { - this.EnsureNotDisposed(); - if (this.memoryChunk is null) { - this.memoryChunk = this.AllocateMemoryChunk(); - this.writeChunk = this.memoryChunk; - this.writeOffset = 0; + return 0; } - Span chunkBuffer = this.writeChunk.Buffer.GetSpan(); - int chunkSize = this.writeChunk.Length; - int count = buffer.Length; - int offset = 0; - while (count > 0) - { - if (this.writeOffset == chunkSize) - { - // Allocate a new chunk if the current one is full - this.writeChunk.Next = this.AllocateMemoryChunk(); - this.writeChunk = this.writeChunk.Next; - this.writeOffset = 0; - chunkBuffer = this.writeChunk.Buffer.GetSpan(); - chunkSize = this.writeChunk.Length; - } - - int copyCount = Math.Min(count, chunkSize - this.writeOffset); - buffer.Slice(offset, copyCount).CopyTo(chunkBuffer[this.writeOffset..]); - - offset += copyCount; - count -= copyCount; - this.writeOffset += copyCount; - } + this.readChunk = this.memoryChunk; + this.readOffset = 0; } - /// - public override void WriteByte(byte value) + IMemoryOwner chunkBuffer = this.readChunk.Buffer; + int chunkSize = this.readChunk.Length; + if (this.readChunk.Next is null) { - this.EnsureNotDisposed(); + chunkSize = this.writeOffset; + } - if (this.memoryChunk is null) + if (this.readOffset == chunkSize) + { + // Exit if no more chunks are currently available + if (this.readChunk.Next is null) { - this.memoryChunk = this.AllocateMemoryChunk(); - this.writeChunk = this.memoryChunk; - this.writeOffset = 0; + return -1; } - IMemoryOwner chunkBuffer = this.writeChunk.Buffer; - int chunkSize = this.writeChunk.Length; + this.readChunk = this.readChunk.Next; + this.readOffset = 0; + chunkBuffer = this.readChunk.Buffer; + } + + return chunkBuffer.GetSpan()[this.readOffset++]; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Write(byte[] buffer, int offset, int count) + { + Guard.NotNull(buffer, nameof(buffer)); + Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); + Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); + + const string bufferMessage = "Offset subtracted from the buffer length is less than count."; + Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); + + this.WriteImpl(buffer.AsSpan(offset, count)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Write(ReadOnlySpan buffer) => this.WriteImpl(buffer); + private void WriteImpl(ReadOnlySpan buffer) + { + this.EnsureNotDisposed(); + + if (this.memoryChunk is null) + { + this.memoryChunk = this.AllocateMemoryChunk(); + this.writeChunk = this.memoryChunk; + this.writeOffset = 0; + } + + Span chunkBuffer = this.writeChunk.Buffer.GetSpan(); + int chunkSize = this.writeChunk.Length; + int count = buffer.Length; + int offset = 0; + while (count > 0) + { if (this.writeOffset == chunkSize) { // Allocate a new chunk if the current one is full this.writeChunk.Next = this.AllocateMemoryChunk(); this.writeChunk = this.writeChunk.Next; this.writeOffset = 0; - chunkBuffer = this.writeChunk.Buffer; + chunkBuffer = this.writeChunk.Buffer.GetSpan(); + chunkSize = this.writeChunk.Length; } - chunkBuffer.GetSpan()[this.writeOffset++] = value; + int copyCount = Math.Min(count, chunkSize - this.writeOffset); + buffer.Slice(offset, copyCount).CopyTo(chunkBuffer[this.writeOffset..]); + + offset += copyCount; + count -= copyCount; + this.writeOffset += copyCount; } + } - /// - /// Copy entire buffer into an array. - /// - /// The . - public byte[] ToArray() + /// + public override void WriteByte(byte value) + { + this.EnsureNotDisposed(); + + if (this.memoryChunk is null) { - int length = (int)this.Length; // This will throw if stream is closed - byte[] copy = new byte[this.Length]; + this.memoryChunk = this.AllocateMemoryChunk(); + this.writeChunk = this.memoryChunk; + this.writeOffset = 0; + } - MemoryChunk backupReadChunk = this.readChunk; - int backupReadOffset = this.readOffset; + IMemoryOwner chunkBuffer = this.writeChunk.Buffer; + int chunkSize = this.writeChunk.Length; - this.readChunk = this.memoryChunk; - this.readOffset = 0; - this.Read(copy, 0, length); + if (this.writeOffset == chunkSize) + { + // Allocate a new chunk if the current one is full + this.writeChunk.Next = this.AllocateMemoryChunk(); + this.writeChunk = this.writeChunk.Next; + this.writeOffset = 0; + chunkBuffer = this.writeChunk.Buffer; + } - this.readChunk = backupReadChunk; - this.readOffset = backupReadOffset; + chunkBuffer.GetSpan()[this.writeOffset++] = value; + } - return copy; - } + /// + /// Copy entire buffer into an array. + /// + /// The . + public byte[] ToArray() + { + int length = (int)this.Length; // This will throw if stream is closed + byte[] copy = new byte[this.Length]; - /// - /// Write remainder of this stream to another stream. - /// - /// The stream to write to. - public void WriteTo(Stream stream) - { - this.EnsureNotDisposed(); + MemoryChunk backupReadChunk = this.readChunk; + int backupReadOffset = this.readOffset; - Guard.NotNull(stream, nameof(stream)); + this.readChunk = this.memoryChunk; + this.readOffset = 0; + this.Read(copy, 0, length); - if (this.readChunk is null) - { - if (this.memoryChunk is null) - { - return; - } + this.readChunk = backupReadChunk; + this.readOffset = backupReadOffset; - this.readChunk = this.memoryChunk; - this.readOffset = 0; - } + return copy; + } - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) + /// + /// Write remainder of this stream to another stream. + /// + /// The stream to write to. + public void WriteTo(Stream stream) + { + this.EnsureNotDisposed(); + + Guard.NotNull(stream, nameof(stream)); + + if (this.readChunk is null) + { + if (this.memoryChunk is null) { - chunkSize = this.writeOffset; + return; } - // Following code mirrors Read() logic (readChunk/readOffset should - // point just past last byte of last chunk when done) - // loop until end of chunks is found - while (true) + this.readChunk = this.memoryChunk; + this.readOffset = 0; + } + + IMemoryOwner chunkBuffer = this.readChunk.Buffer; + int chunkSize = this.readChunk.Length; + if (this.readChunk.Next is null) + { + chunkSize = this.writeOffset; + } + + // Following code mirrors Read() logic (readChunk/readOffset should + // point just past last byte of last chunk when done) + // loop until end of chunks is found + while (true) + { + if (this.readOffset == chunkSize) { - if (this.readOffset == chunkSize) + // Exit if no more chunks are currently available + if (this.readChunk.Next is null) { - // Exit if no more chunks are currently available - if (this.readChunk.Next is null) - { - break; - } - - this.readChunk = this.readChunk.Next; - this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer; - chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } + break; } - int writeCount = chunkSize - this.readOffset; - stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount); - this.readOffset = chunkSize; + this.readChunk = this.readChunk.Next; + this.readOffset = 0; + chunkBuffer = this.readChunk.Buffer; + chunkSize = this.readChunk.Length; + if (this.readChunk.Next is null) + { + chunkSize = this.writeOffset; + } } + + int writeCount = chunkSize - this.readOffset; + stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount); + this.readOffset = chunkSize; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsureNotDisposed() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureNotDisposed() + { + if (this.isDisposed) { - if (this.isDisposed) - { - ThrowDisposed(); - } + ThrowDisposed(); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed."); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed."); - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value); - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin."); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin."); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryChunk AllocateMemoryChunk() - { - // Tweak our buffer sizes to take the minimum of the provided buffer sizes - // or the allocator buffer capacity which provides us with the largest - // available contiguous buffer size. - IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MemoryChunk AllocateMemoryChunk() + { + // Tweak our buffer sizes to take the minimum of the provided buffer sizes + // or the allocator buffer capacity which provides us with the largest + // available contiguous buffer size. + IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); - return new MemoryChunk - { - Buffer = buffer, - Next = null, - Length = buffer.Length() - }; - } + return new MemoryChunk + { + Buffer = buffer, + Next = null, + Length = buffer.Length() + }; + } - private static void ReleaseMemoryChunks(MemoryChunk chunk) + private static void ReleaseMemoryChunks(MemoryChunk chunk) + { + while (chunk != null) { - while (chunk != null) - { - chunk.Dispose(); - chunk = chunk.Next; - } + chunk.Dispose(); + chunk = chunk.Next; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetChunkSize(int i) - { - // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator. - // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetChunkSize(int i) + { + // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator. + // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 #pragma warning disable IDE1006 // Naming Styles - const int _128K = 1 << 17; - const int _4M = 1 << 22; - return i < 16 ? _128K * (1 << (i / 4)) : _4M; + const int _128K = 1 << 17; + const int _4M = 1 << 22; + return i < 16 ? _128K * (1 << (i / 4)) : _4M; #pragma warning restore IDE1006 // Naming Styles - } + } - private sealed class MemoryChunk : IDisposable - { - private bool isDisposed; + private sealed class MemoryChunk : IDisposable + { + private bool isDisposed; - public IMemoryOwner Buffer { get; set; } + public IMemoryOwner Buffer { get; set; } - public MemoryChunk Next { get; set; } + public MemoryChunk Next { get; set; } - public int Length { get; set; } + public int Length { get; set; } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!this.isDisposed) { - if (!this.isDisposed) + if (disposing) { - if (disposing) - { - this.Buffer.Dispose(); - } - - this.Buffer = null; - this.isDisposed = true; + this.Buffer.Dispose(); } - } - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); + this.Buffer = null; + this.isDisposed = true; } } + + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs index ec03278def..96a9b5ba01 100644 --- a/src/ImageSharp/IO/IFileSystem.cs +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -1,27 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.IO +/// +/// A simple interface representing the filesystem. +/// +internal interface IFileSystem { /// - /// A simple interface representing the filesystem. + /// Returns a readable stream as defined by the path. /// - internal interface IFileSystem - { - /// - /// Returns a readable stream as defined by the path. - /// - /// Path to the file to open. - /// A stream representing the file to open. - Stream OpenRead(string path); + /// Path to the file to open. + /// A stream representing the file to open. + Stream OpenRead(string path); - /// - /// Creates or opens a file and returns it as a writable stream as defined by the path. - /// - /// Path to the file to open. - /// A stream representing the file to open. - Stream Create(string path); - } + /// + /// Creates or opens a file and returns it as a writable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream Create(string path); } diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index 65a83eaf3c..f4dfa2fe14 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -1,19 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; +namespace SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.IO +/// +/// A wrapper around the local File apis. +/// +internal sealed class LocalFileSystem : IFileSystem { - /// - /// A wrapper around the local File apis. - /// - internal sealed class LocalFileSystem : IFileSystem - { - /// - public Stream OpenRead(string path) => File.OpenRead(path); + /// + public Stream OpenRead(string path) => File.OpenRead(path); - /// - public Stream Create(string path) => File.Create(path); - } + /// + public Stream Create(string path) => File.Create(path); } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index efa337d6d0..6953da1cea 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -1,171 +1,167 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing the decoding of new images. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing the decoding of new images. - /// - public abstract partial class Image + /// + /// Creates an instance backed by an uninitialized memory buffer. + /// This is an optimized creation method intended to be used by decoders. + /// The image might be filled with memory garbage. + /// + /// The pixel type + /// The + /// The width of the image + /// The height of the image + /// The + /// The result + internal static Image CreateUninitialized( + Configuration configuration, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel { - /// - /// Creates an instance backed by an uninitialized memory buffer. - /// This is an optimized creation method intended to be used by decoders. - /// The image might be filled with memory garbage. - /// - /// The pixel type - /// The - /// The width of the image - /// The height of the image - /// The - /// The result - internal static Image CreateUninitialized( - Configuration configuration, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel + Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers); + return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); + } + + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The general configuration. + /// The image stream to read the header from. + /// The mime type or null if none found. + private static IImageFormat InternalDetectFormat(Configuration configuration, Stream stream) + { + // We take a minimum of the stream length vs the max header size and always check below + // to ensure that only formats that headers fit within the given buffer length are tested. + int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); + if (headerSize <= 0) { - Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers); - return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); + return null; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general configuration. - /// The image stream to read the header from. - /// The mime type or null if none found. - private static IImageFormat InternalDetectFormat(Configuration configuration, Stream stream) + // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice, + // and heap allocation will never happen, there is no need for the usual try-finally ArrayPool dance. + // The array case is only a safety mechanism following stackalloc best practices. + Span headersBuffer = headerSize > 512 ? new byte[headerSize] : stackalloc byte[headerSize]; + long startPosition = stream.Position; + + // Read doesn't always guarantee the full returned length so read a byte + // at a time until we get either our count or hit the end of the stream. + int n = 0; + int i; + do { - // We take a minimum of the stream length vs the max header size and always check below - // to ensure that only formats that headers fit within the given buffer length are tested. - int headerSize = (int)Math.Min(configuration.MaxHeaderSize, stream.Length); - if (headerSize <= 0) - { - return null; - } - - // Header sizes are so small, that headersBuffer will be always stackalloc-ed in practice, - // and heap allocation will never happen, there is no need for the usual try-finally ArrayPool dance. - // The array case is only a safety mechanism following stackalloc best practices. - Span headersBuffer = headerSize > 512 ? new byte[headerSize] : stackalloc byte[headerSize]; - long startPosition = stream.Position; - - // Read doesn't always guarantee the full returned length so read a byte - // at a time until we get either our count or hit the end of the stream. - int n = 0; - int i; - do - { - i = stream.Read(headersBuffer, n, headerSize - n); - n += i; - } - while (n < headerSize && i > 0); + i = stream.Read(headersBuffer, n, headerSize - n); + n += i; + } + while (n < headerSize && i > 0); - stream.Position = startPosition; + stream.Position = startPosition; - // Does the given stream contain enough data to fit in the header for the format - // and does that data match the format specification? - // Individual formats should still check since they are public. - IImageFormat format = null; - foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) + // Does the given stream contain enough data to fit in the header for the format + // and does that data match the format specification? + // Individual formats should still check since they are public. + IImageFormat format = null; + foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) + { + if (formatDetector.HeaderSize <= headerSize) { - if (formatDetector.HeaderSize <= headerSize) + IImageFormat attemptFormat = formatDetector.DetectFormat(headersBuffer); + if (attemptFormat != null) { - IImageFormat attemptFormat = formatDetector.DetectFormat(headersBuffer); - if (attemptFormat != null) - { - format = attemptFormat; - } + format = attemptFormat; } } - - return format; } - /// - /// By reading the header on the provided stream this calculates the images format. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The IImageFormat. - /// The image format or null if none found. - private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format) - { - format = InternalDetectFormat(options.Configuration, stream); + return format; + } - return format != null - ? options.Configuration.ImageFormatsManager.FindDecoder(format) - : null; - } + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The general decoder options. + /// The image stream to read the header from. + /// The IImageFormat. + /// The image format or null if none found. + private static IImageDecoder DiscoverDecoder(DecoderOptions options, Stream stream, out IImageFormat format) + { + format = InternalDetectFormat(options.Configuration, stream); - /// - /// Decodes the image stream to the current image. - /// - /// The general decoder options. - /// The stream. - /// The token to monitor for cancellation requests. - /// The pixel format. - /// - /// A new . - /// - private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); - if (decoder is null) - { - return (null, null); - } + return format != null + ? options.Configuration.ImageFormatsManager.FindDecoder(format) + : null; + } - Image img = decoder.Decode(options, stream, cancellationToken); - return (img, format); + /// + /// Decodes the image stream to the current image. + /// + /// The general decoder options. + /// The stream. + /// The token to monitor for cancellation requests. + /// The pixel format. + /// + /// A new . + /// + private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); + if (decoder is null) + { + return (null, null); } - private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - { - IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); - if (decoder is null) - { - return (null, null); - } + Image img = decoder.Decode(options, stream, cancellationToken); + return (img, format); + } - Image img = decoder.Decode(options, stream, cancellationToken); - return (img, format); + private static (Image Image, IImageFormat Format) Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + { + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); + if (decoder is null) + { + return (null, null); } - /// - /// Reads the raw image information from the specified stream. - /// - /// The general decoder options. - /// The stream. - /// The token to monitor for cancellation requests. - /// - /// The or null if a suitable info detector is not found. - /// - private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - { - IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); + Image img = decoder.Decode(options, stream, cancellationToken); + return (img, format); + } - if (decoder is not IImageInfoDetector detector) - { - return (null, null); - } + /// + /// Reads the raw image information from the specified stream. + /// + /// The general decoder options. + /// The stream. + /// The token to monitor for cancellation requests. + /// + /// The or null if a suitable info detector is not found. + /// + private static (IImageInfo ImageInfo, IImageFormat Format) InternalIdentity(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + { + IImageDecoder decoder = DiscoverDecoder(options, stream, out IImageFormat format); - IImageInfo info = detector?.Identify(options, stream, cancellationToken); - return (info, format); + if (decoder is not IImageInfoDetector detector) + { + return (null, null); } + + IImageInfo info = detector?.Identify(options, stream, cancellationToken); + return (info, format); } } diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 341961a2b9..d7d1f26913 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -1,229 +1,226 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing the creation of new image from a byte span. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing the creation of new image from a byte span. - /// - public abstract partial class Image + /// + /// By reading the header on the provided byte span this calculates the images format. + /// + /// The byte span containing encoded image data to read the header from. + /// The format or null if none found. + public static IImageFormat DetectFormat(ReadOnlySpan data) + => DetectFormat(DecoderOptions.Default, data); + + /// + /// By reading the header on the provided byte span this calculates the images format. + /// + /// The general decoder options. + /// The byte span containing encoded image data to read the header from. + /// The options are null. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan data) { - /// - /// By reading the header on the provided byte span this calculates the images format. - /// - /// The byte span containing encoded image data to read the header from. - /// The format or null if none found. - public static IImageFormat DetectFormat(ReadOnlySpan data) - => DetectFormat(DecoderOptions.Default, data); - - /// - /// By reading the header on the provided byte span this calculates the images format. - /// - /// The general decoder options. - /// The byte span containing encoded image data to read the header from. - /// The options are null. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(DecoderOptions options, ReadOnlySpan data) + Guard.NotNull(options, nameof(options.Configuration)); + + Configuration configuration = options.Configuration; + int maxHeaderSize = configuration.MaxHeaderSize; + if (maxHeaderSize <= 0) { - Guard.NotNull(options, nameof(options.Configuration)); + return null; + } - Configuration configuration = options.Configuration; - int maxHeaderSize = configuration.MaxHeaderSize; - if (maxHeaderSize <= 0) - { - return null; - } + foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) + { + IImageFormat f = detector.DetectFormat(data); - foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) + if (f != null) { - IImageFormat f = detector.DetectFormat(data); - - if (f != null) - { - return f; - } + return f; } - - return default; } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The byte span containing encoded image data to read the header from. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector not found. - /// - public static IImageInfo Identify(ReadOnlySpan 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. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector not found. - /// - public static IImageInfo Identify(ReadOnlySpan data, out IImageFormat format) - => Identify(DecoderOptions.Default, data, out format); - - /// - /// Reads the raw image information from the specified span of bytes without fully decoding it. - /// - /// The general decoder options. - /// The byte span containing encoded image data to read the header from. - /// The format type of the decoded image. - /// The configuration is null. - /// The data is null. - /// The data is not readable. - /// - /// The or null if suitable info detector is not found. - /// - public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan data, out IImageFormat format) + return default; + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte span containing encoded image data to read the header from. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(ReadOnlySpan 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. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(ReadOnlySpan data, out IImageFormat format) + => Identify(DecoderOptions.Default, data, out format); + + /// + /// Reads the raw image information from the specified span of bytes without fully decoding it. + /// + /// The general decoder options. + /// The byte span containing encoded image data to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// The data is null. + /// The data is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static unsafe IImageInfo Identify(DecoderOptions options, ReadOnlySpan data, out IImageFormat format) + { + fixed (byte* ptr = data) { - fixed (byte* ptr = data) - { - using var stream = new UnmanagedMemoryStream(ptr, data.Length); - return Identify(options, stream, out format); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Identify(options, stream, out format); } + } - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The byte span containing encoded image data. - /// The pixel format. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static Image Load(ReadOnlySpan data) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, data); - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The byte span containing image data. - /// The mime type of the decoded image. - /// The pixel format. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static Image Load(ReadOnlySpan data, out IImageFormat format) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, data, out format); - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The general decoder options. - /// The byte span containing encoded image data. - /// The pixel format. - /// The options are null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static unsafe Image Load(DecoderOptions options, ReadOnlySpan data) - where TPixel : unmanaged, IPixel + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing encoded image data. + /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// A new . + public static Image Load(ReadOnlySpan data) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, data); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// The mime type of the decoded image. + /// The pixel format. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// A new . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, data, out format); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The general decoder options. + /// The byte span containing encoded image data. + /// The pixel format. + /// The options are null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// A new . + public static unsafe Image Load(DecoderOptions options, ReadOnlySpan data) + where TPixel : unmanaged, IPixel + { + fixed (byte* ptr = data) { - fixed (byte* ptr = data) - { - using var stream = new UnmanagedMemoryStream(ptr, data.Length); - return Load(options, stream); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream); } + } - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The general decoder options. - /// The byte span containing image data. - /// The of the decoded image. - /// The pixel format. - /// The options are null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// A new . - public static unsafe Image Load( - DecoderOptions options, - ReadOnlySpan data, - out IImageFormat format) - where TPixel : unmanaged, IPixel + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The general decoder options. + /// The byte span containing image data. + /// The of the decoded image. + /// The pixel format. + /// The options are null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// A new . + public static unsafe Image Load( + DecoderOptions options, + ReadOnlySpan data, + out IImageFormat format) + where TPixel : unmanaged, IPixel + { + fixed (byte* ptr = data) { - fixed (byte* ptr = data) - { - using var stream = new UnmanagedMemoryStream(ptr, data.Length); - return Load(options, stream, out format); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream, out format); } + } - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The byte span containing image data. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(ReadOnlySpan data) - => Load(DecoderOptions.Default, data); - - /// - /// Load a new instance of from the given encoded byte array. - /// - /// The byte span containing image data. - /// The detected format. - /// The decoder is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static Image Load(ReadOnlySpan data, out IImageFormat format) - => Load(DecoderOptions.Default, data, out format); - - /// - /// Decodes a new instance of from the given encoded byte span. - /// - /// The general decoder options. - /// The byte span containing image data. - /// The . - public static Image Load(DecoderOptions options, ReadOnlySpan data) - => Load(options, data, out _); - - /// - /// Load a new instance of from the given encoded byte span. - /// - /// The general decoder options. - /// The byte span containing image data. - /// The of the decoded image.> - /// The options are null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The . - public static unsafe Image Load( - DecoderOptions options, - ReadOnlySpan data, - out IImageFormat format) + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The byte span containing image data. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The . + public static Image Load(ReadOnlySpan data) + => Load(DecoderOptions.Default, data); + + /// + /// Load a new instance of from the given encoded byte array. + /// + /// The byte span containing image data. + /// The detected format. + /// The decoder is null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The . + public static Image Load(ReadOnlySpan data, out IImageFormat format) + => Load(DecoderOptions.Default, data, out format); + + /// + /// Decodes a new instance of from the given encoded byte span. + /// + /// The general decoder options. + /// The byte span containing image data. + /// The . + public static Image Load(DecoderOptions options, ReadOnlySpan data) + => Load(options, data, out _); + + /// + /// Load a new instance of from the given encoded byte span. + /// + /// The general decoder options. + /// The byte span containing image data. + /// The of the decoded image.> + /// The options are null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The . + public static unsafe Image Load( + DecoderOptions options, + ReadOnlySpan data, + out IImageFormat format) + { + fixed (byte* ptr = data) { - fixed (byte* ptr = data) - { - using var stream = new UnmanagedMemoryStream(ptr, data.Length); - return Load(options, stream, out format); - } + using var stream = new UnmanagedMemoryStream(ptr, data.Length); + return Load(options, stream, out format); } } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 6594ed2183..199d450b89 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -1,364 +1,359 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing the creation of new image from a given file. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing the creation of new image from a given file. - /// - public abstract partial class Image - { - /// - /// By reading the header on the provided file this calculates the images mime type. - /// - /// The image file to open and to read the header from. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(string filePath) - => DetectFormat(DecoderOptions.Default, filePath); + /// + /// By reading the header on the provided file this calculates the images mime type. + /// + /// The image file to open and to read the header from. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(string filePath) + => DetectFormat(DecoderOptions.Default, filePath); - /// - /// By reading the header on the provided file this calculates the images mime type. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The configuration is null. - /// The mime type or null if none found. - public static IImageFormat DetectFormat(DecoderOptions options, string filePath) - { - Guard.NotNull(options, nameof(options)); + /// + /// By reading the header on the provided file this calculates the images mime type. + /// + /// The general decoder options. + /// The image file to open and to read the header from. + /// The configuration is null. + /// The mime type or null if none found. + public static IImageFormat DetectFormat(DecoderOptions options, string filePath) + { + Guard.NotNull(options, nameof(options)); - using Stream file = options.Configuration.FileSystem.OpenRead(filePath); - return DetectFormat(options, file); - } + using Stream file = options.Configuration.FileSystem.OpenRead(filePath); + return DetectFormat(options, 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. - /// - /// 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 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. - /// - /// The or null if suitable info detector not found. - /// - public static IImageInfo Identify(string filePath, out IImageFormat format) - => Identify(DecoderOptions.Default, filePath, out format); + /// + /// 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. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath, out IImageFormat format) + => Identify(DecoderOptions.Default, filePath, out format); - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The format type of the decoded image. - /// The configuration is null. - /// - /// The or null if suitable info detector is not found. - /// - public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format) - { - Guard.NotNull(options, nameof(options)); - using Stream file = options.Configuration.FileSystem.OpenRead(filePath); - return Identify(options, file, out format); - } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// The configuration is null. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(DecoderOptions options, string filePath, out IImageFormat format) + { + Guard.NotNull(options, nameof(options)); + using Stream file = options.Configuration.FileSystem.OpenRead(filePath); + return Identify(options, file, out format); + } - /// - /// 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 token to monitor for cancellation requests. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken = default) - => IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken); + /// + /// 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 token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task IdentifyAsync(string filePath, CancellationToken cancellationToken = default) + => IdentifyAsync(DecoderOptions.Default, filePath, cancellationToken); - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static async Task IdentifyAsync( - DecoderOptions options, - string filePath, - CancellationToken cancellationToken = default) - { - (IImageInfo ImageInfo, IImageFormat Format) res = - await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false); - return res.ImageInfo; - } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task IdentifyAsync( + DecoderOptions options, + string filePath, + CancellationToken cancellationToken = default) + { + (IImageInfo ImageInfo, IImageFormat Format) res = + await IdentifyWithFormatAsync(options, filePath, cancellationToken).ConfigureAwait(false); + return res.ImageInfo; + } - /// - /// 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 token to monitor for cancellation requests. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - string filePath, - CancellationToken cancellationToken = default) - => IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken); + /// + /// 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 token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + string filePath, + CancellationToken cancellationToken = default) + => IdentifyWithFormatAsync(DecoderOptions.Default, filePath, cancellationToken); - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image file to open and to read the header from. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - DecoderOptions options, - string filePath, - CancellationToken cancellationToken = default) - { - Guard.NotNull(options, nameof(options)); - using Stream stream = options.Configuration.FileSystem.OpenRead(filePath); - return await IdentifyWithFormatAsync(options, stream, cancellationToken) - .ConfigureAwait(false); - } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image file to open and to read the header from. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static async Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + DecoderOptions options, + string filePath, + CancellationToken cancellationToken = default) + { + Guard.NotNull(options, nameof(options)); + using Stream stream = options.Configuration.FileSystem.OpenRead(filePath); + return await IdentifyWithFormatAsync(options, stream, cancellationToken) + .ConfigureAwait(false); + } - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// - /// Thrown if the stream is not readable nor seekable. - /// - /// The . - public static Image Load(string path) - => Load(DecoderOptions.Default, path); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The . + public static Image Load(string path) + => Load(DecoderOptions.Default, path); - /// - /// Create a new instance of the class from the given file. - /// - /// 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(string path, out IImageFormat format) - => Load(DecoderOptions.Default, path, out format); + /// + /// Create a new instance of the class from the given file. + /// + /// 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(string path, out IImageFormat format) + => Load(DecoderOptions.Default, path, out format); - /// - /// Create a new instance of the class from the given file. - /// - /// The general decoder options. - /// The file path to the image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The . - public static Image Load(DecoderOptions options, string path) - => Load(options, path, out _); + /// + /// Create a new instance of the class from the given file. + /// + /// The general decoder options. + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// The . + public static Image Load(DecoderOptions options, string path) + => Load(options, path, out _); - /// - /// Create a new instance of the class from the given file. - /// - /// The general decoder options. - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task LoadAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - { - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) - .ConfigureAwait(false); - return img; - } + /// + /// Create a new instance of the class from the given file. + /// + /// The general decoder options. + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync( + DecoderOptions options, + string path, + CancellationToken cancellationToken = default) + { + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// The decoder is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(string path, CancellationToken cancellationToken = default) - => LoadAsync(DecoderOptions.Default, path, cancellationToken); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// The decoder is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(string path, CancellationToken cancellationToken = default) + => LoadAsync(DecoderOptions.Default, path, cancellationToken); - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadAsync(DecoderOptions.Default, path, cancellationToken); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => LoadAsync(DecoderOptions.Default, path, cancellationToken); - /// - /// Create a new instance of the class from the given file. - /// - /// The general decoder options. - /// The file path to the image. - /// The token to monitor for cancellation requests. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static async Task> LoadAsync( - DecoderOptions options, - string path, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); + /// + /// Create a new instance of the class from the given file. + /// + /// The general decoder options. + /// The file path to the image. + /// The token to monitor for cancellation requests. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync( + DecoderOptions options, + string path, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(options, nameof(options)); - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - (Image img, _) = - await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); - return img; - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + (Image img, _) = + await LoadWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); + return img; + } - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The path is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The pixel format. - /// A new . - public static Image Load(string path) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, path); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The pixel format. + /// A new . + public static Image Load(string path) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, path); - /// - /// Create a new instance of the class from the given file. - /// - /// The file path to the image. - /// The mime type of the decoded image. - /// The path is null. - /// Image format not recognised. - /// Image contains invalid content. - /// Image format is not supported. - /// The pixel format. - /// A new . - public static Image Load(string path, out IImageFormat format) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, path, out format); + /// + /// Create a new instance of the class from the given file. + /// + /// The file path to the image. + /// The mime type of the decoded image. + /// The path is null. + /// Image format not recognised. + /// Image contains invalid content. + /// Image format is not supported. + /// The pixel format. + /// A new . + public static Image Load(string path, out IImageFormat format) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, path, out format); - /// - /// Create a new instance of the class from the given file. - /// - /// The general decoder options. - /// The file path to the image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(DecoderOptions options, string path) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); + /// + /// Create a new instance of the class from the given file. + /// + /// The general decoder options. + /// The file path to the image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(DecoderOptions options, string path) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(path, nameof(path)); - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Load(options, stream); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream); + } - /// - /// Create a new instance of the class from the given file. - /// - /// The general decoder options. - /// The file path to the image. - /// The mime type of the decoded image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(DecoderOptions options, string path, out IImageFormat format) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); + /// + /// Create a new instance of the class from the given file. + /// + /// The general decoder options. + /// The file path to the image. + /// The mime type of the decoded image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(DecoderOptions options, string path, out IImageFormat format) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(path, nameof(path)); - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Load(options, stream, out format); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream, out format); + } - /// - /// Create a new instance of the class from the given file. - /// The pixel type is selected by the decoder. - /// - /// The general decoder options. - /// The file path to the image. - /// The mime type of the decoded image. - /// The configuration is null. - /// The path is null. - /// Image format not recognised. - /// Image format is not supported. - /// Image contains invalid content. - /// A new . - public static Image Load(DecoderOptions options, string path, out IImageFormat format) - { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(path, nameof(path)); + /// + /// Create a new instance of the class from the given file. + /// The pixel type is selected by the decoder. + /// + /// The general decoder options. + /// The file path to the image. + /// The mime type of the decoded image. + /// The configuration is null. + /// The path is null. + /// Image format not recognised. + /// Image format is not supported. + /// Image contains invalid content. + /// A new . + public static Image Load(DecoderOptions options, string path, out IImageFormat format) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(path, nameof(path)); - using Stream stream = options.Configuration.FileSystem.OpenRead(path); - return Load(options, stream, out format); - } + using Stream stream = options.Configuration.FileSystem.OpenRead(path); + return Load(options, stream, out format); } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 4d1b172313..eb33ea9978 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -1,612 +1,606 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing the creation of new image from a given stream. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing the creation of new image from a given stream. - /// - public abstract partial class Image + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static IImageFormat DetectFormat(Stream stream) + => DetectFormat(DecoderOptions.Default, stream); + + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The general decoder options. + /// The image stream to read the header from. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// The format type or null if none found. + public static IImageFormat DetectFormat(DecoderOptions options, Stream stream) + => WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); + + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The image stream to read the header from. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable. + /// A representing the asynchronous operation or null if none is found. + public static Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// By reading the header on the provided stream this calculates the images format type. + /// + /// The general decoder options. + /// The image stream to read the header from. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// A representing the asynchronous operation. + public static Task DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + => WithSeekableStreamAsync( + options, + stream, + (s, _) => InternalDetectFormat(options.Configuration, s), + cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Stream stream) + => Identify(stream, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the header from. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static Task IdentifyAsync(Stream stream, CancellationToken cancellationToken = default) + => IdentifyAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// 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. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(Stream stream, out IImageFormat format) + => Identify(DecoderOptions.Default, stream, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image stream to read the information from. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(DecoderOptions options, Stream stream) + => Identify(options, stream, out _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// A representing the asynchronous operation or null if + /// a suitable detector is not found. + /// + public static async Task IdentifyAsync( + DecoderOptions options, + Stream stream, + CancellationToken cancellationToken = default) { - /// - /// By reading the header on the provided stream this calculates the images format type. - /// - /// The image stream to read the header from. - /// The stream is null. - /// The stream is not readable. - /// The format type or null if none found. - public static IImageFormat DetectFormat(Stream stream) - => DetectFormat(DecoderOptions.Default, stream); - - /// - /// By reading the header on the provided stream this calculates the images format type. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// The format type or null if none found. - public static IImageFormat DetectFormat(DecoderOptions options, Stream stream) - => WithSeekableStream(options, stream, s => InternalDetectFormat(options.Configuration, s)); - - /// - /// By reading the header on the provided stream this calculates the images format type. - /// - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable. - /// A representing the asynchronous operation or null if none is found. - public static Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) - => DetectFormatAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// By reading the header on the provided stream this calculates the images format type. - /// - /// The general decoder options. - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// A representing the asynchronous operation. - public static Task DetectFormatAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - => WithSeekableStreamAsync( - options, - stream, - (s, _) => InternalDetectFormat(options.Configuration, s), - cancellationToken); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the header from. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The or null if a suitable info detector is not found. - /// - public static IImageInfo Identify(Stream stream) - => Identify(stream, out IImageFormat _); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the header from. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// A representing the asynchronous operation or null if - /// a suitable detector is not found. - /// - public static Task IdentifyAsync(Stream stream, CancellationToken cancellationToken = default) - => IdentifyAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// 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. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The or null if a suitable info detector is not found. - /// - public static IImageInfo Identify(Stream stream, out IImageFormat format) - => Identify(DecoderOptions.Default, stream, out format); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The or null if a suitable info detector is not found. - /// - public static IImageInfo Identify(DecoderOptions options, Stream stream) - => Identify(options, stream, out _); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// A representing the asynchronous operation or null if - /// a suitable detector is not found. - /// - public static async Task IdentifyAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - { - (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); - return res.ImageInfo; - } + (IImageInfo ImageInfo, IImageFormat Format) res = await IdentifyWithFormatAsync(options, stream, cancellationToken).ConfigureAwait(false); + return res.ImageInfo; + } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The format type of the decoded image. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The or null if a suitable info detector is not found. - /// - public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format) - { - (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentity(options, s)); + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image stream to read the information from. + /// The format type of the decoded image. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The or null if a suitable info detector is not found. + /// + public static IImageInfo Identify(DecoderOptions options, Stream stream, out IImageFormat format) + { + (IImageInfo ImageInfo, IImageFormat Format) data = WithSeekableStream(options, stream, s => InternalIdentity(options, s)); - format = data.Format; - return data.ImageInfo; - } + format = data.Format; + return data.ImageInfo; + } - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The image stream to read the information from. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - Stream stream, - CancellationToken cancellationToken = default) - => IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Reads the raw image information from the specified stream without fully decoding it. - /// - /// The general decoder options. - /// The image stream to read the information from. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable. - /// Image contains invalid content. - /// - /// The representing the asynchronous operation with the parameter type - /// property set to null if suitable info detector is not found. - /// - public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - => WithSeekableStreamAsync( - options, - stream, - (s, ct) => InternalIdentity(options, s, ct), - cancellationToken); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The stream containing image information. - /// The format type of the decoded image. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The . - public static Image Load(Stream stream, out IImageFormat format) - => Load(DecoderOptions.Default, stream, out format); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) - => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The stream containing image information. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The . - public static Image Load(Stream stream) => Load(DecoderOptions.Default, stream); - - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) - => LoadAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Decode a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A new . - public static Image Load(DecoderOptions options, Stream stream) - => Load(options, stream, out _); - - /// - /// Decode a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) - { - (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken) - .ConfigureAwait(false); - return fmt.Image; - } + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + Stream stream, + CancellationToken cancellationToken = default) + => IdentifyWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The general decoder options. + /// The image stream to read the information from. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable. + /// Image contains invalid content. + /// + /// The representing the asynchronous operation with the parameter type + /// property set to null if suitable info detector is not found. + /// + public static Task<(IImageInfo ImageInfo, IImageFormat Format)> IdentifyWithFormatAsync( + DecoderOptions options, + Stream stream, + CancellationToken cancellationToken = default) + => WithSeekableStreamAsync( + options, + stream, + (s, ct) => InternalIdentity(options, s, ct), + cancellationToken); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream, out IImageFormat format) + => Load(DecoderOptions.Default, stream, out format); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The . + public static Image Load(Stream stream) => Load(DecoderOptions.Default, stream); + + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// Decode a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(DecoderOptions options, Stream stream) + => Load(options, stream, out _); + + /// + /// Decode a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task LoadAsync(DecoderOptions options, Stream stream, CancellationToken cancellationToken = default) + { + (Image Image, IImageFormat Format) fmt = await LoadWithFormatAsync(options, stream, cancellationToken) + .ConfigureAwait(false); + return fmt.Image; + } + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(Stream stream) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, stream); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => LoadAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The format type of the decoded image. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(Stream stream, out IImageFormat format) + where TPixel : unmanaged, IPixel + => Load(DecoderOptions.Default, stream, out format); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A new . + public static Image Load(DecoderOptions options, Stream stream) + where TPixel : unmanaged, IPixel + => Load(options, stream, out IImageFormat _); + + /// + /// Create a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The format type of the decoded image. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) + where TPixel : unmanaged, IPixel + { + (Image Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode(options, s)); + + format = data.Format; - /// - /// Create a new instance of the class from the given stream. - /// - /// The stream containing image information. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(Stream stream) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, stream); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The stream containing image information. - /// The format type of the decoded image. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(Stream stream, out IImageFormat format) - where TPixel : unmanaged, IPixel - => Load(DecoderOptions.Default, stream, out format); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - => LoadWithFormatAsync(DecoderOptions.Default, stream, cancellationToken); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A new . - public static Image Load(DecoderOptions options, Stream stream) - where TPixel : unmanaged, IPixel - => Load(options, stream, out IImageFormat _); - - /// - /// Create a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The format type of the decoded image. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) - where TPixel : unmanaged, IPixel + if (data.Image is null) { - (Image Image, IImageFormat Format) data = WithSeekableStream(options, stream, s => Decode(options, s)); + ThrowNotLoaded(options); + } - format = data.Format; + return data.Image; + } - if (data.Image is null) - { - ThrowNotLoaded(options); - } + /// + /// Create a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + DecoderOptions options, + Stream stream, + CancellationToken cancellationToken = default) + { + (Image Image, IImageFormat Format) data = + await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) + .ConfigureAwait(false); - return data.Image; + if (data.Image is null) + { + ThrowNotLoaded(options); } - /// - /// Create a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - { - (Image Image, IImageFormat Format) data = - await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) - .ConfigureAwait(false); + return data; + } - if (data.Image is null) - { - ThrowNotLoaded(options); - } + /// + /// Create a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( + DecoderOptions options, + Stream stream, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + (Image Image, IImageFormat Format) data = + await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) + .ConfigureAwait(false); - return data; + if (data.Image is null) + { + ThrowNotLoaded(options); } - /// - /// Create a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - (Image Image, IImageFormat Format) data = - await WithSeekableStreamAsync(options, stream, (s, ct) => Decode(options, s, ct), cancellationToken) - .ConfigureAwait(false); + return data; + } - if (data.Image is null) - { - ThrowNotLoaded(options); - } + /// + /// Create a new instance of the class from the given stream. + /// + /// The general decoder options. + /// The stream containing image information. + /// The token to monitor for cancellation requests. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// The pixel format. + /// A representing the asynchronous operation. + public static async Task> LoadAsync( + DecoderOptions options, + Stream stream, + CancellationToken cancellationToken = default) + where TPixel : unmanaged, IPixel + { + (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) + .ConfigureAwait(false); + return img; + } - return data; - } + /// + /// Decode a new instance of the class from the given stream. + /// The pixel format is selected by the decoder. + /// + /// The general decoder options. + /// The stream containing image information. + /// The format type of the decoded image. + /// The options are null. + /// The stream is null. + /// The stream is not readable or the image format is not supported. + /// Image format not recognised. + /// Image contains invalid content. + /// A new . + public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) + { + (Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s)); - /// - /// Create a new instance of the class from the given stream. - /// - /// The general decoder options. - /// The stream containing image information. - /// The token to monitor for cancellation requests. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// The pixel format. - /// A representing the asynchronous operation. - public static async Task> LoadAsync( - DecoderOptions options, - Stream stream, - CancellationToken cancellationToken = default) - where TPixel : unmanaged, IPixel - { - (Image img, _) = await LoadWithFormatAsync(options, stream, cancellationToken) - .ConfigureAwait(false); - return img; - } + format = fmt; - /// - /// Decode a new instance of the class from the given stream. - /// The pixel format is selected by the decoder. - /// - /// The general decoder options. - /// The stream containing image information. - /// The format type of the decoded image. - /// The options are null. - /// The stream is null. - /// The stream is not readable or the image format is not supported. - /// Image format not recognised. - /// Image contains invalid content. - /// A new . - public static Image Load(DecoderOptions options, Stream stream, out IImageFormat format) + if (img is null) { - (Image img, IImageFormat fmt) = WithSeekableStream(options, stream, s => Decode(options, s)); + ThrowNotLoaded(options); + } - format = fmt; + return img; + } - if (img is null) - { - ThrowNotLoaded(options); - } + /// + /// Performs the given action against the stream ensuring that it is seekable. + /// + /// The type of object returned from the action. + /// The general decoder options. + /// The input stream. + /// The action to perform. + /// The . + internal static T WithSeekableStream( + DecoderOptions options, + Stream stream, + Func action) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - return img; + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); } - /// - /// Performs the given action against the stream ensuring that it is seekable. - /// - /// The type of object returned from the action. - /// The general decoder options. - /// The input stream. - /// The action to perform. - /// The . - internal static T WithSeekableStream( - DecoderOptions options, - Stream stream, - Func action) + Configuration configuration = options.Configuration; + if (stream.CanSeek) { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); - - if (!stream.CanRead) + if (configuration.ReadOrigin == ReadOrigin.Begin) { - throw new NotSupportedException("Cannot read from the stream."); + stream.Position = 0; } - Configuration configuration = options.Configuration; - if (stream.CanSeek) - { - if (configuration.ReadOrigin == ReadOrigin.Begin) - { - stream.Position = 0; - } + return action(stream); + } - return action(stream); - } + // We want to be able to load images from things like HttpContext.Request.Body + using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); + stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); + memoryStream.Position = 0; - // We want to be able to load images from things like HttpContext.Request.Body - using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - stream.CopyTo(memoryStream, configuration.StreamProcessingBufferSize); - memoryStream.Position = 0; + return action(memoryStream); + } - return action(memoryStream); - } + /// + /// Performs the given action asynchronously against the stream ensuring that it is seekable. + /// + /// The type of object returned from the action. + /// The general decoder options. + /// The input stream. + /// The action to perform. + /// The cancellation token. + /// The . + /// Cannot read from the stream. + internal static async Task WithSeekableStreamAsync( + DecoderOptions options, + Stream stream, + Func action, + CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); - /// - /// Performs the given action asynchronously against the stream ensuring that it is seekable. - /// - /// The type of object returned from the action. - /// The general decoder options. - /// The input stream. - /// The action to perform. - /// The cancellation token. - /// The . - /// Cannot read from the stream. - internal static async Task WithSeekableStreamAsync( - DecoderOptions options, - Stream stream, - Func action, - CancellationToken cancellationToken) + if (!stream.CanRead) { - Guard.NotNull(options, nameof(options)); - Guard.NotNull(stream, nameof(stream)); + throw new NotSupportedException("Cannot read from the stream."); + } - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); - } + Configuration configuration = options.Configuration; + if (stream.CanSeek && configuration.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; - Configuration configuration = options.Configuration; - if (stream.CanSeek && configuration.ReadOrigin == ReadOrigin.Begin) - { - stream.Position = 0; + // NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that + // would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the + // code below to copy the stream to an in-memory buffer before invoking the action. + } - // NOTE: We are explicitly not executing the action against the stream here as we do in WithSeekableStream() because that - // would incur synchronous IO reads which must be avoided in this asynchronous method. Instead, we will *always* run the - // code below to copy the stream to an in-memory buffer before invoking the action. - } + using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); + await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); + memoryStream.Position = 0; - using ChunkedMemoryStream memoryStream = new(configuration.MemoryAllocator); - await stream.CopyToAsync(memoryStream, configuration.StreamProcessingBufferSize, cancellationToken).ConfigureAwait(false); - memoryStream.Position = 0; + return action(memoryStream, cancellationToken); + } - return action(memoryStream, cancellationToken); - } + [DoesNotReturn] + private static void ThrowNotLoaded(DecoderOptions options) + { + StringBuilder sb = new(); + sb.AppendLine("Image cannot be loaded. Available decoders:"); - [DoesNotReturn] - private static void ThrowNotLoaded(DecoderOptions options) + foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) { - StringBuilder sb = new(); - sb.AppendLine("Image cannot be loaded. Available decoders:"); - - foreach (KeyValuePair val in options.Configuration.ImageFormatsManager.ImageDecoders) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new UnknownImageFormatException(sb.ToString()); + sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } + + throw new UnknownImageFormatException(sb.ToString()); } } diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index a8db3483dc..9a37adb3c0 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing the creation of new image from raw pixel data. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing the creation of new image from raw pixel data. - /// - public abstract partial class Image - { - /// - /// Create a new instance of the class from the raw data. - /// - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Create a new instance of the class from the raw data. + /// + /// The readonly span of bytes containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// The data length is incorrect. + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(Configuration.Default, data, width, height); - /// - /// Create a new instance of the class from the given readonly span of bytes in format. - /// - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(Configuration.Default, data, width, height); + /// + /// Create a new instance of the class from the given readonly span of bytes in format. + /// + /// The readonly span of bytes containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// The data length is incorrect. + /// A new . + public static Image LoadPixelData(ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(Configuration.Default, data, width, height); - /// - /// Create a new instance of the class from the given readonly span of bytes in format. - /// - /// The configuration for the decoder. - /// The readonly span of bytes containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// The configuration is null. - /// The data length is incorrect. - /// A new . - public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + /// + /// Create a new instance of the class from the given readonly span of bytes in format. + /// + /// The configuration for the decoder. + /// The readonly span of bytes containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// The configuration is null. + /// The data length is incorrect. + /// A new . + public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); - /// - /// Create a new instance of the class from the raw data. - /// - /// The configuration for the decoder. - /// The readonly span containing the image pixel data. - /// The width of the final image. - /// The height of the final image. - /// The configuration is null. - /// The data length is incorrect. - /// The pixel format. - /// A new . - public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); + /// + /// Create a new instance of the class from the raw data. + /// + /// The configuration for the decoder. + /// The readonly span containing the image pixel data. + /// The width of the final image. + /// The height of the final image. + /// The configuration is null. + /// The data length is incorrect. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new Image(configuration, width, height); - data = data[..count]; - data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); + var image = new Image(configuration, width, height); + data = data[..count]; + data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); - return image; - } + return image; } } diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 9e68d901a6..0c9e148962 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -1,504 +1,502 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Adds static methods allowing wrapping an existing memory area as an image. +/// +public abstract partial class Image { - /// - /// Adds static methods allowing wrapping an existing memory area as an image. - /// - public abstract partial class Image + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type + /// The + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// The . + /// The configuration is null. + /// The metadata is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + Memory pixelMemory, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel { - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - Memory pixelMemory, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - Guard.IsTrue(pixelMemory.Length >= width * height, nameof(pixelMemory), "The length of the input memory is less than the specified image size"); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + Guard.IsTrue(pixelMemory.Length >= width * height, nameof(pixelMemory), "The length of the input memory is less than the specified image size"); - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemory); - return new Image(configuration, memorySource, width, height, metadata); - } + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemory); + return new Image(configuration, memorySource, width, height, metadata); + } - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static Image WrapMemory( - Configuration configuration, - Memory pixelMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata()); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type + /// The + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance. + public static Image WrapMemory( + Configuration configuration, + Memory pixelMemory, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The pixel memory. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - Memory pixelMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pixelMemory, width, height); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type. + /// The pixel memory. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + Memory pixelMemory, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemory, width, height); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The - /// The that is being transferred to the image - /// The width of the memory image. - /// The height of the memory image. - /// The - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner pixelMemoryOwner, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); - Guard.IsTrue(pixelMemoryOwner.Memory.Length >= width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The + /// The that is being transferred to the image + /// The width of the memory image. + /// The height of the memory image. + /// The + /// The configuration is null. + /// The metadata is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner pixelMemoryOwner, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); + Guard.IsTrue(pixelMemoryOwner.Memory.Length >= width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); - return new Image(configuration, memorySource, width, height, metadata); - } + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, metadata); + } - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner pixelMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type. + /// The + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pixelMemoryOwner, width, height, new ImageMetadata()); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - IMemoryOwner pixelMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + IMemoryOwner pixelMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - Memory byteMemory, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type + /// The + /// The byte memory representing the pixel data. + /// The width of the memory image. + /// The height of the memory image. + /// The . + /// The configuration is null. + /// The metadata is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + Memory byteMemory, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); - ByteMemoryManager memoryManager = new(byteMemory); + ByteMemoryManager memoryManager = new(byteMemory); - Guard.IsTrue(memoryManager.Memory.Length >= width * height, nameof(byteMemory), "The length of the input memory is less than the specified image size"); + Guard.IsTrue(memoryManager.Memory.Length >= width * height, nameof(byteMemory), "The length of the input memory is less than the specified image size"); - MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); - return new Image(configuration, memorySource, width, height, metadata); - } + MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); + return new Image(configuration, memorySource, width, height, metadata); + } - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type - /// The - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static Image WrapMemory( - Configuration configuration, - Memory byteMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, byteMemory, width, height, new ImageMetadata()); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type + /// The + /// The byte memory representing the pixel data. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance. + public static Image WrapMemory( + Configuration configuration, + Memory byteMemory, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, byteMemory, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: using this method does not transfer the ownership of the underlying buffer of the input - /// to the new instance. This means that consumers of this method must ensure that the input buffer - /// is either self-contained, (for example, a instance wrapping a new array that was - /// created), or that the owning object is not disposed until the returned is disposed. - /// - /// - /// If the input instance is one retrieved from an instance - /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still - /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other - /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. - /// - /// - /// The pixel type. - /// The byte memory representing the pixel data. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - Memory byteMemory, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, byteMemory, width, height); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: using this method does not transfer the ownership of the underlying buffer of the input + /// to the new instance. This means that consumers of this method must ensure that the input buffer + /// is either self-contained, (for example, a instance wrapping a new array that was + /// created), or that the owning object is not disposed until the returned is disposed. + /// + /// + /// If the input instance is one retrieved from an instance + /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still + /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other + /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately. + /// + /// + /// The pixel type. + /// The byte memory representing the pixel data. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + Memory byteMemory, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, byteMemory, width, height); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The - /// The that is being transferred to the image - /// The width of the memory image. - /// The height of the memory image. - /// The - /// The configuration is null. - /// The metadata is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner byteMemoryOwner, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The + /// The that is being transferred to the image + /// The width of the memory image. + /// The height of the memory image. + /// The + /// The configuration is null. + /// The metadata is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner byteMemoryOwner, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); - ByteMemoryOwner pixelMemoryOwner = new(byteMemoryOwner); + ByteMemoryOwner pixelMemoryOwner = new(byteMemoryOwner); - Guard.IsTrue(pixelMemoryOwner.Memory.Length >= (long)width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); + Guard.IsTrue(pixelMemoryOwner.Memory.Length >= (long)width * height, nameof(pixelMemoryOwner), "The length of the input memory is less than the specified image size"); - MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); - return new Image(configuration, memorySource, width, height, metadata); - } + MemoryGroup memorySource = MemoryGroup.Wrap(pixelMemoryOwner); + return new Image(configuration, memorySource, width, height, metadata); + } - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type. - /// The - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance - public static Image WrapMemory( - Configuration configuration, - IMemoryOwner byteMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, byteMemoryOwner, width, height, new ImageMetadata()); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type. + /// The + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance + public static Image WrapMemory( + Configuration configuration, + IMemoryOwner byteMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, byteMemoryOwner, width, height, new ImageMetadata()); - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, - /// allowing to view/manipulate it as an instance. - /// The ownership of the is being transferred to the new instance, - /// meaning that the caller is not allowed to dispose . - /// It will be disposed together with the result image. - /// - /// The pixel type - /// The that is being transferred to the image. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static Image WrapMemory( - IMemoryOwner byteMemoryOwner, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, byteMemoryOwner, width, height); + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels, + /// allowing to view/manipulate it as an instance. + /// The ownership of the is being transferred to the new instance, + /// meaning that the caller is not allowed to dispose . + /// It will be disposed together with the result image. + /// + /// The pixel type + /// The that is being transferred to the image. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static Image WrapMemory( + IMemoryOwner byteMemoryOwner, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, byteMemoryOwner, width, height); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type - /// The - /// The pointer to the target memory buffer to wrap. - /// The width of the memory image. - /// The height of the memory image. - /// The . - /// The configuration is null. - /// The metadata is null. - /// An instance - public static unsafe Image WrapMemory( - Configuration configuration, - void* pointer, - int width, - int height, - ImageMetadata metadata) - where TPixel : unmanaged, IPixel - { - Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null"); - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the + /// pointer and that the lifetime of such a memory area is at least equal to that of the returned + /// instance. For example, if the input pointer references an unmanaged memory area, + /// callers must ensure that the memory area is not freed as long as the returned is + /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers + /// must ensure that objects will remain pinned as long as the instance is in use. + /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. + /// + /// + /// Note also that if you have a or an array (which can be cast to ) of + /// either or values, it is highly recommended to use one of the other + /// available overloads of this method instead (such as + /// or , to make the resulting code less error + /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when + /// doing interop or working with buffers that are located in unmanaged memory. + /// + /// + /// The pixel type + /// The + /// The pointer to the target memory buffer to wrap. + /// The width of the memory image. + /// The height of the memory image. + /// The . + /// The configuration is null. + /// The metadata is null. + /// An instance + public static unsafe Image WrapMemory( + Configuration configuration, + void* pointer, + int width, + int height, + ImageMetadata metadata) + where TPixel : unmanaged, IPixel + { + Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null"); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); - UnmanagedMemoryManager memoryManager = new(pointer, width * height); + UnmanagedMemoryManager memoryManager = new(pointer, width * height); - MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); - return new Image(configuration, memorySource, width, height, metadata); - } + MemoryGroup memorySource = MemoryGroup.Wrap(memoryManager.Memory); + return new Image(configuration, memorySource, width, height, metadata); + } - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type - /// The - /// The pointer to the target memory buffer to wrap. - /// The width of the memory image. - /// The height of the memory image. - /// The configuration is null. - /// An instance. - public static unsafe Image WrapMemory( - Configuration configuration, - void* pointer, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(configuration, pointer, width, height, new ImageMetadata()); + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the + /// pointer and that the lifetime of such a memory area is at least equal to that of the returned + /// instance. For example, if the input pointer references an unmanaged memory area, + /// callers must ensure that the memory area is not freed as long as the returned is + /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers + /// must ensure that objects will remain pinned as long as the instance is in use. + /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. + /// + /// + /// Note also that if you have a or an array (which can be cast to ) of + /// either or values, it is highly recommended to use one of the other + /// available overloads of this method instead (such as + /// or , to make the resulting code less error + /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when + /// doing interop or working with buffers that are located in unmanaged memory. + /// + /// + /// The pixel type + /// The + /// The pointer to the target memory buffer to wrap. + /// The width of the memory image. + /// The height of the memory image. + /// The configuration is null. + /// An instance. + public static unsafe Image WrapMemory( + Configuration configuration, + void* pointer, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(configuration, pointer, width, height, new ImageMetadata()); - /// - /// - /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as - /// an instance. - /// - /// - /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the - /// pointer and that the lifetime of such a memory area is at least equal to that of the returned - /// instance. For example, if the input pointer references an unmanaged memory area, - /// callers must ensure that the memory area is not freed as long as the returned is - /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers - /// must ensure that objects will remain pinned as long as the instance is in use. - /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. - /// - /// - /// Note also that if you have a or an array (which can be cast to ) of - /// either or values, it is highly recommended to use one of the other - /// available overloads of this method instead (such as - /// or , to make the resulting code less error - /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when - /// doing interop or working with buffers that are located in unmanaged memory. - /// - /// - /// The pixel type. - /// The pointer to the target memory buffer to wrap. - /// The width of the memory image. - /// The height of the memory image. - /// An instance. - public static unsafe Image WrapMemory( - void* pointer, - int width, - int height) - where TPixel : unmanaged, IPixel - => WrapMemory(Configuration.Default, pointer, width, height); - } + /// + /// + /// Wraps an existing contiguous memory area of at least 'width' x 'height' pixels allowing viewing/manipulation as + /// an instance. + /// + /// + /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the + /// pointer and that the lifetime of such a memory area is at least equal to that of the returned + /// instance. For example, if the input pointer references an unmanaged memory area, + /// callers must ensure that the memory area is not freed as long as the returned is + /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers + /// must ensure that objects will remain pinned as long as the instance is in use. + /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes. + /// + /// + /// Note also that if you have a or an array (which can be cast to ) of + /// either or values, it is highly recommended to use one of the other + /// available overloads of this method instead (such as + /// or , to make the resulting code less error + /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when + /// doing interop or working with buffers that are located in unmanaged memory. + /// + /// + /// The pixel type. + /// The pointer to the target memory buffer to wrap. + /// The width of the memory image. + /// The height of the memory image. + /// An instance. + public static unsafe Image WrapMemory( + void* pointer, + int width, + int height) + where TPixel : unmanaged, IPixel + => WrapMemory(Configuration.Default, pointer, width, height); } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index fcc06703f0..91c96b55f6 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -1,208 +1,203 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. +/// For the non-generic type, the pixel type is only known at runtime. +/// is always implemented by a pixel-specific instance. +/// +public abstract partial class Image : IImage, IConfigurationProvider { + private bool isDisposed; + + private Size size; + private readonly Configuration configuration; + /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// For the non-generic type, the pixel type is only known at runtime. - /// is always implemented by a pixel-specific instance. + /// Initializes a new instance of the class. /// - public abstract partial class Image : IImage, IConfigurationProvider + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The . + /// The . + /// The . + protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) { - private bool isDisposed; - - private Size size; - private readonly Configuration configuration; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The configuration which allows altering default behaviour or extending the library. - /// - /// The . - /// The . - /// The . - protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata metadata, Size size) - { - this.configuration = configuration ?? Configuration.Default; - this.PixelType = pixelType; - this.size = size; - this.Metadata = metadata ?? new ImageMetadata(); - } + this.configuration = configuration ?? Configuration.Default; + this.PixelType = pixelType; + this.size = size; + this.Metadata = metadata ?? new ImageMetadata(); + } - /// - /// Initializes a new instance of the class. - /// - internal Image( - Configuration configuration, - PixelTypeInfo pixelType, - ImageMetadata metadata, - int width, - int height) - : this(configuration, pixelType, metadata, new Size(width, height)) - { - } + /// + /// Initializes a new instance of the class. + /// + internal Image( + Configuration configuration, + PixelTypeInfo pixelType, + ImageMetadata metadata, + int width, + int height) + : this(configuration, pixelType, metadata, new Size(width, height)) + { + } - /// - /// Gets the implementing the public property. - /// - protected abstract ImageFrameCollection NonGenericFrameCollection { get; } + /// + /// Gets the implementing the public property. + /// + protected abstract ImageFrameCollection NonGenericFrameCollection { get; } - /// - public PixelTypeInfo PixelType { get; } + /// + public PixelTypeInfo PixelType { get; } - /// - public int Width => this.size.Width; + /// + public int Width => this.size.Width; - /// - public int Height => this.size.Height; + /// + public int Height => this.size.Height; - /// - public ImageMetadata Metadata { get; } + /// + public ImageMetadata Metadata { get; } - /// - /// Gets the frames of the image as (non-generic) . - /// - public ImageFrameCollection Frames => this.NonGenericFrameCollection; + /// + /// Gets the frames of the image as (non-generic) . + /// + public ImageFrameCollection Frames => this.NonGenericFrameCollection; - /// - Configuration IConfigurationProvider.Configuration => this.configuration; + /// + Configuration IConfigurationProvider.Configuration => this.configuration; - /// - public void Dispose() + /// + public void Dispose() + { + if (this.isDisposed) { - if (this.isDisposed) - { - return; - } + return; + } - this.Dispose(true); - GC.SuppressFinalize(this); + this.Dispose(true); + GC.SuppressFinalize(this); - this.isDisposed = true; - } + this.isDisposed = true; + } - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream or encoder is null. - public void Save(Stream stream, IImageEncoder encoder) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - this.EnsureNotDisposed(); + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream or encoder is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + this.EnsureNotDisposed(); - this.AcceptVisitor(new EncodeVisitor(encoder, stream)); - } + this.AcceptVisitor(new EncodeVisitor(encoder, stream)); + } - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// Thrown if the stream or encoder is null. - /// A representing the asynchronous operation. - public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(encoder, nameof(encoder)); - this.EnsureNotDisposed(); + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream or encoder is null. + /// A representing the asynchronous operation. + public Task SaveAsync(Stream stream, IImageEncoder encoder, CancellationToken cancellationToken = default) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(encoder, nameof(encoder)); + this.EnsureNotDisposed(); - return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken); - } + return this.AcceptVisitorAsync(new EncodeVisitor(encoder, stream), cancellationToken); + } + + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The + public Image CloneAs() + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); + + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The . + public abstract Image CloneAs(Configuration configuration) + where TPixel2 : unmanaged, IPixel; + + /// + /// Update the size of the image after mutation. + /// + /// The . + protected void UpdateSize(Size size) => this.size = size; + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected abstract void Dispose(bool disposing); - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The - public Image CloneAs() - where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); - - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The . - public abstract Image CloneAs(Configuration configuration) - where TPixel2 : unmanaged, IPixel; - - /// - /// Update the size of the image after mutation. - /// - /// The . - protected void UpdateSize(Size size) => this.size = size; - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - - /// - /// Throws if the image is disposed. - /// - internal void EnsureNotDisposed() + /// + /// Throws if the image is disposed. + /// + internal void EnsureNotDisposed() + { + if (this.isDisposed) { - if (this.isDisposed) - { - ThrowObjectDisposedException(this.GetType()); - } + ThrowObjectDisposedException(this.GetType()); } + } - /// - /// Accepts a . - /// Implemented by invoking - /// with the pixel type of the image. - /// - /// The visitor. - internal abstract void Accept(IImageVisitor visitor); - - /// - /// Accepts a . - /// Implemented by invoking - /// with the pixel type of the image. - /// - /// The visitor. - /// The token to monitor for cancellation requests. - internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken); - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); - - private class EncodeVisitor : IImageVisitor, IImageVisitorAsync - { - private readonly IImageEncoder encoder; + /// + /// Accepts a . + /// Implemented by invoking + /// with the pixel type of the image. + /// + /// The visitor. + internal abstract void Accept(IImageVisitor visitor); - private readonly Stream stream; + /// + /// Accepts a . + /// Implemented by invoking + /// with the pixel type of the image. + /// + /// The visitor. + /// The token to monitor for cancellation requests. + internal abstract Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken); - public EncodeVisitor(IImageEncoder encoder, Stream stream) - { - this.encoder = encoder; - this.stream = stream; - } + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); + + private class EncodeVisitor : IImageVisitor, IImageVisitorAsync + { + private readonly IImageEncoder encoder; - public void Visit(Image image) - where TPixel : unmanaged, IPixel => this.encoder.Encode(image, this.stream); + private readonly Stream stream; - public Task VisitAsync(Image image, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel => this.encoder.EncodeAsync(image, this.stream, cancellationToken); + public EncodeVisitor(IImageEncoder encoder, Stream stream) + { + this.encoder = encoder; + this.stream = stream; } + + public void Visit(Image image) + where TPixel : unmanaged, IPixel => this.encoder.Encode(image, this.stream); + + public Task VisitAsync(Image image, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => this.encoder.EncodeAsync(image, this.stream, cancellationToken); } } diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs index 2b522cd3f0..76c9b85224 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -4,28 +4,27 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Contains internal extensions for +/// +public static partial class ImageExtensions { - /// - /// Contains internal extensions for - /// - public static partial class ImageExtensions + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The type of the pixel. + /// The image. + /// + /// The + /// + internal static Buffer2D GetRootFramePixelBuffer(this Image image) + where TPixel : unmanaged, IPixel { - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The type of the pixel. - /// The image. - /// - /// The - /// - internal static Buffer2D GetRootFramePixelBuffer(this Image image) - where TPixel : unmanaged, IPixel - { - return image.Frames.RootFrame.PixelBuffer; - } + return image.Frames.RootFrame.PixelBuffer; } } diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index 9427c8d616..e79fceeaa0 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -1,194 +1,188 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class ImageExtensions { /// - /// Extension methods for the type. + /// Writes the image to the given file path using an encoder detected from the path. + /// + /// The source image. + /// The file path to save the image to. + /// The path is null. + /// No encoder available for provided path. + public static void Save(this Image source, string path) + => source.Save(path, source.DetectEncoder(path)); + + /// + /// Writes the image to the given file path using an encoder detected from the path. + /// + /// The source image. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// The path is null. + /// No encoder available for provided path. + /// A representing the asynchronous operation. + public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default) + => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); + + /// + /// Writes the image to the given file path using the given image encoder. /// - public static partial class ImageExtensions + /// The source image. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The path is null. + /// The encoder is null. + public static void Save(this Image source, string path, IImageEncoder encoder) { - /// - /// Writes the image to the given file path using an encoder detected from the path. - /// - /// The source image. - /// The file path to save the image to. - /// The path is null. - /// No encoder available for provided path. - public static void Save(this Image source, string path) - => source.Save(path, source.DetectEncoder(path)); - - /// - /// Writes the image to the given file path using an encoder detected from the path. - /// - /// The source image. - /// The file path to save the image to. - /// The token to monitor for cancellation requests. - /// The path is null. - /// No encoder available for provided path. - /// A representing the asynchronous operation. - public static Task SaveAsync(this Image source, string path, CancellationToken cancellationToken = default) - => source.SaveAsync(path, source.DetectEncoder(path), cancellationToken); - - /// - /// Writes the image to the given file path using the given image encoder. - /// - /// The source image. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The path is null. - /// The encoder is null. - public static void Save(this Image source, string path, IImageEncoder encoder) + Guard.NotNull(path, nameof(path)); + Guard.NotNull(encoder, nameof(encoder)); + using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(encoder, nameof(encoder)); - using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) - { - source.Save(fs, encoder); - } + source.Save(fs, encoder); } + } - /// - /// Writes the image to the given file path using the given image encoder. - /// - /// The source image. - /// The file path to save the image to. - /// The encoder to save the image with. - /// The token to monitor for cancellation requests. - /// The path is null. - /// The encoder is null. - /// A representing the asynchronous operation. - public static async Task SaveAsync( - this Image source, - string path, - IImageEncoder encoder, - CancellationToken cancellationToken = default) - { - Guard.NotNull(path, nameof(path)); - Guard.NotNull(encoder, nameof(encoder)); + /// + /// Writes the image to the given file path using the given image encoder. + /// + /// The source image. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// The path is null. + /// The encoder is null. + /// A representing the asynchronous operation. + public static async Task SaveAsync( + this Image source, + string path, + IImageEncoder encoder, + CancellationToken cancellationToken = default) + { + Guard.NotNull(path, nameof(path)); + Guard.NotNull(encoder, nameof(encoder)); - using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) - { - await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); - } + using (Stream fs = source.GetConfiguration().FileSystem.Create(path)) + { + await source.SaveAsync(fs, encoder, cancellationToken).ConfigureAwait(false); } + } - /// - /// Writes the image to the given stream using the given image format. - /// - /// The source image. - /// The stream to save the image to. - /// The format to save the image in. - /// The stream is null. - /// The format is null. - /// The stream is not writable. - /// No encoder available for provided format. - public static void Save(this Image source, Stream stream, IImageFormat format) + /// + /// Writes the image to the given stream using the given image format. + /// + /// The source image. + /// The stream to save the image to. + /// The format to save the image in. + /// The stream is null. + /// The format is null. + /// The stream is not writable. + /// No encoder available for provided format. + public static void Save(this Image source, Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(format, nameof(format)); + + if (!stream.CanWrite) { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(format, nameof(format)); + throw new NotSupportedException("Cannot write to the stream."); + } - if (!stream.CanWrite) - { - throw new NotSupportedException("Cannot write to the stream."); - } + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); - IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + if (encoder is null) + { + StringBuilder sb = new(); + sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); - if (encoder is null) + foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { - StringBuilder sb = new(); - sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); - - foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } - - throw new NotSupportedException(sb.ToString()); + sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } - source.Save(stream, encoder); + throw new NotSupportedException(sb.ToString()); } - /// - /// Writes the image to the given stream using the given image format. - /// - /// The source image. - /// The stream to save the image to. - /// The format to save the image in. - /// The token to monitor for cancellation requests. - /// The stream is null. - /// The format is null. - /// The stream is not writable. - /// No encoder available for provided format. - /// A representing the asynchronous operation. - public static Task SaveAsync( - this Image source, - Stream stream, - IImageFormat format, - CancellationToken cancellationToken = default) - { - Guard.NotNull(stream, nameof(stream)); - Guard.NotNull(format, nameof(format)); + source.Save(stream, encoder); + } - if (!stream.CanWrite) - { - throw new NotSupportedException("Cannot write to the stream."); - } + /// + /// Writes the image to the given stream using the given image format. + /// + /// The source image. + /// The stream to save the image to. + /// The format to save the image in. + /// The token to monitor for cancellation requests. + /// The stream is null. + /// The format is null. + /// The stream is not writable. + /// No encoder available for provided format. + /// A representing the asynchronous operation. + public static Task SaveAsync( + this Image source, + Stream stream, + IImageFormat format, + CancellationToken cancellationToken = default) + { + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(format, nameof(format)); - IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); + if (!stream.CanWrite) + { + throw new NotSupportedException("Cannot write to the stream."); + } - if (encoder is null) - { - StringBuilder sb = new(); - sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); - foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); - } + if (encoder is null) + { + StringBuilder sb = new(); + sb.AppendLine("No encoder was found for the provided mime type. Registered encoders include:"); - throw new NotSupportedException(sb.ToString()); + foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " - {0} : {1}{2}", val.Key.Name, val.Value.GetType().Name, Environment.NewLine); } - return source.SaveAsync(stream, encoder, cancellationToken); + throw new NotSupportedException(sb.ToString()); } - /// - /// Returns a Base64 encoded string from the given image. - /// The result is prepended with a Data URI - /// - /// - /// For example: - /// - /// - /// - /// - /// The source image - /// The format. - /// The format is null. - /// The - public static string ToBase64String(this Image source, IImageFormat format) - { - Guard.NotNull(format, nameof(format)); + return source.SaveAsync(stream, encoder, cancellationToken); + } - using MemoryStream stream = new(); - source.Save(stream, format); + /// + /// Returns a Base64 encoded string from the given image. + /// The result is prepended with a Data URI + /// + /// + /// For example: + /// + /// + /// + /// + /// The source image + /// The format. + /// The format is null. + /// The + public static string ToBase64String(this Image source, IImageFormat format) + { + Guard.NotNull(format, nameof(format)); - // Always available. - stream.TryGetBuffer(out ArraySegment buffer); - return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array, 0, (int)stream.Length)}"; - } + using MemoryStream stream = new(); + source.Save(stream, format); + + // Always available. + stream.TryGetBuffer(out ArraySegment buffer); + return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(buffer.Array, 0, (int)stream.Length)}"; } } diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index b1f2b14bdc..61f4c0ea9d 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -1,53 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Contains methods for loading raw pixel data. +/// +public partial class ImageFrame { - /// - /// Contains methods for loading raw pixel data. - /// - public partial class ImageFrame - { - /// - /// Create a new instance of the class from the given byte array in format. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The byte array containing image data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// A new . - internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); - /// - /// Create a new instance of the class from the raw data. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The Span containing the image Pixel data. - /// The width of the final image. - /// The height of the final image. - /// The pixel format. - /// A new . - internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : unmanaged, IPixel - { - int count = width * height; - Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + /// + /// Create a new instance of the class from the raw data. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) + where TPixel : unmanaged, IPixel + { + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new ImageFrame(configuration, width, height); + var image = new ImageFrame(configuration, width, height); - data = data[..count]; - data.CopyTo(image.PixelBuffer.FastMemoryGroup); + data = data[..count]; + data.CopyTo(image.PixelBuffer.FastMemoryGroup); - return image; - } + return image; } } diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index f15fef4dc9..1e5d40385c 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a pixel-agnostic image frame containing all pixel data and . +/// In case of animated formats like gif, it contains the single frame in a animation. +/// In all other cases it is the only frame of the image. +/// +public abstract partial class ImageFrame : IConfigurationProvider, IDisposable { + private readonly Configuration configuration; + /// - /// Represents a pixel-agnostic image frame containing all pixel data and . - /// In case of animated formats like gif, it contains the single frame in a animation. - /// In all other cases it is the only frame of the image. + /// Initializes a new instance of the class. /// - public abstract partial class ImageFrame : IConfigurationProvider, IDisposable + /// The configuration which allows altering default behaviour or extending the library. + /// The frame width. + /// The frame height. + /// The . + protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) { - private readonly Configuration configuration; - - /// - /// 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 . - protected ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(metadata, nameof(metadata)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(metadata, nameof(metadata)); - this.configuration = configuration ?? Configuration.Default; - this.Width = width; - this.Height = height; - this.Metadata = metadata; - } + this.configuration = configuration ?? Configuration.Default; + this.Width = width; + this.Height = height; + this.Metadata = metadata; + } - /// - /// Gets the width. - /// - public int Width { get; private set; } + /// + /// Gets the width. + /// + public int Width { get; private set; } - /// - /// Gets the height. - /// - public int Height { get; private set; } + /// + /// Gets the height. + /// + public int Height { get; private set; } - /// - /// Gets the metadata of the frame. - /// - public ImageFrameMetadata Metadata { get; } + /// + /// Gets the metadata of the frame. + /// + public ImageFrameMetadata Metadata { get; } - /// - Configuration IConfigurationProvider.Configuration => this.configuration; + /// + Configuration IConfigurationProvider.Configuration => this.configuration; - /// - /// Gets the size of the frame. - /// - /// The - public Size Size() => new Size(this.Width, this.Height); + /// + /// Gets the size of the frame. + /// + /// The + public Size Size() => new Size(this.Width, this.Height); - /// - /// Gets the bounds of the frame. - /// - /// The - public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); + /// + /// Gets the bounds of the frame. + /// + /// The + public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected abstract void Dispose(bool disposing); - internal abstract void CopyPixelsTo(MemoryGroup destination) - where TDestinationPixel : unmanaged, IPixel; + internal abstract void CopyPixelsTo(MemoryGroup destination) + where TDestinationPixel : unmanaged, IPixel; - /// - /// Updates the size of the image frame. - /// - internal void UpdateSize(Size size) - { - this.Width = size.Width; - this.Height = size.Height; - } + /// + /// Updates the size of the image frame. + /// + internal void UpdateSize(Size size) + { + this.Width = size.Width; + this.Height = size.Height; } } diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 2a5179c1b0..1632134260 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -1,270 +1,267 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections; -using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Encapsulates a pixel-agnostic collection of instances +/// that make up an . +/// +public abstract class ImageFrameCollection : IDisposable, IEnumerable { + private bool isDisposed; + /// - /// Encapsulates a pixel-agnostic collection of instances - /// that make up an . + /// Gets the number of frames. /// - public abstract class ImageFrameCollection : IDisposable, IEnumerable - { - private bool isDisposed; - - /// - /// Gets the number of frames. - /// - public abstract int Count { get; } + public abstract int Count { get; } - /// - /// Gets the root frame. - /// - public ImageFrame RootFrame + /// + /// Gets the root frame. + /// + public ImageFrame RootFrame + { + get { - get - { - this.EnsureNotDisposed(); + this.EnsureNotDisposed(); - return this.NonGenericRootFrame; - } + return this.NonGenericRootFrame; } + } - /// - /// Gets the root frame. (Implements .) - /// - protected abstract ImageFrame NonGenericRootFrame { get; } - - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// The at the specified index. - public ImageFrame this[int index] - { - get - { - this.EnsureNotDisposed(); - - return this.NonGenericGetFrame(index); - } - } + /// + /// Gets the root frame. (Implements .) + /// + protected abstract ImageFrame NonGenericRootFrame { get; } - /// - /// Determines the index of a specific in the . - /// - /// The to locate in the . - /// The index of item if found in the list; otherwise, -1. - public abstract int IndexOf(ImageFrame frame); - - /// - /// Clones and inserts the into the at the specified . - /// - /// The zero-based index to insert the frame at. - /// The to clone and insert into the . - /// Frame must have the same dimensions as the image. - /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) + /// + /// Gets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// The at the specified index. + public ImageFrame this[int index] + { + get { this.EnsureNotDisposed(); - return this.NonGenericInsertFrame(index, source); + return this.NonGenericGetFrame(index); } + } - /// - /// Clones the frame and appends the clone to the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The cloned . - public ImageFrame AddFrame(ImageFrame source) - { - this.EnsureNotDisposed(); + /// + /// Determines the index of a specific in the . + /// + /// The to locate in the . + /// The index of item if found in the list; otherwise, -1. + public abstract int IndexOf(ImageFrame frame); - return this.NonGenericAddFrame(source); - } + /// + /// Clones and inserts the into the at the specified . + /// + /// The zero-based index to insert the frame at. + /// The to clone and insert into the . + /// Frame must have the same dimensions as the image. + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) + { + this.EnsureNotDisposed(); - /// - /// Removes the frame at the specified index and frees all freeable resources associated with it. - /// - /// The zero-based index of the frame to remove. - /// Cannot remove last frame. - public abstract void RemoveFrame(int index); - - /// - /// Determines whether the contains the . - /// - /// The frame. - /// - /// true if the contains the specified frame; otherwise, false. - /// - public abstract bool Contains(ImageFrame frame); - - /// - /// Moves an from to . - /// - /// The zero-based index of the frame to move. - /// The index to move the frame to. - public abstract void MoveFrame(int sourceIndex, int destinationIndex); - - /// - /// Removes the frame at the specified index and creates a new image with only the removed frame - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to export. - /// Cannot remove last frame. - /// The new with the specified frame. - public Image ExportFrame(int index) - { - this.EnsureNotDisposed(); + return this.NonGenericInsertFrame(index, source); + } - return this.NonGenericExportFrame(index); - } + /// + /// Clones the frame and appends the clone to the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The cloned . + public ImageFrame AddFrame(ImageFrame source) + { + this.EnsureNotDisposed(); - /// - /// Creates an with only the frame at the specified index - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to clone. - /// The new with the specified frame. - public Image CloneFrame(int index) - { - this.EnsureNotDisposed(); + return this.NonGenericAddFrame(source); + } - return this.NonGenericCloneFrame(index); - } + /// + /// Removes the frame at the specified index and frees all freeable resources associated with it. + /// + /// The zero-based index of the frame to remove. + /// Cannot remove last frame. + public abstract void RemoveFrame(int index); - /// - /// Creates a new and appends it to the end of the collection. - /// - /// - /// The new . - /// - public ImageFrame CreateFrame() - { - this.EnsureNotDisposed(); + /// + /// Determines whether the contains the . + /// + /// The frame. + /// + /// true if the contains the specified frame; otherwise, false. + /// + public abstract bool Contains(ImageFrame frame); - return this.NonGenericCreateFrame(); - } + /// + /// Moves an from to . + /// + /// The zero-based index of the frame to move. + /// The index to move the frame to. + public abstract void MoveFrame(int sourceIndex, int destinationIndex); - /// - /// Creates a new and appends it to the end of the collection. - /// - /// The background color to initialize the pixels with. - /// - /// The new . - /// - public ImageFrame CreateFrame(Color backgroundColor) - { - this.EnsureNotDisposed(); + /// + /// Removes the frame at the specified index and creates a new image with only the removed frame + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to export. + /// Cannot remove last frame. + /// The new with the specified frame. + public Image ExportFrame(int index) + { + this.EnsureNotDisposed(); - return this.NonGenericCreateFrame(backgroundColor); - } + return this.NonGenericExportFrame(index); + } - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } + /// + /// Creates an with only the frame at the specified index + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to clone. + /// The new with the specified frame. + public Image CloneFrame(int index) + { + this.EnsureNotDisposed(); - this.Dispose(true); - GC.SuppressFinalize(this); + return this.NonGenericCloneFrame(index); + } - this.isDisposed = true; - } + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public ImageFrame CreateFrame() + { + this.EnsureNotDisposed(); - /// - public IEnumerator GetEnumerator() - { - this.EnsureNotDisposed(); + return this.NonGenericCreateFrame(); + } - return this.NonGenericGetEnumerator(); - } + /// + /// Creates a new and appends it to the end of the collection. + /// + /// The background color to initialize the pixels with. + /// + /// The new . + /// + public ImageFrame CreateFrame(Color backgroundColor) + { + this.EnsureNotDisposed(); - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + return this.NonGenericCreateFrame(backgroundColor); + } - /// - /// Throws if the image frame is disposed. - /// - protected void EnsureNotDisposed() + /// + public void Dispose() + { + if (this.isDisposed) { - if (this.isDisposed) - { - ThrowObjectDisposedException(this.GetType()); - } + return; } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose of managed and unmanaged objects. - protected abstract void Dispose(bool disposing); - - /// - /// Implements . - /// - /// The enumerator. - protected abstract IEnumerator NonGenericGetEnumerator(); - - /// - /// Implements the getter of the indexer. - /// - /// The index. - /// The frame. - protected abstract ImageFrame NonGenericGetFrame(int index); - - /// - /// Implements . - /// - /// The index. - /// The frame. - /// The new frame. - protected abstract ImageFrame NonGenericInsertFrame(int index, ImageFrame source); - - /// - /// Implements . - /// - /// The frame. - /// The new frame. - protected abstract ImageFrame NonGenericAddFrame(ImageFrame source); - - /// - /// Implements . - /// - /// The index. - /// The new image. - protected abstract Image NonGenericExportFrame(int index); - - /// - /// Implements . - /// - /// The index. - /// The new image. - protected abstract Image NonGenericCloneFrame(int index); - - /// - /// Implements . - /// - /// The new frame. - protected abstract ImageFrame NonGenericCreateFrame(); - - /// - /// Implements . - /// - /// The background color. - /// The new frame. - protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); + this.Dispose(true); + GC.SuppressFinalize(this); + + this.isDisposed = true; + } + + /// + public IEnumerator GetEnumerator() + { + this.EnsureNotDisposed(); + + return this.NonGenericGetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + /// + /// Throws if the image frame is disposed. + /// + protected void EnsureNotDisposed() + { + if (this.isDisposed) + { + ThrowObjectDisposedException(this.GetType()); + } } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected abstract void Dispose(bool disposing); + + /// + /// Implements . + /// + /// The enumerator. + protected abstract IEnumerator NonGenericGetEnumerator(); + + /// + /// Implements the getter of the indexer. + /// + /// The index. + /// The frame. + protected abstract ImageFrame NonGenericGetFrame(int index); + + /// + /// Implements . + /// + /// The index. + /// The frame. + /// The new frame. + protected abstract ImageFrame NonGenericInsertFrame(int index, ImageFrame source); + + /// + /// Implements . + /// + /// The frame. + /// The new frame. + protected abstract ImageFrame NonGenericAddFrame(ImageFrame source); + + /// + /// Implements . + /// + /// The index. + /// The new image. + protected abstract Image NonGenericExportFrame(int index); + + /// + /// Implements . + /// + /// The index. + /// The new image. + protected abstract Image NonGenericCloneFrame(int index); + + /// + /// Implements . + /// + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(); + + /// + /// Implements . + /// + /// The background color. + /// The new frame. + protected abstract ImageFrame NonGenericCreateFrame(Color backgroundColor); + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException(Type type) => throw new ObjectDisposedException(type.Name); } diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 9736a6edb3..60b5c6d6ad 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -1,421 +1,418 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections; -using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp -{ - /// - /// Encapsulates a pixel-specific collection of instances - /// that make up an . - /// - /// The type of the pixel. - public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> - where TPixel : unmanaged, IPixel - { - private readonly IList> frames = new List>(); - private readonly Image parent; - - internal ImageFrameCollection(Image parent, int width, int height, TPixel backgroundColor) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); +namespace SixLabors.ImageSharp; - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); - } - - internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) - { - this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); +/// +/// Encapsulates a pixel-specific collection of instances +/// that make up an . +/// +/// The type of the pixel. +public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> + where TPixel : unmanaged, IPixel +{ + private readonly IList> frames = new List>(); + private readonly Image parent; - // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, memorySource)); - } + internal ImageFrameCollection(Image parent, int width, int height, TPixel backgroundColor) + { + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - internal ImageFrameCollection(Image parent, IEnumerable> frames) - { - Guard.NotNull(parent, nameof(parent)); - Guard.NotNull(frames, nameof(frames)); + // Frames are already cloned within the caller + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); + } - this.parent = parent; + internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) + { + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); - // Frames are already cloned by the caller - foreach (ImageFrame f in frames) - { - this.ValidateFrame(f); - this.frames.Add(f); - } + // Frames are already cloned within the caller + this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, memorySource)); + } - // Ensure at least 1 frame was added to the frames collection - if (this.frames.Count == 0) - { - throw new ArgumentException("Must not be empty.", nameof(frames)); - } - } + internal ImageFrameCollection(Image parent, IEnumerable> frames) + { + Guard.NotNull(parent, nameof(parent)); + Guard.NotNull(frames, nameof(frames)); - /// - /// Gets the number of frames. - /// - public override int Count => this.frames.Count; + this.parent = parent; - /// - /// Gets the root frame. - /// - public new ImageFrame RootFrame + // Frames are already cloned by the caller + foreach (ImageFrame f in frames) { - get - { - this.EnsureNotDisposed(); - - // frame collection would always contain at least 1 frame - // the only exception is when collection is disposed what is checked via EnsureNotDisposed() call - return this.frames[0]; - } + this.ValidateFrame(f); + this.frames.Add(f); } - /// - /// Gets root frame accessor in unsafe manner without any checks. - /// - /// - /// This property is most likely to be called from for indexing pixels. - /// already checks if it was disposed before querying for root frame. - /// - internal ImageFrame RootFrameUnsafe => this.frames[0]; - - /// - protected override ImageFrame NonGenericRootFrame => this.RootFrame; - - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// The at the specified index. - public new ImageFrame this[int index] + // Ensure at least 1 frame was added to the frames collection + if (this.frames.Count == 0) { - get - { - this.EnsureNotDisposed(); - - return this.frames[index]; - } + throw new ArgumentException("Must not be empty.", nameof(frames)); } + } + + /// + /// Gets the number of frames. + /// + public override int Count => this.frames.Count; - /// - public override int IndexOf(ImageFrame frame) + /// + /// Gets the root frame. + /// + public new ImageFrame RootFrame + { + get { this.EnsureNotDisposed(); - return frame is ImageFrame specific ? this.frames.IndexOf(specific) : -1; + // frame collection would always contain at least 1 frame + // the only exception is when collection is disposed what is checked via EnsureNotDisposed() call + return this.frames[0]; } + } - /// - /// Determines the index of a specific in the . - /// - /// The to locate in the . - /// The index of item if found in the list; otherwise, -1. - public int IndexOf(ImageFrame frame) - { - this.EnsureNotDisposed(); + /// + /// Gets root frame accessor in unsafe manner without any checks. + /// + /// + /// This property is most likely to be called from for indexing pixels. + /// already checks if it was disposed before querying for root frame. + /// + internal ImageFrame RootFrameUnsafe => this.frames[0]; - return this.frames.IndexOf(frame); - } + /// + protected override ImageFrame NonGenericRootFrame => this.RootFrame; - /// - /// Clones and inserts the into the at the specified . - /// - /// The zero-based index to insert the frame at. - /// The to clone and insert into the . - /// Frame must have the same dimensions as the image. - /// The cloned . - public ImageFrame InsertFrame(int index, ImageFrame source) + /// + /// Gets the at the specified index. + /// + /// + /// The . + /// + /// The index. + /// The at the specified index. + public new ImageFrame this[int index] + { + get { this.EnsureNotDisposed(); - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); - this.frames.Insert(index, clonedFrame); - return clonedFrame; + return this.frames[index]; } + } - /// - /// Clones the frame and appends the clone to the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The cloned . - public ImageFrame AddFrame(ImageFrame source) - { - this.EnsureNotDisposed(); + /// + public override int IndexOf(ImageFrame frame) + { + this.EnsureNotDisposed(); - this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); - this.frames.Add(clonedFrame); - return clonedFrame; - } + return frame is ImageFrame specific ? this.frames.IndexOf(specific) : -1; + } - /// - /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the - /// new frame at the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The new . - public ImageFrame AddFrame(ReadOnlySpan source) - { - this.EnsureNotDisposed(); + /// + /// Determines the index of a specific in the . + /// + /// The to locate in the . + /// The index of item if found in the list; otherwise, -1. + public int IndexOf(ImageFrame frame) + { + this.EnsureNotDisposed(); - var frame = ImageFrame.LoadPixelData( - this.parent.GetConfiguration(), - source, - this.RootFrame.Width, - this.RootFrame.Height); - this.frames.Add(frame); - return frame; - } + return this.frames.IndexOf(frame); + } - /// - /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the - /// new frame at the end of the collection. - /// - /// The raw pixel data to generate the from. - /// The new . - public ImageFrame AddFrame(TPixel[] source) - { - Guard.NotNull(source, nameof(source)); + /// + /// Clones and inserts the into the at the specified . + /// + /// The zero-based index to insert the frame at. + /// The to clone and insert into the . + /// Frame must have the same dimensions as the image. + /// The cloned . + public ImageFrame InsertFrame(int index, ImageFrame source) + { + this.EnsureNotDisposed(); - return this.AddFrame(source.AsSpan()); - } + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Insert(index, clonedFrame); + return clonedFrame; + } - /// - /// Removes the frame at the specified index and frees all freeable resources associated with it. - /// - /// The zero-based index of the frame to remove. - /// Cannot remove last frame. - public override void RemoveFrame(int index) - { - this.EnsureNotDisposed(); + /// + /// Clones the frame and appends the clone to the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The cloned . + public ImageFrame AddFrame(ImageFrame source) + { + this.EnsureNotDisposed(); - if (index == 0 && this.Count == 1) - { - throw new InvalidOperationException("Cannot remove last frame."); - } + this.ValidateFrame(source); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); + this.frames.Add(clonedFrame); + return clonedFrame; + } - ImageFrame frame = this.frames[index]; - this.frames.RemoveAt(index); - frame.Dispose(); - } + /// + /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the + /// new frame at the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(ReadOnlySpan source) + { + this.EnsureNotDisposed(); + + var frame = ImageFrame.LoadPixelData( + this.parent.GetConfiguration(), + source, + this.RootFrame.Width, + this.RootFrame.Height); + this.frames.Add(frame); + return frame; + } - /// - public override bool Contains(ImageFrame frame) - { - this.EnsureNotDisposed(); + /// + /// Creates a new frame from the pixel data with the same dimensions as the other frames and inserts the + /// new frame at the end of the collection. + /// + /// The raw pixel data to generate the from. + /// The new . + public ImageFrame AddFrame(TPixel[] source) + { + Guard.NotNull(source, nameof(source)); - return frame is ImageFrame specific && this.frames.Contains(specific); - } + return this.AddFrame(source.AsSpan()); + } - /// - /// Determines whether the contains the . - /// - /// The frame. - /// - /// true if the contains the specified frame; otherwise, false. - /// - public bool Contains(ImageFrame frame) - { - this.EnsureNotDisposed(); + /// + /// Removes the frame at the specified index and frees all freeable resources associated with it. + /// + /// The zero-based index of the frame to remove. + /// Cannot remove last frame. + public override void RemoveFrame(int index) + { + this.EnsureNotDisposed(); - return this.frames.Contains(frame); + if (index == 0 && this.Count == 1) + { + throw new InvalidOperationException("Cannot remove last frame."); } - /// - /// Moves an from to . - /// - /// The zero-based index of the frame to move. - /// The index to move the frame to. - public override void MoveFrame(int sourceIndex, int destinationIndex) - { - this.EnsureNotDisposed(); + ImageFrame frame = this.frames[index]; + this.frames.RemoveAt(index); + frame.Dispose(); + } - if (sourceIndex == destinationIndex) - { - return; - } + /// + public override bool Contains(ImageFrame frame) + { + this.EnsureNotDisposed(); - ImageFrame frameAtIndex = this.frames[sourceIndex]; - this.frames.RemoveAt(sourceIndex); - this.frames.Insert(destinationIndex, frameAtIndex); - } + return frame is ImageFrame specific && this.frames.Contains(specific); + } + + /// + /// Determines whether the contains the . + /// + /// The frame. + /// + /// true if the contains the specified frame; otherwise, false. + /// + public bool Contains(ImageFrame frame) + { + this.EnsureNotDisposed(); - /// - /// Removes the frame at the specified index and creates a new image with only the removed frame - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to export. - /// Cannot remove last frame. - /// The new with the specified frame. - public new Image ExportFrame(int index) + return this.frames.Contains(frame); + } + + /// + /// Moves an from to . + /// + /// The zero-based index of the frame to move. + /// The index to move the frame to. + public override void MoveFrame(int sourceIndex, int destinationIndex) + { + this.EnsureNotDisposed(); + + if (sourceIndex == destinationIndex) { - this.EnsureNotDisposed(); + return; + } - ImageFrame frame = this[index]; + ImageFrame frameAtIndex = this.frames[sourceIndex]; + this.frames.RemoveAt(sourceIndex); + this.frames.Insert(destinationIndex, frameAtIndex); + } - if (this.Count == 1 && this.frames.Contains(frame)) - { - throw new InvalidOperationException("Cannot remove last frame."); - } + /// + /// Removes the frame at the specified index and creates a new image with only the removed frame + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to export. + /// Cannot remove last frame. + /// The new with the specified frame. + public new Image ExportFrame(int index) + { + this.EnsureNotDisposed(); - this.frames.Remove(frame); + ImageFrame frame = this[index]; - return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); + if (this.Count == 1 && this.frames.Contains(frame)) + { + throw new InvalidOperationException("Cannot remove last frame."); } - /// - /// Creates an with only the frame at the specified index - /// with the same metadata as the original image. - /// - /// The zero-based index of the frame to clone. - /// The new with the specified frame. - public new Image CloneFrame(int index) - { - this.EnsureNotDisposed(); + this.frames.Remove(frame); - ImageFrame frame = this[index]; - ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); - } + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { frame }); + } - /// - /// Creates a new and appends it to the end of the collection. - /// - /// - /// The new . - /// - public new ImageFrame CreateFrame() - { - this.EnsureNotDisposed(); + /// + /// Creates an with only the frame at the specified index + /// with the same metadata as the original image. + /// + /// The zero-based index of the frame to clone. + /// The new with the specified frame. + public new Image CloneFrame(int index) + { + this.EnsureNotDisposed(); - var frame = new ImageFrame( - this.parent.GetConfiguration(), - this.RootFrame.Width, - this.RootFrame.Height); - this.frames.Add(frame); - return frame; - } + ImageFrame frame = this[index]; + ImageFrame clonedFrame = frame.Clone(); + return new Image(this.parent.GetConfiguration(), this.parent.Metadata.DeepClone(), new[] { clonedFrame }); + } - /// - protected override IEnumerator NonGenericGetEnumerator() => this.frames.GetEnumerator(); + /// + /// Creates a new and appends it to the end of the collection. + /// + /// + /// The new . + /// + public new ImageFrame CreateFrame() + { + this.EnsureNotDisposed(); + + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height); + this.frames.Add(frame); + return frame; + } - /// - protected override ImageFrame NonGenericGetFrame(int index) => this[index]; + /// + protected override IEnumerator NonGenericGetEnumerator() => this.frames.GetEnumerator(); - /// - protected override ImageFrame NonGenericInsertFrame(int index, ImageFrame source) - { - Guard.NotNull(source, nameof(source)); + /// + protected override ImageFrame NonGenericGetFrame(int index) => this[index]; - if (source is ImageFrame compatibleSource) - { - return this.InsertFrame(index, compatibleSource); - } + /// + protected override ImageFrame NonGenericInsertFrame(int index, ImageFrame source) + { + Guard.NotNull(source, nameof(source)); - ImageFrame result = this.CopyNonCompatibleFrame(source); - this.frames.Insert(index, result); - return result; + if (source is ImageFrame compatibleSource) + { + return this.InsertFrame(index, compatibleSource); } - /// - protected override ImageFrame NonGenericAddFrame(ImageFrame source) - { - Guard.NotNull(source, nameof(source)); + ImageFrame result = this.CopyNonCompatibleFrame(source); + this.frames.Insert(index, result); + return result; + } - if (source is ImageFrame compatibleSource) - { - return this.AddFrame(compatibleSource); - } + /// + protected override ImageFrame NonGenericAddFrame(ImageFrame source) + { + Guard.NotNull(source, nameof(source)); - ImageFrame result = this.CopyNonCompatibleFrame(source); - this.frames.Add(result); - return result; + if (source is ImageFrame compatibleSource) + { + return this.AddFrame(compatibleSource); } - /// - protected override Image NonGenericExportFrame(int index) => this.ExportFrame(index); + ImageFrame result = this.CopyNonCompatibleFrame(source); + this.frames.Add(result); + return result; + } - /// - protected override Image NonGenericCloneFrame(int index) => this.CloneFrame(index); + /// + protected override Image NonGenericExportFrame(int index) => this.ExportFrame(index); - /// - protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) => - this.CreateFrame(backgroundColor.ToPixel()); + /// + protected override Image NonGenericCloneFrame(int index) => this.CloneFrame(index); - /// - protected override ImageFrame NonGenericCreateFrame() => this.CreateFrame(); + /// + protected override ImageFrame NonGenericCreateFrame(Color backgroundColor) => + this.CreateFrame(backgroundColor.ToPixel()); - /// - /// Creates a new and appends it to the end of the collection. - /// - /// The background color to initialize the pixels with. - /// - /// The new . - /// - public ImageFrame CreateFrame(TPixel backgroundColor) - { - var frame = new ImageFrame( - this.parent.GetConfiguration(), - this.RootFrame.Width, - this.RootFrame.Height, - backgroundColor); - this.frames.Add(frame); - return frame; - } + /// + protected override ImageFrame NonGenericCreateFrame() => this.CreateFrame(); + + /// + /// Creates a new and appends it to the end of the collection. + /// + /// The background color to initialize the pixels with. + /// + /// The new . + /// + public ImageFrame CreateFrame(TPixel backgroundColor) + { + var frame = new ImageFrame( + this.parent.GetConfiguration(), + this.RootFrame.Width, + this.RootFrame.Height, + backgroundColor); + this.frames.Add(frame); + return frame; + } - /// - IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); + /// + IEnumerator> IEnumerable>.GetEnumerator() => this.frames.GetEnumerator(); - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this.frames).GetEnumerator(); - private void ValidateFrame(ImageFrame frame) - { - Guard.NotNull(frame, nameof(frame)); + private void ValidateFrame(ImageFrame frame) + { + Guard.NotNull(frame, nameof(frame)); - if (this.Count != 0) + if (this.Count != 0) + { + if (this.RootFrame.Width != frame.Width || this.RootFrame.Height != frame.Height) { - if (this.RootFrame.Width != frame.Width || this.RootFrame.Height != frame.Height) - { - throw new ArgumentException("Frame must have the same dimensions as the image.", nameof(frame)); - } + throw new ArgumentException("Frame must have the same dimensions as the image.", nameof(frame)); } } + } - /// - protected override void Dispose(bool disposing) + /// + protected override void Dispose(bool disposing) + { + if (disposing) { - if (disposing) + foreach (ImageFrame f in this.frames) { - foreach (ImageFrame f in this.frames) - { - f.Dispose(); - } - - this.frames.Clear(); + f.Dispose(); } - } - private ImageFrame CopyNonCompatibleFrame(ImageFrame source) - { - var result = new ImageFrame( - this.parent.GetConfiguration(), - source.Size(), - source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); - return result; + this.frames.Clear(); } } + + private ImageFrame CopyNonCompatibleFrame(ImageFrame source) + { + var result = new ImageFrame( + this.parent.GetConfiguration(), + source.Size(), + source.Metadata.DeepClone()); + source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); + return result; + } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 29877b9005..d8708135ef 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; @@ -10,480 +8,479 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a pixel-specific image frame containing all pixel data and . +/// In case of animated formats like gif, it contains the single frame in a animation. +/// In all other cases it is the only frame of the image. +/// +/// The pixel format. +public sealed class ImageFrame : ImageFrame, IPixelSource + where TPixel : unmanaged, IPixel { + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + internal ImageFrame(Configuration configuration, int width, int height) + : this(configuration, width, height, new ImageFrameMetadata()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The of the frame. + /// The metadata. + internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) + : this(configuration, size.Width, size.Height, metadata) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) + : base(configuration, width, height, metadata) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers, + AllocationOptions.Clean); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to clear the image with. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) + : base(configuration, width, height, metadata) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers); + this.Clear(backgroundColor); + } + + /// + /// Initializes a new instance of the class wrapping an existing buffer. + /// + /// The configuration providing initialization code which allows extending the library. + /// 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, MemoryGroup memorySource) + : this(configuration, width, height, memorySource, new ImageFrameMetadata()) + { + } + /// - /// Represents a pixel-specific image frame containing all pixel data and . - /// In case of animated formats like gif, it contains the single frame in a animation. - /// In all other cases it is the only frame of the image. + /// Initializes a new instance of the class wrapping an existing buffer. /// - /// The pixel format. - public sealed class ImageFrame : ImageFrame, IPixelSource - where TPixel : unmanaged, IPixel + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The memory source. + /// The metadata. + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) + : base(configuration, width, height, metadata) { - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - internal ImageFrame(Configuration configuration, int width, int height) - : this(configuration, width, height, new ImageFrameMetadata()) + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.PixelBuffer = new Buffer2D(memorySource, width, height); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The source. + internal ImageFrame(Configuration configuration, ImageFrame source) + : base(configuration, source.Width, source.Height, source.Metadata.DeepClone()) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + source.PixelBuffer.Width, + source.PixelBuffer.Height, + configuration.PreferContiguousImageBuffers); + source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); + } + + /// + public Buffer2D PixelBuffer { get; private set; } + + /// + /// Gets or sets the pixel at the specified position. + /// + /// 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(InliningOptions.ShortMethod)] + get { + this.VerifyCoords(x, y); + return this.PixelBuffer.GetElementUnsafe(x, y); } - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The of the frame. - /// The metadata. - internal ImageFrame(Configuration configuration, Size size, ImageFrameMetadata metadata) - : this(configuration, size.Width, size.Height, metadata) + [MethodImpl(InliningOptions.ShortMethod)] + set { + this.VerifyCoords(x, y); + this.PixelBuffer.GetElementUnsafe(x, y) = value; } + } + + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) + try { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers, - AllocationOptions.Clean); + var accessor = new PixelAccessor(this.PixelBuffer); + processPixels(accessor); } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to clear the image with. - internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor) - : this(configuration, width, height, backgroundColor, new ImageFrameMetadata()) + finally { + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); } + } + + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + public void ProcessPixelRows( + ImageFrame frame2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(processPixels, nameof(processPixels)); - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to clear the image with. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, TPixel backgroundColor, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( - width, - height, - configuration.PreferContiguousImageBuffers); - this.Clear(backgroundColor); + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + processPixels(accessor1, accessor2); } - - /// - /// Initializes a new instance of the class wrapping an existing buffer. - /// - /// The configuration providing initialization code which allows extending the library. - /// 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, MemoryGroup memorySource) - : this(configuration, width, height, memorySource, new ImageFrameMetadata()) + finally { + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); } + } - /// - /// Initializes a new instance of the class wrapping an existing buffer. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The memory source. - /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) - : base(configuration, width, height, metadata) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The third image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + /// The pixel type of the third image frame. + public void ProcessPixelRows( + ImageFrame frame2, + ImageFrame frame3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(frame3, nameof(frame3)); + Guard.NotNull(processPixels, nameof(processPixels)); - this.PixelBuffer = new Buffer2D(memorySource, width, height); - } + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source. - internal ImageFrame(Configuration configuration, ImageFrame source) - : base(configuration, source.Width, source.Height, source.Metadata.DeepClone()) + try { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( - source.PixelBuffer.Width, - source.PixelBuffer.Height, - configuration.PreferContiguousImageBuffers); - source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + var accessor3 = new PixelAccessor(frame3.PixelBuffer); + processPixels(accessor1, accessor2, accessor3); } - - /// - public Buffer2D PixelBuffer { get; private set; } - - /// - /// Gets or sets the pixel at the specified position. - /// - /// 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] + finally { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.VerifyCoords(x, y); - return this.PixelBuffer.GetElementUnsafe(x, y); - } - - [MethodImpl(InliningOptions.ShortMethod)] - set - { - this.VerifyCoords(x, y); - this.PixelBuffer.GetElementUnsafe(x, y) = value; - } + frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); } + } - /// - /// Execute to process image pixels in a safe and efficient manner. - /// - /// The defining the pixel operations. - public void ProcessPixelRows(PixelAccessorAction processPixels) - { - Guard.NotNull(processPixels, nameof(processPixels)); + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); - try - { - var accessor = new PixelAccessor(this.PixelBuffer); - processPixels(accessor); - } - finally - { - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - } + /// + /// 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. + /// + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). + /// + /// WARNING: Disposing or leaking the underlying image while still working with the 's + /// might lead to memory corruption. + /// + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) + { + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + memory = default; + return false; } - /// - /// Execute to process pixels of multiple image frames in a safe and efficient manner. - /// - /// The second image frame. - /// The defining the pixel operations. - /// The pixel type of the second image frame. - public void ProcessPixelRows( - ImageFrame frame2, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - { - Guard.NotNull(frame2, nameof(frame2)); - Guard.NotNull(processPixels, nameof(processPixels)); + memory = mg.Single(); + return true; + } - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + /// + /// Gets a reference to the pixel at the specified position. + /// + /// 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; - try - { - var accessor1 = new PixelAccessor(this.PixelBuffer); - var accessor2 = new PixelAccessor(frame2.PixelBuffer); - processPixels(accessor1, accessor2); - } - finally - { - frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - } + /// + /// Copies the pixels to a of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(Buffer2D target) + { + if (this.Size() != target.Size()) + { + throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - /// - /// Execute to process pixels of multiple image frames in a safe and efficient manner. - /// - /// The second image frame. - /// The third image frame. - /// The defining the pixel operations. - /// The pixel type of the second image frame. - /// The pixel type of the third image frame. - public void ProcessPixelRows( - ImageFrame frame2, - ImageFrame frame3, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel - { - Guard.NotNull(frame2, nameof(frame2)); - Guard.NotNull(frame3, nameof(frame3)); - Guard.NotNull(processPixels, nameof(processPixels)); + this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); + } - this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); - frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + /// + /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// + /// The pixel source. + internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) + { + Guard.NotNull(pixelSource, nameof(pixelSource)); - try - { - var accessor1 = new PixelAccessor(this.PixelBuffer); - var accessor2 = new PixelAccessor(frame2.PixelBuffer); - var accessor3 = new PixelAccessor(frame3.PixelBuffer); - processPixels(accessor1, accessor2, accessor3); - } - finally - { - frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); - } - } + Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); + this.UpdateSize(this.PixelBuffer.Size()); + } - /// - /// Copy image pixels to . - /// - /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); - - /// - /// Copy image pixels to . - /// - /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); - - /// - /// 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. - /// - /// To ensure the memory is contiguous, should be set - /// to true, preferably on a non-global configuration instance (not ). - /// - /// WARNING: Disposing or leaking the underlying image while still working with the 's - /// might lead to memory corruption. - /// - /// The referencing the image buffer. - /// The indicating the success. - public bool DangerousTryGetSinglePixelMemory(out Memory memory) + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) - { - memory = default; - return false; - } + return; + } - memory = mg.Single(); - return true; + if (disposing) + { + this.PixelBuffer?.Dispose(); + this.PixelBuffer = null; } - /// - /// Gets a reference to the pixel at the specified position. - /// - /// 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. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; - - /// - /// Copies the pixels to a of the same size. - /// - /// The target pixel buffer accessor. - internal void CopyTo(Buffer2D target) + this.isDisposed = true; + } + + internal override void CopyPixelsTo(MemoryGroup destination) + { + if (typeof(TPixel) == typeof(TDestinationPixel)) { - if (this.Size() != target.Size()) + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => { - throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); - } - - this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); + Span d1 = MemoryMarshal.Cast(d); + s.CopyTo(d1); + }); + return; } - /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. - /// - /// The pixel source. - internal void SwapOrCopyPixelsBufferFrom(ImageFrame pixelSource) - { - Guard.NotNull(pixelSource, nameof(pixelSource)); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) + => PixelOperations.Instance.To(this.GetConfiguration(), s, d)); + } - Buffer2D.SwapOrCopyContent(this.PixelBuffer, pixelSource.PixelBuffer); - this.UpdateSize(this.PixelBuffer.Size()); - } + /// + public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } + /// + /// Clones the current instance. + /// + /// The + internal ImageFrame Clone() => this.Clone(this.GetConfiguration()); - if (disposing) - { - this.PixelBuffer?.Dispose(); - this.PixelBuffer = null; - } + /// + /// Clones the current instance. + /// + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame Clone(Configuration configuration) => new ImageFrame(configuration, this); - this.isDisposed = true; - } + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The + internal ImageFrame CloneAs() + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); - internal override void CopyPixelsTo(MemoryGroup destination) + /// + /// Returns a copy of the image frame in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + internal ImageFrame CloneAs(Configuration configuration) + where TPixel2 : unmanaged, IPixel + { + if (typeof(TPixel2) == typeof(TPixel)) { - if (typeof(TPixel) == typeof(TDestinationPixel)) - { - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => - { - Span d1 = MemoryMarshal.Cast(d); - s.CopyTo(d1); - }); - return; - } - - this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) - => PixelOperations.Instance.To(this.GetConfiguration(), s, d)); + return this.Clone(configuration) as ImageFrame; } - /// - public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>({this.Width}x{this.Height})"; - - /// - /// Clones the current instance. - /// - /// The - internal ImageFrame Clone() => this.Clone(this.GetConfiguration()); - - /// - /// Clones the current instance. - /// - /// The configuration providing initialization code which allows extending the library. - /// The - internal ImageFrame Clone(Configuration configuration) => new ImageFrame(configuration, this); - - /// - /// Returns a copy of the image frame in the given pixel format. - /// - /// The pixel format. - /// The - internal ImageFrame CloneAs() - where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); - - /// - /// Returns a copy of the image frame in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The - internal ImageFrame CloneAs(Configuration configuration) - where TPixel2 : unmanaged, IPixel - { - if (typeof(TPixel2) == typeof(TPixel)) - { - return this.Clone(configuration) as ImageFrame; - } + var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this.PixelBuffer, target.PixelBuffer, configuration); - var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - var operation = new RowIntervalOperation(this.PixelBuffer, target.PixelBuffer, configuration); + ParallelRowIterator.IterateRowIntervals( + configuration, + this.Bounds(), + in operation); - ParallelRowIterator.IterateRowIntervals( - configuration, - this.Bounds(), - in operation); + return target; + } - return target; - } + /// + /// Clears the bitmap. + /// + /// The value to initialize the bitmap with. + internal void Clear(TPixel value) + { + MemoryGroup group = this.PixelBuffer.FastMemoryGroup; - /// - /// Clears the bitmap. - /// - /// The value to initialize the bitmap with. - internal void Clear(TPixel value) + if (value.Equals(default)) { - MemoryGroup group = this.PixelBuffer.FastMemoryGroup; - - if (value.Equals(default)) - { - group.Clear(); - } - else - { - group.Fill(value); - } + group.Clear(); } - - [MethodImpl(InliningOptions.ShortMethod)] - private void VerifyCoords(int x, int y) + else { - if ((uint)x >= (uint)this.Width) - { - ThrowArgumentOutOfRangeException(nameof(x)); - } + group.Fill(value); + } + } - if ((uint)y >= (uint)this.Height) - { - ThrowArgumentOutOfRangeException(nameof(y)); - } + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if ((uint)x >= (uint)this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); } - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowArgumentOutOfRangeException(string paramName) + if ((uint)y >= (uint)this.Height) { - throw new ArgumentOutOfRangeException(paramName); + 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 + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TPixel2 : unmanaged, IPixel + { + private readonly Buffer2D source; + private readonly Buffer2D target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Buffer2D source, + Buffer2D target, + Configuration configuration) { - private readonly Buffer2D source; - private readonly Buffer2D target; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Buffer2D source, - Buffer2D target, - Configuration configuration) - { - this.source = source; - this.target = target; - this.configuration = configuration; - } + this.source = source; + this.target = target; + this.configuration = configuration; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.source.DangerousGetRowSpan(y); - Span targetRow = this.target.DangerousGetRowSpan(y); - PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); - } + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.target.DangerousGetRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); } } } diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index f92a7dd11e..5142a91a68 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -4,38 +4,37 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Contains information about the image including dimensions, pixel type information and additional metadata +/// +internal sealed class ImageInfo : IImageInfo { /// - /// Contains information about the image including dimensions, pixel type information and additional metadata + /// Initializes a new instance of the class. /// - internal sealed class ImageInfo : IImageInfo + /// The image pixel type information. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata) { - /// - /// Initializes a new instance of the class. - /// - /// The image pixel type information. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The images metadata. - public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata metadata) - { - this.PixelType = pixelType; - this.Width = width; - this.Height = height; - this.Metadata = metadata; - } + this.PixelType = pixelType; + this.Width = width; + this.Height = height; + this.Metadata = metadata; + } - /// - public PixelTypeInfo PixelType { get; } + /// + public PixelTypeInfo PixelType { get; } - /// - public int Width { get; } + /// + public int Width { get; } - /// - public int Height { get; } + /// + public int Height { get; } - /// - public ImageMetadata Metadata { get; } - } + /// + public ImageMetadata Metadata { get; } } diff --git a/src/ImageSharp/ImageInfoExtensions.cs b/src/ImageSharp/ImageInfoExtensions.cs index 9ff7244f4a..9dfc892897 100644 --- a/src/ImageSharp/ImageInfoExtensions.cs +++ b/src/ImageSharp/ImageInfoExtensions.cs @@ -1,25 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods that allow the addition of geometry calculating methods to the type +/// +public static class ImageInfoExtensions { /// - /// Extension methods that allow the addition of geometry calculating methods to the type + /// Gets the bounds of the image. /// - public static class ImageInfoExtensions - { - /// - /// Gets the bounds of the image. - /// - /// The image info - /// The - public static Size Size(this IImageInfo info) => new Size(info.Width, info.Height); + /// The image info + /// The + public static Size Size(this IImageInfo info) => new Size(info.Width, info.Height); - /// - /// Gets the bounds of the image. - /// - /// The image info - /// The - public static Rectangle Bounds(this IImageInfo info) => new Rectangle(0, 0, info.Width, info.Height); - } + /// + /// Gets the bounds of the image. + /// + /// The image info + /// The + public static Rectangle Bounds(this IImageInfo info) => new Rectangle(0, 0, info.Width, info.Height); } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 96be06eb74..41ca9b9fa9 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -38,6 +38,7 @@ + diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 4cb4c4d2a6..814843013e 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -1,472 +1,466 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. +/// For generic -s the pixel type is known at compile time. +/// +/// The pixel format. +public sealed class Image : Image + where TPixel : unmanaged, IPixel { + private readonly ImageFrameCollection frames; + /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// For generic -s the pixel type is known at compile time. + /// Initializes a new instance of the class + /// with the height and the width of the image. /// - /// The pixel format. - public sealed class Image : Image - where TPixel : unmanaged, IPixel + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(Configuration configuration, int width, int height) + : this(configuration, width, height, new ImageMetadata()) { - private readonly ImageFrameCollection frames; - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - public Image(Configuration configuration, int width, int height) - : this(configuration, width, height, new ImageMetadata()) - { - } + } - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - public Image(Configuration configuration, int width, int height, TPixel backgroundColor) - : this(configuration, width, height, backgroundColor, new ImageMetadata()) - { - } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + public Image(Configuration configuration, int width, int height, TPixel backgroundColor) + : this(configuration, width, height, backgroundColor, new ImageMetadata()) + { + } - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - public Image(int width, int height, TPixel backgroundColor) - : this(Configuration.Default, width, height, backgroundColor, new ImageMetadata()) - { - } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + public Image(int width, int height, TPixel backgroundColor) + : this(Configuration.Default, width, height, backgroundColor, new ImageMetadata()) + { + } - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - public Image(int width, int height) - : this(Configuration.Default, width, height) - { - } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : this(Configuration.Default, width, height) + { + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + internal Image(Configuration configuration, int width, int height, ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + { + this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); + } + + /// + /// Initializes a new instance of the class + /// wrapping an external pixel bufferx. + /// + /// The configuration providing initialization code which allows extending the library. + /// Pixel buffer. + /// The images metadata. + internal Image( + Configuration configuration, + Buffer2D pixelBuffer, + ImageMetadata metadata) + : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) + { + } + + /// + /// Initializes a new instance of the class + /// wrapping an external . + /// + /// The configuration providing initialization code which allows extending the library. + /// The memory source. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + internal Image( + Configuration configuration, + MemoryGroup memoryGroup, + int width, + int height, + ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + { + this.frames = new ImageFrameCollection(this, width, height, memoryGroup); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + /// The images metadata. + internal Image( + Configuration configuration, + int width, + int height, + TPixel backgroundColor, + ImageMetadata metadata) + : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + { + this.frames = new ImageFrameCollection(this, width, height, backgroundColor); + } - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The images metadata. - internal Image(Configuration configuration, int width, int height, ImageMetadata metadata) - : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The configuration providing initialization code which allows extending the library. + /// The images metadata. + /// The frames that will be owned by this image instance. + internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) + : base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(frames)) + { + this.frames = new ImageFrameCollection(this, frames); + } + + /// + protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; + + /// + /// Gets the collection of image frames. + /// + public new ImageFrameCollection Frames + { + get { - this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); + this.EnsureNotDisposed(); + return this.frames; } + } - /// - /// Initializes a new instance of the class - /// wrapping an external pixel bufferx. - /// - /// The configuration providing initialization code which allows extending the library. - /// Pixel buffer. - /// The images metadata. - internal Image( - Configuration configuration, - Buffer2D pixelBuffer, - ImageMetadata metadata) - : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) + /// + /// Gets the root frame. + /// + private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe; + + /// + /// Gets or sets the pixel at the specified position. + /// + /// 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(InliningOptions.ShortMethod)] + get { + this.EnsureNotDisposed(); + + this.VerifyCoords(x, y); + return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y); } - /// - /// Initializes a new instance of the class - /// wrapping an external . - /// - /// The configuration providing initialization code which allows extending the library. - /// The memory source. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The images metadata. - internal Image( - Configuration configuration, - MemoryGroup memoryGroup, - int width, - int height, - ImageMetadata metadata) - : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + [MethodImpl(InliningOptions.ShortMethod)] + set { - this.frames = new ImageFrameCollection(this, width, height, memoryGroup); + this.EnsureNotDisposed(); + + this.VerifyCoords(x, y); + this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y) = value; } + } - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The width of the image in pixels. - /// The height of the image in pixels. - /// The color to initialize the pixels with. - /// The images metadata. - internal Image( - Configuration configuration, - int width, - int height, - TPixel backgroundColor, - ImageMetadata metadata) - : base(configuration, PixelTypeInfo.Create(), metadata, width, height) + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; + buffer.FastMemoryGroup.IncreaseRefCounts(); + + try { - this.frames = new ImageFrameCollection(this, width, height, backgroundColor); + var accessor = new PixelAccessor(buffer); + processPixels(accessor); } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The configuration providing initialization code which allows extending the library. - /// The images metadata. - /// The frames that will be owned by this image instance. - internal Image(Configuration configuration, ImageMetadata metadata, IEnumerable> frames) - : base(configuration, PixelTypeInfo.Create(), metadata, ValidateFramesAndGetSize(frames)) + finally { - this.frames = new ImageFrameCollection(this, frames); + buffer.FastMemoryGroup.DecreaseRefCounts(); } + } - /// - protected override ImageFrameCollection NonGenericFrameCollection => this.Frames; + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The defining the pixel operations. + /// The pixel type of the second image. + public void ProcessPixelRows( + Image image2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(processPixels, nameof(processPixels)); - /// - /// Gets the collection of image frames. - /// - public new ImageFrameCollection Frames - { - get - { - this.EnsureNotDisposed(); - return this.frames; - } - } + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); - /// - /// Gets the root frame. - /// - private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe; - - /// - /// Gets or sets the pixel at the specified position. - /// - /// 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] + try { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.EnsureNotDisposed(); - - this.VerifyCoords(x, y); - return this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y); - } - - [MethodImpl(InliningOptions.ShortMethod)] - set - { - this.EnsureNotDisposed(); - - this.VerifyCoords(x, y); - this.PixelSourceUnsafe.PixelBuffer.GetElementUnsafe(x, y) = value; - } + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + processPixels(accessor1, accessor2); } - - /// - /// Execute to process image pixels in a safe and efficient manner. - /// - /// The defining the pixel operations. - public void ProcessPixelRows(PixelAccessorAction processPixels) + finally { - Guard.NotNull(processPixels, nameof(processPixels)); - Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; - buffer.FastMemoryGroup.IncreaseRefCounts(); - - try - { - var accessor = new PixelAccessor(buffer); - processPixels(accessor); - } - finally - { - buffer.FastMemoryGroup.DecreaseRefCounts(); - } + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); } + } - /// - /// Execute to process pixels of multiple images in a safe and efficient manner. - /// - /// The second image. - /// The defining the pixel operations. - /// The pixel type of the second image. - public void ProcessPixelRows( - Image image2, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The third image. + /// The defining the pixel operations. + /// The pixel type of the second image. + /// The pixel type of the third image. + public void ProcessPixelRows( + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(image3, nameof(image3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + buffer3.FastMemoryGroup.IncreaseRefCounts(); + + try { - Guard.NotNull(image2, nameof(image2)); - Guard.NotNull(processPixels, nameof(processPixels)); - - Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; - Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; - - buffer1.FastMemoryGroup.IncreaseRefCounts(); - buffer2.FastMemoryGroup.IncreaseRefCounts(); - - try - { - var accessor1 = new PixelAccessor(buffer1); - var accessor2 = new PixelAccessor(buffer2); - processPixels(accessor1, accessor2); - } - finally - { - buffer2.FastMemoryGroup.DecreaseRefCounts(); - buffer1.FastMemoryGroup.DecreaseRefCounts(); - } + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + var accessor3 = new PixelAccessor(buffer3); + processPixels(accessor1, accessor2, accessor3); } - - /// - /// Execute to process pixels of multiple images in a safe and efficient manner. - /// - /// The second image. - /// The third image. - /// The defining the pixel operations. - /// The pixel type of the second image. - /// The pixel type of the third image. - public void ProcessPixelRows( - Image image2, - Image image3, - PixelAccessorAction processPixels) - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel + finally { - Guard.NotNull(image2, nameof(image2)); - Guard.NotNull(image3, nameof(image3)); - Guard.NotNull(processPixels, nameof(processPixels)); - - Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; - Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; - Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; - - buffer1.FastMemoryGroup.IncreaseRefCounts(); - buffer2.FastMemoryGroup.IncreaseRefCounts(); - buffer3.FastMemoryGroup.IncreaseRefCounts(); - - try - { - var accessor1 = new PixelAccessor(buffer1); - var accessor2 = new PixelAccessor(buffer2); - var accessor3 = new PixelAccessor(buffer3); - processPixels(accessor1, accessor2, accessor3); - } - finally - { - buffer3.FastMemoryGroup.DecreaseRefCounts(); - buffer2.FastMemoryGroup.DecreaseRefCounts(); - buffer1.FastMemoryGroup.DecreaseRefCounts(); - } + buffer3.FastMemoryGroup.DecreaseRefCounts(); + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); } + } - /// - /// Copy image pixels to . - /// - /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); - - /// - /// Copy image pixels to . - /// - /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); - - /// - /// 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. - /// - /// To ensure the memory is contiguous, should be set - /// to true, preferably on a non-global configuration instance (not ). - /// - /// WARNING: Disposing or leaking the underlying image while still working with the 's - /// might lead to memory corruption. - /// - /// The referencing the image buffer. - /// The indicating the success. - public bool DangerousTryGetSinglePixelMemory(out Memory memory) + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + + /// + /// 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. + /// + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). + /// + /// WARNING: Disposing or leaking the underlying image while still working with the 's + /// might lead to memory corruption. + /// + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) + { + IMemoryGroup mg = this.GetPixelMemoryGroup(); + if (mg.Count > 1) { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) - { - memory = default; - return false; - } - - memory = mg.Single(); - return true; + memory = default; + return false; } - /// - /// Clones the current image - /// - /// Returns a new image with all the same metadata as the original. - public Image Clone() => this.Clone(this.GetConfiguration()); - - /// - /// Clones the current image with the given configuration. - /// - /// The configuration providing initialization code which allows extending the library. - /// Returns a new with all the same pixel data as the original. - public Image Clone(Configuration configuration) - { - this.EnsureNotDisposed(); + memory = mg.Single(); + return true; + } - var clonedFrames = new ImageFrame[this.frames.Count]; - for (int i = 0; i < clonedFrames.Length; i++) - { - clonedFrames[i] = this.frames[i].Clone(configuration); - } + /// + /// Clones the current image + /// + /// Returns a new image with all the same metadata as the original. + public Image Clone() => this.Clone(this.GetConfiguration()); - return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); - } + /// + /// Clones the current image with the given configuration. + /// + /// The configuration providing initialization code which allows extending the library. + /// Returns a new with all the same pixel data as the original. + public Image Clone(Configuration configuration) + { + this.EnsureNotDisposed(); - /// - /// Returns a copy of the image in the given pixel format. - /// - /// The pixel format. - /// The configuration providing initialization code which allows extending the library. - /// The . - public override Image CloneAs(Configuration configuration) + var clonedFrames = new ImageFrame[this.frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) { - this.EnsureNotDisposed(); + clonedFrames[i] = this.frames[i].Clone(configuration); + } - var clonedFrames = new ImageFrame[this.frames.Count]; - for (int i = 0; i < clonedFrames.Length; i++) - { - clonedFrames[i] = this.frames[i].CloneAs(configuration); - } + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); + } - return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); - } + /// + /// Returns a copy of the image in the given pixel format. + /// + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The . + public override Image CloneAs(Configuration configuration) + { + this.EnsureNotDisposed(); - /// - protected override void Dispose(bool disposing) + var clonedFrames = new ImageFrame[this.frames.Count]; + for (int i = 0; i < clonedFrames.Length; i++) { - if (disposing) - { - this.frames.Dispose(); - } + clonedFrames[i] = this.frames[i].CloneAs(configuration); } - /// - public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + return new Image(configuration, this.Metadata.DeepClone(), clonedFrames); + } - /// - internal override void Accept(IImageVisitor visitor) + /// + protected override void Dispose(bool disposing) + { + if (disposing) { - this.EnsureNotDisposed(); - - visitor.Visit(this); + this.frames.Dispose(); } + } - /// - internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken) - { - this.EnsureNotDisposed(); + /// + public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; - return visitor.VisitAsync(this, cancellationToken); - } + /// + internal override void Accept(IImageVisitor visitor) + { + this.EnsureNotDisposed(); - /// - /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. - /// - /// The pixel source. - internal void SwapOrCopyPixelsBuffersFrom(Image pixelSource) - { - Guard.NotNull(pixelSource, nameof(pixelSource)); + visitor.Visit(this); + } - this.EnsureNotDisposed(); + /// + internal override Task AcceptAsync(IImageVisitorAsync visitor, CancellationToken cancellationToken) + { + this.EnsureNotDisposed(); - ImageFrameCollection sourceFrames = pixelSource.Frames; - for (int i = 0; i < this.frames.Count; i++) - { - this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); - } + return visitor.VisitAsync(this, cancellationToken); + } - this.UpdateSize(pixelSource.Size()); - } + /// + /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. + /// + /// The pixel source. + internal void SwapOrCopyPixelsBuffersFrom(Image pixelSource) + { + Guard.NotNull(pixelSource, nameof(pixelSource)); - private static Size ValidateFramesAndGetSize(IEnumerable> frames) + this.EnsureNotDisposed(); + + ImageFrameCollection sourceFrames = pixelSource.Frames; + for (int i = 0; i < this.frames.Count; i++) { - Guard.NotNull(frames, nameof(frames)); + this.frames[i].SwapOrCopyPixelsBufferFrom(sourceFrames[i]); + } - ImageFrame rootFrame = frames.FirstOrDefault(); + this.UpdateSize(pixelSource.Size()); + } + + private static Size ValidateFramesAndGetSize(IEnumerable> frames) + { + Guard.NotNull(frames, nameof(frames)); - if (rootFrame == null) - { - throw new ArgumentException("Must not be empty.", nameof(frames)); - } + ImageFrame rootFrame = frames.FirstOrDefault(); - Size rootSize = rootFrame.Size(); + if (rootFrame == null) + { + throw new ArgumentException("Must not be empty.", nameof(frames)); + } - if (frames.Any(f => f.Size() != rootSize)) - { - throw new ArgumentException("The provided frames must be of the same size.", nameof(frames)); - } + Size rootSize = rootFrame.Size(); - return rootSize; + if (frames.Any(f => f.Size() != rootSize)) + { + throw new ArgumentException("The provided frames must be of the same size.", nameof(frames)); } - [MethodImpl(InliningOptions.ShortMethod)] - private void VerifyCoords(int x, int y) + return rootSize; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if ((uint)x >= (uint)this.Width) { - if ((uint)x >= (uint)this.Width) - { - ThrowArgumentOutOfRangeException(nameof(x)); - } - - if ((uint)y >= (uint)this.Height) - { - ThrowArgumentOutOfRangeException(nameof(y)); - } + ThrowArgumentOutOfRangeException(nameof(x)); } - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowArgumentOutOfRangeException(string paramName) + if ((uint)y >= (uint)this.Height) { - throw new ArgumentOutOfRangeException(paramName); + ThrowArgumentOutOfRangeException(nameof(y)); } } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } } diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 2bd5099028..3d37bf5bcc 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; @@ -9,109 +8,108 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. +/// +/// The pixel format. +public sealed class IndexedImageFrame : IPixelSource, IDisposable + where TPixel : unmanaged, IPixel { + private Buffer2D pixelBuffer; + private IMemoryOwner paletteOwner; + private bool isDisposed; + /// - /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. + /// Initializes a new instance of the class. /// - /// The pixel format. - public sealed class IndexedImageFrame : IPixelSource, IDisposable - where TPixel : unmanaged, IPixel + /// + /// 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) { - private Buffer2D pixelBuffer; - private IMemoryOwner paletteOwner; - private bool isDisposed; + 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)); - /// - /// 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.pixelBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - this.Configuration = configuration; - this.Width = width; - this.Height = height; - this.pixelBuffer = configuration.MemoryAllocator.Allocate2D(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[..palette.Length]; - } + // 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[..palette.Length]; + } - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - public Configuration Configuration { get; } + /// + /// 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 width of this . + /// + public int Width { get; } - /// - /// Gets the height of this . - /// - public int Height { get; } + /// + /// Gets the height of this . + /// + public int Height { get; } - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette { get; } + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; } - /// - Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; + /// + Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// WARNING: Disposing or leaking the underlying while still working with it's - /// might lead to memory corruption. - /// - /// The row index in the pixel buffer. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan DangerousGetRowSpan(int rowIndex) - => this.GetWritablePixelRowSpanUnsafe(rowIndex); + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// WARNING: Disposing or leaking the underlying while still working with it's + /// might lead to memory corruption. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan DangerousGetRowSpan(int rowIndex) + => this.GetWritablePixelRowSpanUnsafe(rowIndex); - /// - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from 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.pixelBuffer.DangerousGetRowSpan(rowIndex); + /// + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from 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.pixelBuffer.DangerousGetRowSpan(rowIndex); - /// - public void Dispose() + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.isDisposed = true; - this.pixelBuffer.Dispose(); - this.paletteOwner.Dispose(); - this.pixelBuffer = null; - this.paletteOwner = null; - } + this.isDisposed = true; + this.pixelBuffer.Dispose(); + this.paletteOwner.Dispose(); + this.pixelBuffer = null; + this.paletteOwner = null; } } } diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs index e95cc2fb7f..f496f5fbef 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -1,24 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Memory +/// +/// Options for allocating buffers. +/// +[Flags] +public enum AllocationOptions { /// - /// Options for allocating buffers. + /// Indicates that the buffer should just be allocated. /// - [Flags] - public enum AllocationOptions - { - /// - /// Indicates that the buffer should just be allocated. - /// - None = 0, + None = 0, - /// - /// Indicates that the allocated buffer should be cleaned following allocation. - /// - Clean = 1 - } + /// + /// Indicates that the allocated buffer should be cleaned following allocation. + /// + Clean = 1 } diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs index e2f5a8b32e..3ead1c5df7 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs @@ -1,10 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +internal static class AllocationOptionsExtensions { - internal static class AllocationOptionsExtensions - { - public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; - } + public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; } diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs index 13f6960ecf..9f34602fb1 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -1,61 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Wraps an array as an instance. +/// +/// +internal class BasicArrayBuffer : ManagedBufferBase + where T : struct { /// - /// Wraps an array as an instance. + /// Initializes a new instance of the class. + /// + /// The array. + /// The length of the buffer. + public BasicArrayBuffer(T[] array, int length) + { + 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) + { + } + + /// + /// Gets the array. /// + public T[] Array { get; } + + /// + /// Gets the length. + /// + public int Length { get; } + + /// + public override Span GetSpan() => this.Array.AsSpan(0, this.Length); + + /// + protected override void Dispose(bool disposing) + { + } + /// - internal class BasicArrayBuffer : ManagedBufferBase - where T : struct + protected override object GetPinnableObject() { - /// - /// Initializes a new instance of the class. - /// - /// The array. - /// The length of the buffer. - public BasicArrayBuffer(T[] array, int length) - { - 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) - { - } - - /// - /// Gets the array. - /// - public T[] Array { get; } - - /// - /// Gets the length. - /// - public int Length { get; } - - /// - public override Span GetSpan() => this.Array.AsSpan(0, this.Length); - - /// - protected override void Dispose(bool disposing) - { - } - - /// - protected override object GetPinnableObject() - { - return this.Array; - } + return this.Array; } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs index 54d8e17f01..173e916457 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs @@ -3,109 +3,107 @@ // Port of BCL internal utility: // https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs -using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) +/// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) +/// +internal sealed class Gen2GcCallback : CriticalFinalizerObject { - /// - /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) - /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) - /// - internal sealed class Gen2GcCallback : CriticalFinalizerObject - { - private readonly Func callback0; - private readonly Func callback1; - private GCHandle weakTargetObj; + private readonly Func callback0; + private readonly Func callback1; + private GCHandle weakTargetObj; - private Gen2GcCallback(Func callback) => this.callback0 = callback; + private Gen2GcCallback(Func callback) => this.callback0 = callback; - private Gen2GcCallback(Func callback, object targetObj) - { - this.callback1 = callback; - this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); - } + private Gen2GcCallback(Func callback, object targetObj) + { + this.callback1 = callback; + this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); + } - ~Gen2GcCallback() + ~Gen2GcCallback() + { + if (this.weakTargetObj.IsAllocated) { - if (this.weakTargetObj.IsAllocated) + // Check to see if the target object is still alive. + object targetObj = this.weakTargetObj.Target; + if (targetObj == null) { - // Check to see if the target object is still alive. - object targetObj = this.weakTargetObj.Target; - if (targetObj == null) + // The target object is dead, so this callback object is no longer needed. + this.weakTargetObj.Free(); + return; + } + + // Execute the callback method. + try + { + if (!this.callback1(targetObj)) { - // The target object is dead, so this callback object is no longer needed. + // If the callback returns false, this callback object is no longer needed. this.weakTargetObj.Free(); return; } - - // Execute the callback method. - try - { - if (!this.callback1(targetObj)) - { - // If the callback returns false, this callback object is no longer needed. - this.weakTargetObj.Free(); - return; - } - } - catch - { - // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. #if DEBUG - // Except in DEBUG, as we really shouldn't be hitting any exceptions here. - throw; + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; #endif - } } - else + } + else + { + // Execute the callback method. + try { - // Execute the callback method. - try + if (!this.callback0()) { - if (!this.callback0()) - { - // If the callback returns false, this callback object is no longer needed. - return; - } + // If the callback returns false, this callback object is no longer needed. + return; } - catch - { - // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. #if DEBUG - // Except in DEBUG, as we really shouldn't be hitting any exceptions here. - throw; + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; #endif - } } - - // Resurrect ourselves by re-registering for finalization. - GC.ReRegisterForFinalize(this); } - /// - /// Schedule 'callback' to be called in the next GC. If the callback returns true it is - /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. - /// - public static void Register(Func callback) => + // Resurrect ourselves by re-registering for finalization. + GC.ReRegisterForFinalize(this); + } + + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + public static void Register(Func callback) => - // Create a unreachable object that remembers the callback function and target object. - _ = new Gen2GcCallback(callback); + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback); - /// - /// - /// Schedule 'callback' to be called in the next GC. If the callback returns true it is - /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. - /// - /// - /// NOTE: This callback will be kept alive until either the callback function returns false, - /// or the target object dies. - /// - /// - public static void Register(Func callback, object targetObj) => + /// + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + /// + /// NOTE: This callback will be kept alive until either the callback function returns false, + /// or the target object dies. + /// + /// + public static void Register(Func callback, object targetObj) => - // Create a unreachable object that remembers the callback function and target object. - _ = new Gen2GcCallback(callback, targetObj); - } + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback, targetObj); } diff --git a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs index 9dada3e419..901623d83a 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Defines an common interface for ref-counted objects. +/// +internal interface IRefCounted { /// - /// Defines an common interface for ref-counted objects. + /// Increments the reference counter. /// - internal interface IRefCounted - { - /// - /// Increments the reference counter. - /// - void AddRef(); + void AddRef(); - /// - /// Decrements the reference counter. - /// - void ReleaseRef(); - } + /// + /// Decrements the reference counter. + /// + void ReleaseRef(); } diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs index a59af80dab..a18bd34474 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -5,44 +5,43 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Provides a base class for implementations by implementing pinning logic for adaption. +/// +/// The element type. +internal abstract class ManagedBufferBase : MemoryManager + where T : struct { - /// - /// Provides a base class for implementations by implementing pinning logic for adaption. - /// - /// The element type. - internal abstract class ManagedBufferBase : MemoryManager - where T : struct - { - private GCHandle pinHandle; + private GCHandle pinHandle; - /// - public override unsafe MemoryHandle Pin(int elementIndex = 0) + /// + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + if (!this.pinHandle.IsAllocated) { - if (!this.pinHandle.IsAllocated) - { - this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); - } + this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); + } - void* ptr = Unsafe.Add((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex); + void* ptr = Unsafe.Add((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex); - // We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager instance. - return new MemoryHandle(ptr, pinnable: this); - } + // We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager instance. + return new MemoryHandle(ptr, pinnable: this); + } - /// - public override void Unpin() + /// + public override void Unpin() + { + if (this.pinHandle.IsAllocated) { - if (this.pinHandle.IsAllocated) - { - this.pinHandle.Free(); - } + this.pinHandle.Free(); } - - /// - /// Gets the object that should be pinned. - /// - /// The pinnable . - protected abstract object GetPinnableObject(); } + + /// + /// Gets the object that should be pinned. + /// + /// The pinnable . + protected abstract object GetPinnableObject(); } diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs index bd2f9c5474..0edc41434b 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs @@ -1,78 +1,74 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Diagnostics; -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Implements reference counting lifetime guard mechanism for memory resources +/// and maintains the value of . +/// +internal abstract class RefCountedMemoryLifetimeGuard : IDisposable { - /// - /// Implements reference counting lifetime guard mechanism for memory resources - /// and maintains the value of . - /// - internal abstract class RefCountedMemoryLifetimeGuard : IDisposable - { - private int refCount = 1; - private int disposed; - private int released; - private string allocationStackTrace; + private int refCount = 1; + private int disposed; + private int released; + private string allocationStackTrace; - protected RefCountedMemoryLifetimeGuard() + protected RefCountedMemoryLifetimeGuard() + { + if (MemoryDiagnostics.UndisposedAllocationSubscribed) { - if (MemoryDiagnostics.UndisposedAllocationSubscribed) - { - this.allocationStackTrace = Environment.StackTrace; - } - - MemoryDiagnostics.IncrementTotalUndisposedAllocationCount(); + this.allocationStackTrace = Environment.StackTrace; } - ~RefCountedMemoryLifetimeGuard() - { - Interlocked.Exchange(ref this.disposed, 1); - this.ReleaseRef(true); - } + MemoryDiagnostics.IncrementTotalUndisposedAllocationCount(); + } + + ~RefCountedMemoryLifetimeGuard() + { + Interlocked.Exchange(ref this.disposed, 1); + this.ReleaseRef(true); + } - public bool IsDisposed => this.disposed == 1; + public bool IsDisposed => this.disposed == 1; - public void AddRef() => Interlocked.Increment(ref this.refCount); + public void AddRef() => Interlocked.Increment(ref this.refCount); - public void ReleaseRef() => this.ReleaseRef(false); + public void ReleaseRef() => this.ReleaseRef(false); - public void Dispose() + public void Dispose() + { + int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); + if (wasDisposed == 0) { - int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); - if (wasDisposed == 0) - { - this.ReleaseRef(); - GC.SuppressFinalize(this); - } + this.ReleaseRef(); + GC.SuppressFinalize(this); } + } - protected abstract void Release(); + protected abstract void Release(); - private void ReleaseRef(bool finalizing) + private void ReleaseRef(bool finalizing) + { + Interlocked.Decrement(ref this.refCount); + if (this.refCount == 0) { - Interlocked.Decrement(ref this.refCount); - if (this.refCount == 0) - { - int wasReleased = Interlocked.Exchange(ref this.released, 1); + int wasReleased = Interlocked.Exchange(ref this.released, 1); - if (wasReleased == 0) + if (wasReleased == 0) + { + if (!finalizing) { - if (!finalizing) - { - MemoryDiagnostics.DecrementTotalUndisposedAllocationCount(); - } - else if (this.allocationStackTrace != null) - { - MemoryDiagnostics.RaiseUndisposedMemoryResource(this.allocationStackTrace); - } - - this.Release(); + MemoryDiagnostics.DecrementTotalUndisposedAllocationCount(); } + else if (this.allocationStackTrace != null) + { + MemoryDiagnostics.RaiseUndisposedMemoryResource(this.allocationStackTrace); + } + + this.Release(); } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 947fed6243..5876fd429d 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -1,81 +1,79 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted + where T : struct { - internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted - where T : struct + private readonly int lengthInBytes; + private LifetimeGuard lifetimeGuard; + + public SharedArrayPoolBuffer(int lengthInElements) { - private readonly int lengthInBytes; - private LifetimeGuard lifetimeGuard; + this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); + this.Array = ArrayPool.Shared.Rent(this.lengthInBytes); + this.lifetimeGuard = new LifetimeGuard(this.Array); + } - public SharedArrayPoolBuffer(int lengthInElements) + public byte[] Array { get; private set; } + + protected override void Dispose(bool disposing) + { + if (this.Array == null) { - this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); - this.Array = ArrayPool.Shared.Rent(this.lengthInBytes); - this.lifetimeGuard = new LifetimeGuard(this.Array); + return; } - public byte[] Array { get; private set; } + this.lifetimeGuard.Dispose(); + this.Array = null; + } - protected override void Dispose(bool disposing) - { - if (this.Array == null) - { - return; - } + public override Span GetSpan() + { + this.CheckDisposed(); + return MemoryMarshal.Cast(this.Array.AsSpan(0, this.lengthInBytes)); + } - this.lifetimeGuard.Dispose(); - this.Array = null; - } + protected override object GetPinnableObject() => this.Array; - public override Span GetSpan() - { - this.CheckDisposed(); - return MemoryMarshal.Cast(this.Array.AsSpan(0, this.lengthInBytes)); - } + public void AddRef() + { + this.CheckDisposed(); + this.lifetimeGuard.AddRef(); + } - protected override object GetPinnableObject() => this.Array; + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); - public void AddRef() + [Conditional("DEBUG")] + private void CheckDisposed() + { + if (this.Array == null) { - this.CheckDisposed(); - this.lifetimeGuard.AddRef(); + throw new ObjectDisposedException("SharedArrayPoolBuffer"); } + } - public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + private sealed class LifetimeGuard : RefCountedMemoryLifetimeGuard + { + private byte[] array; - [Conditional("DEBUG")] - private void CheckDisposed() - { - if (this.Array == null) - { - throw new ObjectDisposedException("SharedArrayPoolBuffer"); - } - } + public LifetimeGuard(byte[] array) => this.array = array; - private sealed class LifetimeGuard : RefCountedMemoryLifetimeGuard + protected override void Release() { - private byte[] array; - - public LifetimeGuard(byte[] array) => this.array = array; - - protected override void Release() - { - // If this is called by a finalizer, we will end storing the first array of this bucket - // on the thread local storage of the finalizer thread. - // This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets, - // meaning likely a different bucket than it was rented from, - // but this is PROBABLY better than not returning the arrays at all. - ArrayPool.Shared.Return(this.array); - this.array = null; - } + // If this is called by a finalizer, we will end storing the first array of this bucket + // on the thread local storage of the finalizer thread. + // This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets, + // meaning likely a different bucket than it was rented from, + // but this is PROBABLY better than not returning the arrays at all. + ArrayPool.Shared.Return(this.array); + this.array = null; } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs index 9a126e2f6b..24bf52b1f9 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs @@ -1,64 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +internal partial class UniformUnmanagedMemoryPool { - internal partial class UniformUnmanagedMemoryPool + public UnmanagedBuffer CreateGuardedBuffer( + UnmanagedMemoryHandle handle, + int lengthInElements, + bool clear) + where T : struct { - public UnmanagedBuffer CreateGuardedBuffer( - UnmanagedMemoryHandle handle, - int lengthInElements, - bool clear) - where T : struct + var buffer = new UnmanagedBuffer(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); + if (clear) { - var buffer = new UnmanagedBuffer(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); - if (clear) - { - buffer.Clear(); - } - - return buffer; + buffer.Clear(); } - public RefCountedMemoryLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); + return buffer; + } - private sealed class GroupLifetimeGuard : RefCountedMemoryLifetimeGuard - { - private readonly UniformUnmanagedMemoryPool pool; - private readonly UnmanagedMemoryHandle[] handles; + public RefCountedMemoryLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); - public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) - { - this.pool = pool; - this.handles = handles; - } + private sealed class GroupLifetimeGuard : RefCountedMemoryLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryHandle[] handles; - protected override void Release() + public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) + { + this.pool = pool; + this.handles = handles; + } + + protected override void Release() + { + if (!this.pool.Return(this.handles)) { - if (!this.pool.Return(this.handles)) + foreach (UnmanagedMemoryHandle handle in this.handles) { - foreach (UnmanagedMemoryHandle handle in this.handles) - { - handle.Free(); - } + handle.Free(); } } } + } - private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard - { - private readonly UniformUnmanagedMemoryPool pool; + private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; - public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) - : base(handle) => - this.pool = pool; + public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) + : base(handle) => + this.pool = pool; - protected override void Release() + protected override void Release() + { + if (!this.pool.Return(this.Handle)) { - if (!this.pool.Return(this.Handle)) - { - this.Handle.Free(); - } + this.Handle.Free(); } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 756f5a9955..571d713b52 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -1,346 +1,342 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; -namespace SixLabors.ImageSharp.Memory.Internals -{ - // CriticalFinalizerObject: - // In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, - // but we should not rely on this. - internal partial class UniformUnmanagedMemoryPool : System.Runtime.ConstrainedExecution.CriticalFinalizerObject - { - private static int minTrimPeriodMilliseconds = int.MaxValue; - private static readonly List> AllPools = new(); - private static Timer trimTimer; +namespace SixLabors.ImageSharp.Memory.Internals; - private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); +// CriticalFinalizerObject: +// In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, +// but we should not rely on this. +internal partial class UniformUnmanagedMemoryPool : System.Runtime.ConstrainedExecution.CriticalFinalizerObject +{ + private static int minTrimPeriodMilliseconds = int.MaxValue; + private static readonly List> AllPools = new(); + private static Timer trimTimer; - private readonly TrimSettings trimSettings; - private readonly UnmanagedMemoryHandle[] buffers; - private int index; - private long lastTrimTimestamp; - private int finalized; + private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); - public UniformUnmanagedMemoryPool(int bufferLength, int capacity) - : this(bufferLength, capacity, TrimSettings.Default) - { - } + private readonly TrimSettings trimSettings; + private readonly UnmanagedMemoryHandle[] buffers; + private int index; + private long lastTrimTimestamp; + private int finalized; - public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) - { - this.trimSettings = trimSettings; - this.Capacity = capacity; - this.BufferLength = bufferLength; - this.buffers = new UnmanagedMemoryHandle[capacity]; + public UniformUnmanagedMemoryPool(int bufferLength, int capacity) + : this(bufferLength, capacity, TrimSettings.Default) + { + } - if (trimSettings.Enabled) - { - UpdateTimer(trimSettings, this); - Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); - this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; - } - } + public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) + { + this.trimSettings = trimSettings; + this.Capacity = capacity; + this.BufferLength = bufferLength; + this.buffers = new UnmanagedMemoryHandle[capacity]; - // We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable, - // since the types don't really match Disposable semantics. - // If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(), - // which normally should free the already returned (!) buffers. - // However in case if this doesn't happen, we need the retained memory to be freed by the finalizer. - ~UniformUnmanagedMemoryPool() + if (trimSettings.Enabled) { - Interlocked.Exchange(ref this.finalized, 1); - this.TrimAll(this.buffers); + UpdateTimer(trimSettings, this); + Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; } + } - public int BufferLength { get; } + // We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable, + // since the types don't really match Disposable semantics. + // If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(), + // which normally should free the already returned (!) buffers. + // However in case if this doesn't happen, we need the retained memory to be freed by the finalizer. + ~UniformUnmanagedMemoryPool() + { + Interlocked.Exchange(ref this.finalized, 1); + this.TrimAll(this.buffers); + } - public int Capacity { get; } + public int BufferLength { get; } - private bool Finalized => this.finalized == 1; + public int Capacity { get; } - /// - /// Rent a single buffer. If the pool is full, return . - /// - public UnmanagedMemoryHandle Rent() + private bool Finalized => this.finalized == 1; + + /// + /// Rent a single buffer. If the pool is full, return . + /// + public UnmanagedMemoryHandle Rent() + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + // Avoid taking the lock if the pool is is over it's limit: + if (this.index == buffersLocal.Length || this.Finalized) { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; + return UnmanagedMemoryHandle.NullHandle; + } - // Avoid taking the lock if the pool is is over it's limit: + UnmanagedMemoryHandle buffer; + lock (buffersLocal) + { + // Check again after taking the lock: if (this.index == buffersLocal.Length || this.Finalized) { return UnmanagedMemoryHandle.NullHandle; } - UnmanagedMemoryHandle buffer; - lock (buffersLocal) - { - // Check again after taking the lock: - if (this.index == buffersLocal.Length || this.Finalized) - { - return UnmanagedMemoryHandle.NullHandle; - } + buffer = buffersLocal[this.index]; + buffersLocal[this.index++] = default; + } - buffer = buffersLocal[this.index]; - buffersLocal[this.index++] = default; - } + if (buffer.IsInvalid) + { + buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); + } - if (buffer.IsInvalid) - { - buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); - } + return buffer; + } - return buffer; - } + /// + /// Rent buffers or return 'null' if the pool is full. + /// + public UnmanagedMemoryHandle[] Rent(int bufferCount) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; - /// - /// Rent buffers or return 'null' if the pool is full. - /// - public UnmanagedMemoryHandle[] Rent(int bufferCount) + // Avoid taking the lock if the pool is is over it's limit: + if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; + return null; + } - // Avoid taking the lock if the pool is is over it's limit: + UnmanagedMemoryHandle[] result; + lock (buffersLocal) + { + // Check again after taking the lock: if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) { return null; } - UnmanagedMemoryHandle[] result; - lock (buffersLocal) + result = new UnmanagedMemoryHandle[bufferCount]; + for (int i = 0; i < bufferCount; i++) { - // Check again after taking the lock: - if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) - { - return null; - } - - result = new UnmanagedMemoryHandle[bufferCount]; - for (int i = 0; i < bufferCount; i++) - { - result[i] = buffersLocal[this.index]; - buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; - } + result[i] = buffersLocal[this.index]; + buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; } + } - for (int i = 0; i < result.Length; i++) + for (int i = 0; i < result.Length; i++) + { + if (result[i].IsInvalid) { - if (result[i].IsInvalid) - { - result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); - } + result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); } - - return result; } - // The Return methods return false if and only if: - // (1) More buffers are returned than rented OR - // (2) The pool has been finalized. - // This is defensive programming, since neither of the cases should happen normally - // (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract), - // so we throw in Debug instead of returning false. - // In Release, the caller should Free() the handles if false is returned to avoid memory leaks. - public bool Return(UnmanagedMemoryHandle bufferHandle) + return result; + } + + // The Return methods return false if and only if: + // (1) More buffers are returned than rented OR + // (2) The pool has been finalized. + // This is defensive programming, since neither of the cases should happen normally + // (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract), + // so we throw in Debug instead of returning false. + // In Release, the caller should Free() the handles if false is returned to avoid memory leaks. + public bool Return(UnmanagedMemoryHandle bufferHandle) + { + Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); + lock (this.buffers) { - Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); - lock (this.buffers) + if (this.Finalized || this.index == 0) { - if (this.Finalized || this.index == 0) - { - this.DebugThrowInvalidReturn(); - return false; - } - - this.buffers[--this.index] = bufferHandle; + this.DebugThrowInvalidReturn(); + return false; } - return true; + this.buffers[--this.index] = bufferHandle; } - public bool Return(Span bufferHandles) + return true; + } + + public bool Return(Span bufferHandles) + { + lock (this.buffers) { - lock (this.buffers) + if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) { - if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) - { - this.DebugThrowInvalidReturn(); - return false; - } - - for (int i = bufferHandles.Length - 1; i >= 0; i--) - { - ref UnmanagedMemoryHandle h = ref bufferHandles[i]; - Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); - this.buffers[--this.index] = h; - } + this.DebugThrowInvalidReturn(); + return false; } - return true; + for (int i = bufferHandles.Length - 1; i >= 0; i--) + { + ref UnmanagedMemoryHandle h = ref bufferHandles[i]; + Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); + this.buffers[--this.index] = h; + } } - public void Release() + return true; + } + + public void Release() + { + lock (this.buffers) { - lock (this.buffers) + for (int i = this.index; i < this.buffers.Length; i++) { - for (int i = this.index; i < this.buffers.Length; i++) + ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; + if (buffer.IsInvalid) { - ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; - if (buffer.IsInvalid) - { - break; - } - - buffer.Free(); + break; } + + buffer.Free(); } } + } - [Conditional("DEBUG")] - private void DebugThrowInvalidReturn() + [Conditional("DEBUG")] + private void DebugThrowInvalidReturn() + { + if (this.Finalized) { - if (this.Finalized) - { - throw new ObjectDisposedException( - nameof(UniformUnmanagedMemoryPool), - "Invalid handle return to the pool! The pool has been finalized."); - } - - throw new InvalidOperationException( - "Invalid handle return to the pool! Returning more buffers than rented."); + throw new ObjectDisposedException( + nameof(UniformUnmanagedMemoryPool), + "Invalid handle return to the pool! The pool has been finalized."); } - private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) - { - lock (AllPools) - { - AllPools.Add(new WeakReference(pool)); + throw new InvalidOperationException( + "Invalid handle return to the pool! Returning more buffers than rented."); + } - // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds. - // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. - int period = settings.TrimPeriodMilliseconds / 4; - if (trimTimer == null) - { - trimTimer = new Timer(_ => TimerCallback(), null, period, period); - } - else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) - { - trimTimer.Change(period, period); - } + private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) + { + lock (AllPools) + { + AllPools.Add(new WeakReference(pool)); - minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); + // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds. + // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. + int period = settings.TrimPeriodMilliseconds / 4; + if (trimTimer == null) + { + trimTimer = new Timer(_ => TimerCallback(), null, period, period); + } + else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) + { + trimTimer.Change(period, period); } + + minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); } + } - private static void TimerCallback() + private static void TimerCallback() + { + lock (AllPools) { - lock (AllPools) + // Remove lost references from the list: + for (int i = AllPools.Count - 1; i >= 0; i--) { - // Remove lost references from the list: - for (int i = AllPools.Count - 1; i >= 0; i--) + if (!AllPools[i].TryGetTarget(out _)) { - if (!AllPools[i].TryGetTarget(out _)) - { - AllPools.RemoveAt(i); - } + AllPools.RemoveAt(i); } + } - foreach (WeakReference weakPoolRef in AllPools) + foreach (WeakReference weakPoolRef in AllPools) + { + if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) { - if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) - { - pool.Trim(); - } + pool.Trim(); } } } + } - private bool Trim() + private bool Trim() + { + if (this.Finalized) { - if (this.Finalized) - { - return false; - } + return false; + } - UnmanagedMemoryHandle[] buffersLocal = this.buffers; + UnmanagedMemoryHandle[] buffersLocal = this.buffers; - bool isHighPressure = this.IsHighMemoryPressure(); - - if (isHighPressure) - { - this.TrimAll(buffersLocal); - return true; - } - - long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; - if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) - { - return this.TrimLowPressure(buffersLocal); - } + bool isHighPressure = this.IsHighMemoryPressure(); + if (isHighPressure) + { + this.TrimAll(buffersLocal); return true; } - private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) + long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; + if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) + { + return this.TrimLowPressure(buffersLocal); + } + + return true; + } + + private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) + { + lock (buffersLocal) { - lock (buffersLocal) + // Trim all: + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) { - // Trim all: - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) - { - buffersLocal[i].Free(); - } + buffersLocal[i].Free(); } } + } - private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) + private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) + { + lock (buffersLocal) { - lock (buffersLocal) + // Count the buffers in the pool: + int retainedCount = 0; + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) { - // Count the buffers in the pool: - int retainedCount = 0; - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) - { - retainedCount++; - } - - // Trim 'trimRate' of 'retainedCount': - int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); - int trimStart = this.index + retainedCount - 1; - int trimStop = this.index + retainedCount - trimCount; - for (int i = trimStart; i >= trimStop; i--) - { - buffersLocal[i].Free(); - } + retainedCount++; + } - this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; + // Trim 'trimRate' of 'retainedCount': + int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); + int trimStart = this.index + retainedCount - 1; + int trimStop = this.index + retainedCount - trimCount; + for (int i = trimStart; i >= trimStop; i--) + { + buffersLocal[i].Free(); } - return true; + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; } - private bool IsHighMemoryPressure() - { - GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); - return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; - } + return true; + } - public class TrimSettings - { - // Trim half of the retained pool buffers every minute. - public int TrimPeriodMilliseconds { get; set; } = 60_000; + private bool IsHighMemoryPressure() + { + GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); + return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; + } + + public class TrimSettings + { + // Trim half of the retained pool buffers every minute. + public int TrimPeriodMilliseconds { get; set; } = 60_000; - public float Rate { get; set; } = 0.5f; + public float Rate { get; set; } = 0.5f; - // Be more strict about high pressure on 32 bit. - public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; + // Be more strict about high pressure on 32 bit. + public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; - public bool Enabled => this.Rate > 0; + public bool Enabled => this.Rate > 0; - public static TrimSettings Default => new TrimSettings(); - } + public static TrimSettings Default => new TrimSettings(); } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs index 4104dfc291..29d8658fb3 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory.Internals +namespace SixLabors.ImageSharp.Memory.Internals; + +/// +/// Defines a strategy for managing unmanaged memory ownership. +/// +internal abstract class UnmanagedBufferLifetimeGuard : RefCountedMemoryLifetimeGuard { - /// - /// Defines a strategy for managing unmanaged memory ownership. - /// - internal abstract class UnmanagedBufferLifetimeGuard : RefCountedMemoryLifetimeGuard - { - private UnmanagedMemoryHandle handle; + private UnmanagedMemoryHandle handle; - protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; + protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; - public ref UnmanagedMemoryHandle Handle => ref this.handle; + public ref UnmanagedMemoryHandle Handle => ref this.handle; - public sealed class FreeHandle : UnmanagedBufferLifetimeGuard + public sealed class FreeHandle : UnmanagedBufferLifetimeGuard + { + public FreeHandle(UnmanagedMemoryHandle handle) + : base(handle) { - public FreeHandle(UnmanagedMemoryHandle handle) - : base(handle) - { - } - - protected override void Release() => this.Handle.Free(); } + + protected override void Release() => this.Handle.Free(); } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 3b7dabbd49..de09647261 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -1,80 +1,77 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -namespace SixLabors.ImageSharp.Memory.Internals -{ - /// - /// Allocates and provides an implementation giving - /// access to unmanaged buffers allocated by . - /// - /// The element type. - internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted - where T : struct - { - private readonly int lengthInElements; +namespace SixLabors.ImageSharp.Memory.Internals; - private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; +/// +/// Allocates and provides an implementation giving +/// access to unmanaged buffers allocated by . +/// +/// The element type. +internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted + where T : struct +{ + private readonly int lengthInElements; - private int disposed; + private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; - public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) - { - DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); + private int disposed; - this.lengthInElements = lengthInElements; - this.lifetimeGuard = lifetimeGuard; - } + public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) + { + DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); - public void* Pointer => this.lifetimeGuard.Handle.Pointer; + this.lengthInElements = lengthInElements; + this.lifetimeGuard = lifetimeGuard; + } - public override Span GetSpan() - { - DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); - DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); - return new(this.Pointer, this.lengthInElements); - } + public void* Pointer => this.lifetimeGuard.Handle.Pointer; - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); - DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + public override Span GetSpan() + { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + return new(this.Pointer, this.lengthInElements); + } - // Will be released in Unpin - this.lifetimeGuard.AddRef(); + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); - void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData, pinnable: this); - } + // Will be released in Unpin + this.lifetimeGuard.AddRef(); - /// - protected override void Dispose(bool disposing) - { - DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); + void* pbData = Unsafe.Add(this.Pointer, elementIndex); + return new MemoryHandle(pbData, pinnable: this); + } - if (Interlocked.Exchange(ref this.disposed, 1) == 1) - { - // Already disposed - return; - } + /// + protected override void Dispose(bool disposing) + { + DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); - this.lifetimeGuard.Dispose(); + if (Interlocked.Exchange(ref this.disposed, 1) == 1) + { + // Already disposed + return; } - /// - public override void Unpin() => this.lifetimeGuard.ReleaseRef(); + this.lifetimeGuard.Dispose(); + } - public void AddRef() => this.lifetimeGuard.AddRef(); + /// + public override void Unpin() => this.lifetimeGuard.ReleaseRef(); - public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + public void AddRef() => this.lifetimeGuard.AddRef(); - public static UnmanagedBuffer Allocate(int lengthInElements) => - new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()))); - } + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + + public static UnmanagedBuffer Allocate(int lengthInElements) => + new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()))); } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index 7933f3e13f..5bf2a8f593 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -1,133 +1,129 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -namespace SixLabors.ImageSharp.Memory.Internals -{ - /// - /// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a . - /// - internal struct UnmanagedMemoryHandle : IEquatable - { - // Number of allocation re-attempts when detecting OutOfMemoryException. - private const int MaxAllocationAttempts = 1000; +namespace SixLabors.ImageSharp.Memory.Internals; - // Track allocations for testing purposes: - private static int totalOutstandingHandles; +/// +/// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a . +/// +internal struct UnmanagedMemoryHandle : IEquatable +{ + // Number of allocation re-attempts when detecting OutOfMemoryException. + private const int MaxAllocationAttempts = 1000; - private static long totalOomRetries; + // Track allocations for testing purposes: + private static int totalOutstandingHandles; - // A Monitor to wait/signal when we are low on memory. - private static object lowMemoryMonitor; + private static long totalOomRetries; - public static readonly UnmanagedMemoryHandle NullHandle; + // A Monitor to wait/signal when we are low on memory. + private static object lowMemoryMonitor; - private IntPtr handle; - private int lengthInBytes; + public static readonly UnmanagedMemoryHandle NullHandle; - private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) - { - this.handle = handle; - this.lengthInBytes = lengthInBytes; + private IntPtr handle; + private int lengthInBytes; - if (lengthInBytes > 0) - { - GC.AddMemoryPressure(lengthInBytes); - } + private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) + { + this.handle = handle; + this.lengthInBytes = lengthInBytes; - Interlocked.Increment(ref totalOutstandingHandles); + if (lengthInBytes > 0) + { + GC.AddMemoryPressure(lengthInBytes); } - public IntPtr Handle => this.handle; - - public bool IsInvalid => this.Handle == IntPtr.Zero; + Interlocked.Increment(ref totalOutstandingHandles); + } - public bool IsValid => this.Handle != IntPtr.Zero; + public IntPtr Handle => this.handle; - public unsafe void* Pointer => (void*)this.Handle; + public bool IsInvalid => this.Handle == IntPtr.Zero; - /// - /// Gets the total outstanding handle allocations for testing purposes. - /// - internal static int TotalOutstandingHandles => totalOutstandingHandles; + public bool IsValid => this.Handle != IntPtr.Zero; - /// - /// Gets the total number -s retried. - /// - internal static long TotalOomRetries => totalOomRetries; + public unsafe void* Pointer => (void*)this.Handle; - public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b); + /// + /// Gets the total outstanding handle allocations for testing purposes. + /// + internal static int TotalOutstandingHandles => totalOutstandingHandles; - public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); + /// + /// Gets the total number -s retried. + /// + internal static long TotalOomRetries => totalOomRetries; - public static UnmanagedMemoryHandle Allocate(int lengthInBytes) - { - IntPtr handle = AllocateHandle(lengthInBytes); - return new UnmanagedMemoryHandle(handle, lengthInBytes); - } + public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b); - private static IntPtr AllocateHandle(int lengthInBytes) - { - int counter = 0; - IntPtr handle = IntPtr.Zero; - while (handle == IntPtr.Zero) - { - try - { - handle = Marshal.AllocHGlobal(lengthInBytes); - } - catch (OutOfMemoryException) when (counter < MaxAllocationAttempts) - { - // We are low on memory, but expect some memory to be freed soon. - // Block the thread & retry to avoid OOM. - counter++; - Interlocked.Increment(ref totalOomRetries); - - Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null); - Monitor.Enter(lowMemoryMonitor); - Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1); - Monitor.Exit(lowMemoryMonitor); - } - } + public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); - return handle; - } + public static UnmanagedMemoryHandle Allocate(int lengthInBytes) + { + IntPtr handle = AllocateHandle(lengthInBytes); + return new UnmanagedMemoryHandle(handle, lengthInBytes); + } - public void Free() + private static IntPtr AllocateHandle(int lengthInBytes) + { + int counter = 0; + IntPtr handle = IntPtr.Zero; + while (handle == IntPtr.Zero) { - IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero); - - if (h == IntPtr.Zero) + try { - return; + handle = Marshal.AllocHGlobal(lengthInBytes); } - - Marshal.FreeHGlobal(h); - Interlocked.Decrement(ref totalOutstandingHandles); - if (this.lengthInBytes > 0) + catch (OutOfMemoryException) when (counter < MaxAllocationAttempts) { - GC.RemoveMemoryPressure(this.lengthInBytes); - } + // We are low on memory, but expect some memory to be freed soon. + // Block the thread & retry to avoid OOM. + counter++; + Interlocked.Increment(ref totalOomRetries); - if (Volatile.Read(ref lowMemoryMonitor) != null) - { - // We are low on memory. Signal all threads waiting in AllocateHandle(). + Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null); Monitor.Enter(lowMemoryMonitor); - Monitor.PulseAll(lowMemoryMonitor); + Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1); Monitor.Exit(lowMemoryMonitor); } + } + + return handle; + } - this.lengthInBytes = 0; + public void Free() + { + IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero); + + if (h == IntPtr.Zero) + { + return; } - public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + Marshal.FreeHGlobal(h); + Interlocked.Decrement(ref totalOutstandingHandles); + if (this.lengthInBytes > 0) + { + GC.RemoveMemoryPressure(this.lengthInBytes); + } - public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + if (Volatile.Read(ref lowMemoryMonitor) != null) + { + // We are low on memory. Signal all threads waiting in AllocateHandle(). + Monitor.Enter(lowMemoryMonitor); + Monitor.PulseAll(lowMemoryMonitor); + Monitor.Exit(lowMemoryMonitor); + } - public override int GetHashCode() => this.handle.GetHashCode(); + this.lengthInBytes = 0; } + + public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + + public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + + public override int GetHashCode() => this.handle.GetHashCode(); } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 8991074034..2bd9cb5eef 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -1,80 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Memory managers are used to allocate memory for image processing operations. +/// +public abstract class MemoryAllocator { /// - /// Memory managers are used to allocate memory for image processing operations. + /// Gets the default platform-specific global instance that + /// serves as the default value for . + /// + /// This is a get-only property, + /// you should set 's + /// to change the default allocator used by and it's operations. /// - public abstract class MemoryAllocator - { - /// - /// Gets the default platform-specific global instance that - /// serves as the default value for . - /// - /// This is a get-only property, - /// you should set 's - /// to change the default allocator used by and it's operations. - /// - public static MemoryAllocator Default { get; } = Create(); - - /// - /// 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(); + public static MemoryAllocator Default { get; } = Create(); - /// - /// Creates a default instance of a optimized for the executing platform. - /// - /// The . - public static MemoryAllocator Create() => - new UniformUnmanagedMemoryPoolMemoryAllocator(null); + /// + /// 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(); - /// - /// Creates the default using the provided options. - /// - /// The . - /// The . - public static MemoryAllocator Create(MemoryAllocatorOptions options) => - new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes); + /// + /// Creates a default instance of a optimized for the executing platform. + /// + /// The . + public static MemoryAllocator Create() => + new UniformUnmanagedMemoryPoolMemoryAllocator(null); - /// - /// Allocates an , holding a of length . - /// - /// Type of the data stored in the buffer. - /// 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; + /// + /// Creates the default using the provided options. + /// + /// The . + /// The . + public static MemoryAllocator Create(MemoryAllocatorOptions options) => + new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes); - /// - /// Releases all retained resources not being in use. - /// Eg: by resetting array pools and letting GC to free the arrays. - /// - public virtual void ReleaseRetainedResources() - { - } + /// + /// Allocates an , holding a of length . + /// + /// Type of the data stored in the buffer. + /// 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; - /// - /// Allocates a . - /// - /// 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 virtual MemoryGroup AllocateGroup( - long totalLength, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) - where T : struct - => MemoryGroup.Allocate(this, totalLength, bufferAlignment, options); + /// + /// Releases all retained resources not being in use. + /// Eg: by resetting array pools and letting GC to free the arrays. + /// + public virtual void ReleaseRetainedResources() + { } + + /// + /// Allocates a . + /// + /// 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 virtual MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(this, totalLength, bufferAlignment, options); } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index c5f7fbeca2..5a821fd04a 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Defines options for creating the default . +/// +public struct MemoryAllocatorOptions { + private int? maximumPoolSizeMegabytes; + /// - /// Defines options for creating the default . + /// Gets or sets a value defining the maximum size of the 's internal memory pool + /// in Megabytes. means platform default. /// - public struct MemoryAllocatorOptions + public int? MaximumPoolSizeMegabytes { - private int? maximumPoolSizeMegabytes; - - /// - /// Gets or sets a value defining the maximum size of the 's internal memory pool - /// in Megabytes. means platform default. - /// - public int? MaximumPoolSizeMegabytes + get => this.maximumPoolSizeMegabytes; + set { - get => this.maximumPoolSizeMegabytes; - set + if (value.HasValue) { - if (value.HasValue) - { - Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); - } - - this.maximumPoolSizeMegabytes = value; + Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); } + + this.maximumPoolSizeMegabytes = value; } } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index e7ba9b53b6..41730d9678 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -4,22 +4,21 @@ using System.Buffers; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Implements by newing up managed arrays on every allocation request. +/// +public sealed class SimpleGcMemoryAllocator : MemoryAllocator { - /// - /// Implements by newing up managed arrays on every allocation request. - /// - public sealed class SimpleGcMemoryAllocator : MemoryAllocator - { - /// - protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + /// + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + /// + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - return new BasicArrayBuffer(new T[length]); - } + return new BasicArrayBuffer(new T[length]); } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index af04718e83..bf75a716dd 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -1,168 +1,166 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator { - internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator + private const int OneMegabyte = 1 << 20; + + // 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values: + private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; + private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; + private readonly int sharedArrayPoolThresholdInBytes; + private readonly int poolBufferSizeInBytes; + private readonly int poolCapacity; + private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings; + + private readonly UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryAllocator nonPoolAllocator; + + public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes) + : this( + DefaultContiguousPoolBlockSizeBytes, + maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), + DefaultNonPoolBlockSizeBytes) { - private const int OneMegabyte = 1 << 20; - - // 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values: - private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; - private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; - private readonly int sharedArrayPoolThresholdInBytes; - private readonly int poolBufferSizeInBytes; - private readonly int poolCapacity; - private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings; - - private readonly UniformUnmanagedMemoryPool pool; - private readonly UnmanagedMemoryAllocator nonPoolAllocator; - - public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes) - : this( - DefaultContiguousPoolBlockSizeBytes, - maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), - DefaultNonPoolBlockSizeBytes) - { - } - - public UniformUnmanagedMemoryPoolMemoryAllocator( - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes) - : this( - OneMegabyte, - poolBufferSizeInBytes, - maxPoolSizeInBytes, - unmanagedBufferSizeInBytes) - { - } - - internal UniformUnmanagedMemoryPoolMemoryAllocator( - int sharedArrayPoolThresholdInBytes, - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes) - : this( - sharedArrayPoolThresholdInBytes, - poolBufferSizeInBytes, - maxPoolSizeInBytes, - unmanagedBufferSizeInBytes, - UniformUnmanagedMemoryPool.TrimSettings.Default) - { - } + } - internal UniformUnmanagedMemoryPoolMemoryAllocator( - int sharedArrayPoolThresholdInBytes, - int poolBufferSizeInBytes, - long maxPoolSizeInBytes, - int unmanagedBufferSizeInBytes, - UniformUnmanagedMemoryPool.TrimSettings trimSettings) - { - this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; - this.poolBufferSizeInBytes = poolBufferSizeInBytes; - this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); - this.trimSettings = trimSettings; - this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings); - this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); - } + public UniformUnmanagedMemoryPoolMemoryAllocator( + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + : this( + OneMegabyte, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes) + { + } - // This delegate allows overriding the method returning the available system memory, - // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466 - internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + : this( + sharedArrayPoolThresholdInBytes, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings.Default) + { + } - /// - protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings trimSettings) + { + this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; + this.poolBufferSizeInBytes = poolBufferSizeInBytes; + this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); + this.trimSettings = trimSettings; + this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings); + this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); + } - /// - public override IMemoryOwner Allocate( - int length, - AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - int lengthInBytes = length * Unsafe.SizeOf(); + // This delegate allows overriding the method returning the available system memory, + // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466 + internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; - if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) - { - var buffer = new SharedArrayPoolBuffer(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } + /// + protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; - return buffer; - } + /// + public override IMemoryOwner Allocate( + int length, + AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int lengthInBytes = length * Unsafe.SizeOf(); - if (lengthInBytes <= this.poolBufferSizeInBytes) + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer(length); + if (options.Has(AllocationOptions.Clean)) { - UnmanagedMemoryHandle mem = this.pool.Rent(); - if (mem.IsValid) - { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return buffer; - } + buffer.GetSpan().Clear(); } - return this.nonPoolAllocator.Allocate(length, options); + return buffer; } - /// - internal override MemoryGroup AllocateGroup( - long totalLength, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) + if (lengthInBytes <= this.poolBufferSizeInBytes) { - long totalLengthInBytes = totalLength * Unsafe.SizeOf(); - if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) { - var buffer = new SharedArrayPoolBuffer((int)totalLength); - return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); + return buffer; } + } - if (totalLengthInBytes <= this.poolBufferSizeInBytes) - { - // Optimized path renting single array from the pool - UnmanagedMemoryHandle mem = this.pool.Rent(); - if (mem.IsValid) - { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options.Has(AllocationOptions.Clean)); - return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); - } - } + return this.nonPoolAllocator.Allocate(length, options); + } + + /// + internal override MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + long totalLengthInBytes = totalLength * Unsafe.SizeOf(); + if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer((int)totalLength); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + } - // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: - if (MemoryGroup.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup poolGroup)) + if (totalLengthInBytes <= this.poolBufferSizeInBytes) + { + // Optimized path renting single array from the pool + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) { - return poolGroup; + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options.Has(AllocationOptions.Clean)); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); } + } - return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: + if (MemoryGroup.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup poolGroup)) + { + return poolGroup; } - public override void ReleaseRetainedResources() => this.pool.Release(); + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + } + + public override void ReleaseRetainedResources() => this.pool.Release(); - private static long GetDefaultMaxPoolSizeBytes() + private static long GetDefaultMaxPoolSizeBytes() + { + // On 64 bit set the pool size to a portion of the total available memory. + // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 + if (Environment.Is64BitProcess) { - // On 64 bit set the pool size to a portion of the total available memory. - // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 - if (Environment.Is64BitProcess) - { - long total = GetTotalAvailableMemoryBytes(); + long total = GetTotalAvailableMemoryBytes(); - // Workaround for https://github.com/dotnet/runtime/issues/65466 - if (total > 0) - { - return total / 8; - } + // Workaround for https://github.com/dotnet/runtime/issues/65466 + if (total > 0) + { + return total / 8; } - - // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: - return 128 * OneMegabyte; } + + // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: + return 128 * OneMegabyte; } } diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index 2ad922cce0..da202aa596 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -1,33 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// A implementation that allocates memory on the unmanaged heap +/// without any pooling. +/// +internal class UnmanagedMemoryAllocator : MemoryAllocator { - /// - /// A implementation that allocates memory on the unmanaged heap - /// without any pooling. - /// - internal class UnmanagedMemoryAllocator : MemoryAllocator - { - private readonly int bufferCapacityInBytes; + private readonly int bufferCapacityInBytes; - public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes; + public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes; - protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; + protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + var buffer = UnmanagedBuffer.Allocate(length); + if (options.Has(AllocationOptions.Clean)) { - var buffer = UnmanagedBuffer.Allocate(length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; + buffer.GetSpan().Clear(); } + + return buffer; } } diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 74295fa60f..2eb05ea935 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -1,141 +1,138 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Defines extension methods for . +/// +public static class Buffer2DExtensions { /// - /// Defines extension methods for . + /// Gets the backing . /// - public static class Buffer2DExtensions + /// The buffer. + /// The element type. + /// The MemoryGroup. + public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) + where T : struct { - /// - /// Gets the backing . - /// - /// The buffer. - /// The element type. - /// The MemoryGroup. - public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.FastMemoryGroup.View; - } + Guard.NotNull(buffer, nameof(buffer)); + 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 . - /// - internal static unsafe void DangerousCopyColumns( - this Buffer2D buffer, - int sourceIndex, - int destIndex, - int columnCount) - where T : struct - { - DebugGuard.NotNull(buffer, nameof(buffer)); - DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); - DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); - CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); + /// + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. + /// Copy columns of inplace, + /// from positions starting at to positions at . + /// + internal static unsafe void DangerousCopyColumns( + this Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) + where T : struct + { + DebugGuard.NotNull(buffer, nameof(buffer)); + DebugGuard.MustBeGreaterThanOrEqualTo(sourceIndex, 0, nameof(sourceIndex)); + DebugGuard.MustBeGreaterThanOrEqualTo(destIndex, 0, nameof(sourceIndex)); + CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destIndex, columnCount); - int elementSize = Unsafe.SizeOf(); - int width = buffer.Width * elementSize; - int sOffset = sourceIndex * elementSize; - int dOffset = destIndex * elementSize; - long count = columnCount * elementSize; + int elementSize = Unsafe.SizeOf(); + int width = buffer.Width * elementSize; + int sOffset = sourceIndex * elementSize; + int dOffset = destIndex * elementSize; + long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span); + Span span = MemoryMarshal.AsBytes(buffer.DangerousGetSingleMemory().Span); - fixed (byte* ptr = span) + fixed (byte* ptr = span) + { + byte* basePtr = ptr; + for (int y = 0; y < buffer.Height; y++) { - byte* basePtr = ptr; - for (int y = 0; y < buffer.Height; y++) - { - byte* sPtr = basePtr + sOffset; - byte* dPtr = basePtr + dOffset; + byte* sPtr = basePtr + sOffset; + byte* dPtr = basePtr + dOffset; - Buffer.MemoryCopy(sPtr, dPtr, count, count); + Buffer.MemoryCopy(sPtr, dPtr, count, count); - basePtr += width; - } + basePtr += width; } } + } - /// - /// Returns a representing the full area of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle FullRectangle(this Buffer2D buffer) - where T : struct - { - return new Rectangle(0, 0, buffer.Width, buffer.Height); - } + /// + /// Returns a representing the full area of the buffer. + /// + /// The element type + /// The + /// The + internal static Rectangle FullRectangle(this Buffer2D buffer) + where T : struct + { + return new Rectangle(0, 0, buffer.Width, buffer.Height); + } - /// - /// Return a to the subregion represented by 'rectangle' - /// - /// The element type - /// The - /// The rectangle subregion - /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) - where T : unmanaged => - new Buffer2DRegion(buffer, rectangle); - - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) - where T : unmanaged => - new Buffer2DRegion(buffer, new Rectangle(x, y, width, height)); - - /// - /// Return a to the whole area of 'buffer' - /// - /// The element type - /// The - /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer) - where T : unmanaged => - new Buffer2DRegion(buffer); - - /// - /// Returns the size of the buffer. - /// - /// The element type - /// The - /// The of the buffer - internal static Size Size(this Buffer2D buffer) - where T : struct => - new(buffer.Width, buffer.Height); - - /// - /// Gets the bounds of the buffer. - /// - /// The - internal static Rectangle Bounds(this Buffer2D buffer) - where T : struct => - new(0, 0, buffer.Width, buffer.Height); - - [Conditional("DEBUG")] - private static void CheckColumnRegionsDoNotOverlap( - Buffer2D buffer, - int sourceIndex, - int destIndex, - int columnCount) - where T : struct + /// + /// Return a to the subregion represented by 'rectangle' + /// + /// The element type + /// The + /// The rectangle subregion + /// The + internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) + where T : unmanaged => + new Buffer2DRegion(buffer, rectangle); + + internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) + where T : unmanaged => + new Buffer2DRegion(buffer, new Rectangle(x, y, width, height)); + + /// + /// Return a to the whole area of 'buffer' + /// + /// The element type + /// The + /// The + internal static Buffer2DRegion GetRegion(this Buffer2D buffer) + where T : unmanaged => + new Buffer2DRegion(buffer); + + /// + /// Returns the size of the buffer. + /// + /// The element type + /// The + /// The of the buffer + internal static Size Size(this Buffer2D buffer) + where T : struct => + new(buffer.Width, buffer.Height); + + /// + /// Gets the bounds of the buffer. + /// + /// The + internal static Rectangle Bounds(this Buffer2D buffer) + where T : struct => + new(0, 0, buffer.Width, buffer.Height); + + [Conditional("DEBUG")] + private static void CheckColumnRegionsDoNotOverlap( + Buffer2D buffer, + int sourceIndex, + int destIndex, + int columnCount) + where T : struct + { + int minIndex = Math.Min(sourceIndex, destIndex); + int maxIndex = Math.Max(sourceIndex, destIndex); + if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount) { - int minIndex = Math.Min(sourceIndex, destIndex); - int maxIndex = Math.Max(sourceIndex, destIndex); - if (maxIndex < minIndex + columnCount || maxIndex > buffer.Width - columnCount) - { - throw new InvalidOperationException("Column regions should not overlap!"); - } + throw new InvalidOperationException("Column regions should not overlap!"); } } } diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index 63947aedb2..033b0a25a6 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Represents a rectangular region inside a 2D memory buffer (). +/// +/// The element type. +public readonly struct Buffer2DRegion + where T : unmanaged { /// - /// Represents a rectangular region inside a 2D memory buffer (). + /// Initializes a new instance of the struct. /// - /// The element type. - public readonly struct Buffer2DRegion - where T : unmanaged + /// The . + /// The defining a rectangular area within the buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer2DRegion(Buffer2D buffer, Rectangle rectangle) { - /// - /// Initializes a new instance of the struct. - /// - /// The . - /// The defining a rectangular area within the buffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer, Rectangle rectangle) - { - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); + DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); + DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); - this.Buffer = buffer; - this.Rectangle = rectangle; - } + this.Buffer = buffer; + this.Rectangle = rectangle; + } - /// - /// Initializes a new instance of the struct. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer) - : this(buffer, buffer.FullRectangle()) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer2DRegion(Buffer2D buffer) + : this(buffer, buffer.FullRectangle()) + { + } - /// - /// Gets the rectangle specifying the boundaries of the area in . - /// - public Rectangle Rectangle { get; } - - /// - /// Gets the being pointed by this instance. - /// - public Buffer2D Buffer { get; } - - /// - /// Gets the width - /// - public int Width => this.Rectangle.Width; - - /// - /// Gets the height - /// - public int Height => this.Rectangle.Height; - - /// - /// Gets the pixel stride which is equal to the width of . - /// - public int Stride => this.Buffer.Width; - - /// - /// Gets the size of the area. - /// - internal Size Size => this.Rectangle.Size; - - /// - /// Gets a value indicating whether the area refers to the entire - /// - internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); - - /// - /// Gets or sets a value at the given index. - /// - /// The position inside a row - /// The row index - /// The reference to the value - internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; - - /// - /// Gets a span to row 'y' inside this area. - /// - /// The row index - /// The span - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span DangerousGetRowSpan(int y) - { - int yy = this.Rectangle.Y + y; - int xx = this.Rectangle.X; - int width = this.Rectangle.Width; + /// + /// Gets the rectangle specifying the boundaries of the area in . + /// + public Rectangle Rectangle { get; } - return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); - } + /// + /// Gets the being pointed by this instance. + /// + public Buffer2D Buffer { get; } - /// - /// Returns a subregion as . (Similar to .) - /// - /// The x index at the subregion origin. - /// The y index at the subregion origin. - /// The desired width of the subregion. - /// The desired height of the subregion. - /// The subregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion GetSubRegion(int x, int y, int width, int height) - { - var rectangle = new Rectangle(x, y, width, height); - return this.GetSubRegion(rectangle); - } + /// + /// Gets the width + /// + public int Width => this.Rectangle.Width; - /// - /// Returns a subregion as . (Similar to .) - /// - /// The specifying the boundaries of the subregion - /// The subregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion GetSubRegion(Rectangle rectangle) - { - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + /// + /// Gets the height + /// + public int Height => this.Rectangle.Height; + + /// + /// Gets the pixel stride which is equal to the width of . + /// + public int Stride => this.Buffer.Width; + + /// + /// Gets the size of the area. + /// + internal Size Size => this.Rectangle.Size; - int x = this.Rectangle.X + rectangle.X; - int y = this.Rectangle.Y + rectangle.Y; - rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); - return new Buffer2DRegion(this.Buffer, rectangle); + /// + /// Gets a value indicating whether the area refers to the entire + /// + internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); + + /// + /// Gets or sets a value at the given index. + /// + /// The position inside a row + /// The row index + /// The reference to the value + internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; + + /// + /// Gets a span to row 'y' inside this area. + /// + /// The row index + /// The span + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span DangerousGetRowSpan(int y) + { + int yy = this.Rectangle.Y + y; + int xx = this.Rectangle.X; + int width = this.Rectangle.Width; + + return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); + } + + /// + /// Returns a subregion as . (Similar to .) + /// + /// The x index at the subregion origin. + /// The y index at the subregion origin. + /// The desired width of the subregion. + /// The desired height of the subregion. + /// The subregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer2DRegion GetSubRegion(int x, int y, int width, int height) + { + var rectangle = new Rectangle(x, y, width, height); + return this.GetSubRegion(rectangle); + } + + /// + /// Returns a subregion as . (Similar to .) + /// + /// The specifying the boundaries of the subregion + /// The subregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer2DRegion GetSubRegion(Rectangle rectangle) + { + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + + int x = this.Rectangle.X + rectangle.X; + int y = this.Rectangle.Y + rectangle.Y; + rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); + return new Buffer2DRegion(this.Buffer, rectangle); + } + + /// + /// Gets a reference to the [0,0] element. + /// + /// The reference to the [0,0] element + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T GetReferenceToOrigin() + { + int y = this.Rectangle.Y; + int x = this.Rectangle.X; + return ref this.Buffer.DangerousGetRowSpan(y)[x]; + } + + /// + /// Clears the contents of this . + /// + internal void Clear() + { + // Optimization for when the size of the area is the same as the buffer size. + if (this.IsFullBufferArea) + { + this.Buffer.FastMemoryGroup.Clear(); + return; } - /// - /// Gets a reference to the [0,0] element. - /// - /// The reference to the [0,0] element - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ref T GetReferenceToOrigin() + for (int y = 0; y < this.Rectangle.Height; y++) { - int y = this.Rectangle.Y; - int x = this.Rectangle.X; - return ref this.Buffer.DangerousGetRowSpan(y)[x]; + Span row = this.DangerousGetRowSpan(y); + row.Clear(); } + } - /// - /// Clears the contents of this . - /// - internal void Clear() + /// + /// Fills the elements of this with the specified value. + /// + /// The value to assign to each element of the region. + internal void Fill(T value) + { + // Optimization for when the size of the area is the same as the buffer size. + if (this.IsFullBufferArea) { - // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) - { - this.Buffer.FastMemoryGroup.Clear(); - return; - } - - for (int y = 0; y < this.Rectangle.Height; y++) - { - Span row = this.DangerousGetRowSpan(y); - row.Clear(); - } + this.Buffer.FastMemoryGroup.Fill(value); + return; } - /// - /// Fills the elements of this with the specified value. - /// - /// The value to assign to each element of the region. - internal void Fill(T value) + for (int y = 0; y < this.Rectangle.Height; y++) { - // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) - { - this.Buffer.FastMemoryGroup.Fill(value); - return; - } - - for (int y = 0; y < this.Rectangle.Height; y++) - { - Span row = this.DangerousGetRowSpan(y); - row.Fill(value); - } + Span row = this.DangerousGetRowSpan(y); + row.Fill(value); } } } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index c73b96ae60..8d6465389f 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,205 +1,201 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Represents a buffer of value type objects +/// interpreted as a 2D region of x elements. +/// +/// +/// Before RC1, this class might be target of API changes, use it on your own risk! +/// +/// The value type. +public sealed class Buffer2D : IDisposable + where T : struct { /// - /// Represents a buffer of value type objects - /// interpreted as a 2D region of x elements. + /// Initializes a new instance of the class. /// - /// - /// Before RC1, this class might be target of API changes, use it on your own risk! - /// - /// The value type. - public sealed class Buffer2D : IDisposable - where T : struct + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - /// - /// Initializes a new instance of the class. - /// - /// The to wrap. - /// The number of elements in a row. - /// The number of rows. - internal Buffer2D(MemoryGroup memoryGroup, int width, int height) - { - this.FastMemoryGroup = memoryGroup; - this.Width = width; - this.Height = height; - } + this.FastMemoryGroup = memoryGroup; + this.Width = width; + this.Height = height; + } - /// - /// Gets the width. - /// - public int Width { get; private set; } - - /// - /// Gets the height. - /// - public int Height { get; private set; } - - /// - /// Gets the backing . - /// - /// The MemoryGroup. - public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; - - /// - /// Gets the backing without the view abstraction. - /// - /// - /// This property has been kept internal intentionally. - /// It's public counterpart is , - /// which only exposes the view of the MemoryGroup. - /// - internal MemoryGroup FastMemoryGroup { get; private set; } - - /// - /// Gets a reference to the element at the specified position. - /// - /// The x coordinate (row) - /// The y coordinate (position at row) - /// A reference to the element. - /// When index is out of range of the buffer. - public ref T this[int x, int y] - { - [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)); + /// + /// Gets the width. + /// + public int Width { get; private set; } - return ref this.DangerousGetRowSpan(y)[x]; - } - } + /// + /// Gets the height. + /// + public int Height { get; private set; } + + /// + /// Gets the backing . + /// + /// The MemoryGroup. + public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; + + /// + /// Gets the backing without the view abstraction. + /// + /// + /// This property has been kept internal intentionally. + /// It's public counterpart is , + /// which only exposes the view of the MemoryGroup. + /// + internal MemoryGroup FastMemoryGroup { get; private set; } - /// - /// Disposes the instance - /// - public void Dispose() => this.FastMemoryGroup.Dispose(); - - /// - /// 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. + /// + /// Gets a reference to the element at the specified position. + /// + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + /// When index is out of range of the buffer. + public ref T this[int x, int y] + { [MethodImpl(InliningOptions.ShortMethod)] - public Span DangerousGetRowSpan(int y) + get { - if ((uint)y >= (uint)this.Height) - { - this.ThrowYOutOfRangeException(y); - } + 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)); - return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + return ref this.DangerousGetRowSpan(y)[x]; } + } - internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) + /// + /// Disposes the instance + /// + public void Dispose() => this.FastMemoryGroup.Dispose(); + + /// + /// 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 DangerousGetRowSpan(int y) + { + if ((uint)y >= (uint)this.Height) { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + this.ThrowYOutOfRangeException(y); + } - int stride = this.Width + padding; + return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + } - Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); + internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - if (slice.Length < stride) - { - paddedSpan = default; - return false; - } + int stride = this.Width + padding; - paddedSpan = slice[..stride]; - return true; - } + Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); - [MethodImpl(InliningOptions.ShortMethod)] - internal ref T GetElementUnsafe(int x, int y) + if (slice.Length < stride) { - Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); - return ref span[x]; + paddedSpan = default; + return false; } - /// - /// 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.GetBoundedMemorySlice(y * (long)this.Width, this.Width); - } + paddedSpan = slice[..stride]; + return true; + } - /// - /// 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 DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; - - /// - /// 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 DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); + [MethodImpl(InliningOptions.ShortMethod)] + internal ref T GetElementUnsafe(int x, int y) + { + Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + return ref span[x]; + } + + /// + /// 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.GetBoundedMemorySlice(y * (long)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 DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; + + /// + /// 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 DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); - /// - /// 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! - /// - internal static bool SwapOrCopyContent(Buffer2D destination, Buffer2D source) + /// + /// 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! + /// + internal static bool SwapOrCopyContent(Buffer2D destination, Buffer2D source) + { + bool swapped = false; + if (MemoryGroup.CanSwapContent(destination.FastMemoryGroup, source.FastMemoryGroup)) { - bool swapped = false; - if (MemoryGroup.CanSwapContent(destination.FastMemoryGroup, source.FastMemoryGroup)) - { - (destination.FastMemoryGroup, source.FastMemoryGroup) = - (source.FastMemoryGroup, destination.FastMemoryGroup); - destination.FastMemoryGroup.RecreateViewAfterSwap(); - source.FastMemoryGroup.RecreateViewAfterSwap(); - swapped = true; - } - else + (destination.FastMemoryGroup, source.FastMemoryGroup) = + (source.FastMemoryGroup, destination.FastMemoryGroup); + destination.FastMemoryGroup.RecreateViewAfterSwap(); + source.FastMemoryGroup.RecreateViewAfterSwap(); + swapped = true; + } + else + { + if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.TotalLength) { - if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.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.FastMemoryGroup.CopyTo(destination.MemoryGroup); + throw new InvalidMemoryOperationException( + "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); } - (destination.Width, source.Width) = (source.Width, destination.Width); - (destination.Height, source.Height) = (source.Height, destination.Height); - return swapped; + source.FastMemoryGroup.CopyTo(destination.MemoryGroup); } - [MethodImpl(InliningOptions.ColdPath)] - private void ThrowYOutOfRangeException(int y) => - throw new ArgumentOutOfRangeException( - $"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); + (destination.Width, source.Width) = (source.Width, destination.Width); + (destination.Height, source.Height) = (source.Height, destination.Height); + return swapped; } + + [MethodImpl(InliningOptions.ColdPath)] + private void ThrowYOutOfRangeException(int y) => + throw new ArgumentOutOfRangeException( + $"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); } diff --git a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs index f6d79642a3..5efb8b32b7 100644 --- a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs +++ b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs @@ -1,58 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// A custom that can wrap of instances +/// and cast them to be for any arbitrary unmanaged value type. +/// +/// The value type to use when casting the wrapped instance. +internal sealed class ByteMemoryManager : MemoryManager + where T : unmanaged { /// - /// A custom that can wrap of instances - /// and cast them to be for any arbitrary unmanaged value type. + /// The wrapped of instance. + /// + private readonly Memory memory; + + /// + /// Initializes a new instance of the class. /// - /// The value type to use when casting the wrapped instance. - internal sealed class ByteMemoryManager : MemoryManager - where T : unmanaged + /// The of instance to wrap. + public ByteMemoryManager(Memory memory) + { + this.memory = memory; + } + + /// + protected override void Dispose(bool disposing) + { + } + + /// + public override Span GetSpan() + { + return MemoryMarshal.Cast(this.memory.Span); + } + + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + // We need to adjust the offset into the wrapped byte segment, + // as the input index refers to the target-cast memory of T. + // We just have to shift this index by the byte size of T. + return this.memory[(elementIndex * Unsafe.SizeOf())..].Pin(); + } + + /// + public override void Unpin() { - /// - /// The wrapped of instance. - /// - private readonly Memory memory; - - /// - /// Initializes a new instance of the class. - /// - /// The of instance to wrap. - public ByteMemoryManager(Memory memory) - { - this.memory = memory; - } - - /// - protected override void Dispose(bool disposing) - { - } - - /// - public override Span GetSpan() - { - return MemoryMarshal.Cast(this.memory.Span); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - // We need to adjust the offset into the wrapped byte segment, - // as the input index refers to the target-cast memory of T. - // We just have to shift this index by the byte size of T. - return this.memory[(elementIndex * Unsafe.SizeOf())..].Pin(); - } - - /// - public override void Unpin() - { - } } } diff --git a/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs b/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs index 64f28b0d84..bc71751412 100644 --- a/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs +++ b/src/ImageSharp/Memory/ByteMemoryOwner{T}.cs @@ -1,54 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// A custom that can wrap of instances +/// and cast them to be for any arbitrary unmanaged value type. +/// +/// The value type to use when casting the wrapped instance. +internal sealed class ByteMemoryOwner : IMemoryOwner + where T : unmanaged { + private readonly IMemoryOwner memoryOwner; + private readonly ByteMemoryManager memoryManager; + private bool disposedValue; + /// - /// A custom that can wrap of instances - /// and cast them to be for any arbitrary unmanaged value type. + /// Initializes a new instance of the class. /// - /// The value type to use when casting the wrapped instance. - internal sealed class ByteMemoryOwner : IMemoryOwner - where T : unmanaged + /// The of instance to wrap. + public ByteMemoryOwner(IMemoryOwner memoryOwner) { - private readonly IMemoryOwner memoryOwner; - private readonly ByteMemoryManager memoryManager; - private bool disposedValue; - - /// - /// Initializes a new instance of the class. - /// - /// The of instance to wrap. - public ByteMemoryOwner(IMemoryOwner memoryOwner) - { - this.memoryOwner = memoryOwner; - this.memoryManager = new ByteMemoryManager(memoryOwner.Memory); - } + this.memoryOwner = memoryOwner; + this.memoryManager = new ByteMemoryManager(memoryOwner.Memory); + } - /// - public Memory Memory => this.memoryManager.Memory; + /// + public Memory Memory => this.memoryManager.Memory; - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!this.disposedValue) { - if (!this.disposedValue) + if (disposing) { - if (disposing) - { - this.memoryOwner.Dispose(); - } - - this.disposedValue = true; + this.memoryOwner.Dispose(); } - } - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - this.Dispose(disposing: true); + this.disposedValue = true; } } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index d7ac480581..7e9719ea75 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -1,47 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Memory; -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 { /// - /// Represents discontiguous group of multiple uniformly-sized memory segments. - /// The last segment can be smaller than the preceding ones. + /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. + /// The last buffer is allowed to be smaller. /// - /// 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; } + int BufferLength { get; } - /// - /// Gets the aggregate number of elements in the group. - /// - long TotalLength { 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; } + /// + /// 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; } - /// - /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current - /// instance. The return type shouldn't be used directly: just use a block on - /// the instance in use and the C# compiler will automatically invoke this - /// method behind the scenes. This method takes precedence over the - /// implementation, which is still available when casting to one of the underlying interfaces. - /// - /// A new instance mapping the current values in use. - new MemoryGroupEnumerator GetEnumerator(); - } + /// + /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current + /// instance. The return type shouldn't be used directly: just use a block on + /// the instance in use and the C# compiler will automatically invoke this + /// method behind the scenes. This method takes precedence over the + /// implementation, which is still available when casting to one of the underlying interfaces. + /// + /// A new instance mapping the current values in use. + new MemoryGroupEnumerator GetEnumerator(); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs index 01f18cf8d0..aaff3ec677 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupEnumerator{T}.cs @@ -1,69 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// A value-type enumerator for instances. +/// +/// The element type. +[EditorBrowsable(EditorBrowsableState.Never)] +public ref struct MemoryGroupEnumerator + where T : struct { - /// - /// A value-type enumerator for instances. - /// - /// The element type. - [EditorBrowsable(EditorBrowsableState.Never)] - public ref struct MemoryGroupEnumerator - where T : struct + private readonly IMemoryGroup memoryGroup; + private readonly int count; + private int index; + + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup) { - private readonly IMemoryGroup memoryGroup; - private readonly int count; - private int index; + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } - [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroup.Owned memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup) + { + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } - [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroup.Consumed memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } + [MethodImpl(InliningOptions.ShortMethod)] + internal MemoryGroupEnumerator(MemoryGroupView memoryGroup) + { + this.memoryGroup = memoryGroup; + this.count = memoryGroup.Count; + this.index = -1; + } + /// + public Memory Current + { [MethodImpl(InliningOptions.ShortMethod)] - internal MemoryGroupEnumerator(MemoryGroupView memoryGroup) - { - this.memoryGroup = memoryGroup; - this.count = memoryGroup.Count; - this.index = -1; - } + get => this.memoryGroup[this.index]; + } - /// - public Memory Current - { - [MethodImpl(InliningOptions.ShortMethod)] - get => this.memoryGroup[this.index]; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool MoveNext() + { + int index = this.index + 1; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool MoveNext() + if (index < this.count) { - int index = this.index + 1; + this.index = index; - if (index < this.count) - { - this.index = index; - - return true; - } - - return false; + return true; } + + return false; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 51774c8816..4656e6ef95 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -1,244 +1,241 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Memory +internal static class MemoryGroupExtensions { - internal static class MemoryGroupExtensions + /// + /// Fills the elements of this with the specified value. + /// + /// The type of element. + /// The group to fill. + /// The value to assign to each element of the group. + internal static void Fill(this IMemoryGroup group, T value) + where T : struct { - /// - /// Fills the elements of this with the specified value. - /// - /// The type of element. - /// The group to fill. - /// The value to assign to each element of the group. - internal static void Fill(this IMemoryGroup group, T value) - where T : struct + foreach (Memory memory in group) { - foreach (Memory memory in group) - { - memory.Span.Fill(value); - } + memory.Span.Fill(value); } + } - /// - /// Clears the contents of this . - /// - /// The type of element. - /// The group to clear. - internal static void Clear(this IMemoryGroup group) - where T : struct + /// + /// Clears the contents of this . + /// + /// The type of element. + /// The group to clear. + internal static void Clear(this IMemoryGroup group) + where T : struct + { + foreach (Memory memory in group) { - foreach (Memory memory in group) - { - memory.Span.Clear(); - } + memory.Span.Clear(); } + } - /// - /// Returns a slice that is expected to be within the bounds of a single buffer. - /// Otherwise is thrown. - /// - internal static Memory GetBoundedMemorySlice(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)Math.DivRem(start, group.BufferLength, out long bufferStartLong); - int bufferStart = (int)bufferStartLong; + /// + /// Returns a slice that is expected to be within the bounds of a single buffer. + /// Otherwise is thrown. + /// + internal static Memory GetBoundedMemorySlice(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)); - // if (bufferIdx < 0 || bufferIdx >= group.Count) - if ((uint)bufferIdx >= group.Count) - { - throw new ArgumentOutOfRangeException(nameof(start)); - } + int bufferIdx = (int)Math.DivRem(start, group.BufferLength, out long bufferStartLong); + int bufferStart = (int)bufferStartLong; - int bufferEnd = bufferStart + length; - Memory memory = group[bufferIdx]; + // if (bufferIdx < 0 || bufferIdx >= group.Count) + if ((uint)bufferIdx >= group.Count) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } - if (bufferEnd > memory.Length) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } + int bufferEnd = bufferStart + length; + Memory memory = group[bufferIdx]; - return memory.Slice(bufferStart, length); + if (bufferEnd > memory.Length) + { + throw new ArgumentOutOfRangeException(nameof(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)); + return memory.Slice(bufferStart, length); + } - 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); + 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)); - cur.Forward(fwd); - target = target[fwd..]; - position += fwd; - } + 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[fwd..]; + position += fwd; } + } - internal static void CopyTo(this Span source, IMemoryGroup target) - where T : struct - => CopyTo((ReadOnlySpan)source, target); + 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)); + 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); + var cur = new MemoryGroupCursor(target); - while (!source.IsEmpty) - { - int fwd = Math.Min(cur.LookAhead(), source.Length); - source[..fwd].CopyTo(cur.GetSpan(fwd)); - cur.Forward(fwd); - source = source[fwd..]; - } + while (!source.IsEmpty) + { + int fwd = Math.Min(cur.LookAhead(), source.Length); + source[..fwd].CopyTo(cur.GetSpan(fwd)); + cur.Forward(fwd); + source = source[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!"); + 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; - } + if (source.IsEmpty()) + { + return; + } - long position = 0; - var srcCur = new MemoryGroupCursor(source); - var trgCur = new MemoryGroupCursor(target); + 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; - } + 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 + 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()) { - 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; - } + return; + } - long position = 0; - var srcCur = new MemoryGroupCursor(source); - var trgCur = new MemoryGroupCursor(target); + 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; - } + 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 + internal static void TransformInplace( + this IMemoryGroup memoryGroup, + TransformItemsInplaceDelegate transform) + where T : struct + { + foreach (Memory memory in memoryGroup) { - foreach (Memory memory in memoryGroup) - { - transform(memory.Span); - } + 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; - internal static bool IsEmpty(this IMemoryGroup group) - where T : struct - => group.Count == 0; + private int elementIndex; - private struct MemoryGroupCursor - where T : struct + public MemoryGroupCursor(IMemoryGroup memoryGroup) { - private readonly IMemoryGroup memoryGroup; + this.memoryGroup = memoryGroup; + this.bufferIndex = 0; + this.elementIndex = 0; + } - private int bufferIndex; + private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; - private int elementIndex; + private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; - public MemoryGroupCursor(IMemoryGroup memoryGroup) - { - this.memoryGroup = memoryGroup; - this.bufferIndex = 0; - this.elementIndex = 0; - } + public Span GetSpan(int length) + { + return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + } - private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; + public int LookAhead() + { + return this.CurrentBufferLength - this.elementIndex; + } - private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; + public void Forward(int steps) + { + int nextIdx = this.elementIndex + steps; + int currentBufferLength = this.CurrentBufferLength; - public Span GetSpan(int length) + if (nextIdx < currentBufferLength) { - return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + this.elementIndex = nextIdx; } - - public int LookAhead() + else if (nextIdx == currentBufferLength) { - return this.CurrentBufferLength - this.elementIndex; + this.bufferIndex++; + this.elementIndex = 0; } - - public void Forward(int steps) + else { - 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)); - } + // 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/MemoryGroupSpanCache.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs index a91f03e595..36abfba886 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs @@ -1,52 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Cached pointer or array data enabling fast access from +/// known implementations. +/// +internal unsafe struct MemoryGroupSpanCache { - /// - /// Cached pointer or array data enabling fast access from - /// known implementations. - /// - internal unsafe struct MemoryGroupSpanCache - { - public SpanCacheMode Mode; - public byte[] SingleArray; - public void* SinglePointer; - public void*[] MultiPointer; + public SpanCacheMode Mode; + public byte[] SingleArray; + public void* SinglePointer; + public void*[] MultiPointer; - public static MemoryGroupSpanCache Create(IMemoryOwner[] memoryOwners) - where T : struct + public static MemoryGroupSpanCache Create(IMemoryOwner[] memoryOwners) + where T : struct + { + IMemoryOwner owner0 = memoryOwners[0]; + MemoryGroupSpanCache memoryGroupSpanCache = default; + if (memoryOwners.Length == 1) { - IMemoryOwner owner0 = memoryOwners[0]; - MemoryGroupSpanCache memoryGroupSpanCache = default; - if (memoryOwners.Length == 1) + if (owner0 is SharedArrayPoolBuffer sharedPoolBuffer) { - if (owner0 is SharedArrayPoolBuffer sharedPoolBuffer) - { - memoryGroupSpanCache.Mode = SpanCacheMode.SingleArray; - memoryGroupSpanCache.SingleArray = sharedPoolBuffer.Array; - } - else if (owner0 is UnmanagedBuffer unmanagedBuffer) - { - memoryGroupSpanCache.Mode = SpanCacheMode.SinglePointer; - memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; - } + memoryGroupSpanCache.Mode = SpanCacheMode.SingleArray; + memoryGroupSpanCache.SingleArray = sharedPoolBuffer.Array; } - else if (owner0 is UnmanagedBuffer) + else if (owner0 is UnmanagedBuffer unmanagedBuffer) { - memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; - memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; - for (int i = 0; i < memoryOwners.Length; i++) - { - memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; - } + memoryGroupSpanCache.Mode = SpanCacheMode.SinglePointer; + memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; } - - return memoryGroupSpanCache; } + else if (owner0 is UnmanagedBuffer) + { + memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; + memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; + for (int i = 0; i < memoryOwners.Length; i++) + { + memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; + } + } + + return memoryGroupSpanCache; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index fa972d859f..333fede6a6 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -1,145 +1,142 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Memory +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 { - /// - /// 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) { - private MemoryGroup owner; - private readonly MemoryOwnerWrapper[] memoryWrappers; + this.owner = owner; + this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; - public MemoryGroupView(MemoryGroup owner) + for (int i = 0; i < owner.Count; i++) { - this.owner = owner; - this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; + this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); + } + } - for (int i = 0; i < owner.Count; i++) - { - this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); - } + public int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.EnsureIsValid(); + return this.owner.Count; } + } - public int Count + public int BufferLength + { + get { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.EnsureIsValid(); - return this.owner.Count; - } + this.EnsureIsValid(); + return this.owner.BufferLength; } + } - public int BufferLength + public long TotalLength + { + get { - get - { - this.EnsureIsValid(); - return this.owner.BufferLength; - } + this.EnsureIsValid(); + return this.owner.TotalLength; } + } - public long TotalLength + public bool IsValid => this.owner != null; + + public Memory this[int index] + { + get { - get - { - this.EnsureIsValid(); - return this.owner.TotalLength; - } + this.EnsureIsValid(); + return this.memoryWrappers[index].Memory; } + } - public bool IsValid => this.owner != null; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } - public Memory this[int index] + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + this.EnsureIsValid(); + for (int i = 0; i < this.Count; i++) { - get - { - this.EnsureIsValid(); - return this.memoryWrappers[index].Memory; - } + yield return this.memoryWrappers[i].Memory; } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public MemoryGroupEnumerator GetEnumerator() + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + + internal void Invalidate() + { + this.owner = null; + } + + private void EnsureIsValid() + { + if (!this.IsValid) { - return new MemoryGroupEnumerator(this); + throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); } + } + + private class MemoryOwnerWrapper : MemoryManager + { + private readonly MemoryGroupView view; - /// - IEnumerator> IEnumerable>.GetEnumerator() + private readonly int index; + + public MemoryOwnerWrapper(MemoryGroupView view, int index) { - this.EnsureIsValid(); - for (int i = 0; i < this.Count; i++) - { - yield return this.memoryWrappers[i].Memory; - } + this.view = view; + this.index = index; } - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); + protected override void Dispose(bool disposing) + { + } - internal void Invalidate() + public override Span GetSpan() { - this.owner = null; + this.view.EnsureIsValid(); + return this.view.owner[this.index].Span; } - private void EnsureIsValid() + public override MemoryHandle Pin(int elementIndex = 0) { - if (!this.IsValid) - { - throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); - } + this.view.EnsureIsValid(); + return this.view.owner[this.index].Pin(); } - private class MemoryOwnerWrapper : MemoryManager + public override void Unpin() { - 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(); - } + throw new NotSupportedException(); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index abb3f47b75..950e2a019e 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -1,56 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +internal abstract partial class MemoryGroup { - internal abstract partial class MemoryGroup + /// + /// A implementation that consumes the underlying memory buffers. + /// + public sealed class Consumed : MemoryGroup, IEnumerable> { - /// - /// A implementation that consumes the underlying memory buffers. - /// - public sealed class Consumed : MemoryGroup, IEnumerable> + private readonly Memory[] source; + + public Consumed(Memory[] source, int bufferLength, long totalLength) + : base(bufferLength, totalLength) { - private readonly Memory[] source; + this.source = source; + this.View = new MemoryGroupView(this); + } - public Consumed(Memory[] source, int bufferLength, long totalLength) - : base(bufferLength, totalLength) - { - this.source = source; - this.View = new MemoryGroupView(this); - } + public override int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get => this.source.Length; + } - public override int Count - { - [MethodImpl(InliningOptions.ShortMethod)] - get => this.source.Length; - } + public override Memory this[int index] => this.source[index]; - public override Memory this[int index] => this.source[index]; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override MemoryGroupEnumerator GetEnumerator() + { + return new MemoryGroupEnumerator(this); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } - - /// - IEnumerator> IEnumerable>.GetEnumerator() - { - /* The runtime sees the Array class as if it implemented the - * type-generic collection interfaces explicitly, so here we - * can just cast the source array to IList> (or to - * an equivalent type), and invoke the generic GetEnumerator - * method directly from that interface reference. This saves - * having to create our own iterator block here. */ - return ((IList>)this.source).GetEnumerator(); - } - - public override void Dispose() => this.View.Invalidate(); + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + /* The runtime sees the Array class as if it implemented the + * type-generic collection interfaces explicitly, so here we + * can just cast the source array to IList> (or to + * an equivalent type), and invoke the generic GetEnumerator + * method directly from that interface reference. This saves + * having to create our own iterator block here. */ + return ((IList>)this.source).GetEnumerator(); } + + 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 index 4fb0711af5..8637ea7a33 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -1,223 +1,219 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +internal abstract partial class MemoryGroup { - internal abstract partial class MemoryGroup + /// + /// A implementation that owns the underlying memory buffers. + /// + public sealed class Owned : MemoryGroup, IEnumerable> { - /// - /// A implementation that owns the underlying memory buffers. - /// - public sealed class Owned : MemoryGroup, IEnumerable> - { - private IMemoryOwner[] memoryOwners; - private RefCountedMemoryLifetimeGuard groupLifetimeGuard; + private IMemoryOwner[] memoryOwners; + private RefCountedMemoryLifetimeGuard groupLifetimeGuard; - public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) - : base(bufferLength, totalLength) - { - this.memoryOwners = memoryOwners; - this.Swappable = swappable; - this.View = new MemoryGroupView(this); - this.memoryGroupSpanCache = MemoryGroupSpanCache.Create(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); + this.memoryGroupSpanCache = MemoryGroupSpanCache.Create(memoryOwners); + } - public Owned( - UniformUnmanagedMemoryPool pool, - UnmanagedMemoryHandle[] pooledHandles, - int bufferLength, - long totalLength, - int sizeOfLastBuffer, - AllocationOptions options) - : this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) => - this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles); + public Owned( + UniformUnmanagedMemoryPool pool, + UnmanagedMemoryHandle[] pooledHandles, + int bufferLength, + long totalLength, + int sizeOfLastBuffer, + AllocationOptions options) + : this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) => + this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles); - public bool Swappable { get; } + public bool Swappable { get; } - private bool IsDisposed => this.memoryOwners == null; + private bool IsDisposed => this.memoryOwners == null; - public override int Count + public override int Count + { + [MethodImpl(InliningOptions.ShortMethod)] + get { - [MethodImpl(InliningOptions.ShortMethod)] - get - { - this.EnsureNotDisposed(); - return this.memoryOwners.Length; - } + this.EnsureNotDisposed(); + return this.memoryOwners.Length; } + } - public override Memory this[int index] + public override Memory this[int index] + { + get { - get - { - this.EnsureNotDisposed(); - return this.memoryOwners[index].Memory; - } + this.EnsureNotDisposed(); + return this.memoryOwners[index].Memory; } + } - private static IMemoryOwner[] CreateBuffers( - UnmanagedMemoryHandle[] pooledBuffers, - int bufferLength, - int sizeOfLastBuffer, - AllocationOptions options) + private static IMemoryOwner[] CreateBuffers( + UnmanagedMemoryHandle[] pooledBuffers, + int bufferLength, + int sizeOfLastBuffer, + AllocationOptions options) + { + var result = new IMemoryOwner[pooledBuffers.Length]; + for (int i = 0; i < pooledBuffers.Length - 1; i++) { - var result = new IMemoryOwner[pooledBuffers.Length]; - for (int i = 0; i < pooledBuffers.Length - 1; i++) - { - var currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options); - result[i] = currentBuffer; - } - - var lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); - result[result.Length - 1] = lastBuffer; - return result; + var currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options); + result[i] = currentBuffer; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() => new(this); + var lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); + result[result.Length - 1] = lastBuffer; + return result; + } - public override void IncreaseRefCounts() - { - this.EnsureNotDisposed(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override MemoryGroupEnumerator GetEnumerator() => new(this); - if (this.groupLifetimeGuard != null) - { - this.groupLifetimeGuard.AddRef(); - } - else + public override void IncreaseRefCounts() + { + this.EnsureNotDisposed(); + + if (this.groupLifetimeGuard != null) + { + this.groupLifetimeGuard.AddRef(); + } + else + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + if (memoryOwner is IRefCounted unmanagedBuffer) { - if (memoryOwner is IRefCounted unmanagedBuffer) - { - unmanagedBuffer.AddRef(); - } + unmanagedBuffer.AddRef(); } } } + } - public override void DecreaseRefCounts() + public override void DecreaseRefCounts() + { + this.EnsureNotDisposed(); + if (this.groupLifetimeGuard != null) { - this.EnsureNotDisposed(); - if (this.groupLifetimeGuard != null) - { - this.groupLifetimeGuard.ReleaseRef(); - } - else + this.groupLifetimeGuard.ReleaseRef(); + } + else + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + if (memoryOwner is IRefCounted unmanagedBuffer) { - if (memoryOwner is IRefCounted unmanagedBuffer) - { - unmanagedBuffer.ReleaseRef(); - } + unmanagedBuffer.ReleaseRef(); } } } + } + + public override void RecreateViewAfterSwap() + { + this.View.Invalidate(); + this.View = new MemoryGroupView(this); + } - public override void RecreateViewAfterSwap() + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + this.EnsureNotDisposed(); + return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + } + + public override void Dispose() + { + if (this.IsDisposed) { - this.View.Invalidate(); - this.View = new MemoryGroupView(this); + return; } - /// - IEnumerator> IEnumerable>.GetEnumerator() + this.View.Invalidate(); + + if (this.groupLifetimeGuard != null) { - this.EnsureNotDisposed(); - return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + this.groupLifetimeGuard.Dispose(); } - - public override void Dispose() + else { - if (this.IsDisposed) - { - return; - } - - this.View.Invalidate(); - - if (this.groupLifetimeGuard != null) + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { - this.groupLifetimeGuard.Dispose(); + memoryOwner.Dispose(); } - else - { - foreach (IMemoryOwner memoryOwner in this.memoryOwners) - { - memoryOwner.Dispose(); - } - } - - this.memoryOwners = null; - this.IsValid = false; - this.groupLifetimeGuard = null; } - [MethodImpl(InliningOptions.ShortMethod)] - private void EnsureNotDisposed() + this.memoryOwners = null; + this.IsValid = false; + this.groupLifetimeGuard = null; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void EnsureNotDisposed() + { + if (this.memoryOwners is null) { - if (this.memoryOwners is null) - { - ThrowObjectDisposedException(); - } + ThrowObjectDisposedException(); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); - // When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`, - // the lifetime of the individual buffers is managed by the guard. - // Group buffer IMemoryOwner-s d not manage ownership. - private sealed class ObservedBuffer : MemoryManager + // When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`, + // the lifetime of the individual buffers is managed by the guard. + // Group buffer IMemoryOwner-s d not manage ownership. + private sealed class ObservedBuffer : MemoryManager + { + private readonly UnmanagedMemoryHandle handle; + private readonly int lengthInElements; + + private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements) { - private readonly UnmanagedMemoryHandle handle; - private readonly int lengthInElements; + this.handle = handle; + this.lengthInElements = lengthInElements; + } - private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements) + public static ObservedBuffer Create( + UnmanagedMemoryHandle handle, + int lengthInElements, + AllocationOptions options) + { + var buffer = new ObservedBuffer(handle, lengthInElements); + if (options.Has(AllocationOptions.Clean)) { - this.handle = handle; - this.lengthInElements = lengthInElements; + buffer.GetSpan().Clear(); } - public static ObservedBuffer Create( - UnmanagedMemoryHandle handle, - int lengthInElements, - AllocationOptions options) - { - var buffer = new ObservedBuffer(handle, lengthInElements); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } + return buffer; + } - protected override void Dispose(bool disposing) - { - // No-op. - } + protected override void Dispose(bool disposing) + { + // No-op. + } - public override unsafe Span GetSpan() => new(this.handle.Pointer, this.lengthInElements); + public override unsafe Span GetSpan() => new(this.handle.Pointer, this.lengthInElements); - public override unsafe MemoryHandle Pin(int elementIndex = 0) - { - void* pbData = Unsafe.Add(this.handle.Pointer, elementIndex); - return new MemoryHandle(pbData); - } + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + void* pbData = Unsafe.Add(this.handle.Pointer, elementIndex); + return new MemoryHandle(pbData); + } - public override void Unpin() - { - } + public override void Unpin() + { } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index f044e6cb9e..c032316649 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -1,315 +1,312 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Memory +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 { - /// - /// 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 MemoryGroupSpanCache memoryGroupSpanCache; + + private MemoryGroup(int bufferLength, long totalLength) { - private static readonly int ElementSize = Unsafe.SizeOf(); + this.BufferLength = bufferLength; + this.TotalLength = totalLength; + } - private MemoryGroupSpanCache memoryGroupSpanCache; + /// + public abstract int Count { get; } - private MemoryGroup(int bufferLength, long totalLength) - { - this.BufferLength = bufferLength; - this.TotalLength = totalLength; - } + /// + public int BufferLength { get; } - /// - public abstract int Count { get; } + /// + public long TotalLength { get; } - /// - public int BufferLength { get; } + /// + public bool IsValid { get; private set; } = true; - /// - public long TotalLength { get; } + public MemoryGroupView View { get; private set; } - /// - public bool IsValid { get; private set; } = true; + /// + public abstract Memory this[int index] { get; } - public MemoryGroupView View { get; private set; } + /// + public abstract void Dispose(); - /// - public abstract Memory this[int index] { get; } + /// + public abstract MemoryGroupEnumerator GetEnumerator(); - /// - public abstract void Dispose(); + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + /* This method is implemented in each derived class. + * Implementing the method here as non-abstract and throwing, + * then reimplementing it explicitly in each derived class, is + * a workaround for the lack of support for abstract explicit + * interface method implementations in C#. */ + throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); + } + + /// + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)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 totalLengthInElements, + int bufferAlignmentInElements, + AllocationOptions options = AllocationOptions.None) + { + int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); - /// - public abstract MemoryGroupEnumerator GetEnumerator(); + int blockCapacityInElements = bufferCapacityInBytes / ElementSize; - /// - IEnumerator> IEnumerable>.GetEnumerator() + if (bufferAlignmentInElements > blockCapacityInElements) { - /* This method is implemented in each derived class. - * Implementing the method here as non-abstract and throwing, - * then reimplementing it explicitly in each derived class, is - * a workaround for the lack of support for abstract explicit - * interface method implementations in C#. */ - throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); + throw new InvalidMemoryOperationException( + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}."); } - /// - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)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 totalLengthInElements, - int bufferAlignmentInElements, - AllocationOptions options = AllocationOptions.None) + if (totalLengthInElements == 0) { - int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); - Guard.NotNull(allocator, nameof(allocator)); - Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); - Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); + var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; + return new Owned(buffers0, 0, 0, true); + } - int blockCapacityInElements = bufferCapacityInBytes / ElementSize; + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) + { + bufferLength = (int)totalLengthInElements; + } - if (bufferAlignmentInElements > blockCapacityInElements) - { - throw new InvalidMemoryOperationException( - $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}."); - } + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + long bufferCount = totalLengthInElements / bufferLength; - if (totalLengthInElements == 0) - { - var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; - return new Owned(buffers0, 0, 0, true); - } + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; - int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; - if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) - { - bufferLength = (int)totalLengthInElements; - } + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferLength, options); + } - int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); - long bufferCount = totalLengthInElements / bufferLength; + if (bufferCount > 0) + { + buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); + } - if (sizeOfLastBuffer == 0) - { - sizeOfLastBuffer = bufferLength; - } - else - { - bufferCount++; - } + return new Owned(buffers, bufferLength, totalLengthInElements, true); + } - var buffers = new IMemoryOwner[bufferCount]; - for (int i = 0; i < buffers.Length - 1; i++) - { - buffers[i] = allocator.Allocate(bufferLength, options); - } + public static MemoryGroup CreateContiguous(IMemoryOwner buffer, bool clear) + { + if (clear) + { + buffer.GetSpan().Clear(); + } - if (bufferCount > 0) - { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); - } + int length = buffer.Memory.Length; + var buffers = new IMemoryOwner[1] { buffer }; + return new Owned(buffers, length, length, true); + } - return new Owned(buffers, bufferLength, totalLengthInElements, true); - } + public static bool TryAllocate( + UniformUnmanagedMemoryPool pool, + long totalLengthInElements, + int bufferAlignmentInElements, + AllocationOptions options, + out MemoryGroup memoryGroup) + { + Guard.NotNull(pool, nameof(pool)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); - public static MemoryGroup CreateContiguous(IMemoryOwner buffer, bool clear) + int blockCapacityInElements = pool.BufferLength / ElementSize; + + if (bufferAlignmentInElements > blockCapacityInElements) { - if (clear) - { - buffer.GetSpan().Clear(); - } + memoryGroup = null; + return false; + } - int length = buffer.Memory.Length; - var buffers = new IMemoryOwner[1] { buffer }; - return new Owned(buffers, length, length, true); + if (totalLengthInElements == 0) + { + throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed"); } - public static bool TryAllocate( - UniformUnmanagedMemoryPool pool, - long totalLengthInElements, - int bufferAlignmentInElements, - AllocationOptions options, - out MemoryGroup memoryGroup) + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) { - Guard.NotNull(pool, nameof(pool)); - Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); - Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); + bufferLength = (int)totalLengthInElements; + } - int blockCapacityInElements = pool.BufferLength / ElementSize; + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + int bufferCount = (int)(totalLengthInElements / bufferLength); - if (bufferAlignmentInElements > blockCapacityInElements) - { - memoryGroup = null; - return false; - } + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } - if (totalLengthInElements == 0) - { - throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed"); - } + UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount); - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; - int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; - if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) - { - bufferLength = (int)totalLengthInElements; - } + if (arrays == null) + { + // Pool is full + memoryGroup = null; + return false; + } - int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); - int bufferCount = (int)(totalLengthInElements / bufferLength); + memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); + return true; + } - if (sizeOfLastBuffer == 0) - { - sizeOfLastBuffer = bufferLength; - } - else + 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) { - bufferCount++; + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); } + } - UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount); + if (source.Length > 0 && source[^1].Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } - if (arrays == null) - { - // Pool is full - memoryGroup = null; - return false; - } + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; - memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); - return true; - } + return new Consumed(source, bufferLength, totalLength); + } - public static MemoryGroup Wrap(params Memory[] source) + 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++) { - int bufferLength = source.Length > 0 ? source[0].Length : 0; - for (int i = 1; i < source.Length - 1; i++) + if (source[i].Memory.Length != bufferLength) { - if (source[i].Length != bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); - } + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); } + } - if (source.Length > 0 && source[^1].Length > bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); - } + if (source.Length > 0 && source[^1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; - return new Consumed(source, bufferLength, totalLength); - } + return new Owned(source, bufferLength, totalLength, false); + } - public static MemoryGroup Wrap(params IMemoryOwner[] source) + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe Span GetRowSpanCoreUnsafe(int y, int width) + { + switch (this.memoryGroupSpanCache.Mode) { - int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; - for (int i = 1; i < source.Length - 1; i++) + case SpanCacheMode.SingleArray: { - if (source[i].Memory.Length != bufferLength) - { - throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); - } + ref byte b0 = ref MemoryMarshal.GetReference(this.memoryGroupSpanCache.SingleArray); + ref T e0 = ref Unsafe.As(ref b0); + e0 = ref Unsafe.Add(ref e0, y * width); + return MemoryMarshal.CreateSpan(ref e0, width); } - if (source.Length > 0 && source[^1].Memory.Length > bufferLength) + case SpanCacheMode.SinglePointer: { - throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + void* start = Unsafe.Add(this.memoryGroupSpanCache.SinglePointer, y * width); + return new Span(start, width); } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; - - return new Owned(source, bufferLength, totalLength, false); - } + case SpanCacheMode.MultiPointer: + { + this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); + void* start = Unsafe.Add(this.memoryGroupSpanCache.MultiPointer[bufferIdx], bufferStart); + return new Span(start, width); + } - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe Span GetRowSpanCoreUnsafe(int y, int width) - { - switch (this.memoryGroupSpanCache.Mode) + default: { - case SpanCacheMode.SingleArray: - { - ref byte b0 = ref MemoryMarshal.GetReference(this.memoryGroupSpanCache.SingleArray); - ref T e0 = ref Unsafe.As(ref b0); - e0 = ref Unsafe.Add(ref e0, y * width); - return MemoryMarshal.CreateSpan(ref e0, width); - } - - case SpanCacheMode.SinglePointer: - { - void* start = Unsafe.Add(this.memoryGroupSpanCache.SinglePointer, y * width); - return new Span(start, width); - } - - case SpanCacheMode.MultiPointer: - { - this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); - void* start = Unsafe.Add(this.memoryGroupSpanCache.MultiPointer[bufferIdx], bufferStart); - return new Span(start, width); - } - - default: - { - this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); - return this[bufferIdx].Span.Slice(bufferStart, width); - } + this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); + return this[bufferIdx].Span.Slice(bufferStart, width); } } + } - /// - /// Returns the slice of the buffer starting at global index that goes until the end of the buffer. - /// - public Span GetRemainingSliceOfBuffer(long start) - { - long bufferIdx = Math.DivRem(start, this.BufferLength, out long bufferStart); - Memory memory = this[(int)bufferIdx]; - return memory.Span[(int)bufferStart..]; - } + /// + /// Returns the slice of the buffer starting at global index that goes until the end of the buffer. + /// + public Span GetRemainingSliceOfBuffer(long start) + { + long bufferIdx = Math.DivRem(start, this.BufferLength, out long bufferStart); + Memory memory = this[(int)bufferIdx]; + return memory.Span[(int)bufferStart..]; + } - public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => - source is Owned { Swappable: true } && target is Owned { Swappable: true }; + public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => + source is Owned { Swappable: true } && target is Owned { Swappable: true }; - public virtual void RecreateViewAfterSwap() - { - } + public virtual void RecreateViewAfterSwap() + { + } - public virtual void IncreaseRefCounts() - { - } + public virtual void IncreaseRefCounts() + { + } - public virtual void DecreaseRefCounts() - { - } + public virtual void DecreaseRefCounts() + { + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void GetMultiBufferPosition(int y, int width, out int bufferIdx, out int bufferStart) - { - long start = y * (long)width; - long bufferIdxLong = Math.DivRem(start, this.BufferLength, out long bufferStartLong); - bufferIdx = (int)bufferIdxLong; - bufferStart = (int)bufferStartLong; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetMultiBufferPosition(int y, int width, out int bufferIdx, out int bufferStart) + { + long start = y * (long)width; + long bufferIdxLong = Math.DivRem(start, this.BufferLength, out long bufferStartLong); + bufferIdx = (int)bufferIdxLong; + bufferStart = (int)bufferStartLong; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs index 59d405d2b3..d3ab85cf40 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Selects active values in . +/// +internal enum SpanCacheMode { - /// - /// Selects active values in . - /// - internal enum SpanCacheMode - { - Default = default, - SingleArray, - SinglePointer, - MultiPointer - } + Default = default, + SingleArray, + SinglePointer, + MultiPointer } diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs index 43901b89e0..6a55472236 100644 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -1,30 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -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 { /// - /// Exception thrown when the library detects an invalid memory allocation request, - /// or an attempt has been made to use an invalidated . + /// Initializes a new instance of the class. /// - public class InvalidMemoryOperationException : InvalidOperationException + /// The exception message text. + public InvalidMemoryOperationException(string message) + : base(message) { - /// - /// 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() - { - } + /// + /// Initializes a new instance of the class. + /// + public InvalidMemoryOperationException() + { } } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 9563294406..ff306e1e45 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -2,134 +2,131 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO; -using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Extension methods for . +/// +public static class MemoryAllocatorExtensions { /// - /// Extension methods for . + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. /// - public static class MemoryAllocatorExtensions + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + bool preferContiguosImageBuffers, + AllocationOptions options = AllocationOptions.None) + where T : struct { - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of x elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer width. - /// The buffer height. - /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - int width, - int height, - bool preferContiguosImageBuffers, - AllocationOptions options = AllocationOptions.None) - where T : struct + long groupLength = (long)width * height; + MemoryGroup memoryGroup; + if (preferContiguosImageBuffers && groupLength < int.MaxValue) { - long groupLength = (long)width * height; - MemoryGroup memoryGroup; - if (preferContiguosImageBuffers && groupLength < int.MaxValue) - { - IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); - memoryGroup = MemoryGroup.CreateContiguous(buffer, false); - } - else - { - memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); - } - - return new Buffer2D(memoryGroup, width, height); + IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); + memoryGroup = MemoryGroup.CreateContiguous(buffer, false); + } + else + { + memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); } - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of x elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer width. - /// The buffer height. - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - int width, - int height, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, width, height, false, options); + return new Buffer2D(memoryGroup, width, height); + } - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of width x height elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer size. - /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - Size size, - bool preferContiguosImageBuffers, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options); + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, width, height, false, options); - /// - /// Allocates a buffer of value type objects interpreted as a 2D region - /// of width x height elements. - /// - /// The type of buffer items to allocate. - /// The memory allocator. - /// The buffer size. - /// The allocation options. - /// The . - public static Buffer2D Allocate2D( - this MemoryAllocator memoryAllocator, - Size size, - AllocationOptions options = AllocationOptions.None) - where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, false, options); + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + bool preferContiguosImageBuffers, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, 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 a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, false, options); - /// - /// Allocates padded buffers. Generally used by encoder/decoders. - /// - /// The . - /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB. - /// The padding. - /// A . - internal static IMemoryOwner AllocatePaddedPixelRowBuffer( - this MemoryAllocator memoryAllocator, - int width, - int pixelSizeInBytes, - int paddingInBytes) - { - int length = (width * pixelSizeInBytes) + paddingInBytes; - return memoryAllocator.Allocate(length); - } + 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. Generally used by encoder/decoders. + /// + /// The . + /// Pixel count in the row + /// The pixel size in bytes, eg. 3 for RGB. + /// The padding. + /// A . + internal static IMemoryOwner AllocatePaddedPixelRowBuffer( + this MemoryAllocator memoryAllocator, + int width, + int pixelSizeInBytes, + int paddingInBytes) + { + int length = (width * pixelSizeInBytes) + paddingInBytes; + return memoryAllocator.Allocate(length); } } diff --git a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs index 8c3d49d1eb..1fdf838281 100644 --- a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs +++ b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs @@ -1,85 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// Extension methods for +/// +internal static class MemoryOwnerExtensions { /// - /// Extension methods for + /// Gets a from an instance. /// - internal static class MemoryOwnerExtensions + /// The buffer + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetSpan(this IMemoryOwner buffer) { - /// - /// Gets a from an instance. - /// - /// The buffer - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(this IMemoryOwner buffer) - { - return buffer.Memory.Span; - } + return buffer.Memory.Span; + } - /// - /// Gets the length of an internal buffer. - /// - /// The buffer - /// The length of the buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Length(this IMemoryOwner buffer) - { - return buffer.Memory.Length; - } + /// + /// Gets the length of an internal buffer. + /// + /// The buffer + /// The length of the buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IMemoryOwner buffer) + { + return buffer.Memory.Length; + } - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The buffer - /// The start - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span Slice(this IMemoryOwner buffer, int start) - { - return buffer.GetSpan()[start..]; - } + /// + /// Gets a to an offsetted position inside the buffer. + /// + /// The buffer + /// The start + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IMemoryOwner buffer, int start) + { + return buffer.GetSpan()[start..]; + } - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The buffer - /// The start - /// The length of the slice - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span Slice(this IMemoryOwner buffer, int start, int length) - { - return buffer.GetSpan().Slice(start, length); - } + /// + /// Gets a to an offsetted position inside the buffer. + /// + /// The buffer + /// The start + /// The length of the slice + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IMemoryOwner buffer, int start, int length) + { + return buffer.GetSpan().Slice(start, length); + } - /// - /// Clears the contents of this buffer. - /// - /// The buffer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this IMemoryOwner buffer) - { - buffer.GetSpan().Clear(); - } + /// + /// Clears the contents of this buffer. + /// + /// The buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this IMemoryOwner buffer) + { + buffer.GetSpan().Clear(); + } - /// - /// Gets a reference to the first item in the internal buffer for an instance. - /// - /// The buffer - /// A reference to the first item within the memory wrapped by - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetReference(this IMemoryOwner buffer) - where T : struct - { - return ref MemoryMarshal.GetReference(buffer.GetSpan()); - } + /// + /// Gets a reference to the first item in the internal buffer for an instance. + /// + /// The buffer + /// A reference to the first item within the memory wrapped by + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this IMemoryOwner buffer) + where T : struct + { + return ref MemoryMarshal.GetReference(buffer.GetSpan()); } } diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs index b6141fe86e..7f40c91565 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -1,88 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Memory +/// +/// Represents an interval of rows in a and/or +/// +/// +/// Before RC1, this class might be target of API changes, use it on your own risk! +/// +public readonly struct RowInterval : IEquatable { /// - /// Represents an interval of rows in a and/or + /// Initializes a new instance of the struct. /// - /// - /// Before RC1, this class might be target of API changes, use it on your own risk! - /// - public readonly struct RowInterval : IEquatable + /// The inclusive minimum row. + /// The exclusive maximum row. + public RowInterval(int min, int max) { - /// - /// Initializes a new instance of the struct. - /// - /// The inclusive minimum row. - /// The exclusive maximum row. - public RowInterval(int min, int max) - { - Guard.MustBeLessThan(min, max, nameof(min)); + Guard.MustBeLessThan(min, max, nameof(min)); - this.Min = min; - this.Max = max; - } + this.Min = min; + this.Max = max; + } - /// - /// Gets the inclusive minimum row. - /// - public int Min { get; } + /// + /// Gets the inclusive minimum row. + /// + public int Min { get; } - /// - /// Gets the exclusive maximum row. - /// - public int Max { get; } + /// + /// Gets the exclusive maximum row. + /// + public int Max { get; } - /// - /// Gets the difference ( - ). - /// - public int Height => this.Max - this.Min; + /// + /// Gets the difference ( - ). + /// + public int Height => this.Max - this.Min; - /// - /// Returns a boolean indicating whether the given two -s are equal. - /// - /// The first to compare. - /// The second to compare. - /// True if the given -s are equal; False otherwise. - public static bool operator ==(RowInterval left, RowInterval right) - { - return left.Equals(right); - } + /// + /// Returns a boolean indicating whether the given two -s are equal. + /// + /// The first to compare. + /// The second to compare. + /// True if the given -s are equal; False otherwise. + public static bool operator ==(RowInterval left, RowInterval right) + { + return left.Equals(right); + } - /// - /// Returns a boolean indicating whether the given two -s are not equal. - /// - /// The first to compare. - /// The second to compare. - /// True if the given -s are not equal; False otherwise. - public static bool operator !=(RowInterval left, RowInterval right) - { - return !left.Equals(right); - } + /// + /// Returns a boolean indicating whether the given two -s are not equal. + /// + /// The first to compare. + /// The second to compare. + /// True if the given -s are not equal; False otherwise. + public static bool operator !=(RowInterval left, RowInterval right) + { + return !left.Equals(right); + } - /// - public bool Equals(RowInterval other) - { - return this.Min == other.Min && this.Max == other.Max; - } + /// + public bool Equals(RowInterval other) + { + return this.Min == other.Min && this.Max == other.Max; + } - /// - public override bool Equals(object obj) - { - return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); - } + /// + public override bool Equals(object obj) + { + return !ReferenceEquals(null, obj) && obj is RowInterval other && this.Equals(other); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); + /// + public override int GetHashCode() => HashCode.Combine(this.Min, this.Max); - /// - public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; + /// + public override string ToString() => $"RowInterval [{this.Min}->{this.Max}]"; - internal RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); + internal RowInterval Slice(int start) => new RowInterval(this.Min + start, this.Max); - internal RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); - } + internal RowInterval Slice(int start, int length) => new RowInterval(this.Min + start, this.Min + start + length); } diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs index ff428b0291..bc3d17f8f0 100644 --- a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs +++ b/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs @@ -1,11 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Memory -{ #pragma warning disable SA1649 // File name should match first type name - internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); +internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); #pragma warning restore SA1649 // File name should match first type name -} diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs index 923c7abbdd..d1ef51fb85 100644 --- a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs +++ b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Memory -{ - internal delegate void TransformItemsInplaceDelegate(Span data); -} +internal delegate void TransformItemsInplaceDelegate(Span data); diff --git a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs index 789bfd919d..7f3163af3d 100644 --- a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs +++ b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs @@ -1,60 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -namespace SixLabors.ImageSharp.Memory +namespace SixLabors.ImageSharp.Memory; + +/// +/// A custom that can wrap a rawpointer to a buffer of a specified type. +/// +/// The value type to use when casting the wrapped instance. +/// This manager doesn't own the memory buffer that it points to. +internal sealed unsafe class UnmanagedMemoryManager : MemoryManager + where T : unmanaged { /// - /// A custom that can wrap a rawpointer to a buffer of a specified type. + /// The pointer to the memory buffer. + /// + private readonly void* pointer; + + /// + /// The length of the memory area. + /// + private readonly int length; + + /// + /// Initializes a new instance of the class. /// - /// The value type to use when casting the wrapped instance. - /// This manager doesn't own the memory buffer that it points to. - internal sealed unsafe class UnmanagedMemoryManager : MemoryManager - where T : unmanaged + /// The pointer to the memory buffer. + /// The length of the memory area. + public UnmanagedMemoryManager(void* pointer, int length) + { + this.pointer = pointer; + this.length = length; + } + + /// + protected override void Dispose(bool disposing) + { + } + + /// + public override Span GetSpan() + { + return new Span(this.pointer, this.length); + } + + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this); + } + + /// + public override void Unpin() { - /// - /// The pointer to the memory buffer. - /// - private readonly void* pointer; - - /// - /// The length of the memory area. - /// - private readonly int length; - - /// - /// Initializes a new instance of the class. - /// - /// The pointer to the memory buffer. - /// The length of the memory area. - public UnmanagedMemoryManager(void* pointer, int length) - { - this.pointer = pointer; - this.length = length; - } - - /// - protected override void Dispose(bool disposing) - { - } - - /// - public override Span GetSpan() - { - return new Span(this.pointer, this.length); - } - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this); - } - - /// - public override void Unpin() - { - } } } diff --git a/src/ImageSharp/Metadata/FrameDecodingMode.cs b/src/ImageSharp/Metadata/FrameDecodingMode.cs index b4ea6c7553..3a59654897 100644 --- a/src/ImageSharp/Metadata/FrameDecodingMode.cs +++ b/src/ImageSharp/Metadata/FrameDecodingMode.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata +namespace SixLabors.ImageSharp.Metadata; + +/// +/// Enumerated frame process modes to apply to multi-frame images. +/// +public enum FrameDecodingMode { /// - /// Enumerated frame process modes to apply to multi-frame images. + /// Decodes all the frames of a multi-frame image. /// - public enum FrameDecodingMode - { - /// - /// Decodes all the frames of a multi-frame image. - /// - All, + All, - /// - /// Decodes only the first frame of a multi-frame image. - /// - First - } + /// + /// Decodes only the first frame of a multi-frame image. + /// + First } diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index a864d648da..69ff5201f2 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -1,95 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Metadata +namespace SixLabors.ImageSharp.Metadata; + +/// +/// Encapsulates the metadata of an image frame. +/// +public sealed class ImageFrameMetadata : IDeepCloneable { + private readonly Dictionary formatMetadata = new(); + /// - /// Encapsulates the metadata of an image frame. + /// Initializes a new instance of the class. /// - public sealed class ImageFrameMetadata : IDeepCloneable + internal ImageFrameMetadata() { - private readonly Dictionary formatMetadata = new(); + } - /// - /// Initializes a new instance of the class. - /// - internal ImageFrameMetadata() - { - } + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + internal ImageFrameMetadata(ImageFrameMetadata other) + { + DebugGuard.NotNull(other, nameof(other)); - /// - /// Initializes a new instance of the class - /// by making a copy from other metadata. - /// - /// - /// The other to create this instance from. - /// - internal ImageFrameMetadata(ImageFrameMetadata other) + foreach (KeyValuePair meta in other.formatMetadata) { - DebugGuard.NotNull(other, nameof(other)); - - foreach (KeyValuePair meta in other.formatMetadata) - { - this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); - } - - this.ExifProfile = other.ExifProfile?.DeepClone(); - this.IccProfile = other.IccProfile?.DeepClone(); - this.IptcProfile = other.IptcProfile?.DeepClone(); - this.XmpProfile = other.XmpProfile?.DeepClone(); + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); } - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile ExifProfile { get; set; } + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + this.IptcProfile = other.IptcProfile?.DeepClone(); + this.XmpProfile = other.XmpProfile?.DeepClone(); + } - /// - /// Gets or sets the XMP profile. - /// - public XmpProfile XmpProfile { get; set; } + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } - /// - /// Gets or sets the list of ICC profiles. - /// - public IccProfile IccProfile { get; set; } + /// + /// Gets or sets the XMP profile. + /// + public XmpProfile XmpProfile { get; set; } + + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } - /// - /// Gets or sets the iptc profile. - /// - public IptcProfile IptcProfile { get; set; } + /// + /// Gets or sets the iptc profile. + /// + public IptcProfile IptcProfile { get; set; } - /// - public ImageFrameMetadata DeepClone() => new(this); + /// + public ImageFrameMetadata DeepClone() => new(this); - /// - /// Gets the metadata value associated with the specified key. - /// - /// The type of format metadata. - /// The type of format frame metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatFrameMetadata GetFormatMetadata(IImageFormat key) - where TFormatMetadata : class - where TFormatFrameMetadata : class, IDeepCloneable + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of format metadata. + /// The type of format frame metadata. + /// The key of the value to get. + /// + /// The . + /// + public TFormatFrameMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class + where TFormatFrameMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) { - if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) - { - return (TFormatFrameMetadata)meta; - } - - TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); - this.formatMetadata[key] = newMeta; - return newMeta; + return (TFormatFrameMetadata)meta; } + + TFormatFrameMetadata newMeta = key.CreateDefaultFormatFrameMetadata(); + this.formatMetadata[key] = newMeta; + return newMeta; } } diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index f12f3934e2..3d63b8f455 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -1,186 +1,184 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -namespace SixLabors.ImageSharp.Metadata +namespace SixLabors.ImageSharp.Metadata; + +/// +/// Encapsulates the metadata of an image. +/// +public sealed class ImageMetadata : IDeepCloneable { /// - /// Encapsulates the metadata of an image. + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 . + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 . /// - public sealed class ImageMetadata : IDeepCloneable + public const double DefaultVerticalResolution = 96; + + /// + /// The default pixel resolution units. + /// The default value is . + /// + public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; + + private readonly Dictionary formatMetadata = new(); + private double horizontalResolution; + private double verticalResolution; + + /// + /// Initializes a new instance of the class. + /// + public ImageMetadata() { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 . - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 . - /// - public const double DefaultVerticalResolution = 96; - - /// - /// The default pixel resolution units. - /// The default value is . - /// - public const PixelResolutionUnit DefaultPixelResolutionUnits = PixelResolutionUnit.PixelsPerInch; - - private readonly Dictionary formatMetadata = new(); - private double horizontalResolution; - private double verticalResolution; - - /// - /// Initializes a new instance of the class. - /// - public ImageMetadata() - { - this.horizontalResolution = DefaultHorizontalResolution; - this.verticalResolution = DefaultVerticalResolution; - this.ResolutionUnits = DefaultPixelResolutionUnits; - } + this.horizontalResolution = DefaultHorizontalResolution; + this.verticalResolution = DefaultVerticalResolution; + this.ResolutionUnits = DefaultPixelResolutionUnits; + } + + /// + /// Initializes a new instance of the class + /// by making a copy from other metadata. + /// + /// + /// The other to create this instance from. + /// + private ImageMetadata(ImageMetadata other) + { + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.ResolutionUnits = other.ResolutionUnits; - /// - /// Initializes a new instance of the class - /// by making a copy from other metadata. - /// - /// - /// The other to create this instance from. - /// - private ImageMetadata(ImageMetadata other) + foreach (KeyValuePair meta in other.formatMetadata) { - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.ResolutionUnits = other.ResolutionUnits; + this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); + } - foreach (KeyValuePair meta in other.formatMetadata) - { - this.formatMetadata.Add(meta.Key, meta.Value.DeepClone()); - } + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); + this.IptcProfile = other.IptcProfile?.DeepClone(); + this.XmpProfile = other.XmpProfile?.DeepClone(); + } - this.ExifProfile = other.ExifProfile?.DeepClone(); - this.IccProfile = other.IccProfile?.DeepClone(); - this.IptcProfile = other.IptcProfile?.DeepClone(); - this.XmpProfile = other.XmpProfile?.DeepClone(); - } + /// + /// Gets or sets the resolution of the image in x- direction. + /// It is defined as the number of dots per and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution + { + get => this.horizontalResolution; - /// - /// Gets or sets the resolution of the image in x- direction. - /// It is defined as the number of dots per and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution + set { - get => this.horizontalResolution; - - set + if (value > 0) { - if (value > 0) - { - this.horizontalResolution = value; - } + this.horizontalResolution = value; } } + } - /// - /// Gets or sets the resolution of the image in y- direction. - /// It is defined as the number of dots per and should be an positive value. - /// - /// The density of the image in y- direction. - public double VerticalResolution - { - get => this.verticalResolution; + /// + /// Gets or sets the resolution of the image in y- direction. + /// It is defined as the number of dots per and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution + { + get => this.verticalResolution; - set + set + { + if (value > 0) { - if (value > 0) - { - this.verticalResolution = value; - } + this.verticalResolution = value; } } + } - /// - /// Gets or sets unit of measure used when reporting resolution. - /// - /// - /// Value - /// Unit - /// - /// - /// AspectRatio (00) - /// No units; width:height pixel aspect ratio = Ydensity:Xdensity - /// - /// - /// PixelsPerInch (01) - /// Pixels per inch (2.54 cm) - /// - /// - /// PixelsPerCentimeter (02) - /// Pixels per centimeter - /// - /// - /// PixelsPerMeter (03) - /// Pixels per meter (100 cm) - /// - /// - /// - public PixelResolutionUnit ResolutionUnits { get; set; } - - /// - /// Gets or sets the Exif profile. - /// - public ExifProfile ExifProfile { get; set; } - - /// - /// Gets or sets the XMP profile. - /// - public XmpProfile XmpProfile { get; set; } - - /// - /// Gets or sets the list of ICC profiles. - /// - public IccProfile IccProfile { get; set; } - - /// - /// Gets or sets the IPTC profile. - /// - public IptcProfile IptcProfile { get; set; } - - /// - /// Gets the metadata value associated with the specified key. - /// - /// The type of metadata. - /// The key of the value to get. - /// - /// The . - /// - public TFormatMetadata GetFormatMetadata(IImageFormat key) - where TFormatMetadata : class, IDeepCloneable - { - if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) - { - return (TFormatMetadata)meta; - } + /// + /// Gets or sets unit of measure used when reporting resolution. + /// + /// + /// Value + /// Unit + /// + /// + /// AspectRatio (00) + /// No units; width:height pixel aspect ratio = Ydensity:Xdensity + /// + /// + /// PixelsPerInch (01) + /// Pixels per inch (2.54 cm) + /// + /// + /// PixelsPerCentimeter (02) + /// Pixels per centimeter + /// + /// + /// PixelsPerMeter (03) + /// Pixels per meter (100 cm) + /// + /// + /// + public PixelResolutionUnit ResolutionUnits { get; set; } - TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); - this.formatMetadata[key] = newMeta; - return newMeta; - } + /// + /// Gets or sets the Exif profile. + /// + public ExifProfile ExifProfile { get; set; } + + /// + /// Gets or sets the XMP profile. + /// + public XmpProfile XmpProfile { get; set; } - /// - public ImageMetadata DeepClone() => new(this); + /// + /// Gets or sets the list of ICC profiles. + /// + public IccProfile IccProfile { get; set; } + + /// + /// Gets or sets the IPTC profile. + /// + public IptcProfile IptcProfile { get; set; } - /// - /// Synchronizes the profiles with the current metadata. - /// - internal void SyncProfiles() => this.ExifProfile?.Sync(this); + /// + /// Gets the metadata value associated with the specified key. + /// + /// The type of metadata. + /// The key of the value to get. + /// + /// The . + /// + public TFormatMetadata GetFormatMetadata(IImageFormat key) + where TFormatMetadata : class, IDeepCloneable + { + if (this.formatMetadata.TryGetValue(key, out IDeepCloneable meta)) + { + return (TFormatMetadata)meta; + } + + TFormatMetadata newMeta = key.CreateDefaultFormatMetadata(); + this.formatMetadata[key] = newMeta; + return newMeta; } + + /// + public ImageMetadata DeepClone() => new(this); + + /// + /// Synchronizes the profiles with the current metadata. + /// + internal void SyncProfiles() => this.ExifProfile?.Sync(this); } diff --git a/src/ImageSharp/Metadata/PixelResolutionUnit.cs b/src/ImageSharp/Metadata/PixelResolutionUnit.cs index b21b452db2..67abd3ffc1 100644 --- a/src/ImageSharp/Metadata/PixelResolutionUnit.cs +++ b/src/ImageSharp/Metadata/PixelResolutionUnit.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata +namespace SixLabors.ImageSharp.Metadata; + +/// +/// Provides enumeration of available pixel density units. +/// +public enum PixelResolutionUnit : byte { /// - /// Provides enumeration of available pixel density units. + /// No units; width:height pixel aspect ratio. /// - public enum PixelResolutionUnit : byte - { - /// - /// No units; width:height pixel aspect ratio. - /// - AspectRatio = 0, + AspectRatio = 0, - /// - /// Pixels per inch (2.54 cm). - /// - PixelsPerInch = 1, + /// + /// Pixels per inch (2.54 cm). + /// + PixelsPerInch = 1, - /// - /// Pixels per centimeter. - /// - PixelsPerCentimeter = 2, + /// + /// Pixels per centimeter. + /// + PixelsPerCentimeter = 2, - /// - /// Pixels per meter (100 cm). - /// - PixelsPerMeter = 3 - } + /// + /// Pixels per meter (100 cm). + /// + PixelsPerMeter = 3 } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs index f350bb5e07..725533ea5f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -1,30 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal static class ExifConstants { - internal static class ExifConstants + public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] { - public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] - { - (byte)'I', - (byte)'I', - 0x2A, - 0x00, - }; + (byte)'I', + (byte)'I', + 0x2A, + 0x00, + }; - public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] - { - (byte)'M', - (byte)'M', - 0x00, - 0x2A - }; + public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] + { + (byte)'M', + (byte)'M', + 0x00, + 0x2A + }; - // UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way - public static Encoding DefaultEncoding => Encoding.UTF8; - } + // UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way + public static Encoding DefaultEncoding => Encoding.UTF8; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs index db9b278940..90a5d15b74 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs @@ -1,100 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Specifies exif data types. +/// +public enum ExifDataType { /// - /// Specifies exif data types. - /// - public enum ExifDataType - { - /// - /// Unknown - /// - Unknown = 0, - - /// - /// An 8-bit unsigned integer. - /// - Byte = 1, - - /// - /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. - /// - /// Although the standard defines ASCII this has commonly been ignored as - /// ASCII cannot properly encode text in many languages. - /// - /// - Ascii = 2, - - /// - /// A 16-bit (2-byte) unsigned integer. - /// - Short = 3, - - /// - /// A 32-bit (4-byte) unsigned integer. - /// - Long = 4, - - /// - /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. - /// - Rational = 5, - - /// - /// An 8-bit signed integer. - /// - SignedByte = 6, - - /// - /// An 8-bit byte that can take any value depending on the field definition. - /// - Undefined = 7, - - /// - /// A 16-bit (2-byte) signed integer. - /// - SignedShort = 8, - - /// - /// A 32-bit (4-byte) signed integer (2's complement notation). - /// - SignedLong = 9, - - /// - /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. - /// - SignedRational = 10, - - /// - /// A 32-bit single precision floating point value. - /// - SingleFloat = 11, - - /// - /// A 64-bit double precision floating point value. - /// - DoubleFloat = 12, - - /// - /// Reference to an IFD (32-bit (4-byte) unsigned integer). - /// - Ifd = 13, - - /// - /// A 64-bit (8-byte) unsigned integer. - /// - Long8 = 16, - - /// - /// A 64-bit (8-byte) signed integer (2's complement notation). - /// - SignedLong8 = 17, - - /// - /// Reference to an IFD (64-bit (8-byte) unsigned integer). - /// - Ifd8 = 18, - } + /// Unknown + /// + Unknown = 0, + + /// + /// An 8-bit unsigned integer. + /// + Byte = 1, + + /// + /// An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. + /// + /// Although the standard defines ASCII this has commonly been ignored as + /// ASCII cannot properly encode text in many languages. + /// + /// + Ascii = 2, + + /// + /// A 16-bit (2-byte) unsigned integer. + /// + Short = 3, + + /// + /// A 32-bit (4-byte) unsigned integer. + /// + Long = 4, + + /// + /// Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + /// + Rational = 5, + + /// + /// An 8-bit signed integer. + /// + SignedByte = 6, + + /// + /// An 8-bit byte that can take any value depending on the field definition. + /// + Undefined = 7, + + /// + /// A 16-bit (2-byte) signed integer. + /// + SignedShort = 8, + + /// + /// A 32-bit (4-byte) signed integer (2's complement notation). + /// + SignedLong = 9, + + /// + /// Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + /// + SignedRational = 10, + + /// + /// A 32-bit single precision floating point value. + /// + SingleFloat = 11, + + /// + /// A 64-bit double precision floating point value. + /// + DoubleFloat = 12, + + /// + /// Reference to an IFD (32-bit (4-byte) unsigned integer). + /// + Ifd = 13, + + /// + /// A 64-bit (8-byte) unsigned integer. + /// + Long8 = 16, + + /// + /// A 64-bit (8-byte) signed integer (2's complement notation). + /// + SignedLong8 = 17, + + /// + /// Reference to an IFD (64-bit (8-byte) unsigned integer). + /// + Ifd8 = 18, } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs index a595e780b0..272ad7fc49 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs @@ -1,49 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal static class ExifDataTypes { - internal static class ExifDataTypes + /// + /// Gets the size in bytes of the given data type. + /// + /// The data type. + /// + /// The . + /// + /// + /// Thrown if the type is unsupported. + /// + public static uint GetSize(ExifDataType dataType) { - /// - /// Gets the size in bytes of the given data type. - /// - /// The data type. - /// - /// The . - /// - /// - /// Thrown if the type is unsupported. - /// - public static uint GetSize(ExifDataType dataType) + switch (dataType) { - switch (dataType) - { - case ExifDataType.Ascii: - case ExifDataType.Byte: - case ExifDataType.SignedByte: - case ExifDataType.Undefined: - return 1; - case ExifDataType.Short: - case ExifDataType.SignedShort: - return 2; - case ExifDataType.Long: - case ExifDataType.SignedLong: - case ExifDataType.SingleFloat: - case ExifDataType.Ifd: - return 4; - case ExifDataType.DoubleFloat: - case ExifDataType.Rational: - case ExifDataType.SignedRational: - case ExifDataType.Long8: - case ExifDataType.SignedLong8: - case ExifDataType.Ifd8: - return 8; - default: - throw new NotSupportedException(dataType.ToString()); - } + case ExifDataType.Ascii: + case ExifDataType.Byte: + case ExifDataType.SignedByte: + case ExifDataType.Undefined: + return 1; + case ExifDataType.Short: + case ExifDataType.SignedShort: + return 2; + case ExifDataType.Long: + case ExifDataType.SignedLong: + case ExifDataType.SingleFloat: + case ExifDataType.Ifd: + return 4; + case ExifDataType.DoubleFloat: + case ExifDataType.Rational: + case ExifDataType.SignedRational: + case ExifDataType.Long8: + case ExifDataType.SignedLong8: + case ExifDataType.Ifd8: + return 8; + default: + throw new NotSupportedException(dataType.ToString()); } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs index f5f884f289..949dc23880 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs @@ -1,113 +1,111 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Text; using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal static class ExifEncodedStringHelpers { - internal static class ExifEncodedStringHelpers - { - public const int CharacterCodeBytesLength = 8; + public const int CharacterCodeBytesLength = 8; - private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41; - private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A; - private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55; - private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; + private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41; + private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A; + private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55; + private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; - private static ReadOnlySpan AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; + private static ReadOnlySpan AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; - private static ReadOnlySpan JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; + private static ReadOnlySpan JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; - private static ReadOnlySpan UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; + private static ReadOnlySpan UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; - private static ReadOnlySpan UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + private static ReadOnlySpan UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; - // 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990) - // https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0 - private static Encoding JIS0208Encoding => CodePagesEncodingProvider.Instance.GetEncoding(20932); + // 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990) + // https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0 + private static Encoding JIS0208Encoding => CodePagesEncodingProvider.Instance.GetEncoding(20932); - public static bool IsEncodedString(ExifTagValue tag) => tag switch - { - ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true, - _ => false - }; + public static bool IsEncodedString(ExifTagValue tag) => tag switch + { + ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true, + _ => false + }; - public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch - { - CharacterCode.ASCII => AsciiCodeBytes, - CharacterCode.JIS => JISCodeBytes, - CharacterCode.Unicode => UnicodeCodeBytes, - CharacterCode.Undefined => UndefinedCodeBytes, - _ => UndefinedCodeBytes - }; - - public static Encoding GetEncoding(CharacterCode code) => code switch - { - CharacterCode.ASCII => Encoding.ASCII, - CharacterCode.JIS => JIS0208Encoding, - CharacterCode.Unicode => Encoding.Unicode, - CharacterCode.Undefined => Encoding.UTF8, - _ => Encoding.UTF8 - }; - - public static bool TryParse(ReadOnlySpan buffer, out EncodedString encodedString) + public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch + { + CharacterCode.ASCII => AsciiCodeBytes, + CharacterCode.JIS => JISCodeBytes, + CharacterCode.Unicode => UnicodeCodeBytes, + CharacterCode.Undefined => UndefinedCodeBytes, + _ => UndefinedCodeBytes + }; + + public static Encoding GetEncoding(CharacterCode code) => code switch + { + CharacterCode.ASCII => Encoding.ASCII, + CharacterCode.JIS => JIS0208Encoding, + CharacterCode.Unicode => Encoding.Unicode, + CharacterCode.Undefined => Encoding.UTF8, + _ => Encoding.UTF8 + }; + + public static bool TryParse(ReadOnlySpan buffer, out EncodedString encodedString) + { + if (TryDetect(buffer, out CharacterCode code)) { - if (TryDetect(buffer, out CharacterCode code)) - { - string text = GetEncoding(code).GetString(buffer[CharacterCodeBytesLength..]); - encodedString = new EncodedString(code, text); - return true; - } - - encodedString = default; - return false; + string text = GetEncoding(code).GetString(buffer[CharacterCodeBytesLength..]); + encodedString = new EncodedString(code, text); + return true; } - public static uint GetDataLength(EncodedString encodedString) => - (uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; + encodedString = default; + return false; + } - public static int Write(EncodedString encodedString, Span destination) - { - GetCodeBytes(encodedString.Code).CopyTo(destination); + public static uint GetDataLength(EncodedString encodedString) => + (uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; - string text = encodedString.Text; - int count = Write(GetEncoding(encodedString.Code), text, destination[CharacterCodeBytesLength..]); + public static int Write(EncodedString encodedString, Span destination) + { + GetCodeBytes(encodedString.Code).CopyTo(destination); - return CharacterCodeBytesLength + count; - } + string text = encodedString.Text; + int count = Write(GetEncoding(encodedString.Code), text, destination[CharacterCodeBytesLength..]); - public static unsafe int Write(Encoding encoding, string value, Span destination) - => encoding.GetBytes(value.AsSpan(), destination); + return CharacterCodeBytesLength + count; + } + + public static unsafe int Write(Encoding encoding, string value, Span destination) + => encoding.GetBytes(value.AsSpan(), destination); - private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) + private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) + { + if (buffer.Length >= CharacterCodeBytesLength) { - if (buffer.Length >= CharacterCodeBytesLength) + ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + switch (test) { - ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); - switch (test) - { - case AsciiCode: - code = CharacterCode.ASCII; - return true; - case JISCode: - code = CharacterCode.JIS; - return true; - case UnicodeCode: - code = CharacterCode.Unicode; - return true; - case UndefinedCode: - code = CharacterCode.Undefined; - return true; - default: - break; - } + case AsciiCode: + code = CharacterCode.ASCII; + return true; + case JISCode: + code = CharacterCode.JIS; + return true; + case UnicodeCode: + code = CharacterCode.Unicode; + return true; + case UndefinedCode: + code = CharacterCode.Undefined; + return true; + default: + break; } - - code = default; - return false; } + + code = default; + return false; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs index 8aa075b27f..7c465b7c1d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifParts.cs @@ -1,39 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +/// +/// Specifies which parts will be written when the profile is added to an image. +/// +[Flags] +public enum ExifParts { /// - /// Specifies which parts will be written when the profile is added to an image. + /// None /// - [Flags] - public enum ExifParts - { - /// - /// None - /// - None = 0, + None = 0, - /// - /// IfdTags - /// - IfdTags = 1, + /// + /// IfdTags + /// + IfdTags = 1, - /// - /// ExifTags - /// - ExifTags = 2, + /// + /// ExifTags + /// + ExifTags = 2, - /// - /// GPSTags - /// - GpsTags = 4, + /// + /// GPSTags + /// + GpsTags = 4, - /// - /// All - /// - All = IfdTags | ExifTags | GpsTags - } + /// + /// All + /// + All = IfdTags | ExifTags | GpsTags } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index f07abb6469..93c5cea3cb 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -1,324 +1,320 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Represents an EXIF profile providing access to the collection of values. +/// +public sealed class ExifProfile : IDeepCloneable { /// - /// Represents an EXIF profile providing access to the collection of values. + /// The byte array to read the EXIF profile from. /// - public sealed class ExifProfile : IDeepCloneable - { - /// - /// The byte array to read the EXIF profile from. - /// - private readonly byte[] data; - - /// - /// The collection of EXIF values - /// - private List values; - - /// - /// The thumbnail offset position in the byte stream - /// - private int thumbnailOffset; - - /// - /// The thumbnail length in the byte stream - /// - private int thumbnailLength; - - /// - /// Initializes a new instance of the class. - /// - public ExifProfile() - : this((byte[])null) - { - } + private readonly byte[] data; - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the EXIF profile from. - public ExifProfile(byte[] data) - { - this.Parts = ExifParts.All; - this.data = data; - this.InvalidTags = Array.Empty(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The values. - /// The invalid tags. - internal ExifProfile(List values, IReadOnlyList invalidTags) - { - this.Parts = ExifParts.All; - this.values = values; - this.InvalidTags = invalidTags; - } + /// + /// The collection of EXIF values + /// + private List values; - /// - /// Initializes a new instance of the class - /// by making a copy from another EXIF profile. - /// - /// The other EXIF profile, where the clone should be made from. - /// is null.> - private ExifProfile(ExifProfile other) - { - Guard.NotNull(other, nameof(other)); + /// + /// The thumbnail offset position in the byte stream + /// + private int thumbnailOffset; - this.Parts = other.Parts; - this.thumbnailLength = other.thumbnailLength; - this.thumbnailOffset = other.thumbnailOffset; + /// + /// The thumbnail length in the byte stream + /// + private int thumbnailLength; - this.InvalidTags = other.InvalidTags.Count > 0 - ? new List(other.InvalidTags) - : (IReadOnlyList)Array.Empty(); + /// + /// Initializes a new instance of the class. + /// + public ExifProfile() + : this((byte[])null) + { + } - if (other.values != null) - { - this.values = new List(other.Values.Count); + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the EXIF profile from. + public ExifProfile(byte[] data) + { + this.Parts = ExifParts.All; + this.data = data; + this.InvalidTags = Array.Empty(); + } - foreach (IExifValue value in other.Values) - { - this.values.Add(value.DeepClone()); - } - } + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The invalid tags. + internal ExifProfile(List values, IReadOnlyList invalidTags) + { + this.Parts = ExifParts.All; + this.values = values; + this.InvalidTags = invalidTags; + } - if (other.data != null) - { - this.data = new byte[other.data.Length]; - other.data.AsSpan().CopyTo(this.data); - } - } + /// + /// Initializes a new instance of the class + /// by making a copy from another EXIF profile. + /// + /// The other EXIF profile, where the clone should be made from. + /// is null.> + private ExifProfile(ExifProfile other) + { + Guard.NotNull(other, nameof(other)); - /// - /// Gets or sets which parts will be written when the profile is added to an image. - /// - public ExifParts Parts { get; set; } + this.Parts = other.Parts; + this.thumbnailLength = other.thumbnailLength; + this.thumbnailOffset = other.thumbnailOffset; - /// - /// Gets the tags that where found but contained an invalid value. - /// - public IReadOnlyList InvalidTags { get; private set; } + this.InvalidTags = other.InvalidTags.Count > 0 + ? new List(other.InvalidTags) + : (IReadOnlyList)Array.Empty(); - /// - /// Gets the values of this EXIF profile. - /// - public IReadOnlyList Values + if (other.values != null) { - get + this.values = new List(other.Values.Count); + + foreach (IExifValue value in other.Values) { - this.InitializeValues(); - return this.values; + this.values.Add(value.DeepClone()); } } - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// - /// The . - /// - public Image CreateThumbnail() => this.CreateThumbnail(); - - /// - /// Returns the thumbnail in the EXIF profile when available. - /// - /// The pixel format. - /// - /// The . - /// - public Image CreateThumbnail() - where TPixel : unmanaged, IPixel + if (other.data != null) + { + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); + } + } + + /// + /// Gets or sets which parts will be written when the profile is added to an image. + /// + public ExifParts Parts { get; set; } + + /// + /// Gets the tags that where found but contained an invalid value. + /// + public IReadOnlyList InvalidTags { get; private set; } + + /// + /// Gets the values of this EXIF profile. + /// + public IReadOnlyList Values + { + get { this.InitializeValues(); + return this.values; + } + } - if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) - { - return null; - } + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// + /// The . + /// + public Image CreateThumbnail() => this.CreateThumbnail(); - if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) - { - return null; - } + /// + /// Returns the thumbnail in the EXIF profile when available. + /// + /// The pixel format. + /// + /// The . + /// + public Image CreateThumbnail() + where TPixel : unmanaged, IPixel + { + this.InitializeValues(); - using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) - { - return Image.Load(memStream); - } + if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) + { + return null; } - /// - /// Returns the value with the specified tag. - /// - /// The tag of the exif value. - /// The value with the specified tag. - /// The data type of the tag. - public IExifValue GetValue(ExifTag tag) + if (this.data is null || this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) { - IExifValue value = this.GetValueInternal(tag); - return value is null ? null : (IExifValue)value; + return null; } - /// - /// Removes the value with the specified tag. - /// - /// The tag of the EXIF value. - /// - /// True, if the value was removed, otherwise false. - /// - public bool RemoveValue(ExifTag tag) + using (var memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) { - this.InitializeValues(); + return Image.Load(memStream); + } + } - for (int i = 0; i < this.values.Count; i++) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - return true; - } - } + /// + /// Returns the value with the specified tag. + /// + /// The tag of the exif value. + /// The value with the specified tag. + /// The data type of the tag. + public IExifValue GetValue(ExifTag tag) + { + IExifValue value = this.GetValueInternal(tag); + return value is null ? null : (IExifValue)value; + } - return false; - } + /// + /// Removes the value with the specified tag. + /// + /// The tag of the EXIF value. + /// + /// True, if the value was removed, otherwise false. + /// + public bool RemoveValue(ExifTag tag) + { + this.InitializeValues(); - /// - /// Sets the value of the specified tag. - /// - /// The tag of the exif value. - /// The value. - /// The data type of the tag. - public void SetValue(ExifTag tag, TValueType value) - => this.SetValueInternal(tag, value); - - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[] ToByteArray() + for (int i = 0; i < this.values.Count; i++) { - if (this.values is null) + if (this.values[i].Tag == tag) { - return this.data; + this.values.RemoveAt(i); + return true; } - - if (this.values.Count == 0) - { - return Array.Empty(); - } - - var writer = new ExifWriter(this.values, this.Parts); - return writer.GetData(); } - /// - public ExifProfile DeepClone() => new ExifProfile(this); + return false; + } - /// - /// Returns the value with the specified tag. - /// - /// The tag of the exif value. - /// The value with the specified tag. - internal IExifValue GetValueInternal(ExifTag tag) + /// + /// Sets the value of the specified tag. + /// + /// The tag of the exif value. + /// The value. + /// The data type of the tag. + public void SetValue(ExifTag tag, TValueType value) + => this.SetValueInternal(tag, value); + + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.values is null) { - foreach (IExifValue exifValue in this.Values) - { - if (exifValue.Tag == tag) - { - return exifValue; - } - } + return this.data; + } - return null; + if (this.values.Count == 0) + { + return Array.Empty(); } - /// - /// Sets the value of the specified tag. - /// - /// The tag of the exif value. - /// The value. - internal void SetValueInternal(ExifTag tag, object value) + var writer = new ExifWriter(this.values, this.Parts); + return writer.GetData(); + } + + /// + public ExifProfile DeepClone() => new ExifProfile(this); + + /// + /// Returns the value with the specified tag. + /// + /// The tag of the exif value. + /// The value with the specified tag. + internal IExifValue GetValueInternal(ExifTag tag) + { + foreach (IExifValue exifValue in this.Values) { - foreach (IExifValue exifValue in this.Values) + if (exifValue.Tag == tag) { - if (exifValue.Tag == tag) - { - exifValue.TrySetValue(value); - return; - } + return exifValue; } + } + + return null; + } - ExifValue newExifValue = ExifValues.Create(tag); - if (newExifValue is null) + /// + /// Sets the value of the specified tag. + /// + /// The tag of the exif value. + /// The value. + internal void SetValueInternal(ExifTag tag, object value) + { + foreach (IExifValue exifValue in this.Values) + { + if (exifValue.Tag == tag) { - throw new NotSupportedException(); + exifValue.TrySetValue(value); + return; } - - newExifValue.TrySetValue(value); - this.values.Add(newExifValue); } - /// - /// Synchronizes the profiles with the specified metadata. - /// - /// The metadata. - internal void Sync(ImageMetadata metadata) + ExifValue newExifValue = ExifValues.Create(tag); + if (newExifValue is null) { - this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); - this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); + throw new NotSupportedException(); } - private void SyncResolution(ExifTag tag, double resolution) - { - IExifValue value = this.GetValue(tag); + newExifValue.TrySetValue(value); + this.values.Add(newExifValue); + } - if (value is null) - { - return; - } + /// + /// Synchronizes the profiles with the specified metadata. + /// + /// The metadata. + internal void Sync(ImageMetadata metadata) + { + this.SyncResolution(ExifTag.XResolution, metadata.HorizontalResolution); + this.SyncResolution(ExifTag.YResolution, metadata.VerticalResolution); + } - if (value.IsArray || value.DataType != ExifDataType.Rational) - { - this.RemoveValue(value.Tag); - } + private void SyncResolution(ExifTag tag, double resolution) + { + IExifValue value = this.GetValue(tag); - var newResolution = new Rational(resolution, false); - this.SetValue(tag, newResolution); + if (value is null) + { + return; } - private void InitializeValues() + if (value.IsArray || value.DataType != ExifDataType.Rational) { - if (this.values != null) - { - return; - } + this.RemoveValue(value.Tag); + } - if (this.data is null) - { - this.values = new List(); - return; - } + var newResolution = new Rational(resolution, false); + this.SetValue(tag, newResolution); + } - var reader = new ExifReader(this.data); + private void InitializeValues() + { + if (this.values != null) + { + return; + } + + if (this.data is null) + { + this.values = new List(); + return; + } - this.values = reader.ReadValues(); + var reader = new ExifReader(this.data); - this.InvalidTags = reader.InvalidTags.Count > 0 - ? new List(reader.InvalidTags) - : (IReadOnlyList)Array.Empty(); + this.values = reader.ReadValues(); - this.thumbnailOffset = (int)reader.ThumbnailOffset; - this.thumbnailLength = (int)reader.ThumbnailLength; - } + this.InvalidTags = reader.InvalidTags.Count > 0 + ? new List(reader.InvalidTags) + : (IReadOnlyList)Array.Empty(); + + this.thumbnailOffset = (int)reader.ThumbnailOffset; + this.thumbnailLength = (int)reader.ThumbnailLength; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 722f59316f..09daf601d0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -1,678 +1,673 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Buffers.Binary; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal class ExifReader : BaseExifReader { - internal class ExifReader : BaseExifReader + public ExifReader(byte[] exifData) + : this(exifData, null) { - public ExifReader(byte[] exifData) - : this(exifData, null) - { - } + } - public ExifReader(byte[] exifData, MemoryAllocator allocator) - : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator) - { - } + public ExifReader(byte[] exifData, MemoryAllocator allocator) + : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator) + { + } - /// - /// Reads and returns the collection of EXIF values. - /// - /// - /// The . - /// - public List ReadValues() - { - List values = new(); + /// + /// Reads and returns the collection of EXIF values. + /// + /// + /// The . + /// + public List ReadValues() + { + List values = new(); - // II == 0x4949 - this.IsBigEndian = this.ReadUInt16() != 0x4949; + // II == 0x4949 + this.IsBigEndian = this.ReadUInt16() != 0x4949; - if (this.ReadUInt16() != 0x002A) - { - return values; - } + if (this.ReadUInt16() != 0x002A) + { + return values; + } - uint ifdOffset = this.ReadUInt32(); - this.ReadValues(values, ifdOffset); + uint ifdOffset = this.ReadUInt32(); + this.ReadValues(values, ifdOffset); - uint thumbnailOffset = this.ReadUInt32(); - this.GetThumbnail(thumbnailOffset); + uint thumbnailOffset = this.ReadUInt32(); + this.GetThumbnail(thumbnailOffset); - this.ReadSubIfd(values); + this.ReadSubIfd(values); - this.ReadBigValues(values); + this.ReadBigValues(values); - return values; + return values; + } + + private void GetThumbnail(uint offset) + { + if (offset == 0) + { + return; } - private void GetThumbnail(uint offset) + List values = new(); + this.ReadValues(values, offset); + + for (int i = 0; i < values.Count; i++) { - if (offset == 0) + ExifValue value = (ExifValue)values[i]; + if (value == ExifTag.JPEGInterchangeFormat) { - return; + this.ThumbnailOffset = ((ExifLong)value).Value; } - - List values = new(); - this.ReadValues(values, offset); - - for (int i = 0; i < values.Count; i++) + else if (value == ExifTag.JPEGInterchangeFormatLength) { - ExifValue value = (ExifValue)values[i]; - if (value == ExifTag.JPEGInterchangeFormat) - { - this.ThumbnailOffset = ((ExifLong)value).Value; - } - else if (value == ExifTag.JPEGInterchangeFormatLength) - { - this.ThumbnailLength = ((ExifLong)value).Value; - } + this.ThumbnailLength = ((ExifLong)value).Value; } } } +} - /// - /// Reads and parses EXIF data from a stream. - /// - internal abstract class BaseExifReader - { - private readonly byte[] buf8 = new byte[8]; - private readonly byte[] buf4 = new byte[4]; - private readonly byte[] buf2 = new byte[2]; +/// +/// Reads and parses EXIF data from a stream. +/// +internal abstract class BaseExifReader +{ + private readonly byte[] buf8 = new byte[8]; + private readonly byte[] buf4 = new byte[4]; + private readonly byte[] buf2 = new byte[2]; - private readonly MemoryAllocator allocator; - private readonly Stream data; - private List invalidTags; + private readonly MemoryAllocator allocator; + private readonly Stream data; + private List invalidTags; - private List subIfds; + private List subIfds; - protected BaseExifReader(Stream stream, MemoryAllocator allocator) - { - this.data = stream ?? throw new ArgumentNullException(nameof(stream)); - this.allocator = allocator; - } + protected BaseExifReader(Stream stream, MemoryAllocator allocator) + { + this.data = stream ?? throw new ArgumentNullException(nameof(stream)); + this.allocator = allocator; + } - private delegate TDataType ConverterMethod(ReadOnlySpan data); + private delegate TDataType ConverterMethod(ReadOnlySpan data); - /// - /// Gets the invalid tags. - /// - public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); + /// + /// Gets the invalid tags. + /// + public IReadOnlyList InvalidTags => this.invalidTags ?? (IReadOnlyList)Array.Empty(); - /// - /// Gets or sets the thumbnail length in the byte stream. - /// - public uint ThumbnailLength { get; protected set; } + /// + /// Gets or sets the thumbnail length in the byte stream. + /// + public uint ThumbnailLength { get; protected set; } - /// - /// Gets or sets the thumbnail offset position in the byte stream. - /// - public uint ThumbnailOffset { get; protected set; } + /// + /// Gets or sets the thumbnail offset position in the byte stream. + /// + public uint ThumbnailOffset { get; protected set; } - public bool IsBigEndian { get; protected set; } + public bool IsBigEndian { get; protected set; } - public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new(); + public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new(); - protected void ReadBigValues(List values) + protected void ReadBigValues(List values) + { + if (this.BigValues.Count == 0) { - if (this.BigValues.Count == 0) - { - return; - } + return; + } - int maxSize = 0; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) - { - ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - DebugGuard.MustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); + int maxSize = 0; + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) + { + ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); + DebugGuard.MustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); - if ((int)size > maxSize) - { - maxSize = (int)size; - } + if ((int)size > maxSize) + { + maxSize = (int)size; } + } - if (this.allocator != null) + if (this.allocator != null) + { + // tiff, bigTiff + using IMemoryOwner memory = this.allocator.Allocate(maxSize); + Span buf = memory.GetSpan(); + foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) { - // tiff, bigTiff - using IMemoryOwner memory = this.allocator.Allocate(maxSize); - Span buf = memory.GetSpan(); - foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) - { - ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); - this.ReadBigValue(values, tag, buf[..(int)size]); - } + ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); + this.ReadBigValue(values, tag, buf[..(int)size]); } - else + } + else + { + // embedded exif + Span buf = maxSize <= 256 ? stackalloc byte[256] : new byte[maxSize]; + foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) { - // embedded exif - Span buf = maxSize <= 256 ? stackalloc byte[256] : new byte[maxSize]; - foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) - { - ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); - this.ReadBigValue(values, tag, buf[..(int)size]); - } + ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); + this.ReadBigValue(values, tag, buf[..(int)size]); } - - this.BigValues.Clear(); } - /// - /// Reads the values to the values collection. - /// - /// The values. - /// The IFD offset. - protected void ReadValues(List values, uint offset) + this.BigValues.Clear(); + } + + /// + /// Reads the values to the values collection. + /// + /// The values. + /// The IFD offset. + protected void ReadValues(List values, uint offset) + { + if (offset > this.data.Length) { - if (offset > this.data.Length) - { - return; - } + return; + } - this.Seek(offset); - int count = this.ReadUInt16(); + this.Seek(offset); + int count = this.ReadUInt16(); - Span offsetBuffer = stackalloc byte[4]; - for (int i = 0; i < count; i++) - { - this.ReadValue(values, offsetBuffer); - } + Span offsetBuffer = stackalloc byte[4]; + for (int i = 0; i < count; i++) + { + this.ReadValue(values, offsetBuffer); } + } - protected void ReadSubIfd(List values) + protected void ReadSubIfd(List values) + { + if (this.subIfds is not null) { - if (this.subIfds is not null) + foreach (ulong subIfdOffset in this.subIfds) { - foreach (ulong subIfdOffset in this.subIfds) - { - this.ReadValues(values, (uint)subIfdOffset); - } + this.ReadValues(values, (uint)subIfdOffset); } } + } - protected void ReadValues64(List values, ulong offset) - { - DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue."); + protected void ReadValues64(List values, ulong offset) + { + DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue."); - this.Seek(offset); - ulong count = this.ReadUInt64(); + this.Seek(offset); + ulong count = this.ReadUInt64(); - Span offsetBuffer = stackalloc byte[8]; - for (ulong i = 0; i < count; i++) - { - this.ReadValue64(values, offsetBuffer); - } + Span offsetBuffer = stackalloc byte[8]; + for (ulong i = 0; i < count; i++) + { + this.ReadValue64(values, offsetBuffer); } + } - protected void ReadBigValue(IList values, (ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag, Span buffer) + protected void ReadBigValue(IList values, (ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag, Span buffer) + { + this.Seek(tag.Offset); + if (this.TryReadSpan(buffer)) { - this.Seek(tag.Offset); - if (this.TryReadSpan(buffer)) - { - object value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); - this.Add(values, tag.Exif, value); - } + object value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); + this.Add(values, tag.Exif, value); } + } - private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) + private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) + { + int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); + int length = data.Length / dataTypeSize; + + TDataType[] result = new TDataType[length]; + + for (int i = 0; i < length; i++) { - int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); - int length = data.Length / dataTypeSize; + ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); - TDataType[] result = new TDataType[length]; + result.SetValue(converter(buffer), i); + } - for (int i = 0; i < length; i++) - { - ReadOnlySpan buffer = data.Slice(i * dataTypeSize, dataTypeSize); + return result; + } - result.SetValue(converter(buffer), i); - } + private static string ConvertToString(Encoding encoding, ReadOnlySpan buffer) + { + int nullCharIndex = buffer.IndexOf((byte)0); - return result; + if (nullCharIndex > -1) + { + buffer = buffer[..nullCharIndex]; } - private static string ConvertToString(Encoding encoding, ReadOnlySpan buffer) - { - int nullCharIndex = buffer.IndexOf((byte)0); + return encoding.GetString(buffer); + } - if (nullCharIndex > -1) - { - buffer = buffer[..nullCharIndex]; - } + private static byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; - return encoding.GetString(buffer); + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, bool isArray) + { + if (buffer.Length == 0) + { + return null; } - private static byte ConvertToByte(ReadOnlySpan buffer) => buffer[0]; - - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, bool isArray) + switch (dataType) { - if (buffer.Length == 0) - { + case ExifDataType.Unknown: return null; - } + case ExifDataType.Ascii: + return ConvertToString(ExifConstants.DefaultEncoding, buffer); + case ExifDataType.Byte: + case ExifDataType.Undefined: + if (!isArray) + { + return ConvertToByte(buffer); + } - switch (dataType) - { - case ExifDataType.Unknown: - return null; - case ExifDataType.Ascii: - return ConvertToString(ExifConstants.DefaultEncoding, buffer); - case ExifDataType.Byte: - case ExifDataType.Undefined: - if (!isArray) - { - return ConvertToByte(buffer); - } - - return buffer.ToArray(); - case ExifDataType.DoubleFloat: - if (!isArray) - { - return this.ConvertToDouble(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToDouble); - case ExifDataType.Long: - case ExifDataType.Ifd: - if (!isArray) - { - return this.ConvertToUInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt32); - case ExifDataType.Rational: - if (!isArray) - { - return this.ToRational(buffer); - } - - return ToArray(dataType, buffer, this.ToRational); - case ExifDataType.Short: - if (!isArray) - { - return this.ConvertToShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToShort); - case ExifDataType.SignedByte: - if (!isArray) - { - return this.ConvertToSignedByte(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedByte); - case ExifDataType.SignedLong: - if (!isArray) - { - return this.ConvertToInt32(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToInt32); - case ExifDataType.SignedRational: - if (!isArray) - { - return this.ToSignedRational(buffer); - } - - return ToArray(dataType, buffer, this.ToSignedRational); - case ExifDataType.SignedShort: - if (!isArray) - { - return this.ConvertToSignedShort(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSignedShort); - case ExifDataType.SingleFloat: - if (!isArray) - { - return this.ConvertToSingle(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToSingle); - case ExifDataType.Long8: - case ExifDataType.Ifd8: - if (!isArray) - { - return this.ConvertToUInt64(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt64); - case ExifDataType.SignedLong8: - if (!isArray) - { - return this.ConvertToInt64(buffer); - } - - return ToArray(dataType, buffer, this.ConvertToUInt64); - - default: - throw new NotSupportedException($"Data type {dataType} is not supported."); - } - } + return buffer.ToArray(); + case ExifDataType.DoubleFloat: + if (!isArray) + { + return this.ConvertToDouble(buffer); + } - private void ReadValue(List values, Span offsetBuffer) - { - // 2 | 2 | 4 | 4 - // tag | type | count | value offset - if ((this.data.Length - this.data.Position) < 12) - { - return; - } + return ToArray(dataType, buffer, this.ConvertToDouble); + case ExifDataType.Long: + case ExifDataType.Ifd: + if (!isArray) + { + return this.ConvertToUInt32(buffer); + } - ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); - ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + return ToArray(dataType, buffer, this.ConvertToUInt32); + case ExifDataType.Rational: + if (!isArray) + { + return this.ToRational(buffer); + } - uint numberOfComponents = this.ReadUInt32(); + return ToArray(dataType, buffer, this.ToRational); + case ExifDataType.Short: + if (!isArray) + { + return this.ConvertToShort(buffer); + } - this.TryReadSpan(offsetBuffer); + return ToArray(dataType, buffer, this.ConvertToShort); + case ExifDataType.SignedByte: + if (!isArray) + { + return this.ConvertToSignedByte(buffer); + } - // Ensure that the data type is valid - if (dataType == ExifDataType.Unknown) - { - return; - } + return ToArray(dataType, buffer, this.ConvertToSignedByte); + case ExifDataType.SignedLong: + if (!isArray) + { + return this.ConvertToInt32(buffer); + } - // Issue #132: ExifDataType == Undefined is treated like a byte array. - // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) - if (numberOfComponents == 0) - { - numberOfComponents = 4 / ExifDataTypes.GetSize(dataType); - } + return ToArray(dataType, buffer, this.ConvertToInt32); + case ExifDataType.SignedRational: + if (!isArray) + { + return this.ToSignedRational(buffer); + } - ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + return ToArray(dataType, buffer, this.ToSignedRational); + case ExifDataType.SignedShort: + if (!isArray) + { + return this.ConvertToSignedShort(buffer); + } - if (exifValue is null) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } + return ToArray(dataType, buffer, this.ConvertToSignedShort); + case ExifDataType.SingleFloat: + if (!isArray) + { + return this.ConvertToSingle(buffer); + } - uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > 4) - { - uint newOffset = this.ConvertToUInt32(offsetBuffer); + return ToArray(dataType, buffer, this.ConvertToSingle); + case ExifDataType.Long8: + case ExifDataType.Ifd8: + if (!isArray) + { + return this.ConvertToUInt64(buffer); + } - // Ensure that the new index does not overrun the data. - if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) + return ToArray(dataType, buffer, this.ConvertToUInt64); + case ExifDataType.SignedLong8: + if (!isArray) { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; + return this.ConvertToInt64(buffer); } - this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); - } - else - { - object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); - this.Add(values, exifValue, value); - } + return ToArray(dataType, buffer, this.ConvertToUInt64); + + default: + throw new NotSupportedException($"Data type {dataType} is not supported."); } + } - private void ReadValue64(List values, Span offsetBuffer) + private void ReadValue(List values, Span offsetBuffer) + { + // 2 | 2 | 4 | 4 + // tag | type | count | value offset + if ((this.data.Length - this.data.Position) < 12) { - if ((this.data.Length - this.data.Position) < 20) - { - return; - } + return; + } - ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); - ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); + ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); - ulong numberOfComponents = this.ReadUInt64(); + uint numberOfComponents = this.ReadUInt32(); - this.TryReadSpan(offsetBuffer); + this.TryReadSpan(offsetBuffer); - if (dataType == ExifDataType.Unknown) - { - return; - } - - if (numberOfComponents == 0) - { - numberOfComponents = 8 / ExifDataTypes.GetSize(dataType); - } + // Ensure that the data type is valid + if (dataType == ExifDataType.Unknown) + { + return; + } - ExifValue exifValue = tag switch - { - ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets), - ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts), - ExifTagValue.TileOffsets => new ExifLong8Array(ExifTagValue.TileOffsets), - ExifTagValue.TileByteCounts => new ExifLong8Array(ExifTagValue.TileByteCounts), - _ => ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents), - }; - if (exifValue is null) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } + // Issue #132: ExifDataType == Undefined is treated like a byte array. + // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) + if (numberOfComponents == 0) + { + numberOfComponents = 4 / ExifDataTypes.GetSize(dataType); + } - ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > 8) - { - ulong newOffset = this.ConvertToUInt64(offsetBuffer); - if (newOffset > ulong.MaxValue || newOffset > ((ulong)this.data.Length - size)) - { - this.AddInvalidTag(new UnkownExifTag(tag)); - return; - } + ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); - this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); - } - else - { - object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); - this.Add(values, exifValue, value); - } + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; } - private void Add(IList values, IExifValue exif, object value) + uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); + if (size > 4) { - if (!exif.TrySetValue(value)) - { - return; - } + uint newOffset = this.ConvertToUInt32(offsetBuffer); - foreach (IExifValue val in values) + // Ensure that the new index does not overrun the data. + if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) { - // Sometimes duplicates appear, can compare val.Tag == exif.Tag - if (val == exif) - { - Debug.WriteLine($"Duplicate Exif tag: tag={exif.Tag}, dataType={exif.DataType}"); - return; - } + this.AddInvalidTag(new UnkownExifTag(tag)); + return; } - if (exif.Tag == ExifTag.SubIFDOffset) - { - this.AddSubIfd(value); - } - else if (exif.Tag == ExifTag.GPSIFDOffset) - { - this.AddSubIfd(value); - } - else - { - values.Add(exif); - } + this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); } - - private void AddInvalidTag(ExifTag tag) - => (this.invalidTags ??= new List()).Add(tag); - - private void AddSubIfd(object val) - => (this.subIfds ??= new List()).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture)); - - private void Seek(ulong pos) - => this.data.Seek((long)pos, SeekOrigin.Begin); - - private bool TryReadSpan(Span span) + else { - int length = span.Length; - if ((this.data.Length - this.data.Position) < length) - { - return false; - } + object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); + this.Add(values, exifValue, value); + } + } - int read = this.data.Read(span); - return read == length; + private void ReadValue64(List values, Span offsetBuffer) + { + if ((this.data.Length - this.data.Position) < 20) + { + return; } - protected ulong ReadUInt64() => - this.TryReadSpan(this.buf8) - ? this.ConvertToUInt64(this.buf8) - : default; + ExifTagValue tag = (ExifTagValue)this.ReadUInt16(); + ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); - // Known as Long in Exif Specification. - protected uint ReadUInt32() => - this.TryReadSpan(this.buf4) - ? this.ConvertToUInt32(this.buf4) - : default; + ulong numberOfComponents = this.ReadUInt64(); - protected ushort ReadUInt16() => this.TryReadSpan(this.buf2) - ? this.ConvertToShort(this.buf2) - : default; + this.TryReadSpan(offsetBuffer); - private long ConvertToInt64(ReadOnlySpan buffer) + if (dataType == ExifDataType.Unknown) { - if (buffer.Length < 8) - { - return default; - } - - return this.IsBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); + return; } - private ulong ConvertToUInt64(ReadOnlySpan buffer) + if (numberOfComponents == 0) { - if (buffer.Length < 8) - { - return default; - } + numberOfComponents = 8 / ExifDataTypes.GetSize(dataType); + } - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt64BigEndian(buffer) - : BinaryPrimitives.ReadUInt64LittleEndian(buffer); + ExifValue exifValue = tag switch + { + ExifTagValue.StripOffsets => new ExifLong8Array(ExifTagValue.StripOffsets), + ExifTagValue.StripByteCounts => new ExifLong8Array(ExifTagValue.StripByteCounts), + ExifTagValue.TileOffsets => new ExifLong8Array(ExifTagValue.TileOffsets), + ExifTagValue.TileByteCounts => new ExifLong8Array(ExifTagValue.TileByteCounts), + _ => ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents), + }; + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; } - private double ConvertToDouble(ReadOnlySpan buffer) + ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); + if (size > 8) { - if (buffer.Length < 8) + ulong newOffset = this.ConvertToUInt64(offsetBuffer); + if (newOffset > ulong.MaxValue || newOffset > ((ulong)this.data.Length - size)) { - return default; + this.AddInvalidTag(new UnkownExifTag(tag)); + return; } - long intValue = this.IsBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); + this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); + } + else + { + object value = this.ConvertValue(dataType, offsetBuffer[..(int)size], numberOfComponents > 1 || exifValue.IsArray); + this.Add(values, exifValue, value); + } + } - return Unsafe.As(ref intValue); + private void Add(IList values, IExifValue exif, object value) + { + if (!exif.TrySetValue(value)) + { + return; } - private uint ConvertToUInt32(ReadOnlySpan buffer) + foreach (IExifValue val in values) { - // Known as Long in Exif Specification. - if (buffer.Length < 4) + // Sometimes duplicates appear, can compare val.Tag == exif.Tag + if (val == exif) { - return default; + Debug.WriteLine($"Duplicate Exif tag: tag={exif.Tag}, dataType={exif.DataType}"); + return; } - - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt32BigEndian(buffer) - : BinaryPrimitives.ReadUInt32LittleEndian(buffer); } - private ushort ConvertToShort(ReadOnlySpan buffer) + if (exif.Tag == ExifTag.SubIFDOffset) { - if (buffer.Length < 2) - { - return default; - } + this.AddSubIfd(value); + } + else if (exif.Tag == ExifTag.GPSIFDOffset) + { + this.AddSubIfd(value); + } + else + { + values.Add(exif); + } + } + + private void AddInvalidTag(ExifTag tag) + => (this.invalidTags ??= new List()).Add(tag); + + private void AddSubIfd(object val) + => (this.subIfds ??= new List()).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture)); + + private void Seek(ulong pos) + => this.data.Seek((long)pos, SeekOrigin.Begin); - return this.IsBigEndian - ? BinaryPrimitives.ReadUInt16BigEndian(buffer) - : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + private bool TryReadSpan(Span span) + { + int length = span.Length; + if ((this.data.Length - this.data.Position) < length) + { + return false; } - private float ConvertToSingle(ReadOnlySpan buffer) + int read = this.data.Read(span); + return read == length; + } + + protected ulong ReadUInt64() => + this.TryReadSpan(this.buf8) + ? this.ConvertToUInt64(this.buf8) + : default; + + // Known as Long in Exif Specification. + protected uint ReadUInt32() => + this.TryReadSpan(this.buf4) + ? this.ConvertToUInt32(this.buf4) + : default; + + protected ushort ReadUInt16() => this.TryReadSpan(this.buf2) + ? this.ConvertToShort(this.buf2) + : default; + + private long ConvertToInt64(ReadOnlySpan buffer) + { + if (buffer.Length < 8) { - if (buffer.Length < 4) - { - return default; - } + return default; + } - int intValue = this.IsBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); + return this.IsBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + } - return Unsafe.As(ref intValue); + private ulong ConvertToUInt64(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; } - private Rational ToRational(ReadOnlySpan buffer) + return this.IsBigEndian + ? BinaryPrimitives.ReadUInt64BigEndian(buffer) + : BinaryPrimitives.ReadUInt64LittleEndian(buffer); + } + + private double ConvertToDouble(ReadOnlySpan buffer) + { + if (buffer.Length < 8) { - if (buffer.Length < 8) - { - return default; - } + return default; + } - uint numerator = this.ConvertToUInt32(buffer[..4]); - uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); + long intValue = this.IsBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } - return new Rational(numerator, denominator, false); + private uint ConvertToUInt32(ReadOnlySpan buffer) + { + // Known as Long in Exif Specification. + if (buffer.Length < 4) + { + return default; } - private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); + return this.IsBigEndian + ? BinaryPrimitives.ReadUInt32BigEndian(buffer) + : BinaryPrimitives.ReadUInt32LittleEndian(buffer); + } - private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification + private ushort ConvertToShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) { - if (buffer.Length < 4) - { - return default; - } + return default; + } + + return this.IsBigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(buffer) + : BinaryPrimitives.ReadUInt16LittleEndian(buffer); + } - return this.IsBigEndian - ? BinaryPrimitives.ReadInt32BigEndian(buffer) - : BinaryPrimitives.ReadInt32LittleEndian(buffer); + private float ConvertToSingle(ReadOnlySpan buffer) + { + if (buffer.Length < 4) + { + return default; } - private SignedRational ToSignedRational(ReadOnlySpan buffer) + int intValue = this.IsBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + + return Unsafe.As(ref intValue); + } + + private Rational ToRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) { - if (buffer.Length < 8) - { - return default; - } + return default; + } + + uint numerator = this.ConvertToUInt32(buffer[..4]); + uint denominator = this.ConvertToUInt32(buffer.Slice(4, 4)); - int numerator = this.ConvertToInt32(buffer[..4]); - int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); + return new Rational(numerator, denominator, false); + } - return new SignedRational(numerator, denominator, false); + private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); + + private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification + { + if (buffer.Length < 4) + { + return default; } - private short ConvertToSignedShort(ReadOnlySpan buffer) + return this.IsBigEndian + ? BinaryPrimitives.ReadInt32BigEndian(buffer) + : BinaryPrimitives.ReadInt32LittleEndian(buffer); + } + + private SignedRational ToSignedRational(ReadOnlySpan buffer) + { + if (buffer.Length < 8) { - if (buffer.Length < 2) - { - return default; - } + return default; + } - return this.IsBigEndian - ? BinaryPrimitives.ReadInt16BigEndian(buffer) - : BinaryPrimitives.ReadInt16LittleEndian(buffer); + int numerator = this.ConvertToInt32(buffer[..4]); + int denominator = this.ConvertToInt32(buffer.Slice(4, 4)); + + return new SignedRational(numerator, denominator, false); + } + + private short ConvertToSignedShort(ReadOnlySpan buffer) + { + if (buffer.Length < 2) + { + return default; } + + return this.IsBigEndian + ? BinaryPrimitives.ReadInt16BigEndian(buffer) + : BinaryPrimitives.ReadInt16LittleEndian(buffer); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs index 8b4efad9ab..b3957dfb2a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTagDescriptionAttribute.cs @@ -1,55 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Class that provides a description for an ExifTag value. +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] +internal sealed class ExifTagDescriptionAttribute : Attribute { /// - /// Class that provides a description for an ExifTag value. + /// Initializes a new instance of the class. + /// + /// The value of the exif tag. + /// The description for the value of the exif tag. + public ExifTagDescriptionAttribute(object value, string description) + { + } + + /// + /// Gets the tag description from any custom attributes. /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] - internal sealed class ExifTagDescriptionAttribute : Attribute + /// The tag. + /// The value. + /// + /// The . + /// + public static string GetDescription(ExifTag tag, object value) { - /// - /// Initializes a new instance of the class. - /// - /// The value of the exif tag. - /// The description for the value of the exif tag. - public ExifTagDescriptionAttribute(object value, string description) + var tagValue = (ExifTagValue)(ushort)tag; + FieldInfo field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + if (field is null) { + return null; } - /// - /// Gets the tag description from any custom attributes. - /// - /// The tag. - /// The value. - /// - /// The . - /// - public static string GetDescription(ExifTag tag, object value) + foreach (CustomAttributeData customAttribute in field.CustomAttributes) { - var tagValue = (ExifTagValue)(ushort)tag; - FieldInfo field = typeof(ExifTagValue).GetField(tagValue.ToString(), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + object attributeValue = customAttribute.ConstructorArguments[0].Value; - if (field is null) + if (Equals(attributeValue, value)) { - return null; + return (string)customAttribute.ConstructorArguments[1].Value; } - - foreach (CustomAttributeData customAttribute in field.CustomAttributes) - { - object attributeValue = customAttribute.ConstructorArguments[0].Value; - - if (Equals(attributeValue, value)) - { - return (string)customAttribute.ConstructorArguments[1].Value; - } - } - - return null; } + + return null; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs index 7c784997d8..27d97ffaca 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifTags.cs @@ -1,275 +1,274 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal static class ExifTags { - internal static class ExifTags + public static ExifParts GetPart(ExifTag tag) { - public static ExifParts GetPart(ExifTag tag) + switch ((ExifTagValue)(ushort)tag) { - switch ((ExifTagValue)(ushort)tag) - { - case ExifTagValue.SubfileType: - case ExifTagValue.OldSubfileType: - case ExifTagValue.ImageWidth: - case ExifTagValue.ImageLength: - case ExifTagValue.BitsPerSample: - case ExifTagValue.Compression: - case ExifTagValue.PhotometricInterpretation: - case ExifTagValue.Thresholding: - case ExifTagValue.CellWidth: - case ExifTagValue.CellLength: - case ExifTagValue.FillOrder: - case ExifTagValue.DocumentName: - case ExifTagValue.ImageDescription: - case ExifTagValue.Make: - case ExifTagValue.Model: - case ExifTagValue.StripOffsets: - case ExifTagValue.Orientation: - case ExifTagValue.SamplesPerPixel: - case ExifTagValue.RowsPerStrip: - case ExifTagValue.StripByteCounts: - case ExifTagValue.MinSampleValue: - case ExifTagValue.MaxSampleValue: - case ExifTagValue.XResolution: - case ExifTagValue.YResolution: - case ExifTagValue.PlanarConfiguration: - case ExifTagValue.PageName: - case ExifTagValue.XPosition: - case ExifTagValue.YPosition: - case ExifTagValue.FreeOffsets: - case ExifTagValue.FreeByteCounts: - case ExifTagValue.GrayResponseUnit: - case ExifTagValue.GrayResponseCurve: - case ExifTagValue.T4Options: - case ExifTagValue.T6Options: - case ExifTagValue.ResolutionUnit: - case ExifTagValue.PageNumber: - case ExifTagValue.ColorResponseUnit: - case ExifTagValue.TransferFunction: - case ExifTagValue.Software: - case ExifTagValue.DateTime: - case ExifTagValue.Artist: - case ExifTagValue.HostComputer: - case ExifTagValue.Predictor: - case ExifTagValue.WhitePoint: - case ExifTagValue.PrimaryChromaticities: - case ExifTagValue.ColorMap: - case ExifTagValue.HalftoneHints: - case ExifTagValue.TileWidth: - case ExifTagValue.TileLength: - case ExifTagValue.TileOffsets: - case ExifTagValue.TileByteCounts: - case ExifTagValue.BadFaxLines: - case ExifTagValue.CleanFaxData: - case ExifTagValue.ConsecutiveBadFaxLines: - case ExifTagValue.InkSet: - case ExifTagValue.InkNames: - case ExifTagValue.NumberOfInks: - case ExifTagValue.DotRange: - case ExifTagValue.TargetPrinter: - case ExifTagValue.ExtraSamples: - case ExifTagValue.SampleFormat: - case ExifTagValue.SMinSampleValue: - case ExifTagValue.SMaxSampleValue: - case ExifTagValue.TransferRange: - case ExifTagValue.ClipPath: - case ExifTagValue.XClipPathUnits: - case ExifTagValue.YClipPathUnits: - case ExifTagValue.Indexed: - case ExifTagValue.JPEGTables: - case ExifTagValue.OPIProxy: - case ExifTagValue.ProfileType: - case ExifTagValue.FaxProfile: - case ExifTagValue.CodingMethods: - case ExifTagValue.VersionYear: - case ExifTagValue.ModeNumber: - case ExifTagValue.Decode: - case ExifTagValue.DefaultImageColor: - case ExifTagValue.T82ptions: - case ExifTagValue.JPEGProc: - case ExifTagValue.JPEGInterchangeFormat: - case ExifTagValue.JPEGInterchangeFormatLength: - case ExifTagValue.JPEGRestartInterval: - case ExifTagValue.JPEGLosslessPredictors: - case ExifTagValue.JPEGPointTransforms: - case ExifTagValue.JPEGQTables: - case ExifTagValue.JPEGDCTables: - case ExifTagValue.JPEGACTables: - case ExifTagValue.YCbCrCoefficients: - case ExifTagValue.YCbCrPositioning: - case ExifTagValue.YCbCrSubsampling: - case ExifTagValue.ReferenceBlackWhite: - case ExifTagValue.StripRowCounts: - case ExifTagValue.XMP: - case ExifTagValue.Rating: - case ExifTagValue.RatingPercent: - case ExifTagValue.ImageID: - case ExifTagValue.CFARepeatPatternDim: - case ExifTagValue.CFAPattern2: - case ExifTagValue.BatteryLevel: - case ExifTagValue.Copyright: - case ExifTagValue.MDFileTag: - case ExifTagValue.MDScalePixel: - case ExifTagValue.MDLabName: - case ExifTagValue.MDSampleInfo: - case ExifTagValue.MDPrepDate: - case ExifTagValue.MDPrepTime: - case ExifTagValue.MDFileUnits: - case ExifTagValue.PixelScale: - case ExifTagValue.IntergraphPacketData: - case ExifTagValue.IntergraphRegisters: - case ExifTagValue.IntergraphMatrix: - case ExifTagValue.ModelTiePoint: - case ExifTagValue.SEMInfo: - case ExifTagValue.ModelTransform: - case ExifTagValue.ImageLayer: - case ExifTagValue.FaxRecvParams: - case ExifTagValue.FaxSubaddress: - case ExifTagValue.FaxRecvTime: - case ExifTagValue.ImageSourceData: - case ExifTagValue.XPTitle: - case ExifTagValue.XPComment: - case ExifTagValue.XPAuthor: - case ExifTagValue.XPKeywords: - case ExifTagValue.XPSubject: - case ExifTagValue.GDALMetadata: - case ExifTagValue.GDALNoData: - return ExifParts.IfdTags; + case ExifTagValue.SubfileType: + case ExifTagValue.OldSubfileType: + case ExifTagValue.ImageWidth: + case ExifTagValue.ImageLength: + case ExifTagValue.BitsPerSample: + case ExifTagValue.Compression: + case ExifTagValue.PhotometricInterpretation: + case ExifTagValue.Thresholding: + case ExifTagValue.CellWidth: + case ExifTagValue.CellLength: + case ExifTagValue.FillOrder: + case ExifTagValue.DocumentName: + case ExifTagValue.ImageDescription: + case ExifTagValue.Make: + case ExifTagValue.Model: + case ExifTagValue.StripOffsets: + case ExifTagValue.Orientation: + case ExifTagValue.SamplesPerPixel: + case ExifTagValue.RowsPerStrip: + case ExifTagValue.StripByteCounts: + case ExifTagValue.MinSampleValue: + case ExifTagValue.MaxSampleValue: + case ExifTagValue.XResolution: + case ExifTagValue.YResolution: + case ExifTagValue.PlanarConfiguration: + case ExifTagValue.PageName: + case ExifTagValue.XPosition: + case ExifTagValue.YPosition: + case ExifTagValue.FreeOffsets: + case ExifTagValue.FreeByteCounts: + case ExifTagValue.GrayResponseUnit: + case ExifTagValue.GrayResponseCurve: + case ExifTagValue.T4Options: + case ExifTagValue.T6Options: + case ExifTagValue.ResolutionUnit: + case ExifTagValue.PageNumber: + case ExifTagValue.ColorResponseUnit: + case ExifTagValue.TransferFunction: + case ExifTagValue.Software: + case ExifTagValue.DateTime: + case ExifTagValue.Artist: + case ExifTagValue.HostComputer: + case ExifTagValue.Predictor: + case ExifTagValue.WhitePoint: + case ExifTagValue.PrimaryChromaticities: + case ExifTagValue.ColorMap: + case ExifTagValue.HalftoneHints: + case ExifTagValue.TileWidth: + case ExifTagValue.TileLength: + case ExifTagValue.TileOffsets: + case ExifTagValue.TileByteCounts: + case ExifTagValue.BadFaxLines: + case ExifTagValue.CleanFaxData: + case ExifTagValue.ConsecutiveBadFaxLines: + case ExifTagValue.InkSet: + case ExifTagValue.InkNames: + case ExifTagValue.NumberOfInks: + case ExifTagValue.DotRange: + case ExifTagValue.TargetPrinter: + case ExifTagValue.ExtraSamples: + case ExifTagValue.SampleFormat: + case ExifTagValue.SMinSampleValue: + case ExifTagValue.SMaxSampleValue: + case ExifTagValue.TransferRange: + case ExifTagValue.ClipPath: + case ExifTagValue.XClipPathUnits: + case ExifTagValue.YClipPathUnits: + case ExifTagValue.Indexed: + case ExifTagValue.JPEGTables: + case ExifTagValue.OPIProxy: + case ExifTagValue.ProfileType: + case ExifTagValue.FaxProfile: + case ExifTagValue.CodingMethods: + case ExifTagValue.VersionYear: + case ExifTagValue.ModeNumber: + case ExifTagValue.Decode: + case ExifTagValue.DefaultImageColor: + case ExifTagValue.T82ptions: + case ExifTagValue.JPEGProc: + case ExifTagValue.JPEGInterchangeFormat: + case ExifTagValue.JPEGInterchangeFormatLength: + case ExifTagValue.JPEGRestartInterval: + case ExifTagValue.JPEGLosslessPredictors: + case ExifTagValue.JPEGPointTransforms: + case ExifTagValue.JPEGQTables: + case ExifTagValue.JPEGDCTables: + case ExifTagValue.JPEGACTables: + case ExifTagValue.YCbCrCoefficients: + case ExifTagValue.YCbCrPositioning: + case ExifTagValue.YCbCrSubsampling: + case ExifTagValue.ReferenceBlackWhite: + case ExifTagValue.StripRowCounts: + case ExifTagValue.XMP: + case ExifTagValue.Rating: + case ExifTagValue.RatingPercent: + case ExifTagValue.ImageID: + case ExifTagValue.CFARepeatPatternDim: + case ExifTagValue.CFAPattern2: + case ExifTagValue.BatteryLevel: + case ExifTagValue.Copyright: + case ExifTagValue.MDFileTag: + case ExifTagValue.MDScalePixel: + case ExifTagValue.MDLabName: + case ExifTagValue.MDSampleInfo: + case ExifTagValue.MDPrepDate: + case ExifTagValue.MDPrepTime: + case ExifTagValue.MDFileUnits: + case ExifTagValue.PixelScale: + case ExifTagValue.IntergraphPacketData: + case ExifTagValue.IntergraphRegisters: + case ExifTagValue.IntergraphMatrix: + case ExifTagValue.ModelTiePoint: + case ExifTagValue.SEMInfo: + case ExifTagValue.ModelTransform: + case ExifTagValue.ImageLayer: + case ExifTagValue.FaxRecvParams: + case ExifTagValue.FaxSubaddress: + case ExifTagValue.FaxRecvTime: + case ExifTagValue.ImageSourceData: + case ExifTagValue.XPTitle: + case ExifTagValue.XPComment: + case ExifTagValue.XPAuthor: + case ExifTagValue.XPKeywords: + case ExifTagValue.XPSubject: + case ExifTagValue.GDALMetadata: + case ExifTagValue.GDALNoData: + return ExifParts.IfdTags; - case ExifTagValue.ExposureTime: - case ExifTagValue.FNumber: - case ExifTagValue.ExposureProgram: - case ExifTagValue.SpectralSensitivity: - case ExifTagValue.ISOSpeedRatings: - case ExifTagValue.OECF: - case ExifTagValue.Interlace: - case ExifTagValue.TimeZoneOffset: - case ExifTagValue.SelfTimerMode: - case ExifTagValue.SensitivityType: - case ExifTagValue.StandardOutputSensitivity: - case ExifTagValue.RecommendedExposureIndex: - case ExifTagValue.ISOSpeed: - case ExifTagValue.ISOSpeedLatitudeyyy: - case ExifTagValue.ISOSpeedLatitudezzz: - case ExifTagValue.ExifVersion: - case ExifTagValue.DateTimeOriginal: - case ExifTagValue.DateTimeDigitized: - case ExifTagValue.OffsetTime: - case ExifTagValue.OffsetTimeOriginal: - case ExifTagValue.OffsetTimeDigitized: - case ExifTagValue.ComponentsConfiguration: - case ExifTagValue.CompressedBitsPerPixel: - case ExifTagValue.ShutterSpeedValue: - case ExifTagValue.ApertureValue: - case ExifTagValue.BrightnessValue: - case ExifTagValue.ExposureBiasValue: - case ExifTagValue.MaxApertureValue: - case ExifTagValue.SubjectDistance: - case ExifTagValue.MeteringMode: - case ExifTagValue.LightSource: - case ExifTagValue.Flash: - case ExifTagValue.FocalLength: - case ExifTagValue.FlashEnergy2: - case ExifTagValue.SpatialFrequencyResponse2: - case ExifTagValue.Noise: - case ExifTagValue.FocalPlaneXResolution2: - case ExifTagValue.FocalPlaneYResolution2: - case ExifTagValue.FocalPlaneResolutionUnit2: - case ExifTagValue.ImageNumber: - case ExifTagValue.SecurityClassification: - case ExifTagValue.ImageHistory: - case ExifTagValue.SubjectArea: - case ExifTagValue.ExposureIndex2: - case ExifTagValue.TIFFEPStandardID: - case ExifTagValue.SensingMethod2: - case ExifTagValue.MakerNote: - case ExifTagValue.UserComment: - case ExifTagValue.SubsecTime: - case ExifTagValue.SubsecTimeOriginal: - case ExifTagValue.SubsecTimeDigitized: - case ExifTagValue.AmbientTemperature: - case ExifTagValue.Humidity: - case ExifTagValue.Pressure: - case ExifTagValue.WaterDepth: - case ExifTagValue.Acceleration: - case ExifTagValue.CameraElevationAngle: - case ExifTagValue.FlashpixVersion: - case ExifTagValue.ColorSpace: - case ExifTagValue.PixelXDimension: - case ExifTagValue.PixelYDimension: - case ExifTagValue.RelatedSoundFile: - case ExifTagValue.FlashEnergy: - case ExifTagValue.SpatialFrequencyResponse: - case ExifTagValue.FocalPlaneXResolution: - case ExifTagValue.FocalPlaneYResolution: - case ExifTagValue.FocalPlaneResolutionUnit: - case ExifTagValue.SubjectLocation: - case ExifTagValue.ExposureIndex: - case ExifTagValue.SensingMethod: - case ExifTagValue.FileSource: - case ExifTagValue.SceneType: - case ExifTagValue.CFAPattern: - case ExifTagValue.CustomRendered: - case ExifTagValue.ExposureMode: - case ExifTagValue.WhiteBalance: - case ExifTagValue.DigitalZoomRatio: - case ExifTagValue.FocalLengthIn35mmFilm: - case ExifTagValue.SceneCaptureType: - case ExifTagValue.GainControl: - case ExifTagValue.Contrast: - case ExifTagValue.Saturation: - case ExifTagValue.Sharpness: - case ExifTagValue.DeviceSettingDescription: - case ExifTagValue.SubjectDistanceRange: - case ExifTagValue.ImageUniqueID: - case ExifTagValue.OwnerName: - case ExifTagValue.SerialNumber: - case ExifTagValue.LensSpecification: - case ExifTagValue.LensMake: - case ExifTagValue.LensModel: - case ExifTagValue.LensSerialNumber: - return ExifParts.ExifTags; + case ExifTagValue.ExposureTime: + case ExifTagValue.FNumber: + case ExifTagValue.ExposureProgram: + case ExifTagValue.SpectralSensitivity: + case ExifTagValue.ISOSpeedRatings: + case ExifTagValue.OECF: + case ExifTagValue.Interlace: + case ExifTagValue.TimeZoneOffset: + case ExifTagValue.SelfTimerMode: + case ExifTagValue.SensitivityType: + case ExifTagValue.StandardOutputSensitivity: + case ExifTagValue.RecommendedExposureIndex: + case ExifTagValue.ISOSpeed: + case ExifTagValue.ISOSpeedLatitudeyyy: + case ExifTagValue.ISOSpeedLatitudezzz: + case ExifTagValue.ExifVersion: + case ExifTagValue.DateTimeOriginal: + case ExifTagValue.DateTimeDigitized: + case ExifTagValue.OffsetTime: + case ExifTagValue.OffsetTimeOriginal: + case ExifTagValue.OffsetTimeDigitized: + case ExifTagValue.ComponentsConfiguration: + case ExifTagValue.CompressedBitsPerPixel: + case ExifTagValue.ShutterSpeedValue: + case ExifTagValue.ApertureValue: + case ExifTagValue.BrightnessValue: + case ExifTagValue.ExposureBiasValue: + case ExifTagValue.MaxApertureValue: + case ExifTagValue.SubjectDistance: + case ExifTagValue.MeteringMode: + case ExifTagValue.LightSource: + case ExifTagValue.Flash: + case ExifTagValue.FocalLength: + case ExifTagValue.FlashEnergy2: + case ExifTagValue.SpatialFrequencyResponse2: + case ExifTagValue.Noise: + case ExifTagValue.FocalPlaneXResolution2: + case ExifTagValue.FocalPlaneYResolution2: + case ExifTagValue.FocalPlaneResolutionUnit2: + case ExifTagValue.ImageNumber: + case ExifTagValue.SecurityClassification: + case ExifTagValue.ImageHistory: + case ExifTagValue.SubjectArea: + case ExifTagValue.ExposureIndex2: + case ExifTagValue.TIFFEPStandardID: + case ExifTagValue.SensingMethod2: + case ExifTagValue.MakerNote: + case ExifTagValue.UserComment: + case ExifTagValue.SubsecTime: + case ExifTagValue.SubsecTimeOriginal: + case ExifTagValue.SubsecTimeDigitized: + case ExifTagValue.AmbientTemperature: + case ExifTagValue.Humidity: + case ExifTagValue.Pressure: + case ExifTagValue.WaterDepth: + case ExifTagValue.Acceleration: + case ExifTagValue.CameraElevationAngle: + case ExifTagValue.FlashpixVersion: + case ExifTagValue.ColorSpace: + case ExifTagValue.PixelXDimension: + case ExifTagValue.PixelYDimension: + case ExifTagValue.RelatedSoundFile: + case ExifTagValue.FlashEnergy: + case ExifTagValue.SpatialFrequencyResponse: + case ExifTagValue.FocalPlaneXResolution: + case ExifTagValue.FocalPlaneYResolution: + case ExifTagValue.FocalPlaneResolutionUnit: + case ExifTagValue.SubjectLocation: + case ExifTagValue.ExposureIndex: + case ExifTagValue.SensingMethod: + case ExifTagValue.FileSource: + case ExifTagValue.SceneType: + case ExifTagValue.CFAPattern: + case ExifTagValue.CustomRendered: + case ExifTagValue.ExposureMode: + case ExifTagValue.WhiteBalance: + case ExifTagValue.DigitalZoomRatio: + case ExifTagValue.FocalLengthIn35mmFilm: + case ExifTagValue.SceneCaptureType: + case ExifTagValue.GainControl: + case ExifTagValue.Contrast: + case ExifTagValue.Saturation: + case ExifTagValue.Sharpness: + case ExifTagValue.DeviceSettingDescription: + case ExifTagValue.SubjectDistanceRange: + case ExifTagValue.ImageUniqueID: + case ExifTagValue.OwnerName: + case ExifTagValue.SerialNumber: + case ExifTagValue.LensSpecification: + case ExifTagValue.LensMake: + case ExifTagValue.LensModel: + case ExifTagValue.LensSerialNumber: + return ExifParts.ExifTags; - case ExifTagValue.GPSVersionID: - case ExifTagValue.GPSLatitudeRef: - case ExifTagValue.GPSLatitude: - case ExifTagValue.GPSLongitudeRef: - case ExifTagValue.GPSLongitude: - case ExifTagValue.GPSAltitudeRef: - case ExifTagValue.GPSAltitude: - case ExifTagValue.GPSTimestamp: - case ExifTagValue.GPSSatellites: - case ExifTagValue.GPSStatus: - case ExifTagValue.GPSMeasureMode: - case ExifTagValue.GPSDOP: - case ExifTagValue.GPSSpeedRef: - case ExifTagValue.GPSSpeed: - case ExifTagValue.GPSTrackRef: - case ExifTagValue.GPSTrack: - case ExifTagValue.GPSImgDirectionRef: - case ExifTagValue.GPSImgDirection: - case ExifTagValue.GPSMapDatum: - case ExifTagValue.GPSDestLatitudeRef: - case ExifTagValue.GPSDestLatitude: - case ExifTagValue.GPSDestLongitudeRef: - case ExifTagValue.GPSDestLongitude: - case ExifTagValue.GPSDestBearingRef: - case ExifTagValue.GPSDestBearing: - case ExifTagValue.GPSDestDistanceRef: - case ExifTagValue.GPSDestDistance: - case ExifTagValue.GPSProcessingMethod: - case ExifTagValue.GPSAreaInformation: - case ExifTagValue.GPSDateStamp: - case ExifTagValue.GPSDifferential: - return ExifParts.GpsTags; + case ExifTagValue.GPSVersionID: + case ExifTagValue.GPSLatitudeRef: + case ExifTagValue.GPSLatitude: + case ExifTagValue.GPSLongitudeRef: + case ExifTagValue.GPSLongitude: + case ExifTagValue.GPSAltitudeRef: + case ExifTagValue.GPSAltitude: + case ExifTagValue.GPSTimestamp: + case ExifTagValue.GPSSatellites: + case ExifTagValue.GPSStatus: + case ExifTagValue.GPSMeasureMode: + case ExifTagValue.GPSDOP: + case ExifTagValue.GPSSpeedRef: + case ExifTagValue.GPSSpeed: + case ExifTagValue.GPSTrackRef: + case ExifTagValue.GPSTrack: + case ExifTagValue.GPSImgDirectionRef: + case ExifTagValue.GPSImgDirection: + case ExifTagValue.GPSMapDatum: + case ExifTagValue.GPSDestLatitudeRef: + case ExifTagValue.GPSDestLatitude: + case ExifTagValue.GPSDestLongitudeRef: + case ExifTagValue.GPSDestLongitude: + case ExifTagValue.GPSDestBearingRef: + case ExifTagValue.GPSDestBearing: + case ExifTagValue.GPSDestDistanceRef: + case ExifTagValue.GPSDestDistance: + case ExifTagValue.GPSProcessingMethod: + case ExifTagValue.GPSAreaInformation: + case ExifTagValue.GPSDateStamp: + case ExifTagValue.GPSDifferential: + return ExifParts.GpsTags; - case ExifTagValue.Unknown: - case ExifTagValue.SubIFDOffset: - case ExifTagValue.GPSIFDOffset: - default: - return ExifParts.None; - } + case ExifTagValue.Unknown: + case ExifTagValue.SubIFDOffset: + case ExifTagValue.GPSIFDOffset: + default: + return ExifParts.None; } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs index 1f9a1659d9..b025ce1ae5 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifUcs2StringHelpers.cs @@ -1,21 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal static class ExifUcs2StringHelpers { - internal static class ExifUcs2StringHelpers - { - public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2"); + public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2"); - public static bool IsUcs2Tag(ExifTagValue tag) => tag switch - { - ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true, - _ => false, - }; + public static bool IsUcs2Tag(ExifTagValue tag) => tag switch + { + ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true, + _ => false, + }; - public static int Write(string value, Span destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination); - } + public static int Write(string value, Span destination) => ExifEncodedStringHelpers.Write(Ucs2Encoding, value, destination); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 1212e16ccc..fc0a50dc74 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -1,451 +1,448 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.Collections.Generic; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Contains methods for writing EXIF metadata. +/// +internal sealed class ExifWriter { /// - /// Contains methods for writing EXIF metadata. + /// Which parts will be written. /// - internal sealed class ExifWriter - { - /// - /// Which parts will be written. - /// - private readonly ExifParts allowedParts; - private readonly IList values; - private List dataOffsets; - private readonly List ifdValues; - private readonly List exifValues; - private readonly List gpsValues; - - /// - /// Initializes a new instance of the class. - /// - /// The values. - /// The allowed parts. - public ExifWriter(IList values, ExifParts allowedParts) - { - this.values = values; - this.allowedParts = allowedParts; - this.ifdValues = this.GetPartValues(ExifParts.IfdTags); - this.exifValues = this.GetPartValues(ExifParts.ExifTags); - this.gpsValues = this.GetPartValues(ExifParts.GpsTags); - } - - /// - /// Returns the EXIF data. - /// - /// - /// The . - /// - public byte[] GetData() - { - const uint startIndex = 0; + private readonly ExifParts allowedParts; + private readonly IList values; + private List dataOffsets; + private readonly List ifdValues; + private readonly List exifValues; + private readonly List gpsValues; - IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); - IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); + /// + /// Initializes a new instance of the class. + /// + /// The values. + /// The allowed parts. + public ExifWriter(IList values, ExifParts allowedParts) + { + this.values = values; + this.allowedParts = allowedParts; + this.ifdValues = this.GetPartValues(ExifParts.IfdTags); + this.exifValues = this.GetPartValues(ExifParts.ExifTags); + this.gpsValues = this.GetPartValues(ExifParts.GpsTags); + } - uint ifdLength = GetLength(this.ifdValues); - uint exifLength = GetLength(this.exifValues); - uint gpsLength = GetLength(this.gpsValues); + /// + /// Returns the EXIF data. + /// + /// + /// The . + /// + public byte[] GetData() + { + const uint startIndex = 0; - uint length = ifdLength + exifLength + gpsLength; + IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); + IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); - if (length == 0) - { - return Array.Empty(); - } + uint ifdLength = GetLength(this.ifdValues); + uint exifLength = GetLength(this.exifValues); + uint gpsLength = GetLength(this.gpsValues); - // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total - length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; + uint length = ifdLength + exifLength + gpsLength; - // first IFD offset - length += 4; + if (length == 0) + { + return Array.Empty(); + } - byte[] result = new byte[length]; + // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total + length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; - int i = 0; + // first IFD offset + length += 4; - // The byte order marker for little-endian, followed by the number 42 and a 0 - ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); - i += ExifConstants.LittleEndianByteOrderMarker.Length; + byte[] result = new byte[length]; - uint ifdOffset = (uint)i - startIndex + 4U; + int i = 0; - exifOffset?.TrySetValue(ifdOffset + ifdLength); - gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); + // The byte order marker for little-endian, followed by the number 42 and a 0 + ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); + i += ExifConstants.LittleEndianByteOrderMarker.Length; - i = WriteUInt32(ifdOffset, result, i); - i = this.WriteHeaders(this.ifdValues, result, i); - i = this.WriteData(startIndex, this.ifdValues, result, i); + uint ifdOffset = (uint)i - startIndex + 4U; - if (exifLength > 0) - { - i = this.WriteHeaders(this.exifValues, result, i); - i = this.WriteData(startIndex, this.exifValues, result, i); - } + exifOffset?.TrySetValue(ifdOffset + ifdLength); + gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); - if (gpsLength > 0) - { - i = this.WriteHeaders(this.gpsValues, result, i); - i = this.WriteData(startIndex, this.gpsValues, result, i); - } + i = WriteUInt32(ifdOffset, result, i); + i = this.WriteHeaders(this.ifdValues, result, i); + i = this.WriteData(startIndex, this.ifdValues, result, i); - return result; + if (exifLength > 0) + { + i = this.WriteHeaders(this.exifValues, result, i); + i = this.WriteData(startIndex, this.exifValues, result, i); } - private static unsafe int WriteSingle(float value, Span destination, int offset) + if (gpsLength > 0) { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *(int*)&value); - - return offset + 4; + i = this.WriteHeaders(this.gpsValues, result, i); + i = this.WriteData(startIndex, this.gpsValues, result, i); } - private static unsafe int WriteDouble(double value, Span destination, int offset) - { - BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *(long*)&value); + return result; + } - return offset + 8; - } + private static unsafe int WriteSingle(float value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), *(int*)&value); - private static int Write(ReadOnlySpan source, Span destination, int offset) - { - source.CopyTo(destination.Slice(offset, source.Length)); + return offset + 4; + } - return offset + source.Length; - } + private static unsafe int WriteDouble(double value, Span destination, int offset) + { + BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), *(long*)&value); - private static int WriteInt16(short value, Span destination, int offset) - { - BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); + return offset + 8; + } - return offset + 2; - } + private static int Write(ReadOnlySpan source, Span destination, int offset) + { + source.CopyTo(destination.Slice(offset, source.Length)); - private static int WriteUInt16(ushort value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); + return offset + source.Length; + } - return offset + 2; - } + private static int WriteInt16(short value, Span destination, int offset) + { + BinaryPrimitives.WriteInt16LittleEndian(destination.Slice(offset, 2), value); - private static int WriteUInt32(uint value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); + return offset + 2; + } - return offset + 4; - } + private static int WriteUInt16(ushort value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(offset, 2), value); - private static int WriteInt64(long value, Span destination, int offset) - { - BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), value); + return offset + 2; + } - return offset + 8; - } + private static int WriteUInt32(uint value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(offset, 4), value); - private static int WriteUInt64(ulong value, Span destination, int offset) - { - BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(offset, 8), value); + return offset + 4; + } - return offset + 8; - } + private static int WriteInt64(long value, Span destination, int offset) + { + BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), value); - private static int WriteInt32(int value, Span destination, int offset) - { - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); + return offset + 8; + } - return offset + 4; - } + private static int WriteUInt64(ulong value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(offset, 8), value); - private static IExifValue GetOffsetValue(List ifdValues, List values, ExifTag offset) - { - int index = -1; + return offset + 8; + } - for (int i = 0; i < ifdValues.Count; i++) - { - if (ifdValues[i].Tag == offset) - { - index = i; - } - } + private static int WriteInt32(int value, Span destination, int offset) + { + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); - if (values.Count > 0) - { - if (index != -1) - { - return ifdValues[index]; - } + return offset + 4; + } - ExifValue result = ExifValues.Create(offset); - ifdValues.Add(result); + private static IExifValue GetOffsetValue(List ifdValues, List values, ExifTag offset) + { + int index = -1; - return result; - } - else if (index != -1) + for (int i = 0; i < ifdValues.Count; i++) + { + if (ifdValues[i].Tag == offset) { - ifdValues.RemoveAt(index); + index = i; } - - return null; } - private List GetPartValues(ExifParts part) + if (values.Count > 0) { - List result = new(); - - if (!EnumUtils.HasFlag(this.allowedParts, part)) + if (index != -1) { - return result; + return ifdValues[index]; } - foreach (IExifValue value in this.values) - { - if (!HasValue(value)) - { - continue; - } + ExifValue result = ExifValues.Create(offset); + ifdValues.Add(result); - if (ExifTags.GetPart(value.Tag) == part) - { - result.Add(value); - } - } + return result; + } + else if (index != -1) + { + ifdValues.RemoveAt(index); + } + + return null; + } + private List GetPartValues(ExifParts part) + { + List result = new(); + + if (!EnumUtils.HasFlag(this.allowedParts, part)) + { return result; } - private static bool HasValue(IExifValue exifValue) + foreach (IExifValue value in this.values) { - object value = exifValue.GetValue(); - if (value is null) + if (!HasValue(value)) { - return false; + continue; } - if (exifValue.DataType == ExifDataType.Ascii) + if (ExifTags.GetPart(value.Tag) == part) { - string stringValue = (string)value; - return stringValue.Length > 0; + result.Add(value); } + } - if (value is Array arrayValue) - { - return arrayValue.Length > 0; - } + return result; + } - return true; + private static bool HasValue(IExifValue exifValue) + { + object value = exifValue.GetValue(); + if (value is null) + { + return false; } - private static uint GetLength(IList values) + if (exifValue.DataType == ExifDataType.Ascii) { - if (values.Count == 0) - { - return 0; - } + string stringValue = (string)value; + return stringValue.Length > 0; + } - uint length = 2; + if (value is Array arrayValue) + { + return arrayValue.Length > 0; + } - foreach (IExifValue value in values) - { - uint valueLength = GetLength(value); + return true; + } - length += 12; + private static uint GetLength(IList values) + { + if (values.Count == 0) + { + return 0; + } - if (valueLength > 4) - { - length += valueLength; - } + uint length = 2; + + foreach (IExifValue value in values) + { + uint valueLength = GetLength(value); + + length += 12; + + if (valueLength > 4) + { + length += valueLength; } + } + + // next IFD offset + length += 4; + + return length; + } + + internal static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); - // next IFD offset - length += 4; + internal static uint GetNumberOfComponents(IExifValue exifValue) + { + object value = exifValue.GetValue(); - return length; + if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) + { + return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string)value); } - internal static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType); + if (value is EncodedString encodedString) + { + return ExifEncodedStringHelpers.GetDataLength(encodedString); + } - internal static uint GetNumberOfComponents(IExifValue exifValue) + if (exifValue.DataType == ExifDataType.Ascii) { - object value = exifValue.GetValue(); + return (uint)ExifConstants.DefaultEncoding.GetByteCount((string)value) + 1; + } - if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) - { - return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string)value); - } + if (value is Array arrayValue) + { + return (uint)arrayValue.Length; + } - if (value is EncodedString encodedString) - { - return ExifEncodedStringHelpers.GetDataLength(encodedString); - } + return 1; + } - if (exifValue.DataType == ExifDataType.Ascii) - { - return (uint)ExifConstants.DefaultEncoding.GetByteCount((string)value) + 1; - } + private static int WriteArray(IExifValue value, Span destination, int offset) + { + int newOffset = offset; + foreach (object obj in (Array)value.GetValue()) + { + newOffset = WriteValue(value.DataType, obj, destination, newOffset); + } - if (value is Array arrayValue) - { - return (uint)arrayValue.Length; - } + return newOffset; + } - return 1; + private int WriteData(uint startIndex, List values, Span destination, int offset) + { + if (this.dataOffsets.Count == 0) + { + return offset; } - private static int WriteArray(IExifValue value, Span destination, int offset) + int newOffset = offset; + + int i = 0; + foreach (IExifValue value in values) { - int newOffset = offset; - foreach (object obj in (Array)value.GetValue()) + if (GetLength(value) > 4) { - newOffset = WriteValue(value.DataType, obj, destination, newOffset); + WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); + newOffset = WriteValue(value, destination, newOffset); } + } + + return newOffset; + } + + private int WriteHeaders(List values, Span destination, int offset) + { + this.dataOffsets = new List(); + + int newOffset = WriteUInt16((ushort)values.Count, destination, offset); + if (values.Count == 0) + { return newOffset; } - private int WriteData(uint startIndex, List values, Span destination, int offset) + foreach (IExifValue value in values) { - if (this.dataOffsets.Count == 0) + newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); + newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); + newOffset = WriteUInt32(GetNumberOfComponents(value), destination, newOffset); + + uint length = GetLength(value); + if (length > 4) { - return offset; + this.dataOffsets.Add(newOffset); } - - int newOffset = offset; - - int i = 0; - foreach (IExifValue value in values) + else { - if (GetLength(value) > 4) - { - WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]); - newOffset = WriteValue(value, destination, newOffset); - } + WriteValue(value, destination, newOffset); } - return newOffset; + newOffset += 4; } - private int WriteHeaders(List values, Span destination, int offset) - { - this.dataOffsets = new List(); - - int newOffset = WriteUInt16((ushort)values.Count, destination, offset); + // next IFD offset + return WriteUInt32(0, destination, newOffset); + } - if (values.Count == 0) - { - return newOffset; - } + private static void WriteRational(Span destination, in Rational value) + { + BinaryPrimitives.WriteUInt32LittleEndian(destination[..4], value.Numerator); + BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } - foreach (IExifValue value in values) - { - newOffset = WriteUInt16((ushort)value.Tag, destination, newOffset); - newOffset = WriteUInt16((ushort)value.DataType, destination, newOffset); - newOffset = WriteUInt32(GetNumberOfComponents(value), destination, newOffset); + private static void WriteSignedRational(Span destination, in SignedRational value) + { + BinaryPrimitives.WriteInt32LittleEndian(destination[..4], value.Numerator); + BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + } - uint length = GetLength(value); - if (length > 4) + private static int WriteValue(ExifDataType dataType, object value, Span destination, int offset) + { + switch (dataType) + { + case ExifDataType.Ascii: + offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset); + destination[offset] = 0; + return offset + 1; + case ExifDataType.Byte: + case ExifDataType.Undefined: + destination[offset] = (byte)value; + return offset + 1; + case ExifDataType.DoubleFloat: + return WriteDouble((double)value, destination, offset); + case ExifDataType.Short: + if (value is Number shortNumber) { - this.dataOffsets.Add(newOffset); + return WriteUInt16((ushort)shortNumber, destination, offset); } - else + + return WriteUInt16((ushort)value, destination, offset); + case ExifDataType.Long: + if (value is Number longNumber) { - WriteValue(value, destination, newOffset); + return WriteUInt32((uint)longNumber, destination, offset); } - newOffset += 4; - } - - // next IFD offset - return WriteUInt32(0, destination, newOffset); + return WriteUInt32((uint)value, destination, offset); + case ExifDataType.Long8: + return WriteUInt64((ulong)value, destination, offset); + case ExifDataType.SignedLong8: + return WriteInt64((long)value, destination, offset); + case ExifDataType.Rational: + WriteRational(destination.Slice(offset, 8), (Rational)value); + return offset + 8; + case ExifDataType.SignedByte: + destination[offset] = unchecked((byte)(sbyte)value); + return offset + 1; + case ExifDataType.SignedLong: + return WriteInt32((int)value, destination, offset); + case ExifDataType.SignedShort: + return WriteInt16((short)value, destination, offset); + case ExifDataType.SignedRational: + WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); + return offset + 8; + case ExifDataType.SingleFloat: + return WriteSingle((float)value, destination, offset); + default: + throw new NotImplementedException(); } + } - private static void WriteRational(Span destination, in Rational value) - { - BinaryPrimitives.WriteUInt32LittleEndian(destination[..4], value.Numerator); - BinaryPrimitives.WriteUInt32LittleEndian(destination.Slice(4, 4), value.Denominator); - } + internal static int WriteValue(IExifValue exifValue, Span destination, int offset) + { + object value = exifValue.GetValue(); - private static void WriteSignedRational(Span destination, in SignedRational value) + if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) { - BinaryPrimitives.WriteInt32LittleEndian(destination[..4], value.Numerator); - BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator); + return offset + ExifUcs2StringHelpers.Write((string)value, destination[offset..]); } - - private static int WriteValue(ExifDataType dataType, object value, Span destination, int offset) + else if (value is EncodedString encodedString) { - switch (dataType) - { - case ExifDataType.Ascii: - offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset); - destination[offset] = 0; - return offset + 1; - case ExifDataType.Byte: - case ExifDataType.Undefined: - destination[offset] = (byte)value; - return offset + 1; - case ExifDataType.DoubleFloat: - return WriteDouble((double)value, destination, offset); - case ExifDataType.Short: - if (value is Number shortNumber) - { - return WriteUInt16((ushort)shortNumber, destination, offset); - } - - return WriteUInt16((ushort)value, destination, offset); - case ExifDataType.Long: - if (value is Number longNumber) - { - return WriteUInt32((uint)longNumber, destination, offset); - } - - return WriteUInt32((uint)value, destination, offset); - case ExifDataType.Long8: - return WriteUInt64((ulong)value, destination, offset); - case ExifDataType.SignedLong8: - return WriteInt64((long)value, destination, offset); - case ExifDataType.Rational: - WriteRational(destination.Slice(offset, 8), (Rational)value); - return offset + 8; - case ExifDataType.SignedByte: - destination[offset] = unchecked((byte)(sbyte)value); - return offset + 1; - case ExifDataType.SignedLong: - return WriteInt32((int)value, destination, offset); - case ExifDataType.SignedShort: - return WriteInt16((short)value, destination, offset); - case ExifDataType.SignedRational: - WriteSignedRational(destination.Slice(offset, 8), (SignedRational)value); - return offset + 8; - case ExifDataType.SingleFloat: - return WriteSingle((float)value, destination, offset); - default: - throw new NotImplementedException(); - } + return offset + ExifEncodedStringHelpers.Write(encodedString, destination[offset..]); } - internal static int WriteValue(IExifValue exifValue, Span destination, int offset) + if (exifValue.IsArray) { - object value = exifValue.GetValue(); - - if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag)) - { - return offset + ExifUcs2StringHelpers.Write((string)value, destination[offset..]); - } - else if (value is EncodedString encodedString) - { - return offset + ExifEncodedStringHelpers.Write(encodedString, destination[offset..]); - } - - if (exifValue.IsArray) - { - return WriteArray(exifValue, destination, offset); - } - - return WriteValue(exifValue.DataType, value, destination, offset); + return WriteArray(exifValue, destination, offset); } + + return WriteValue(exifValue.DataType, value, destination, offset); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs index fd3d5fa097..9ee2cf2f45 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Byte.cs @@ -1,24 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the FaxProfile exif tag. - /// - public static ExifTag FaxProfile { get; } = new ExifTag(ExifTagValue.FaxProfile); + /// + /// Gets the FaxProfile exif tag. + /// + public static ExifTag FaxProfile { get; } = new ExifTag(ExifTagValue.FaxProfile); - /// - /// Gets the ModeNumber exif tag. - /// - public static ExifTag ModeNumber { get; } = new ExifTag(ExifTagValue.ModeNumber); + /// + /// Gets the ModeNumber exif tag. + /// + public static ExifTag ModeNumber { get; } = new ExifTag(ExifTagValue.ModeNumber); - /// - /// Gets the GPSAltitudeRef exif tag. - /// - public static ExifTag GPSAltitudeRef { get; } = new ExifTag(ExifTagValue.GPSAltitudeRef); - } + /// + /// Gets the GPSAltitudeRef exif tag. + /// + public static ExifTag GPSAltitudeRef { get; } = new ExifTag(ExifTagValue.GPSAltitudeRef); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs index 4b2b9cd5f4..00a9056d34 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs @@ -1,49 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the ClipPath exif tag. - /// - public static ExifTag ClipPath => new ExifTag(ExifTagValue.ClipPath); - - /// - /// Gets the VersionYear exif tag. - /// - public static ExifTag VersionYear => new ExifTag(ExifTagValue.VersionYear); - - /// - /// Gets the XMP exif tag. - /// - public static ExifTag XMP => new ExifTag(ExifTagValue.XMP); - - /// - /// Gets the IPTC exif tag. - /// - public static ExifTag IPTC => new ExifTag(ExifTagValue.IPTC); - - /// - /// Gets the IccProfile exif tag. - /// - public static ExifTag IccProfile => new ExifTag(ExifTagValue.IccProfile); - - /// - /// Gets the CFAPattern2 exif tag. - /// - public static ExifTag CFAPattern2 => new ExifTag(ExifTagValue.CFAPattern2); - - /// - /// Gets the TIFFEPStandardID exif tag. - /// - public static ExifTag TIFFEPStandardID => new ExifTag(ExifTagValue.TIFFEPStandardID); - - /// - /// Gets the GPSVersionID exif tag. - /// - public static ExifTag GPSVersionID => new ExifTag(ExifTagValue.GPSVersionID); - } + /// + /// Gets the ClipPath exif tag. + /// + public static ExifTag ClipPath => new ExifTag(ExifTagValue.ClipPath); + + /// + /// Gets the VersionYear exif tag. + /// + public static ExifTag VersionYear => new ExifTag(ExifTagValue.VersionYear); + + /// + /// Gets the XMP exif tag. + /// + public static ExifTag XMP => new ExifTag(ExifTagValue.XMP); + + /// + /// Gets the IPTC exif tag. + /// + public static ExifTag IPTC => new ExifTag(ExifTagValue.IPTC); + + /// + /// Gets the IccProfile exif tag. + /// + public static ExifTag IccProfile => new ExifTag(ExifTagValue.IccProfile); + + /// + /// Gets the CFAPattern2 exif tag. + /// + public static ExifTag CFAPattern2 => new ExifTag(ExifTagValue.CFAPattern2); + + /// + /// Gets the TIFFEPStandardID exif tag. + /// + public static ExifTag TIFFEPStandardID => new ExifTag(ExifTagValue.TIFFEPStandardID); + + /// + /// Gets the GPSVersionID exif tag. + /// + public static ExifTag GPSVersionID => new ExifTag(ExifTagValue.GPSVersionID); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs index 9fd8103ce8..91d0c97b38 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.DoubleArray.cs @@ -1,29 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the PixelScale exif tag. - /// - public static ExifTag PixelScale { get; } = new ExifTag(ExifTagValue.PixelScale); + /// + /// Gets the PixelScale exif tag. + /// + public static ExifTag PixelScale { get; } = new ExifTag(ExifTagValue.PixelScale); - /// - /// Gets the IntergraphMatrix exif tag. - /// - public static ExifTag IntergraphMatrix { get; } = new ExifTag(ExifTagValue.IntergraphMatrix); + /// + /// Gets the IntergraphMatrix exif tag. + /// + public static ExifTag IntergraphMatrix { get; } = new ExifTag(ExifTagValue.IntergraphMatrix); - /// - /// Gets the ModelTiePoint exif tag. - /// - public static ExifTag ModelTiePoint { get; } = new ExifTag(ExifTagValue.ModelTiePoint); + /// + /// Gets the ModelTiePoint exif tag. + /// + public static ExifTag ModelTiePoint { get; } = new ExifTag(ExifTagValue.ModelTiePoint); - /// - /// Gets the ModelTransform exif tag. - /// - public static ExifTag ModelTransform { get; } = new ExifTag(ExifTagValue.ModelTransform); - } + /// + /// Gets the ModelTransform exif tag. + /// + public static ExifTag ModelTransform { get; } = new ExifTag(ExifTagValue.ModelTransform); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs index 104b299e3d..4b53ba6360 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.EncodedString.cs @@ -1,24 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the UserComment exif tag. - /// - public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); + /// + /// Gets the UserComment exif tag. + /// + public static ExifTag UserComment { get; } = new ExifTag(ExifTagValue.UserComment); - /// - /// Gets the GPSProcessingMethod exif tag. - /// - public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); + /// + /// Gets the GPSProcessingMethod exif tag. + /// + public static ExifTag GPSProcessingMethod { get; } = new ExifTag(ExifTagValue.GPSProcessingMethod); - /// - /// Gets the GPSAreaInformation exif tag. - /// - public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); - } + /// + /// Gets the GPSAreaInformation exif tag. + /// + public static ExifTag GPSAreaInformation { get; } = new ExifTag(ExifTagValue.GPSAreaInformation); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs index d27bf20683..f6c7c8ea73 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs @@ -1,114 +1,113 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the SubfileType exif tag. - /// - public static ExifTag SubfileType { get; } = new ExifTag(ExifTagValue.SubfileType); - - /// - /// Gets the SubIFDOffset exif tag. - /// - public static ExifTag SubIFDOffset { get; } = new ExifTag(ExifTagValue.SubIFDOffset); - - /// - /// Gets the GPSIFDOffset exif tag. - /// - public static ExifTag GPSIFDOffset { get; } = new ExifTag(ExifTagValue.GPSIFDOffset); - - /// - /// Gets the T4Options exif tag. - /// - public static ExifTag T4Options { get; } = new ExifTag(ExifTagValue.T4Options); - - /// - /// Gets the T6Options exif tag. - /// - public static ExifTag T6Options { get; } = new ExifTag(ExifTagValue.T6Options); - - /// - /// Gets the XClipPathUnits exif tag. - /// - public static ExifTag XClipPathUnits { get; } = new ExifTag(ExifTagValue.XClipPathUnits); - - /// - /// Gets the YClipPathUnits exif tag. - /// - public static ExifTag YClipPathUnits { get; } = new ExifTag(ExifTagValue.YClipPathUnits); - - /// - /// Gets the ProfileType exif tag. - /// - public static ExifTag ProfileType { get; } = new ExifTag(ExifTagValue.ProfileType); - - /// - /// Gets the CodingMethods exif tag. - /// - public static ExifTag CodingMethods { get; } = new ExifTag(ExifTagValue.CodingMethods); - - /// - /// Gets the T82ptions exif tag. - /// - public static ExifTag T82ptions { get; } = new ExifTag(ExifTagValue.T82ptions); - - /// - /// Gets the JPEGInterchangeFormat exif tag. - /// - public static ExifTag JPEGInterchangeFormat { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormat); - - /// - /// Gets the JPEGInterchangeFormatLength exif tag. - /// - public static ExifTag JPEGInterchangeFormatLength { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormatLength); - - /// - /// Gets the MDFileTag exif tag. - /// - public static ExifTag MDFileTag { get; } = new ExifTag(ExifTagValue.MDFileTag); - - /// - /// Gets the StandardOutputSensitivity exif tag. - /// - public static ExifTag StandardOutputSensitivity { get; } = new ExifTag(ExifTagValue.StandardOutputSensitivity); - - /// - /// Gets the RecommendedExposureIndex exif tag. - /// - public static ExifTag RecommendedExposureIndex { get; } = new ExifTag(ExifTagValue.RecommendedExposureIndex); - - /// - /// Gets the ISOSpeed exif tag. - /// - public static ExifTag ISOSpeed { get; } = new ExifTag(ExifTagValue.ISOSpeed); - - /// - /// Gets the ISOSpeedLatitudeyyy exif tag. - /// - public static ExifTag ISOSpeedLatitudeyyy { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudeyyy); - - /// - /// Gets the ISOSpeedLatitudezzz exif tag. - /// - public static ExifTag ISOSpeedLatitudezzz { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudezzz); - - /// - /// Gets the FaxRecvParams exif tag. - /// - public static ExifTag FaxRecvParams { get; } = new ExifTag(ExifTagValue.FaxRecvParams); - - /// - /// Gets the FaxRecvTime exif tag. - /// - public static ExifTag FaxRecvTime { get; } = new ExifTag(ExifTagValue.FaxRecvTime); - - /// - /// Gets the ImageNumber exif tag. - /// - public static ExifTag ImageNumber { get; } = new ExifTag(ExifTagValue.ImageNumber); - } + /// + /// Gets the SubfileType exif tag. + /// + public static ExifTag SubfileType { get; } = new ExifTag(ExifTagValue.SubfileType); + + /// + /// Gets the SubIFDOffset exif tag. + /// + public static ExifTag SubIFDOffset { get; } = new ExifTag(ExifTagValue.SubIFDOffset); + + /// + /// Gets the GPSIFDOffset exif tag. + /// + public static ExifTag GPSIFDOffset { get; } = new ExifTag(ExifTagValue.GPSIFDOffset); + + /// + /// Gets the T4Options exif tag. + /// + public static ExifTag T4Options { get; } = new ExifTag(ExifTagValue.T4Options); + + /// + /// Gets the T6Options exif tag. + /// + public static ExifTag T6Options { get; } = new ExifTag(ExifTagValue.T6Options); + + /// + /// Gets the XClipPathUnits exif tag. + /// + public static ExifTag XClipPathUnits { get; } = new ExifTag(ExifTagValue.XClipPathUnits); + + /// + /// Gets the YClipPathUnits exif tag. + /// + public static ExifTag YClipPathUnits { get; } = new ExifTag(ExifTagValue.YClipPathUnits); + + /// + /// Gets the ProfileType exif tag. + /// + public static ExifTag ProfileType { get; } = new ExifTag(ExifTagValue.ProfileType); + + /// + /// Gets the CodingMethods exif tag. + /// + public static ExifTag CodingMethods { get; } = new ExifTag(ExifTagValue.CodingMethods); + + /// + /// Gets the T82ptions exif tag. + /// + public static ExifTag T82ptions { get; } = new ExifTag(ExifTagValue.T82ptions); + + /// + /// Gets the JPEGInterchangeFormat exif tag. + /// + public static ExifTag JPEGInterchangeFormat { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormat); + + /// + /// Gets the JPEGInterchangeFormatLength exif tag. + /// + public static ExifTag JPEGInterchangeFormatLength { get; } = new ExifTag(ExifTagValue.JPEGInterchangeFormatLength); + + /// + /// Gets the MDFileTag exif tag. + /// + public static ExifTag MDFileTag { get; } = new ExifTag(ExifTagValue.MDFileTag); + + /// + /// Gets the StandardOutputSensitivity exif tag. + /// + public static ExifTag StandardOutputSensitivity { get; } = new ExifTag(ExifTagValue.StandardOutputSensitivity); + + /// + /// Gets the RecommendedExposureIndex exif tag. + /// + public static ExifTag RecommendedExposureIndex { get; } = new ExifTag(ExifTagValue.RecommendedExposureIndex); + + /// + /// Gets the ISOSpeed exif tag. + /// + public static ExifTag ISOSpeed { get; } = new ExifTag(ExifTagValue.ISOSpeed); + + /// + /// Gets the ISOSpeedLatitudeyyy exif tag. + /// + public static ExifTag ISOSpeedLatitudeyyy { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudeyyy); + + /// + /// Gets the ISOSpeedLatitudezzz exif tag. + /// + public static ExifTag ISOSpeedLatitudezzz { get; } = new ExifTag(ExifTagValue.ISOSpeedLatitudezzz); + + /// + /// Gets the FaxRecvParams exif tag. + /// + public static ExifTag FaxRecvParams { get; } = new ExifTag(ExifTagValue.FaxRecvParams); + + /// + /// Gets the FaxRecvTime exif tag. + /// + public static ExifTag FaxRecvTime { get; } = new ExifTag(ExifTagValue.FaxRecvTime); + + /// + /// Gets the ImageNumber exif tag. + /// + public static ExifTag ImageNumber { get; } = new ExifTag(ExifTagValue.ImageNumber); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs index b26d75eb8b..29de869435 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -1,74 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the FreeOffsets exif tag. - /// - public static ExifTag FreeOffsets { get; } = new ExifTag(ExifTagValue.FreeOffsets); + /// + /// Gets the FreeOffsets exif tag. + /// + public static ExifTag FreeOffsets { get; } = new ExifTag(ExifTagValue.FreeOffsets); - /// - /// Gets the FreeByteCounts exif tag. - /// - public static ExifTag FreeByteCounts { get; } = new ExifTag(ExifTagValue.FreeByteCounts); + /// + /// Gets the FreeByteCounts exif tag. + /// + public static ExifTag FreeByteCounts { get; } = new ExifTag(ExifTagValue.FreeByteCounts); - /// - /// Gets the ColorResponseUnit exif tag. - /// - public static ExifTag ColorResponseUnit { get; } = new ExifTag(ExifTagValue.ColorResponseUnit); + /// + /// Gets the ColorResponseUnit exif tag. + /// + public static ExifTag ColorResponseUnit { get; } = new ExifTag(ExifTagValue.ColorResponseUnit); - /// - /// Gets the TileOffsets exif tag. - /// - public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); + /// + /// Gets the TileOffsets exif tag. + /// + public static ExifTag TileOffsets { get; } = new ExifTag(ExifTagValue.TileOffsets); - /// - /// Gets the SMinSampleValue exif tag. - /// - public static ExifTag SMinSampleValue { get; } = new ExifTag(ExifTagValue.SMinSampleValue); + /// + /// Gets the SMinSampleValue exif tag. + /// + public static ExifTag SMinSampleValue { get; } = new ExifTag(ExifTagValue.SMinSampleValue); - /// - /// Gets the SMaxSampleValue exif tag. - /// - public static ExifTag SMaxSampleValue { get; } = new ExifTag(ExifTagValue.SMaxSampleValue); + /// + /// Gets the SMaxSampleValue exif tag. + /// + public static ExifTag SMaxSampleValue { get; } = new ExifTag(ExifTagValue.SMaxSampleValue); - /// - /// Gets the JPEGQTables exif tag. - /// - public static ExifTag JPEGQTables { get; } = new ExifTag(ExifTagValue.JPEGQTables); + /// + /// Gets the JPEGQTables exif tag. + /// + public static ExifTag JPEGQTables { get; } = new ExifTag(ExifTagValue.JPEGQTables); - /// - /// Gets the JPEGDCTables exif tag. - /// - public static ExifTag JPEGDCTables { get; } = new ExifTag(ExifTagValue.JPEGDCTables); + /// + /// Gets the JPEGDCTables exif tag. + /// + public static ExifTag JPEGDCTables { get; } = new ExifTag(ExifTagValue.JPEGDCTables); - /// - /// Gets the JPEGACTables exif tag. - /// - public static ExifTag JPEGACTables { get; } = new ExifTag(ExifTagValue.JPEGACTables); + /// + /// Gets the JPEGACTables exif tag. + /// + public static ExifTag JPEGACTables { get; } = new ExifTag(ExifTagValue.JPEGACTables); - /// - /// Gets the StripRowCounts exif tag. - /// - public static ExifTag StripRowCounts { get; } = new ExifTag(ExifTagValue.StripRowCounts); + /// + /// Gets the StripRowCounts exif tag. + /// + public static ExifTag StripRowCounts { get; } = new ExifTag(ExifTagValue.StripRowCounts); - /// - /// Gets the IntergraphRegisters exif tag. - /// - public static ExifTag IntergraphRegisters { get; } = new ExifTag(ExifTagValue.IntergraphRegisters); + /// + /// Gets the IntergraphRegisters exif tag. + /// + public static ExifTag IntergraphRegisters { get; } = new ExifTag(ExifTagValue.IntergraphRegisters); - /// - /// Gets the TimeZoneOffset exif tag. - /// - public static ExifTag TimeZoneOffset { get; } = new ExifTag(ExifTagValue.TimeZoneOffset); + /// + /// Gets the TimeZoneOffset exif tag. + /// + public static ExifTag TimeZoneOffset { get; } = new ExifTag(ExifTagValue.TimeZoneOffset); - /// - /// Gets the offset to child IFDs exif tag. - /// - public static ExifTag SubIFDs { get; } = new ExifTag(ExifTagValue.SubIFDs); - } + /// + /// Gets the offset to child IFDs exif tag. + /// + public static ExifTag SubIFDs { get; } = new ExifTag(ExifTagValue.SubIFDs); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs index e01478b3c4..2018e4cea0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Number.cs @@ -1,54 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the ImageWidth exif tag. - /// - public static ExifTag ImageWidth { get; } = new ExifTag(ExifTagValue.ImageWidth); - - /// - /// Gets the ImageLength exif tag. - /// - public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); - - /// - /// Gets the RowsPerStrip exif tag. - /// - public static ExifTag RowsPerStrip { get; } = new ExifTag(ExifTagValue.RowsPerStrip); - - /// - /// Gets the TileWidth exif tag. - /// - public static ExifTag TileWidth { get; } = new ExifTag(ExifTagValue.TileWidth); - - /// - /// Gets the TileLength exif tag. - /// - public static ExifTag TileLength { get; } = new ExifTag(ExifTagValue.TileLength); - - /// - /// Gets the BadFaxLines exif tag. - /// - public static ExifTag BadFaxLines { get; } = new ExifTag(ExifTagValue.BadFaxLines); - - /// - /// Gets the ConsecutiveBadFaxLines exif tag. - /// - public static ExifTag ConsecutiveBadFaxLines { get; } = new ExifTag(ExifTagValue.ConsecutiveBadFaxLines); - - /// - /// Gets the PixelXDimension exif tag. - /// - public static ExifTag PixelXDimension { get; } = new ExifTag(ExifTagValue.PixelXDimension); - - /// - /// Gets the PixelYDimension exif tag. - /// - public static ExifTag PixelYDimension { get; } = new ExifTag(ExifTagValue.PixelYDimension); - } + /// + /// Gets the ImageWidth exif tag. + /// + public static ExifTag ImageWidth { get; } = new ExifTag(ExifTagValue.ImageWidth); + + /// + /// Gets the ImageLength exif tag. + /// + public static ExifTag ImageLength { get; } = new ExifTag(ExifTagValue.ImageLength); + + /// + /// Gets the RowsPerStrip exif tag. + /// + public static ExifTag RowsPerStrip { get; } = new ExifTag(ExifTagValue.RowsPerStrip); + + /// + /// Gets the TileWidth exif tag. + /// + public static ExifTag TileWidth { get; } = new ExifTag(ExifTagValue.TileWidth); + + /// + /// Gets the TileLength exif tag. + /// + public static ExifTag TileLength { get; } = new ExifTag(ExifTagValue.TileLength); + + /// + /// Gets the BadFaxLines exif tag. + /// + public static ExifTag BadFaxLines { get; } = new ExifTag(ExifTagValue.BadFaxLines); + + /// + /// Gets the ConsecutiveBadFaxLines exif tag. + /// + public static ExifTag ConsecutiveBadFaxLines { get; } = new ExifTag(ExifTagValue.ConsecutiveBadFaxLines); + + /// + /// Gets the PixelXDimension exif tag. + /// + public static ExifTag PixelXDimension { get; } = new ExifTag(ExifTagValue.PixelXDimension); + + /// + /// Gets the PixelYDimension exif tag. + /// + public static ExifTag PixelYDimension { get; } = new ExifTag(ExifTagValue.PixelYDimension); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs index 5d364e9004..ecd4d87d5a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.NumberArray.cs @@ -1,29 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the StripOffsets exif tag. - /// - public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); + /// + /// Gets the StripOffsets exif tag. + /// + public static ExifTag StripOffsets { get; } = new ExifTag(ExifTagValue.StripOffsets); - /// - /// Gets the StripByteCounts exif tag. - /// - public static ExifTag StripByteCounts { get; } = new ExifTag(ExifTagValue.StripByteCounts); + /// + /// Gets the StripByteCounts exif tag. + /// + public static ExifTag StripByteCounts { get; } = new ExifTag(ExifTagValue.StripByteCounts); - /// - /// Gets the TileByteCounts exif tag. - /// - public static ExifTag TileByteCounts { get; } = new ExifTag(ExifTagValue.TileByteCounts); + /// + /// Gets the TileByteCounts exif tag. + /// + public static ExifTag TileByteCounts { get; } = new ExifTag(ExifTagValue.TileByteCounts); - /// - /// Gets the ImageLayer exif tag. - /// - public static ExifTag ImageLayer { get; } = new ExifTag(ExifTagValue.ImageLayer); - } + /// + /// Gets the ImageLayer exif tag. + /// + public static ExifTag ImageLayer { get; } = new ExifTag(ExifTagValue.ImageLayer); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs index 16b81a9d47..96603b182a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Rational.cs @@ -1,169 +1,168 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the XPosition exif tag. - /// - public static ExifTag XPosition { get; } = new ExifTag(ExifTagValue.XPosition); - - /// - /// Gets the YPosition exif tag. - /// - public static ExifTag YPosition { get; } = new ExifTag(ExifTagValue.YPosition); - - /// - /// Gets the XResolution exif tag. - /// - public static ExifTag XResolution { get; } = new ExifTag(ExifTagValue.XResolution); - - /// - /// Gets the YResolution exif tag. - /// - public static ExifTag YResolution { get; } = new ExifTag(ExifTagValue.YResolution); - - /// - /// Gets the BatteryLevel exif tag. - /// - public static ExifTag BatteryLevel { get; } = new ExifTag(ExifTagValue.BatteryLevel); - - /// - /// Gets the ExposureTime exif tag. - /// - public static ExifTag ExposureTime { get; } = new ExifTag(ExifTagValue.ExposureTime); - - /// - /// Gets the FNumber exif tag. - /// - public static ExifTag FNumber { get; } = new ExifTag(ExifTagValue.FNumber); - - /// - /// Gets the MDScalePixel exif tag. - /// - public static ExifTag MDScalePixel { get; } = new ExifTag(ExifTagValue.MDScalePixel); - - /// - /// Gets the CompressedBitsPerPixel exif tag. - /// - public static ExifTag CompressedBitsPerPixel { get; } = new ExifTag(ExifTagValue.CompressedBitsPerPixel); - - /// - /// Gets the ApertureValue exif tag. - /// - public static ExifTag ApertureValue { get; } = new ExifTag(ExifTagValue.ApertureValue); - - /// - /// Gets the MaxApertureValue exif tag. - /// - public static ExifTag MaxApertureValue { get; } = new ExifTag(ExifTagValue.MaxApertureValue); - - /// - /// Gets the SubjectDistance exif tag. - /// - public static ExifTag SubjectDistance { get; } = new ExifTag(ExifTagValue.SubjectDistance); - - /// - /// Gets the FocalLength exif tag. - /// - public static ExifTag FocalLength { get; } = new ExifTag(ExifTagValue.FocalLength); - - /// - /// Gets the FlashEnergy2 exif tag. - /// - public static ExifTag FlashEnergy2 { get; } = new ExifTag(ExifTagValue.FlashEnergy2); - - /// - /// Gets the FocalPlaneXResolution2 exif tag. - /// - public static ExifTag FocalPlaneXResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution2); - - /// - /// Gets the FocalPlaneYResolution2 exif tag. - /// - public static ExifTag FocalPlaneYResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution2); - - /// - /// Gets the ExposureIndex2 exif tag. - /// - public static ExifTag ExposureIndex2 { get; } = new ExifTag(ExifTagValue.ExposureIndex2); - - /// - /// Gets the Humidity exif tag. - /// - public static ExifTag Humidity { get; } = new ExifTag(ExifTagValue.Humidity); - - /// - /// Gets the Pressure exif tag. - /// - public static ExifTag Pressure { get; } = new ExifTag(ExifTagValue.Pressure); - - /// - /// Gets the Acceleration exif tag. - /// - public static ExifTag Acceleration { get; } = new ExifTag(ExifTagValue.Acceleration); - - /// - /// Gets the FlashEnergy exif tag. - /// - public static ExifTag FlashEnergy { get; } = new ExifTag(ExifTagValue.FlashEnergy); - - /// - /// Gets the FocalPlaneXResolution exif tag. - /// - public static ExifTag FocalPlaneXResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution); - - /// - /// Gets the FocalPlaneYResolution exif tag. - /// - public static ExifTag FocalPlaneYResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution); - - /// - /// Gets the ExposureIndex exif tag. - /// - public static ExifTag ExposureIndex { get; } = new ExifTag(ExifTagValue.ExposureIndex); - - /// - /// Gets the DigitalZoomRatio exif tag. - /// - public static ExifTag DigitalZoomRatio { get; } = new ExifTag(ExifTagValue.DigitalZoomRatio); - - /// - /// Gets the GPSAltitude exif tag. - /// - public static ExifTag GPSAltitude { get; } = new ExifTag(ExifTagValue.GPSAltitude); - - /// - /// Gets the GPSDOP exif tag. - /// - public static ExifTag GPSDOP { get; } = new ExifTag(ExifTagValue.GPSDOP); - - /// - /// Gets the GPSSpeed exif tag. - /// - public static ExifTag GPSSpeed { get; } = new ExifTag(ExifTagValue.GPSSpeed); - - /// - /// Gets the GPSTrack exif tag. - /// - public static ExifTag GPSTrack { get; } = new ExifTag(ExifTagValue.GPSTrack); - - /// - /// Gets the GPSImgDirection exif tag. - /// - public static ExifTag GPSImgDirection { get; } = new ExifTag(ExifTagValue.GPSImgDirection); - - /// - /// Gets the GPSDestBearing exif tag. - /// - public static ExifTag GPSDestBearing { get; } = new ExifTag(ExifTagValue.GPSDestBearing); - - /// - /// Gets the GPSDestDistance exif tag. - /// - public static ExifTag GPSDestDistance { get; } = new ExifTag(ExifTagValue.GPSDestDistance); - } + /// + /// Gets the XPosition exif tag. + /// + public static ExifTag XPosition { get; } = new ExifTag(ExifTagValue.XPosition); + + /// + /// Gets the YPosition exif tag. + /// + public static ExifTag YPosition { get; } = new ExifTag(ExifTagValue.YPosition); + + /// + /// Gets the XResolution exif tag. + /// + public static ExifTag XResolution { get; } = new ExifTag(ExifTagValue.XResolution); + + /// + /// Gets the YResolution exif tag. + /// + public static ExifTag YResolution { get; } = new ExifTag(ExifTagValue.YResolution); + + /// + /// Gets the BatteryLevel exif tag. + /// + public static ExifTag BatteryLevel { get; } = new ExifTag(ExifTagValue.BatteryLevel); + + /// + /// Gets the ExposureTime exif tag. + /// + public static ExifTag ExposureTime { get; } = new ExifTag(ExifTagValue.ExposureTime); + + /// + /// Gets the FNumber exif tag. + /// + public static ExifTag FNumber { get; } = new ExifTag(ExifTagValue.FNumber); + + /// + /// Gets the MDScalePixel exif tag. + /// + public static ExifTag MDScalePixel { get; } = new ExifTag(ExifTagValue.MDScalePixel); + + /// + /// Gets the CompressedBitsPerPixel exif tag. + /// + public static ExifTag CompressedBitsPerPixel { get; } = new ExifTag(ExifTagValue.CompressedBitsPerPixel); + + /// + /// Gets the ApertureValue exif tag. + /// + public static ExifTag ApertureValue { get; } = new ExifTag(ExifTagValue.ApertureValue); + + /// + /// Gets the MaxApertureValue exif tag. + /// + public static ExifTag MaxApertureValue { get; } = new ExifTag(ExifTagValue.MaxApertureValue); + + /// + /// Gets the SubjectDistance exif tag. + /// + public static ExifTag SubjectDistance { get; } = new ExifTag(ExifTagValue.SubjectDistance); + + /// + /// Gets the FocalLength exif tag. + /// + public static ExifTag FocalLength { get; } = new ExifTag(ExifTagValue.FocalLength); + + /// + /// Gets the FlashEnergy2 exif tag. + /// + public static ExifTag FlashEnergy2 { get; } = new ExifTag(ExifTagValue.FlashEnergy2); + + /// + /// Gets the FocalPlaneXResolution2 exif tag. + /// + public static ExifTag FocalPlaneXResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution2); + + /// + /// Gets the FocalPlaneYResolution2 exif tag. + /// + public static ExifTag FocalPlaneYResolution2 { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution2); + + /// + /// Gets the ExposureIndex2 exif tag. + /// + public static ExifTag ExposureIndex2 { get; } = new ExifTag(ExifTagValue.ExposureIndex2); + + /// + /// Gets the Humidity exif tag. + /// + public static ExifTag Humidity { get; } = new ExifTag(ExifTagValue.Humidity); + + /// + /// Gets the Pressure exif tag. + /// + public static ExifTag Pressure { get; } = new ExifTag(ExifTagValue.Pressure); + + /// + /// Gets the Acceleration exif tag. + /// + public static ExifTag Acceleration { get; } = new ExifTag(ExifTagValue.Acceleration); + + /// + /// Gets the FlashEnergy exif tag. + /// + public static ExifTag FlashEnergy { get; } = new ExifTag(ExifTagValue.FlashEnergy); + + /// + /// Gets the FocalPlaneXResolution exif tag. + /// + public static ExifTag FocalPlaneXResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneXResolution); + + /// + /// Gets the FocalPlaneYResolution exif tag. + /// + public static ExifTag FocalPlaneYResolution { get; } = new ExifTag(ExifTagValue.FocalPlaneYResolution); + + /// + /// Gets the ExposureIndex exif tag. + /// + public static ExifTag ExposureIndex { get; } = new ExifTag(ExifTagValue.ExposureIndex); + + /// + /// Gets the DigitalZoomRatio exif tag. + /// + public static ExifTag DigitalZoomRatio { get; } = new ExifTag(ExifTagValue.DigitalZoomRatio); + + /// + /// Gets the GPSAltitude exif tag. + /// + public static ExifTag GPSAltitude { get; } = new ExifTag(ExifTagValue.GPSAltitude); + + /// + /// Gets the GPSDOP exif tag. + /// + public static ExifTag GPSDOP { get; } = new ExifTag(ExifTagValue.GPSDOP); + + /// + /// Gets the GPSSpeed exif tag. + /// + public static ExifTag GPSSpeed { get; } = new ExifTag(ExifTagValue.GPSSpeed); + + /// + /// Gets the GPSTrack exif tag. + /// + public static ExifTag GPSTrack { get; } = new ExifTag(ExifTagValue.GPSTrack); + + /// + /// Gets the GPSImgDirection exif tag. + /// + public static ExifTag GPSImgDirection { get; } = new ExifTag(ExifTagValue.GPSImgDirection); + + /// + /// Gets the GPSDestBearing exif tag. + /// + public static ExifTag GPSDestBearing { get; } = new ExifTag(ExifTagValue.GPSDestBearing); + + /// + /// Gets the GPSDestDistance exif tag. + /// + public static ExifTag GPSDestDistance { get; } = new ExifTag(ExifTagValue.GPSDestDistance); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs index c760901a2f..8b764c3f77 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.RationalArray.cs @@ -1,59 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the WhitePoint exif tag. - /// - public static ExifTag WhitePoint { get; } = new ExifTag(ExifTagValue.WhitePoint); - - /// - /// Gets the PrimaryChromaticities exif tag. - /// - public static ExifTag PrimaryChromaticities { get; } = new ExifTag(ExifTagValue.PrimaryChromaticities); - - /// - /// Gets the YCbCrCoefficients exif tag. - /// - public static ExifTag YCbCrCoefficients { get; } = new ExifTag(ExifTagValue.YCbCrCoefficients); - - /// - /// Gets the ReferenceBlackWhite exif tag. - /// - public static ExifTag ReferenceBlackWhite { get; } = new ExifTag(ExifTagValue.ReferenceBlackWhite); - - /// - /// Gets the GPSLatitude exif tag. - /// - public static ExifTag GPSLatitude { get; } = new ExifTag(ExifTagValue.GPSLatitude); - - /// - /// Gets the GPSLongitude exif tag. - /// - public static ExifTag GPSLongitude { get; } = new ExifTag(ExifTagValue.GPSLongitude); - - /// - /// Gets the GPSTimestamp exif tag. - /// - public static ExifTag GPSTimestamp { get; } = new ExifTag(ExifTagValue.GPSTimestamp); - - /// - /// Gets the GPSDestLatitude exif tag. - /// - public static ExifTag GPSDestLatitude { get; } = new ExifTag(ExifTagValue.GPSDestLatitude); - - /// - /// Gets the GPSDestLongitude exif tag. - /// - public static ExifTag GPSDestLongitude { get; } = new ExifTag(ExifTagValue.GPSDestLongitude); - - /// - /// Gets the LensSpecification exif tag. - /// - public static ExifTag LensSpecification { get; } = new ExifTag(ExifTagValue.LensSpecification); - } + /// + /// Gets the WhitePoint exif tag. + /// + public static ExifTag WhitePoint { get; } = new ExifTag(ExifTagValue.WhitePoint); + + /// + /// Gets the PrimaryChromaticities exif tag. + /// + public static ExifTag PrimaryChromaticities { get; } = new ExifTag(ExifTagValue.PrimaryChromaticities); + + /// + /// Gets the YCbCrCoefficients exif tag. + /// + public static ExifTag YCbCrCoefficients { get; } = new ExifTag(ExifTagValue.YCbCrCoefficients); + + /// + /// Gets the ReferenceBlackWhite exif tag. + /// + public static ExifTag ReferenceBlackWhite { get; } = new ExifTag(ExifTagValue.ReferenceBlackWhite); + + /// + /// Gets the GPSLatitude exif tag. + /// + public static ExifTag GPSLatitude { get; } = new ExifTag(ExifTagValue.GPSLatitude); + + /// + /// Gets the GPSLongitude exif tag. + /// + public static ExifTag GPSLongitude { get; } = new ExifTag(ExifTagValue.GPSLongitude); + + /// + /// Gets the GPSTimestamp exif tag. + /// + public static ExifTag GPSTimestamp { get; } = new ExifTag(ExifTagValue.GPSTimestamp); + + /// + /// Gets the GPSDestLatitude exif tag. + /// + public static ExifTag GPSDestLatitude { get; } = new ExifTag(ExifTagValue.GPSDestLatitude); + + /// + /// Gets the GPSDestLongitude exif tag. + /// + public static ExifTag GPSDestLongitude { get; } = new ExifTag(ExifTagValue.GPSDestLongitude); + + /// + /// Gets the LensSpecification exif tag. + /// + public static ExifTag LensSpecification { get; } = new ExifTag(ExifTagValue.LensSpecification); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs index c70e154774..aeeda58bb0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Short.cs @@ -1,244 +1,243 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the OldSubfileType exif tag. - /// - public static ExifTag OldSubfileType { get; } = new ExifTag(ExifTagValue.OldSubfileType); - - /// - /// Gets the Compression exif tag. - /// - public static ExifTag Compression { get; } = new ExifTag(ExifTagValue.Compression); - - /// - /// Gets the PhotometricInterpretation exif tag. - /// - public static ExifTag PhotometricInterpretation { get; } = new ExifTag(ExifTagValue.PhotometricInterpretation); - - /// - /// Gets the Thresholding exif tag. - /// - public static ExifTag Thresholding { get; } = new ExifTag(ExifTagValue.Thresholding); - - /// - /// Gets the CellWidth exif tag. - /// - public static ExifTag CellWidth { get; } = new ExifTag(ExifTagValue.CellWidth); - - /// - /// Gets the CellLength exif tag. - /// - public static ExifTag CellLength { get; } = new ExifTag(ExifTagValue.CellLength); - - /// - /// Gets the FillOrder exif tag. - /// - public static ExifTag FillOrder { get; } = new ExifTag(ExifTagValue.FillOrder); - - /// - /// Gets the Orientation exif tag. - /// - public static ExifTag Orientation { get; } = new ExifTag(ExifTagValue.Orientation); - - /// - /// Gets the SamplesPerPixel exif tag. - /// - public static ExifTag SamplesPerPixel { get; } = new ExifTag(ExifTagValue.SamplesPerPixel); - - /// - /// Gets the PlanarConfiguration exif tag. - /// - public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); - - /// - /// Gets the Predictor exif tag. - /// - public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); - - /// - /// Gets the GrayResponseUnit exif tag. - /// - public static ExifTag GrayResponseUnit { get; } = new ExifTag(ExifTagValue.GrayResponseUnit); - - /// - /// Gets the ResolutionUnit exif tag. - /// - public static ExifTag ResolutionUnit { get; } = new ExifTag(ExifTagValue.ResolutionUnit); - - /// - /// Gets the CleanFaxData exif tag. - /// - public static ExifTag CleanFaxData { get; } = new ExifTag(ExifTagValue.CleanFaxData); - - /// - /// Gets the InkSet exif tag. - /// - public static ExifTag InkSet { get; } = new ExifTag(ExifTagValue.InkSet); - - /// - /// Gets the NumberOfInks exif tag. - /// - public static ExifTag NumberOfInks { get; } = new ExifTag(ExifTagValue.NumberOfInks); - - /// - /// Gets the DotRange exif tag. - /// - public static ExifTag DotRange { get; } = new ExifTag(ExifTagValue.DotRange); - - /// - /// Gets the Indexed exif tag. - /// - public static ExifTag Indexed { get; } = new ExifTag(ExifTagValue.Indexed); - - /// - /// Gets the OPIProxy exif tag. - /// - public static ExifTag OPIProxy { get; } = new ExifTag(ExifTagValue.OPIProxy); - - /// - /// Gets the JPEGProc exif tag. - /// - public static ExifTag JPEGProc { get; } = new ExifTag(ExifTagValue.JPEGProc); - - /// - /// Gets the JPEGRestartInterval exif tag. - /// - public static ExifTag JPEGRestartInterval { get; } = new ExifTag(ExifTagValue.JPEGRestartInterval); - - /// - /// Gets the YCbCrPositioning exif tag. - /// - public static ExifTag YCbCrPositioning { get; } = new ExifTag(ExifTagValue.YCbCrPositioning); - - /// - /// Gets the Rating exif tag. - /// - public static ExifTag Rating { get; } = new ExifTag(ExifTagValue.Rating); - - /// - /// Gets the RatingPercent exif tag. - /// - public static ExifTag RatingPercent { get; } = new ExifTag(ExifTagValue.RatingPercent); - - /// - /// Gets the ExposureProgram exif tag. - /// - public static ExifTag ExposureProgram { get; } = new ExifTag(ExifTagValue.ExposureProgram); - - /// - /// Gets the Interlace exif tag. - /// - public static ExifTag Interlace { get; } = new ExifTag(ExifTagValue.Interlace); - - /// - /// Gets the SelfTimerMode exif tag. - /// - public static ExifTag SelfTimerMode { get; } = new ExifTag(ExifTagValue.SelfTimerMode); - - /// - /// Gets the SensitivityType exif tag. - /// - public static ExifTag SensitivityType { get; } = new ExifTag(ExifTagValue.SensitivityType); - - /// - /// Gets the MeteringMode exif tag. - /// - public static ExifTag MeteringMode { get; } = new ExifTag(ExifTagValue.MeteringMode); - - /// - /// Gets the LightSource exif tag. - /// - public static ExifTag LightSource { get; } = new ExifTag(ExifTagValue.LightSource); - - /// - /// Gets the FocalPlaneResolutionUnit2 exif tag. - /// - public static ExifTag FocalPlaneResolutionUnit2 { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit2); - - /// - /// Gets the SensingMethod2 exif tag. - /// - public static ExifTag SensingMethod2 { get; } = new ExifTag(ExifTagValue.SensingMethod2); - - /// - /// Gets the Flash exif tag. - /// - public static ExifTag Flash { get; } = new ExifTag(ExifTagValue.Flash); - - /// - /// Gets the ColorSpace exif tag. - /// - public static ExifTag ColorSpace { get; } = new ExifTag(ExifTagValue.ColorSpace); - - /// - /// Gets the FocalPlaneResolutionUnit exif tag. - /// - public static ExifTag FocalPlaneResolutionUnit { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit); - - /// - /// Gets the SensingMethod exif tag. - /// - public static ExifTag SensingMethod { get; } = new ExifTag(ExifTagValue.SensingMethod); - - /// - /// Gets the CustomRendered exif tag. - /// - public static ExifTag CustomRendered { get; } = new ExifTag(ExifTagValue.CustomRendered); - - /// - /// Gets the ExposureMode exif tag. - /// - public static ExifTag ExposureMode { get; } = new ExifTag(ExifTagValue.ExposureMode); - - /// - /// Gets the WhiteBalance exif tag. - /// - public static ExifTag WhiteBalance { get; } = new ExifTag(ExifTagValue.WhiteBalance); - - /// - /// Gets the FocalLengthIn35mmFilm exif tag. - /// - public static ExifTag FocalLengthIn35mmFilm { get; } = new ExifTag(ExifTagValue.FocalLengthIn35mmFilm); - - /// - /// Gets the SceneCaptureType exif tag. - /// - public static ExifTag SceneCaptureType { get; } = new ExifTag(ExifTagValue.SceneCaptureType); - - /// - /// Gets the GainControl exif tag. - /// - public static ExifTag GainControl { get; } = new ExifTag(ExifTagValue.GainControl); - - /// - /// Gets the Contrast exif tag. - /// - public static ExifTag Contrast { get; } = new ExifTag(ExifTagValue.Contrast); - - /// - /// Gets the Saturation exif tag. - /// - public static ExifTag Saturation { get; } = new ExifTag(ExifTagValue.Saturation); - - /// - /// Gets the Sharpness exif tag. - /// - public static ExifTag Sharpness { get; } = new ExifTag(ExifTagValue.Sharpness); - - /// - /// Gets the SubjectDistanceRange exif tag. - /// - public static ExifTag SubjectDistanceRange { get; } = new ExifTag(ExifTagValue.SubjectDistanceRange); - - /// - /// Gets the GPSDifferential exif tag. - /// - public static ExifTag GPSDifferential { get; } = new ExifTag(ExifTagValue.GPSDifferential); - } + /// + /// Gets the OldSubfileType exif tag. + /// + public static ExifTag OldSubfileType { get; } = new ExifTag(ExifTagValue.OldSubfileType); + + /// + /// Gets the Compression exif tag. + /// + public static ExifTag Compression { get; } = new ExifTag(ExifTagValue.Compression); + + /// + /// Gets the PhotometricInterpretation exif tag. + /// + public static ExifTag PhotometricInterpretation { get; } = new ExifTag(ExifTagValue.PhotometricInterpretation); + + /// + /// Gets the Thresholding exif tag. + /// + public static ExifTag Thresholding { get; } = new ExifTag(ExifTagValue.Thresholding); + + /// + /// Gets the CellWidth exif tag. + /// + public static ExifTag CellWidth { get; } = new ExifTag(ExifTagValue.CellWidth); + + /// + /// Gets the CellLength exif tag. + /// + public static ExifTag CellLength { get; } = new ExifTag(ExifTagValue.CellLength); + + /// + /// Gets the FillOrder exif tag. + /// + public static ExifTag FillOrder { get; } = new ExifTag(ExifTagValue.FillOrder); + + /// + /// Gets the Orientation exif tag. + /// + public static ExifTag Orientation { get; } = new ExifTag(ExifTagValue.Orientation); + + /// + /// Gets the SamplesPerPixel exif tag. + /// + public static ExifTag SamplesPerPixel { get; } = new ExifTag(ExifTagValue.SamplesPerPixel); + + /// + /// Gets the PlanarConfiguration exif tag. + /// + public static ExifTag PlanarConfiguration { get; } = new ExifTag(ExifTagValue.PlanarConfiguration); + + /// + /// Gets the Predictor exif tag. + /// + public static ExifTag Predictor { get; } = new ExifTag(ExifTagValue.Predictor); + + /// + /// Gets the GrayResponseUnit exif tag. + /// + public static ExifTag GrayResponseUnit { get; } = new ExifTag(ExifTagValue.GrayResponseUnit); + + /// + /// Gets the ResolutionUnit exif tag. + /// + public static ExifTag ResolutionUnit { get; } = new ExifTag(ExifTagValue.ResolutionUnit); + + /// + /// Gets the CleanFaxData exif tag. + /// + public static ExifTag CleanFaxData { get; } = new ExifTag(ExifTagValue.CleanFaxData); + + /// + /// Gets the InkSet exif tag. + /// + public static ExifTag InkSet { get; } = new ExifTag(ExifTagValue.InkSet); + + /// + /// Gets the NumberOfInks exif tag. + /// + public static ExifTag NumberOfInks { get; } = new ExifTag(ExifTagValue.NumberOfInks); + + /// + /// Gets the DotRange exif tag. + /// + public static ExifTag DotRange { get; } = new ExifTag(ExifTagValue.DotRange); + + /// + /// Gets the Indexed exif tag. + /// + public static ExifTag Indexed { get; } = new ExifTag(ExifTagValue.Indexed); + + /// + /// Gets the OPIProxy exif tag. + /// + public static ExifTag OPIProxy { get; } = new ExifTag(ExifTagValue.OPIProxy); + + /// + /// Gets the JPEGProc exif tag. + /// + public static ExifTag JPEGProc { get; } = new ExifTag(ExifTagValue.JPEGProc); + + /// + /// Gets the JPEGRestartInterval exif tag. + /// + public static ExifTag JPEGRestartInterval { get; } = new ExifTag(ExifTagValue.JPEGRestartInterval); + + /// + /// Gets the YCbCrPositioning exif tag. + /// + public static ExifTag YCbCrPositioning { get; } = new ExifTag(ExifTagValue.YCbCrPositioning); + + /// + /// Gets the Rating exif tag. + /// + public static ExifTag Rating { get; } = new ExifTag(ExifTagValue.Rating); + + /// + /// Gets the RatingPercent exif tag. + /// + public static ExifTag RatingPercent { get; } = new ExifTag(ExifTagValue.RatingPercent); + + /// + /// Gets the ExposureProgram exif tag. + /// + public static ExifTag ExposureProgram { get; } = new ExifTag(ExifTagValue.ExposureProgram); + + /// + /// Gets the Interlace exif tag. + /// + public static ExifTag Interlace { get; } = new ExifTag(ExifTagValue.Interlace); + + /// + /// Gets the SelfTimerMode exif tag. + /// + public static ExifTag SelfTimerMode { get; } = new ExifTag(ExifTagValue.SelfTimerMode); + + /// + /// Gets the SensitivityType exif tag. + /// + public static ExifTag SensitivityType { get; } = new ExifTag(ExifTagValue.SensitivityType); + + /// + /// Gets the MeteringMode exif tag. + /// + public static ExifTag MeteringMode { get; } = new ExifTag(ExifTagValue.MeteringMode); + + /// + /// Gets the LightSource exif tag. + /// + public static ExifTag LightSource { get; } = new ExifTag(ExifTagValue.LightSource); + + /// + /// Gets the FocalPlaneResolutionUnit2 exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit2 { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit2); + + /// + /// Gets the SensingMethod2 exif tag. + /// + public static ExifTag SensingMethod2 { get; } = new ExifTag(ExifTagValue.SensingMethod2); + + /// + /// Gets the Flash exif tag. + /// + public static ExifTag Flash { get; } = new ExifTag(ExifTagValue.Flash); + + /// + /// Gets the ColorSpace exif tag. + /// + public static ExifTag ColorSpace { get; } = new ExifTag(ExifTagValue.ColorSpace); + + /// + /// Gets the FocalPlaneResolutionUnit exif tag. + /// + public static ExifTag FocalPlaneResolutionUnit { get; } = new ExifTag(ExifTagValue.FocalPlaneResolutionUnit); + + /// + /// Gets the SensingMethod exif tag. + /// + public static ExifTag SensingMethod { get; } = new ExifTag(ExifTagValue.SensingMethod); + + /// + /// Gets the CustomRendered exif tag. + /// + public static ExifTag CustomRendered { get; } = new ExifTag(ExifTagValue.CustomRendered); + + /// + /// Gets the ExposureMode exif tag. + /// + public static ExifTag ExposureMode { get; } = new ExifTag(ExifTagValue.ExposureMode); + + /// + /// Gets the WhiteBalance exif tag. + /// + public static ExifTag WhiteBalance { get; } = new ExifTag(ExifTagValue.WhiteBalance); + + /// + /// Gets the FocalLengthIn35mmFilm exif tag. + /// + public static ExifTag FocalLengthIn35mmFilm { get; } = new ExifTag(ExifTagValue.FocalLengthIn35mmFilm); + + /// + /// Gets the SceneCaptureType exif tag. + /// + public static ExifTag SceneCaptureType { get; } = new ExifTag(ExifTagValue.SceneCaptureType); + + /// + /// Gets the GainControl exif tag. + /// + public static ExifTag GainControl { get; } = new ExifTag(ExifTagValue.GainControl); + + /// + /// Gets the Contrast exif tag. + /// + public static ExifTag Contrast { get; } = new ExifTag(ExifTagValue.Contrast); + + /// + /// Gets the Saturation exif tag. + /// + public static ExifTag Saturation { get; } = new ExifTag(ExifTagValue.Saturation); + + /// + /// Gets the Sharpness exif tag. + /// + public static ExifTag Sharpness { get; } = new ExifTag(ExifTagValue.Sharpness); + + /// + /// Gets the SubjectDistanceRange exif tag. + /// + public static ExifTag SubjectDistanceRange { get; } = new ExifTag(ExifTagValue.SubjectDistanceRange); + + /// + /// Gets the GPSDifferential exif tag. + /// + public static ExifTag GPSDifferential { get; } = new ExifTag(ExifTagValue.GPSDifferential); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs index a511b116f1..dc708c50f1 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ShortArray.cs @@ -1,109 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the BitsPerSample exif tag. - /// - public static ExifTag BitsPerSample { get; } = new ExifTag(ExifTagValue.BitsPerSample); - - /// - /// Gets the MinSampleValue exif tag. - /// - public static ExifTag MinSampleValue { get; } = new ExifTag(ExifTagValue.MinSampleValue); - - /// - /// Gets the MaxSampleValue exif tag. - /// - public static ExifTag MaxSampleValue { get; } = new ExifTag(ExifTagValue.MaxSampleValue); - - /// - /// Gets the GrayResponseCurve exif tag. - /// - public static ExifTag GrayResponseCurve { get; } = new ExifTag(ExifTagValue.GrayResponseCurve); - - /// - /// Gets the ColorMap exif tag. - /// - public static ExifTag ColorMap { get; } = new ExifTag(ExifTagValue.ColorMap); - - /// - /// Gets the ExtraSamples exif tag. - /// - public static ExifTag ExtraSamples { get; } = new ExifTag(ExifTagValue.ExtraSamples); - - /// - /// Gets the PageNumber exif tag. - /// - public static ExifTag PageNumber { get; } = new ExifTag(ExifTagValue.PageNumber); - - /// - /// Gets the TransferFunction exif tag. - /// - public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); - - /// - /// Gets the HalftoneHints exif tag. - /// - public static ExifTag HalftoneHints { get; } = new ExifTag(ExifTagValue.HalftoneHints); - - /// - /// Gets the SampleFormat exif tag. - /// - public static ExifTag SampleFormat { get; } = new ExifTag(ExifTagValue.SampleFormat); - - /// - /// Gets the TransferRange exif tag. - /// - public static ExifTag TransferRange { get; } = new ExifTag(ExifTagValue.TransferRange); - - /// - /// Gets the DefaultImageColor exif tag. - /// - public static ExifTag DefaultImageColor { get; } = new ExifTag(ExifTagValue.DefaultImageColor); - - /// - /// Gets the JPEGLosslessPredictors exif tag. - /// - public static ExifTag JPEGLosslessPredictors { get; } = new ExifTag(ExifTagValue.JPEGLosslessPredictors); - - /// - /// Gets the JPEGPointTransforms exif tag. - /// - public static ExifTag JPEGPointTransforms { get; } = new ExifTag(ExifTagValue.JPEGPointTransforms); - - /// - /// Gets the YCbCrSubsampling exif tag. - /// - public static ExifTag YCbCrSubsampling { get; } = new ExifTag(ExifTagValue.YCbCrSubsampling); - - /// - /// Gets the CFARepeatPatternDim exif tag. - /// - public static ExifTag CFARepeatPatternDim { get; } = new ExifTag(ExifTagValue.CFARepeatPatternDim); - - /// - /// Gets the IntergraphPacketData exif tag. - /// - public static ExifTag IntergraphPacketData { get; } = new ExifTag(ExifTagValue.IntergraphPacketData); - - /// - /// Gets the ISOSpeedRatings exif tag. - /// - public static ExifTag ISOSpeedRatings { get; } = new ExifTag(ExifTagValue.ISOSpeedRatings); - - /// - /// Gets the SubjectArea exif tag. - /// - public static ExifTag SubjectArea { get; } = new ExifTag(ExifTagValue.SubjectArea); - - /// - /// Gets the SubjectLocation exif tag. - /// - public static ExifTag SubjectLocation { get; } = new ExifTag(ExifTagValue.SubjectLocation); - } + /// + /// Gets the BitsPerSample exif tag. + /// + public static ExifTag BitsPerSample { get; } = new ExifTag(ExifTagValue.BitsPerSample); + + /// + /// Gets the MinSampleValue exif tag. + /// + public static ExifTag MinSampleValue { get; } = new ExifTag(ExifTagValue.MinSampleValue); + + /// + /// Gets the MaxSampleValue exif tag. + /// + public static ExifTag MaxSampleValue { get; } = new ExifTag(ExifTagValue.MaxSampleValue); + + /// + /// Gets the GrayResponseCurve exif tag. + /// + public static ExifTag GrayResponseCurve { get; } = new ExifTag(ExifTagValue.GrayResponseCurve); + + /// + /// Gets the ColorMap exif tag. + /// + public static ExifTag ColorMap { get; } = new ExifTag(ExifTagValue.ColorMap); + + /// + /// Gets the ExtraSamples exif tag. + /// + public static ExifTag ExtraSamples { get; } = new ExifTag(ExifTagValue.ExtraSamples); + + /// + /// Gets the PageNumber exif tag. + /// + public static ExifTag PageNumber { get; } = new ExifTag(ExifTagValue.PageNumber); + + /// + /// Gets the TransferFunction exif tag. + /// + public static ExifTag TransferFunction { get; } = new ExifTag(ExifTagValue.TransferFunction); + + /// + /// Gets the HalftoneHints exif tag. + /// + public static ExifTag HalftoneHints { get; } = new ExifTag(ExifTagValue.HalftoneHints); + + /// + /// Gets the SampleFormat exif tag. + /// + public static ExifTag SampleFormat { get; } = new ExifTag(ExifTagValue.SampleFormat); + + /// + /// Gets the TransferRange exif tag. + /// + public static ExifTag TransferRange { get; } = new ExifTag(ExifTagValue.TransferRange); + + /// + /// Gets the DefaultImageColor exif tag. + /// + public static ExifTag DefaultImageColor { get; } = new ExifTag(ExifTagValue.DefaultImageColor); + + /// + /// Gets the JPEGLosslessPredictors exif tag. + /// + public static ExifTag JPEGLosslessPredictors { get; } = new ExifTag(ExifTagValue.JPEGLosslessPredictors); + + /// + /// Gets the JPEGPointTransforms exif tag. + /// + public static ExifTag JPEGPointTransforms { get; } = new ExifTag(ExifTagValue.JPEGPointTransforms); + + /// + /// Gets the YCbCrSubsampling exif tag. + /// + public static ExifTag YCbCrSubsampling { get; } = new ExifTag(ExifTagValue.YCbCrSubsampling); + + /// + /// Gets the CFARepeatPatternDim exif tag. + /// + public static ExifTag CFARepeatPatternDim { get; } = new ExifTag(ExifTagValue.CFARepeatPatternDim); + + /// + /// Gets the IntergraphPacketData exif tag. + /// + public static ExifTag IntergraphPacketData { get; } = new ExifTag(ExifTagValue.IntergraphPacketData); + + /// + /// Gets the ISOSpeedRatings exif tag. + /// + public static ExifTag ISOSpeedRatings { get; } = new ExifTag(ExifTagValue.ISOSpeedRatings); + + /// + /// Gets the SubjectArea exif tag. + /// + public static ExifTag SubjectArea { get; } = new ExifTag(ExifTagValue.SubjectArea); + + /// + /// Gets the SubjectLocation exif tag. + /// + public static ExifTag SubjectLocation { get; } = new ExifTag(ExifTagValue.SubjectLocation); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs index c6ec4e6213..d645b5176a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRational.cs @@ -1,39 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the ShutterSpeedValue exif tag. - /// - public static ExifTag ShutterSpeedValue { get; } = new ExifTag(ExifTagValue.ShutterSpeedValue); + /// + /// Gets the ShutterSpeedValue exif tag. + /// + public static ExifTag ShutterSpeedValue { get; } = new ExifTag(ExifTagValue.ShutterSpeedValue); - /// - /// Gets the BrightnessValue exif tag. - /// - public static ExifTag BrightnessValue { get; } = new ExifTag(ExifTagValue.BrightnessValue); + /// + /// Gets the BrightnessValue exif tag. + /// + public static ExifTag BrightnessValue { get; } = new ExifTag(ExifTagValue.BrightnessValue); - /// - /// Gets the ExposureBiasValue exif tag. - /// - public static ExifTag ExposureBiasValue { get; } = new ExifTag(ExifTagValue.ExposureBiasValue); + /// + /// Gets the ExposureBiasValue exif tag. + /// + public static ExifTag ExposureBiasValue { get; } = new ExifTag(ExifTagValue.ExposureBiasValue); - /// - /// Gets the AmbientTemperature exif tag. - /// - public static ExifTag AmbientTemperature { get; } = new ExifTag(ExifTagValue.AmbientTemperature); + /// + /// Gets the AmbientTemperature exif tag. + /// + public static ExifTag AmbientTemperature { get; } = new ExifTag(ExifTagValue.AmbientTemperature); - /// - /// Gets the WaterDepth exif tag. - /// - public static ExifTag WaterDepth { get; } = new ExifTag(ExifTagValue.WaterDepth); + /// + /// Gets the WaterDepth exif tag. + /// + public static ExifTag WaterDepth { get; } = new ExifTag(ExifTagValue.WaterDepth); - /// - /// Gets the CameraElevationAngle exif tag. - /// - public static ExifTag CameraElevationAngle { get; } = new ExifTag(ExifTagValue.CameraElevationAngle); - } + /// + /// Gets the CameraElevationAngle exif tag. + /// + public static ExifTag CameraElevationAngle { get; } = new ExifTag(ExifTagValue.CameraElevationAngle); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs index 1173b91d3f..ef1e8afea4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.SignedRationalArray.cs @@ -1,14 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the Decode exif tag. - /// - public static ExifTag Decode { get; } = new ExifTag(ExifTagValue.Decode); - } + /// + /// Gets the Decode exif tag. + /// + public static ExifTag Decode { get; } = new ExifTag(ExifTagValue.Decode); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs index 76b01080d7..498dad3ebb 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.String.cs @@ -1,279 +1,278 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the ImageDescription exif tag. - /// - public static ExifTag ImageDescription { get; } = new ExifTag(ExifTagValue.ImageDescription); - - /// - /// Gets the Make exif tag. - /// - public static ExifTag Make { get; } = new ExifTag(ExifTagValue.Make); - - /// - /// Gets the Model exif tag. - /// - public static ExifTag Model { get; } = new ExifTag(ExifTagValue.Model); - - /// - /// Gets the Software exif tag. - /// - public static ExifTag Software { get; } = new ExifTag(ExifTagValue.Software); - - /// - /// Gets the DateTime exif tag. - /// - public static ExifTag DateTime { get; } = new ExifTag(ExifTagValue.DateTime); - - /// - /// Gets the Artist exif tag. - /// - public static ExifTag Artist { get; } = new ExifTag(ExifTagValue.Artist); - - /// - /// Gets the HostComputer exif tag. - /// - public static ExifTag HostComputer { get; } = new ExifTag(ExifTagValue.HostComputer); - - /// - /// Gets the Copyright exif tag. - /// - public static ExifTag Copyright { get; } = new ExifTag(ExifTagValue.Copyright); - - /// - /// Gets the DocumentName exif tag. - /// - public static ExifTag DocumentName { get; } = new ExifTag(ExifTagValue.DocumentName); - - /// - /// Gets the PageName exif tag. - /// - public static ExifTag PageName { get; } = new ExifTag(ExifTagValue.PageName); - - /// - /// Gets the InkNames exif tag. - /// - public static ExifTag InkNames { get; } = new ExifTag(ExifTagValue.InkNames); - - /// - /// Gets the TargetPrinter exif tag. - /// - public static ExifTag TargetPrinter { get; } = new ExifTag(ExifTagValue.TargetPrinter); - - /// - /// Gets the ImageID exif tag. - /// - public static ExifTag ImageID { get; } = new ExifTag(ExifTagValue.ImageID); - - /// - /// Gets the MDLabName exif tag. - /// - public static ExifTag MDLabName { get; } = new ExifTag(ExifTagValue.MDLabName); - - /// - /// Gets the MDSampleInfo exif tag. - /// - public static ExifTag MDSampleInfo { get; } = new ExifTag(ExifTagValue.MDSampleInfo); - - /// - /// Gets the MDPrepDate exif tag. - /// - public static ExifTag MDPrepDate { get; } = new ExifTag(ExifTagValue.MDPrepDate); - - /// - /// Gets the MDPrepTime exif tag. - /// - public static ExifTag MDPrepTime { get; } = new ExifTag(ExifTagValue.MDPrepTime); - - /// - /// Gets the MDFileUnits exif tag. - /// - public static ExifTag MDFileUnits { get; } = new ExifTag(ExifTagValue.MDFileUnits); - - /// - /// Gets the SEMInfo exif tag. - /// - public static ExifTag SEMInfo { get; } = new ExifTag(ExifTagValue.SEMInfo); - - /// - /// Gets the SpectralSensitivity exif tag. - /// - public static ExifTag SpectralSensitivity { get; } = new ExifTag(ExifTagValue.SpectralSensitivity); - - /// - /// Gets the DateTimeOriginal exif tag. - /// - public static ExifTag DateTimeOriginal { get; } = new ExifTag(ExifTagValue.DateTimeOriginal); - - /// - /// Gets the DateTimeDigitized exif tag. - /// - public static ExifTag DateTimeDigitized { get; } = new ExifTag(ExifTagValue.DateTimeDigitized); - - /// - /// Gets the SubsecTime exif tag. - /// - public static ExifTag SubsecTime { get; } = new ExifTag(ExifTagValue.SubsecTime); - - /// - /// Gets the SubsecTimeOriginal exif tag. - /// - public static ExifTag SubsecTimeOriginal { get; } = new ExifTag(ExifTagValue.SubsecTimeOriginal); - - /// - /// Gets the SubsecTimeDigitized exif tag. - /// - public static ExifTag SubsecTimeDigitized { get; } = new ExifTag(ExifTagValue.SubsecTimeDigitized); - - /// - /// Gets the RelatedSoundFile exif tag. - /// - public static ExifTag RelatedSoundFile { get; } = new ExifTag(ExifTagValue.RelatedSoundFile); - - /// - /// Gets the FaxSubaddress exif tag. - /// - public static ExifTag FaxSubaddress { get; } = new ExifTag(ExifTagValue.FaxSubaddress); - - /// - /// Gets the OffsetTime exif tag. - /// - public static ExifTag OffsetTime { get; } = new ExifTag(ExifTagValue.OffsetTime); - - /// - /// Gets the OffsetTimeOriginal exif tag. - /// - public static ExifTag OffsetTimeOriginal { get; } = new ExifTag(ExifTagValue.OffsetTimeOriginal); - - /// - /// Gets the OffsetTimeDigitized exif tag. - /// - public static ExifTag OffsetTimeDigitized { get; } = new ExifTag(ExifTagValue.OffsetTimeDigitized); - - /// - /// Gets the SecurityClassification exif tag. - /// - public static ExifTag SecurityClassification { get; } = new ExifTag(ExifTagValue.SecurityClassification); - - /// - /// Gets the ImageHistory exif tag. - /// - public static ExifTag ImageHistory { get; } = new ExifTag(ExifTagValue.ImageHistory); - - /// - /// Gets the ImageUniqueID exif tag. - /// - public static ExifTag ImageUniqueID { get; } = new ExifTag(ExifTagValue.ImageUniqueID); - - /// - /// Gets the OwnerName exif tag. - /// - public static ExifTag OwnerName { get; } = new ExifTag(ExifTagValue.OwnerName); - - /// - /// Gets the SerialNumber exif tag. - /// - public static ExifTag SerialNumber { get; } = new ExifTag(ExifTagValue.SerialNumber); - - /// - /// Gets the LensMake exif tag. - /// - public static ExifTag LensMake { get; } = new ExifTag(ExifTagValue.LensMake); - - /// - /// Gets the LensModel exif tag. - /// - public static ExifTag LensModel { get; } = new ExifTag(ExifTagValue.LensModel); - - /// - /// Gets the LensSerialNumber exif tag. - /// - public static ExifTag LensSerialNumber { get; } = new ExifTag(ExifTagValue.LensSerialNumber); - - /// - /// Gets the GDALMetadata exif tag. - /// - public static ExifTag GDALMetadata { get; } = new ExifTag(ExifTagValue.GDALMetadata); - - /// - /// Gets the GDALNoData exif tag. - /// - public static ExifTag GDALNoData { get; } = new ExifTag(ExifTagValue.GDALNoData); - - /// - /// Gets the GPSLatitudeRef exif tag. - /// - public static ExifTag GPSLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSLatitudeRef); - - /// - /// Gets the GPSLongitudeRef exif tag. - /// - public static ExifTag GPSLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSLongitudeRef); - - /// - /// Gets the GPSSatellites exif tag. - /// - public static ExifTag GPSSatellites { get; } = new ExifTag(ExifTagValue.GPSSatellites); - - /// - /// Gets the GPSStatus exif tag. - /// - public static ExifTag GPSStatus { get; } = new ExifTag(ExifTagValue.GPSStatus); - - /// - /// Gets the GPSMeasureMode exif tag. - /// - public static ExifTag GPSMeasureMode { get; } = new ExifTag(ExifTagValue.GPSMeasureMode); - - /// - /// Gets the GPSSpeedRef exif tag. - /// - public static ExifTag GPSSpeedRef { get; } = new ExifTag(ExifTagValue.GPSSpeedRef); - - /// - /// Gets the GPSTrackRef exif tag. - /// - public static ExifTag GPSTrackRef { get; } = new ExifTag(ExifTagValue.GPSTrackRef); - - /// - /// Gets the GPSImgDirectionRef exif tag. - /// - public static ExifTag GPSImgDirectionRef { get; } = new ExifTag(ExifTagValue.GPSImgDirectionRef); - - /// - /// Gets the GPSMapDatum exif tag. - /// - public static ExifTag GPSMapDatum { get; } = new ExifTag(ExifTagValue.GPSMapDatum); - - /// - /// Gets the GPSDestLatitudeRef exif tag. - /// - public static ExifTag GPSDestLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLatitudeRef); - - /// - /// Gets the GPSDestLongitudeRef exif tag. - /// - public static ExifTag GPSDestLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLongitudeRef); - - /// - /// Gets the GPSDestBearingRef exif tag. - /// - public static ExifTag GPSDestBearingRef { get; } = new ExifTag(ExifTagValue.GPSDestBearingRef); - - /// - /// Gets the GPSDestDistanceRef exif tag. - /// - public static ExifTag GPSDestDistanceRef { get; } = new ExifTag(ExifTagValue.GPSDestDistanceRef); - - /// - /// Gets the GPSDateStamp exif tag. - /// - public static ExifTag GPSDateStamp { get; } = new ExifTag(ExifTagValue.GPSDateStamp); - } + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag ImageDescription { get; } = new ExifTag(ExifTagValue.ImageDescription); + + /// + /// Gets the Make exif tag. + /// + public static ExifTag Make { get; } = new ExifTag(ExifTagValue.Make); + + /// + /// Gets the Model exif tag. + /// + public static ExifTag Model { get; } = new ExifTag(ExifTagValue.Model); + + /// + /// Gets the Software exif tag. + /// + public static ExifTag Software { get; } = new ExifTag(ExifTagValue.Software); + + /// + /// Gets the DateTime exif tag. + /// + public static ExifTag DateTime { get; } = new ExifTag(ExifTagValue.DateTime); + + /// + /// Gets the Artist exif tag. + /// + public static ExifTag Artist { get; } = new ExifTag(ExifTagValue.Artist); + + /// + /// Gets the HostComputer exif tag. + /// + public static ExifTag HostComputer { get; } = new ExifTag(ExifTagValue.HostComputer); + + /// + /// Gets the Copyright exif tag. + /// + public static ExifTag Copyright { get; } = new ExifTag(ExifTagValue.Copyright); + + /// + /// Gets the DocumentName exif tag. + /// + public static ExifTag DocumentName { get; } = new ExifTag(ExifTagValue.DocumentName); + + /// + /// Gets the PageName exif tag. + /// + public static ExifTag PageName { get; } = new ExifTag(ExifTagValue.PageName); + + /// + /// Gets the InkNames exif tag. + /// + public static ExifTag InkNames { get; } = new ExifTag(ExifTagValue.InkNames); + + /// + /// Gets the TargetPrinter exif tag. + /// + public static ExifTag TargetPrinter { get; } = new ExifTag(ExifTagValue.TargetPrinter); + + /// + /// Gets the ImageID exif tag. + /// + public static ExifTag ImageID { get; } = new ExifTag(ExifTagValue.ImageID); + + /// + /// Gets the MDLabName exif tag. + /// + public static ExifTag MDLabName { get; } = new ExifTag(ExifTagValue.MDLabName); + + /// + /// Gets the MDSampleInfo exif tag. + /// + public static ExifTag MDSampleInfo { get; } = new ExifTag(ExifTagValue.MDSampleInfo); + + /// + /// Gets the MDPrepDate exif tag. + /// + public static ExifTag MDPrepDate { get; } = new ExifTag(ExifTagValue.MDPrepDate); + + /// + /// Gets the MDPrepTime exif tag. + /// + public static ExifTag MDPrepTime { get; } = new ExifTag(ExifTagValue.MDPrepTime); + + /// + /// Gets the MDFileUnits exif tag. + /// + public static ExifTag MDFileUnits { get; } = new ExifTag(ExifTagValue.MDFileUnits); + + /// + /// Gets the SEMInfo exif tag. + /// + public static ExifTag SEMInfo { get; } = new ExifTag(ExifTagValue.SEMInfo); + + /// + /// Gets the SpectralSensitivity exif tag. + /// + public static ExifTag SpectralSensitivity { get; } = new ExifTag(ExifTagValue.SpectralSensitivity); + + /// + /// Gets the DateTimeOriginal exif tag. + /// + public static ExifTag DateTimeOriginal { get; } = new ExifTag(ExifTagValue.DateTimeOriginal); + + /// + /// Gets the DateTimeDigitized exif tag. + /// + public static ExifTag DateTimeDigitized { get; } = new ExifTag(ExifTagValue.DateTimeDigitized); + + /// + /// Gets the SubsecTime exif tag. + /// + public static ExifTag SubsecTime { get; } = new ExifTag(ExifTagValue.SubsecTime); + + /// + /// Gets the SubsecTimeOriginal exif tag. + /// + public static ExifTag SubsecTimeOriginal { get; } = new ExifTag(ExifTagValue.SubsecTimeOriginal); + + /// + /// Gets the SubsecTimeDigitized exif tag. + /// + public static ExifTag SubsecTimeDigitized { get; } = new ExifTag(ExifTagValue.SubsecTimeDigitized); + + /// + /// Gets the RelatedSoundFile exif tag. + /// + public static ExifTag RelatedSoundFile { get; } = new ExifTag(ExifTagValue.RelatedSoundFile); + + /// + /// Gets the FaxSubaddress exif tag. + /// + public static ExifTag FaxSubaddress { get; } = new ExifTag(ExifTagValue.FaxSubaddress); + + /// + /// Gets the OffsetTime exif tag. + /// + public static ExifTag OffsetTime { get; } = new ExifTag(ExifTagValue.OffsetTime); + + /// + /// Gets the OffsetTimeOriginal exif tag. + /// + public static ExifTag OffsetTimeOriginal { get; } = new ExifTag(ExifTagValue.OffsetTimeOriginal); + + /// + /// Gets the OffsetTimeDigitized exif tag. + /// + public static ExifTag OffsetTimeDigitized { get; } = new ExifTag(ExifTagValue.OffsetTimeDigitized); + + /// + /// Gets the SecurityClassification exif tag. + /// + public static ExifTag SecurityClassification { get; } = new ExifTag(ExifTagValue.SecurityClassification); + + /// + /// Gets the ImageHistory exif tag. + /// + public static ExifTag ImageHistory { get; } = new ExifTag(ExifTagValue.ImageHistory); + + /// + /// Gets the ImageUniqueID exif tag. + /// + public static ExifTag ImageUniqueID { get; } = new ExifTag(ExifTagValue.ImageUniqueID); + + /// + /// Gets the OwnerName exif tag. + /// + public static ExifTag OwnerName { get; } = new ExifTag(ExifTagValue.OwnerName); + + /// + /// Gets the SerialNumber exif tag. + /// + public static ExifTag SerialNumber { get; } = new ExifTag(ExifTagValue.SerialNumber); + + /// + /// Gets the LensMake exif tag. + /// + public static ExifTag LensMake { get; } = new ExifTag(ExifTagValue.LensMake); + + /// + /// Gets the LensModel exif tag. + /// + public static ExifTag LensModel { get; } = new ExifTag(ExifTagValue.LensModel); + + /// + /// Gets the LensSerialNumber exif tag. + /// + public static ExifTag LensSerialNumber { get; } = new ExifTag(ExifTagValue.LensSerialNumber); + + /// + /// Gets the GDALMetadata exif tag. + /// + public static ExifTag GDALMetadata { get; } = new ExifTag(ExifTagValue.GDALMetadata); + + /// + /// Gets the GDALNoData exif tag. + /// + public static ExifTag GDALNoData { get; } = new ExifTag(ExifTagValue.GDALNoData); + + /// + /// Gets the GPSLatitudeRef exif tag. + /// + public static ExifTag GPSLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSLatitudeRef); + + /// + /// Gets the GPSLongitudeRef exif tag. + /// + public static ExifTag GPSLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSLongitudeRef); + + /// + /// Gets the GPSSatellites exif tag. + /// + public static ExifTag GPSSatellites { get; } = new ExifTag(ExifTagValue.GPSSatellites); + + /// + /// Gets the GPSStatus exif tag. + /// + public static ExifTag GPSStatus { get; } = new ExifTag(ExifTagValue.GPSStatus); + + /// + /// Gets the GPSMeasureMode exif tag. + /// + public static ExifTag GPSMeasureMode { get; } = new ExifTag(ExifTagValue.GPSMeasureMode); + + /// + /// Gets the GPSSpeedRef exif tag. + /// + public static ExifTag GPSSpeedRef { get; } = new ExifTag(ExifTagValue.GPSSpeedRef); + + /// + /// Gets the GPSTrackRef exif tag. + /// + public static ExifTag GPSTrackRef { get; } = new ExifTag(ExifTagValue.GPSTrackRef); + + /// + /// Gets the GPSImgDirectionRef exif tag. + /// + public static ExifTag GPSImgDirectionRef { get; } = new ExifTag(ExifTagValue.GPSImgDirectionRef); + + /// + /// Gets the GPSMapDatum exif tag. + /// + public static ExifTag GPSMapDatum { get; } = new ExifTag(ExifTagValue.GPSMapDatum); + + /// + /// Gets the GPSDestLatitudeRef exif tag. + /// + public static ExifTag GPSDestLatitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLatitudeRef); + + /// + /// Gets the GPSDestLongitudeRef exif tag. + /// + public static ExifTag GPSDestLongitudeRef { get; } = new ExifTag(ExifTagValue.GPSDestLongitudeRef); + + /// + /// Gets the GPSDestBearingRef exif tag. + /// + public static ExifTag GPSDestBearingRef { get; } = new ExifTag(ExifTagValue.GPSDestBearingRef); + + /// + /// Gets the GPSDestDistanceRef exif tag. + /// + public static ExifTag GPSDestDistanceRef { get; } = new ExifTag(ExifTagValue.GPSDestDistanceRef); + + /// + /// Gets the GPSDateStamp exif tag. + /// + public static ExifTag GPSDateStamp { get; } = new ExifTag(ExifTagValue.GPSDateStamp); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs index 4e3c740b6d..65acc4bd75 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Ucs2String.cs @@ -1,34 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the title tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); + /// + /// Gets the title tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPTitle => new ExifTag(ExifTagValue.XPTitle); - /// - /// Gets the comment tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); + /// + /// Gets the comment tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPComment => new ExifTag(ExifTagValue.XPComment); - /// - /// Gets the author tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); + /// + /// Gets the author tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPAuthor => new ExifTag(ExifTagValue.XPAuthor); - /// - /// Gets the keywords tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); + /// + /// Gets the keywords tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPKeywords => new ExifTag(ExifTagValue.XPKeywords); - /// - /// Gets the subject tag used by Windows (encoded in UCS2). - /// - public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); - } + /// + /// Gets the subject tag used by Windows (encoded in UCS2). + /// + public static ExifTag XPSubject => new ExifTag(ExifTagValue.XPSubject); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs index 17a05d737e..417673b4ae 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Undefined.cs @@ -1,79 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +public abstract partial class ExifTag { - /// - public abstract partial class ExifTag - { - /// - /// Gets the JPEGTables exif tag. - /// - public static ExifTag JPEGTables { get; } = new ExifTag(ExifTagValue.JPEGTables); + /// + /// Gets the JPEGTables exif tag. + /// + public static ExifTag JPEGTables { get; } = new ExifTag(ExifTagValue.JPEGTables); - /// - /// Gets the OECF exif tag. - /// - public static ExifTag OECF { get; } = new ExifTag(ExifTagValue.OECF); + /// + /// Gets the OECF exif tag. + /// + public static ExifTag OECF { get; } = new ExifTag(ExifTagValue.OECF); - /// - /// Gets the ExifVersion exif tag. - /// - public static ExifTag ExifVersion { get; } = new ExifTag(ExifTagValue.ExifVersion); + /// + /// Gets the ExifVersion exif tag. + /// + public static ExifTag ExifVersion { get; } = new ExifTag(ExifTagValue.ExifVersion); - /// - /// Gets the ComponentsConfiguration exif tag. - /// - public static ExifTag ComponentsConfiguration { get; } = new ExifTag(ExifTagValue.ComponentsConfiguration); + /// + /// Gets the ComponentsConfiguration exif tag. + /// + public static ExifTag ComponentsConfiguration { get; } = new ExifTag(ExifTagValue.ComponentsConfiguration); - /// - /// Gets the MakerNote exif tag. - /// - public static ExifTag MakerNote { get; } = new ExifTag(ExifTagValue.MakerNote); + /// + /// Gets the MakerNote exif tag. + /// + public static ExifTag MakerNote { get; } = new ExifTag(ExifTagValue.MakerNote); - /// - /// Gets the FlashpixVersion exif tag. - /// - public static ExifTag FlashpixVersion { get; } = new ExifTag(ExifTagValue.FlashpixVersion); + /// + /// Gets the FlashpixVersion exif tag. + /// + public static ExifTag FlashpixVersion { get; } = new ExifTag(ExifTagValue.FlashpixVersion); - /// - /// Gets the SpatialFrequencyResponse exif tag. - /// - public static ExifTag SpatialFrequencyResponse { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse); + /// + /// Gets the SpatialFrequencyResponse exif tag. + /// + public static ExifTag SpatialFrequencyResponse { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse); - /// - /// Gets the SpatialFrequencyResponse2 exif tag. - /// - public static ExifTag SpatialFrequencyResponse2 { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse2); + /// + /// Gets the SpatialFrequencyResponse2 exif tag. + /// + public static ExifTag SpatialFrequencyResponse2 { get; } = new ExifTag(ExifTagValue.SpatialFrequencyResponse2); - /// - /// Gets the Noise exif tag. - /// - public static ExifTag Noise { get; } = new ExifTag(ExifTagValue.Noise); + /// + /// Gets the Noise exif tag. + /// + public static ExifTag Noise { get; } = new ExifTag(ExifTagValue.Noise); - /// - /// Gets the CFAPattern exif tag. - /// - public static ExifTag CFAPattern { get; } = new ExifTag(ExifTagValue.CFAPattern); + /// + /// Gets the CFAPattern exif tag. + /// + public static ExifTag CFAPattern { get; } = new ExifTag(ExifTagValue.CFAPattern); - /// - /// Gets the DeviceSettingDescription exif tag. - /// - public static ExifTag DeviceSettingDescription { get; } = new ExifTag(ExifTagValue.DeviceSettingDescription); + /// + /// Gets the DeviceSettingDescription exif tag. + /// + public static ExifTag DeviceSettingDescription { get; } = new ExifTag(ExifTagValue.DeviceSettingDescription); - /// - /// Gets the ImageSourceData exif tag. - /// - public static ExifTag ImageSourceData { get; } = new ExifTag(ExifTagValue.ImageSourceData); + /// + /// Gets the ImageSourceData exif tag. + /// + public static ExifTag ImageSourceData { get; } = new ExifTag(ExifTagValue.ImageSourceData); - /// - /// Gets the FileSource exif tag. - /// - public static ExifTag FileSource { get; } = new ExifTag(ExifTagValue.FileSource); + /// + /// Gets the FileSource exif tag. + /// + public static ExifTag FileSource { get; } = new ExifTag(ExifTagValue.FileSource); - /// - /// Gets the ImageDescription exif tag. - /// - public static ExifTag SceneType { get; } = new ExifTag(ExifTagValue.SceneType); - } + /// + /// Gets the ImageDescription exif tag. + /// + public static ExifTag SceneType { get; } = new ExifTag(ExifTagValue.SceneType); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs index e01241fa7c..d6e1b7e561 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs @@ -1,70 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +/// +/// Class that represents an exif tag from the Exif standard 2.31. +/// +public abstract partial class ExifTag : IEquatable { + private readonly ushort value; + + internal ExifTag(ushort value) => this.value = value; + /// - /// Class that represents an exif tag from the Exif standard 2.31. + /// Converts the specified to a . /// - public abstract partial class ExifTag : IEquatable - { - private readonly ushort value; + /// The to convert. + public static explicit operator ushort(ExifTag tag) => tag?.value ?? (ushort)ExifTagValue.Unknown; - internal ExifTag(ushort value) => this.value = value; + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(ExifTag left, ExifTag right) => Equals(left, right); - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator ushort(ExifTag tag) => tag?.value ?? (ushort)ExifTagValue.Unknown; + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(ExifTag left, ExifTag right) => !Equals(left, right); - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator ==(ExifTag left, ExifTag right) => Equals(left, right); + /// + public override bool Equals(object obj) + { + if (obj is ExifTag value) + { + return this.Equals(value); + } - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator !=(ExifTag left, ExifTag right) => !Equals(left, right); + return false; + } - /// - public override bool Equals(object obj) + /// + public bool Equals(ExifTag other) + { + if (other is null) { - if (obj is ExifTag value) - { - return this.Equals(value); - } - return false; } - /// - public bool Equals(ExifTag other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.value == other.value; + return true; } - /// - public override int GetHashCode() => this.value.GetHashCode(); - - /// - public override string ToString() => ((ExifTagValue)this.value).ToString(); + return this.value == other.value; } + + /// + public override int GetHashCode() => this.value.GetHashCode(); + + /// + public override string ToString() => ((ExifTagValue)this.value).ToString(); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs index f7b1ef4641..3788a1296f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs @@ -1,1730 +1,1729 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// All exif tags from the Exif standard 2.31. +/// +internal enum ExifTagValue { /// - /// All exif tags from the Exif standard 2.31. - /// - internal enum ExifTagValue - { - /// - /// Unknown - /// - Unknown = 0xFFFF, - - /// - /// SubIFDOffset - /// - SubIFDOffset = 0x8769, - - /// - /// GPSIFDOffset - /// - GPSIFDOffset = 0x8825, - - /// - /// Indicates the identification of the Interoperability rule. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability/interoperabilityindex.html - /// - [ExifTagDescription("R98", "Indicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.")] - [ExifTagDescription("THM", "Indicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.")] - InteroperabilityIndex = 0x0001, - - /// - /// A general indication of the kind of data contained in this subfile. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription(0U, "Full-resolution Image")] - [ExifTagDescription(1U, "Reduced-resolution image")] - [ExifTagDescription(2U, "Single page of multi-page image")] - [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] - [ExifTagDescription(4U, "Transparency mask")] - [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] - [ExifTagDescription(6U, "Transparency mask of multi-page image")] - [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] - [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] - SubfileType = 0x00FE, - - /// - /// A general indication of the kind of data contained in this subfile. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Full-resolution Image")] - [ExifTagDescription((ushort)2, "Reduced-resolution image")] - [ExifTagDescription((ushort)3, "Single page of multi-page image")] - OldSubfileType = 0x00FF, - - /// - /// The number of columns in the image, i.e., the number of pixels per row. - /// See Section 8: Baseline Fields. - /// - ImageWidth = 0x0100, - - /// - /// The number of rows of pixels in the image. - /// See Section 8: Baseline Fields. - /// - ImageLength = 0x0101, - - /// - /// Number of bits per component. - /// See Section 8: Baseline Fields. - /// - BitsPerSample = 0x0102, - - /// - /// Compression scheme used on the image data. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Uncompressed")] - [ExifTagDescription((ushort)2, "CCITT 1D")] - [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] - [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] - [ExifTagDescription((ushort)5, "LZW")] - [ExifTagDescription((ushort)6, "JPEG (old-style)")] - [ExifTagDescription((ushort)7, "JPEG")] - [ExifTagDescription((ushort)8, "Adobe Deflate")] - [ExifTagDescription((ushort)9, "JBIG B&W")] - [ExifTagDescription((ushort)10, "JBIG Color")] - [ExifTagDescription((ushort)99, "JPEG")] - [ExifTagDescription((ushort)262, "Kodak 262")] - [ExifTagDescription((ushort)32766, "Next")] - [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] - [ExifTagDescription((ushort)32769, "Packed RAW")] - [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] - [ExifTagDescription((ushort)32771, "CCIRLEW")] - [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] - [ExifTagDescription((ushort)32773, "PackBits")] - [ExifTagDescription((ushort)32809, "Thunderscan")] - [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] - [ExifTagDescription((ushort)32895, "IT8CTPAD")] - [ExifTagDescription((ushort)32896, "IT8LW")] - [ExifTagDescription((ushort)32897, "IT8MP")] - [ExifTagDescription((ushort)32898, "IT8BL")] - [ExifTagDescription((ushort)32908, "PixarFilm")] - [ExifTagDescription((ushort)32909, "PixarLog")] - [ExifTagDescription((ushort)32946, "Deflate")] - [ExifTagDescription((ushort)32947, "DCS")] - [ExifTagDescription((ushort)34661, "JBIG")] - [ExifTagDescription((ushort)34676, "SGILog")] - [ExifTagDescription((ushort)34677, "SGILog24")] - [ExifTagDescription((ushort)34712, "JPEG 2000")] - [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] - [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] - [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] - [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] - [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] - [ExifTagDescription((ushort)34892, "Lossy JPEG")] - [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] - [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] - Compression = 0x0103, - - /// - /// The color space of the image data. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)0, "WhiteIsZero")] - [ExifTagDescription((ushort)1, "BlackIsZero")] - [ExifTagDescription((ushort)2, "RGB")] - [ExifTagDescription((ushort)3, "RGB Palette")] - [ExifTagDescription((ushort)4, "Transparency Mask")] - [ExifTagDescription((ushort)5, "CMYK")] - [ExifTagDescription((ushort)6, "YCbCr")] - [ExifTagDescription((ushort)8, "CIELab")] - [ExifTagDescription((ushort)9, "ICCLab")] - [ExifTagDescription((ushort)10, "TULab")] - [ExifTagDescription((ushort)32803, "Color Filter Array")] - [ExifTagDescription((ushort)32844, "Pixar LogL")] - [ExifTagDescription((ushort)32845, "Pixar LogLuv")] - [ExifTagDescription((ushort)34892, "Linear Raw")] - PhotometricInterpretation = 0x0106, - - /// - /// For black and white TIFF files that represent shades of gray, the technique used to convert from gray to black and white pixels. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "No dithering or halftoning")] - [ExifTagDescription((ushort)2, "Ordered dither or halftone")] - [ExifTagDescription((ushort)3, "Randomized dither")] - Thresholding = 0x0107, - - /// - /// The width of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. - /// See Section 8: Baseline Fields. - /// - CellWidth = 0x0108, - - /// - /// The length of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. - /// See Section 8: Baseline Fields. - /// - CellLength = 0x0109, - - /// - /// The logical order of bits within a byte. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Reversed")] - FillOrder = 0x010A, - - /// - /// The name of the document from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - DocumentName = 0x010D, - - /// - /// A string that describes the subject of the image. - /// See Section 8: Baseline Fields. - /// - ImageDescription = 0x010E, - - /// - /// The scanner manufacturer. - /// See Section 8: Baseline Fields. - /// - Make = 0x010F, - - /// - /// The scanner model name or number. - /// See Section 8: Baseline Fields. - /// - Model = 0x0110, - - /// - /// For each strip, the byte offset of that strip. - /// See Section 8: Baseline Fields. - /// - StripOffsets = 0x0111, - - /// - /// The orientation of the image with respect to the rows and columns. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Horizontal (normal)")] - [ExifTagDescription((ushort)2, "Mirror horizontal")] - [ExifTagDescription((ushort)3, "Rotate 180")] - [ExifTagDescription((ushort)4, "Mirror vertical")] - [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] - [ExifTagDescription((ushort)6, "Rotate 90 CW")] - [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] - [ExifTagDescription((ushort)8, "Rotate 270 CW")] - Orientation = 0x0112, - - /// - /// The number of components per pixel. - /// See Section 8: Baseline Fields. - /// - SamplesPerPixel = 0x0115, - - /// - /// The number of rows per strip. - /// See Section 8: Baseline Fields. - /// - RowsPerStrip = 0x0116, - - /// - /// For each strip, the number of bytes in the strip after compression. - /// See Section 8: Baseline Fields. - /// - StripByteCounts = 0x0117, - - /// - /// The minimum component value used. - /// See Section 8: Baseline Fields. - /// - MinSampleValue = 0x0118, - - /// - /// The maximum component value used. - /// See Section 8: Baseline Fields. - /// - MaxSampleValue = 0x0119, - - /// - /// The number of pixels per ResolutionUnit in the ImageWidth direction. - /// See Section 8: Baseline Fields. - /// - XResolution = 0x011A, - - /// - /// The number of pixels per ResolutionUnit in the direction. - /// See Section 8: Baseline Fields. - /// - YResolution = 0x011B, - - /// - /// How the components of each pixel are stored. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "Chunky")] - [ExifTagDescription((ushort)2, "Planar")] - PlanarConfiguration = 0x011C, - - /// - /// The name of the page from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - PageName = 0x011D, - - /// - /// X position of the image. - /// See Section 12: Document Storage and Retrieval. - /// - XPosition = 0x011E, - - /// - /// Y position of the image. - /// See Section 12: Document Storage and Retrieval. - /// - YPosition = 0x011F, - - /// - /// For each string of contiguous unused bytes in a TIFF file, the byte offset of the string. - /// See Section 8: Baseline Fields. - /// - FreeOffsets = 0x0120, - - /// - /// For each string of contiguous unused bytes in a TIFF file, the number of bytes in the string. - /// See Section 8: Baseline Fields. - /// - FreeByteCounts = 0x0121, - - /// - /// The precision of the information contained in the GrayResponseCurve. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "0.1")] - [ExifTagDescription((ushort)2, "0.001")] - [ExifTagDescription((ushort)3, "0.0001")] - [ExifTagDescription((ushort)4, "1e-05")] - [ExifTagDescription((ushort)5, "1e-06")] - GrayResponseUnit = 0x0122, - - /// - /// For grayscale data, the optical density of each possible pixel value. - /// See Section 8: Baseline Fields. - /// - GrayResponseCurve = 0x0123, - - /// - /// Options for Group 3 Fax compression. - /// - [ExifTagDescription(0U, "2-Dimensional encoding")] - [ExifTagDescription(1U, "Uncompressed")] - [ExifTagDescription(2U, "Fill bits added")] - T4Options = 0x0124, - - /// - /// Options for Group 4 Fax compression. - /// - [ExifTagDescription(1U, "Uncompressed")] - T6Options = 0x0125, - - /// - /// The unit of measurement for XResolution and YResolution. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - ResolutionUnit = 0x0128, - - /// - /// The page number of the page from which this image was scanned. - /// See Section 12: Document Storage and Retrieval. - /// - PageNumber = 0x0129, - - /// - /// ColorResponseUnit - /// - ColorResponseUnit = 0x012C, - - /// - /// TransferFunction - /// - TransferFunction = 0x012D, - - /// - /// Name and version number of the software package(s) used to create the image. - /// See Section 8: Baseline Fields. - /// - Software = 0x0131, - - /// - /// Date and time of image creation. - /// See Section 8: Baseline Fields. - /// - DateTime = 0x0132, - - /// - /// Person who created the image. - /// See Section 8: Baseline Fields. - /// - Artist = 0x013B, - - /// - /// The computer and/or operating system in use at the time of image creation. - /// See Section 8: Baseline Fields. - /// - HostComputer = 0x013C, - - /// - /// Predictor - /// - Predictor = 0x013D, - - /// - /// WhitePoint - /// - WhitePoint = 0x013E, - - /// - /// PrimaryChromaticities - /// - PrimaryChromaticities = 0x013F, - - /// - /// A color map for palette color images. - /// See Section 8: Baseline Fields. - /// - ColorMap = 0x0140, - - /// - /// HalftoneHints - /// - HalftoneHints = 0x0141, - - /// - /// TileWidth - /// - TileWidth = 0x0142, - - /// - /// TileLength - /// - TileLength = 0x0143, - - /// - /// TileOffsets - /// - TileOffsets = 0x0144, - - /// - /// TileByteCounts - /// - TileByteCounts = 0x0145, - - /// - /// BadFaxLines - /// - BadFaxLines = 0x0146, - - /// - /// CleanFaxData - /// - [ExifTagDescription(0U, "Clean")] - [ExifTagDescription(1U, "Regenerated")] - [ExifTagDescription(2U, "Unclean")] - CleanFaxData = 0x0147, - - /// - /// ConsecutiveBadFaxLines - /// - ConsecutiveBadFaxLines = 0x0148, - - /// - /// Offset to child IFDs. - /// See TIFF Supplement 1: Adobe Pagemaker 6.0. - /// Each value is an offset (from the beginning of the TIFF file, as always) to a child IFD. Child images provide extra information for the parent image - such as a subsampled version of the parent image. - /// TIFF data type is Long or 13, IFD. The IFD type is identical to LONG, except that it is only used to point to other valid IFDs. - /// - SubIFDs = 0x014A, - - /// - /// InkSet - /// - [ExifTagDescription((ushort)1, "CMYK")] - [ExifTagDescription((ushort)2, "Not CMYK")] - InkSet = 0x014C, - - /// - /// InkNames - /// - InkNames = 0x014D, - - /// - /// NumberOfInks - /// - NumberOfInks = 0x014E, - - /// - /// DotRange - /// - DotRange = 0x0150, - - /// - /// TargetPrinter - /// - TargetPrinter = 0x0151, - - /// - /// Description of extra components. - /// See Section 8: Baseline Fields. - /// - [ExifTagDescription((ushort)0, "Unspecified")] - [ExifTagDescription((ushort)1, "Associated Alpha")] - [ExifTagDescription((ushort)2, "Unassociated Alpha")] - ExtraSamples = 0x0152, - - /// - /// SampleFormat - /// - [ExifTagDescription((ushort)1, "Unsigned")] - [ExifTagDescription((ushort)2, "Signed")] - [ExifTagDescription((ushort)3, "Float")] - [ExifTagDescription((ushort)4, "Undefined")] - [ExifTagDescription((ushort)5, "Complex int")] - [ExifTagDescription((ushort)6, "Complex float")] - SampleFormat = 0x0153, - - /// - /// SMinSampleValue - /// - SMinSampleValue = 0x0154, - - /// - /// SMaxSampleValue - /// - SMaxSampleValue = 0x0155, - - /// - /// TransferRange - /// - TransferRange = 0x0156, - - /// - /// ClipPath - /// - ClipPath = 0x0157, - - /// - /// XClipPathUnits - /// - XClipPathUnits = 0x0158, - - /// - /// YClipPathUnits - /// - YClipPathUnits = 0x0159, - - /// - /// Indexed - /// - [ExifTagDescription((ushort)0, "Not indexed")] - [ExifTagDescription((ushort)1, "Indexed")] - Indexed = 0x015A, - - /// - /// JPEGTables - /// - JPEGTables = 0x015B, - - /// - /// OPIProxy - /// - [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] - [ExifTagDescription((ushort)1, "Higher resolution image exists")] - OPIProxy = 0x015F, - - /// - /// Used in the TIFF-FX standard to point to an IFD containing tags that are globally applicable to the complete TIFF file. - /// See RFC2301: TIFF-F/FX Specification. - /// It is recommended that a TIFF writer place this field in the first IFD, where a TIFF reader would find it quickly. - /// Each field in the GlobalParametersIFD is a TIFF field that is legal in any IFD. Required baseline fields should not be located in the GlobalParametersIFD, but should be in each image IFD. If a conflict exists between fields in the GlobalParametersIFD and in the image IFDs, then the data in the image IFD shall prevail. - /// - GlobalParametersIFD = 0x0190, - - /// - /// ProfileType - /// - [ExifTagDescription(0U, "Unspecified")] - [ExifTagDescription(1U, "Group 3 FAX")] - ProfileType = 0x0191, - - /// - /// FaxProfile - /// - [ExifTagDescription((byte)0, "Unknown")] - [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] - [ExifTagDescription((byte)2, "Extended B&W lossless, F")] - [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] - [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] - [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] - [ExifTagDescription((byte)6, "Mixed raster content, M")] - [ExifTagDescription((byte)7, "Profile T")] - [ExifTagDescription((byte)255, "Multi Profiles")] - FaxProfile = 0x0192, - - /// - /// CodingMethods - /// - [ExifTagDescription(0UL, "Unspecified compression")] - [ExifTagDescription(1UL, "Modified Huffman")] - [ExifTagDescription(2UL, "Modified Read")] - [ExifTagDescription(4UL, "Modified MR")] - [ExifTagDescription(8UL, "JBIG")] - [ExifTagDescription(16UL, "Baseline JPEG")] - [ExifTagDescription(32UL, "JBIG color")] - CodingMethods = 0x0193, - - /// - /// VersionYear - /// - VersionYear = 0x0194, - - /// - /// ModeNumber - /// - ModeNumber = 0x0195, - - /// - /// Decode - /// - Decode = 0x01B1, - - /// - /// DefaultImageColor - /// - DefaultImageColor = 0x01B2, - - /// - /// T82ptions - /// - T82ptions = 0x01B3, - - /// - /// JPEGProc - /// - [ExifTagDescription((ushort)1, "Baseline")] - [ExifTagDescription((ushort)14, "Lossless")] - JPEGProc = 0x0200, - - /// - /// JPEGInterchangeFormat - /// - JPEGInterchangeFormat = 0x0201, - - /// - /// JPEGInterchangeFormatLength - /// - JPEGInterchangeFormatLength = 0x0202, - - /// - /// JPEGRestartInterval - /// - JPEGRestartInterval = 0x0203, - - /// - /// JPEGLosslessPredictors - /// - JPEGLosslessPredictors = 0x0205, - - /// - /// JPEGPointTransforms - /// - JPEGPointTransforms = 0x0206, - - /// - /// JPEGQTables - /// - JPEGQTables = 0x0207, - - /// - /// JPEGDCTables - /// - JPEGDCTables = 0x0208, - - /// - /// JPEGACTables - /// - JPEGACTables = 0x0209, - - /// - /// YCbCrCoefficients - /// - YCbCrCoefficients = 0x0211, - - /// - /// YCbCrSubsampling - /// - YCbCrSubsampling = 0x0212, - - /// - /// YCbCrPositioning - /// - [ExifTagDescription((ushort)1, "Centered")] - [ExifTagDescription((ushort)2, "Co-sited")] - YCbCrPositioning = 0x0213, - - /// - /// ReferenceBlackWhite - /// - ReferenceBlackWhite = 0x0214, - - /// - /// StripRowCounts - /// - StripRowCounts = 0x022F, - - /// - /// XMP - /// - XMP = 0x02BC, - - /// - /// Rating - /// - Rating = 0x4746, - - /// - /// RatingPercent - /// - RatingPercent = 0x4749, - - /// - /// ImageID - /// - ImageID = 0x800D, - - /// - /// Annotation data, as used in 'Imaging for Windows'. - /// See Other Private TIFF tags: http://www.awaresystems.be/imaging/tiff/tifftags/private.html - /// - WangAnnotation = 0x80A4, - - /// - /// CFARepeatPatternDim - /// - CFARepeatPatternDim = 0x828D, - - /// - /// CFAPattern2 - /// - CFAPattern2 = 0x828E, - - /// - /// BatteryLevel - /// - BatteryLevel = 0x828F, - - /// - /// Copyright notice. - /// See Section 8: Baseline Fields. - /// - Copyright = 0x8298, - - /// - /// ExposureTime - /// - ExposureTime = 0x829A, - - /// - /// FNumber - /// - FNumber = 0x829D, - - /// - /// Specifies the pixel data format encoding in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - [ExifTagDescription((ushort)2, "Squary root data format")] - [ExifTagDescription((ushort)128, "Linear data format")] - MDFileTag = 0x82A5, - - /// - /// Specifies a scale factor in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// The scale factor is to be applies to each pixel before presenting it to the user. - /// - MDScalePixel = 0x82A6, - - /// - /// Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// Since the display is only 9bit, the 16bit data must be converted before display. - /// 8bit value = (16bit value - low range ) * 255 / (high range - low range) - /// Count: n. - /// - [ExifTagDescription((ushort)0, "lowest possible")] - [ExifTagDescription((ushort)1, "low range")] - [ExifTagDescription("n-2", "high range")] - [ExifTagDescription("n-1", "highest possible")] - MDColorTable = 0x82A7, - - /// - /// Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - MDLabName = 0x82A8, - - /// - /// Information about the sample, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// This information is entered by the person that scanned the file. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDSampleInfo = 0x82A9, - - /// - /// Date the sample was prepared, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// The format of this data is YY/MM/DD. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDPrepDate = 0x82AA, - - /// - /// Time the sample was prepared, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// Format of this data is HH:MM using the 24-hour clock. - /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. - /// - MDPrepTime = 0x82AB, - - /// - /// Units for data in this file, as used in the Molecular Dynamics GEL file format. - /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html - /// - [ExifTagDescription("O.D.", "Densitometer")] - [ExifTagDescription("Counts", "PhosphorImager")] - [ExifTagDescription("RFU", "FluorImager")] - MDFileUnits = 0x82AC, - - /// - /// PixelScale - /// - PixelScale = 0x830E, - - /// - /// IPTC (International Press Telecommunications Council) metadata. - /// See IPTC 4.1 specification. - /// - IPTC = 0x83BB, - - /// - /// IntergraphPacketData - /// - IntergraphPacketData = 0x847E, - - /// - /// IntergraphRegisters - /// - IntergraphRegisters = 0x847F, - - /// - /// IntergraphMatrix - /// - IntergraphMatrix = 0x8480, - - /// - /// ModelTiePoint - /// - ModelTiePoint = 0x8482, - - /// - /// SEMInfo - /// - SEMInfo = 0x8546, - - /// - /// ModelTransform - /// - ModelTransform = 0x85D8, - - /// - /// Collection of Photoshop 'Image Resource Blocks' (Embedded Metadata). - /// See Extracting the Thumbnail from the PhotoShop private TIFF Tag: https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html - /// - Photoshop = 0x8649, - - /// - /// ICC profile data. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/iccprofile.html - /// - IccProfile = 0x8773, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html - /// This tag is also know as 'ProjectionInfoTag' and 'CoordSystemInfoTag' - /// This tag may be used to store the GeoKey Directory, which defines and references the "GeoKeys". - /// - GeoKeyDirectoryTag = 0x87AF, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html - /// This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE and stored here. - /// - GeoDoubleParamsTag = 0x87B0, - - /// - /// Used in interchangeable GeoTIFF files. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html - /// This tag is used to store all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag. Since keys use offsets into tags, any special comments may be placed at the beginning of this tag. For the most part, the only keys that are ASCII valued are "Citation" keys, giving documentation and references for obscure projections, datums, etc. - /// - GeoAsciiParamsTag = 0x87B1, - - /// - /// ImageLayer - /// - ImageLayer = 0x87AC, - - /// - /// ExposureProgram - /// - [ExifTagDescription((ushort)0, "Not Defined")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Program AE")] - [ExifTagDescription((ushort)3, "Aperture-priority AE")] - [ExifTagDescription((ushort)4, "Shutter speed priority AE")] - [ExifTagDescription((ushort)5, "Creative (Slow speed)")] - [ExifTagDescription((ushort)6, "Action (High speed)")] - [ExifTagDescription((ushort)7, "Portrait")] - [ExifTagDescription((ushort)8, "Landscape")] - [ExifTagDescription((ushort)9, "Bulb")] - ExposureProgram = 0x8822, - - /// - /// SpectralSensitivity - /// - SpectralSensitivity = 0x8824, - - /// - /// ISOSpeedRatings - /// - ISOSpeedRatings = 0x8827, - - /// - /// OECF - /// - OECF = 0x8828, - - /// - /// Interlace - /// - Interlace = 0x8829, - - /// - /// TimeZoneOffset - /// - TimeZoneOffset = 0x882A, - - /// - /// SelfTimerMode - /// - SelfTimerMode = 0x882B, - - /// - /// SensitivityType - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] - [ExifTagDescription((ushort)2, "Recommended Exposure Index")] - [ExifTagDescription((ushort)3, "ISO Speed")] - [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] - [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] - [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] - [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] - SensitivityType = 0x8830, - - /// - /// StandardOutputSensitivity - /// - StandardOutputSensitivity = 0x8831, - - /// - /// RecommendedExposureIndex - /// - RecommendedExposureIndex = 0x8832, - - /// - /// ISOSpeed - /// - ISOSpeed = 0x8833, - - /// - /// ISOSpeedLatitudeyyy - /// - ISOSpeedLatitudeyyy = 0x8834, - - /// - /// ISOSpeedLatitudezzz - /// - ISOSpeedLatitudezzz = 0x8835, - - /// - /// FaxRecvParams - /// - FaxRecvParams = 0x885C, - - /// - /// FaxSubaddress - /// - FaxSubaddress = 0x885D, - - /// - /// FaxRecvTime - /// - FaxRecvTime = 0x885E, - - /// - /// ExifVersion - /// - ExifVersion = 0x9000, - - /// - /// DateTimeOriginal - /// - DateTimeOriginal = 0x9003, - - /// - /// DateTimeDigitized - /// - DateTimeDigitized = 0x9004, - - /// - /// OffsetTime - /// - OffsetTime = 0x9010, - - /// - /// OffsetTimeOriginal - /// - OffsetTimeOriginal = 0x9011, - - /// - /// OffsetTimeDigitized - /// - OffsetTimeDigitized = 0x9012, - - /// - /// ComponentsConfiguration - /// - ComponentsConfiguration = 0x9101, - - /// - /// CompressedBitsPerPixel - /// - CompressedBitsPerPixel = 0x9102, - - /// - /// ShutterSpeedValue - /// - ShutterSpeedValue = 0x9201, - - /// - /// ApertureValue - /// - ApertureValue = 0x9202, - - /// - /// BrightnessValue - /// - BrightnessValue = 0x9203, - - /// - /// ExposureBiasValue - /// - ExposureBiasValue = 0x9204, - - /// - /// MaxApertureValue - /// - MaxApertureValue = 0x9205, - - /// - /// SubjectDistance - /// - SubjectDistance = 0x9206, - - /// - /// MeteringMode - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Average")] - [ExifTagDescription((ushort)2, "Center-weighted average")] - [ExifTagDescription((ushort)3, "Spot")] - [ExifTagDescription((ushort)4, "Multi-spot")] - [ExifTagDescription((ushort)5, "Multi-segment")] - [ExifTagDescription((ushort)6, "Partial")] - [ExifTagDescription((ushort)255, "Other")] - MeteringMode = 0x9207, - - /// - /// LightSource - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Daylight")] - [ExifTagDescription((ushort)2, "Fluorescent")] - [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] - [ExifTagDescription((ushort)4, "Flash")] - [ExifTagDescription((ushort)9, "Fine Weather")] - [ExifTagDescription((ushort)10, "Cloudy")] - [ExifTagDescription((ushort)11, "Shade")] - [ExifTagDescription((ushort)12, "Daylight Fluorescent")] - [ExifTagDescription((ushort)13, "Day White Fluorescent")] - [ExifTagDescription((ushort)14, "Cool White Fluorescent")] - [ExifTagDescription((ushort)15, "White Fluorescent")] - [ExifTagDescription((ushort)16, "Warm White Fluorescent")] - [ExifTagDescription((ushort)17, "Standard Light A")] - [ExifTagDescription((ushort)18, "Standard Light B")] - [ExifTagDescription((ushort)19, "Standard Light C")] - [ExifTagDescription((ushort)20, "D55")] - [ExifTagDescription((ushort)21, "D65")] - [ExifTagDescription((ushort)22, "D75")] - [ExifTagDescription((ushort)23, "D50")] - [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] - [ExifTagDescription((ushort)255, "Other")] - LightSource = 0x9208, - - /// - /// Flash - /// - [ExifTagDescription((ushort)0, "No Flash")] - [ExifTagDescription((ushort)1, "Fired")] - [ExifTagDescription((ushort)5, "Fired, Return not detected")] - [ExifTagDescription((ushort)7, "Fired, Return detected")] - [ExifTagDescription((ushort)8, "On, Did not fire")] - [ExifTagDescription((ushort)9, "On, Fired")] - [ExifTagDescription((ushort)13, "On, Return not detected")] - [ExifTagDescription((ushort)15, "On, Return detected")] - [ExifTagDescription((ushort)16, "Off, Did not fire")] - [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] - [ExifTagDescription((ushort)24, "Auto, Did not fire")] - [ExifTagDescription((ushort)25, "Auto, Fired")] - [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] - [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] - [ExifTagDescription((ushort)32, "No flash function")] - [ExifTagDescription((ushort)48, "Off, No flash function")] - [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] - [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)73, "On, Red-eye reduction")] - [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] - [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] - [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] - [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] - [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] - [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] - Flash = 0x9209, - - /// - /// FocalLength - /// - FocalLength = 0x920A, - - /// - /// FlashEnergy2 - /// - FlashEnergy2 = 0x920B, - - /// - /// SpatialFrequencyResponse2 - /// - SpatialFrequencyResponse2 = 0x920C, - - /// - /// Noise - /// - Noise = 0x920D, - - /// - /// FocalPlaneXResolution2 - /// - FocalPlaneXResolution2 = 0x920E, - - /// - /// FocalPlaneYResolution2 - /// - FocalPlaneYResolution2 = 0x920F, - - /// - /// FocalPlaneResolutionUnit2 - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit2 = 0x9210, - - /// - /// ImageNumber - /// - ImageNumber = 0x9211, - - /// - /// SecurityClassification - /// - [ExifTagDescription("C", "Confidential")] - [ExifTagDescription("R", "Restricted")] - [ExifTagDescription("S", "Secret")] - [ExifTagDescription("T", "Top Secret")] - [ExifTagDescription("U", "Unclassified")] - SecurityClassification = 0x9212, - - /// - /// ImageHistory - /// - ImageHistory = 0x9213, - - /// - /// SubjectArea - /// - SubjectArea = 0x9214, - - /// - /// ExposureIndex2 - /// - ExposureIndex2 = 0x9215, - - /// - /// TIFFEPStandardID - /// - TIFFEPStandardID = 0x9216, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod2 = 0x9217, - - /// - /// MakerNote - /// - MakerNote = 0x927C, - - /// - /// UserComment - /// - UserComment = 0x9286, - - /// - /// SubsecTime - /// - SubsecTime = 0x9290, - - /// - /// SubsecTimeOriginal - /// - SubsecTimeOriginal = 0x9291, - - /// - /// SubsecTimeDigitized - /// - SubsecTimeDigitized = 0x9292, - - /// - /// ImageSourceData - /// - ImageSourceData = 0x935C, - - /// - /// AmbientTemperature - /// - AmbientTemperature = 0x9400, - - /// - /// Humidity - /// - Humidity = 0x9401, - - /// - /// Pressure - /// - Pressure = 0x9402, - - /// - /// WaterDepth - /// - WaterDepth = 0x9403, - - /// - /// Acceleration - /// - Acceleration = 0x9404, - - /// - /// CameraElevationAngle - /// - CameraElevationAngle = 0x9405, - - /// - /// XPTitle - /// - XPTitle = 0x9C9B, - - /// - /// XPComment - /// - XPComment = 0x9C9C, - - /// - /// XPAuthor - /// - XPAuthor = 0x9C9D, - - /// - /// XPKeywords - /// - XPKeywords = 0x9C9E, - - /// - /// XPSubject - /// - XPSubject = 0x9C9F, - - /// - /// FlashpixVersion - /// - FlashpixVersion = 0xA000, - - /// - /// ColorSpace - /// - [ExifTagDescription((ushort)1, "sRGB")] - [ExifTagDescription((ushort)2, "Adobe RGB")] - [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] - [ExifTagDescription((ushort)65534, "ICC Profile")] - [ExifTagDescription((ushort)65535, "Uncalibrated")] - ColorSpace = 0xA001, - - /// - /// PixelXDimension - /// - PixelXDimension = 0xA002, - - /// - /// PixelYDimension - /// - PixelYDimension = 0xA003, - - /// - /// RelatedSoundFile - /// - RelatedSoundFile = 0xA004, - - /// - /// A pointer to the Exif-related Interoperability IFD. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability.html - /// Interoperability IFD is composed of tags which stores the information to ensure the Interoperability. - /// The Interoperability structure of Interoperability IFD is same as TIFF defined IFD structure but does not contain the image data characteristically compared with normal TIFF IFD. - /// - InteroperabilityIFD = 0xA005, - - /// - /// FlashEnergy - /// - FlashEnergy = 0xA20B, - - /// - /// SpatialFrequencyResponse - /// - SpatialFrequencyResponse = 0xA20C, - - /// - /// FocalPlaneXResolution - /// - FocalPlaneXResolution = 0xA20E, - - /// - /// FocalPlaneYResolution - /// - FocalPlaneYResolution = 0xA20F, - - /// - /// FocalPlaneResolutionUnit - /// - [ExifTagDescription((ushort)1, "None")] - [ExifTagDescription((ushort)2, "Inches")] - [ExifTagDescription((ushort)3, "Centimeter")] - [ExifTagDescription((ushort)4, "Millimeter")] - [ExifTagDescription((ushort)5, "Micrometer")] - FocalPlaneResolutionUnit = 0xA210, - - /// - /// SubjectLocation - /// - SubjectLocation = 0xA214, - - /// - /// ExposureIndex - /// - ExposureIndex = 0xA215, - - /// - /// SensingMethod - /// - [ExifTagDescription((ushort)1, "Not defined")] - [ExifTagDescription((ushort)2, "One-chip color area")] - [ExifTagDescription((ushort)3, "Two-chip color area")] - [ExifTagDescription((ushort)4, "Three-chip color area")] - [ExifTagDescription((ushort)5, "Color sequential area")] - [ExifTagDescription((ushort)7, "Trilinear")] - [ExifTagDescription((ushort)8, "Color sequential linear")] - SensingMethod = 0xA217, - - /// - /// FileSource - /// - FileSource = 0xA300, - - /// - /// SceneType - /// - SceneType = 0xA301, - - /// - /// CFAPattern - /// - CFAPattern = 0xA302, - - /// - /// CustomRendered - /// - [ExifTagDescription((ushort)1, "Normal")] - [ExifTagDescription((ushort)2, "Custom")] - CustomRendered = 0xA401, - - /// - /// ExposureMode - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - [ExifTagDescription((ushort)2, "Auto bracket")] - ExposureMode = 0xA402, - - /// - /// WhiteBalance - /// - [ExifTagDescription((ushort)0, "Auto")] - [ExifTagDescription((ushort)1, "Manual")] - WhiteBalance = 0xA403, - - /// - /// DigitalZoomRatio - /// - DigitalZoomRatio = 0xA404, - - /// - /// FocalLengthIn35mmFilm - /// - FocalLengthIn35mmFilm = 0xA405, - - /// - /// SceneCaptureType - /// - [ExifTagDescription((ushort)0, "Standard")] - [ExifTagDescription((ushort)1, "Landscape")] - [ExifTagDescription((ushort)2, "Portrait")] - [ExifTagDescription((ushort)3, "Night")] - SceneCaptureType = 0xA406, - - /// - /// GainControl - /// - [ExifTagDescription((ushort)0, "None")] - [ExifTagDescription((ushort)1, "Low gain up")] - [ExifTagDescription((ushort)2, "High gain up")] - [ExifTagDescription((ushort)3, "Low gain down")] - [ExifTagDescription((ushort)4, "High gain down")] - GainControl = 0xA407, - - /// - /// Contrast - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Contrast = 0xA408, - - /// - /// Saturation - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Low")] - [ExifTagDescription((ushort)2, "High")] - Saturation = 0xA409, - - /// - /// Sharpness - /// - [ExifTagDescription((ushort)0, "Normal")] - [ExifTagDescription((ushort)1, "Soft")] - [ExifTagDescription((ushort)2, "Hard")] - Sharpness = 0xA40A, - - /// - /// DeviceSettingDescription - /// - DeviceSettingDescription = 0xA40B, - - /// - /// SubjectDistanceRange - /// - [ExifTagDescription((ushort)0, "Unknown")] - [ExifTagDescription((ushort)1, "Macro")] - [ExifTagDescription((ushort)2, "Close")] - [ExifTagDescription((ushort)3, "Distant")] - SubjectDistanceRange = 0xA40C, - - /// - /// ImageUniqueID - /// - ImageUniqueID = 0xA420, - - /// - /// OwnerName - /// - OwnerName = 0xA430, - - /// - /// SerialNumber - /// - SerialNumber = 0xA431, - - /// - /// LensSpecification - /// - LensSpecification = 0xA432, - - /// - /// LensMake - /// - LensMake = 0xA433, - - /// - /// LensModel - /// - LensModel = 0xA434, - - /// - /// LensSerialNumber - /// - LensSerialNumber = 0xA435, - - /// - /// GDALMetadata - /// - GDALMetadata = 0xA480, - - /// - /// GDALNoData - /// - GDALNoData = 0xA481, - - /// - /// GPSVersionID - /// - GPSVersionID = 0x0000, - - /// - /// GPSLatitudeRef - /// - GPSLatitudeRef = 0x0001, - - /// - /// GPSLatitude - /// - GPSLatitude = 0x0002, - - /// - /// GPSLongitudeRef - /// - GPSLongitudeRef = 0x0003, - - /// - /// GPSLongitude - /// - GPSLongitude = 0x0004, - - /// - /// GPSAltitudeRef - /// - GPSAltitudeRef = 0x0005, - - /// - /// GPSAltitude - /// - GPSAltitude = 0x0006, - - /// - /// GPSTimestamp - /// - GPSTimestamp = 0x0007, - - /// - /// GPSSatellites - /// - GPSSatellites = 0x0008, - - /// - /// GPSStatus - /// - GPSStatus = 0x0009, - - /// - /// GPSMeasureMode - /// - GPSMeasureMode = 0x000A, - - /// - /// GPSDOP - /// - GPSDOP = 0x000B, - - /// - /// GPSSpeedRef - /// - GPSSpeedRef = 0x000C, - - /// - /// GPSSpeed - /// - GPSSpeed = 0x000D, - - /// - /// GPSTrackRef - /// - GPSTrackRef = 0x000E, - - /// - /// GPSTrack - /// - GPSTrack = 0x000F, - - /// - /// GPSImgDirectionRef - /// - GPSImgDirectionRef = 0x0010, - - /// - /// GPSImgDirection - /// - GPSImgDirection = 0x0011, - - /// - /// GPSMapDatum - /// - GPSMapDatum = 0x0012, - - /// - /// GPSDestLatitudeRef - /// - GPSDestLatitudeRef = 0x0013, - - /// - /// GPSDestLatitude - /// - GPSDestLatitude = 0x0014, - - /// - /// GPSDestLongitudeRef - /// - GPSDestLongitudeRef = 0x0015, - - /// - /// GPSDestLongitude - /// - GPSDestLongitude = 0x0016, - - /// - /// GPSDestBearingRef - /// - GPSDestBearingRef = 0x0017, - - /// - /// GPSDestBearing - /// - GPSDestBearing = 0x0018, - - /// - /// GPSDestDistanceRef - /// - GPSDestDistanceRef = 0x0019, - - /// - /// GPSDestDistance - /// - GPSDestDistance = 0x001A, - - /// - /// GPSProcessingMethod - /// - GPSProcessingMethod = 0x001B, - - /// - /// GPSAreaInformation - /// - GPSAreaInformation = 0x001C, - - /// - /// GPSDateStamp - /// - GPSDateStamp = 0x001D, - - /// - /// GPSDifferential - /// - GPSDifferential = 0x001E, - - /// - /// Used in the Oce scanning process. - /// Identifies the scanticket used in the scanning process. - /// Includes a trailing zero. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceScanjobDescription = 0xC427, - - /// - /// Used in the Oce scanning process. - /// Identifies the application to process the TIFF file that results from scanning. - /// Includes a trailing zero. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceApplicationSelector = 0xC428, - - /// - /// Used in the Oce scanning process. - /// This is the user's answer to an optional question embedded in the Oce scanticket, and presented to that user before scanning. It can serve in further determination of the workflow. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceIdentificationNumber = 0xC429, - - /// - /// Used in the Oce scanning process. - /// This tag encodes the imageprocessing done by the Oce ImageLogic module in the scanner to ensure optimal quality for certain workflows. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html - /// - OceImageLogicCharacteristics = 0xC42A, - - /// - /// Alias Sketchbook Pro layer usage description. - /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/alias.html - /// - AliasLayerMetadata = 0xC660, - } + /// Unknown + /// + Unknown = 0xFFFF, + + /// + /// SubIFDOffset + /// + SubIFDOffset = 0x8769, + + /// + /// GPSIFDOffset + /// + GPSIFDOffset = 0x8825, + + /// + /// Indicates the identification of the Interoperability rule. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability/interoperabilityindex.html + /// + [ExifTagDescription("R98", "Indicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.")] + [ExifTagDescription("THM", "Indicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.")] + InteroperabilityIndex = 0x0001, + + /// + /// A general indication of the kind of data contained in this subfile. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription(0U, "Full-resolution Image")] + [ExifTagDescription(1U, "Reduced-resolution image")] + [ExifTagDescription(2U, "Single page of multi-page image")] + [ExifTagDescription(3U, "Single page of multi-page reduced-resolution image")] + [ExifTagDescription(4U, "Transparency mask")] + [ExifTagDescription(5U, "Transparency mask of reduced-resolution image")] + [ExifTagDescription(6U, "Transparency mask of multi-page image")] + [ExifTagDescription(7U, "Transparency mask of reduced-resolution multi-page image")] + [ExifTagDescription(0x10001U, "Alternate reduced-resolution image ")] + SubfileType = 0x00FE, + + /// + /// A general indication of the kind of data contained in this subfile. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "Full-resolution Image")] + [ExifTagDescription((ushort)2, "Reduced-resolution image")] + [ExifTagDescription((ushort)3, "Single page of multi-page image")] + OldSubfileType = 0x00FF, + + /// + /// The number of columns in the image, i.e., the number of pixels per row. + /// See Section 8: Baseline Fields. + /// + ImageWidth = 0x0100, + + /// + /// The number of rows of pixels in the image. + /// See Section 8: Baseline Fields. + /// + ImageLength = 0x0101, + + /// + /// Number of bits per component. + /// See Section 8: Baseline Fields. + /// + BitsPerSample = 0x0102, + + /// + /// Compression scheme used on the image data. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "Uncompressed")] + [ExifTagDescription((ushort)2, "CCITT 1D")] + [ExifTagDescription((ushort)3, "T4/Group 3 Fax")] + [ExifTagDescription((ushort)4, "T6/Group 4 Fax")] + [ExifTagDescription((ushort)5, "LZW")] + [ExifTagDescription((ushort)6, "JPEG (old-style)")] + [ExifTagDescription((ushort)7, "JPEG")] + [ExifTagDescription((ushort)8, "Adobe Deflate")] + [ExifTagDescription((ushort)9, "JBIG B&W")] + [ExifTagDescription((ushort)10, "JBIG Color")] + [ExifTagDescription((ushort)99, "JPEG")] + [ExifTagDescription((ushort)262, "Kodak 262")] + [ExifTagDescription((ushort)32766, "Next")] + [ExifTagDescription((ushort)32767, "Sony ARW Compressed")] + [ExifTagDescription((ushort)32769, "Packed RAW")] + [ExifTagDescription((ushort)32770, "Samsung SRW Compressed")] + [ExifTagDescription((ushort)32771, "CCIRLEW")] + [ExifTagDescription((ushort)32772, "Samsung SRW Compressed 2")] + [ExifTagDescription((ushort)32773, "PackBits")] + [ExifTagDescription((ushort)32809, "Thunderscan")] + [ExifTagDescription((ushort)32867, "Kodak KDC Compressed")] + [ExifTagDescription((ushort)32895, "IT8CTPAD")] + [ExifTagDescription((ushort)32896, "IT8LW")] + [ExifTagDescription((ushort)32897, "IT8MP")] + [ExifTagDescription((ushort)32898, "IT8BL")] + [ExifTagDescription((ushort)32908, "PixarFilm")] + [ExifTagDescription((ushort)32909, "PixarLog")] + [ExifTagDescription((ushort)32946, "Deflate")] + [ExifTagDescription((ushort)32947, "DCS")] + [ExifTagDescription((ushort)34661, "JBIG")] + [ExifTagDescription((ushort)34676, "SGILog")] + [ExifTagDescription((ushort)34677, "SGILog24")] + [ExifTagDescription((ushort)34712, "JPEG 2000")] + [ExifTagDescription((ushort)34713, "Nikon NEF Compressed")] + [ExifTagDescription((ushort)34715, "JBIG2 TIFF FX")] + [ExifTagDescription((ushort)34718, "Microsoft Document Imaging (MDI) Binary Level Codec")] + [ExifTagDescription((ushort)34719, "Microsoft Document Imaging (MDI) Progressive Transform Codec")] + [ExifTagDescription((ushort)34720, "Microsoft Document Imaging (MDI) Vector")] + [ExifTagDescription((ushort)34892, "Lossy JPEG")] + [ExifTagDescription((ushort)65000, "Kodak DCR Compressed")] + [ExifTagDescription((ushort)65535, "Pentax PEF Compressed")] + Compression = 0x0103, + + /// + /// The color space of the image data. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)0, "WhiteIsZero")] + [ExifTagDescription((ushort)1, "BlackIsZero")] + [ExifTagDescription((ushort)2, "RGB")] + [ExifTagDescription((ushort)3, "RGB Palette")] + [ExifTagDescription((ushort)4, "Transparency Mask")] + [ExifTagDescription((ushort)5, "CMYK")] + [ExifTagDescription((ushort)6, "YCbCr")] + [ExifTagDescription((ushort)8, "CIELab")] + [ExifTagDescription((ushort)9, "ICCLab")] + [ExifTagDescription((ushort)10, "TULab")] + [ExifTagDescription((ushort)32803, "Color Filter Array")] + [ExifTagDescription((ushort)32844, "Pixar LogL")] + [ExifTagDescription((ushort)32845, "Pixar LogLuv")] + [ExifTagDescription((ushort)34892, "Linear Raw")] + PhotometricInterpretation = 0x0106, + + /// + /// For black and white TIFF files that represent shades of gray, the technique used to convert from gray to black and white pixels. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "No dithering or halftoning")] + [ExifTagDescription((ushort)2, "Ordered dither or halftone")] + [ExifTagDescription((ushort)3, "Randomized dither")] + Thresholding = 0x0107, + + /// + /// The width of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. + /// See Section 8: Baseline Fields. + /// + CellWidth = 0x0108, + + /// + /// The length of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file. + /// See Section 8: Baseline Fields. + /// + CellLength = 0x0109, + + /// + /// The logical order of bits within a byte. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Reversed")] + FillOrder = 0x010A, + + /// + /// The name of the document from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. + /// + DocumentName = 0x010D, + + /// + /// A string that describes the subject of the image. + /// See Section 8: Baseline Fields. + /// + ImageDescription = 0x010E, + + /// + /// The scanner manufacturer. + /// See Section 8: Baseline Fields. + /// + Make = 0x010F, + + /// + /// The scanner model name or number. + /// See Section 8: Baseline Fields. + /// + Model = 0x0110, + + /// + /// For each strip, the byte offset of that strip. + /// See Section 8: Baseline Fields. + /// + StripOffsets = 0x0111, + + /// + /// The orientation of the image with respect to the rows and columns. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "Horizontal (normal)")] + [ExifTagDescription((ushort)2, "Mirror horizontal")] + [ExifTagDescription((ushort)3, "Rotate 180")] + [ExifTagDescription((ushort)4, "Mirror vertical")] + [ExifTagDescription((ushort)5, "Mirror horizontal and rotate 270 CW")] + [ExifTagDescription((ushort)6, "Rotate 90 CW")] + [ExifTagDescription((ushort)7, "Mirror horizontal and rotate 90 CW")] + [ExifTagDescription((ushort)8, "Rotate 270 CW")] + Orientation = 0x0112, + + /// + /// The number of components per pixel. + /// See Section 8: Baseline Fields. + /// + SamplesPerPixel = 0x0115, + + /// + /// The number of rows per strip. + /// See Section 8: Baseline Fields. + /// + RowsPerStrip = 0x0116, + + /// + /// For each strip, the number of bytes in the strip after compression. + /// See Section 8: Baseline Fields. + /// + StripByteCounts = 0x0117, + + /// + /// The minimum component value used. + /// See Section 8: Baseline Fields. + /// + MinSampleValue = 0x0118, + + /// + /// The maximum component value used. + /// See Section 8: Baseline Fields. + /// + MaxSampleValue = 0x0119, + + /// + /// The number of pixels per ResolutionUnit in the ImageWidth direction. + /// See Section 8: Baseline Fields. + /// + XResolution = 0x011A, + + /// + /// The number of pixels per ResolutionUnit in the direction. + /// See Section 8: Baseline Fields. + /// + YResolution = 0x011B, + + /// + /// How the components of each pixel are stored. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "Chunky")] + [ExifTagDescription((ushort)2, "Planar")] + PlanarConfiguration = 0x011C, + + /// + /// The name of the page from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. + /// + PageName = 0x011D, + + /// + /// X position of the image. + /// See Section 12: Document Storage and Retrieval. + /// + XPosition = 0x011E, + + /// + /// Y position of the image. + /// See Section 12: Document Storage and Retrieval. + /// + YPosition = 0x011F, + + /// + /// For each string of contiguous unused bytes in a TIFF file, the byte offset of the string. + /// See Section 8: Baseline Fields. + /// + FreeOffsets = 0x0120, + + /// + /// For each string of contiguous unused bytes in a TIFF file, the number of bytes in the string. + /// See Section 8: Baseline Fields. + /// + FreeByteCounts = 0x0121, + + /// + /// The precision of the information contained in the GrayResponseCurve. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "0.1")] + [ExifTagDescription((ushort)2, "0.001")] + [ExifTagDescription((ushort)3, "0.0001")] + [ExifTagDescription((ushort)4, "1e-05")] + [ExifTagDescription((ushort)5, "1e-06")] + GrayResponseUnit = 0x0122, + + /// + /// For grayscale data, the optical density of each possible pixel value. + /// See Section 8: Baseline Fields. + /// + GrayResponseCurve = 0x0123, + + /// + /// Options for Group 3 Fax compression. + /// + [ExifTagDescription(0U, "2-Dimensional encoding")] + [ExifTagDescription(1U, "Uncompressed")] + [ExifTagDescription(2U, "Fill bits added")] + T4Options = 0x0124, + + /// + /// Options for Group 4 Fax compression. + /// + [ExifTagDescription(1U, "Uncompressed")] + T6Options = 0x0125, + + /// + /// The unit of measurement for XResolution and YResolution. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + ResolutionUnit = 0x0128, + + /// + /// The page number of the page from which this image was scanned. + /// See Section 12: Document Storage and Retrieval. + /// + PageNumber = 0x0129, + + /// + /// ColorResponseUnit + /// + ColorResponseUnit = 0x012C, + + /// + /// TransferFunction + /// + TransferFunction = 0x012D, + + /// + /// Name and version number of the software package(s) used to create the image. + /// See Section 8: Baseline Fields. + /// + Software = 0x0131, + + /// + /// Date and time of image creation. + /// See Section 8: Baseline Fields. + /// + DateTime = 0x0132, + + /// + /// Person who created the image. + /// See Section 8: Baseline Fields. + /// + Artist = 0x013B, + + /// + /// The computer and/or operating system in use at the time of image creation. + /// See Section 8: Baseline Fields. + /// + HostComputer = 0x013C, + + /// + /// Predictor + /// + Predictor = 0x013D, + + /// + /// WhitePoint + /// + WhitePoint = 0x013E, + + /// + /// PrimaryChromaticities + /// + PrimaryChromaticities = 0x013F, + + /// + /// A color map for palette color images. + /// See Section 8: Baseline Fields. + /// + ColorMap = 0x0140, + + /// + /// HalftoneHints + /// + HalftoneHints = 0x0141, + + /// + /// TileWidth + /// + TileWidth = 0x0142, + + /// + /// TileLength + /// + TileLength = 0x0143, + + /// + /// TileOffsets + /// + TileOffsets = 0x0144, + + /// + /// TileByteCounts + /// + TileByteCounts = 0x0145, + + /// + /// BadFaxLines + /// + BadFaxLines = 0x0146, + + /// + /// CleanFaxData + /// + [ExifTagDescription(0U, "Clean")] + [ExifTagDescription(1U, "Regenerated")] + [ExifTagDescription(2U, "Unclean")] + CleanFaxData = 0x0147, + + /// + /// ConsecutiveBadFaxLines + /// + ConsecutiveBadFaxLines = 0x0148, + + /// + /// Offset to child IFDs. + /// See TIFF Supplement 1: Adobe Pagemaker 6.0. + /// Each value is an offset (from the beginning of the TIFF file, as always) to a child IFD. Child images provide extra information for the parent image - such as a subsampled version of the parent image. + /// TIFF data type is Long or 13, IFD. The IFD type is identical to LONG, except that it is only used to point to other valid IFDs. + /// + SubIFDs = 0x014A, + + /// + /// InkSet + /// + [ExifTagDescription((ushort)1, "CMYK")] + [ExifTagDescription((ushort)2, "Not CMYK")] + InkSet = 0x014C, + + /// + /// InkNames + /// + InkNames = 0x014D, + + /// + /// NumberOfInks + /// + NumberOfInks = 0x014E, + + /// + /// DotRange + /// + DotRange = 0x0150, + + /// + /// TargetPrinter + /// + TargetPrinter = 0x0151, + + /// + /// Description of extra components. + /// See Section 8: Baseline Fields. + /// + [ExifTagDescription((ushort)0, "Unspecified")] + [ExifTagDescription((ushort)1, "Associated Alpha")] + [ExifTagDescription((ushort)2, "Unassociated Alpha")] + ExtraSamples = 0x0152, + + /// + /// SampleFormat + /// + [ExifTagDescription((ushort)1, "Unsigned")] + [ExifTagDescription((ushort)2, "Signed")] + [ExifTagDescription((ushort)3, "Float")] + [ExifTagDescription((ushort)4, "Undefined")] + [ExifTagDescription((ushort)5, "Complex int")] + [ExifTagDescription((ushort)6, "Complex float")] + SampleFormat = 0x0153, + + /// + /// SMinSampleValue + /// + SMinSampleValue = 0x0154, + + /// + /// SMaxSampleValue + /// + SMaxSampleValue = 0x0155, + + /// + /// TransferRange + /// + TransferRange = 0x0156, + + /// + /// ClipPath + /// + ClipPath = 0x0157, + + /// + /// XClipPathUnits + /// + XClipPathUnits = 0x0158, + + /// + /// YClipPathUnits + /// + YClipPathUnits = 0x0159, + + /// + /// Indexed + /// + [ExifTagDescription((ushort)0, "Not indexed")] + [ExifTagDescription((ushort)1, "Indexed")] + Indexed = 0x015A, + + /// + /// JPEGTables + /// + JPEGTables = 0x015B, + + /// + /// OPIProxy + /// + [ExifTagDescription((ushort)0, "Higher resolution image does not exist")] + [ExifTagDescription((ushort)1, "Higher resolution image exists")] + OPIProxy = 0x015F, + + /// + /// Used in the TIFF-FX standard to point to an IFD containing tags that are globally applicable to the complete TIFF file. + /// See RFC2301: TIFF-F/FX Specification. + /// It is recommended that a TIFF writer place this field in the first IFD, where a TIFF reader would find it quickly. + /// Each field in the GlobalParametersIFD is a TIFF field that is legal in any IFD. Required baseline fields should not be located in the GlobalParametersIFD, but should be in each image IFD. If a conflict exists between fields in the GlobalParametersIFD and in the image IFDs, then the data in the image IFD shall prevail. + /// + GlobalParametersIFD = 0x0190, + + /// + /// ProfileType + /// + [ExifTagDescription(0U, "Unspecified")] + [ExifTagDescription(1U, "Group 3 FAX")] + ProfileType = 0x0191, + + /// + /// FaxProfile + /// + [ExifTagDescription((byte)0, "Unknown")] + [ExifTagDescription((byte)1, "Minimal B&W lossless, S")] + [ExifTagDescription((byte)2, "Extended B&W lossless, F")] + [ExifTagDescription((byte)3, "Lossless JBIG B&W, J")] + [ExifTagDescription((byte)4, "Lossy color and grayscale, C")] + [ExifTagDescription((byte)5, "Lossless color and grayscale, L")] + [ExifTagDescription((byte)6, "Mixed raster content, M")] + [ExifTagDescription((byte)7, "Profile T")] + [ExifTagDescription((byte)255, "Multi Profiles")] + FaxProfile = 0x0192, + + /// + /// CodingMethods + /// + [ExifTagDescription(0UL, "Unspecified compression")] + [ExifTagDescription(1UL, "Modified Huffman")] + [ExifTagDescription(2UL, "Modified Read")] + [ExifTagDescription(4UL, "Modified MR")] + [ExifTagDescription(8UL, "JBIG")] + [ExifTagDescription(16UL, "Baseline JPEG")] + [ExifTagDescription(32UL, "JBIG color")] + CodingMethods = 0x0193, + + /// + /// VersionYear + /// + VersionYear = 0x0194, + + /// + /// ModeNumber + /// + ModeNumber = 0x0195, + + /// + /// Decode + /// + Decode = 0x01B1, + + /// + /// DefaultImageColor + /// + DefaultImageColor = 0x01B2, + + /// + /// T82ptions + /// + T82ptions = 0x01B3, + + /// + /// JPEGProc + /// + [ExifTagDescription((ushort)1, "Baseline")] + [ExifTagDescription((ushort)14, "Lossless")] + JPEGProc = 0x0200, + + /// + /// JPEGInterchangeFormat + /// + JPEGInterchangeFormat = 0x0201, + + /// + /// JPEGInterchangeFormatLength + /// + JPEGInterchangeFormatLength = 0x0202, + + /// + /// JPEGRestartInterval + /// + JPEGRestartInterval = 0x0203, + + /// + /// JPEGLosslessPredictors + /// + JPEGLosslessPredictors = 0x0205, + + /// + /// JPEGPointTransforms + /// + JPEGPointTransforms = 0x0206, + + /// + /// JPEGQTables + /// + JPEGQTables = 0x0207, + + /// + /// JPEGDCTables + /// + JPEGDCTables = 0x0208, + + /// + /// JPEGACTables + /// + JPEGACTables = 0x0209, + + /// + /// YCbCrCoefficients + /// + YCbCrCoefficients = 0x0211, + + /// + /// YCbCrSubsampling + /// + YCbCrSubsampling = 0x0212, + + /// + /// YCbCrPositioning + /// + [ExifTagDescription((ushort)1, "Centered")] + [ExifTagDescription((ushort)2, "Co-sited")] + YCbCrPositioning = 0x0213, + + /// + /// ReferenceBlackWhite + /// + ReferenceBlackWhite = 0x0214, + + /// + /// StripRowCounts + /// + StripRowCounts = 0x022F, + + /// + /// XMP + /// + XMP = 0x02BC, + + /// + /// Rating + /// + Rating = 0x4746, + + /// + /// RatingPercent + /// + RatingPercent = 0x4749, + + /// + /// ImageID + /// + ImageID = 0x800D, + + /// + /// Annotation data, as used in 'Imaging for Windows'. + /// See Other Private TIFF tags: http://www.awaresystems.be/imaging/tiff/tifftags/private.html + /// + WangAnnotation = 0x80A4, + + /// + /// CFARepeatPatternDim + /// + CFARepeatPatternDim = 0x828D, + + /// + /// CFAPattern2 + /// + CFAPattern2 = 0x828E, + + /// + /// BatteryLevel + /// + BatteryLevel = 0x828F, + + /// + /// Copyright notice. + /// See Section 8: Baseline Fields. + /// + Copyright = 0x8298, + + /// + /// ExposureTime + /// + ExposureTime = 0x829A, + + /// + /// FNumber + /// + FNumber = 0x829D, + + /// + /// Specifies the pixel data format encoding in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// + [ExifTagDescription((ushort)2, "Squary root data format")] + [ExifTagDescription((ushort)128, "Linear data format")] + MDFileTag = 0x82A5, + + /// + /// Specifies a scale factor in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// The scale factor is to be applies to each pixel before presenting it to the user. + /// + MDScalePixel = 0x82A6, + + /// + /// Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// Since the display is only 9bit, the 16bit data must be converted before display. + /// 8bit value = (16bit value - low range ) * 255 / (high range - low range) + /// Count: n. + /// + [ExifTagDescription((ushort)0, "lowest possible")] + [ExifTagDescription((ushort)1, "low range")] + [ExifTagDescription("n-2", "high range")] + [ExifTagDescription("n-1", "highest possible")] + MDColorTable = 0x82A7, + + /// + /// Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// + MDLabName = 0x82A8, + + /// + /// Information about the sample, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// This information is entered by the person that scanned the file. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. + /// + MDSampleInfo = 0x82A9, + + /// + /// Date the sample was prepared, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// The format of this data is YY/MM/DD. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. + /// + MDPrepDate = 0x82AA, + + /// + /// Time the sample was prepared, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// Format of this data is HH:MM using the 24-hour clock. + /// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel. + /// + MDPrepTime = 0x82AB, + + /// + /// Units for data in this file, as used in the Molecular Dynamics GEL file format. + /// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html + /// + [ExifTagDescription("O.D.", "Densitometer")] + [ExifTagDescription("Counts", "PhosphorImager")] + [ExifTagDescription("RFU", "FluorImager")] + MDFileUnits = 0x82AC, + + /// + /// PixelScale + /// + PixelScale = 0x830E, + + /// + /// IPTC (International Press Telecommunications Council) metadata. + /// See IPTC 4.1 specification. + /// + IPTC = 0x83BB, + + /// + /// IntergraphPacketData + /// + IntergraphPacketData = 0x847E, + + /// + /// IntergraphRegisters + /// + IntergraphRegisters = 0x847F, + + /// + /// IntergraphMatrix + /// + IntergraphMatrix = 0x8480, + + /// + /// ModelTiePoint + /// + ModelTiePoint = 0x8482, + + /// + /// SEMInfo + /// + SEMInfo = 0x8546, + + /// + /// ModelTransform + /// + ModelTransform = 0x85D8, + + /// + /// Collection of Photoshop 'Image Resource Blocks' (Embedded Metadata). + /// See Extracting the Thumbnail from the PhotoShop private TIFF Tag: https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html + /// + Photoshop = 0x8649, + + /// + /// ICC profile data. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/iccprofile.html + /// + IccProfile = 0x8773, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html + /// This tag is also know as 'ProjectionInfoTag' and 'CoordSystemInfoTag' + /// This tag may be used to store the GeoKey Directory, which defines and references the "GeoKeys". + /// + GeoKeyDirectoryTag = 0x87AF, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html + /// This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE and stored here. + /// + GeoDoubleParamsTag = 0x87B0, + + /// + /// Used in interchangeable GeoTIFF files. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html + /// This tag is used to store all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag. Since keys use offsets into tags, any special comments may be placed at the beginning of this tag. For the most part, the only keys that are ASCII valued are "Citation" keys, giving documentation and references for obscure projections, datums, etc. + /// + GeoAsciiParamsTag = 0x87B1, + + /// + /// ImageLayer + /// + ImageLayer = 0x87AC, + + /// + /// ExposureProgram + /// + [ExifTagDescription((ushort)0, "Not Defined")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Program AE")] + [ExifTagDescription((ushort)3, "Aperture-priority AE")] + [ExifTagDescription((ushort)4, "Shutter speed priority AE")] + [ExifTagDescription((ushort)5, "Creative (Slow speed)")] + [ExifTagDescription((ushort)6, "Action (High speed)")] + [ExifTagDescription((ushort)7, "Portrait")] + [ExifTagDescription((ushort)8, "Landscape")] + [ExifTagDescription((ushort)9, "Bulb")] + ExposureProgram = 0x8822, + + /// + /// SpectralSensitivity + /// + SpectralSensitivity = 0x8824, + + /// + /// ISOSpeedRatings + /// + ISOSpeedRatings = 0x8827, + + /// + /// OECF + /// + OECF = 0x8828, + + /// + /// Interlace + /// + Interlace = 0x8829, + + /// + /// TimeZoneOffset + /// + TimeZoneOffset = 0x882A, + + /// + /// SelfTimerMode + /// + SelfTimerMode = 0x882B, + + /// + /// SensitivityType + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Standard Output Sensitivity")] + [ExifTagDescription((ushort)2, "Recommended Exposure Index")] + [ExifTagDescription((ushort)3, "ISO Speed")] + [ExifTagDescription((ushort)4, "Standard Output Sensitivity and Recommended Exposure Index")] + [ExifTagDescription((ushort)5, "Standard Output Sensitivity and ISO Speed")] + [ExifTagDescription((ushort)6, "Recommended Exposure Index and ISO Speed")] + [ExifTagDescription((ushort)7, "Standard Output Sensitivity, Recommended Exposure Index and ISO Speed")] + SensitivityType = 0x8830, + + /// + /// StandardOutputSensitivity + /// + StandardOutputSensitivity = 0x8831, + + /// + /// RecommendedExposureIndex + /// + RecommendedExposureIndex = 0x8832, + + /// + /// ISOSpeed + /// + ISOSpeed = 0x8833, + + /// + /// ISOSpeedLatitudeyyy + /// + ISOSpeedLatitudeyyy = 0x8834, + + /// + /// ISOSpeedLatitudezzz + /// + ISOSpeedLatitudezzz = 0x8835, + + /// + /// FaxRecvParams + /// + FaxRecvParams = 0x885C, + + /// + /// FaxSubaddress + /// + FaxSubaddress = 0x885D, + + /// + /// FaxRecvTime + /// + FaxRecvTime = 0x885E, + + /// + /// ExifVersion + /// + ExifVersion = 0x9000, + + /// + /// DateTimeOriginal + /// + DateTimeOriginal = 0x9003, + + /// + /// DateTimeDigitized + /// + DateTimeDigitized = 0x9004, + + /// + /// OffsetTime + /// + OffsetTime = 0x9010, + + /// + /// OffsetTimeOriginal + /// + OffsetTimeOriginal = 0x9011, + + /// + /// OffsetTimeDigitized + /// + OffsetTimeDigitized = 0x9012, + + /// + /// ComponentsConfiguration + /// + ComponentsConfiguration = 0x9101, + + /// + /// CompressedBitsPerPixel + /// + CompressedBitsPerPixel = 0x9102, + + /// + /// ShutterSpeedValue + /// + ShutterSpeedValue = 0x9201, + + /// + /// ApertureValue + /// + ApertureValue = 0x9202, + + /// + /// BrightnessValue + /// + BrightnessValue = 0x9203, + + /// + /// ExposureBiasValue + /// + ExposureBiasValue = 0x9204, + + /// + /// MaxApertureValue + /// + MaxApertureValue = 0x9205, + + /// + /// SubjectDistance + /// + SubjectDistance = 0x9206, + + /// + /// MeteringMode + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Average")] + [ExifTagDescription((ushort)2, "Center-weighted average")] + [ExifTagDescription((ushort)3, "Spot")] + [ExifTagDescription((ushort)4, "Multi-spot")] + [ExifTagDescription((ushort)5, "Multi-segment")] + [ExifTagDescription((ushort)6, "Partial")] + [ExifTagDescription((ushort)255, "Other")] + MeteringMode = 0x9207, + + /// + /// LightSource + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Daylight")] + [ExifTagDescription((ushort)2, "Fluorescent")] + [ExifTagDescription((ushort)3, "Tungsten (Incandescent)")] + [ExifTagDescription((ushort)4, "Flash")] + [ExifTagDescription((ushort)9, "Fine Weather")] + [ExifTagDescription((ushort)10, "Cloudy")] + [ExifTagDescription((ushort)11, "Shade")] + [ExifTagDescription((ushort)12, "Daylight Fluorescent")] + [ExifTagDescription((ushort)13, "Day White Fluorescent")] + [ExifTagDescription((ushort)14, "Cool White Fluorescent")] + [ExifTagDescription((ushort)15, "White Fluorescent")] + [ExifTagDescription((ushort)16, "Warm White Fluorescent")] + [ExifTagDescription((ushort)17, "Standard Light A")] + [ExifTagDescription((ushort)18, "Standard Light B")] + [ExifTagDescription((ushort)19, "Standard Light C")] + [ExifTagDescription((ushort)20, "D55")] + [ExifTagDescription((ushort)21, "D65")] + [ExifTagDescription((ushort)22, "D75")] + [ExifTagDescription((ushort)23, "D50")] + [ExifTagDescription((ushort)24, "ISO Studio Tungsten")] + [ExifTagDescription((ushort)255, "Other")] + LightSource = 0x9208, + + /// + /// Flash + /// + [ExifTagDescription((ushort)0, "No Flash")] + [ExifTagDescription((ushort)1, "Fired")] + [ExifTagDescription((ushort)5, "Fired, Return not detected")] + [ExifTagDescription((ushort)7, "Fired, Return detected")] + [ExifTagDescription((ushort)8, "On, Did not fire")] + [ExifTagDescription((ushort)9, "On, Fired")] + [ExifTagDescription((ushort)13, "On, Return not detected")] + [ExifTagDescription((ushort)15, "On, Return detected")] + [ExifTagDescription((ushort)16, "Off, Did not fire")] + [ExifTagDescription((ushort)20, "Off, Did not fire, Return not detected")] + [ExifTagDescription((ushort)24, "Auto, Did not fire")] + [ExifTagDescription((ushort)25, "Auto, Fired")] + [ExifTagDescription((ushort)29, "Auto, Fired, Return not detected")] + [ExifTagDescription((ushort)31, "Auto, Fired, Return detected")] + [ExifTagDescription((ushort)32, "No flash function")] + [ExifTagDescription((ushort)48, "Off, No flash function")] + [ExifTagDescription((ushort)65, "Fired, Red-eye reduction")] + [ExifTagDescription((ushort)69, "Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)71, "Fired, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)73, "On, Red-eye reduction")] + [ExifTagDescription((ushort)77, "On, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)79, "On, Red-eye reduction, Return detected")] + [ExifTagDescription((ushort)80, "Off, Red-eye reduction")] + [ExifTagDescription((ushort)88, "Auto, Did not fire, Red-eye reduction")] + [ExifTagDescription((ushort)89, "Auto, Fired, Red-eye reduction")] + [ExifTagDescription((ushort)93, "Auto, Fired, Red-eye reduction, Return not detected")] + [ExifTagDescription((ushort)95, "Auto, Fired, Red-eye reduction, Return detected")] + Flash = 0x9209, + + /// + /// FocalLength + /// + FocalLength = 0x920A, + + /// + /// FlashEnergy2 + /// + FlashEnergy2 = 0x920B, + + /// + /// SpatialFrequencyResponse2 + /// + SpatialFrequencyResponse2 = 0x920C, + + /// + /// Noise + /// + Noise = 0x920D, + + /// + /// FocalPlaneXResolution2 + /// + FocalPlaneXResolution2 = 0x920E, + + /// + /// FocalPlaneYResolution2 + /// + FocalPlaneYResolution2 = 0x920F, + + /// + /// FocalPlaneResolutionUnit2 + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit2 = 0x9210, + + /// + /// ImageNumber + /// + ImageNumber = 0x9211, + + /// + /// SecurityClassification + /// + [ExifTagDescription("C", "Confidential")] + [ExifTagDescription("R", "Restricted")] + [ExifTagDescription("S", "Secret")] + [ExifTagDescription("T", "Top Secret")] + [ExifTagDescription("U", "Unclassified")] + SecurityClassification = 0x9212, + + /// + /// ImageHistory + /// + ImageHistory = 0x9213, + + /// + /// SubjectArea + /// + SubjectArea = 0x9214, + + /// + /// ExposureIndex2 + /// + ExposureIndex2 = 0x9215, + + /// + /// TIFFEPStandardID + /// + TIFFEPStandardID = 0x9216, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod2 = 0x9217, + + /// + /// MakerNote + /// + MakerNote = 0x927C, + + /// + /// UserComment + /// + UserComment = 0x9286, + + /// + /// SubsecTime + /// + SubsecTime = 0x9290, + + /// + /// SubsecTimeOriginal + /// + SubsecTimeOriginal = 0x9291, + + /// + /// SubsecTimeDigitized + /// + SubsecTimeDigitized = 0x9292, + + /// + /// ImageSourceData + /// + ImageSourceData = 0x935C, + + /// + /// AmbientTemperature + /// + AmbientTemperature = 0x9400, + + /// + /// Humidity + /// + Humidity = 0x9401, + + /// + /// Pressure + /// + Pressure = 0x9402, + + /// + /// WaterDepth + /// + WaterDepth = 0x9403, + + /// + /// Acceleration + /// + Acceleration = 0x9404, + + /// + /// CameraElevationAngle + /// + CameraElevationAngle = 0x9405, + + /// + /// XPTitle + /// + XPTitle = 0x9C9B, + + /// + /// XPComment + /// + XPComment = 0x9C9C, + + /// + /// XPAuthor + /// + XPAuthor = 0x9C9D, + + /// + /// XPKeywords + /// + XPKeywords = 0x9C9E, + + /// + /// XPSubject + /// + XPSubject = 0x9C9F, + + /// + /// FlashpixVersion + /// + FlashpixVersion = 0xA000, + + /// + /// ColorSpace + /// + [ExifTagDescription((ushort)1, "sRGB")] + [ExifTagDescription((ushort)2, "Adobe RGB")] + [ExifTagDescription((ushort)4093, "Wide Gamut RGB")] + [ExifTagDescription((ushort)65534, "ICC Profile")] + [ExifTagDescription((ushort)65535, "Uncalibrated")] + ColorSpace = 0xA001, + + /// + /// PixelXDimension + /// + PixelXDimension = 0xA002, + + /// + /// PixelYDimension + /// + PixelYDimension = 0xA003, + + /// + /// RelatedSoundFile + /// + RelatedSoundFile = 0xA004, + + /// + /// A pointer to the Exif-related Interoperability IFD. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability.html + /// Interoperability IFD is composed of tags which stores the information to ensure the Interoperability. + /// The Interoperability structure of Interoperability IFD is same as TIFF defined IFD structure but does not contain the image data characteristically compared with normal TIFF IFD. + /// + InteroperabilityIFD = 0xA005, + + /// + /// FlashEnergy + /// + FlashEnergy = 0xA20B, + + /// + /// SpatialFrequencyResponse + /// + SpatialFrequencyResponse = 0xA20C, + + /// + /// FocalPlaneXResolution + /// + FocalPlaneXResolution = 0xA20E, + + /// + /// FocalPlaneYResolution + /// + FocalPlaneYResolution = 0xA20F, + + /// + /// FocalPlaneResolutionUnit + /// + [ExifTagDescription((ushort)1, "None")] + [ExifTagDescription((ushort)2, "Inches")] + [ExifTagDescription((ushort)3, "Centimeter")] + [ExifTagDescription((ushort)4, "Millimeter")] + [ExifTagDescription((ushort)5, "Micrometer")] + FocalPlaneResolutionUnit = 0xA210, + + /// + /// SubjectLocation + /// + SubjectLocation = 0xA214, + + /// + /// ExposureIndex + /// + ExposureIndex = 0xA215, + + /// + /// SensingMethod + /// + [ExifTagDescription((ushort)1, "Not defined")] + [ExifTagDescription((ushort)2, "One-chip color area")] + [ExifTagDescription((ushort)3, "Two-chip color area")] + [ExifTagDescription((ushort)4, "Three-chip color area")] + [ExifTagDescription((ushort)5, "Color sequential area")] + [ExifTagDescription((ushort)7, "Trilinear")] + [ExifTagDescription((ushort)8, "Color sequential linear")] + SensingMethod = 0xA217, + + /// + /// FileSource + /// + FileSource = 0xA300, + + /// + /// SceneType + /// + SceneType = 0xA301, + + /// + /// CFAPattern + /// + CFAPattern = 0xA302, + + /// + /// CustomRendered + /// + [ExifTagDescription((ushort)1, "Normal")] + [ExifTagDescription((ushort)2, "Custom")] + CustomRendered = 0xA401, + + /// + /// ExposureMode + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + [ExifTagDescription((ushort)2, "Auto bracket")] + ExposureMode = 0xA402, + + /// + /// WhiteBalance + /// + [ExifTagDescription((ushort)0, "Auto")] + [ExifTagDescription((ushort)1, "Manual")] + WhiteBalance = 0xA403, + + /// + /// DigitalZoomRatio + /// + DigitalZoomRatio = 0xA404, + + /// + /// FocalLengthIn35mmFilm + /// + FocalLengthIn35mmFilm = 0xA405, + + /// + /// SceneCaptureType + /// + [ExifTagDescription((ushort)0, "Standard")] + [ExifTagDescription((ushort)1, "Landscape")] + [ExifTagDescription((ushort)2, "Portrait")] + [ExifTagDescription((ushort)3, "Night")] + SceneCaptureType = 0xA406, + + /// + /// GainControl + /// + [ExifTagDescription((ushort)0, "None")] + [ExifTagDescription((ushort)1, "Low gain up")] + [ExifTagDescription((ushort)2, "High gain up")] + [ExifTagDescription((ushort)3, "Low gain down")] + [ExifTagDescription((ushort)4, "High gain down")] + GainControl = 0xA407, + + /// + /// Contrast + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Contrast = 0xA408, + + /// + /// Saturation + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Low")] + [ExifTagDescription((ushort)2, "High")] + Saturation = 0xA409, + + /// + /// Sharpness + /// + [ExifTagDescription((ushort)0, "Normal")] + [ExifTagDescription((ushort)1, "Soft")] + [ExifTagDescription((ushort)2, "Hard")] + Sharpness = 0xA40A, + + /// + /// DeviceSettingDescription + /// + DeviceSettingDescription = 0xA40B, + + /// + /// SubjectDistanceRange + /// + [ExifTagDescription((ushort)0, "Unknown")] + [ExifTagDescription((ushort)1, "Macro")] + [ExifTagDescription((ushort)2, "Close")] + [ExifTagDescription((ushort)3, "Distant")] + SubjectDistanceRange = 0xA40C, + + /// + /// ImageUniqueID + /// + ImageUniqueID = 0xA420, + + /// + /// OwnerName + /// + OwnerName = 0xA430, + + /// + /// SerialNumber + /// + SerialNumber = 0xA431, + + /// + /// LensSpecification + /// + LensSpecification = 0xA432, + + /// + /// LensMake + /// + LensMake = 0xA433, + + /// + /// LensModel + /// + LensModel = 0xA434, + + /// + /// LensSerialNumber + /// + LensSerialNumber = 0xA435, + + /// + /// GDALMetadata + /// + GDALMetadata = 0xA480, + + /// + /// GDALNoData + /// + GDALNoData = 0xA481, + + /// + /// GPSVersionID + /// + GPSVersionID = 0x0000, + + /// + /// GPSLatitudeRef + /// + GPSLatitudeRef = 0x0001, + + /// + /// GPSLatitude + /// + GPSLatitude = 0x0002, + + /// + /// GPSLongitudeRef + /// + GPSLongitudeRef = 0x0003, + + /// + /// GPSLongitude + /// + GPSLongitude = 0x0004, + + /// + /// GPSAltitudeRef + /// + GPSAltitudeRef = 0x0005, + + /// + /// GPSAltitude + /// + GPSAltitude = 0x0006, + + /// + /// GPSTimestamp + /// + GPSTimestamp = 0x0007, + + /// + /// GPSSatellites + /// + GPSSatellites = 0x0008, + + /// + /// GPSStatus + /// + GPSStatus = 0x0009, + + /// + /// GPSMeasureMode + /// + GPSMeasureMode = 0x000A, + + /// + /// GPSDOP + /// + GPSDOP = 0x000B, + + /// + /// GPSSpeedRef + /// + GPSSpeedRef = 0x000C, + + /// + /// GPSSpeed + /// + GPSSpeed = 0x000D, + + /// + /// GPSTrackRef + /// + GPSTrackRef = 0x000E, + + /// + /// GPSTrack + /// + GPSTrack = 0x000F, + + /// + /// GPSImgDirectionRef + /// + GPSImgDirectionRef = 0x0010, + + /// + /// GPSImgDirection + /// + GPSImgDirection = 0x0011, + + /// + /// GPSMapDatum + /// + GPSMapDatum = 0x0012, + + /// + /// GPSDestLatitudeRef + /// + GPSDestLatitudeRef = 0x0013, + + /// + /// GPSDestLatitude + /// + GPSDestLatitude = 0x0014, + + /// + /// GPSDestLongitudeRef + /// + GPSDestLongitudeRef = 0x0015, + + /// + /// GPSDestLongitude + /// + GPSDestLongitude = 0x0016, + + /// + /// GPSDestBearingRef + /// + GPSDestBearingRef = 0x0017, + + /// + /// GPSDestBearing + /// + GPSDestBearing = 0x0018, + + /// + /// GPSDestDistanceRef + /// + GPSDestDistanceRef = 0x0019, + + /// + /// GPSDestDistance + /// + GPSDestDistance = 0x001A, + + /// + /// GPSProcessingMethod + /// + GPSProcessingMethod = 0x001B, + + /// + /// GPSAreaInformation + /// + GPSAreaInformation = 0x001C, + + /// + /// GPSDateStamp + /// + GPSDateStamp = 0x001D, + + /// + /// GPSDifferential + /// + GPSDifferential = 0x001E, + + /// + /// Used in the Oce scanning process. + /// Identifies the scanticket used in the scanning process. + /// Includes a trailing zero. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceScanjobDescription = 0xC427, + + /// + /// Used in the Oce scanning process. + /// Identifies the application to process the TIFF file that results from scanning. + /// Includes a trailing zero. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceApplicationSelector = 0xC428, + + /// + /// Used in the Oce scanning process. + /// This is the user's answer to an optional question embedded in the Oce scanticket, and presented to that user before scanning. It can serve in further determination of the workflow. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceIdentificationNumber = 0xC429, + + /// + /// Used in the Oce scanning process. + /// This tag encodes the imageprocessing done by the Oce ImageLogic module in the scanner to ensure optimal quality for certain workflows. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html + /// + OceImageLogicCharacteristics = 0xC42A, + + /// + /// Alias Sketchbook Pro layer usage description. + /// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/alias.html + /// + AliasLayerMetadata = 0xC660, } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs index a973f8ab84..0d6a0a75b1 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag{TValueType}.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Class that represents an exif tag from the Exif standard 2.31 with as the data type of the tag. +/// +/// The data type of the tag. +public sealed class ExifTag : ExifTag { - /// - /// Class that represents an exif tag from the Exif standard 2.31 with as the data type of the tag. - /// - /// The data type of the tag. - public sealed class ExifTag : ExifTag + internal ExifTag(ExifTagValue value) + : base((ushort)value) { - internal ExifTag(ExifTagValue value) - : base((ushort)value) - { - } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs index 6e4e4220f5..07807a72b7 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/UnkownExifTag.cs @@ -1,13 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class UnkownExifTag : ExifTag { - internal sealed class UnkownExifTag : ExifTag + internal UnkownExifTag(ExifTagValue value) + : base((ushort)value) { - internal UnkownExifTag(ExifTagValue value) - : base((ushort)value) - { - } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs index 2af413cb95..3fc8e13b7f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs @@ -1,116 +1,113 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +/// +/// The EXIF encoded string structure. +/// +public readonly struct EncodedString : IEquatable { /// - /// The EXIF encoded string structure. + /// Initializes a new instance of the struct. + /// Default use Unicode character code. /// - public readonly struct EncodedString : IEquatable + /// The text value. + public EncodedString(string text) + : this(CharacterCode.Unicode, text) { - /// - /// Initializes a new instance of the struct. - /// Default use Unicode character code. - /// - /// The text value. - public EncodedString(string text) - : this(CharacterCode.Unicode, text) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The character code. - /// The text value. - public EncodedString(CharacterCode code, string text) - { - this.Text = text; - this.Code = code; - } + /// + /// Initializes a new instance of the struct. + /// + /// The character code. + /// The text value. + public EncodedString(CharacterCode code, string text) + { + this.Text = text; + this.Code = code; + } + /// + /// The 8-byte character code enum. + /// + public enum CharacterCode + { /// - /// The 8-byte character code enum. + /// The ASCII (ITU-T T.50 IA5) character code. /// - public enum CharacterCode - { - /// - /// The ASCII (ITU-T T.50 IA5) character code. - /// - ASCII, - - /// - /// The JIS (X208-1990) character code. - /// - JIS, - - /// - /// The Unicode character code. - /// - Unicode, - - /// - /// The undefined character code. - /// - Undefined - } + ASCII, /// - /// Gets the character ode. + /// The JIS (X208-1990) character code. /// - public CharacterCode Code { get; } + JIS, /// - /// Gets the text. + /// The Unicode character code. /// - public string Text { get; } + Unicode, /// - /// Converts the specified to an instance of this type. + /// The undefined character code. /// - /// The text value. - public static implicit operator EncodedString(string text) => new(text); + Undefined + } - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator string(EncodedString encodedString) => encodedString.Text; + /// + /// Gets the character ode. + /// + public CharacterCode Code { get; } - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EncodedString left, EncodedString right) => left.Equals(right); + /// + /// Gets the text. + /// + public string Text { get; } - /// - /// Checks whether two structures are not equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EncodedString left, EncodedString right) => !(left == right); + /// + /// Converts the specified to an instance of this type. + /// + /// The text value. + public static implicit operator EncodedString(string text) => new(text); - /// - public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other); + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator string(EncodedString encodedString) => encodedString.Text; - /// - public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code; + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EncodedString left, EncodedString right) => left.Equals(right); + + /// + /// Checks whether two structures are not equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EncodedString left, EncodedString right) => !(left == right); - /// - public override int GetHashCode() => HashCode.Combine(this.Text, this.Code); + /// + public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other); - /// - public override string ToString() => this.Text; - } + /// + public bool Equals(EncodedString other) => this.Text == other.Text && this.Code == other.Code; + + /// + public override int GetHashCode() => HashCode.Combine(this.Text, this.Code); + + /// + public override string ToString() => this.Text; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs index 0a4e17486f..2405f24ad8 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifArrayValue{TValueType}.cs @@ -1,55 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal abstract class ExifArrayValue : ExifValue, IExifValue { - internal abstract class ExifArrayValue : ExifValue, IExifValue + protected ExifArrayValue(ExifTag tag) + : base(tag) { - protected ExifArrayValue(ExifTag tag) - : base(tag) - { - } + } + + protected ExifArrayValue(ExifTagValue tag) + : base(tag) + { + } + + internal ExifArrayValue(ExifArrayValue value) + : base(value) + { + } - protected ExifArrayValue(ExifTagValue tag) - : base(tag) + public override bool IsArray => true; + + public TValueType[] Value { get; set; } + + public override object GetValue() => this.Value; + + public override bool TrySetValue(object value) + { + if (value is null) { + this.Value = null; + return true; } - internal ExifArrayValue(ExifArrayValue value) - : base(value) + Type type = value.GetType(); + if (value.GetType() == typeof(TValueType[])) { + this.Value = (TValueType[])value; + return true; } - public override bool IsArray => true; - - public TValueType[] Value { get; set; } - - public override object GetValue() => this.Value; - - public override bool TrySetValue(object value) + if (type == typeof(TValueType)) { - if (value is null) - { - this.Value = null; - return true; - } - - Type type = value.GetType(); - if (value.GetType() == typeof(TValueType[])) - { - this.Value = (TValueType[])value; - return true; - } - - if (type == typeof(TValueType)) - { - this.Value = new TValueType[] { (TValueType)value }; - return true; - } - - return false; + this.Value = new TValueType[] { (TValueType)value }; + return true; } + + return false; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs index 3dd45cb20c..910da27da0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByte.cs @@ -3,45 +3,44 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifByte : ExifValue { - internal sealed class ExifByte : ExifValue - { - public ExifByte(ExifTag tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; + public ExifByte(ExifTag tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; - public ExifByte(ExifTagValue tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; + public ExifByte(ExifTagValue tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; - private ExifByte(ExifByte value) - : base(value) => this.DataType = value.DataType; + private ExifByte(ExifByte value) + : base(value) => this.DataType = value.DataType; - public override ExifDataType DataType { get; } + public override ExifDataType DataType { get; } - protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= byte.MinValue && intValue <= byte.MaxValue) - { - this.Value = (byte)intValue; - return true; - } - - return false; - default: - return base.TrySetValue(value); - } + return true; } - public override IExifValue DeepClone() => new ExifByte(this); + switch (value) + { + case int intValue: + if (intValue >= byte.MinValue && intValue <= byte.MaxValue) + { + this.Value = (byte)intValue; + return true; + } + + return false; + default: + return base.TrySetValue(value); + } } + + public override IExifValue DeepClone() => new ExifByte(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs index b5ce70a16d..532d69395c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifByteArray.cs @@ -1,66 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifByteArray : ExifArrayValue { - internal sealed class ExifByteArray : ExifArrayValue - { - public ExifByteArray(ExifTag tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; + public ExifByteArray(ExifTag tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; - public ExifByteArray(ExifTagValue tag, ExifDataType dataType) - : base(tag) => this.DataType = dataType; + public ExifByteArray(ExifTagValue tag, ExifDataType dataType) + : base(tag) => this.DataType = dataType; - private ExifByteArray(ExifByteArray value) - : base(value) => this.DataType = value.DataType; + private ExifByteArray(ExifByteArray value) + : base(value) => this.DataType = value.DataType; - public override ExifDataType DataType { get; } + public override ExifDataType DataType { get; } - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - if (value is int[] intArrayValue) - { - return this.TrySetSignedIntArray(intArrayValue); - } + if (value is int[] intArrayValue) + { + return this.TrySetSignedIntArray(intArrayValue); + } - if (value is int intValue) + if (value is int intValue) + { + if (intValue >= byte.MinValue && intValue <= byte.MaxValue) { - if (intValue >= byte.MinValue && intValue <= byte.MaxValue) - { - this.Value = new byte[] { (byte)intValue }; - } - - return true; + this.Value = new byte[] { (byte)intValue }; } - return false; + return true; } - public override IExifValue DeepClone() => new ExifByteArray(this); + return false; + } - private bool TrySetSignedIntArray(int[] intArrayValue) - { - if (Array.FindIndex(intArrayValue, x => x < byte.MinValue || x > byte.MaxValue) > -1) - { - return false; - } + public override IExifValue DeepClone() => new ExifByteArray(this); - var value = new byte[intArrayValue.Length]; - for (int i = 0; i < intArrayValue.Length; i++) - { - int s = intArrayValue[i]; - value[i] = (byte)s; - } + private bool TrySetSignedIntArray(int[] intArrayValue) + { + if (Array.FindIndex(intArrayValue, x => x < byte.MinValue || x > byte.MaxValue) > -1) + { + return false; + } - this.Value = value; - return true; + var value = new byte[intArrayValue.Length]; + for (int i = 0; i < intArrayValue.Length; i++) + { + int s = intArrayValue[i]; + value[i] = (byte)s; } + + this.Value = value; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs index 78cdbd3ce8..e8a2699d82 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDouble.cs @@ -3,46 +3,45 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifDouble : ExifValue { - internal sealed class ExifDouble : ExifValue + public ExifDouble(ExifTag tag) + : base(tag) { - public ExifDouble(ExifTag tag) - : base(tag) - { - } + } - public ExifDouble(ExifTagValue tag) - : base(tag) - { - } + public ExifDouble(ExifTagValue tag) + : base(tag) + { + } - private ExifDouble(ExifDouble value) - : base(value) - { - } + private ExifDouble(ExifDouble value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.DoubleFloat; + public override ExifDataType DataType => ExifDataType.DoubleFloat; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue; - return true; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifDouble(this); + switch (value) + { + case int intValue: + this.Value = intValue; + return true; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifDouble(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs index c6f19b29cf..a4f43d6f7d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifDoubleArray.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifDoubleArray : ExifArrayValue { - internal sealed class ExifDoubleArray : ExifArrayValue + public ExifDoubleArray(ExifTag tag) + : base(tag) { - public ExifDoubleArray(ExifTag tag) - : base(tag) - { - } + } - public ExifDoubleArray(ExifTagValue tag) - : base(tag) - { - } + public ExifDoubleArray(ExifTagValue tag) + : base(tag) + { + } - private ExifDoubleArray(ExifDoubleArray value) - : base(value) - { - } + private ExifDoubleArray(ExifDoubleArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.DoubleFloat; + public override ExifDataType DataType => ExifDataType.DoubleFloat; - public override IExifValue DeepClone() => new ExifDoubleArray(this); - } + public override IExifValue DeepClone() => new ExifDoubleArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs index 6b2c4c7772..317f8d7716 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs @@ -1,55 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Globalization; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifEncodedString : ExifValue { - internal sealed class ExifEncodedString : ExifValue + public ExifEncodedString(ExifTag tag) + : base(tag) { - public ExifEncodedString(ExifTag tag) - : base(tag) - { - } + } - public ExifEncodedString(ExifTagValue tag) - : base(tag) - { - } + public ExifEncodedString(ExifTagValue tag) + : base(tag) + { + } - private ExifEncodedString(ExifEncodedString value) - : base(value) - { - } + private ExifEncodedString(ExifEncodedString value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Undefined; + public override ExifDataType DataType => ExifDataType.Undefined; - protected override string StringValue => this.Value.Text; + protected override string StringValue => this.Value.Text; - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - if (value is string stringValue) + if (value is string stringValue) + { + this.Value = new EncodedString(stringValue); + return true; + } + else if (value is byte[] buffer) + { + if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString)) { - this.Value = new EncodedString(stringValue); + this.Value = encodedString; return true; } - else if (value is byte[] buffer) - { - if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString)) - { - this.Value = encodedString; - return true; - } - } - - return false; } - public override IExifValue DeepClone() => new ExifEncodedString(this); + return false; } + + public override IExifValue DeepClone() => new ExifEncodedString(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs index b8206d498b..904a9ee02c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloat.cs @@ -3,41 +3,40 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifFloat : ExifValue { - internal sealed class ExifFloat : ExifValue + public ExifFloat(ExifTagValue tag) + : base(tag) { - public ExifFloat(ExifTagValue tag) - : base(tag) - { - } + } - private ExifFloat(ExifFloat value) - : base(value) - { - } + private ExifFloat(ExifFloat value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SingleFloat; + public override ExifDataType DataType => ExifDataType.SingleFloat; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue; - return true; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifFloat(this); + switch (value) + { + case int intValue: + this.Value = intValue; + return true; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifFloat(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs index 9f20bcd303..7f382d4164 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifFloatArray.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifFloatArray : ExifArrayValue { - internal sealed class ExifFloatArray : ExifArrayValue + public ExifFloatArray(ExifTagValue tag) + : base(tag) { - public ExifFloatArray(ExifTagValue tag) - : base(tag) - { - } + } - private ExifFloatArray(ExifFloatArray value) - : base(value) - { - } + private ExifFloatArray(ExifFloatArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SingleFloat; + public override ExifDataType DataType => ExifDataType.SingleFloat; - public override IExifValue DeepClone() => new ExifFloatArray(this); - } + public override IExifValue DeepClone() => new ExifFloatArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs index 8a6883fca9..4fb5e8092b 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong.cs @@ -3,51 +3,50 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifLong : ExifValue { - internal sealed class ExifLong : ExifValue + public ExifLong(ExifTag tag) + : base(tag) { - public ExifLong(ExifTag tag) - : base(tag) - { - } + } - public ExifLong(ExifTagValue tag) - : base(tag) - { - } + public ExifLong(ExifTagValue tag) + : base(tag) + { + } - private ExifLong(ExifLong value) - : base(value) - { - } + private ExifLong(ExifLong value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Long; + public override ExifDataType DataType => ExifDataType.Long; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } - - return false; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifLong(this); + switch (value) + { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; + return true; + } + + return false; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifLong(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs index dffd4ac675..9f25031446 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs @@ -3,64 +3,63 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifLong8 : ExifValue { - internal sealed class ExifLong8 : ExifValue + public ExifLong8(ExifTag tag) + : base(tag) { - public ExifLong8(ExifTag tag) - : base(tag) - { - } + } - public ExifLong8(ExifTagValue tag) - : base(tag) - { - } + public ExifLong8(ExifTagValue tag) + : base(tag) + { + } - private ExifLong8(ExifLong8 value) - : base(value) - { - } + private ExifLong8(ExifLong8 value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Long8; + public override ExifDataType DataType => ExifDataType.Long8; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } + switch (value) + { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; + return true; + } - return false; - case uint uintValue: - this.Value = uintValue; + return false; + case uint uintValue: + this.Value = uintValue; + return true; + case long intValue: + if (intValue >= 0) + { + this.Value = (ulong)intValue; return true; - case long intValue: - if (intValue >= 0) - { - this.Value = (ulong)intValue; - return true; - } + } - return false; - default: + return false; + default: - return false; - } + return false; } - - public override IExifValue DeepClone() => new ExifLong8(this); } + + public override IExifValue DeepClone() => new ExifLong8(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 292edfbb14..ff48fc7741 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -1,171 +1,168 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifLong8Array : ExifArrayValue { - internal sealed class ExifLong8Array : ExifArrayValue + public ExifLong8Array(ExifTagValue tag) + : base(tag) { - public ExifLong8Array(ExifTagValue tag) - : base(tag) - { - } + } - private ExifLong8Array(ExifLong8Array value) - : base(value) - { - } + private ExifLong8Array(ExifLong8Array value) + : base(value) + { + } - public override ExifDataType DataType + public override ExifDataType DataType + { + get { - get + if (this.Value is not null) { - if (this.Value is not null) + foreach (ulong value in this.Value) { - foreach (ulong value in this.Value) + if (value > uint.MaxValue) { - if (value > uint.MaxValue) - { - return ExifDataType.Long8; - } + return ExifDataType.Long8; } } - - return ExifDataType.Long; } + + return ExifDataType.Long; } + } - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - switch (value) - { - case int val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, int.MaxValue)); + switch (value) + { + case int val: + return this.SetSingle((ulong)Numerics.Clamp(val, 0, int.MaxValue)); - case uint val: - return this.SetSingle((ulong)val); + case uint val: + return this.SetSingle((ulong)val); - case short val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, short.MaxValue)); + case short val: + return this.SetSingle((ulong)Numerics.Clamp(val, 0, short.MaxValue)); - case ushort val: - return this.SetSingle((ulong)val); + case ushort val: + return this.SetSingle((ulong)val); - case long val: - return this.SetSingle((ulong)Numerics.Clamp(val, 0, long.MaxValue)); + case long val: + return this.SetSingle((ulong)Numerics.Clamp(val, 0, long.MaxValue)); - case long[] array: + case long[] array: + { + if (value.GetType() == typeof(ulong[])) { - if (value.GetType() == typeof(ulong[])) - { - return this.SetArray((ulong[])value); - } - - return this.SetArray(array); + return this.SetArray((ulong[])value); } - case int[] array: - { - if (value.GetType() == typeof(uint[])) - { - return this.SetArray((uint[])value); - } + return this.SetArray(array); + } - return this.SetArray(array); + case int[] array: + { + if (value.GetType() == typeof(uint[])) + { + return this.SetArray((uint[])value); } - case short[] array: - { - if (value.GetType() == typeof(ushort[])) - { - return this.SetArray((ushort[])value); - } + return this.SetArray(array); + } - return this.SetArray(array); + case short[] array: + { + if (value.GetType() == typeof(ushort[])) + { + return this.SetArray((ushort[])value); } - } - return false; + return this.SetArray(array); + } } - public override IExifValue DeepClone() => new ExifLong8Array(this); + return false; + } + + public override IExifValue DeepClone() => new ExifLong8Array(this); + + private bool SetSingle(ulong value) + { + this.Value = new[] { value }; + return true; + } - private bool SetSingle(ulong value) + private bool SetArray(long[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) { - this.Value = new[] { value }; - return true; + numbers[i] = (ulong)(values[i] < 0 ? 0 : values[i]); } - private bool SetArray(long[] values) - { - var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)(values[i] < 0 ? 0 : values[i]); - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; - } + private bool SetArray(ulong[] values) + { + this.Value = values; + return true; + } - private bool SetArray(ulong[] values) + private bool SetArray(int[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) { - this.Value = values; - return true; + numbers[i] = (ulong)Numerics.Clamp(values[i], 0, int.MaxValue); } - private bool SetArray(int[] values) - { - var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)Numerics.Clamp(values[i], 0, int.MaxValue); - } - - this.Value = numbers; - return true; - } + this.Value = numbers; + return true; + } - private bool SetArray(uint[] values) + private bool SetArray(uint[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) { - var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)values[i]; - } - - this.Value = numbers; - return true; + numbers[i] = (ulong)values[i]; } - private bool SetArray(short[] values) - { - var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)Numerics.Clamp(values[i], 0, short.MaxValue); - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(short[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)Numerics.Clamp(values[i], 0, short.MaxValue); } - private bool SetArray(ushort[] values) - { - var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)values[i]; - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(ushort[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; } + + this.Value = numbers; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs index 46b9478a0f..f44c527f5e 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLongArray.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifLongArray : ExifArrayValue { - internal sealed class ExifLongArray : ExifArrayValue + public ExifLongArray(ExifTag tag) + : base(tag) { - public ExifLongArray(ExifTag tag) - : base(tag) - { - } + } - public ExifLongArray(ExifTagValue tag) - : base(tag) - { - } + public ExifLongArray(ExifTagValue tag) + : base(tag) + { + } - private ExifLongArray(ExifLongArray value) - : base(value) - { - } + private ExifLongArray(ExifLongArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Long; + public override ExifDataType DataType => ExifDataType.Long; - public override IExifValue DeepClone() => new ExifLongArray(this); - } + public override IExifValue DeepClone() => new ExifLongArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs index 11071966e2..780f389ab4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs @@ -3,71 +3,70 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifNumber : ExifValue { - internal sealed class ExifNumber : ExifValue + public ExifNumber(ExifTag tag) + : base(tag) { - public ExifNumber(ExifTag tag) - : base(tag) - { - } + } - private ExifNumber(ExifNumber value) - : base(value) - { - } + private ExifNumber(ExifNumber value) + : base(value) + { + } - public override ExifDataType DataType + public override ExifDataType DataType + { + get { - get + if (this.Value > ushort.MaxValue) { - if (this.Value > ushort.MaxValue) - { - return ExifDataType.Long; - } - - return ExifDataType.Short; + return ExifDataType.Long; } + + return ExifDataType.Short; } + } - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= uint.MinValue) - { - this.Value = (uint)intValue; - return true; - } + return true; + } - return false; - case uint uintValue: - this.Value = uintValue; + switch (value) + { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; return true; - case short shortValue: - if (shortValue >= uint.MinValue) - { - this.Value = (uint)shortValue; - return true; - } + } - return false; - case ushort ushortValue: - this.Value = ushortValue; + return false; + case uint uintValue: + this.Value = uintValue; + return true; + case short shortValue: + if (shortValue >= uint.MinValue) + { + this.Value = (uint)shortValue; return true; - default: - return false; - } - } + } - public override IExifValue DeepClone() => new ExifNumber(this); + return false; + case ushort ushortValue: + this.Value = ushortValue; + return true; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifNumber(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 20a0b877d0..6b5aafbad5 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -1,135 +1,134 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifNumberArray : ExifArrayValue { - internal sealed class ExifNumberArray : ExifArrayValue + public ExifNumberArray(ExifTag tag) + : base(tag) { - public ExifNumberArray(ExifTag tag) - : base(tag) - { - } + } - private ExifNumberArray(ExifNumberArray value) - : base(value) - { - } + private ExifNumberArray(ExifNumberArray value) + : base(value) + { + } - public override ExifDataType DataType + public override ExifDataType DataType + { + get { - get + if (this.Value is not null) { - if (this.Value is not null) + foreach (Number value in this.Value) { - foreach (Number value in this.Value) + if (value > ushort.MaxValue) { - if (value > ushort.MaxValue) - { - return ExifDataType.Long; - } + return ExifDataType.Long; } } - - return ExifDataType.Short; } + + return ExifDataType.Short; } + } - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - switch (value) + switch (value) + { + case int val: + return this.SetSingle(val); + case uint val: + return this.SetSingle(val); + case short val: + return this.SetSingle(val); + case ushort val: + return this.SetSingle(val); + case int[] array: { - case int val: - return this.SetSingle(val); - case uint val: - return this.SetSingle(val); - case short val: - return this.SetSingle(val); - case ushort val: - return this.SetSingle(val); - case int[] array: + // workaround for inconsistent covariance of value-typed arrays + if (value.GetType() == typeof(uint[])) { - // workaround for inconsistent covariance of value-typed arrays - if (value.GetType() == typeof(uint[])) - { - return this.SetArray((uint[])value); - } - - return this.SetArray(array); + return this.SetArray((uint[])value); } - case short[] array: - { - if (value.GetType() == typeof(ushort[])) - { - return this.SetArray((ushort[])value); - } + return this.SetArray(array); + } - return this.SetArray(array); + case short[] array: + { + if (value.GetType() == typeof(ushort[])) + { + return this.SetArray((ushort[])value); } - } - return false; + return this.SetArray(array); + } } - public override IExifValue DeepClone() => new ExifNumberArray(this); + return false; + } - private bool SetSingle(Number value) - { - this.Value = new[] { value }; - return true; - } + public override IExifValue DeepClone() => new ExifNumberArray(this); - private bool SetArray(int[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } + private bool SetSingle(Number value) + { + this.Value = new[] { value }; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(int[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; } - private bool SetArray(uint[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(uint[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; } - private bool SetArray(short[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(short[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; } - private bool SetArray(ushort[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } + this.Value = numbers; + return true; + } - this.Value = numbers; - return true; + private bool SetArray(ushort[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; } + + this.Value = numbers; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs index 73b220217a..a368913e2f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs @@ -1,56 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// Enumerates the available orientation values supplied by EXIF metadata. +/// +public static class ExifOrientationMode { /// - /// Enumerates the available orientation values supplied by EXIF metadata. - /// - public static class ExifOrientationMode - { - /// - /// Unknown rotation. - /// - public const ushort Unknown = 0; - - /// - /// The 0th row at the top, the 0th column on the left. - /// - public const ushort TopLeft = 1; - - /// - /// The 0th row at the top, the 0th column on the right. - /// - public const ushort TopRight = 2; - - /// - /// The 0th row at the bottom, the 0th column on the right. - /// - public const ushort BottomRight = 3; - - /// - /// The 0th row at the bottom, the 0th column on the left. - /// - public const ushort BottomLeft = 4; - - /// - /// The 0th row on the left, the 0th column at the top. - /// - public const ushort LeftTop = 5; - - /// - /// The 0th row at the right, the 0th column at the top. - /// - public const ushort RightTop = 6; - - /// - /// The 0th row on the right, the 0th column at the bottom. - /// - public const ushort RightBottom = 7; - - /// - /// The 0th row on the left, the 0th column at the bottom. - /// - public const ushort LeftBottom = 8; - } + /// Unknown rotation. + /// + public const ushort Unknown = 0; + + /// + /// The 0th row at the top, the 0th column on the left. + /// + public const ushort TopLeft = 1; + + /// + /// The 0th row at the top, the 0th column on the right. + /// + public const ushort TopRight = 2; + + /// + /// The 0th row at the bottom, the 0th column on the right. + /// + public const ushort BottomRight = 3; + + /// + /// The 0th row at the bottom, the 0th column on the left. + /// + public const ushort BottomLeft = 4; + + /// + /// The 0th row on the left, the 0th column at the top. + /// + public const ushort LeftTop = 5; + + /// + /// The 0th row at the right, the 0th column at the top. + /// + public const ushort RightTop = 6; + + /// + /// The 0th row on the right, the 0th column at the bottom. + /// + public const ushort RightBottom = 7; + + /// + /// The 0th row on the left, the 0th column at the bottom. + /// + public const ushort LeftBottom = 8; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs index 490269416e..958c6f8342 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRational.cs @@ -3,51 +3,50 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifRational : ExifValue { - internal sealed class ExifRational : ExifValue + public ExifRational(ExifTag tag) + : base(tag) { - public ExifRational(ExifTag tag) - : base(tag) - { - } + } - public ExifRational(ExifTagValue tag) - : base(tag) - { - } + public ExifRational(ExifTagValue tag) + : base(tag) + { + } - private ExifRational(ExifRational value) - : base(value) - { - } + private ExifRational(ExifRational value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Rational; + public override ExifDataType DataType => ExifDataType.Rational; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } + return true; + } - switch (value) - { - case SignedRational signed: + switch (value) + { + case SignedRational signed: - if (signed.Numerator >= uint.MinValue && signed.Denominator >= uint.MinValue) - { - this.Value = new Rational((uint)signed.Numerator, (uint)signed.Denominator); - } + if (signed.Numerator >= uint.MinValue && signed.Denominator >= uint.MinValue) + { + this.Value = new Rational((uint)signed.Numerator, (uint)signed.Denominator); + } - return true; - default: - return false; - } + return true; + default: + return false; } - - public override IExifValue DeepClone() => new ExifRational(this); } + + public override IExifValue DeepClone() => new ExifRational(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs index d00e251b88..0114a7fbda 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifRationalArray.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifRationalArray : ExifArrayValue { - internal sealed class ExifRationalArray : ExifArrayValue + public ExifRationalArray(ExifTag tag) + : base(tag) { - public ExifRationalArray(ExifTag tag) - : base(tag) - { - } + } + + public ExifRationalArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifRationalArray(ExifRationalArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Rational; - public ExifRationalArray(ExifTagValue tag) - : base(tag) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { + return true; } - private ExifRationalArray(ExifRationalArray value) - : base(value) + if (value is SignedRational[] signedArray) { + return this.TrySetSignedArray(signedArray); } - public override ExifDataType DataType => ExifDataType.Rational; - - public override bool TrySetValue(object value) + if (value is SignedRational signed) { - if (base.TrySetValue(value)) + if (signed.Numerator >= 0 && signed.Denominator >= 0) { - return true; + this.Value = new[] { new Rational((uint)signed.Numerator, (uint)signed.Denominator) }; } - if (value is SignedRational[] signedArray) - { - return this.TrySetSignedArray(signedArray); - } + return true; + } - if (value is SignedRational signed) - { - if (signed.Numerator >= 0 && signed.Denominator >= 0) - { - this.Value = new[] { new Rational((uint)signed.Numerator, (uint)signed.Denominator) }; - } + return false; + } - return true; - } + public override IExifValue DeepClone() => new ExifRationalArray(this); + private bool TrySetSignedArray(SignedRational[] signed) + { + if (Array.FindIndex(signed, x => x.Numerator < 0 || x.Denominator < 0) > -1) + { return false; } - public override IExifValue DeepClone() => new ExifRationalArray(this); - - private bool TrySetSignedArray(SignedRational[] signed) + var unsigned = new Rational[signed.Length]; + for (int i = 0; i < signed.Length; i++) { - if (Array.FindIndex(signed, x => x.Numerator < 0 || x.Denominator < 0) > -1) - { - return false; - } - - var unsigned = new Rational[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - SignedRational s = signed[i]; - unsigned[i] = new Rational((uint)s.Numerator, (uint)s.Denominator); - } - - this.Value = unsigned; - return true; + SignedRational s = signed[i]; + unsigned[i] = new Rational((uint)s.Numerator, (uint)s.Denominator); } + + this.Value = unsigned; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs index a3cd5d6110..68932b3b87 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShort.cs @@ -3,59 +3,58 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifShort : ExifValue { - internal sealed class ExifShort : ExifValue + public ExifShort(ExifTag tag) + : base(tag) { - public ExifShort(ExifTag tag) - : base(tag) - { - } + } - public ExifShort(ExifTagValue tag) - : base(tag) - { - } + public ExifShort(ExifTagValue tag) + : base(tag) + { + } - private ExifShort(ExifShort value) - : base(value) - { - } + private ExifShort(ExifShort value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Short; + public override ExifDataType DataType => ExifDataType.Short; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= ushort.MinValue && intValue <= ushort.MaxValue) - { - this.Value = (ushort)intValue; - return true; - } - - return false; - case short shortValue: - if (shortValue >= ushort.MinValue) - { - this.Value = (ushort)shortValue; - return true; - } - - return false; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifShort(this); + switch (value) + { + case int intValue: + if (intValue >= ushort.MinValue && intValue <= ushort.MaxValue) + { + this.Value = (ushort)intValue; + return true; + } + + return false; + case short shortValue: + if (shortValue >= ushort.MinValue) + { + this.Value = (ushort)shortValue; + return true; + } + + return false; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifShort(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs index 745e4d020d..42c919bb7d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifShortArray.cs @@ -1,105 +1,102 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifShortArray : ExifArrayValue { - internal sealed class ExifShortArray : ExifArrayValue + public ExifShortArray(ExifTag tag) + : base(tag) { - public ExifShortArray(ExifTag tag) - : base(tag) + } + + public ExifShortArray(ExifTagValue tag) + : base(tag) + { + } + + private ExifShortArray(ExifShortArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Short; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { + return true; } - public ExifShortArray(ExifTagValue tag) - : base(tag) + if (value is int[] signedIntArray) { + return this.TrySetSignedIntArray(signedIntArray); } - private ExifShortArray(ExifShortArray value) - : base(value) + if (value is short[] signedShortArray) { + return this.TrySetSignedShortArray(signedShortArray); } - public override ExifDataType DataType => ExifDataType.Short; - - public override bool TrySetValue(object value) + if (value is int signedInt) { - if (base.TrySetValue(value)) + if (signedInt >= ushort.MinValue && signedInt <= ushort.MaxValue) { - return true; + this.Value = new ushort[] { (ushort)signedInt }; } - if (value is int[] signedIntArray) - { - return this.TrySetSignedIntArray(signedIntArray); - } + return true; + } - if (value is short[] signedShortArray) + if (value is short signedShort) + { + if (signedShort >= ushort.MinValue) { - return this.TrySetSignedShortArray(signedShortArray); + this.Value = new ushort[] { (ushort)signedShort }; } - if (value is int signedInt) - { - if (signedInt >= ushort.MinValue && signedInt <= ushort.MaxValue) - { - this.Value = new ushort[] { (ushort)signedInt }; - } - - return true; - } + return true; + } - if (value is short signedShort) - { - if (signedShort >= ushort.MinValue) - { - this.Value = new ushort[] { (ushort)signedShort }; - } + return false; + } - return true; - } + public override IExifValue DeepClone() => new ExifShortArray(this); + private bool TrySetSignedIntArray(int[] signed) + { + if (Array.FindIndex(signed, x => x < ushort.MinValue || x > ushort.MaxValue) > -1) + { return false; } - public override IExifValue DeepClone() => new ExifShortArray(this); - - private bool TrySetSignedIntArray(int[] signed) + var unsigned = new ushort[signed.Length]; + for (int i = 0; i < signed.Length; i++) { - if (Array.FindIndex(signed, x => x < ushort.MinValue || x > ushort.MaxValue) > -1) - { - return false; - } + int s = signed[i]; + unsigned[i] = (ushort)s; + } - var unsigned = new ushort[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - int s = signed[i]; - unsigned[i] = (ushort)s; - } + this.Value = unsigned; + return true; + } - this.Value = unsigned; - return true; + private bool TrySetSignedShortArray(short[] signed) + { + if (Array.FindIndex(signed, x => x < ushort.MinValue) > -1) + { + return false; } - private bool TrySetSignedShortArray(short[] signed) + var unsigned = new ushort[signed.Length]; + for (int i = 0; i < signed.Length; i++) { - if (Array.FindIndex(signed, x => x < ushort.MinValue) > -1) - { - return false; - } - - var unsigned = new ushort[signed.Length]; - for (int i = 0; i < signed.Length; i++) - { - short s = signed[i]; - unsigned[i] = (ushort)s; - } - - this.Value = unsigned; - return true; + short s = signed[i]; + unsigned[i] = (ushort)s; } + + this.Value = unsigned; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs index a82c16fb34..c9bf7a820f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByte.cs @@ -3,46 +3,45 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedByte : ExifValue { - internal sealed class ExifSignedByte : ExifValue + public ExifSignedByte(ExifTagValue tag) + : base(tag) { - public ExifSignedByte(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedByte(ExifSignedByte value) - : base(value) - { - } + private ExifSignedByte(ExifSignedByte value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedByte; + public override ExifDataType DataType => ExifDataType.SignedByte; - protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString("X2", CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= sbyte.MinValue && intValue <= sbyte.MaxValue) - { - this.Value = (sbyte)intValue; - return true; - } - - return false; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifSignedByte(this); + switch (value) + { + case int intValue: + if (intValue >= sbyte.MinValue && intValue <= sbyte.MaxValue) + { + this.Value = (sbyte)intValue; + return true; + } + + return false; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifSignedByte(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs index 5d94c368de..fcb2757eb3 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedByteArray.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedByteArray : ExifArrayValue { - internal sealed class ExifSignedByteArray : ExifArrayValue + public ExifSignedByteArray(ExifTagValue tag) + : base(tag) { - public ExifSignedByteArray(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedByteArray(ExifSignedByteArray value) - : base(value) - { - } + private ExifSignedByteArray(ExifSignedByteArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedByte; + public override ExifDataType DataType => ExifDataType.SignedByte; - public override IExifValue DeepClone() => new ExifSignedByteArray(this); - } + public override IExifValue DeepClone() => new ExifSignedByteArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs index 9365da6847..e3bf3a8618 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong.cs @@ -3,24 +3,23 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedLong : ExifValue { - internal sealed class ExifSignedLong : ExifValue + public ExifSignedLong(ExifTagValue tag) + : base(tag) { - public ExifSignedLong(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedLong(ExifSignedLong value) - : base(value) - { - } + private ExifSignedLong(ExifSignedLong value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedLong; + public override ExifDataType DataType => ExifDataType.SignedLong; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override IExifValue DeepClone() => new ExifSignedLong(this); - } + public override IExifValue DeepClone() => new ExifSignedLong(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs index 8dd144271f..7c9e254f5f 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs @@ -3,24 +3,23 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedLong8 : ExifValue { - internal sealed class ExifSignedLong8 : ExifValue + public ExifSignedLong8(ExifTagValue tag) + : base(tag) { - public ExifSignedLong8(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedLong8(ExifSignedLong8 value) - : base(value) - { - } + private ExifSignedLong8(ExifSignedLong8 value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedLong8; + public override ExifDataType DataType => ExifDataType.SignedLong8; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override IExifValue DeepClone() => new ExifSignedLong8(this); - } + public override IExifValue DeepClone() => new ExifSignedLong8(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs index b49dc32034..3c9777166c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedLong8Array : ExifArrayValue { - internal sealed class ExifSignedLong8Array : ExifArrayValue + public ExifSignedLong8Array(ExifTagValue tag) + : base(tag) { - public ExifSignedLong8Array(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedLong8Array(ExifSignedLong8Array value) - : base(value) - { - } + private ExifSignedLong8Array(ExifSignedLong8Array value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedLong8; + public override ExifDataType DataType => ExifDataType.SignedLong8; - public override IExifValue DeepClone() => new ExifSignedLong8Array(this); - } + public override IExifValue DeepClone() => new ExifSignedLong8Array(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs index b9a6319c8e..f80dd9ae5c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLongArray.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedLongArray : ExifArrayValue { - internal sealed class ExifSignedLongArray : ExifArrayValue + public ExifSignedLongArray(ExifTagValue tag) + : base(tag) { - public ExifSignedLongArray(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedLongArray(ExifSignedLongArray value) - : base(value) - { - } + private ExifSignedLongArray(ExifSignedLongArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedLong; + public override ExifDataType DataType => ExifDataType.SignedLong; - public override IExifValue DeepClone() => new ExifSignedLongArray(this); - } + public override IExifValue DeepClone() => new ExifSignedLongArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs index 4c30a4d30c..5cd456ac13 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRational.cs @@ -3,29 +3,28 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedRational : ExifValue { - internal sealed class ExifSignedRational : ExifValue + internal ExifSignedRational(ExifTag tag) + : base(tag) { - internal ExifSignedRational(ExifTag tag) - : base(tag) - { - } + } - internal ExifSignedRational(ExifTagValue tag) - : base(tag) - { - } + internal ExifSignedRational(ExifTagValue tag) + : base(tag) + { + } - private ExifSignedRational(ExifSignedRational value) - : base(value) - { - } + private ExifSignedRational(ExifSignedRational value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedRational; + public override ExifDataType DataType => ExifDataType.SignedRational; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override IExifValue DeepClone() => new ExifSignedRational(this); - } + public override IExifValue DeepClone() => new ExifSignedRational(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs index 00b6840493..86e843129c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedRationalArray.cs @@ -1,27 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedRationalArray : ExifArrayValue { - internal sealed class ExifSignedRationalArray : ExifArrayValue + public ExifSignedRationalArray(ExifTag tag) + : base(tag) { - public ExifSignedRationalArray(ExifTag tag) - : base(tag) - { - } + } - public ExifSignedRationalArray(ExifTagValue tag) - : base(tag) - { - } + public ExifSignedRationalArray(ExifTagValue tag) + : base(tag) + { + } - private ExifSignedRationalArray(ExifSignedRationalArray value) - : base(value) - { - } + private ExifSignedRationalArray(ExifSignedRationalArray value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedRational; + public override ExifDataType DataType => ExifDataType.SignedRational; - public override IExifValue DeepClone() => new ExifSignedRationalArray(this); - } + public override IExifValue DeepClone() => new ExifSignedRationalArray(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs index 12a4f389c9..72fd0b65a1 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShort.cs @@ -3,46 +3,45 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifSignedShort : ExifValue { - internal sealed class ExifSignedShort : ExifValue + public ExifSignedShort(ExifTagValue tag) + : base(tag) { - public ExifSignedShort(ExifTagValue tag) - : base(tag) - { - } + } - private ExifSignedShort(ExifSignedShort value) - : base(value) - { - } + private ExifSignedShort(ExifSignedShort value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.SignedShort; + public override ExifDataType DataType => ExifDataType.SignedShort; - protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - if (intValue >= short.MinValue && intValue <= short.MaxValue) - { - this.Value = (short)intValue; - return true; - } - - return false; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifSignedShort(this); + switch (value) + { + case int intValue: + if (intValue >= short.MinValue && intValue <= short.MaxValue) + { + this.Value = (short)intValue; + return true; + } + + return false; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifSignedShort(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs index 6c113eb39d..464105b3be 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedShortArray.cs @@ -1,67 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +internal sealed class ExifSignedShortArray : ExifArrayValue { - internal sealed class ExifSignedShortArray : ExifArrayValue + public ExifSignedShortArray(ExifTagValue tag) + : base(tag) { - public ExifSignedShortArray(ExifTagValue tag) - : base(tag) + } + + private ExifSignedShortArray(ExifSignedShortArray value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedShort; + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { + return true; } - private ExifSignedShortArray(ExifSignedShortArray value) - : base(value) + if (value is int[] intArray) { + return this.TrySetSignedArray(intArray); } - public override ExifDataType DataType => ExifDataType.SignedShort; - - public override bool TrySetValue(object value) + if (value is int intValue) { - if (base.TrySetValue(value)) + if (intValue >= short.MinValue && intValue <= short.MaxValue) { - return true; + this.Value = new short[] { (short)intValue }; } - if (value is int[] intArray) - { - return this.TrySetSignedArray(intArray); - } + return true; + } - if (value is int intValue) - { - if (intValue >= short.MinValue && intValue <= short.MaxValue) - { - this.Value = new short[] { (short)intValue }; - } + return false; + } - return true; - } + public override IExifValue DeepClone() => new ExifSignedShortArray(this); + private bool TrySetSignedArray(int[] intArray) + { + if (Array.FindIndex(intArray, x => x < short.MinValue || x > short.MaxValue) > -1) + { return false; } - public override IExifValue DeepClone() => new ExifSignedShortArray(this); - - private bool TrySetSignedArray(int[] intArray) + var value = new short[intArray.Length]; + for (int i = 0; i < intArray.Length; i++) { - if (Array.FindIndex(intArray, x => x < short.MinValue || x > short.MaxValue) > -1) - { - return false; - } - - var value = new short[intArray.Length]; - for (int i = 0; i < intArray.Length; i++) - { - int s = intArray[i]; - value[i] = (short)s; - } - - this.Value = value; - return true; + int s = intArray[i]; + value[i] = (short)s; } + + this.Value = value; + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs index 97f6fd0e04..1eb712bcac 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifString.cs @@ -3,46 +3,45 @@ using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifString : ExifValue { - internal sealed class ExifString : ExifValue + public ExifString(ExifTag tag) + : base(tag) { - public ExifString(ExifTag tag) - : base(tag) - { - } + } - public ExifString(ExifTagValue tag) - : base(tag) - { - } + public ExifString(ExifTagValue tag) + : base(tag) + { + } - private ExifString(ExifString value) - : base(value) - { - } + private ExifString(ExifString value) + : base(value) + { + } - public override ExifDataType DataType => ExifDataType.Ascii; + public override ExifDataType DataType => ExifDataType.Ascii; - protected override string StringValue => this.Value; + protected override string StringValue => this.Value; - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) { - if (base.TrySetValue(value)) - { - return true; - } - - switch (value) - { - case int intValue: - this.Value = intValue.ToString(CultureInfo.InvariantCulture); - return true; - default: - return false; - } + return true; } - public override IExifValue DeepClone() => new ExifString(this); + switch (value) + { + case int intValue: + this.Value = intValue.ToString(CultureInfo.InvariantCulture); + return true; + default: + return false; + } } + + public override IExifValue DeepClone() => new ExifString(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs index 61e11542f5..9e7c96dac9 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifUcs2String.cs @@ -1,47 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal sealed class ExifUcs2String : ExifValue { - internal sealed class ExifUcs2String : ExifValue + public ExifUcs2String(ExifTag tag) + : base(tag) { - public ExifUcs2String(ExifTag tag) - : base(tag) - { - } + } - public ExifUcs2String(ExifTagValue tag) - : base(tag) - { - } + public ExifUcs2String(ExifTagValue tag) + : base(tag) + { + } - private ExifUcs2String(ExifUcs2String value) - : base(value) - { - } + private ExifUcs2String(ExifUcs2String value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Byte; - public override ExifDataType DataType => ExifDataType.Byte; + protected override string StringValue => this.Value; - protected override string StringValue => this.Value; + public override object GetValue() => this.Value; - public override object GetValue() => this.Value; + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } - public override bool TrySetValue(object value) + if (value is byte[] buffer) { - if (base.TrySetValue(value)) - { - return true; - } - - if (value is byte[] buffer) - { - this.Value = ExifUcs2StringHelpers.Ucs2Encoding.GetString(buffer); - return true; - } - - return false; + this.Value = ExifUcs2StringHelpers.Ucs2Encoding.GetString(buffer); + return true; } - public override IExifValue DeepClone() => new ExifUcs2String(this); + return false; } + + public override IExifValue DeepClone() => new ExifUcs2String(this); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs index 5a17bf9691..59ee815376 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal abstract class ExifValue : IExifValue, IEquatable { - internal abstract class ExifValue : IExifValue, IEquatable + protected ExifValue(ExifTag tag) => this.Tag = tag; + + protected ExifValue(ExifTagValue tag) => this.Tag = new UnkownExifTag(tag); + + internal ExifValue(ExifValue other) { - protected ExifValue(ExifTag tag) => this.Tag = tag; + Guard.NotNull(other, nameof(other)); - protected ExifValue(ExifTagValue tag) => this.Tag = new UnkownExifTag(tag); + this.DataType = other.DataType; + this.IsArray = other.IsArray; + this.Tag = other.Tag; - internal ExifValue(ExifValue other) + if (!other.IsArray) + { + // All types are value types except for string which is immutable so safe to simply assign. + this.TrySetValue(other.GetValue()); + } + else { - Guard.NotNull(other, nameof(other)); - - this.DataType = other.DataType; - this.IsArray = other.IsArray; - this.Tag = other.Tag; - - if (!other.IsArray) - { - // All types are value types except for string which is immutable so safe to simply assign. - this.TrySetValue(other.GetValue()); - } - else - { - // All array types are value types so Clone() is sufficient here. - var array = (Array)other.GetValue(); - this.TrySetValue(array?.Clone()); - } + // All array types are value types so Clone() is sufficient here. + var array = (Array)other.GetValue(); + this.TrySetValue(array?.Clone()); } + } - public virtual ExifDataType DataType { get; } + public virtual ExifDataType DataType { get; } - public virtual bool IsArray { get; } + public virtual bool IsArray { get; } - public ExifTag Tag { get; } + public ExifTag Tag { get; } - public static bool operator ==(ExifValue left, ExifTag right) => Equals(left, right); + public static bool operator ==(ExifValue left, ExifTag right) => Equals(left, right); - public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right); + public static bool operator !=(ExifValue left, ExifTag right) => !Equals(left, right); - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (obj is null) { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj is ExifTag tag) - { - return this.Equals(tag); - } - - if (obj is ExifValue value) - { - return this.Tag.Equals(value.Tag) && Equals(this.GetValue(), value.GetValue()); - } - return false; } - [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(ExifTag other) => this.Tag.Equals(other); - - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue()); + if (ReferenceEquals(this, obj)) + { + return true; + } - public abstract object GetValue(); + if (obj is ExifTag tag) + { + return this.Equals(tag); + } - public abstract bool TrySetValue(object value); + if (obj is ExifValue value) + { + return this.Tag.Equals(value.Tag) && Equals(this.GetValue(), value.GetValue()); + } - public abstract IExifValue DeepClone(); + return false; } + + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(ExifTag other) => this.Tag.Equals(other); + + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashCode.Combine(this.Tag, this.GetValue()); + + public abstract object GetValue(); + + public abstract bool TrySetValue(object value); + + public abstract IExifValue DeepClone(); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index 6ce382fe2a..93720fbe94 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -1,582 +1,581 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal static partial class ExifValues { - internal static partial class ExifValues - { - public static ExifValue Create(ExifTagValue tag) => (ExifValue)CreateValue(tag); + public static ExifValue Create(ExifTagValue tag) => (ExifValue)CreateValue(tag); - public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); + public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); - public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); - public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray) + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray) + { + switch (dataType) { - switch (dataType) - { - case ExifDataType.Byte: - return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); - case ExifDataType.DoubleFloat: - return isArray ? new ExifDoubleArray(tag) : new ExifDouble(tag); - case ExifDataType.SingleFloat: - return isArray ? new ExifFloatArray(tag) : new ExifFloat(tag); - case ExifDataType.Long: - return isArray ? new ExifLongArray(tag) : new ExifLong(tag); - case ExifDataType.Long8: - return isArray ? new ExifLong8Array(tag) : new ExifLong8(tag); - case ExifDataType.Rational: - return isArray ? new ExifRationalArray(tag) : new ExifRational(tag); - case ExifDataType.Short: - return isArray ? new ExifShortArray(tag) : new ExifShort(tag); - case ExifDataType.SignedByte: - return isArray ? new ExifSignedByteArray(tag) : new ExifSignedByte(tag); - case ExifDataType.SignedLong: - return isArray ? new ExifSignedLongArray(tag) : new ExifSignedLong(tag); - case ExifDataType.SignedLong8: - return isArray ? new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); - case ExifDataType.SignedRational: - return isArray ? new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); - case ExifDataType.SignedShort: - return isArray ? new ExifSignedShortArray(tag) : new ExifSignedShort(tag); - case ExifDataType.Ascii: - return new ExifString(tag); - case ExifDataType.Undefined: - return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); - default: - return null; - } + case ExifDataType.Byte: + return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + case ExifDataType.DoubleFloat: + return isArray ? new ExifDoubleArray(tag) : new ExifDouble(tag); + case ExifDataType.SingleFloat: + return isArray ? new ExifFloatArray(tag) : new ExifFloat(tag); + case ExifDataType.Long: + return isArray ? new ExifLongArray(tag) : new ExifLong(tag); + case ExifDataType.Long8: + return isArray ? new ExifLong8Array(tag) : new ExifLong8(tag); + case ExifDataType.Rational: + return isArray ? new ExifRationalArray(tag) : new ExifRational(tag); + case ExifDataType.Short: + return isArray ? new ExifShortArray(tag) : new ExifShort(tag); + case ExifDataType.SignedByte: + return isArray ? new ExifSignedByteArray(tag) : new ExifSignedByte(tag); + case ExifDataType.SignedLong: + return isArray ? new ExifSignedLongArray(tag) : new ExifSignedLong(tag); + case ExifDataType.SignedLong8: + return isArray ? new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); + case ExifDataType.SignedRational: + return isArray ? new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); + case ExifDataType.SignedShort: + return isArray ? new ExifSignedShortArray(tag) : new ExifSignedShort(tag); + case ExifDataType.Ascii: + return new ExifString(tag); + case ExifDataType.Undefined: + return isArray ? new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType); + default: + return null; } + } - private static object CreateValue(ExifTagValue tag) + private static object CreateValue(ExifTagValue tag) + { + switch (tag) { - switch (tag) - { - case ExifTagValue.FaxProfile: - return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); - case ExifTagValue.ModeNumber: - return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); - case ExifTagValue.GPSAltitudeRef: - return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); + case ExifTagValue.FaxProfile: + return new ExifByte(ExifTag.FaxProfile, ExifDataType.Byte); + case ExifTagValue.ModeNumber: + return new ExifByte(ExifTag.ModeNumber, ExifDataType.Byte); + case ExifTagValue.GPSAltitudeRef: + return new ExifByte(ExifTag.GPSAltitudeRef, ExifDataType.Byte); - case ExifTagValue.ClipPath: - return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); - case ExifTagValue.VersionYear: - return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); - case ExifTagValue.XMP: - return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); - case ExifTagValue.CFAPattern2: - return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); - case ExifTagValue.TIFFEPStandardID: - return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); - case ExifTagValue.GPSVersionID: - return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); + case ExifTagValue.ClipPath: + return new ExifByteArray(ExifTag.ClipPath, ExifDataType.Byte); + case ExifTagValue.VersionYear: + return new ExifByteArray(ExifTag.VersionYear, ExifDataType.Byte); + case ExifTagValue.XMP: + return new ExifByteArray(ExifTag.XMP, ExifDataType.Byte); + case ExifTagValue.CFAPattern2: + return new ExifByteArray(ExifTag.CFAPattern2, ExifDataType.Byte); + case ExifTagValue.TIFFEPStandardID: + return new ExifByteArray(ExifTag.TIFFEPStandardID, ExifDataType.Byte); + case ExifTagValue.GPSVersionID: + return new ExifByteArray(ExifTag.GPSVersionID, ExifDataType.Byte); - case ExifTagValue.PixelScale: - return new ExifDoubleArray(ExifTag.PixelScale); - case ExifTagValue.IntergraphMatrix: - return new ExifDoubleArray(ExifTag.IntergraphMatrix); - case ExifTagValue.ModelTiePoint: - return new ExifDoubleArray(ExifTag.ModelTiePoint); - case ExifTagValue.ModelTransform: - return new ExifDoubleArray(ExifTag.ModelTransform); + case ExifTagValue.PixelScale: + return new ExifDoubleArray(ExifTag.PixelScale); + case ExifTagValue.IntergraphMatrix: + return new ExifDoubleArray(ExifTag.IntergraphMatrix); + case ExifTagValue.ModelTiePoint: + return new ExifDoubleArray(ExifTag.ModelTiePoint); + case ExifTagValue.ModelTransform: + return new ExifDoubleArray(ExifTag.ModelTransform); - case ExifTagValue.SubfileType: - return new ExifLong(ExifTag.SubfileType); - case ExifTagValue.SubIFDOffset: - return new ExifLong(ExifTag.SubIFDOffset); - case ExifTagValue.GPSIFDOffset: - return new ExifLong(ExifTag.GPSIFDOffset); - case ExifTagValue.T4Options: - return new ExifLong(ExifTag.T4Options); - case ExifTagValue.T6Options: - return new ExifLong(ExifTag.T6Options); - case ExifTagValue.XClipPathUnits: - return new ExifLong(ExifTag.XClipPathUnits); - case ExifTagValue.YClipPathUnits: - return new ExifLong(ExifTag.YClipPathUnits); - case ExifTagValue.ProfileType: - return new ExifLong(ExifTag.ProfileType); - case ExifTagValue.CodingMethods: - return new ExifLong(ExifTag.CodingMethods); - case ExifTagValue.T82ptions: - return new ExifLong(ExifTag.T82ptions); - case ExifTagValue.JPEGInterchangeFormat: - return new ExifLong(ExifTag.JPEGInterchangeFormat); - case ExifTagValue.JPEGInterchangeFormatLength: - return new ExifLong(ExifTag.JPEGInterchangeFormatLength); - case ExifTagValue.MDFileTag: - return new ExifLong(ExifTag.MDFileTag); - case ExifTagValue.StandardOutputSensitivity: - return new ExifLong(ExifTag.StandardOutputSensitivity); - case ExifTagValue.RecommendedExposureIndex: - return new ExifLong(ExifTag.RecommendedExposureIndex); - case ExifTagValue.ISOSpeed: - return new ExifLong(ExifTag.ISOSpeed); - case ExifTagValue.ISOSpeedLatitudeyyy: - return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); - case ExifTagValue.ISOSpeedLatitudezzz: - return new ExifLong(ExifTag.ISOSpeedLatitudezzz); - case ExifTagValue.FaxRecvParams: - return new ExifLong(ExifTag.FaxRecvParams); - case ExifTagValue.FaxRecvTime: - return new ExifLong(ExifTag.FaxRecvTime); - case ExifTagValue.ImageNumber: - return new ExifLong(ExifTag.ImageNumber); + case ExifTagValue.SubfileType: + return new ExifLong(ExifTag.SubfileType); + case ExifTagValue.SubIFDOffset: + return new ExifLong(ExifTag.SubIFDOffset); + case ExifTagValue.GPSIFDOffset: + return new ExifLong(ExifTag.GPSIFDOffset); + case ExifTagValue.T4Options: + return new ExifLong(ExifTag.T4Options); + case ExifTagValue.T6Options: + return new ExifLong(ExifTag.T6Options); + case ExifTagValue.XClipPathUnits: + return new ExifLong(ExifTag.XClipPathUnits); + case ExifTagValue.YClipPathUnits: + return new ExifLong(ExifTag.YClipPathUnits); + case ExifTagValue.ProfileType: + return new ExifLong(ExifTag.ProfileType); + case ExifTagValue.CodingMethods: + return new ExifLong(ExifTag.CodingMethods); + case ExifTagValue.T82ptions: + return new ExifLong(ExifTag.T82ptions); + case ExifTagValue.JPEGInterchangeFormat: + return new ExifLong(ExifTag.JPEGInterchangeFormat); + case ExifTagValue.JPEGInterchangeFormatLength: + return new ExifLong(ExifTag.JPEGInterchangeFormatLength); + case ExifTagValue.MDFileTag: + return new ExifLong(ExifTag.MDFileTag); + case ExifTagValue.StandardOutputSensitivity: + return new ExifLong(ExifTag.StandardOutputSensitivity); + case ExifTagValue.RecommendedExposureIndex: + return new ExifLong(ExifTag.RecommendedExposureIndex); + case ExifTagValue.ISOSpeed: + return new ExifLong(ExifTag.ISOSpeed); + case ExifTagValue.ISOSpeedLatitudeyyy: + return new ExifLong(ExifTag.ISOSpeedLatitudeyyy); + case ExifTagValue.ISOSpeedLatitudezzz: + return new ExifLong(ExifTag.ISOSpeedLatitudezzz); + case ExifTagValue.FaxRecvParams: + return new ExifLong(ExifTag.FaxRecvParams); + case ExifTagValue.FaxRecvTime: + return new ExifLong(ExifTag.FaxRecvTime); + case ExifTagValue.ImageNumber: + return new ExifLong(ExifTag.ImageNumber); - case ExifTagValue.FreeOffsets: - return new ExifLongArray(ExifTag.FreeOffsets); - case ExifTagValue.FreeByteCounts: - return new ExifLongArray(ExifTag.FreeByteCounts); - case ExifTagValue.ColorResponseUnit: - return new ExifLongArray(ExifTag.ColorResponseUnit); - case ExifTagValue.TileOffsets: - return new ExifLongArray(ExifTag.TileOffsets); - case ExifTagValue.SMinSampleValue: - return new ExifLongArray(ExifTag.SMinSampleValue); - case ExifTagValue.SMaxSampleValue: - return new ExifLongArray(ExifTag.SMaxSampleValue); - case ExifTagValue.JPEGQTables: - return new ExifLongArray(ExifTag.JPEGQTables); - case ExifTagValue.JPEGDCTables: - return new ExifLongArray(ExifTag.JPEGDCTables); - case ExifTagValue.JPEGACTables: - return new ExifLongArray(ExifTag.JPEGACTables); - case ExifTagValue.StripRowCounts: - return new ExifLongArray(ExifTag.StripRowCounts); - case ExifTagValue.IntergraphRegisters: - return new ExifLongArray(ExifTag.IntergraphRegisters); - case ExifTagValue.TimeZoneOffset: - return new ExifLongArray(ExifTag.TimeZoneOffset); - case ExifTagValue.SubIFDs: - return new ExifLongArray(ExifTag.SubIFDs); + case ExifTagValue.FreeOffsets: + return new ExifLongArray(ExifTag.FreeOffsets); + case ExifTagValue.FreeByteCounts: + return new ExifLongArray(ExifTag.FreeByteCounts); + case ExifTagValue.ColorResponseUnit: + return new ExifLongArray(ExifTag.ColorResponseUnit); + case ExifTagValue.TileOffsets: + return new ExifLongArray(ExifTag.TileOffsets); + case ExifTagValue.SMinSampleValue: + return new ExifLongArray(ExifTag.SMinSampleValue); + case ExifTagValue.SMaxSampleValue: + return new ExifLongArray(ExifTag.SMaxSampleValue); + case ExifTagValue.JPEGQTables: + return new ExifLongArray(ExifTag.JPEGQTables); + case ExifTagValue.JPEGDCTables: + return new ExifLongArray(ExifTag.JPEGDCTables); + case ExifTagValue.JPEGACTables: + return new ExifLongArray(ExifTag.JPEGACTables); + case ExifTagValue.StripRowCounts: + return new ExifLongArray(ExifTag.StripRowCounts); + case ExifTagValue.IntergraphRegisters: + return new ExifLongArray(ExifTag.IntergraphRegisters); + case ExifTagValue.TimeZoneOffset: + return new ExifLongArray(ExifTag.TimeZoneOffset); + case ExifTagValue.SubIFDs: + return new ExifLongArray(ExifTag.SubIFDs); - case ExifTagValue.ImageWidth: - return new ExifNumber(ExifTag.ImageWidth); - case ExifTagValue.ImageLength: - return new ExifNumber(ExifTag.ImageLength); - case ExifTagValue.RowsPerStrip: - return new ExifNumber(ExifTag.RowsPerStrip); - case ExifTagValue.TileWidth: - return new ExifNumber(ExifTag.TileWidth); - case ExifTagValue.TileLength: - return new ExifNumber(ExifTag.TileLength); - case ExifTagValue.BadFaxLines: - return new ExifNumber(ExifTag.BadFaxLines); - case ExifTagValue.ConsecutiveBadFaxLines: - return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); - case ExifTagValue.PixelXDimension: - return new ExifNumber(ExifTag.PixelXDimension); - case ExifTagValue.PixelYDimension: - return new ExifNumber(ExifTag.PixelYDimension); + case ExifTagValue.ImageWidth: + return new ExifNumber(ExifTag.ImageWidth); + case ExifTagValue.ImageLength: + return new ExifNumber(ExifTag.ImageLength); + case ExifTagValue.RowsPerStrip: + return new ExifNumber(ExifTag.RowsPerStrip); + case ExifTagValue.TileWidth: + return new ExifNumber(ExifTag.TileWidth); + case ExifTagValue.TileLength: + return new ExifNumber(ExifTag.TileLength); + case ExifTagValue.BadFaxLines: + return new ExifNumber(ExifTag.BadFaxLines); + case ExifTagValue.ConsecutiveBadFaxLines: + return new ExifNumber(ExifTag.ConsecutiveBadFaxLines); + case ExifTagValue.PixelXDimension: + return new ExifNumber(ExifTag.PixelXDimension); + case ExifTagValue.PixelYDimension: + return new ExifNumber(ExifTag.PixelYDimension); - case ExifTagValue.StripByteCounts: - return new ExifNumberArray(ExifTag.StripByteCounts); - case ExifTagValue.StripOffsets: - return new ExifNumberArray(ExifTag.StripOffsets); - case ExifTagValue.TileByteCounts: - return new ExifNumberArray(ExifTag.TileByteCounts); - case ExifTagValue.ImageLayer: - return new ExifNumberArray(ExifTag.ImageLayer); + case ExifTagValue.StripByteCounts: + return new ExifNumberArray(ExifTag.StripByteCounts); + case ExifTagValue.StripOffsets: + return new ExifNumberArray(ExifTag.StripOffsets); + case ExifTagValue.TileByteCounts: + return new ExifNumberArray(ExifTag.TileByteCounts); + case ExifTagValue.ImageLayer: + return new ExifNumberArray(ExifTag.ImageLayer); - case ExifTagValue.XPosition: - return new ExifRational(ExifTag.XPosition); - case ExifTagValue.YPosition: - return new ExifRational(ExifTag.YPosition); - case ExifTagValue.XResolution: - return new ExifRational(ExifTag.XResolution); - case ExifTagValue.YResolution: - return new ExifRational(ExifTag.YResolution); - case ExifTagValue.BatteryLevel: - return new ExifRational(ExifTag.BatteryLevel); - case ExifTagValue.ExposureTime: - return new ExifRational(ExifTag.ExposureTime); - case ExifTagValue.FNumber: - return new ExifRational(ExifTag.FNumber); - case ExifTagValue.MDScalePixel: - return new ExifRational(ExifTag.MDScalePixel); - case ExifTagValue.CompressedBitsPerPixel: - return new ExifRational(ExifTag.CompressedBitsPerPixel); - case ExifTagValue.ApertureValue: - return new ExifRational(ExifTag.ApertureValue); - case ExifTagValue.MaxApertureValue: - return new ExifRational(ExifTag.MaxApertureValue); - case ExifTagValue.SubjectDistance: - return new ExifRational(ExifTag.SubjectDistance); - case ExifTagValue.FocalLength: - return new ExifRational(ExifTag.FocalLength); - case ExifTagValue.FlashEnergy2: - return new ExifRational(ExifTag.FlashEnergy2); - case ExifTagValue.FocalPlaneXResolution2: - return new ExifRational(ExifTag.FocalPlaneXResolution2); - case ExifTagValue.FocalPlaneYResolution2: - return new ExifRational(ExifTag.FocalPlaneYResolution2); - case ExifTagValue.ExposureIndex2: - return new ExifRational(ExifTag.ExposureIndex2); - case ExifTagValue.Humidity: - return new ExifRational(ExifTag.Humidity); - case ExifTagValue.Pressure: - return new ExifRational(ExifTag.Pressure); - case ExifTagValue.Acceleration: - return new ExifRational(ExifTag.Acceleration); - case ExifTagValue.FlashEnergy: - return new ExifRational(ExifTag.FlashEnergy); - case ExifTagValue.FocalPlaneXResolution: - return new ExifRational(ExifTag.FocalPlaneXResolution); - case ExifTagValue.FocalPlaneYResolution: - return new ExifRational(ExifTag.FocalPlaneYResolution); - case ExifTagValue.ExposureIndex: - return new ExifRational(ExifTag.ExposureIndex); - case ExifTagValue.DigitalZoomRatio: - return new ExifRational(ExifTag.DigitalZoomRatio); - case ExifTagValue.GPSAltitude: - return new ExifRational(ExifTag.GPSAltitude); - case ExifTagValue.GPSDOP: - return new ExifRational(ExifTag.GPSDOP); - case ExifTagValue.GPSSpeed: - return new ExifRational(ExifTag.GPSSpeed); - case ExifTagValue.GPSTrack: - return new ExifRational(ExifTag.GPSTrack); - case ExifTagValue.GPSImgDirection: - return new ExifRational(ExifTag.GPSImgDirection); - case ExifTagValue.GPSDestBearing: - return new ExifRational(ExifTag.GPSDestBearing); - case ExifTagValue.GPSDestDistance: - return new ExifRational(ExifTag.GPSDestDistance); + case ExifTagValue.XPosition: + return new ExifRational(ExifTag.XPosition); + case ExifTagValue.YPosition: + return new ExifRational(ExifTag.YPosition); + case ExifTagValue.XResolution: + return new ExifRational(ExifTag.XResolution); + case ExifTagValue.YResolution: + return new ExifRational(ExifTag.YResolution); + case ExifTagValue.BatteryLevel: + return new ExifRational(ExifTag.BatteryLevel); + case ExifTagValue.ExposureTime: + return new ExifRational(ExifTag.ExposureTime); + case ExifTagValue.FNumber: + return new ExifRational(ExifTag.FNumber); + case ExifTagValue.MDScalePixel: + return new ExifRational(ExifTag.MDScalePixel); + case ExifTagValue.CompressedBitsPerPixel: + return new ExifRational(ExifTag.CompressedBitsPerPixel); + case ExifTagValue.ApertureValue: + return new ExifRational(ExifTag.ApertureValue); + case ExifTagValue.MaxApertureValue: + return new ExifRational(ExifTag.MaxApertureValue); + case ExifTagValue.SubjectDistance: + return new ExifRational(ExifTag.SubjectDistance); + case ExifTagValue.FocalLength: + return new ExifRational(ExifTag.FocalLength); + case ExifTagValue.FlashEnergy2: + return new ExifRational(ExifTag.FlashEnergy2); + case ExifTagValue.FocalPlaneXResolution2: + return new ExifRational(ExifTag.FocalPlaneXResolution2); + case ExifTagValue.FocalPlaneYResolution2: + return new ExifRational(ExifTag.FocalPlaneYResolution2); + case ExifTagValue.ExposureIndex2: + return new ExifRational(ExifTag.ExposureIndex2); + case ExifTagValue.Humidity: + return new ExifRational(ExifTag.Humidity); + case ExifTagValue.Pressure: + return new ExifRational(ExifTag.Pressure); + case ExifTagValue.Acceleration: + return new ExifRational(ExifTag.Acceleration); + case ExifTagValue.FlashEnergy: + return new ExifRational(ExifTag.FlashEnergy); + case ExifTagValue.FocalPlaneXResolution: + return new ExifRational(ExifTag.FocalPlaneXResolution); + case ExifTagValue.FocalPlaneYResolution: + return new ExifRational(ExifTag.FocalPlaneYResolution); + case ExifTagValue.ExposureIndex: + return new ExifRational(ExifTag.ExposureIndex); + case ExifTagValue.DigitalZoomRatio: + return new ExifRational(ExifTag.DigitalZoomRatio); + case ExifTagValue.GPSAltitude: + return new ExifRational(ExifTag.GPSAltitude); + case ExifTagValue.GPSDOP: + return new ExifRational(ExifTag.GPSDOP); + case ExifTagValue.GPSSpeed: + return new ExifRational(ExifTag.GPSSpeed); + case ExifTagValue.GPSTrack: + return new ExifRational(ExifTag.GPSTrack); + case ExifTagValue.GPSImgDirection: + return new ExifRational(ExifTag.GPSImgDirection); + case ExifTagValue.GPSDestBearing: + return new ExifRational(ExifTag.GPSDestBearing); + case ExifTagValue.GPSDestDistance: + return new ExifRational(ExifTag.GPSDestDistance); - case ExifTagValue.WhitePoint: - return new ExifRationalArray(ExifTag.WhitePoint); - case ExifTagValue.PrimaryChromaticities: - return new ExifRationalArray(ExifTag.PrimaryChromaticities); - case ExifTagValue.YCbCrCoefficients: - return new ExifRationalArray(ExifTag.YCbCrCoefficients); - case ExifTagValue.ReferenceBlackWhite: - return new ExifRationalArray(ExifTag.ReferenceBlackWhite); - case ExifTagValue.GPSLatitude: - return new ExifRationalArray(ExifTag.GPSLatitude); - case ExifTagValue.GPSLongitude: - return new ExifRationalArray(ExifTag.GPSLongitude); - case ExifTagValue.GPSTimestamp: - return new ExifRationalArray(ExifTag.GPSTimestamp); - case ExifTagValue.GPSDestLatitude: - return new ExifRationalArray(ExifTag.GPSDestLatitude); - case ExifTagValue.GPSDestLongitude: - return new ExifRationalArray(ExifTag.GPSDestLongitude); - case ExifTagValue.LensSpecification: - return new ExifRationalArray(ExifTag.LensSpecification); + case ExifTagValue.WhitePoint: + return new ExifRationalArray(ExifTag.WhitePoint); + case ExifTagValue.PrimaryChromaticities: + return new ExifRationalArray(ExifTag.PrimaryChromaticities); + case ExifTagValue.YCbCrCoefficients: + return new ExifRationalArray(ExifTag.YCbCrCoefficients); + case ExifTagValue.ReferenceBlackWhite: + return new ExifRationalArray(ExifTag.ReferenceBlackWhite); + case ExifTagValue.GPSLatitude: + return new ExifRationalArray(ExifTag.GPSLatitude); + case ExifTagValue.GPSLongitude: + return new ExifRationalArray(ExifTag.GPSLongitude); + case ExifTagValue.GPSTimestamp: + return new ExifRationalArray(ExifTag.GPSTimestamp); + case ExifTagValue.GPSDestLatitude: + return new ExifRationalArray(ExifTag.GPSDestLatitude); + case ExifTagValue.GPSDestLongitude: + return new ExifRationalArray(ExifTag.GPSDestLongitude); + case ExifTagValue.LensSpecification: + return new ExifRationalArray(ExifTag.LensSpecification); - case ExifTagValue.OldSubfileType: - return new ExifShort(ExifTag.OldSubfileType); - case ExifTagValue.Compression: - return new ExifShort(ExifTag.Compression); - case ExifTagValue.PhotometricInterpretation: - return new ExifShort(ExifTag.PhotometricInterpretation); - case ExifTagValue.Thresholding: - return new ExifShort(ExifTag.Thresholding); - case ExifTagValue.CellWidth: - return new ExifShort(ExifTag.CellWidth); - case ExifTagValue.CellLength: - return new ExifShort(ExifTag.CellLength); - case ExifTagValue.FillOrder: - return new ExifShort(ExifTag.FillOrder); - case ExifTagValue.Orientation: - return new ExifShort(ExifTag.Orientation); - case ExifTagValue.SamplesPerPixel: - return new ExifShort(ExifTag.SamplesPerPixel); - case ExifTagValue.PlanarConfiguration: - return new ExifShort(ExifTag.PlanarConfiguration); - case ExifTagValue.Predictor: - return new ExifShort(ExifTag.Predictor); - case ExifTagValue.GrayResponseUnit: - return new ExifShort(ExifTag.GrayResponseUnit); - case ExifTagValue.ResolutionUnit: - return new ExifShort(ExifTag.ResolutionUnit); - case ExifTagValue.CleanFaxData: - return new ExifShort(ExifTag.CleanFaxData); - case ExifTagValue.InkSet: - return new ExifShort(ExifTag.InkSet); - case ExifTagValue.NumberOfInks: - return new ExifShort(ExifTag.NumberOfInks); - case ExifTagValue.DotRange: - return new ExifShort(ExifTag.DotRange); - case ExifTagValue.Indexed: - return new ExifShort(ExifTag.Indexed); - case ExifTagValue.OPIProxy: - return new ExifShort(ExifTag.OPIProxy); - case ExifTagValue.JPEGProc: - return new ExifShort(ExifTag.JPEGProc); - case ExifTagValue.JPEGRestartInterval: - return new ExifShort(ExifTag.JPEGRestartInterval); - case ExifTagValue.YCbCrPositioning: - return new ExifShort(ExifTag.YCbCrPositioning); - case ExifTagValue.Rating: - return new ExifShort(ExifTag.Rating); - case ExifTagValue.RatingPercent: - return new ExifShort(ExifTag.RatingPercent); - case ExifTagValue.ExposureProgram: - return new ExifShort(ExifTag.ExposureProgram); - case ExifTagValue.Interlace: - return new ExifShort(ExifTag.Interlace); - case ExifTagValue.SelfTimerMode: - return new ExifShort(ExifTag.SelfTimerMode); - case ExifTagValue.SensitivityType: - return new ExifShort(ExifTag.SensitivityType); - case ExifTagValue.MeteringMode: - return new ExifShort(ExifTag.MeteringMode); - case ExifTagValue.LightSource: - return new ExifShort(ExifTag.LightSource); - case ExifTagValue.FocalPlaneResolutionUnit2: - return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); - case ExifTagValue.SensingMethod2: - return new ExifShort(ExifTag.SensingMethod2); - case ExifTagValue.Flash: - return new ExifShort(ExifTag.Flash); - case ExifTagValue.ColorSpace: - return new ExifShort(ExifTag.ColorSpace); - case ExifTagValue.FocalPlaneResolutionUnit: - return new ExifShort(ExifTag.FocalPlaneResolutionUnit); - case ExifTagValue.SensingMethod: - return new ExifShort(ExifTag.SensingMethod); - case ExifTagValue.CustomRendered: - return new ExifShort(ExifTag.CustomRendered); - case ExifTagValue.ExposureMode: - return new ExifShort(ExifTag.ExposureMode); - case ExifTagValue.WhiteBalance: - return new ExifShort(ExifTag.WhiteBalance); - case ExifTagValue.FocalLengthIn35mmFilm: - return new ExifShort(ExifTag.FocalLengthIn35mmFilm); - case ExifTagValue.SceneCaptureType: - return new ExifShort(ExifTag.SceneCaptureType); - case ExifTagValue.GainControl: - return new ExifShort(ExifTag.GainControl); - case ExifTagValue.Contrast: - return new ExifShort(ExifTag.Contrast); - case ExifTagValue.Saturation: - return new ExifShort(ExifTag.Saturation); - case ExifTagValue.Sharpness: - return new ExifShort(ExifTag.Sharpness); - case ExifTagValue.SubjectDistanceRange: - return new ExifShort(ExifTag.SubjectDistanceRange); - case ExifTagValue.GPSDifferential: - return new ExifShort(ExifTag.GPSDifferential); + case ExifTagValue.OldSubfileType: + return new ExifShort(ExifTag.OldSubfileType); + case ExifTagValue.Compression: + return new ExifShort(ExifTag.Compression); + case ExifTagValue.PhotometricInterpretation: + return new ExifShort(ExifTag.PhotometricInterpretation); + case ExifTagValue.Thresholding: + return new ExifShort(ExifTag.Thresholding); + case ExifTagValue.CellWidth: + return new ExifShort(ExifTag.CellWidth); + case ExifTagValue.CellLength: + return new ExifShort(ExifTag.CellLength); + case ExifTagValue.FillOrder: + return new ExifShort(ExifTag.FillOrder); + case ExifTagValue.Orientation: + return new ExifShort(ExifTag.Orientation); + case ExifTagValue.SamplesPerPixel: + return new ExifShort(ExifTag.SamplesPerPixel); + case ExifTagValue.PlanarConfiguration: + return new ExifShort(ExifTag.PlanarConfiguration); + case ExifTagValue.Predictor: + return new ExifShort(ExifTag.Predictor); + case ExifTagValue.GrayResponseUnit: + return new ExifShort(ExifTag.GrayResponseUnit); + case ExifTagValue.ResolutionUnit: + return new ExifShort(ExifTag.ResolutionUnit); + case ExifTagValue.CleanFaxData: + return new ExifShort(ExifTag.CleanFaxData); + case ExifTagValue.InkSet: + return new ExifShort(ExifTag.InkSet); + case ExifTagValue.NumberOfInks: + return new ExifShort(ExifTag.NumberOfInks); + case ExifTagValue.DotRange: + return new ExifShort(ExifTag.DotRange); + case ExifTagValue.Indexed: + return new ExifShort(ExifTag.Indexed); + case ExifTagValue.OPIProxy: + return new ExifShort(ExifTag.OPIProxy); + case ExifTagValue.JPEGProc: + return new ExifShort(ExifTag.JPEGProc); + case ExifTagValue.JPEGRestartInterval: + return new ExifShort(ExifTag.JPEGRestartInterval); + case ExifTagValue.YCbCrPositioning: + return new ExifShort(ExifTag.YCbCrPositioning); + case ExifTagValue.Rating: + return new ExifShort(ExifTag.Rating); + case ExifTagValue.RatingPercent: + return new ExifShort(ExifTag.RatingPercent); + case ExifTagValue.ExposureProgram: + return new ExifShort(ExifTag.ExposureProgram); + case ExifTagValue.Interlace: + return new ExifShort(ExifTag.Interlace); + case ExifTagValue.SelfTimerMode: + return new ExifShort(ExifTag.SelfTimerMode); + case ExifTagValue.SensitivityType: + return new ExifShort(ExifTag.SensitivityType); + case ExifTagValue.MeteringMode: + return new ExifShort(ExifTag.MeteringMode); + case ExifTagValue.LightSource: + return new ExifShort(ExifTag.LightSource); + case ExifTagValue.FocalPlaneResolutionUnit2: + return new ExifShort(ExifTag.FocalPlaneResolutionUnit2); + case ExifTagValue.SensingMethod2: + return new ExifShort(ExifTag.SensingMethod2); + case ExifTagValue.Flash: + return new ExifShort(ExifTag.Flash); + case ExifTagValue.ColorSpace: + return new ExifShort(ExifTag.ColorSpace); + case ExifTagValue.FocalPlaneResolutionUnit: + return new ExifShort(ExifTag.FocalPlaneResolutionUnit); + case ExifTagValue.SensingMethod: + return new ExifShort(ExifTag.SensingMethod); + case ExifTagValue.CustomRendered: + return new ExifShort(ExifTag.CustomRendered); + case ExifTagValue.ExposureMode: + return new ExifShort(ExifTag.ExposureMode); + case ExifTagValue.WhiteBalance: + return new ExifShort(ExifTag.WhiteBalance); + case ExifTagValue.FocalLengthIn35mmFilm: + return new ExifShort(ExifTag.FocalLengthIn35mmFilm); + case ExifTagValue.SceneCaptureType: + return new ExifShort(ExifTag.SceneCaptureType); + case ExifTagValue.GainControl: + return new ExifShort(ExifTag.GainControl); + case ExifTagValue.Contrast: + return new ExifShort(ExifTag.Contrast); + case ExifTagValue.Saturation: + return new ExifShort(ExifTag.Saturation); + case ExifTagValue.Sharpness: + return new ExifShort(ExifTag.Sharpness); + case ExifTagValue.SubjectDistanceRange: + return new ExifShort(ExifTag.SubjectDistanceRange); + case ExifTagValue.GPSDifferential: + return new ExifShort(ExifTag.GPSDifferential); - case ExifTagValue.BitsPerSample: - return new ExifShortArray(ExifTag.BitsPerSample); - case ExifTagValue.MinSampleValue: - return new ExifShortArray(ExifTag.MinSampleValue); - case ExifTagValue.MaxSampleValue: - return new ExifShortArray(ExifTag.MaxSampleValue); - case ExifTagValue.GrayResponseCurve: - return new ExifShortArray(ExifTag.GrayResponseCurve); - case ExifTagValue.ColorMap: - return new ExifShortArray(ExifTag.ColorMap); - case ExifTagValue.ExtraSamples: - return new ExifShortArray(ExifTag.ExtraSamples); - case ExifTagValue.PageNumber: - return new ExifShortArray(ExifTag.PageNumber); - case ExifTagValue.TransferFunction: - return new ExifShortArray(ExifTag.TransferFunction); - case ExifTagValue.HalftoneHints: - return new ExifShortArray(ExifTag.HalftoneHints); - case ExifTagValue.SampleFormat: - return new ExifShortArray(ExifTag.SampleFormat); - case ExifTagValue.TransferRange: - return new ExifShortArray(ExifTag.TransferRange); - case ExifTagValue.DefaultImageColor: - return new ExifShortArray(ExifTag.DefaultImageColor); - case ExifTagValue.JPEGLosslessPredictors: - return new ExifShortArray(ExifTag.JPEGLosslessPredictors); - case ExifTagValue.JPEGPointTransforms: - return new ExifShortArray(ExifTag.JPEGPointTransforms); - case ExifTagValue.YCbCrSubsampling: - return new ExifShortArray(ExifTag.YCbCrSubsampling); - case ExifTagValue.CFARepeatPatternDim: - return new ExifShortArray(ExifTag.CFARepeatPatternDim); - case ExifTagValue.IntergraphPacketData: - return new ExifShortArray(ExifTag.IntergraphPacketData); - case ExifTagValue.ISOSpeedRatings: - return new ExifShortArray(ExifTag.ISOSpeedRatings); - case ExifTagValue.SubjectArea: - return new ExifShortArray(ExifTag.SubjectArea); - case ExifTagValue.SubjectLocation: - return new ExifShortArray(ExifTag.SubjectLocation); + case ExifTagValue.BitsPerSample: + return new ExifShortArray(ExifTag.BitsPerSample); + case ExifTagValue.MinSampleValue: + return new ExifShortArray(ExifTag.MinSampleValue); + case ExifTagValue.MaxSampleValue: + return new ExifShortArray(ExifTag.MaxSampleValue); + case ExifTagValue.GrayResponseCurve: + return new ExifShortArray(ExifTag.GrayResponseCurve); + case ExifTagValue.ColorMap: + return new ExifShortArray(ExifTag.ColorMap); + case ExifTagValue.ExtraSamples: + return new ExifShortArray(ExifTag.ExtraSamples); + case ExifTagValue.PageNumber: + return new ExifShortArray(ExifTag.PageNumber); + case ExifTagValue.TransferFunction: + return new ExifShortArray(ExifTag.TransferFunction); + case ExifTagValue.HalftoneHints: + return new ExifShortArray(ExifTag.HalftoneHints); + case ExifTagValue.SampleFormat: + return new ExifShortArray(ExifTag.SampleFormat); + case ExifTagValue.TransferRange: + return new ExifShortArray(ExifTag.TransferRange); + case ExifTagValue.DefaultImageColor: + return new ExifShortArray(ExifTag.DefaultImageColor); + case ExifTagValue.JPEGLosslessPredictors: + return new ExifShortArray(ExifTag.JPEGLosslessPredictors); + case ExifTagValue.JPEGPointTransforms: + return new ExifShortArray(ExifTag.JPEGPointTransforms); + case ExifTagValue.YCbCrSubsampling: + return new ExifShortArray(ExifTag.YCbCrSubsampling); + case ExifTagValue.CFARepeatPatternDim: + return new ExifShortArray(ExifTag.CFARepeatPatternDim); + case ExifTagValue.IntergraphPacketData: + return new ExifShortArray(ExifTag.IntergraphPacketData); + case ExifTagValue.ISOSpeedRatings: + return new ExifShortArray(ExifTag.ISOSpeedRatings); + case ExifTagValue.SubjectArea: + return new ExifShortArray(ExifTag.SubjectArea); + case ExifTagValue.SubjectLocation: + return new ExifShortArray(ExifTag.SubjectLocation); - case ExifTagValue.ShutterSpeedValue: - return new ExifSignedRational(ExifTag.ShutterSpeedValue); - case ExifTagValue.BrightnessValue: - return new ExifSignedRational(ExifTag.BrightnessValue); - case ExifTagValue.ExposureBiasValue: - return new ExifSignedRational(ExifTag.ExposureBiasValue); - case ExifTagValue.AmbientTemperature: - return new ExifSignedRational(ExifTag.AmbientTemperature); - case ExifTagValue.WaterDepth: - return new ExifSignedRational(ExifTag.WaterDepth); - case ExifTagValue.CameraElevationAngle: - return new ExifSignedRational(ExifTag.CameraElevationAngle); + case ExifTagValue.ShutterSpeedValue: + return new ExifSignedRational(ExifTag.ShutterSpeedValue); + case ExifTagValue.BrightnessValue: + return new ExifSignedRational(ExifTag.BrightnessValue); + case ExifTagValue.ExposureBiasValue: + return new ExifSignedRational(ExifTag.ExposureBiasValue); + case ExifTagValue.AmbientTemperature: + return new ExifSignedRational(ExifTag.AmbientTemperature); + case ExifTagValue.WaterDepth: + return new ExifSignedRational(ExifTag.WaterDepth); + case ExifTagValue.CameraElevationAngle: + return new ExifSignedRational(ExifTag.CameraElevationAngle); - case ExifTagValue.Decode: - return new ExifSignedRationalArray(ExifTag.Decode); + case ExifTagValue.Decode: + return new ExifSignedRationalArray(ExifTag.Decode); - case ExifTagValue.ImageDescription: - return new ExifString(ExifTag.ImageDescription); - case ExifTagValue.Make: - return new ExifString(ExifTag.Make); - case ExifTagValue.Model: - return new ExifString(ExifTag.Model); - case ExifTagValue.Software: - return new ExifString(ExifTag.Software); - case ExifTagValue.DateTime: - return new ExifString(ExifTag.DateTime); - case ExifTagValue.Artist: - return new ExifString(ExifTag.Artist); - case ExifTagValue.HostComputer: - return new ExifString(ExifTag.HostComputer); - case ExifTagValue.Copyright: - return new ExifString(ExifTag.Copyright); - case ExifTagValue.DocumentName: - return new ExifString(ExifTag.DocumentName); - case ExifTagValue.PageName: - return new ExifString(ExifTag.PageName); - case ExifTagValue.InkNames: - return new ExifString(ExifTag.InkNames); - case ExifTagValue.TargetPrinter: - return new ExifString(ExifTag.TargetPrinter); - case ExifTagValue.ImageID: - return new ExifString(ExifTag.ImageID); - case ExifTagValue.MDLabName: - return new ExifString(ExifTag.MDLabName); - case ExifTagValue.MDSampleInfo: - return new ExifString(ExifTag.MDSampleInfo); - case ExifTagValue.MDPrepDate: - return new ExifString(ExifTag.MDPrepDate); - case ExifTagValue.MDPrepTime: - return new ExifString(ExifTag.MDPrepTime); - case ExifTagValue.MDFileUnits: - return new ExifString(ExifTag.MDFileUnits); - case ExifTagValue.SEMInfo: - return new ExifString(ExifTag.SEMInfo); - case ExifTagValue.SpectralSensitivity: - return new ExifString(ExifTag.SpectralSensitivity); - case ExifTagValue.DateTimeOriginal: - return new ExifString(ExifTag.DateTimeOriginal); - case ExifTagValue.DateTimeDigitized: - return new ExifString(ExifTag.DateTimeDigitized); - case ExifTagValue.SubsecTime: - return new ExifString(ExifTag.SubsecTime); - case ExifTagValue.SubsecTimeOriginal: - return new ExifString(ExifTag.SubsecTimeOriginal); - case ExifTagValue.SubsecTimeDigitized: - return new ExifString(ExifTag.SubsecTimeDigitized); - case ExifTagValue.RelatedSoundFile: - return new ExifString(ExifTag.RelatedSoundFile); - case ExifTagValue.FaxSubaddress: - return new ExifString(ExifTag.FaxSubaddress); - case ExifTagValue.OffsetTime: - return new ExifString(ExifTag.OffsetTime); - case ExifTagValue.OffsetTimeOriginal: - return new ExifString(ExifTag.OffsetTimeOriginal); - case ExifTagValue.OffsetTimeDigitized: - return new ExifString(ExifTag.OffsetTimeDigitized); - case ExifTagValue.SecurityClassification: - return new ExifString(ExifTag.SecurityClassification); - case ExifTagValue.ImageHistory: - return new ExifString(ExifTag.ImageHistory); - case ExifTagValue.ImageUniqueID: - return new ExifString(ExifTag.ImageUniqueID); - case ExifTagValue.OwnerName: - return new ExifString(ExifTag.OwnerName); - case ExifTagValue.SerialNumber: - return new ExifString(ExifTag.SerialNumber); - case ExifTagValue.LensMake: - return new ExifString(ExifTag.LensMake); - case ExifTagValue.LensModel: - return new ExifString(ExifTag.LensModel); - case ExifTagValue.LensSerialNumber: - return new ExifString(ExifTag.LensSerialNumber); - case ExifTagValue.GDALMetadata: - return new ExifString(ExifTag.GDALMetadata); - case ExifTagValue.GDALNoData: - return new ExifString(ExifTag.GDALNoData); - case ExifTagValue.GPSLatitudeRef: - return new ExifString(ExifTag.GPSLatitudeRef); - case ExifTagValue.GPSLongitudeRef: - return new ExifString(ExifTag.GPSLongitudeRef); - case ExifTagValue.GPSSatellites: - return new ExifString(ExifTag.GPSSatellites); - case ExifTagValue.GPSStatus: - return new ExifString(ExifTag.GPSStatus); - case ExifTagValue.GPSMeasureMode: - return new ExifString(ExifTag.GPSMeasureMode); - case ExifTagValue.GPSSpeedRef: - return new ExifString(ExifTag.GPSSpeedRef); - case ExifTagValue.GPSTrackRef: - return new ExifString(ExifTag.GPSTrackRef); - case ExifTagValue.GPSImgDirectionRef: - return new ExifString(ExifTag.GPSImgDirectionRef); - case ExifTagValue.GPSMapDatum: - return new ExifString(ExifTag.GPSMapDatum); - case ExifTagValue.GPSDestLatitudeRef: - return new ExifString(ExifTag.GPSDestLatitudeRef); - case ExifTagValue.GPSDestLongitudeRef: - return new ExifString(ExifTag.GPSDestLongitudeRef); - case ExifTagValue.GPSDestBearingRef: - return new ExifString(ExifTag.GPSDestBearingRef); - case ExifTagValue.GPSDestDistanceRef: - return new ExifString(ExifTag.GPSDestDistanceRef); - case ExifTagValue.GPSDateStamp: - return new ExifString(ExifTag.GPSDateStamp); + case ExifTagValue.ImageDescription: + return new ExifString(ExifTag.ImageDescription); + case ExifTagValue.Make: + return new ExifString(ExifTag.Make); + case ExifTagValue.Model: + return new ExifString(ExifTag.Model); + case ExifTagValue.Software: + return new ExifString(ExifTag.Software); + case ExifTagValue.DateTime: + return new ExifString(ExifTag.DateTime); + case ExifTagValue.Artist: + return new ExifString(ExifTag.Artist); + case ExifTagValue.HostComputer: + return new ExifString(ExifTag.HostComputer); + case ExifTagValue.Copyright: + return new ExifString(ExifTag.Copyright); + case ExifTagValue.DocumentName: + return new ExifString(ExifTag.DocumentName); + case ExifTagValue.PageName: + return new ExifString(ExifTag.PageName); + case ExifTagValue.InkNames: + return new ExifString(ExifTag.InkNames); + case ExifTagValue.TargetPrinter: + return new ExifString(ExifTag.TargetPrinter); + case ExifTagValue.ImageID: + return new ExifString(ExifTag.ImageID); + case ExifTagValue.MDLabName: + return new ExifString(ExifTag.MDLabName); + case ExifTagValue.MDSampleInfo: + return new ExifString(ExifTag.MDSampleInfo); + case ExifTagValue.MDPrepDate: + return new ExifString(ExifTag.MDPrepDate); + case ExifTagValue.MDPrepTime: + return new ExifString(ExifTag.MDPrepTime); + case ExifTagValue.MDFileUnits: + return new ExifString(ExifTag.MDFileUnits); + case ExifTagValue.SEMInfo: + return new ExifString(ExifTag.SEMInfo); + case ExifTagValue.SpectralSensitivity: + return new ExifString(ExifTag.SpectralSensitivity); + case ExifTagValue.DateTimeOriginal: + return new ExifString(ExifTag.DateTimeOriginal); + case ExifTagValue.DateTimeDigitized: + return new ExifString(ExifTag.DateTimeDigitized); + case ExifTagValue.SubsecTime: + return new ExifString(ExifTag.SubsecTime); + case ExifTagValue.SubsecTimeOriginal: + return new ExifString(ExifTag.SubsecTimeOriginal); + case ExifTagValue.SubsecTimeDigitized: + return new ExifString(ExifTag.SubsecTimeDigitized); + case ExifTagValue.RelatedSoundFile: + return new ExifString(ExifTag.RelatedSoundFile); + case ExifTagValue.FaxSubaddress: + return new ExifString(ExifTag.FaxSubaddress); + case ExifTagValue.OffsetTime: + return new ExifString(ExifTag.OffsetTime); + case ExifTagValue.OffsetTimeOriginal: + return new ExifString(ExifTag.OffsetTimeOriginal); + case ExifTagValue.OffsetTimeDigitized: + return new ExifString(ExifTag.OffsetTimeDigitized); + case ExifTagValue.SecurityClassification: + return new ExifString(ExifTag.SecurityClassification); + case ExifTagValue.ImageHistory: + return new ExifString(ExifTag.ImageHistory); + case ExifTagValue.ImageUniqueID: + return new ExifString(ExifTag.ImageUniqueID); + case ExifTagValue.OwnerName: + return new ExifString(ExifTag.OwnerName); + case ExifTagValue.SerialNumber: + return new ExifString(ExifTag.SerialNumber); + case ExifTagValue.LensMake: + return new ExifString(ExifTag.LensMake); + case ExifTagValue.LensModel: + return new ExifString(ExifTag.LensModel); + case ExifTagValue.LensSerialNumber: + return new ExifString(ExifTag.LensSerialNumber); + case ExifTagValue.GDALMetadata: + return new ExifString(ExifTag.GDALMetadata); + case ExifTagValue.GDALNoData: + return new ExifString(ExifTag.GDALNoData); + case ExifTagValue.GPSLatitudeRef: + return new ExifString(ExifTag.GPSLatitudeRef); + case ExifTagValue.GPSLongitudeRef: + return new ExifString(ExifTag.GPSLongitudeRef); + case ExifTagValue.GPSSatellites: + return new ExifString(ExifTag.GPSSatellites); + case ExifTagValue.GPSStatus: + return new ExifString(ExifTag.GPSStatus); + case ExifTagValue.GPSMeasureMode: + return new ExifString(ExifTag.GPSMeasureMode); + case ExifTagValue.GPSSpeedRef: + return new ExifString(ExifTag.GPSSpeedRef); + case ExifTagValue.GPSTrackRef: + return new ExifString(ExifTag.GPSTrackRef); + case ExifTagValue.GPSImgDirectionRef: + return new ExifString(ExifTag.GPSImgDirectionRef); + case ExifTagValue.GPSMapDatum: + return new ExifString(ExifTag.GPSMapDatum); + case ExifTagValue.GPSDestLatitudeRef: + return new ExifString(ExifTag.GPSDestLatitudeRef); + case ExifTagValue.GPSDestLongitudeRef: + return new ExifString(ExifTag.GPSDestLongitudeRef); + case ExifTagValue.GPSDestBearingRef: + return new ExifString(ExifTag.GPSDestBearingRef); + case ExifTagValue.GPSDestDistanceRef: + return new ExifString(ExifTag.GPSDestDistanceRef); + case ExifTagValue.GPSDateStamp: + return new ExifString(ExifTag.GPSDateStamp); - case ExifTagValue.FileSource: - return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); - case ExifTagValue.SceneType: - return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); + case ExifTagValue.FileSource: + return new ExifByte(ExifTag.FileSource, ExifDataType.Undefined); + case ExifTagValue.SceneType: + return new ExifByte(ExifTag.SceneType, ExifDataType.Undefined); - case ExifTagValue.JPEGTables: - return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); - case ExifTagValue.OECF: - return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); - case ExifTagValue.ExifVersion: - return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); - case ExifTagValue.ComponentsConfiguration: - return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); - case ExifTagValue.MakerNote: - return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); - case ExifTagValue.FlashpixVersion: - return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); - case ExifTagValue.SpatialFrequencyResponse: - return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); - case ExifTagValue.SpatialFrequencyResponse2: - return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); - case ExifTagValue.Noise: - return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); - case ExifTagValue.CFAPattern: - return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); - case ExifTagValue.DeviceSettingDescription: - return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); - case ExifTagValue.ImageSourceData: - return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); + case ExifTagValue.JPEGTables: + return new ExifByteArray(ExifTag.JPEGTables, ExifDataType.Undefined); + case ExifTagValue.OECF: + return new ExifByteArray(ExifTag.OECF, ExifDataType.Undefined); + case ExifTagValue.ExifVersion: + return new ExifByteArray(ExifTag.ExifVersion, ExifDataType.Undefined); + case ExifTagValue.ComponentsConfiguration: + return new ExifByteArray(ExifTag.ComponentsConfiguration, ExifDataType.Undefined); + case ExifTagValue.MakerNote: + return new ExifByteArray(ExifTag.MakerNote, ExifDataType.Undefined); + case ExifTagValue.FlashpixVersion: + return new ExifByteArray(ExifTag.FlashpixVersion, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse: + return new ExifByteArray(ExifTag.SpatialFrequencyResponse, ExifDataType.Undefined); + case ExifTagValue.SpatialFrequencyResponse2: + return new ExifByteArray(ExifTag.SpatialFrequencyResponse2, ExifDataType.Undefined); + case ExifTagValue.Noise: + return new ExifByteArray(ExifTag.Noise, ExifDataType.Undefined); + case ExifTagValue.CFAPattern: + return new ExifByteArray(ExifTag.CFAPattern, ExifDataType.Undefined); + case ExifTagValue.DeviceSettingDescription: + return new ExifByteArray(ExifTag.DeviceSettingDescription, ExifDataType.Undefined); + case ExifTagValue.ImageSourceData: + return new ExifByteArray(ExifTag.ImageSourceData, ExifDataType.Undefined); - case ExifTagValue.XPTitle: - return new ExifUcs2String(ExifTag.XPTitle); - case ExifTagValue.XPComment: - return new ExifUcs2String(ExifTag.XPComment); - case ExifTagValue.XPAuthor: - return new ExifUcs2String(ExifTag.XPAuthor); - case ExifTagValue.XPKeywords: - return new ExifUcs2String(ExifTag.XPKeywords); - case ExifTagValue.XPSubject: - return new ExifUcs2String(ExifTag.XPSubject); + case ExifTagValue.XPTitle: + return new ExifUcs2String(ExifTag.XPTitle); + case ExifTagValue.XPComment: + return new ExifUcs2String(ExifTag.XPComment); + case ExifTagValue.XPAuthor: + return new ExifUcs2String(ExifTag.XPAuthor); + case ExifTagValue.XPKeywords: + return new ExifUcs2String(ExifTag.XPKeywords); + case ExifTagValue.XPSubject: + return new ExifUcs2String(ExifTag.XPSubject); - case ExifTagValue.UserComment: - return new ExifEncodedString(ExifTag.UserComment); - case ExifTagValue.GPSProcessingMethod: - return new ExifEncodedString(ExifTag.GPSProcessingMethod); - case ExifTagValue.GPSAreaInformation: - return new ExifEncodedString(ExifTag.GPSAreaInformation); + case ExifTagValue.UserComment: + return new ExifEncodedString(ExifTag.UserComment); + case ExifTagValue.GPSProcessingMethod: + return new ExifEncodedString(ExifTag.GPSProcessingMethod); + case ExifTagValue.GPSAreaInformation: + return new ExifEncodedString(ExifTag.GPSAreaInformation); - default: - return null; - } + default: + return null; } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs index 0cd65a1be0..2dcf1d4363 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValue{TValueType}.cs @@ -1,62 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +internal abstract class ExifValue : ExifValue, IExifValue { - internal abstract class ExifValue : ExifValue, IExifValue + protected ExifValue(ExifTag tag) + : base(tag) { - protected ExifValue(ExifTag tag) - : base(tag) - { - } + } - protected ExifValue(ExifTagValue tag) - : base(tag) - { - } + protected ExifValue(ExifTagValue tag) + : base(tag) + { + } - internal ExifValue(ExifValue value) - : base(value) - { - } + internal ExifValue(ExifValue value) + : base(value) + { + } - public TValueType Value { get; set; } + public TValueType Value { get; set; } - /// - /// Gets the value of the current instance as a string. - /// - protected abstract string StringValue { get; } + /// + /// Gets the value of the current instance as a string. + /// + protected abstract string StringValue { get; } - public override object GetValue() => this.Value; + public override object GetValue() => this.Value; - public override bool TrySetValue(object value) + public override bool TrySetValue(object value) + { + if (value is null) { - if (value is null) - { - this.Value = default; - return true; - } - - // We use type comparison here over "is" to avoid compiler optimizations - // that equate short with ushort, and sbyte with byte. - if (value.GetType() == typeof(TValueType)) - { - this.Value = (TValueType)value; - return true; - } - - return false; + this.Value = default; + return true; } - public override string ToString() + // We use type comparison here over "is" to avoid compiler optimizations + // that equate short with ushort, and sbyte with byte. + if (value.GetType() == typeof(TValueType)) { - if (this.Value == null) - { - return null; - } + this.Value = (TValueType)value; + return true; + } - string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value); - return description ?? this.StringValue; + return false; + } + + public override string ToString() + { + if (this.Value == null) + { + return null; } + + string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, this.Value); + return description ?? this.StringValue; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs index 059a150838..a68cf14d43 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue.cs @@ -1,39 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// A value of the exif profile. +/// +public interface IExifValue : IDeepCloneable { /// - /// A value of the exif profile. + /// Gets the data type of the exif value. /// - public interface IExifValue : IDeepCloneable - { - /// - /// Gets the data type of the exif value. - /// - ExifDataType DataType { get; } + ExifDataType DataType { get; } - /// - /// Gets a value indicating whether the value is an array. - /// - bool IsArray { get; } + /// + /// Gets a value indicating whether the value is an array. + /// + bool IsArray { get; } - /// - /// Gets the tag of the exif value. - /// - ExifTag Tag { get; } + /// + /// Gets the tag of the exif value. + /// + ExifTag Tag { get; } - /// - /// Gets the value of this exif value. - /// - /// The value of this exif value. - object GetValue(); + /// + /// Gets the value of this exif value. + /// + /// The value of this exif value. + object GetValue(); - /// - /// Sets the value of this exif value. - /// - /// The value of this exif value. - /// A value indicating whether the value could be set. - bool TrySetValue(object value); - } + /// + /// Sets the value of this exif value. + /// + /// The value of this exif value. + /// A value indicating whether the value could be set. + bool TrySetValue(object value); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs index a71cb1ba23..d49e80ea00 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/IExifValue{TValueType}.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif; + +/// +/// A value of the exif profile. +/// +/// The type of the value. +public interface IExifValue : IExifValue { /// - /// A value of the exif profile. + /// Gets or sets the value. /// - /// The type of the value. - public interface IExifValue : IExifValue - { - /// - /// Gets or sets the value. - /// - TValueType Value { get; set; } - } + TValueType Value { get; set; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs index 6fa985eb2c..9937f156fd 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccCurveSegment.cs @@ -1,47 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A segment of a curve +/// +internal abstract class IccCurveSegment : IEquatable { /// - /// A segment of a curve + /// Initializes a new instance of the class. /// - internal abstract class IccCurveSegment : IEquatable - { - /// - /// Initializes a new instance of the class. - /// - /// The signature of this segment - protected IccCurveSegment(IccCurveSegmentSignature signature) - => this.Signature = signature; - - /// - /// Gets the signature of this segment - /// - public IccCurveSegmentSignature Signature { get; } - - /// - public virtual bool Equals(IccCurveSegment other) - { - if (other is null) - { - return false; - } + /// The signature of this segment + protected IccCurveSegment(IccCurveSegmentSignature signature) + => this.Signature = signature; - if (ReferenceEquals(this, other)) - { - return true; - } + /// + /// Gets the signature of this segment + /// + public IccCurveSegmentSignature Signature { get; } - return this.Signature == other.Signature; + /// + public virtual bool Equals(IccCurveSegment other) + { + if (other is null) + { + return false; } - /// - public override bool Equals(object obj) => this.Equals(obj as IccCurveSegment); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => this.Signature.GetHashCode(); + return this.Signature == other.Signature; } + + /// + public override bool Equals(object obj) => this.Equals(obj as IccCurveSegment); + + /// + public override int GetHashCode() => this.Signature.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs index 5ad61d4a04..ad442671d1 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccFormulaCurveElement.cs @@ -1,104 +1,101 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A formula based curve segment +/// +internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable { /// - /// A formula based curve segment + /// Initializes a new instance of the class. /// - internal sealed class IccFormulaCurveElement : IccCurveSegment, IEquatable + /// The type of this segment + /// Gamma segment parameter + /// A segment parameter + /// B segment parameter + /// C segment parameter + /// D segment parameter + /// E segment parameter + public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e) + : base(IccCurveSegmentSignature.FormulaCurve) { - /// - /// Initializes a new instance of the class. - /// - /// The type of this segment - /// Gamma segment parameter - /// A segment parameter - /// B segment parameter - /// C segment parameter - /// D segment parameter - /// E segment parameter - public IccFormulaCurveElement(IccFormulaCurveType type, float gamma, float a, float b, float c, float d, float e) - : base(IccCurveSegmentSignature.FormulaCurve) - { - this.Type = type; - this.Gamma = gamma; - this.A = a; - this.B = b; - this.C = c; - this.D = d; - this.E = e; - } + this.Type = type; + this.Gamma = gamma; + this.A = a; + this.B = b; + this.C = c; + this.D = d; + this.E = e; + } - /// - /// Gets the type of this curve - /// - public IccFormulaCurveType Type { get; } + /// + /// Gets the type of this curve + /// + public IccFormulaCurveType Type { get; } - /// - /// Gets the gamma curve parameter - /// - public float Gamma { get; } + /// + /// Gets the gamma curve parameter + /// + public float Gamma { get; } - /// - /// Gets the A curve parameter - /// - public float A { get; } + /// + /// Gets the A curve parameter + /// + public float A { get; } - /// - /// Gets the B curve parameter - /// - public float B { get; } + /// + /// Gets the B curve parameter + /// + public float B { get; } - /// - /// Gets the C curve parameter - /// - public float C { get; } + /// + /// Gets the C curve parameter + /// + public float C { get; } - /// - /// Gets the D curve parameter - /// - public float D { get; } + /// + /// Gets the D curve parameter + /// + public float D { get; } - /// - /// Gets the E curve parameter - /// - public float E { get; } + /// + /// Gets the E curve parameter + /// + public float E { get; } - /// - public override bool Equals(IccCurveSegment other) + /// + public override bool Equals(IccCurveSegment other) + { + if (base.Equals(other) && other is IccFormulaCurveElement segment) { - if (base.Equals(other) && other is IccFormulaCurveElement segment) - { - return this.Type == segment.Type - && this.Gamma == segment.Gamma - && this.A == segment.A - && this.B == segment.B - && this.C == segment.C - && this.D == segment.D - && this.E == segment.E; - } - - return false; + return this.Type == segment.Type + && this.Gamma == segment.Gamma + && this.A == segment.A + && this.B == segment.B + && this.C == segment.C + && this.D == segment.D + && this.E == segment.E; } - /// - public bool Equals(IccFormulaCurveElement other) => this.Equals((IccCurveSegment)other); + return false; + } - /// - public override bool Equals(object obj) => this.Equals(obj as IccFormulaCurveElement); + /// + public bool Equals(IccFormulaCurveElement other) => this.Equals((IccCurveSegment)other); - /// - public override int GetHashCode() - => HashCode.Combine( - this.Type, - this.Gamma, - this.A, - this.B, - this.C, - this.D, - this.E); - } + /// + public override bool Equals(object obj) => this.Equals(obj as IccFormulaCurveElement); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Type, + this.Gamma, + this.A, + this.B, + this.C, + this.D, + this.E); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs index d84a18f902..fc3c9030ee 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccOneDimensionalCurve.cs @@ -1,65 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A one dimensional ICC curve. +/// +internal sealed class IccOneDimensionalCurve : IEquatable { /// - /// A one dimensional ICC curve. + /// Initializes a new instance of the class. /// - internal sealed class IccOneDimensionalCurve : IEquatable + /// The break points of this curve + /// The segments of this curve + public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments) { - /// - /// Initializes a new instance of the class. - /// - /// The break points of this curve - /// The segments of this curve - public IccOneDimensionalCurve(float[] breakPoints, IccCurveSegment[] segments) - { - Guard.NotNull(breakPoints, nameof(breakPoints)); - Guard.NotNull(segments, nameof(segments)); + Guard.NotNull(breakPoints, nameof(breakPoints)); + Guard.NotNull(segments, nameof(segments)); - bool isSizeCorrect = breakPoints.Length == segments.Length - 1; - Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments"); + bool isSizeCorrect = breakPoints.Length == segments.Length - 1; + Guard.IsTrue(isSizeCorrect, $"{nameof(breakPoints)},{nameof(segments)}", "Number of BreakPoints must be one less than number of Segments"); - this.BreakPoints = breakPoints; - this.Segments = segments; - } + this.BreakPoints = breakPoints; + this.Segments = segments; + } - /// - /// Gets the breakpoints that separate two curve segments - /// - public float[] BreakPoints { get; } + /// + /// Gets the breakpoints that separate two curve segments + /// + public float[] BreakPoints { get; } - /// - /// Gets an array of curve segments - /// - public IccCurveSegment[] Segments { get; } + /// + /// Gets an array of curve segments + /// + public IccCurveSegment[] Segments { get; } - /// - public bool Equals(IccOneDimensionalCurve other) + /// + public bool Equals(IccOneDimensionalCurve other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints) - && this.Segments.AsSpan().SequenceEqual(other.Segments); + return false; } - /// - public override bool Equals(object obj) - => this.Equals(obj as IccOneDimensionalCurve); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() - => HashCode.Combine(this.BreakPoints, this.Segments); + return this.BreakPoints.AsSpan().SequenceEqual(other.BreakPoints) + && this.Segments.AsSpan().SequenceEqual(other.Segments); } + + /// + public override bool Equals(object obj) + => this.Equals(obj as IccOneDimensionalCurve); + + /// + public override int GetHashCode() + => HashCode.Combine(this.BreakPoints, this.Segments); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs index 65f3985d9c..9ce1876269 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccParametricCurve.cs @@ -1,168 +1,165 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A parametric curve +/// +internal sealed class IccParametricCurve : IEquatable { /// - /// A parametric curve + /// Initializes a new instance of the class. /// - internal sealed class IccParametricCurve : IEquatable + /// G curve parameter + public IccParametricCurve(float g) + : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0) { - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - public IccParametricCurve(float g) - : this(IccParametricCurveType.Type1, g, 0, 0, 0, 0, 0, 0) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - public IccParametricCurve(float g, float a, float b) - : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0) - { - } + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + public IccParametricCurve(float g, float a, float b) + : this(IccParametricCurveType.Cie122_1996, g, a, b, 0, 0, 0, 0) + { + } - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - public IccParametricCurve(float g, float a, float b, float c) - : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0) - { - } + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + public IccParametricCurve(float g, float a, float b, float c) + : this(IccParametricCurveType.Iec61966_3, g, a, b, c, 0, 0, 0) + { + } - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - /// D curve parameter - public IccParametricCurve(float g, float a, float b, float c, float d) - : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0) - { - } + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + public IccParametricCurve(float g, float a, float b, float c, float d) + : this(IccParametricCurveType.SRgb, g, a, b, c, d, 0, 0) + { + } - /// - /// Initializes a new instance of the class. - /// - /// G curve parameter - /// A curve parameter - /// B curve parameter - /// C curve parameter - /// D curve parameter - /// E curve parameter - /// F curve parameter - public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f) - : this(IccParametricCurveType.Type5, g, a, b, c, d, e, f) - { - } + /// + /// Initializes a new instance of the class. + /// + /// G curve parameter + /// A curve parameter + /// B curve parameter + /// C curve parameter + /// D curve parameter + /// E curve parameter + /// F curve parameter + public IccParametricCurve(float g, float a, float b, float c, float d, float e, float f) + : this(IccParametricCurveType.Type5, g, a, b, c, d, e, f) + { + } - private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f) - { - this.Type = type; - this.G = g; - this.A = a; - this.B = b; - this.C = c; - this.D = d; - this.E = e; - this.F = f; - } + private IccParametricCurve(IccParametricCurveType type, float g, float a, float b, float c, float d, float e, float f) + { + this.Type = type; + this.G = g; + this.A = a; + this.B = b; + this.C = c; + this.D = d; + this.E = e; + this.F = f; + } - /// - /// Gets the type of this curve - /// - public IccParametricCurveType Type { get; } - - /// - /// Gets the G curve parameter - /// - public float G { get; } - - /// - /// Gets the A curve parameter - /// - public float A { get; } - - /// - /// Gets the B curve parameter - /// - public float B { get; } - - /// - /// Gets the C curve parameter - /// - public float C { get; } - - /// - /// Gets the D curve parameter - /// - public float D { get; } - - /// - /// Gets the E curve parameter - /// - public float E { get; } - - /// - /// Gets the F curve parameter - /// - public float F { get; } - - /// - public bool Equals(IccParametricCurve other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Type == other.Type - && this.G.Equals(other.G) - && this.A.Equals(other.A) - && this.B.Equals(other.B) - && this.C.Equals(other.C) - && this.D.Equals(other.D) - && this.E.Equals(other.E) - && this.F.Equals(other.F); - } + /// + /// Gets the type of this curve + /// + public IccParametricCurveType Type { get; } + + /// + /// Gets the G curve parameter + /// + public float G { get; } + + /// + /// Gets the A curve parameter + /// + public float A { get; } + + /// + /// Gets the B curve parameter + /// + public float B { get; } + + /// + /// Gets the C curve parameter + /// + public float C { get; } + + /// + /// Gets the D curve parameter + /// + public float D { get; } + + /// + /// Gets the E curve parameter + /// + public float E { get; } + + /// + /// Gets the F curve parameter + /// + public float F { get; } - /// - public override bool Equals(object obj) + /// + public bool Equals(IccParametricCurve other) + { + if (other is null) { - return obj is IccParametricCurve other && this.Equals(other); + return false; } - /// - public override int GetHashCode() + if (ReferenceEquals(this, other)) { - return HashCode.Combine( - this.Type, - this.G.GetHashCode(), - this.A.GetHashCode(), - this.B.GetHashCode(), - this.C.GetHashCode(), - this.D.GetHashCode(), - this.E.GetHashCode(), - this.F.GetHashCode()); + return true; } + + return this.Type == other.Type + && this.G.Equals(other.G) + && this.A.Equals(other.A) + && this.B.Equals(other.B) + && this.C.Equals(other.C) + && this.D.Equals(other.D) + && this.E.Equals(other.E) + && this.F.Equals(other.F); + } + + /// + public override bool Equals(object obj) + { + return obj is IccParametricCurve other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Type, + this.G.GetHashCode(), + this.A.GetHashCode(), + this.B.GetHashCode(), + this.C.GetHashCode(), + this.D.GetHashCode(), + this.E.GetHashCode(), + this.F.GetHashCode()); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs index 4f4a652b5a..969e359591 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccResponseCurve.cs @@ -1,96 +1,94 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// A response curve +/// +internal sealed class IccResponseCurve : IEquatable { /// - /// A response curve + /// Initializes a new instance of the class. /// - internal sealed class IccResponseCurve : IEquatable + /// The type of this curve + /// The XYZ values + /// The response arrays + public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays) { - /// - /// Initializes a new instance of the class. - /// - /// The type of this curve - /// The XYZ values - /// The response arrays - public IccResponseCurve(IccCurveMeasurementEncodings curveType, Vector3[] xyzValues, IccResponseNumber[][] responseArrays) - { - Guard.NotNull(xyzValues, nameof(xyzValues)); - Guard.NotNull(responseArrays, nameof(responseArrays)); + Guard.NotNull(xyzValues, nameof(xyzValues)); + Guard.NotNull(responseArrays, nameof(responseArrays)); - Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length"); - Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues)); + Guard.IsTrue(xyzValues.Length == responseArrays.Length, $"{nameof(xyzValues)},{nameof(responseArrays)}", "Arrays must have same length"); + Guard.MustBeBetweenOrEqualTo(xyzValues.Length, 1, 15, nameof(xyzValues)); - this.CurveType = curveType; - this.XyzValues = xyzValues; - this.ResponseArrays = responseArrays; - } + this.CurveType = curveType; + this.XyzValues = xyzValues; + this.ResponseArrays = responseArrays; + } - /// - /// Gets the type of this curve - /// - public IccCurveMeasurementEncodings CurveType { get; } + /// + /// Gets the type of this curve + /// + public IccCurveMeasurementEncodings CurveType { get; } - /// - /// Gets the XYZ values - /// - public Vector3[] XyzValues { get; } + /// + /// Gets the XYZ values + /// + public Vector3[] XyzValues { get; } - /// - /// Gets the response arrays - /// - public IccResponseNumber[][] ResponseArrays { get; } + /// + /// Gets the response arrays + /// + public IccResponseNumber[][] ResponseArrays { get; } - /// - public bool Equals(IccResponseCurve other) + /// + public bool Equals(IccResponseCurve other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } + return false; + } - return this.CurveType == other.CurveType - && this.XyzValues.AsSpan().SequenceEqual(other.XyzValues) - && this.EqualsResponseArray(other); + if (ReferenceEquals(this, other)) + { + return true; } - /// - public override bool Equals(object obj) => obj is IccResponseCurve other && this.Equals(other); + return this.CurveType == other.CurveType + && this.XyzValues.AsSpan().SequenceEqual(other.XyzValues) + && this.EqualsResponseArray(other); + } + + /// + public override bool Equals(object obj) => obj is IccResponseCurve other && this.Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.CurveType, + this.XyzValues, + this.ResponseArrays); + } - /// - public override int GetHashCode() + private bool EqualsResponseArray(IccResponseCurve other) + { + if (this.ResponseArrays.Length != other.ResponseArrays.Length) { - return HashCode.Combine( - this.CurveType, - this.XyzValues, - this.ResponseArrays); + return false; } - private bool EqualsResponseArray(IccResponseCurve other) + for (int i = 0; i < this.ResponseArrays.Length; i++) { - if (this.ResponseArrays.Length != other.ResponseArrays.Length) + if (!this.ResponseArrays[i].AsSpan().SequenceEqual(other.ResponseArrays[i])) { return false; } - - for (int i = 0; i < this.ResponseArrays.Length; i++) - { - if (!this.ResponseArrays[i].AsSpan().SequenceEqual(other.ResponseArrays[i])) - { - return false; - } - } - - return true; } + + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs index a305eea31d..3fcde5ad2d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Curves/IccSampledCurveElement.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A sampled curve segment +/// +internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable { /// - /// A sampled curve segment + /// Initializes a new instance of the class. /// - internal sealed class IccSampledCurveElement : IccCurveSegment, IEquatable + /// The curve values of this segment + public IccSampledCurveElement(float[] curveEntries) + : base(IccCurveSegmentSignature.SampledCurve) { - /// - /// Initializes a new instance of the class. - /// - /// The curve values of this segment - public IccSampledCurveElement(float[] curveEntries) - : base(IccCurveSegmentSignature.SampledCurve) - { - Guard.NotNull(curveEntries, nameof(curveEntries)); - Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value"); + Guard.NotNull(curveEntries, nameof(curveEntries)); + Guard.IsTrue(curveEntries.Length > 0, nameof(curveEntries), "There must be at least one value"); - this.CurveEntries = curveEntries; - } + this.CurveEntries = curveEntries; + } - /// - /// Gets the curve values of this segment - /// - public float[] CurveEntries { get; } + /// + /// Gets the curve values of this segment + /// + public float[] CurveEntries { get; } - /// - public override bool Equals(IccCurveSegment other) + /// + public override bool Equals(IccCurveSegment other) + { + if (base.Equals(other) && other is IccSampledCurveElement segment) { - if (base.Equals(other) && other is IccSampledCurveElement segment) - { - return this.CurveEntries.AsSpan().SequenceEqual(segment.CurveEntries); - } - - return false; + return this.CurveEntries.AsSpan().SequenceEqual(segment.CurveEntries); } - /// - public bool Equals(IccSampledCurveElement other) - => this.Equals((IccCurveSegment)other); + return false; + } - /// - public override bool Equals(object obj) - => this.Equals(obj as IccSampledCurveElement); + /// + public bool Equals(IccSampledCurveElement other) + => this.Equals((IccCurveSegment)other); - /// - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.CurveEntries); - } + /// + public override bool Equals(object obj) + => this.Equals(obj as IccSampledCurveElement); + + /// + public override int GetHashCode() + => HashCode.Combine(base.GetHashCode(), this.CurveEntries); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs index e2813b3e34..3cf66c0c16 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Curves.cs @@ -3,217 +3,216 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads a /// - internal sealed partial class IccDataReader + /// The read curve + public IccOneDimensionalCurve ReadOneDimensionalCurve() { - /// - /// Reads a - /// - /// The read curve - public IccOneDimensionalCurve ReadOneDimensionalCurve() - { - ushort segmentCount = this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - var breakPoints = new float[segmentCount - 1]; - for (int i = 0; i < breakPoints.Length; i++) - { - breakPoints[i] = this.ReadSingle(); - } - - var segments = new IccCurveSegment[segmentCount]; - for (int i = 0; i < segmentCount; i++) - { - segments[i] = this.ReadCurveSegment(); - } - - return new IccOneDimensionalCurve(breakPoints, segments); + ushort segmentCount = this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + var breakPoints = new float[segmentCount - 1]; + for (int i = 0; i < breakPoints.Length; i++) + { + breakPoints[i] = this.ReadSingle(); } - /// - /// Reads a - /// - /// The number of channels - /// The read curve - public IccResponseCurve ReadResponseCurve(int channelCount) + var segments = new IccCurveSegment[segmentCount]; + for (int i = 0; i < segmentCount; i++) { - var type = (IccCurveMeasurementEncodings)this.ReadUInt32(); - var measurement = new uint[channelCount]; - for (int i = 0; i < channelCount; i++) - { - measurement[i] = this.ReadUInt32(); - } - - var xyzValues = new Vector3[channelCount]; - for (int i = 0; i < channelCount; i++) - { - xyzValues[i] = this.ReadXyzNumber(); - } + segments[i] = this.ReadCurveSegment(); + } - var response = new IccResponseNumber[channelCount][]; - for (int i = 0; i < channelCount; i++) - { - response[i] = new IccResponseNumber[measurement[i]]; - for (uint j = 0; j < measurement[i]; j++) - { - response[i][j] = this.ReadResponseNumber(); - } - } + return new IccOneDimensionalCurve(breakPoints, segments); + } - return new IccResponseCurve(type, xyzValues, response); + /// + /// Reads a + /// + /// The number of channels + /// The read curve + public IccResponseCurve ReadResponseCurve(int channelCount) + { + var type = (IccCurveMeasurementEncodings)this.ReadUInt32(); + var measurement = new uint[channelCount]; + for (int i = 0; i < channelCount; i++) + { + measurement[i] = this.ReadUInt32(); } - /// - /// Reads a - /// - /// The read curve - public IccParametricCurve ReadParametricCurve() + var xyzValues = new Vector3[channelCount]; + for (int i = 0; i < channelCount; i++) { - ushort type = this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - float gamma, a, b, c, d, e, f; - gamma = a = b = c = d = e = f = 0; + xyzValues[i] = this.ReadXyzNumber(); + } - if (type <= 4) + var response = new IccResponseNumber[channelCount][]; + for (int i = 0; i < channelCount; i++) + { + response[i] = new IccResponseNumber[measurement[i]]; + for (uint j = 0; j < measurement[i]; j++) { - gamma = this.ReadFix16(); + response[i][j] = this.ReadResponseNumber(); } + } - if (type > 0 && type <= 4) - { - a = this.ReadFix16(); - b = this.ReadFix16(); - } + return new IccResponseCurve(type, xyzValues, response); + } - if (type > 1 && type <= 4) - { - c = this.ReadFix16(); - } + /// + /// Reads a + /// + /// The read curve + public IccParametricCurve ReadParametricCurve() + { + ushort type = this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + float gamma, a, b, c, d, e, f; + gamma = a = b = c = d = e = f = 0; - if (type > 2 && type <= 4) - { - d = this.ReadFix16(); - } + if (type <= 4) + { + gamma = this.ReadFix16(); + } - if (type == 4) - { - e = this.ReadFix16(); - f = this.ReadFix16(); - } + if (type > 0 && type <= 4) + { + a = this.ReadFix16(); + b = this.ReadFix16(); + } - switch (type) - { - case 0: return new IccParametricCurve(gamma); - case 1: return new IccParametricCurve(gamma, a, b); - case 2: return new IccParametricCurve(gamma, a, b, c); - case 3: return new IccParametricCurve(gamma, a, b, c, d); - case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f); - default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}"); - } + if (type > 1 && type <= 4) + { + c = this.ReadFix16(); } - /// - /// Reads a - /// - /// The read segment - public IccCurveSegment ReadCurveSegment() + if (type > 2 && type <= 4) { - var signature = (IccCurveSegmentSignature)this.ReadUInt32(); - this.AddIndex(4); // 4 bytes reserved + d = this.ReadFix16(); + } - switch (signature) - { - case IccCurveSegmentSignature.FormulaCurve: - return this.ReadFormulaCurveElement(); - case IccCurveSegmentSignature.SampledCurve: - return this.ReadSampledCurveElement(); - default: - throw new InvalidIccProfileException($"Invalid curve segment type of {signature}"); - } + if (type == 4) + { + e = this.ReadFix16(); + f = this.ReadFix16(); } - /// - /// Reads a - /// - /// The read segment - public IccFormulaCurveElement ReadFormulaCurveElement() + switch (type) { - var type = (IccFormulaCurveType)this.ReadUInt16(); - this.AddIndex(2); // 2 bytes reserved - float gamma, a, b, c, d, e; - gamma = d = e = 0; + case 0: return new IccParametricCurve(gamma); + case 1: return new IccParametricCurve(gamma, a, b); + case 2: return new IccParametricCurve(gamma, a, b, c); + case 3: return new IccParametricCurve(gamma, a, b, c, d); + case 4: return new IccParametricCurve(gamma, a, b, c, d, e, f); + default: throw new InvalidIccProfileException($"Invalid parametric curve type of {type}"); + } + } - if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2) - { - gamma = this.ReadSingle(); - } + /// + /// Reads a + /// + /// The read segment + public IccCurveSegment ReadCurveSegment() + { + var signature = (IccCurveSegmentSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes reserved - a = this.ReadSingle(); - b = this.ReadSingle(); - c = this.ReadSingle(); + switch (signature) + { + case IccCurveSegmentSignature.FormulaCurve: + return this.ReadFormulaCurveElement(); + case IccCurveSegmentSignature.SampledCurve: + return this.ReadSampledCurveElement(); + default: + throw new InvalidIccProfileException($"Invalid curve segment type of {signature}"); + } + } - if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3) - { - d = this.ReadSingle(); - } + /// + /// Reads a + /// + /// The read segment + public IccFormulaCurveElement ReadFormulaCurveElement() + { + var type = (IccFormulaCurveType)this.ReadUInt16(); + this.AddIndex(2); // 2 bytes reserved + float gamma, a, b, c, d, e; + gamma = d = e = 0; - if (type == IccFormulaCurveType.Type3) - { - e = this.ReadSingle(); - } + if (type == IccFormulaCurveType.Type1 || type == IccFormulaCurveType.Type2) + { + gamma = this.ReadSingle(); + } - return new IccFormulaCurveElement(type, gamma, a, b, c, d, e); + a = this.ReadSingle(); + b = this.ReadSingle(); + c = this.ReadSingle(); + + if (type == IccFormulaCurveType.Type2 || type == IccFormulaCurveType.Type3) + { + d = this.ReadSingle(); } - /// - /// Reads a - /// - /// The read segment - public IccSampledCurveElement ReadSampledCurveElement() + if (type == IccFormulaCurveType.Type3) { - uint count = this.ReadUInt32(); - var entries = new float[count]; - for (int i = 0; i < count; i++) - { - entries[i] = this.ReadSingle(); - } + e = this.ReadSingle(); + } - return new IccSampledCurveElement(entries); + return new IccFormulaCurveElement(type, gamma, a, b, c, d, e); + } + + /// + /// Reads a + /// + /// The read segment + public IccSampledCurveElement ReadSampledCurveElement() + { + uint count = this.ReadUInt32(); + var entries = new float[count]; + for (int i = 0; i < count; i++) + { + entries[i] = this.ReadSingle(); } - /// - /// Reads curve data - /// - /// Number of input channels - /// The curve data - private IccTagDataEntry[] ReadCurves(int count) + return new IccSampledCurveElement(entries); + } + + /// + /// Reads curve data + /// + /// Number of input channels + /// The curve data + private IccTagDataEntry[] ReadCurves(int count) + { + var tdata = new IccTagDataEntry[count]; + for (int i = 0; i < count; i++) { - var tdata = new IccTagDataEntry[count]; - for (int i = 0; i < count; i++) + IccTypeSignature type = this.ReadTagDataEntryHeader(); + if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve) { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - if (type != IccTypeSignature.Curve && type != IccTypeSignature.ParametricCurve) - { - throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + - $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); - } - - if (type == IccTypeSignature.Curve) - { - tdata[i] = this.ReadCurveTagDataEntry(); - } - else if (type == IccTypeSignature.ParametricCurve) - { - tdata[i] = this.ReadParametricCurveTagDataEntry(); - } - - this.AddPadding(); + throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + + $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); } - return tdata; + if (type == IccTypeSignature.Curve) + { + tdata[i] = this.ReadCurveTagDataEntry(); + } + else if (type == IccTypeSignature.ParametricCurve) + { + tdata[i] = this.ReadParametricCurveTagDataEntry(); + } + + this.AddPadding(); } + + return tdata; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs index 2f35a38612..e88dd8d9e1 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Lut.cs @@ -1,171 +1,168 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads an 8bit lookup table /// - internal sealed partial class IccDataReader + /// The read LUT + public IccLut ReadLut8() { - /// - /// Reads an 8bit lookup table - /// - /// The read LUT - public IccLut ReadLut8() + return new IccLut(this.ReadBytes(256)); + } + + /// + /// Reads a 16bit lookup table + /// + /// The number of entries + /// The read LUT + public IccLut ReadLut16(int count) + { + var values = new ushort[count]; + for (int i = 0; i < count; i++) { - return new IccLut(this.ReadBytes(256)); + values[i] = this.ReadUInt16(); } - /// - /// Reads a 16bit lookup table - /// - /// The number of entries - /// The read LUT - public IccLut ReadLut16(int count) - { - var values = new ushort[count]; - for (int i = 0; i < count; i++) - { - values[i] = this.ReadUInt16(); - } + return new IccLut(values); + } - return new IccLut(values); - } + /// + /// Reads a CLUT depending on type + /// + /// Input channel count + /// Output channel count + /// If true, it's read as CLUTf32, + /// else read as either CLUT8 or CLUT16 depending on embedded information + /// The read CLUT + public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat) + { + // Grid-points are always 16 bytes long but only 0-inChCount are used + var gridPointCount = new byte[inChannelCount]; + Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount); - /// - /// Reads a CLUT depending on type - /// - /// Input channel count - /// Output channel count - /// If true, it's read as CLUTf32, - /// else read as either CLUT8 or CLUT16 depending on embedded information - /// The read CLUT - public IccClut ReadClut(int inChannelCount, int outChannelCount, bool isFloat) + if (!isFloat) { - // Grid-points are always 16 bytes long but only 0-inChCount are used - var gridPointCount = new byte[inChannelCount]; - Buffer.BlockCopy(this.data, this.AddIndex(16), gridPointCount, 0, inChannelCount); + byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved + if (size == 1) + { + return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + } - if (!isFloat) + if (size == 2) { - byte size = this.data[this.AddIndex(4)]; // First byte is info, last 3 bytes are reserved - if (size == 1) - { - return this.ReadClut8(inChannelCount, outChannelCount, gridPointCount); - } - - if (size == 2) - { - return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount); - } - - throw new InvalidIccProfileException($"Invalid CLUT size of {size}"); + return this.ReadClut16(inChannelCount, outChannelCount, gridPointCount); } - return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + throw new InvalidIccProfileException($"Invalid CLUT size of {size}"); } - /// - /// Reads an 8 bit CLUT - /// - /// Input channel count - /// Output channel count - /// Grid point count for each CLUT channel - /// The read CLUT8 - public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount) + return this.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + } + + /// + /// Reads an 8 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT8 + public IccClut ReadClut8(int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChannelCount; i++) { - int start = this.currentIndex; - int length = 0; - for (int i = 0; i < inChannelCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChannelCount); - } + length += (int)Math.Pow(gridPointCount[i], inChannelCount); + } - length /= inChannelCount; + length /= inChannelCount; - const float Max = byte.MaxValue; + const float Max = byte.MaxValue; - var values = new float[length][]; - for (int i = 0; i < length; i++) + var values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChannelCount]; + for (int j = 0; j < outChannelCount; j++) { - values[i] = new float[outChannelCount]; - for (int j = 0; j < outChannelCount; j++) - { - values[i][j] = this.data[this.currentIndex++] / Max; - } + values[i][j] = this.data[this.currentIndex++] / Max; } - - this.currentIndex = start + (length * outChannelCount); - return new IccClut(values, gridPointCount, IccClutDataType.UInt8); } - /// - /// Reads a 16 bit CLUT - /// - /// Input channel count - /// Output channel count - /// Grid point count for each CLUT channel - /// The read CLUT16 - public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount) + this.currentIndex = start + (length * outChannelCount); + return new IccClut(values, gridPointCount, IccClutDataType.UInt8); + } + + /// + /// Reads a 16 bit CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUT16 + public IccClut ReadClut16(int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChannelCount; i++) { - int start = this.currentIndex; - int length = 0; - for (int i = 0; i < inChannelCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChannelCount); - } + length += (int)Math.Pow(gridPointCount[i], inChannelCount); + } - length /= inChannelCount; + length /= inChannelCount; - const float Max = ushort.MaxValue; + const float Max = ushort.MaxValue; - var values = new float[length][]; - for (int i = 0; i < length; i++) + var values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChannelCount]; + for (int j = 0; j < outChannelCount; j++) { - values[i] = new float[outChannelCount]; - for (int j = 0; j < outChannelCount; j++) - { - values[i][j] = this.ReadUInt16() / Max; - } + values[i][j] = this.ReadUInt16() / Max; } - - this.currentIndex = start + (length * outChannelCount * 2); - return new IccClut(values, gridPointCount, IccClutDataType.UInt16); } - /// - /// Reads a 32bit floating point CLUT - /// - /// Input channel count - /// Output channel count - /// Grid point count for each CLUT channel - /// The read CLUTf32 - public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount) + this.currentIndex = start + (length * outChannelCount * 2); + return new IccClut(values, gridPointCount, IccClutDataType.UInt16); + } + + /// + /// Reads a 32bit floating point CLUT + /// + /// Input channel count + /// Output channel count + /// Grid point count for each CLUT channel + /// The read CLUTf32 + public IccClut ReadClutF32(int inChCount, int outChCount, byte[] gridPointCount) + { + int start = this.currentIndex; + int length = 0; + for (int i = 0; i < inChCount; i++) { - int start = this.currentIndex; - int length = 0; - for (int i = 0; i < inChCount; i++) - { - length += (int)Math.Pow(gridPointCount[i], inChCount); - } + length += (int)Math.Pow(gridPointCount[i], inChCount); + } - length /= inChCount; + length /= inChCount; - var values = new float[length][]; - for (int i = 0; i < length; i++) + var values = new float[length][]; + for (int i = 0; i < length; i++) + { + values[i] = new float[outChCount]; + for (int j = 0; j < outChCount; j++) { - values[i] = new float[outChCount]; - for (int j = 0; j < outChCount; j++) - { - values[i][j] = this.ReadSingle(); - } + values[i][j] = this.ReadSingle(); } - - this.currentIndex = start + (length * outChCount * 4); - return new IccClut(values, gridPointCount, IccClutDataType.Float); } + + this.currentIndex = start + (length * outChCount * 4); + return new IccClut(values, gridPointCount, IccClutDataType.Float); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs index c650634b03..61ecda4aab 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Matrix.cs @@ -1,63 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads a two dimensional matrix /// - internal sealed partial class IccDataReader + /// Number of values in X + /// Number of values in Y + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + public float[,] ReadMatrix(int xCount, int yCount, bool isSingle) { - /// - /// Reads a two dimensional matrix - /// - /// Number of values in X - /// Number of values in Y - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The read matrix - public float[,] ReadMatrix(int xCount, int yCount, bool isSingle) + var matrix = new float[xCount, yCount]; + for (int y = 0; y < yCount; y++) { - var matrix = new float[xCount, yCount]; - for (int y = 0; y < yCount; y++) - { - for (int x = 0; x < xCount; x++) - { - if (isSingle) - { - matrix[x, y] = this.ReadSingle(); - } - else - { - matrix[x, y] = this.ReadFix16(); - } - } - } - - return matrix; - } - - /// - /// Reads a one dimensional matrix - /// - /// Number of values - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The read matrix - public float[] ReadMatrix(int yCount, bool isSingle) - { - var matrix = new float[yCount]; - for (int i = 0; i < yCount; i++) + for (int x = 0; x < xCount; x++) { if (isSingle) { - matrix[i] = this.ReadSingle(); + matrix[x, y] = this.ReadSingle(); } else { - matrix[i] = this.ReadFix16(); + matrix[x, y] = this.ReadFix16(); } } + } - return matrix; + return matrix; + } + + /// + /// Reads a one dimensional matrix + /// + /// Number of values + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The read matrix + public float[] ReadMatrix(int yCount, bool isSingle) + { + var matrix = new float[yCount]; + for (int i = 0; i < yCount; i++) + { + if (isSingle) + { + matrix[i] = this.ReadSingle(); + } + else + { + matrix[i] = this.ReadFix16(); + } } + + return matrix; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs index 9f6aaa46c8..5baa904048 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElement.cs @@ -1,85 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads a /// - internal sealed partial class IccDataReader + /// The read + public IccMultiProcessElement ReadMultiProcessElement() { - /// - /// Reads a - /// - /// The read - public IccMultiProcessElement ReadMultiProcessElement() - { - IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32(); - ushort inChannelCount = this.ReadUInt16(); - ushort outChannelCount = this.ReadUInt16(); + IccMultiProcessElementSignature signature = (IccMultiProcessElementSignature)this.ReadUInt32(); + ushort inChannelCount = this.ReadUInt16(); + ushort outChannelCount = this.ReadUInt16(); - switch (signature) - { - case IccMultiProcessElementSignature.CurveSet: - return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.Matrix: - return this.ReadMatrixProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.Clut: - return this.ReadClutProcessElement(inChannelCount, outChannelCount); + switch (signature) + { + case IccMultiProcessElementSignature.CurveSet: + return this.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.Matrix: + return this.ReadMatrixProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.Clut: + return this.ReadClutProcessElement(inChannelCount, outChannelCount); - // Currently just placeholders for future ICC expansion - case IccMultiProcessElementSignature.BAcs: - this.AddIndex(8); - return new IccBAcsProcessElement(inChannelCount, outChannelCount); - case IccMultiProcessElementSignature.EAcs: - this.AddIndex(8); - return new IccEAcsProcessElement(inChannelCount, outChannelCount); + // Currently just placeholders for future ICC expansion + case IccMultiProcessElementSignature.BAcs: + this.AddIndex(8); + return new IccBAcsProcessElement(inChannelCount, outChannelCount); + case IccMultiProcessElementSignature.EAcs: + this.AddIndex(8); + return new IccEAcsProcessElement(inChannelCount, outChannelCount); - default: - throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}"); - } + default: + throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {signature}"); } + } - /// - /// Reads a CurveSet - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount) + /// + /// Reads a CurveSet + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccCurveSetProcessElement ReadCurveSetProcessElement(int inChannelCount, int outChannelCount) + { + var curves = new IccOneDimensionalCurve[inChannelCount]; + for (int i = 0; i < inChannelCount; i++) { - var curves = new IccOneDimensionalCurve[inChannelCount]; - for (int i = 0; i < inChannelCount; i++) - { - curves[i] = this.ReadOneDimensionalCurve(); - this.AddPadding(); - } - - return new IccCurveSetProcessElement(curves); + curves[i] = this.ReadOneDimensionalCurve(); + this.AddPadding(); } - /// - /// Reads a Matrix - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount) - { - return new IccMatrixProcessElement( - this.ReadMatrix(inChannelCount, outChannelCount, true), - this.ReadMatrix(outChannelCount, true)); - } + return new IccCurveSetProcessElement(curves); + } - /// - /// Reads a CLUT - /// - /// Number of input channels - /// Number of output channels - /// The read - public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount) - { - return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true)); - } + /// + /// Reads a Matrix + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccMatrixProcessElement ReadMatrixProcessElement(int inChannelCount, int outChannelCount) + { + return new IccMatrixProcessElement( + this.ReadMatrix(inChannelCount, outChannelCount, true), + this.ReadMatrix(outChannelCount, true)); + } + + /// + /// Reads a CLUT + /// + /// Number of input channels + /// Number of output channels + /// The read + public IccClutProcessElement ReadClutProcessElement(int inChannelCount, int outChannelCount) + { + return new IccClutProcessElement(this.ReadClut(inChannelCount, outChannelCount, true)); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs index 5b39a9ec34..d5369e5fc4 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitives.cs @@ -1,181 +1,179 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads a DateTime /// - internal sealed partial class IccDataReader + /// the value + public DateTime ReadDateTime() { - /// - /// Reads a DateTime - /// - /// the value - public DateTime ReadDateTime() + try { - try - { - return new DateTime( - year: this.ReadUInt16(), - month: this.ReadUInt16(), - day: this.ReadUInt16(), - hour: this.ReadUInt16(), - minute: this.ReadUInt16(), - second: this.ReadUInt16(), - kind: DateTimeKind.Utc); - } - catch (ArgumentOutOfRangeException) - { - return DateTime.MinValue; - } + return new DateTime( + year: this.ReadUInt16(), + month: this.ReadUInt16(), + day: this.ReadUInt16(), + hour: this.ReadUInt16(), + minute: this.ReadUInt16(), + second: this.ReadUInt16(), + kind: DateTimeKind.Utc); } - - /// - /// Reads an ICC profile version number - /// - /// the version number - public IccVersion ReadVersionNumber() + catch (ArgumentOutOfRangeException) { - int version = this.ReadInt32(); + return DateTime.MinValue; + } + } - int major = (version >> 24) & 0xFF; - int minor = (version >> 20) & 0x0F; - int bugfix = (version >> 16) & 0x0F; + /// + /// Reads an ICC profile version number + /// + /// the version number + public IccVersion ReadVersionNumber() + { + int version = this.ReadInt32(); - return new IccVersion(major, minor, bugfix); - } + int major = (version >> 24) & 0xFF; + int minor = (version >> 20) & 0x0F; + int bugfix = (version >> 16) & 0x0F; - /// - /// Reads an XYZ number - /// - /// the XYZ number - public Vector3 ReadXyzNumber() - { - return new Vector3( - x: this.ReadFix16(), - y: this.ReadFix16(), - z: this.ReadFix16()); - } + return new IccVersion(major, minor, bugfix); + } - /// - /// Reads a profile ID - /// - /// the profile ID - public IccProfileId ReadProfileId() - { - return new IccProfileId( - p1: this.ReadUInt32(), - p2: this.ReadUInt32(), - p3: this.ReadUInt32(), - p4: this.ReadUInt32()); - } + /// + /// Reads an XYZ number + /// + /// the XYZ number + public Vector3 ReadXyzNumber() + { + return new Vector3( + x: this.ReadFix16(), + y: this.ReadFix16(), + z: this.ReadFix16()); + } - /// - /// Reads a position number - /// - /// the position number - public IccPositionNumber ReadPositionNumber() - { - return new IccPositionNumber( - offset: this.ReadUInt32(), - size: this.ReadUInt32()); - } + /// + /// Reads a profile ID + /// + /// the profile ID + public IccProfileId ReadProfileId() + { + return new IccProfileId( + p1: this.ReadUInt32(), + p2: this.ReadUInt32(), + p3: this.ReadUInt32(), + p4: this.ReadUInt32()); + } - /// - /// Reads a response number - /// - /// the response number - public IccResponseNumber ReadResponseNumber() - { - return new IccResponseNumber( - deviceCode: this.ReadUInt16(), - measurementValue: this.ReadFix16()); - } + /// + /// Reads a position number + /// + /// the position number + public IccPositionNumber ReadPositionNumber() + { + return new IccPositionNumber( + offset: this.ReadUInt32(), + size: this.ReadUInt32()); + } - /// - /// Reads a named color - /// - /// Number of device coordinates - /// the named color - public IccNamedColor ReadNamedColor(uint deviceCoordCount) - { - string name = this.ReadAsciiString(32); - ushort[] pcsCoord = { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() }; - var deviceCoord = new ushort[deviceCoordCount]; + /// + /// Reads a response number + /// + /// the response number + public IccResponseNumber ReadResponseNumber() + { + return new IccResponseNumber( + deviceCode: this.ReadUInt16(), + measurementValue: this.ReadFix16()); + } - for (int i = 0; i < deviceCoordCount; i++) - { - deviceCoord[i] = this.ReadUInt16(); - } + /// + /// Reads a named color + /// + /// Number of device coordinates + /// the named color + public IccNamedColor ReadNamedColor(uint deviceCoordCount) + { + string name = this.ReadAsciiString(32); + ushort[] pcsCoord = { this.ReadUInt16(), this.ReadUInt16(), this.ReadUInt16() }; + var deviceCoord = new ushort[deviceCoordCount]; - return new IccNamedColor(name, pcsCoord, deviceCoord); + for (int i = 0; i < deviceCoordCount; i++) + { + deviceCoord[i] = this.ReadUInt16(); } - /// - /// Reads a profile description - /// - /// the profile description - public IccProfileDescription ReadProfileDescription() + return new IccNamedColor(name, pcsCoord, deviceCoord); + } + + /// + /// Reads a profile description + /// + /// the profile description + public IccProfileDescription ReadProfileDescription() + { + uint manufacturer = this.ReadUInt32(); + uint model = this.ReadUInt32(); + var attributes = (IccDeviceAttribute)this.ReadInt64(); + var technologyInfo = (IccProfileTag)this.ReadUInt32(); + + IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText(); + IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText(); + + return new IccProfileDescription( + manufacturer, + model, + attributes, + technologyInfo, + manufacturerInfo.Texts, + modelInfo.Texts); + + IccMultiLocalizedUnicodeTagDataEntry ReadText() { - uint manufacturer = this.ReadUInt32(); - uint model = this.ReadUInt32(); - var attributes = (IccDeviceAttribute)this.ReadInt64(); - var technologyInfo = (IccProfileTag)this.ReadUInt32(); - - IccMultiLocalizedUnicodeTagDataEntry manufacturerInfo = ReadText(); - IccMultiLocalizedUnicodeTagDataEntry modelInfo = ReadText(); - - return new IccProfileDescription( - manufacturer, - model, - attributes, - technologyInfo, - manufacturerInfo.Texts, - modelInfo.Texts); - - IccMultiLocalizedUnicodeTagDataEntry ReadText() + IccTypeSignature type = this.ReadTagDataEntryHeader(); + switch (type) { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - switch (type) - { - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.TextDescription: - return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry(); - - default: - throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries"); - } + case IccTypeSignature.MultiLocalizedUnicode: + return this.ReadMultiLocalizedUnicodeTagDataEntry(); + case IccTypeSignature.TextDescription: + return (IccMultiLocalizedUnicodeTagDataEntry)this.ReadTextDescriptionTagDataEntry(); + + default: + throw new InvalidIccProfileException("Profile description can only have multi-localized Unicode or text description entries"); } } + } - /// - /// Reads a colorant table entry - /// - /// the profile description - public IccColorantTableEntry ReadColorantTableEntry() - { - return new IccColorantTableEntry( - name: this.ReadAsciiString(32), - pcs1: this.ReadUInt16(), - pcs2: this.ReadUInt16(), - pcs3: this.ReadUInt16()); - } + /// + /// Reads a colorant table entry + /// + /// the profile description + public IccColorantTableEntry ReadColorantTableEntry() + { + return new IccColorantTableEntry( + name: this.ReadAsciiString(32), + pcs1: this.ReadUInt16(), + pcs2: this.ReadUInt16(), + pcs3: this.ReadUInt16()); + } - /// - /// Reads a screening channel - /// - /// the screening channel - public IccScreeningChannel ReadScreeningChannel() - { - return new IccScreeningChannel( - frequency: this.ReadFix16(), - angle: this.ReadFix16(), - spotShape: (IccScreeningSpotType)this.ReadInt32()); - } + /// + /// Reads a screening channel + /// + /// the screening channel + public IccScreeningChannel ReadScreeningChannel() + { + return new IccScreeningChannel( + frequency: this.ReadFix16(), + angle: this.ReadFix16(), + spotShape: (IccScreeningSpotType)this.ReadInt32()); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs index d63bb74491..47d946d443 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -1,173 +1,171 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads an ushort /// - internal sealed partial class IccDataReader + /// the value + public ushort ReadUInt16() { - /// - /// Reads an ushort - /// - /// the value - public ushort ReadUInt16() - { - return BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); - } + return BinaryPrimitives.ReadUInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); + } - /// - /// Reads a short - /// - /// the value - public short ReadInt16() - { - return BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); - } + /// + /// Reads a short + /// + /// the value + public short ReadInt16() + { + return BinaryPrimitives.ReadInt16BigEndian(this.data.AsSpan(this.AddIndex(2), 2)); + } - /// - /// Reads an uint - /// - /// the value - public uint ReadUInt32() - { - return BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); - } + /// + /// Reads an uint + /// + /// the value + public uint ReadUInt32() + { + return BinaryPrimitives.ReadUInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); + } - /// - /// Reads an int - /// - /// the value - public int ReadInt32() - { - return BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); - } + /// + /// Reads an int + /// + /// the value + public int ReadInt32() + { + return BinaryPrimitives.ReadInt32BigEndian(this.data.AsSpan(this.AddIndex(4), 4)); + } - /// - /// Reads an ulong - /// - /// the value - public ulong ReadUInt64() - { - return BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); - } + /// + /// Reads an ulong + /// + /// the value + public ulong ReadUInt64() + { + return BinaryPrimitives.ReadUInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); + } - /// - /// Reads a long - /// - /// the value - public long ReadInt64() - { - return BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); - } + /// + /// Reads a long + /// + /// the value + public long ReadInt64() + { + return BinaryPrimitives.ReadInt64BigEndian(this.data.AsSpan(this.AddIndex(8), 8)); + } - /// - /// Reads a float. - /// - /// the value - public float ReadSingle() - { - int intValue = this.ReadInt32(); + /// + /// Reads a float. + /// + /// the value + public float ReadSingle() + { + int intValue = this.ReadInt32(); - return Unsafe.As(ref intValue); - } + return Unsafe.As(ref intValue); + } - /// - /// Reads a double - /// - /// the value - public double ReadDouble() - { - long intValue = this.ReadInt64(); + /// + /// Reads a double + /// + /// the value + public double ReadDouble() + { + long intValue = this.ReadInt64(); - return Unsafe.As(ref intValue); - } + return Unsafe.As(ref intValue); + } - /// - /// Reads an ASCII encoded string. - /// - /// number of bytes to read - /// The value as a string - public string ReadAsciiString(int length) + /// + /// Reads an ASCII encoded string. + /// + /// number of bytes to read + /// The value as a string + public string ReadAsciiString(int length) + { + if (length == 0) { - if (length == 0) - { - return string.Empty; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - string value = Encoding.ASCII.GetString(this.data, this.AddIndex(length), length); - - // remove data after (potential) null terminator - int pos = value.IndexOf('\0'); - if (pos >= 0) - { - value = value[..pos]; - } - - return value; + return string.Empty; } - /// - /// Reads an UTF-16 big-endian encoded string. - /// - /// number of bytes to read - /// The value as a string - public string ReadUnicodeString(int length) - { - if (length == 0) - { - return string.Empty; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - - return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length); - } + Guard.MustBeGreaterThan(length, 0, nameof(length)); + string value = Encoding.ASCII.GetString(this.data, this.AddIndex(length), length); - /// - /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits. - /// - /// The number as double - public float ReadFix16() => this.ReadInt32() / 65536f; - - /// - /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits. - /// - /// The number as double - public float ReadUFix16() => this.ReadUInt32() / 65536f; - - /// - /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits. - /// - /// The number as double - public float ReadU1Fix15() => this.ReadUInt16() / 32768f; - - /// - /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits. - /// - /// The number as double - public float ReadUFix8() + // remove data after (potential) null terminator + int pos = value.IndexOf('\0'); + if (pos >= 0) { - return this.ReadUInt16() / 256f; + value = value[..pos]; } - /// - /// Reads a number of bytes and advances the index. - /// - /// The number of bytes to read - /// The read bytes - public byte[] ReadBytes(int count) + return value; + } + + /// + /// Reads an UTF-16 big-endian encoded string. + /// + /// number of bytes to read + /// The value as a string + public string ReadUnicodeString(int length) + { + if (length == 0) { - var bytes = new byte[count]; - Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count); - return bytes; + return string.Empty; } + + Guard.MustBeGreaterThan(length, 0, nameof(length)); + + return Encoding.BigEndianUnicode.GetString(this.data, this.AddIndex(length), length); + } + + /// + /// Reads a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits. + /// + /// The number as double + public float ReadFix16() => this.ReadInt32() / 65536f; + + /// + /// Reads an unsigned 32bit number with 16 value bits and 16 fractional bits. + /// + /// The number as double + public float ReadUFix16() => this.ReadUInt32() / 65536f; + + /// + /// Reads an unsigned 16bit number with 1 value bit and 15 fractional bits. + /// + /// The number as double + public float ReadU1Fix15() => this.ReadUInt16() / 32768f; + + /// + /// Reads an unsigned 16bit number with 8 value bits and 8 fractional bits. + /// + /// The number as double + public float ReadUFix8() + { + return this.ReadUInt16() / 256f; + } + + /// + /// Reads a number of bytes and advances the index. + /// + /// The number of bytes to read + /// The read bytes + public byte[] ReadBytes(int count) + { + var bytes = new byte[count]; + Buffer.BlockCopy(this.data, this.AddIndex(count), bytes, 0, count); + return bytes; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index 176c463353..53b47d38bd 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -1,902 +1,900 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// Reads a tag data entry /// - internal sealed partial class IccDataReader + /// The table entry with reading information + /// the tag data entry + public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) { - /// - /// Reads a tag data entry - /// - /// The table entry with reading information - /// the tag data entry - public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) - { - this.currentIndex = (int)info.Offset; - IccTypeSignature type = this.ReadTagDataEntryHeader(); - - switch (type) - { - case IccTypeSignature.Chromaticity: - return this.ReadChromaticityTagDataEntry(); - case IccTypeSignature.ColorantOrder: - return this.ReadColorantOrderTagDataEntry(); - case IccTypeSignature.ColorantTable: - return this.ReadColorantTableTagDataEntry(); - case IccTypeSignature.Curve: - return this.ReadCurveTagDataEntry(); - case IccTypeSignature.Data: - return this.ReadDataTagDataEntry(info.DataSize); - case IccTypeSignature.DateTime: - return this.ReadDateTimeTagDataEntry(); - case IccTypeSignature.Lut16: - return this.ReadLut16TagDataEntry(); - case IccTypeSignature.Lut8: - return this.ReadLut8TagDataEntry(); - case IccTypeSignature.LutAToB: - return this.ReadLutAtoBTagDataEntry(); - case IccTypeSignature.LutBToA: - return this.ReadLutBtoATagDataEntry(); - case IccTypeSignature.Measurement: - return this.ReadMeasurementTagDataEntry(); - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.MultiProcessElements: - return this.ReadMultiProcessElementsTagDataEntry(); - case IccTypeSignature.NamedColor2: - return this.ReadNamedColor2TagDataEntry(); - case IccTypeSignature.ParametricCurve: - return this.ReadParametricCurveTagDataEntry(); - case IccTypeSignature.ProfileSequenceDesc: - return this.ReadProfileSequenceDescTagDataEntry(); - case IccTypeSignature.ProfileSequenceIdentifier: - return this.ReadProfileSequenceIdentifierTagDataEntry(); - case IccTypeSignature.ResponseCurveSet16: - return this.ReadResponseCurveSet16TagDataEntry(); - case IccTypeSignature.S15Fixed16Array: - return this.ReadFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.Signature: - return this.ReadSignatureTagDataEntry(); - case IccTypeSignature.Text: - return this.ReadTextTagDataEntry(info.DataSize); - case IccTypeSignature.U16Fixed16Array: - return this.ReadUFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt16Array: - return this.ReadUInt16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt32Array: - return this.ReadUInt32ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt64Array: - return this.ReadUInt64ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt8Array: - return this.ReadUInt8ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.ViewingConditions: - return this.ReadViewingConditionsTagDataEntry(); - case IccTypeSignature.Xyz: - return this.ReadXyzTagDataEntry(info.DataSize); - - // V2 Types: - case IccTypeSignature.TextDescription: - return this.ReadTextDescriptionTagDataEntry(); - case IccTypeSignature.CrdInfo: - return this.ReadCrdInfoTagDataEntry(); - case IccTypeSignature.Screening: - return this.ReadScreeningTagDataEntry(); - case IccTypeSignature.UcrBg: - return this.ReadUcrBgTagDataEntry(info.DataSize); - - // Unsupported or unknown - case IccTypeSignature.DeviceSettings: - case IccTypeSignature.NamedColor: - case IccTypeSignature.Unknown: - default: - return this.ReadUnknownTagDataEntry(info.DataSize); - } + this.currentIndex = (int)info.Offset; + IccTypeSignature type = this.ReadTagDataEntryHeader(); + + switch (type) + { + case IccTypeSignature.Chromaticity: + return this.ReadChromaticityTagDataEntry(); + case IccTypeSignature.ColorantOrder: + return this.ReadColorantOrderTagDataEntry(); + case IccTypeSignature.ColorantTable: + return this.ReadColorantTableTagDataEntry(); + case IccTypeSignature.Curve: + return this.ReadCurveTagDataEntry(); + case IccTypeSignature.Data: + return this.ReadDataTagDataEntry(info.DataSize); + case IccTypeSignature.DateTime: + return this.ReadDateTimeTagDataEntry(); + case IccTypeSignature.Lut16: + return this.ReadLut16TagDataEntry(); + case IccTypeSignature.Lut8: + return this.ReadLut8TagDataEntry(); + case IccTypeSignature.LutAToB: + return this.ReadLutAtoBTagDataEntry(); + case IccTypeSignature.LutBToA: + return this.ReadLutBtoATagDataEntry(); + case IccTypeSignature.Measurement: + return this.ReadMeasurementTagDataEntry(); + case IccTypeSignature.MultiLocalizedUnicode: + return this.ReadMultiLocalizedUnicodeTagDataEntry(); + case IccTypeSignature.MultiProcessElements: + return this.ReadMultiProcessElementsTagDataEntry(); + case IccTypeSignature.NamedColor2: + return this.ReadNamedColor2TagDataEntry(); + case IccTypeSignature.ParametricCurve: + return this.ReadParametricCurveTagDataEntry(); + case IccTypeSignature.ProfileSequenceDesc: + return this.ReadProfileSequenceDescTagDataEntry(); + case IccTypeSignature.ProfileSequenceIdentifier: + return this.ReadProfileSequenceIdentifierTagDataEntry(); + case IccTypeSignature.ResponseCurveSet16: + return this.ReadResponseCurveSet16TagDataEntry(); + case IccTypeSignature.S15Fixed16Array: + return this.ReadFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.Signature: + return this.ReadSignatureTagDataEntry(); + case IccTypeSignature.Text: + return this.ReadTextTagDataEntry(info.DataSize); + case IccTypeSignature.U16Fixed16Array: + return this.ReadUFix16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt16Array: + return this.ReadUInt16ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt32Array: + return this.ReadUInt32ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt64Array: + return this.ReadUInt64ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.UInt8Array: + return this.ReadUInt8ArrayTagDataEntry(info.DataSize); + case IccTypeSignature.ViewingConditions: + return this.ReadViewingConditionsTagDataEntry(); + case IccTypeSignature.Xyz: + return this.ReadXyzTagDataEntry(info.DataSize); + + // V2 Types: + case IccTypeSignature.TextDescription: + return this.ReadTextDescriptionTagDataEntry(); + case IccTypeSignature.CrdInfo: + return this.ReadCrdInfoTagDataEntry(); + case IccTypeSignature.Screening: + return this.ReadScreeningTagDataEntry(); + case IccTypeSignature.UcrBg: + return this.ReadUcrBgTagDataEntry(info.DataSize); + + // Unsupported or unknown + case IccTypeSignature.DeviceSettings: + case IccTypeSignature.NamedColor: + case IccTypeSignature.Unknown: + default: + return this.ReadUnknownTagDataEntry(info.DataSize); } + } + + /// + /// Reads the header of a + /// + /// The read signature + public IccTypeSignature ReadTagDataEntryHeader() + { + var type = (IccTypeSignature)this.ReadUInt32(); + this.AddIndex(4); // 4 bytes are not used + return type; + } - /// - /// Reads the header of a - /// - /// The read signature - public IccTypeSignature ReadTagDataEntryHeader() + /// + /// Reads the header of a and checks if it's the expected value + /// + /// expected value to check against + public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) + { + IccTypeSignature type = this.ReadTagDataEntryHeader(); + if (expected != (IccTypeSignature)uint.MaxValue && type != expected) { - var type = (IccTypeSignature)this.ReadUInt32(); - this.AddIndex(4); // 4 bytes are not used - return type; + throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); } + } + + /// + /// Reads a with an unknown + /// + /// The size of the entry in bytes + /// The read entry + public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + return new IccUnknownTagDataEntry(this.ReadBytes(count)); + } - /// - /// Reads the header of a and checks if it's the expected value - /// - /// expected value to check against - public void ReadCheckTagDataEntryHeader(IccTypeSignature expected) + /// + /// Reads a + /// + /// The read entry + public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() + { + ushort channelCount = this.ReadUInt16(); + var colorant = (IccColorantEncoding)this.ReadUInt16(); + + if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) + { + // The type is known and so are the values (they are constant) + // channelCount should always be 3 but it doesn't really matter if it's not + return new IccChromaticityTagDataEntry(colorant); + } + else { - IccTypeSignature type = this.ReadTagDataEntryHeader(); - if (expected != (IccTypeSignature)uint.MaxValue && type != expected) + // The type is not know, so the values need be read + double[][] values = new double[channelCount][]; + for (int i = 0; i < channelCount; i++) { - throw new InvalidIccProfileException($"Read signature {type} is not the expected {expected}"); + values[i] = new double[] { this.ReadUFix16(), this.ReadUFix16() }; } + + return new IccChromaticityTagDataEntry(values); } + } + + /// + /// Reads a + /// + /// The read entry + public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + byte[] number = this.ReadBytes((int)colorantCount); + return new IccColorantOrderTagDataEntry(number); + } - /// - /// Reads a with an unknown - /// - /// The size of the entry in bytes - /// The read entry - public IccUnknownTagDataEntry ReadUnknownTagDataEntry(uint size) + /// + /// Reads a + /// + /// The read entry + public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() + { + uint colorantCount = this.ReadUInt32(); + var cdata = new IccColorantTableEntry[colorantCount]; + for (int i = 0; i < colorantCount; i++) { - int count = (int)size - 8; // 8 is the tag header size - return new IccUnknownTagDataEntry(this.ReadBytes(count)); + cdata[i] = this.ReadColorantTableEntry(); } - /// - /// Reads a - /// - /// The read entry - public IccChromaticityTagDataEntry ReadChromaticityTagDataEntry() - { - ushort channelCount = this.ReadUInt16(); - var colorant = (IccColorantEncoding)this.ReadUInt16(); + return new IccColorantTableTagDataEntry(cdata); + } - if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) - { - // The type is known and so are the values (they are constant) - // channelCount should always be 3 but it doesn't really matter if it's not - return new IccChromaticityTagDataEntry(colorant); - } - else - { - // The type is not know, so the values need be read - double[][] values = new double[channelCount][]; - for (int i = 0; i < channelCount; i++) - { - values[i] = new double[] { this.ReadUFix16(), this.ReadUFix16() }; - } + /// + /// Reads a + /// + /// The read entry + public IccCurveTagDataEntry ReadCurveTagDataEntry() + { + uint pointCount = this.ReadUInt32(); - return new IccChromaticityTagDataEntry(values); - } + if (pointCount == 0) + { + return new IccCurveTagDataEntry(); } - /// - /// Reads a - /// - /// The read entry - public IccColorantOrderTagDataEntry ReadColorantOrderTagDataEntry() + if (pointCount == 1) { - uint colorantCount = this.ReadUInt32(); - byte[] number = this.ReadBytes((int)colorantCount); - return new IccColorantOrderTagDataEntry(number); + return new IccCurveTagDataEntry(this.ReadUFix8()); } - /// - /// Reads a - /// - /// The read entry - public IccColorantTableTagDataEntry ReadColorantTableTagDataEntry() + float[] cdata = new float[pointCount]; + for (int i = 0; i < pointCount; i++) { - uint colorantCount = this.ReadUInt32(); - var cdata = new IccColorantTableEntry[colorantCount]; - for (int i = 0; i < colorantCount; i++) - { - cdata[i] = this.ReadColorantTableEntry(); - } - - return new IccColorantTableTagDataEntry(cdata); + cdata[i] = this.ReadUInt16() / 65535f; } - /// - /// Reads a - /// - /// The read entry - public IccCurveTagDataEntry ReadCurveTagDataEntry() - { - uint pointCount = this.ReadUInt32(); + return new IccCurveTagDataEntry(cdata); - if (pointCount == 0) - { - return new IccCurveTagDataEntry(); - } + // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + } - if (pointCount == 1) - { - return new IccCurveTagDataEntry(this.ReadUFix8()); - } + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccDataTagDataEntry ReadDataTagDataEntry(uint size) + { + this.AddIndex(3); // first 3 bytes are zero + byte b = this.data[this.AddIndex(1)]; - float[] cdata = new float[pointCount]; - for (int i = 0; i < pointCount; i++) - { - cdata[i] = this.ReadUInt16() / 65535f; - } + // last bit of 4th byte is either 0 = ASCII or 1 = binary + bool ascii = GetBit(b, 7); + int length = (int)size - 12; + byte[] cdata = this.ReadBytes(length); - return new IccCurveTagDataEntry(cdata); + return new IccDataTagDataEntry(cdata, ascii); + } - // TODO: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). - } + /// + /// Reads a + /// + /// The read entry + public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() + { + return new IccDateTimeTagDataEntry(this.ReadDateTime()); + } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccDataTagDataEntry ReadDataTagDataEntry(uint size) - { - this.AddIndex(3); // first 3 bytes are zero - byte b = this.data[this.AddIndex(1)]; + /// + /// Reads a + /// + /// The read entry + public IccLut16TagDataEntry ReadLut16TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved - // last bit of 4th byte is either 0 = ASCII or 1 = binary - bool ascii = GetBit(b, 7); - int length = (int)size - 12; - byte[] cdata = this.ReadBytes(length); + float[,] matrix = this.ReadMatrix(3, 3, false); - return new IccDataTagDataEntry(cdata, ascii); - } + ushort inTableCount = this.ReadUInt16(); + ushort outTableCount = this.ReadUInt16(); - /// - /// Reads a - /// - /// The read entry - public IccDateTimeTagDataEntry ReadDateTimeTagDataEntry() + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) { - return new IccDateTimeTagDataEntry(this.ReadDateTime()); + inValues[i] = this.ReadLut16(inTableCount); + gridPointCount[i] = clutPointCount; } - /// - /// Reads a - /// - /// The read entry - public IccLut16TagDataEntry ReadLut16TagDataEntry() + // CLUT + IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); + + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved + outValues[i] = this.ReadLut16(outTableCount); + } - float[,] matrix = this.ReadMatrix(3, 3, false); + return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); + } - ushort inTableCount = this.ReadUInt16(); - ushort outTableCount = this.ReadUInt16(); + /// + /// Reads a + /// + /// The read entry + public IccLut8TagDataEntry ReadLut8TagDataEntry() + { + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + byte clutPointCount = this.data[this.AddIndex(1)]; + this.AddIndex(1); // 1 byte reserved - // Input LUT - var inValues = new IccLut[inChCount]; - byte[] gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut16(inTableCount); - gridPointCount[i] = clutPointCount; - } + float[,] matrix = this.ReadMatrix(3, 3, false); - // CLUT - IccClut clut = this.ReadClut16(inChCount, outChCount, gridPointCount); + // Input LUT + var inValues = new IccLut[inChCount]; + byte[] gridPointCount = new byte[inChCount]; + for (int i = 0; i < inChCount; i++) + { + inValues[i] = this.ReadLut8(); + gridPointCount[i] = clutPointCount; + } - // Output LUT - var outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut16(outTableCount); - } + // CLUT + IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); - return new IccLut16TagDataEntry(matrix, inValues, clut, outValues); + // Output LUT + var outValues = new IccLut[outChCount]; + for (int i = 0; i < outChCount; i++) + { + outValues[i] = this.ReadLut8(); } - /// - /// Reads a - /// - /// The read entry - public IccLut8TagDataEntry ReadLut8TagDataEntry() - { - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - byte clutPointCount = this.data[this.AddIndex(1)]; - this.AddIndex(1); // 1 byte reserved + return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); + } - float[,] matrix = this.ReadMatrix(3, 3, false); + /// + /// Reads a + /// + /// The read entry + public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size - // Input LUT - var inValues = new IccLut[inChCount]; - byte[] gridPointCount = new byte[inChCount]; - for (int i = 0; i < inChCount; i++) - { - inValues[i] = this.ReadLut8(); - gridPointCount[i] = clutPointCount; - } + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved - // CLUT - IccClut clut = this.ReadClut8(inChCount, outChCount, gridPointCount); + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); - // Output LUT - var outValues = new IccLut[outChCount]; - for (int i = 0; i < outChCount; i++) - { - outValues[i] = this.ReadLut8(); - } + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; - return new IccLut8TagDataEntry(matrix, inValues, clut, outValues); + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(outChCount); } - /// - /// Reads a - /// - /// The read entry - public IccLutAToBTagDataEntry ReadLutAtoBTagDataEntry() + if (mCurveOffset != 0) { - int start = this.currentIndex - 8; // 8 is the tag header size + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(outChCount); + } - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(inChCount); + } - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(outChCount); - } + return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(outChCount); - } + /// + /// Reads a + /// + /// The read entry + public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(inChCount); - } + byte inChCount = this.data[this.AddIndex(1)]; + byte outChCount = this.data[this.AddIndex(1)]; + this.AddIndex(2); // 2 bytes reserved - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } + uint bCurveOffset = this.ReadUInt32(); + uint matrixOffset = this.ReadUInt32(); + uint mCurveOffset = this.ReadUInt32(); + uint clutOffset = this.ReadUInt32(); + uint aCurveOffset = this.ReadUInt32(); - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } + IccTagDataEntry[] bCurve = null; + IccTagDataEntry[] mCurve = null; + IccTagDataEntry[] aCurve = null; + IccClut clut = null; + float[,] matrix3x3 = null; + float[] matrix3x1 = null; - return new IccLutAToBTagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + if (bCurveOffset != 0) + { + this.currentIndex = (int)bCurveOffset + start; + bCurve = this.ReadCurves(inChCount); } - /// - /// Reads a - /// - /// The read entry - public IccLutBToATagDataEntry ReadLutBtoATagDataEntry() + if (mCurveOffset != 0) { - int start = this.currentIndex - 8; // 8 is the tag header size - - byte inChCount = this.data[this.AddIndex(1)]; - byte outChCount = this.data[this.AddIndex(1)]; - this.AddIndex(2); // 2 bytes reserved + this.currentIndex = (int)mCurveOffset + start; + mCurve = this.ReadCurves(inChCount); + } - uint bCurveOffset = this.ReadUInt32(); - uint matrixOffset = this.ReadUInt32(); - uint mCurveOffset = this.ReadUInt32(); - uint clutOffset = this.ReadUInt32(); - uint aCurveOffset = this.ReadUInt32(); + if (aCurveOffset != 0) + { + this.currentIndex = (int)aCurveOffset + start; + aCurve = this.ReadCurves(outChCount); + } - IccTagDataEntry[] bCurve = null; - IccTagDataEntry[] mCurve = null; - IccTagDataEntry[] aCurve = null; - IccClut clut = null; - float[,] matrix3x3 = null; - float[] matrix3x1 = null; + if (clutOffset != 0) + { + this.currentIndex = (int)clutOffset + start; + clut = this.ReadClut(inChCount, outChCount, false); + } - if (bCurveOffset != 0) - { - this.currentIndex = (int)bCurveOffset + start; - bCurve = this.ReadCurves(inChCount); - } + if (matrixOffset != 0) + { + this.currentIndex = (int)matrixOffset + start; + matrix3x3 = this.ReadMatrix(3, 3, false); + matrix3x1 = this.ReadMatrix(3, false); + } - if (mCurveOffset != 0) - { - this.currentIndex = (int)mCurveOffset + start; - mCurve = this.ReadCurves(inChCount); - } + return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); + } - if (aCurveOffset != 0) - { - this.currentIndex = (int)aCurveOffset + start; - aCurve = this.ReadCurves(outChCount); - } + /// + /// Reads a + /// + /// The read entry + public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() + { + return new IccMeasurementTagDataEntry( + observer: (IccStandardObserver)this.ReadUInt32(), + xyzBacking: this.ReadXyzNumber(), + geometry: (IccMeasurementGeometry)this.ReadUInt32(), + flare: this.ReadUFix16(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } - if (clutOffset != 0) - { - this.currentIndex = (int)clutOffset + start; - clut = this.ReadClut(inChCount, outChCount, false); - } + /// + /// Reads a + /// + /// The read entry + public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint recordCount = this.ReadUInt32(); - if (matrixOffset != 0) - { - this.currentIndex = (int)matrixOffset + start; - matrix3x3 = this.ReadMatrix(3, 3, false); - matrix3x1 = this.ReadMatrix(3, false); - } + this.ReadUInt32(); // Record size (always 12) + var text = new IccLocalizedString[recordCount]; - return new IccLutBToATagDataEntry(bCurve, matrix3x3, matrix3x1, mCurve, clut, aCurve); - } + var culture = new CultureInfo[recordCount]; + uint[] length = new uint[recordCount]; + uint[] offset = new uint[recordCount]; - /// - /// Reads a - /// - /// The read entry - public IccMeasurementTagDataEntry ReadMeasurementTagDataEntry() + for (int i = 0; i < recordCount; i++) { - return new IccMeasurementTagDataEntry( - observer: (IccStandardObserver)this.ReadUInt32(), - xyzBacking: this.ReadXyzNumber(), - geometry: (IccMeasurementGeometry)this.ReadUInt32(), - flare: this.ReadUFix16(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); + string languageCode = this.ReadAsciiString(2); + string countryCode = this.ReadAsciiString(2); + + culture[i] = ReadCulture(languageCode, countryCode); + length[i] = this.ReadUInt32(); + offset[i] = this.ReadUInt32(); } - /// - /// Reads a - /// - /// The read entry - public IccMultiLocalizedUnicodeTagDataEntry ReadMultiLocalizedUnicodeTagDataEntry() + for (int i = 0; i < recordCount; i++) { - int start = this.currentIndex - 8; // 8 is the tag header size - uint recordCount = this.ReadUInt32(); - - this.ReadUInt32(); // Record size (always 12) - var text = new IccLocalizedString[recordCount]; - - var culture = new CultureInfo[recordCount]; - uint[] length = new uint[recordCount]; - uint[] offset = new uint[recordCount]; - - for (int i = 0; i < recordCount; i++) - { - string languageCode = this.ReadAsciiString(2); - string countryCode = this.ReadAsciiString(2); + this.currentIndex = (int)(start + offset[i]); + text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); + } - culture[i] = ReadCulture(languageCode, countryCode); - length[i] = this.ReadUInt32(); - offset[i] = this.ReadUInt32(); - } + return new IccMultiLocalizedUnicodeTagDataEntry(text); - for (int i = 0; i < recordCount; i++) + CultureInfo ReadCulture(string language, string country) + { + if (string.IsNullOrWhiteSpace(language)) { - this.currentIndex = (int)(start + offset[i]); - text[i] = new IccLocalizedString(culture[i], this.ReadUnicodeString((int)length[i])); + return CultureInfo.InvariantCulture; } - - return new IccMultiLocalizedUnicodeTagDataEntry(text); - - CultureInfo ReadCulture(string language, string country) + else if (string.IsNullOrWhiteSpace(country)) { - if (string.IsNullOrWhiteSpace(language)) + try + { + return new CultureInfo(language); + } + catch (CultureNotFoundException) { return CultureInfo.InvariantCulture; } - else if (string.IsNullOrWhiteSpace(country)) + } + else + { + try { - try - { - return new CultureInfo(language); - } - catch (CultureNotFoundException) - { - return CultureInfo.InvariantCulture; - } + return new CultureInfo($"{language}-{country}"); } - else + catch (CultureNotFoundException) { - try - { - return new CultureInfo($"{language}-{country}"); - } - catch (CultureNotFoundException) - { - return ReadCulture(language, null); - } + return ReadCulture(language, null); } } } + } - /// - /// Reads a - /// - /// The read entry - public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() - { - int start = this.currentIndex - 8; - - this.ReadUInt16(); - this.ReadUInt16(); - uint elementCount = this.ReadUInt32(); - - var positionTable = new IccPositionNumber[elementCount]; - for (int i = 0; i < elementCount; i++) - { - positionTable[i] = this.ReadPositionNumber(); - } + /// + /// Reads a + /// + /// The read entry + public IccMultiProcessElementsTagDataEntry ReadMultiProcessElementsTagDataEntry() + { + int start = this.currentIndex - 8; - var elements = new IccMultiProcessElement[elementCount]; - for (int i = 0; i < elementCount; i++) - { - this.currentIndex = (int)positionTable[i].Offset + start; - elements[i] = this.ReadMultiProcessElement(); - } + this.ReadUInt16(); + this.ReadUInt16(); + uint elementCount = this.ReadUInt32(); - return new IccMultiProcessElementsTagDataEntry(elements); + var positionTable = new IccPositionNumber[elementCount]; + for (int i = 0; i < elementCount; i++) + { + positionTable[i] = this.ReadPositionNumber(); } - /// - /// Reads a - /// - /// The read entry - public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() + var elements = new IccMultiProcessElement[elementCount]; + for (int i = 0; i < elementCount; i++) { - int vendorFlag = this.ReadInt32(); - uint colorCount = this.ReadUInt32(); - uint coordCount = this.ReadUInt32(); - string prefix = this.ReadAsciiString(32); - string suffix = this.ReadAsciiString(32); + this.currentIndex = (int)positionTable[i].Offset + start; + elements[i] = this.ReadMultiProcessElement(); + } - var colors = new IccNamedColor[colorCount]; - for (int i = 0; i < colorCount; i++) - { - colors[i] = this.ReadNamedColor(coordCount); - } + return new IccMultiProcessElementsTagDataEntry(elements); + } - return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); - } + /// + /// Reads a + /// + /// The read entry + public IccNamedColor2TagDataEntry ReadNamedColor2TagDataEntry() + { + int vendorFlag = this.ReadInt32(); + uint colorCount = this.ReadUInt32(); + uint coordCount = this.ReadUInt32(); + string prefix = this.ReadAsciiString(32); + string suffix = this.ReadAsciiString(32); - /// - /// Reads a - /// - /// The read entry - public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() + var colors = new IccNamedColor[colorCount]; + for (int i = 0; i < colorCount; i++) { - return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); + colors[i] = this.ReadNamedColor(coordCount); } - /// - /// Reads a - /// - /// The read entry - public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() - { - uint count = this.ReadUInt32(); - var description = new IccProfileDescription[count]; - for (int i = 0; i < count; i++) - { - description[i] = this.ReadProfileDescription(); - } + return new IccNamedColor2TagDataEntry(vendorFlag, prefix, suffix, colors); + } - return new IccProfileSequenceDescTagDataEntry(description); - } + /// + /// Reads a + /// + /// The read entry + public IccParametricCurveTagDataEntry ReadParametricCurveTagDataEntry() + { + return new IccParametricCurveTagDataEntry(this.ReadParametricCurve()); + } - /// - /// Reads a - /// - /// The read entry - public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceDescTagDataEntry ReadProfileSequenceDescTagDataEntry() + { + uint count = this.ReadUInt32(); + var description = new IccProfileDescription[count]; + for (int i = 0; i < count; i++) { - int start = this.currentIndex - 8; // 8 is the tag header size - uint count = this.ReadUInt32(); - var table = new IccPositionNumber[count]; - for (int i = 0; i < count; i++) - { - table[i] = this.ReadPositionNumber(); - } + description[i] = this.ReadProfileDescription(); + } - var entries = new IccProfileSequenceIdentifier[count]; - for (int i = 0; i < count; i++) - { - this.currentIndex = (int)(start + table[i].Offset); - IccProfileId id = this.ReadProfileId(); - this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); - IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); - entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); - } + return new IccProfileSequenceDescTagDataEntry(description); + } - return new IccProfileSequenceIdentifierTagDataEntry(entries); + /// + /// Reads a + /// + /// The read entry + public IccProfileSequenceIdentifierTagDataEntry ReadProfileSequenceIdentifierTagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + uint count = this.ReadUInt32(); + var table = new IccPositionNumber[count]; + for (int i = 0; i < count; i++) + { + table[i] = this.ReadPositionNumber(); } - /// - /// Reads a - /// - /// The read entry - public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() + var entries = new IccProfileSequenceIdentifier[count]; + for (int i = 0; i < count; i++) { - int start = this.currentIndex - 8; // 8 is the tag header size - ushort channelCount = this.ReadUInt16(); - ushort measurementCount = this.ReadUInt16(); + this.currentIndex = (int)(start + table[i].Offset); + IccProfileId id = this.ReadProfileId(); + this.ReadCheckTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode); + IccMultiLocalizedUnicodeTagDataEntry description = this.ReadMultiLocalizedUnicodeTagDataEntry(); + entries[i] = new IccProfileSequenceIdentifier(id, description.Texts); + } - uint[] offset = new uint[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - offset[i] = this.ReadUInt32(); - } + return new IccProfileSequenceIdentifierTagDataEntry(entries); + } - var curves = new IccResponseCurve[measurementCount]; - for (int i = 0; i < measurementCount; i++) - { - this.currentIndex = (int)(start + offset[i]); - curves[i] = this.ReadResponseCurve(channelCount); - } + /// + /// Reads a + /// + /// The read entry + public IccResponseCurveSet16TagDataEntry ReadResponseCurveSet16TagDataEntry() + { + int start = this.currentIndex - 8; // 8 is the tag header size + ushort channelCount = this.ReadUInt16(); + ushort measurementCount = this.ReadUInt16(); - return new IccResponseCurveSet16TagDataEntry(curves); + uint[] offset = new uint[measurementCount]; + for (int i = 0; i < measurementCount; i++) + { + offset[i] = this.ReadUInt32(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) + var curves = new IccResponseCurve[measurementCount]; + for (int i = 0; i < measurementCount; i++) { - uint count = (size - 8) / 4; - float[] arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadFix16() / 256f; - } - - return new IccFix16ArrayTagDataEntry(arrayData); + this.currentIndex = (int)(start + offset[i]); + curves[i] = this.ReadResponseCurve(channelCount); } - /// - /// Reads a - /// - /// The read entry - public IccSignatureTagDataEntry ReadSignatureTagDataEntry() + return new IccResponseCurveSet16TagDataEntry(curves); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) { - return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); + arrayData[i] = this.ReadFix16() / 256f; } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccTextTagDataEntry ReadTextTagDataEntry(uint size) + return new IccFix16ArrayTagDataEntry(arrayData); + } + + /// + /// Reads a + /// + /// The read entry + public IccSignatureTagDataEntry ReadSignatureTagDataEntry() + { + return new IccSignatureTagDataEntry(this.ReadAsciiString(4)); + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccTextTagDataEntry ReadTextTagDataEntry(uint size) + { + return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size + } + + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + float[] arrayData = new float[count]; + for (int i = 0; i < count; i++) { - return new IccTextTagDataEntry(this.ReadAsciiString((int)size - 8)); // 8 is the tag header size + arrayData[i] = this.ReadUFix16(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - float[] arrayData = new float[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUFix16(); - } + return new IccUFix16ArrayTagDataEntry(arrayData); + } - return new IccUFix16ArrayTagDataEntry(arrayData); + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 2; + ushort[] arrayData = new ushort[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt16(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 2; - ushort[] arrayData = new ushort[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt16(); - } + return new IccUInt16ArrayTagDataEntry(arrayData); + } - return new IccUInt16ArrayTagDataEntry(arrayData); + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 4; + uint[] arrayData = new uint[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt32(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 4; - uint[] arrayData = new uint[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt32(); - } + return new IccUInt32ArrayTagDataEntry(arrayData); + } - return new IccUInt32ArrayTagDataEntry(arrayData); + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) + { + uint count = (size - 8) / 8; + ulong[] arrayData = new ulong[count]; + for (int i = 0; i < count; i++) + { + arrayData[i] = this.ReadUInt64(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) - { - uint count = (size - 8) / 8; - ulong[] arrayData = new ulong[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadUInt64(); - } + return new IccUInt64ArrayTagDataEntry(arrayData); + } - return new IccUInt64ArrayTagDataEntry(arrayData); - } + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) + { + int count = (int)size - 8; // 8 is the tag header size + byte[] adata = this.ReadBytes(count); - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUInt8ArrayTagDataEntry ReadUInt8ArrayTagDataEntry(uint size) - { - int count = (int)size - 8; // 8 is the tag header size - byte[] adata = this.ReadBytes(count); + return new IccUInt8ArrayTagDataEntry(adata); + } - return new IccUInt8ArrayTagDataEntry(adata); - } + /// + /// Reads a + /// + /// The read entry + public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() + { + return new IccViewingConditionsTagDataEntry( + illuminantXyz: this.ReadXyzNumber(), + surroundXyz: this.ReadXyzNumber(), + illuminant: (IccStandardIlluminant)this.ReadUInt32()); + } - /// - /// Reads a - /// - /// The read entry - public IccViewingConditionsTagDataEntry ReadViewingConditionsTagDataEntry() + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) + { + uint count = (size - 8) / 12; + var arrayData = new Vector3[count]; + for (int i = 0; i < count; i++) { - return new IccViewingConditionsTagDataEntry( - illuminantXyz: this.ReadXyzNumber(), - surroundXyz: this.ReadXyzNumber(), - illuminant: (IccStandardIlluminant)this.ReadUInt32()); + arrayData[i] = this.ReadXyzNumber(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccXyzTagDataEntry ReadXyzTagDataEntry(uint size) - { - uint count = (size - 8) / 12; - var arrayData = new Vector3[count]; - for (int i = 0; i < count; i++) - { - arrayData[i] = this.ReadXyzNumber(); - } + return new IccXyzTagDataEntry(arrayData); + } - return new IccXyzTagDataEntry(arrayData); - } + /// + /// Reads a + /// + /// The read entry + public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() + { + string unicodeValue, scriptcodeValue; + string asciiValue = unicodeValue = scriptcodeValue = null; - /// - /// Reads a - /// - /// The read entry - public IccTextDescriptionTagDataEntry ReadTextDescriptionTagDataEntry() + int asciiCount = (int)this.ReadUInt32(); + if (asciiCount > 0) { - string unicodeValue, scriptcodeValue; - string asciiValue = unicodeValue = scriptcodeValue = null; + asciiValue = this.ReadAsciiString(asciiCount - 1); + this.AddIndex(1); // Null terminator + } - int asciiCount = (int)this.ReadUInt32(); - if (asciiCount > 0) - { - asciiValue = this.ReadAsciiString(asciiCount - 1); - this.AddIndex(1); // Null terminator - } + uint unicodeLangCode = this.ReadUInt32(); + int unicodeCount = (int)this.ReadUInt32(); + if (unicodeCount > 0) + { + unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); + this.AddIndex(2); // Null terminator + } - uint unicodeLangCode = this.ReadUInt32(); - int unicodeCount = (int)this.ReadUInt32(); - if (unicodeCount > 0) - { - unicodeValue = this.ReadUnicodeString((unicodeCount * 2) - 2); - this.AddIndex(2); // Null terminator - } + ushort scriptcodeCode = this.ReadUInt16(); + int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); + if (scriptcodeCount > 0) + { + scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); + this.AddIndex(1); // Null terminator + } - ushort scriptcodeCode = this.ReadUInt16(); - int scriptcodeCount = Math.Min(this.data[this.AddIndex(1)], (byte)67); - if (scriptcodeCount > 0) - { - scriptcodeValue = this.ReadAsciiString(scriptcodeCount - 1); - this.AddIndex(1); // Null terminator - } + return new IccTextDescriptionTagDataEntry( + asciiValue, + unicodeValue, + scriptcodeValue, + unicodeLangCode, + scriptcodeCode); + } - return new IccTextDescriptionTagDataEntry( - asciiValue, - unicodeValue, - scriptcodeValue, - unicodeLangCode, - scriptcodeCode); - } + /// + /// Reads a + /// + /// The read entry + public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() + { + uint productNameCount = this.ReadUInt32(); + string productName = this.ReadAsciiString((int)productNameCount); - /// - /// Reads a - /// - /// The read entry - public IccCrdInfoTagDataEntry ReadCrdInfoTagDataEntry() - { - uint productNameCount = this.ReadUInt32(); - string productName = this.ReadAsciiString((int)productNameCount); + uint crd0Count = this.ReadUInt32(); + string crd0Name = this.ReadAsciiString((int)crd0Count); - uint crd0Count = this.ReadUInt32(); - string crd0Name = this.ReadAsciiString((int)crd0Count); + uint crd1Count = this.ReadUInt32(); + string crd1Name = this.ReadAsciiString((int)crd1Count); - uint crd1Count = this.ReadUInt32(); - string crd1Name = this.ReadAsciiString((int)crd1Count); + uint crd2Count = this.ReadUInt32(); + string crd2Name = this.ReadAsciiString((int)crd2Count); - uint crd2Count = this.ReadUInt32(); - string crd2Name = this.ReadAsciiString((int)crd2Count); + uint crd3Count = this.ReadUInt32(); + string crd3Name = this.ReadAsciiString((int)crd3Count); - uint crd3Count = this.ReadUInt32(); - string crd3Name = this.ReadAsciiString((int)crd3Count); + return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); + } - return new IccCrdInfoTagDataEntry(productName, crd0Name, crd1Name, crd2Name, crd3Name); + /// + /// Reads a + /// + /// The read entry + public IccScreeningTagDataEntry ReadScreeningTagDataEntry() + { + var flags = (IccScreeningFlag)this.ReadInt32(); + uint channelCount = this.ReadUInt32(); + var channels = new IccScreeningChannel[channelCount]; + for (int i = 0; i < channels.Length; i++) + { + channels[i] = this.ReadScreeningChannel(); } - /// - /// Reads a - /// - /// The read entry - public IccScreeningTagDataEntry ReadScreeningTagDataEntry() - { - var flags = (IccScreeningFlag)this.ReadInt32(); - uint channelCount = this.ReadUInt32(); - var channels = new IccScreeningChannel[channelCount]; - for (int i = 0; i < channels.Length; i++) - { - channels[i] = this.ReadScreeningChannel(); - } + return new IccScreeningTagDataEntry(flags, channels); + } - return new IccScreeningTagDataEntry(flags, channels); + /// + /// Reads a + /// + /// The size of the entry in bytes + /// The read entry + public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) + { + uint ucrCount = this.ReadUInt32(); + ushort[] ucrCurve = new ushort[ucrCount]; + for (int i = 0; i < ucrCurve.Length; i++) + { + ucrCurve[i] = this.ReadUInt16(); } - /// - /// Reads a - /// - /// The size of the entry in bytes - /// The read entry - public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) + uint bgCount = this.ReadUInt32(); + ushort[] bgCurve = new ushort[bgCount]; + for (int i = 0; i < bgCurve.Length; i++) { - uint ucrCount = this.ReadUInt32(); - ushort[] ucrCurve = new ushort[ucrCount]; - for (int i = 0; i < ucrCurve.Length; i++) - { - ucrCurve[i] = this.ReadUInt16(); - } - - uint bgCount = this.ReadUInt32(); - ushort[] bgCurve = new ushort[bgCount]; - for (int i = 0; i < bgCurve.Length; i++) - { - bgCurve[i] = this.ReadUInt16(); - } + bgCurve[i] = this.ReadUInt16(); + } - // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) - uint dataSize = ((ucrCount + bgCount) * 2) + 8; - int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size - string description = this.ReadAsciiString(descriptionLength); + // ((ucr length + bg length) * UInt16 size) + (ucrCount + bgCount) + uint dataSize = ((ucrCount + bgCount) * 2) + 8; + int descriptionLength = (int)(size - 8 - dataSize); // 8 is the tag header size + string description = this.ReadAsciiString(descriptionLength); - return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); - } + return new IccUcrBgTagDataEntry(ucrCurve, bgCurve, description); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs index 35c8678274..c5464c8d72 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Provides methods to read ICC data types +/// +internal sealed partial class IccDataReader { /// - /// Provides methods to read ICC data types + /// The data that is read /// - internal sealed partial class IccDataReader - { - /// - /// The data that is read - /// - private readonly byte[] data; - - /// - /// The current reading position - /// - private int currentIndex; + private readonly byte[] data; - /// - /// Initializes a new instance of the class. - /// - /// The data to read - public IccDataReader(byte[] data) - => this.data = data ?? throw new ArgumentNullException(nameof(data)); + /// + /// The current reading position + /// + private int currentIndex; - /// - /// Gets the length in bytes of the raw data - /// - public int DataLength => this.data.Length; + /// + /// Initializes a new instance of the class. + /// + /// The data to read + public IccDataReader(byte[] data) + => this.data = data ?? throw new ArgumentNullException(nameof(data)); - /// - /// Sets the reading position to the given value - /// - /// The new index position - public void SetIndex(int index) - => this.currentIndex = Numerics.Clamp(index, 0, this.data.Length); + /// + /// Gets the length in bytes of the raw data + /// + public int DataLength => this.data.Length; - /// - /// Returns the current without increment and adds the given increment - /// - /// The value to increment - /// The current without the increment - private int AddIndex(int increment) - { - int tmp = this.currentIndex; - this.currentIndex += increment; - return tmp; - } + /// + /// Sets the reading position to the given value + /// + /// The new index position + public void SetIndex(int index) + => this.currentIndex = Numerics.Clamp(index, 0, this.data.Length); - /// - /// Calculates the 4 byte padding and adds it to the variable - /// - private void AddPadding() - => this.currentIndex += this.CalcPadding(); + /// + /// Returns the current without increment and adds the given increment + /// + /// The value to increment + /// The current without the increment + private int AddIndex(int increment) + { + int tmp = this.currentIndex; + this.currentIndex += increment; + return tmp; + } - /// - /// Calculates the 4 byte padding - /// - /// the number of bytes to pad - private int CalcPadding() - { - int p = 4 - (this.currentIndex % 4); - return p >= 4 ? 0 : p; - } + /// + /// Calculates the 4 byte padding and adds it to the variable + /// + private void AddPadding() + => this.currentIndex += this.CalcPadding(); - /// - /// Gets the bit value at a specified position - /// - /// The value from where the bit will be extracted - /// Position of the bit. Zero based index from left to right. - /// The bit value at specified position - private static bool GetBit(byte value, int position) - => ((value >> (7 - position)) & 1) == 1; + /// + /// Calculates the 4 byte padding + /// + /// the number of bytes to pad + private int CalcPadding() + { + int p = 4 - (this.currentIndex % 4); + return p >= 4 ? 0 : p; } + + /// + /// Gets the bit value at a specified position + /// + /// The value from where the bit will be extracted + /// Position of the bit. Zero based index from left to right. + /// The bit value at specified position + private static bool GetBit(byte value, int position) + => ((value >> (7 - position)) & 1) == 1; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs index 4a6dc64d77..96adffe278 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Curves.cs @@ -3,174 +3,173 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { - /// - /// Provides methods to write ICC data types - /// - internal sealed partial class IccDataWriter + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteOneDimensionalCurve(IccOneDimensionalCurve value) { - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteOneDimensionalCurve(IccOneDimensionalCurve value) - { - int count = this.WriteUInt16((ushort)value.Segments.Length); - count += this.WriteEmpty(2); - - foreach (float point in value.BreakPoints) - { - count += this.WriteSingle(point); - } + int count = this.WriteUInt16((ushort)value.Segments.Length); + count += this.WriteEmpty(2); - foreach (IccCurveSegment segment in value.Segments) - { - count += this.WriteCurveSegment(segment); - } - - return count; + foreach (float point in value.BreakPoints) + { + count += this.WriteSingle(point); } - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteResponseCurve(IccResponseCurve value) + foreach (IccCurveSegment segment in value.Segments) { - int count = this.WriteUInt32((uint)value.CurveType); - - foreach (IccResponseNumber[] responseArray in value.ResponseArrays) - { - count += this.WriteUInt32((uint)responseArray.Length); - } + count += this.WriteCurveSegment(segment); + } - foreach (Vector3 xyz in value.XyzValues) - { - count += this.WriteXyzNumber(xyz); - } + return count; + } - foreach (IccResponseNumber[] responseArray in value.ResponseArrays) - { - foreach (IccResponseNumber response in responseArray) - { - count += this.WriteResponseNumber(response); - } - } + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteResponseCurve(IccResponseCurve value) + { + int count = this.WriteUInt32((uint)value.CurveType); - return count; + foreach (IccResponseNumber[] responseArray in value.ResponseArrays) + { + count += this.WriteUInt32((uint)responseArray.Length); } - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteParametricCurve(IccParametricCurve value) + foreach (Vector3 xyz in value.XyzValues) { - ushort typeValue = (ushort)value.Type; - int count = this.WriteUInt16(typeValue); - count += this.WriteEmpty(2); - - if (typeValue <= 4) - { - count += this.WriteFix16(value.G); - } + count += this.WriteXyzNumber(xyz); + } - if (typeValue > 0 && typeValue <= 4) + foreach (IccResponseNumber[] responseArray in value.ResponseArrays) + { + foreach (IccResponseNumber response in responseArray) { - count += this.WriteFix16(value.A); - count += this.WriteFix16(value.B); + count += this.WriteResponseNumber(response); } + } - if (typeValue > 1 && typeValue <= 4) - { - count += this.WriteFix16(value.C); - } + return count; + } - if (typeValue > 2 && typeValue <= 4) - { - count += this.WriteFix16(value.D); - } + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteParametricCurve(IccParametricCurve value) + { + ushort typeValue = (ushort)value.Type; + int count = this.WriteUInt16(typeValue); + count += this.WriteEmpty(2); - if (typeValue == 4) - { - count += this.WriteFix16(value.E); - count += this.WriteFix16(value.F); - } + if (typeValue <= 4) + { + count += this.WriteFix16(value.G); + } - return count; + if (typeValue > 0 && typeValue <= 4) + { + count += this.WriteFix16(value.A); + count += this.WriteFix16(value.B); } - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteCurveSegment(IccCurveSegment value) + if (typeValue > 1 && typeValue <= 4) { - int count = this.WriteUInt32((uint)value.Signature); - count += this.WriteEmpty(4); + count += this.WriteFix16(value.C); + } - switch (value.Signature) - { - case IccCurveSegmentSignature.FormulaCurve: - return count + this.WriteFormulaCurveElement((IccFormulaCurveElement)value); - case IccCurveSegmentSignature.SampledCurve: - return count + this.WriteSampledCurveElement((IccSampledCurveElement)value); - default: - throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}"); - } + if (typeValue > 2 && typeValue <= 4) + { + count += this.WriteFix16(value.D); } - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteFormulaCurveElement(IccFormulaCurveElement value) + if (typeValue == 4) { - int count = this.WriteUInt16((ushort)value.Type); - count += this.WriteEmpty(2); + count += this.WriteFix16(value.E); + count += this.WriteFix16(value.F); + } - if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2) - { - count += this.WriteSingle(value.Gamma); - } + return count; + } - count += this.WriteSingle(value.A); - count += this.WriteSingle(value.B); - count += this.WriteSingle(value.C); + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteCurveSegment(IccCurveSegment value) + { + int count = this.WriteUInt32((uint)value.Signature); + count += this.WriteEmpty(4); - if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3) - { - count += this.WriteSingle(value.D); - } + switch (value.Signature) + { + case IccCurveSegmentSignature.FormulaCurve: + return count + this.WriteFormulaCurveElement((IccFormulaCurveElement)value); + case IccCurveSegmentSignature.SampledCurve: + return count + this.WriteSampledCurveElement((IccSampledCurveElement)value); + default: + throw new InvalidIccProfileException($"Invalid CurveSegment type of {value.Signature}"); + } + } - if (value.Type == IccFormulaCurveType.Type3) - { - count += this.WriteSingle(value.E); - } + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteFormulaCurveElement(IccFormulaCurveElement value) + { + int count = this.WriteUInt16((ushort)value.Type); + count += this.WriteEmpty(2); - return count; + if (value.Type == IccFormulaCurveType.Type1 || value.Type == IccFormulaCurveType.Type2) + { + count += this.WriteSingle(value.Gamma); } - /// - /// Writes a - /// - /// The curve to write - /// The number of bytes written - public int WriteSampledCurveElement(IccSampledCurveElement value) + count += this.WriteSingle(value.A); + count += this.WriteSingle(value.B); + count += this.WriteSingle(value.C); + + if (value.Type == IccFormulaCurveType.Type2 || value.Type == IccFormulaCurveType.Type3) { - int count = this.WriteUInt32((uint)value.CurveEntries.Length); - foreach (float entry in value.CurveEntries) - { - count += this.WriteSingle(entry); - } + count += this.WriteSingle(value.D); + } - return count; + if (value.Type == IccFormulaCurveType.Type3) + { + count += this.WriteSingle(value.E); + } + + return count; + } + + /// + /// Writes a + /// + /// The curve to write + /// The number of bytes written + public int WriteSampledCurveElement(IccSampledCurveElement value) + { + int count = this.WriteUInt32((uint)value.CurveEntries.Length); + foreach (float entry in value.CurveEntries) + { + count += this.WriteSingle(entry); } + + return count; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs index 18f2c2a4cc..703a3896bb 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Lut.cs @@ -1,126 +1,125 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { - /// - /// Provides methods to write ICC data types - /// - internal sealed partial class IccDataWriter + /// + /// Writes an 8bit lookup table + /// + /// The LUT to write + /// The number of bytes written + public int WriteLut8(IccLut value) { - /// - /// Writes an 8bit lookup table - /// - /// The LUT to write - /// The number of bytes written - public int WriteLut8(IccLut value) + foreach (float item in value.Values) { - foreach (float item in value.Values) - { - this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); - } - - return value.Values.Length; + this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); } - /// - /// Writes an 16bit lookup table - /// - /// The LUT to write - /// The number of bytes written - public int WriteLut16(IccLut value) - { - foreach (float item in value.Values) - { - this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } + return value.Values.Length; + } - return value.Values.Length * 2; + /// + /// Writes an 16bit lookup table + /// + /// The LUT to write + /// The number of bytes written + public int WriteLut16(IccLut value) + { + foreach (float item in value.Values) + { + this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); } - /// - /// Writes an color lookup table - /// - /// The CLUT to write - /// The number of bytes written - public int WriteClut(IccClut value) - { - int count = this.WriteArray(value.GridPointCount); - count += this.WriteEmpty(16 - value.GridPointCount.Length); + return value.Values.Length * 2; + } - switch (value.DataType) - { - case IccClutDataType.Float: - return count + this.WriteClutF32(value); - case IccClutDataType.UInt8: - count += this.WriteByte(1); - count += this.WriteEmpty(3); - return count + this.WriteClut8(value); - case IccClutDataType.UInt16: - count += this.WriteByte(2); - count += this.WriteEmpty(3); - return count + this.WriteClut16(value); + /// + /// Writes an color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut(IccClut value) + { + int count = this.WriteArray(value.GridPointCount); + count += this.WriteEmpty(16 - value.GridPointCount.Length); - default: - throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}"); - } + switch (value.DataType) + { + case IccClutDataType.Float: + return count + this.WriteClutF32(value); + case IccClutDataType.UInt8: + count += this.WriteByte(1); + count += this.WriteEmpty(3); + return count + this.WriteClut8(value); + case IccClutDataType.UInt16: + count += this.WriteByte(2); + count += this.WriteEmpty(3); + return count + this.WriteClut16(value); + + default: + throw new InvalidIccProfileException($"Invalid CLUT data type of {value.DataType}"); } + } - /// - /// Writes a 8bit color lookup table - /// - /// The CLUT to write - /// The number of bytes written - public int WriteClut8(IccClut value) + /// + /// Writes a 8bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut8(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) { - int count = 0; - foreach (float[] inArray in value.Values) + foreach (float item in inArray) { - foreach (float item in inArray) - { - count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); - } + count += this.WriteByte((byte)Numerics.Clamp((item * byte.MaxValue) + 0.5F, 0, byte.MaxValue)); } - - return count; } - /// - /// Writes a 16bit color lookup table - /// - /// The CLUT to write - /// The number of bytes written - public int WriteClut16(IccClut value) + return count; + } + + /// + /// Writes a 16bit color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClut16(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) { - int count = 0; - foreach (float[] inArray in value.Values) + foreach (float item in inArray) { - foreach (float item in inArray) - { - count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } + count += this.WriteUInt16((ushort)Numerics.Clamp((item * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); } - - return count; } - /// - /// Writes a 32bit float color lookup table - /// - /// The CLUT to write - /// The number of bytes written - public int WriteClutF32(IccClut value) + return count; + } + + /// + /// Writes a 32bit float color lookup table + /// + /// The CLUT to write + /// The number of bytes written + public int WriteClutF32(IccClut value) + { + int count = 0; + foreach (float[] inArray in value.Values) { - int count = 0; - foreach (float[] inArray in value.Values) + foreach (float item in inArray) { - foreach (float item in inArray) - { - count += this.WriteSingle(item); - } + count += this.WriteSingle(item); } - - return count; } + + return count; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs index aa170ebea9..1e5f359e09 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -3,156 +3,155 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { /// - /// Provides methods to write ICC data types + /// Writes a two dimensional matrix /// - internal sealed partial class IccDataWriter + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Matrix4x4 value, bool isSingle) { - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Matrix4x4 value, bool isSingle) - { - int count = 0; + int count = 0; - if (isSingle) - { - count += this.WriteSingle(value.M11); - count += this.WriteSingle(value.M21); - count += this.WriteSingle(value.M31); + if (isSingle) + { + count += this.WriteSingle(value.M11); + count += this.WriteSingle(value.M21); + count += this.WriteSingle(value.M31); - count += this.WriteSingle(value.M12); - count += this.WriteSingle(value.M22); - count += this.WriteSingle(value.M32); + count += this.WriteSingle(value.M12); + count += this.WriteSingle(value.M22); + count += this.WriteSingle(value.M32); - count += this.WriteSingle(value.M13); - count += this.WriteSingle(value.M23); - count += this.WriteSingle(value.M33); - } - else - { - count += this.WriteFix16(value.M11); - count += this.WriteFix16(value.M21); - count += this.WriteFix16(value.M31); - - count += this.WriteFix16(value.M12); - count += this.WriteFix16(value.M22); - count += this.WriteFix16(value.M32); + count += this.WriteSingle(value.M13); + count += this.WriteSingle(value.M23); + count += this.WriteSingle(value.M33); + } + else + { + count += this.WriteFix16(value.M11); + count += this.WriteFix16(value.M21); + count += this.WriteFix16(value.M31); - count += this.WriteFix16(value.M13); - count += this.WriteFix16(value.M23); - count += this.WriteFix16(value.M33); - } + count += this.WriteFix16(value.M12); + count += this.WriteFix16(value.M22); + count += this.WriteFix16(value.M32); - return count; + count += this.WriteFix16(value.M13); + count += this.WriteFix16(value.M23); + count += this.WriteFix16(value.M33); } - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(in DenseMatrix value, bool isSingle) + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(in DenseMatrix value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.Rows; y++) { - int count = 0; - for (int y = 0; y < value.Rows; y++) + for (int x = 0; x < value.Columns; x++) { - for (int x = 0; x < value.Columns; x++) + if (isSingle) { - if (isSingle) - { - count += this.WriteSingle(value[x, y]); - } - else - { - count += this.WriteFix16(value[x, y]); - } + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); } } - - return count; } - /// - /// Writes a two dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[,] value, bool isSingle) + return count; + } + + /// + /// Writes a two dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[,] value, bool isSingle) + { + int count = 0; + for (int y = 0; y < value.GetLength(1); y++) { - int count = 0; - for (int y = 0; y < value.GetLength(1); y++) + for (int x = 0; x < value.GetLength(0); x++) { - for (int x = 0; x < value.GetLength(0); x++) + if (isSingle) { - if (isSingle) - { - count += this.WriteSingle(value[x, y]); - } - else - { - count += this.WriteFix16(value[x, y]); - } + count += this.WriteSingle(value[x, y]); + } + else + { + count += this.WriteFix16(value[x, y]); } } + } - return count; + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(Vector3 value, bool isSingle) + { + int count = 0; + if (isSingle) + { + count += this.WriteSingle(value.X); + count += this.WriteSingle(value.Y); + count += this.WriteSingle(value.Z); + } + else + { + count += this.WriteFix16(value.X); + count += this.WriteFix16(value.Y); + count += this.WriteFix16(value.Z); } - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(Vector3 value, bool isSingle) + return count; + } + + /// + /// Writes a one dimensional matrix + /// + /// The matrix to write + /// True if the values are encoded as Single; false if encoded as Fix16 + /// The number of bytes written + public int WriteMatrix(float[] value, bool isSingle) + { + int count = 0; + for (int i = 0; i < value.Length; i++) { - int count = 0; if (isSingle) { - count += this.WriteSingle(value.X); - count += this.WriteSingle(value.Y); - count += this.WriteSingle(value.Z); + count += this.WriteSingle(value[i]); } else { - count += this.WriteFix16(value.X); - count += this.WriteFix16(value.Y); - count += this.WriteFix16(value.Z); + count += this.WriteFix16(value[i]); } - - return count; } - /// - /// Writes a one dimensional matrix - /// - /// The matrix to write - /// True if the values are encoded as Single; false if encoded as Fix16 - /// The number of bytes written - public int WriteMatrix(float[] value, bool isSingle) - { - int count = 0; - for (int i = 0; i < value.Length; i++) - { - if (isSingle) - { - count += this.WriteSingle(value[i]); - } - else - { - count += this.WriteFix16(value[i]); - } - } - - return count; - } + return count; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs index 094dd086b9..9be693ba22 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElement.cs @@ -1,78 +1,77 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { /// - /// Provides methods to write ICC data types + /// Writes a /// - internal sealed partial class IccDataWriter + /// The element to write + /// The number of bytes written + public int WriteMultiProcessElement(IccMultiProcessElement value) { - /// - /// Writes a - /// - /// The element to write - /// The number of bytes written - public int WriteMultiProcessElement(IccMultiProcessElement value) - { - int count = this.WriteUInt32((uint)value.Signature); - count += this.WriteUInt16((ushort)value.InputChannelCount); - count += this.WriteUInt16((ushort)value.OutputChannelCount); + int count = this.WriteUInt32((uint)value.Signature); + count += this.WriteUInt16((ushort)value.InputChannelCount); + count += this.WriteUInt16((ushort)value.OutputChannelCount); - switch (value.Signature) - { - case IccMultiProcessElementSignature.CurveSet: - return count + this.WriteCurveSetProcessElement((IccCurveSetProcessElement)value); - case IccMultiProcessElementSignature.Matrix: - return count + this.WriteMatrixProcessElement((IccMatrixProcessElement)value); - case IccMultiProcessElementSignature.Clut: - return count + this.WriteClutProcessElement((IccClutProcessElement)value); + switch (value.Signature) + { + case IccMultiProcessElementSignature.CurveSet: + return count + this.WriteCurveSetProcessElement((IccCurveSetProcessElement)value); + case IccMultiProcessElementSignature.Matrix: + return count + this.WriteMatrixProcessElement((IccMatrixProcessElement)value); + case IccMultiProcessElementSignature.Clut: + return count + this.WriteClutProcessElement((IccClutProcessElement)value); - case IccMultiProcessElementSignature.BAcs: - case IccMultiProcessElementSignature.EAcs: - return count + this.WriteEmpty(8); + case IccMultiProcessElementSignature.BAcs: + case IccMultiProcessElementSignature.EAcs: + return count + this.WriteEmpty(8); - default: - throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}"); - } + default: + throw new InvalidIccProfileException($"Invalid MultiProcessElement type of {value.Signature}"); } + } - /// - /// Writes a CurveSet - /// - /// The element to write - /// The number of bytes written - public int WriteCurveSetProcessElement(IccCurveSetProcessElement value) + /// + /// Writes a CurveSet + /// + /// The element to write + /// The number of bytes written + public int WriteCurveSetProcessElement(IccCurveSetProcessElement value) + { + int count = 0; + foreach (IccOneDimensionalCurve curve in value.Curves) { - int count = 0; - foreach (IccOneDimensionalCurve curve in value.Curves) - { - count += this.WriteOneDimensionalCurve(curve); - count += this.WritePadding(); - } - - return count; + count += this.WriteOneDimensionalCurve(curve); + count += this.WritePadding(); } - /// - /// Writes a Matrix - /// - /// The element to write - /// The number of bytes written - public int WriteMatrixProcessElement(IccMatrixProcessElement value) - { - return this.WriteMatrix(value.MatrixIxO, true) - + this.WriteMatrix(value.MatrixOx1, true); - } + return count; + } - /// - /// Writes a CLUT - /// - /// The element to write - /// The number of bytes written - public int WriteClutProcessElement(IccClutProcessElement value) - { - return this.WriteClut(value.ClutValue); - } + /// + /// Writes a Matrix + /// + /// The element to write + /// The number of bytes written + public int WriteMatrixProcessElement(IccMatrixProcessElement value) + { + return this.WriteMatrix(value.MatrixIxO, true) + + this.WriteMatrix(value.MatrixOx1, true); + } + + /// + /// Writes a CLUT + /// + /// The element to write + /// The number of bytes written + public int WriteClutProcessElement(IccClutProcessElement value) + { + return this.WriteClut(value.ClutValue); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs index e93cd105f0..db199b4381 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitives.cs @@ -1,132 +1,130 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { /// - /// Provides methods to write ICC data types + /// Writes a DateTime /// - internal sealed partial class IccDataWriter + /// The value to write + /// the number of bytes written + public int WriteDateTime(DateTime value) { - /// - /// Writes a DateTime - /// - /// The value to write - /// the number of bytes written - public int WriteDateTime(DateTime value) - { - return this.WriteUInt16((ushort)value.Year) - + this.WriteUInt16((ushort)value.Month) - + this.WriteUInt16((ushort)value.Day) - + this.WriteUInt16((ushort)value.Hour) - + this.WriteUInt16((ushort)value.Minute) - + this.WriteUInt16((ushort)value.Second); - } + return this.WriteUInt16((ushort)value.Year) + + this.WriteUInt16((ushort)value.Month) + + this.WriteUInt16((ushort)value.Day) + + this.WriteUInt16((ushort)value.Hour) + + this.WriteUInt16((ushort)value.Minute) + + this.WriteUInt16((ushort)value.Second); + } - /// - /// Writes an ICC profile version number - /// - /// The value to write - /// the number of bytes written - public int WriteVersionNumber(in IccVersion value) - { - int major = Numerics.Clamp(value.Major, 0, byte.MaxValue); - int minor = Numerics.Clamp(value.Minor, 0, 15); - int bugfix = Numerics.Clamp(value.Patch, 0, 15); + /// + /// Writes an ICC profile version number + /// + /// The value to write + /// the number of bytes written + public int WriteVersionNumber(in IccVersion value) + { + int major = Numerics.Clamp(value.Major, 0, byte.MaxValue); + int minor = Numerics.Clamp(value.Minor, 0, 15); + int bugfix = Numerics.Clamp(value.Patch, 0, 15); - int version = (major << 24) | (minor << 20) | (bugfix << 16); - return this.WriteInt32(version); - } + int version = (major << 24) | (minor << 20) | (bugfix << 16); + return this.WriteInt32(version); + } - /// - /// Writes an XYZ number - /// - /// The value to write - /// the number of bytes written - public int WriteXyzNumber(Vector3 value) - { - return this.WriteFix16(value.X) - + this.WriteFix16(value.Y) - + this.WriteFix16(value.Z); - } + /// + /// Writes an XYZ number + /// + /// The value to write + /// the number of bytes written + public int WriteXyzNumber(Vector3 value) + { + return this.WriteFix16(value.X) + + this.WriteFix16(value.Y) + + this.WriteFix16(value.Z); + } - /// - /// Writes a profile ID - /// - /// The value to write - /// the number of bytes written - public int WriteProfileId(in IccProfileId value) - { - return this.WriteUInt32(value.Part1) - + this.WriteUInt32(value.Part2) - + this.WriteUInt32(value.Part3) - + this.WriteUInt32(value.Part4); - } + /// + /// Writes a profile ID + /// + /// The value to write + /// the number of bytes written + public int WriteProfileId(in IccProfileId value) + { + return this.WriteUInt32(value.Part1) + + this.WriteUInt32(value.Part2) + + this.WriteUInt32(value.Part3) + + this.WriteUInt32(value.Part4); + } - /// - /// Writes a position number - /// - /// The value to write - /// the number of bytes written - public int WritePositionNumber(in IccPositionNumber value) - { - return this.WriteUInt32(value.Offset) - + this.WriteUInt32(value.Size); - } + /// + /// Writes a position number + /// + /// The value to write + /// the number of bytes written + public int WritePositionNumber(in IccPositionNumber value) + { + return this.WriteUInt32(value.Offset) + + this.WriteUInt32(value.Size); + } - /// - /// Writes a response number - /// - /// The value to write - /// the number of bytes written - public int WriteResponseNumber(in IccResponseNumber value) - { - return this.WriteUInt16(value.DeviceCode) - + this.WriteFix16(value.MeasurementValue); - } + /// + /// Writes a response number + /// + /// The value to write + /// the number of bytes written + public int WriteResponseNumber(in IccResponseNumber value) + { + return this.WriteUInt16(value.DeviceCode) + + this.WriteFix16(value.MeasurementValue); + } - /// - /// Writes a named color - /// - /// The value to write - /// the number of bytes written - public int WriteNamedColor(in IccNamedColor value) - { - return this.WriteAsciiString(value.Name, 32, true) - + this.WriteArray(value.PcsCoordinates) - + this.WriteArray(value.DeviceCoordinates); - } + /// + /// Writes a named color + /// + /// The value to write + /// the number of bytes written + public int WriteNamedColor(in IccNamedColor value) + { + return this.WriteAsciiString(value.Name, 32, true) + + this.WriteArray(value.PcsCoordinates) + + this.WriteArray(value.DeviceCoordinates); + } - /// - /// Writes a profile description - /// - /// The value to write - /// the number of bytes written - public int WriteProfileDescription(in IccProfileDescription value) - { - return this.WriteUInt32(value.DeviceManufacturer) - + this.WriteUInt32(value.DeviceModel) - + this.WriteInt64((long)value.DeviceAttributes) - + this.WriteUInt32((uint)value.TechnologyInformation) - + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) - + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo)) - + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) - + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo)); - } + /// + /// Writes a profile description + /// + /// The value to write + /// the number of bytes written + public int WriteProfileDescription(in IccProfileDescription value) + { + return this.WriteUInt32(value.DeviceManufacturer) + + this.WriteUInt32(value.DeviceModel) + + this.WriteInt64((long)value.DeviceAttributes) + + this.WriteUInt32((uint)value.TechnologyInformation) + + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) + + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceManufacturerInfo)) + + this.WriteTagDataEntryHeader(IccTypeSignature.MultiLocalizedUnicode) + + this.WriteMultiLocalizedUnicodeTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(value.DeviceModelInfo)); + } - /// - /// Writes a screening channel - /// - /// The value to write - /// the number of bytes written - public int WriteScreeningChannel(in IccScreeningChannel value) - { - return this.WriteFix16(value.Frequency) - + this.WriteFix16(value.Angle) - + this.WriteInt32((int)value.SpotShape); - } + /// + /// Writes a screening channel + /// + /// The value to write + /// the number of bytes written + public int WriteScreeningChannel(in IccScreeningChannel value) + { + return this.WriteFix16(value.Frequency) + + this.WriteFix16(value.Angle) + + this.WriteInt32((int)value.SpotShape); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs index d3113b0140..e1eebb749b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -1,246 +1,244 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { /// - /// Provides methods to write ICC data types + /// Writes a byte /// - internal sealed partial class IccDataWriter + /// The value to write + /// the number of bytes written + public int WriteByte(byte value) { - /// - /// Writes a byte - /// - /// The value to write - /// the number of bytes written - public int WriteByte(byte value) - { - this.dataStream.WriteByte(value); - return 1; - } + this.dataStream.WriteByte(value); + return 1; + } - /// - /// Writes an ushort - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt16(ushort value) - { - return this.WriteBytes((byte*)&value, 2); - } + /// + /// Writes an ushort + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt16(ushort value) + { + return this.WriteBytes((byte*)&value, 2); + } - /// - /// Writes a short - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt16(short value) - { - return this.WriteBytes((byte*)&value, 2); - } + /// + /// Writes a short + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt16(short value) + { + return this.WriteBytes((byte*)&value, 2); + } - /// - /// Writes an uint - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt32(uint value) - { - return this.WriteBytes((byte*)&value, 4); - } + /// + /// Writes an uint + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt32(uint value) + { + return this.WriteBytes((byte*)&value, 4); + } - /// - /// Writes an int - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt32(int value) - { - return this.WriteBytes((byte*)&value, 4); - } + /// + /// Writes an int + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt32(int value) + { + return this.WriteBytes((byte*)&value, 4); + } - /// - /// Writes an ulong - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteUInt64(ulong value) - { - return this.WriteBytes((byte*)&value, 8); - } + /// + /// Writes an ulong + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteUInt64(ulong value) + { + return this.WriteBytes((byte*)&value, 8); + } - /// - /// Writes a long - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteInt64(long value) - { - return this.WriteBytes((byte*)&value, 8); - } + /// + /// Writes a long + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteInt64(long value) + { + return this.WriteBytes((byte*)&value, 8); + } - /// - /// Writes a float - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteSingle(float value) - { - return this.WriteBytes((byte*)&value, 4); - } + /// + /// Writes a float + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteSingle(float value) + { + return this.WriteBytes((byte*)&value, 4); + } - /// - /// Writes a double - /// - /// The value to write - /// the number of bytes written - public unsafe int WriteDouble(double value) - { - return this.WriteBytes((byte*)&value, 8); - } + /// + /// Writes a double + /// + /// The value to write + /// the number of bytes written + public unsafe int WriteDouble(double value) + { + return this.WriteBytes((byte*)&value, 8); + } - /// - /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteFix16(double value) - { - const double Max = short.MaxValue + (65535d / 65536d); - const double Min = short.MinValue; + /// + /// Writes a signed 32bit number with 1 sign bit, 15 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteFix16(double value) + { + const double Max = short.MaxValue + (65535d / 65536d); + const double Min = short.MinValue; - value = Numerics.Clamp(value, Min, Max); - value *= 65536d; + value = Numerics.Clamp(value, Min, Max); + value *= 65536d; - return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero)); - } + return this.WriteInt32((int)Math.Round(value, MidpointRounding.AwayFromZero)); + } - /// - /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteUFix16(double value) - { - const double Max = ushort.MaxValue + (65535d / 65536d); - const double Min = ushort.MinValue; + /// + /// Writes an unsigned 32bit number with 16 value bits and 16 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteUFix16(double value) + { + const double Max = ushort.MaxValue + (65535d / 65536d); + const double Min = ushort.MinValue; - value = Numerics.Clamp(value, Min, Max); - value *= 65536d; + value = Numerics.Clamp(value, Min, Max); + value *= 65536d; - return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero)); - } + return this.WriteUInt32((uint)Math.Round(value, MidpointRounding.AwayFromZero)); + } - /// - /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteU1Fix15(double value) - { - const double Max = 1 + (32767d / 32768d); - const double Min = 0; + /// + /// Writes an unsigned 16bit number with 1 value bit and 15 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteU1Fix15(double value) + { + const double Max = 1 + (32767d / 32768d); + const double Min = 0; + + value = Numerics.Clamp(value, Min, Max); + value *= 32768d; + + return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits + /// + /// The value to write + /// the number of bytes written + public int WriteUFix8(double value) + { + const double Max = byte.MaxValue + (255d / 256d); + const double Min = byte.MinValue; - value = Numerics.Clamp(value, Min, Max); - value *= 32768d; + value = Numerics.Clamp(value, Min, Max); + value *= 256d; - return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + } + + /// + /// Writes an ASCII encoded string + /// + /// the string to write + /// the number of bytes written + public int WriteAsciiString(string value) + { + if (string.IsNullOrEmpty(value)) + { + return 0; } - /// - /// Writes an unsigned 16bit number with 8 value bits and 8 fractional bits - /// - /// The value to write - /// the number of bytes written - public int WriteUFix8(double value) + byte[] data = Encoding.ASCII.GetBytes(value); + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } + + /// + /// Writes an ASCII encoded string resizes it to the given length + /// + /// The string to write + /// The desired length of the string (including potential null terminator) + /// If True, there will be a \0 added at the end + /// the number of bytes written + public int WriteAsciiString(string value, int length, bool ensureNullTerminator) + { + if (length == 0) { - const double Max = byte.MaxValue + (255d / 256d); - const double Min = byte.MinValue; + return 0; + } - value = Numerics.Clamp(value, Min, Max); - value *= 256d; + Guard.MustBeGreaterThan(length, 0, nameof(length)); - return this.WriteUInt16((ushort)Math.Round(value, MidpointRounding.AwayFromZero)); + if (value is null) + { + value = string.Empty; } - /// - /// Writes an ASCII encoded string - /// - /// the string to write - /// the number of bytes written - public int WriteAsciiString(string value) + byte paddingChar = (byte)' '; + int lengthAdjust = 0; + + if (ensureNullTerminator) { - if (string.IsNullOrEmpty(value)) - { - return 0; - } - - byte[] data = Encoding.ASCII.GetBytes(value); - this.dataStream.Write(data, 0, data.Length); - return data.Length; + paddingChar = 0; + lengthAdjust = 1; } - /// - /// Writes an ASCII encoded string resizes it to the given length - /// - /// The string to write - /// The desired length of the string (including potential null terminator) - /// If True, there will be a \0 added at the end - /// the number of bytes written - public int WriteAsciiString(string value, int length, bool ensureNullTerminator) + value = value[..Math.Min(length - lengthAdjust, value.Length)]; + + byte[] textData = Encoding.ASCII.GetBytes(value); + int actualLength = Math.Min(length - lengthAdjust, textData.Length); + this.dataStream.Write(textData, 0, actualLength); + for (int i = 0; i < length - actualLength; i++) { - if (length == 0) - { - return 0; - } - - Guard.MustBeGreaterThan(length, 0, nameof(length)); - - if (value is null) - { - value = string.Empty; - } - - byte paddingChar = (byte)' '; - int lengthAdjust = 0; - - if (ensureNullTerminator) - { - paddingChar = 0; - lengthAdjust = 1; - } - - value = value[..Math.Min(length - lengthAdjust, value.Length)]; - - byte[] textData = Encoding.ASCII.GetBytes(value); - int actualLength = Math.Min(length - lengthAdjust, textData.Length); - this.dataStream.Write(textData, 0, actualLength); - for (int i = 0; i < length - actualLength; i++) - { - this.dataStream.WriteByte(paddingChar); - } - - return length; + this.dataStream.WriteByte(paddingChar); } - /// - /// Writes an UTF-16 big-endian encoded string - /// - /// the string to write - /// the number of bytes written - public int WriteUnicodeString(string value) + return length; + } + + /// + /// Writes an UTF-16 big-endian encoded string + /// + /// the string to write + /// the number of bytes written + public int WriteUnicodeString(string value) + { + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(value)) - { - return 0; - } - - byte[] data = Encoding.BigEndianUnicode.GetBytes(value); - this.dataStream.Write(data, 0, data.Length); - return data.Length; + return 0; } + + byte[] data = Encoding.BigEndianUnicode.GetBytes(value); + this.dataStream.Write(data, 0, data.Length); + return data.Length; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs index 8ff48bf134..2b4fdd2391 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -1,922 +1,919 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter { /// - /// Provides methods to write ICC data types + /// Writes a tag data entry /// - internal sealed partial class IccDataWriter + /// The entry to write + /// The table entry for the written data entry + /// The number of bytes written (excluding padding) + public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table) { - /// - /// Writes a tag data entry - /// - /// The entry to write - /// The table entry for the written data entry - /// The number of bytes written (excluding padding) - public int WriteTagDataEntry(IccTagDataEntry data, out IccTagTableEntry table) - { - uint offset = (uint)this.dataStream.Position; - int count = this.WriteTagDataEntry(data); - this.WritePadding(); - table = new IccTagTableEntry(data.TagSignature, offset, (uint)count); - return count; - } - - /// - /// Writes a tag data entry (without padding) - /// - /// The entry to write - /// The number of bytes written - public int WriteTagDataEntry(IccTagDataEntry entry) - { - int count = this.WriteTagDataEntryHeader(entry.Signature); - - count += entry.Signature switch - { - IccTypeSignature.Chromaticity => this.WriteChromaticityTagDataEntry((IccChromaticityTagDataEntry)entry), - IccTypeSignature.ColorantOrder => this.WriteColorantOrderTagDataEntry((IccColorantOrderTagDataEntry)entry), - IccTypeSignature.ColorantTable => this.WriteColorantTableTagDataEntry((IccColorantTableTagDataEntry)entry), - IccTypeSignature.Curve => this.WriteCurveTagDataEntry((IccCurveTagDataEntry)entry), - IccTypeSignature.Data => this.WriteDataTagDataEntry((IccDataTagDataEntry)entry), - IccTypeSignature.DateTime => this.WriteDateTimeTagDataEntry((IccDateTimeTagDataEntry)entry), - IccTypeSignature.Lut16 => this.WriteLut16TagDataEntry((IccLut16TagDataEntry)entry), - IccTypeSignature.Lut8 => this.WriteLut8TagDataEntry((IccLut8TagDataEntry)entry), - IccTypeSignature.LutAToB => this.WriteLutAtoBTagDataEntry((IccLutAToBTagDataEntry)entry), - IccTypeSignature.LutBToA => this.WriteLutBtoATagDataEntry((IccLutBToATagDataEntry)entry), - IccTypeSignature.Measurement => this.WriteMeasurementTagDataEntry((IccMeasurementTagDataEntry)entry), - IccTypeSignature.MultiLocalizedUnicode => this.WriteMultiLocalizedUnicodeTagDataEntry((IccMultiLocalizedUnicodeTagDataEntry)entry), - IccTypeSignature.MultiProcessElements => this.WriteMultiProcessElementsTagDataEntry((IccMultiProcessElementsTagDataEntry)entry), - IccTypeSignature.NamedColor2 => this.WriteNamedColor2TagDataEntry((IccNamedColor2TagDataEntry)entry), - IccTypeSignature.ParametricCurve => this.WriteParametricCurveTagDataEntry((IccParametricCurveTagDataEntry)entry), - IccTypeSignature.ProfileSequenceDesc => this.WriteProfileSequenceDescTagDataEntry((IccProfileSequenceDescTagDataEntry)entry), - IccTypeSignature.ProfileSequenceIdentifier => this.WriteProfileSequenceIdentifierTagDataEntry((IccProfileSequenceIdentifierTagDataEntry)entry), - IccTypeSignature.ResponseCurveSet16 => this.WriteResponseCurveSet16TagDataEntry((IccResponseCurveSet16TagDataEntry)entry), - IccTypeSignature.S15Fixed16Array => this.WriteFix16ArrayTagDataEntry((IccFix16ArrayTagDataEntry)entry), - IccTypeSignature.Signature => this.WriteSignatureTagDataEntry((IccSignatureTagDataEntry)entry), - IccTypeSignature.Text => this.WriteTextTagDataEntry((IccTextTagDataEntry)entry), - IccTypeSignature.U16Fixed16Array => this.WriteUFix16ArrayTagDataEntry((IccUFix16ArrayTagDataEntry)entry), - IccTypeSignature.UInt16Array => this.WriteUInt16ArrayTagDataEntry((IccUInt16ArrayTagDataEntry)entry), - IccTypeSignature.UInt32Array => this.WriteUInt32ArrayTagDataEntry((IccUInt32ArrayTagDataEntry)entry), - IccTypeSignature.UInt64Array => this.WriteUInt64ArrayTagDataEntry((IccUInt64ArrayTagDataEntry)entry), - IccTypeSignature.UInt8Array => this.WriteUInt8ArrayTagDataEntry((IccUInt8ArrayTagDataEntry)entry), - IccTypeSignature.ViewingConditions => this.WriteViewingConditionsTagDataEntry((IccViewingConditionsTagDataEntry)entry), - IccTypeSignature.Xyz => this.WriteXyzTagDataEntry((IccXyzTagDataEntry)entry), - - // V2 Types: - IccTypeSignature.TextDescription => this.WriteTextDescriptionTagDataEntry((IccTextDescriptionTagDataEntry)entry), - IccTypeSignature.CrdInfo => this.WriteCrdInfoTagDataEntry((IccCrdInfoTagDataEntry)entry), - IccTypeSignature.Screening => this.WriteScreeningTagDataEntry((IccScreeningTagDataEntry)entry), - IccTypeSignature.UcrBg => this.WriteUcrBgTagDataEntry((IccUcrBgTagDataEntry)entry), - - // Unsupported or unknown - _ => this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry), - }; - return count; - } - - /// - /// Writes the header of a - /// - /// The signature of the entry - /// The number of bytes written - public int WriteTagDataEntryHeader(IccTypeSignature signature) - => this.WriteUInt32((uint)signature) + this.WriteEmpty(4); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value) - { - int count = this.WriteUInt16((ushort)value.ChannelCount); - count += this.WriteUInt16((ushort)value.ColorantType); - - for (int i = 0; i < value.ChannelCount; i++) - { - count += this.WriteUFix16(value.ChannelValues[i][0]); - count += this.WriteUFix16(value.ChannelValues[i][1]); - } - - return count; - } + uint offset = (uint)this.dataStream.Position; + int count = this.WriteTagDataEntry(data); + this.WritePadding(); + table = new IccTagTableEntry(data.TagSignature, offset, (uint)count); + return count; + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value) - => this.WriteUInt32((uint)value.ColorantNumber.Length) - + this.WriteArray(value.ColorantNumber); + /// + /// Writes a tag data entry (without padding) + /// + /// The entry to write + /// The number of bytes written + public int WriteTagDataEntry(IccTagDataEntry entry) + { + int count = this.WriteTagDataEntryHeader(entry.Signature); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value) + count += entry.Signature switch { - int count = this.WriteUInt32((uint)value.ColorantData.Length); + IccTypeSignature.Chromaticity => this.WriteChromaticityTagDataEntry((IccChromaticityTagDataEntry)entry), + IccTypeSignature.ColorantOrder => this.WriteColorantOrderTagDataEntry((IccColorantOrderTagDataEntry)entry), + IccTypeSignature.ColorantTable => this.WriteColorantTableTagDataEntry((IccColorantTableTagDataEntry)entry), + IccTypeSignature.Curve => this.WriteCurveTagDataEntry((IccCurveTagDataEntry)entry), + IccTypeSignature.Data => this.WriteDataTagDataEntry((IccDataTagDataEntry)entry), + IccTypeSignature.DateTime => this.WriteDateTimeTagDataEntry((IccDateTimeTagDataEntry)entry), + IccTypeSignature.Lut16 => this.WriteLut16TagDataEntry((IccLut16TagDataEntry)entry), + IccTypeSignature.Lut8 => this.WriteLut8TagDataEntry((IccLut8TagDataEntry)entry), + IccTypeSignature.LutAToB => this.WriteLutAtoBTagDataEntry((IccLutAToBTagDataEntry)entry), + IccTypeSignature.LutBToA => this.WriteLutBtoATagDataEntry((IccLutBToATagDataEntry)entry), + IccTypeSignature.Measurement => this.WriteMeasurementTagDataEntry((IccMeasurementTagDataEntry)entry), + IccTypeSignature.MultiLocalizedUnicode => this.WriteMultiLocalizedUnicodeTagDataEntry((IccMultiLocalizedUnicodeTagDataEntry)entry), + IccTypeSignature.MultiProcessElements => this.WriteMultiProcessElementsTagDataEntry((IccMultiProcessElementsTagDataEntry)entry), + IccTypeSignature.NamedColor2 => this.WriteNamedColor2TagDataEntry((IccNamedColor2TagDataEntry)entry), + IccTypeSignature.ParametricCurve => this.WriteParametricCurveTagDataEntry((IccParametricCurveTagDataEntry)entry), + IccTypeSignature.ProfileSequenceDesc => this.WriteProfileSequenceDescTagDataEntry((IccProfileSequenceDescTagDataEntry)entry), + IccTypeSignature.ProfileSequenceIdentifier => this.WriteProfileSequenceIdentifierTagDataEntry((IccProfileSequenceIdentifierTagDataEntry)entry), + IccTypeSignature.ResponseCurveSet16 => this.WriteResponseCurveSet16TagDataEntry((IccResponseCurveSet16TagDataEntry)entry), + IccTypeSignature.S15Fixed16Array => this.WriteFix16ArrayTagDataEntry((IccFix16ArrayTagDataEntry)entry), + IccTypeSignature.Signature => this.WriteSignatureTagDataEntry((IccSignatureTagDataEntry)entry), + IccTypeSignature.Text => this.WriteTextTagDataEntry((IccTextTagDataEntry)entry), + IccTypeSignature.U16Fixed16Array => this.WriteUFix16ArrayTagDataEntry((IccUFix16ArrayTagDataEntry)entry), + IccTypeSignature.UInt16Array => this.WriteUInt16ArrayTagDataEntry((IccUInt16ArrayTagDataEntry)entry), + IccTypeSignature.UInt32Array => this.WriteUInt32ArrayTagDataEntry((IccUInt32ArrayTagDataEntry)entry), + IccTypeSignature.UInt64Array => this.WriteUInt64ArrayTagDataEntry((IccUInt64ArrayTagDataEntry)entry), + IccTypeSignature.UInt8Array => this.WriteUInt8ArrayTagDataEntry((IccUInt8ArrayTagDataEntry)entry), + IccTypeSignature.ViewingConditions => this.WriteViewingConditionsTagDataEntry((IccViewingConditionsTagDataEntry)entry), + IccTypeSignature.Xyz => this.WriteXyzTagDataEntry((IccXyzTagDataEntry)entry), + + // V2 Types: + IccTypeSignature.TextDescription => this.WriteTextDescriptionTagDataEntry((IccTextDescriptionTagDataEntry)entry), + IccTypeSignature.CrdInfo => this.WriteCrdInfoTagDataEntry((IccCrdInfoTagDataEntry)entry), + IccTypeSignature.Screening => this.WriteScreeningTagDataEntry((IccScreeningTagDataEntry)entry), + IccTypeSignature.UcrBg => this.WriteUcrBgTagDataEntry((IccUcrBgTagDataEntry)entry), + + // Unsupported or unknown + _ => this.WriteUnknownTagDataEntry(entry as IccUnknownTagDataEntry), + }; + return count; + } - for (int i = 0; i < value.ColorantData.Length; i++) - { - ref IccColorantTableEntry colorant = ref value.ColorantData[i]; + /// + /// Writes the header of a + /// + /// The signature of the entry + /// The number of bytes written + public int WriteTagDataEntryHeader(IccTypeSignature signature) + => this.WriteUInt32((uint)signature) + this.WriteEmpty(4); - count += this.WriteAsciiString(colorant.Name, 32, true); - count += this.WriteUInt16(colorant.Pcs1); - count += this.WriteUInt16(colorant.Pcs2); - count += this.WriteUInt16(colorant.Pcs3); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUnknownTagDataEntry(IccUnknownTagDataEntry value) => this.WriteArray(value.Data); - return count; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteChromaticityTagDataEntry(IccChromaticityTagDataEntry value) + { + int count = this.WriteUInt16((ushort)value.ChannelCount); + count += this.WriteUInt16((ushort)value.ColorantType); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteCurveTagDataEntry(IccCurveTagDataEntry value) + for (int i = 0; i < value.ChannelCount; i++) { - int count = 0; + count += this.WriteUFix16(value.ChannelValues[i][0]); + count += this.WriteUFix16(value.ChannelValues[i][1]); + } - if (value.IsIdentityResponse) - { - count += this.WriteUInt32(0); - } - else if (value.IsGamma) - { - count += this.WriteUInt32(1); - count += this.WriteUFix8(value.Gamma); - } - else - { - count += this.WriteUInt32((uint)value.CurveData.Length); - for (int i = 0; i < value.CurveData.Length; i++) - { - count += this.WriteUInt16((ushort)Numerics.Clamp((value.CurveData[i] * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); - } - } + return count; + } - return count; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantOrderTagDataEntry(IccColorantOrderTagDataEntry value) + => this.WriteUInt32((uint)value.ColorantNumber.Length) + + this.WriteArray(value.ColorantNumber); - // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteColorantTableTagDataEntry(IccColorantTableTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.ColorantData.Length); + + for (int i = 0; i < value.ColorantData.Length; i++) + { + ref IccColorantTableEntry colorant = ref value.ColorantData[i]; + + count += this.WriteAsciiString(colorant.Name, 32, true); + count += this.WriteUInt16(colorant.Pcs1); + count += this.WriteUInt16(colorant.Pcs2); + count += this.WriteUInt16(colorant.Pcs3); } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteDataTagDataEntry(IccDataTagDataEntry value) - => this.WriteEmpty(3) - + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00)) - + this.WriteArray(value.Data); + return count; + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) => this.WriteDateTime(value.Value); + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCurveTagDataEntry(IccCurveTagDataEntry value) + { + int count = 0; - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLut16TagDataEntry(IccLut16TagDataEntry value) + if (value.IsIdentityResponse) { - int count = this.WriteByte((byte)value.InputValues.Length); - count += this.WriteByte((byte)value.OutputValues.Length); - count += this.WriteByte(value.ClutValues.GridPointCount[0]); - count += this.WriteEmpty(1); + count += this.WriteUInt32(0); + } + else if (value.IsGamma) + { + count += this.WriteUInt32(1); + count += this.WriteUFix8(value.Gamma); + } + else + { + count += this.WriteUInt32((uint)value.CurveData.Length); + for (int i = 0; i < value.CurveData.Length; i++) + { + count += this.WriteUInt16((ushort)Numerics.Clamp((value.CurveData[i] * ushort.MaxValue) + 0.5F, 0, ushort.MaxValue)); + } + } - count += this.WriteMatrix(value.Matrix, false); + return count; - count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length); - count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length); + // TODO: Page 48: If the input is PCSXYZ, 1+(32 767/32 768) shall be mapped to the value 1,0. If the output is PCSXYZ, the value 1,0 shall be mapped to 1+(32 767/32 768). + } - foreach (IccLut lut in value.InputValues) - { - count += this.WriteLut16(lut); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDataTagDataEntry(IccDataTagDataEntry value) + => this.WriteEmpty(3) + + this.WriteByte((byte)(value.IsAscii ? 0x01 : 0x00)) + + this.WriteArray(value.Data); - count += this.WriteClut16(value.ClutValues); + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteDateTimeTagDataEntry(IccDateTimeTagDataEntry value) => this.WriteDateTime(value.Value); - foreach (IccLut lut in value.OutputValues) - { - count += this.WriteLut16(lut); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut16TagDataEntry(IccLut16TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputValues.Length); + count += this.WriteByte((byte)value.OutputValues.Length); + count += this.WriteByte(value.ClutValues.GridPointCount[0]); + count += this.WriteEmpty(1); - return count; - } + count += this.WriteMatrix(value.Matrix, false); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLut8TagDataEntry(IccLut8TagDataEntry value) + count += this.WriteUInt16((ushort)value.InputValues[0].Values.Length); + count += this.WriteUInt16((ushort)value.OutputValues[0].Values.Length); + + foreach (IccLut lut in value.InputValues) { - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteByte((byte)value.ClutValues.Values[0].Length); - count += this.WriteEmpty(1); + count += this.WriteLut16(lut); + } - count += this.WriteMatrix(value.Matrix, false); + count += this.WriteClut16(value.ClutValues); - foreach (IccLut lut in value.InputValues) - { - count += this.WriteLut8(lut); - } + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut16(lut); + } - count += this.WriteClut8(value.ClutValues); + return count; + } - foreach (IccLut lut in value.OutputValues) - { - count += this.WriteLut8(lut); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLut8TagDataEntry(IccLut8TagDataEntry value) + { + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteByte((byte)value.ClutValues.Values[0].Length); + count += this.WriteEmpty(1); - return count; - } + count += this.WriteMatrix(value.Matrix, false); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value) + foreach (IccLut lut in value.InputValues) { - long start = this.dataStream.Position - 8; // 8 is the tag header size + count += this.WriteLut8(lut); + } - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteEmpty(2); + count += this.WriteClut8(value.ClutValues); - long bCurveOffset = 0; - long matrixOffset = 0; - long mCurveOffset = 0; - long clutOffset = 0; - long aCurveOffset = 0; + foreach (IccLut lut in value.OutputValues) + { + count += this.WriteLut8(lut); + } - // Jump over offset values - long offsetpos = this.dataStream.Position; - this.dataStream.Position += 5 * 4; + return count; + } - if (value.CurveB != null) - { - bCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveB); - count += this.WritePadding(); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutAtoBTagDataEntry(IccLutAToBTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size - if (value.Matrix3x1 != null && value.Matrix3x3 != null) - { - matrixOffset = this.dataStream.Position; - count += this.WriteMatrix(value.Matrix3x3.Value, false); - count += this.WriteMatrix(value.Matrix3x1.Value, false); - count += this.WritePadding(); - } + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); - if (value.CurveM != null) - { - mCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveM); - count += this.WritePadding(); - } + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; - if (value.ClutValues != null) - { - clutOffset = this.dataStream.Position; - count += this.WriteClut(value.ClutValues); - count += this.WritePadding(); - } + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; - if (value.CurveA != null) - { - aCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveA); - count += this.WritePadding(); - } + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } - // Set offset values - long lpos = this.dataStream.Position; - this.dataStream.Position = offsetpos; + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } - if (bCurveOffset != 0) - { - bCurveOffset -= start; - } + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } - if (matrixOffset != 0) - { - matrixOffset -= start; - } + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } - if (mCurveOffset != 0) - { - mCurveOffset -= start; - } + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } - if (clutOffset != 0) - { - clutOffset -= start; - } + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; - if (aCurveOffset != 0) - { - aCurveOffset -= start; - } + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } - count += this.WriteUInt32((uint)bCurveOffset); - count += this.WriteUInt32((uint)matrixOffset); - count += this.WriteUInt32((uint)mCurveOffset); - count += this.WriteUInt32((uint)clutOffset); - count += this.WriteUInt32((uint)aCurveOffset); + if (matrixOffset != 0) + { + matrixOffset -= start; + } - this.dataStream.Position = lpos; - return count; + if (mCurveOffset != 0) + { + mCurveOffset -= start; } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value) + if (clutOffset != 0) { - long start = this.dataStream.Position - 8; // 8 is the tag header size + clutOffset -= start; + } - int count = this.WriteByte((byte)value.InputChannelCount); - count += this.WriteByte((byte)value.OutputChannelCount); - count += this.WriteEmpty(2); + if (aCurveOffset != 0) + { + aCurveOffset -= start; + } - long bCurveOffset = 0; - long matrixOffset = 0; - long mCurveOffset = 0; - long clutOffset = 0; - long aCurveOffset = 0; + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); - // Jump over offset values - long offsetpos = this.dataStream.Position; - this.dataStream.Position += 5 * 4; + this.dataStream.Position = lpos; + return count; + } - if (value.CurveB != null) - { - bCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveB); - count += this.WritePadding(); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteLutBtoATagDataEntry(IccLutBToATagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size - if (value.Matrix3x1 != null && value.Matrix3x3 != null) - { - matrixOffset = this.dataStream.Position; - count += this.WriteMatrix(value.Matrix3x3.Value, false); - count += this.WriteMatrix(value.Matrix3x1.Value, false); - count += this.WritePadding(); - } + int count = this.WriteByte((byte)value.InputChannelCount); + count += this.WriteByte((byte)value.OutputChannelCount); + count += this.WriteEmpty(2); - if (value.CurveM != null) - { - mCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveM); - count += this.WritePadding(); - } + long bCurveOffset = 0; + long matrixOffset = 0; + long mCurveOffset = 0; + long clutOffset = 0; + long aCurveOffset = 0; - if (value.ClutValues != null) - { - clutOffset = this.dataStream.Position; - count += this.WriteClut(value.ClutValues); - count += this.WritePadding(); - } + // Jump over offset values + long offsetpos = this.dataStream.Position; + this.dataStream.Position += 5 * 4; - if (value.CurveA != null) - { - aCurveOffset = this.dataStream.Position; - count += this.WriteCurves(value.CurveA); - count += this.WritePadding(); - } + if (value.CurveB != null) + { + bCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveB); + count += this.WritePadding(); + } - // Set offset values - long lpos = this.dataStream.Position; - this.dataStream.Position = offsetpos; + if (value.Matrix3x1 != null && value.Matrix3x3 != null) + { + matrixOffset = this.dataStream.Position; + count += this.WriteMatrix(value.Matrix3x3.Value, false); + count += this.WriteMatrix(value.Matrix3x1.Value, false); + count += this.WritePadding(); + } - if (bCurveOffset != 0) - { - bCurveOffset -= start; - } + if (value.CurveM != null) + { + mCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveM); + count += this.WritePadding(); + } - if (matrixOffset != 0) - { - matrixOffset -= start; - } + if (value.ClutValues != null) + { + clutOffset = this.dataStream.Position; + count += this.WriteClut(value.ClutValues); + count += this.WritePadding(); + } - if (mCurveOffset != 0) - { - mCurveOffset -= start; - } + if (value.CurveA != null) + { + aCurveOffset = this.dataStream.Position; + count += this.WriteCurves(value.CurveA); + count += this.WritePadding(); + } - if (clutOffset != 0) - { - clutOffset -= start; - } + // Set offset values + long lpos = this.dataStream.Position; + this.dataStream.Position = offsetpos; - if (aCurveOffset != 0) - { - aCurveOffset -= start; - } + if (bCurveOffset != 0) + { + bCurveOffset -= start; + } - count += this.WriteUInt32((uint)bCurveOffset); - count += this.WriteUInt32((uint)matrixOffset); - count += this.WriteUInt32((uint)mCurveOffset); - count += this.WriteUInt32((uint)clutOffset); - count += this.WriteUInt32((uint)aCurveOffset); + if (matrixOffset != 0) + { + matrixOffset -= start; + } - this.dataStream.Position = lpos; - return count; + if (mCurveOffset != 0) + { + mCurveOffset -= start; } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value) - => this.WriteUInt32((uint)value.Observer) - + this.WriteXyzNumber(value.XyzBacking) - + this.WriteUInt32((uint)value.Geometry) - + this.WriteUFix16(value.Flare) - + this.WriteUInt32((uint)value.Illuminant); + if (clutOffset != 0) + { + clutOffset -= start; + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value) + if (aCurveOffset != 0) { - long start = this.dataStream.Position - 8; // 8 is the tag header size + aCurveOffset -= start; + } - int cultureCount = value.Texts.Length; + count += this.WriteUInt32((uint)bCurveOffset); + count += this.WriteUInt32((uint)matrixOffset); + count += this.WriteUInt32((uint)mCurveOffset); + count += this.WriteUInt32((uint)clutOffset); + count += this.WriteUInt32((uint)aCurveOffset); - int count = this.WriteUInt32((uint)cultureCount); - count += this.WriteUInt32(12); // One record has always 12 bytes size + this.dataStream.Position = lpos; + return count; + } - // Jump over position table - long tpos = this.dataStream.Position; - this.dataStream.Position += cultureCount * 12; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMeasurementTagDataEntry(IccMeasurementTagDataEntry value) + => this.WriteUInt32((uint)value.Observer) + + this.WriteXyzNumber(value.XyzBacking) + + this.WriteUInt32((uint)value.Geometry) + + this.WriteUFix16(value.Flare) + + this.WriteUInt32((uint)value.Illuminant); - // TODO: Investigate cost of Linq GroupBy - IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiLocalizedUnicodeTagDataEntry(IccMultiLocalizedUnicodeTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size - uint[] offset = new uint[texts.Length]; - int[] lengths = new int[texts.Length]; + int cultureCount = value.Texts.Length; - for (int i = 0; i < texts.Length; i++) - { - offset[i] = (uint)(this.dataStream.Position - start); - count += lengths[i] = this.WriteUnicodeString(texts[i].Key); - } + int count = this.WriteUInt32((uint)cultureCount); + count += this.WriteUInt32(12); // One record has always 12 bytes size - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tpos; - for (int i = 0; i < texts.Length; i++) + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += cultureCount * 12; + + // TODO: Investigate cost of Linq GroupBy + IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); + + uint[] offset = new uint[texts.Length]; + int[] lengths = new int[texts.Length]; + + for (int i = 0; i < texts.Length; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += lengths[i] = this.WriteUnicodeString(texts[i].Key); + } + + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + for (int i = 0; i < texts.Length; i++) + { + foreach (IccLocalizedString localizedString in texts[i]) { - foreach (IccLocalizedString localizedString in texts[i]) + string cultureName = localizedString.Culture.Name; + if (string.IsNullOrEmpty(cultureName)) { - string cultureName = localizedString.Culture.Name; - if (string.IsNullOrEmpty(cultureName)) - { - count += this.WriteAsciiString("xx", 2, false); - count += this.WriteAsciiString("\0\0", 2, false); - } - else if (cultureName.Contains('-')) - { - string[] code = cultureName.Split('-'); - count += this.WriteAsciiString(code[0].ToLower(localizedString.Culture), 2, false); - count += this.WriteAsciiString(code[1].ToUpper(localizedString.Culture), 2, false); - } - else - { - count += this.WriteAsciiString(cultureName, 2, false); - count += this.WriteAsciiString("\0\0", 2, false); - } - - count += this.WriteUInt32((uint)lengths[i]); - count += this.WriteUInt32(offset[i]); + count += this.WriteAsciiString("xx", 2, false); + count += this.WriteAsciiString("\0\0", 2, false); + } + else if (cultureName.Contains('-')) + { + string[] code = cultureName.Split('-'); + count += this.WriteAsciiString(code[0].ToLower(localizedString.Culture), 2, false); + count += this.WriteAsciiString(code[1].ToUpper(localizedString.Culture), 2, false); + } + else + { + count += this.WriteAsciiString(cultureName, 2, false); + count += this.WriteAsciiString("\0\0", 2, false); } - } - this.dataStream.Position = lpos; - return count; + count += this.WriteUInt32((uint)lengths[i]); + count += this.WriteUInt32(offset[i]); + } } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value) - { - long start = this.dataStream.Position - 8; // 8 is the tag header size + this.dataStream.Position = lpos; + return count; + } - int count = this.WriteUInt16((ushort)value.InputChannelCount); - count += this.WriteUInt16((ushort)value.OutputChannelCount); - count += this.WriteUInt32((uint)value.Data.Length); + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteMultiProcessElementsTagDataEntry(IccMultiProcessElementsTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size - // Jump over position table - long tpos = this.dataStream.Position; - this.dataStream.Position += value.Data.Length * 8; + int count = this.WriteUInt16((ushort)value.InputChannelCount); + count += this.WriteUInt16((ushort)value.OutputChannelCount); + count += this.WriteUInt32((uint)value.Data.Length); - IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; - for (int i = 0; i < value.Data.Length; i++) - { - uint offset = (uint)(this.dataStream.Position - start); - int size = this.WriteMultiProcessElement(value.Data[i]); - count += this.WritePadding(); - posTable[i] = new IccPositionNumber(offset, (uint)size); - count += size; - } + // Jump over position table + long tpos = this.dataStream.Position; + this.dataStream.Position += value.Data.Length * 8; - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tpos; - foreach (IccPositionNumber pos in posTable) - { - count += this.WritePositionNumber(pos); - } - - this.dataStream.Position = lpos; - return count; + IccPositionNumber[] posTable = new IccPositionNumber[value.Data.Length]; + for (int i = 0; i < value.Data.Length; i++) + { + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteMultiProcessElement(value.Data[i]); + count += this.WritePadding(); + posTable[i] = new IccPositionNumber(offset, (uint)size); + count += size; } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value) + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tpos; + foreach (IccPositionNumber pos in posTable) { - int count = this.WriteInt32(value.VendorFlags) - + this.WriteUInt32((uint)value.Colors.Length) - + this.WriteUInt32((uint)value.CoordinateCount) - + this.WriteAsciiString(value.Prefix, 32, true) - + this.WriteAsciiString(value.Suffix, 32, true); - - foreach (IccNamedColor color in value.Colors) - { - count += this.WriteNamedColor(color); - } - - return count; + count += this.WritePositionNumber(pos); } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) => this.WriteParametricCurve(value.Curve); + this.dataStream.Position = lpos; + return count; + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value) + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteNamedColor2TagDataEntry(IccNamedColor2TagDataEntry value) + { + int count = this.WriteInt32(value.VendorFlags) + + this.WriteUInt32((uint)value.Colors.Length) + + this.WriteUInt32((uint)value.CoordinateCount) + + this.WriteAsciiString(value.Prefix, 32, true) + + this.WriteAsciiString(value.Suffix, 32, true); + + foreach (IccNamedColor color in value.Colors) { - int count = this.WriteUInt32((uint)value.Descriptions.Length); + count += this.WriteNamedColor(color); + } - for (int i = 0; i < value.Descriptions.Length; i++) - { - ref IccProfileDescription desc = ref value.Descriptions[i]; + return count; + } - count += this.WriteProfileDescription(desc); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteParametricCurveTagDataEntry(IccParametricCurveTagDataEntry value) => this.WriteParametricCurve(value.Curve); - return count; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceDescTagDataEntry(IccProfileSequenceDescTagDataEntry value) + { + int count = this.WriteUInt32((uint)value.Descriptions.Length); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value) + for (int i = 0; i < value.Descriptions.Length; i++) { - long start = this.dataStream.Position - 8; // 8 is the tag header size - int length = value.Data.Length; + ref IccProfileDescription desc = ref value.Descriptions[i]; - int count = this.WriteUInt32((uint)length); + count += this.WriteProfileDescription(desc); + } - // Jump over position table - long tablePosition = this.dataStream.Position; - this.dataStream.Position += length * 8; - IccPositionNumber[] table = new IccPositionNumber[length]; + return count; + } - for (int i = 0; i < length; i++) - { - ref IccProfileSequenceIdentifier sequenceIdentifier = ref value.Data[i]; - - uint offset = (uint)(this.dataStream.Position - start); - int size = this.WriteProfileId(sequenceIdentifier.Id); - size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(sequenceIdentifier.Description)); - size += this.WritePadding(); - table[i] = new IccPositionNumber(offset, (uint)size); - count += size; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifierTagDataEntry value) + { + long start = this.dataStream.Position - 8; // 8 is the tag header size + int length = value.Data.Length; - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tablePosition; - foreach (IccPositionNumber pos in table) - { - count += this.WritePositionNumber(pos); - } + int count = this.WriteUInt32((uint)length); + + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += length * 8; + IccPositionNumber[] table = new IccPositionNumber[length]; - this.dataStream.Position = lpos; - return count; + for (int i = 0; i < length; i++) + { + ref IccProfileSequenceIdentifier sequenceIdentifier = ref value.Data[i]; + + uint offset = (uint)(this.dataStream.Position - start); + int size = this.WriteProfileId(sequenceIdentifier.Id); + size += this.WriteTagDataEntry(new IccMultiLocalizedUnicodeTagDataEntry(sequenceIdentifier.Description)); + size += this.WritePadding(); + table[i] = new IccPositionNumber(offset, (uint)size); + count += size; } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value) + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + foreach (IccPositionNumber pos in table) { - long start = this.dataStream.Position - 8; + count += this.WritePositionNumber(pos); + } - int count = this.WriteUInt16(value.ChannelCount); - count += this.WriteUInt16((ushort)value.Curves.Length); + this.dataStream.Position = lpos; + return count; + } - // Jump over position table - long tablePosition = this.dataStream.Position; - this.dataStream.Position += value.Curves.Length * 4; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteResponseCurveSet16TagDataEntry(IccResponseCurveSet16TagDataEntry value) + { + long start = this.dataStream.Position - 8; - uint[] offset = new uint[value.Curves.Length]; + int count = this.WriteUInt16(value.ChannelCount); + count += this.WriteUInt16((ushort)value.Curves.Length); - for (int i = 0; i < value.Curves.Length; i++) - { - offset[i] = (uint)(this.dataStream.Position - start); - count += this.WriteResponseCurve(value.Curves[i]); - count += this.WritePadding(); - } + // Jump over position table + long tablePosition = this.dataStream.Position; + this.dataStream.Position += value.Curves.Length * 4; - // Write position table - long lpos = this.dataStream.Position; - this.dataStream.Position = tablePosition; - count += this.WriteArray(offset); + uint[] offset = new uint[value.Curves.Length]; - this.dataStream.Position = lpos; - return count; + for (int i = 0; i < value.Curves.Length; i++) + { + offset[i] = (uint)(this.dataStream.Position - start); + count += this.WriteResponseCurve(value.Curves[i]); + count += this.WritePadding(); } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value) - { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteFix16(value.Data[i] * 256d); - } + // Write position table + long lpos = this.dataStream.Position; + this.dataStream.Position = tablePosition; + count += this.WriteArray(offset); + + this.dataStream.Position = lpos; + return count; + } - return count; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteFix16ArrayTagDataEntry(IccFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteFix16(value.Data[i] * 256d); } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) => this.WriteAsciiString(value.SignatureData, 4, false); + return count; + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteTextTagDataEntry(IccTextTagDataEntry value) => this.WriteAsciiString(value.Text); + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteSignatureTagDataEntry(IccSignatureTagDataEntry value) => this.WriteAsciiString(value.SignatureData, 4, false); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value) + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextTagDataEntry(IccTextTagDataEntry value) => this.WriteAsciiString(value.Text); + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUFix16ArrayTagDataEntry(IccUFix16ArrayTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteUFix16(value.Data[i]); - } + count += this.WriteUFix16(value.Data[i]); + } - return count; - } - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) => this.WriteArray(value.Data); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value) - => this.WriteXyzNumber(value.IlluminantXyz) - + this.WriteXyzNumber(value.SurroundXyz) - + this.WriteUInt32((uint)value.Illuminant); - - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteXyzTagDataEntry(IccXyzTagDataEntry value) - { - int count = 0; - for (int i = 0; i < value.Data.Length; i++) - { - count += this.WriteXyzNumber(value.Data[i]); - } + return count; + } - return count; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt16ArrayTagDataEntry(IccUInt16ArrayTagDataEntry value) => this.WriteArray(value.Data); - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value) - { - int size, count = 0; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt32ArrayTagDataEntry(IccUInt32ArrayTagDataEntry value) => this.WriteArray(value.Data); - if (value.Ascii is null) - { - count += this.WriteUInt32(0); - } - else - { - this.dataStream.Position += 4; - count += size = this.WriteAsciiString(value.Ascii + '\0'); - this.dataStream.Position -= size + 4; - count += this.WriteUInt32((uint)size); - this.dataStream.Position += size; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt64ArrayTagDataEntry(IccUInt64ArrayTagDataEntry value) => this.WriteArray(value.Data); - if (value.Unicode is null) - { - count += this.WriteUInt32(0); - count += this.WriteUInt32(0); - } - else - { - this.dataStream.Position += 8; - count += size = this.WriteUnicodeString(value.Unicode + '\0'); - this.dataStream.Position -= size + 8; - count += this.WriteUInt32(value.UnicodeLanguageCode); - count += this.WriteUInt32((uint)value.Unicode.Length + 1); - this.dataStream.Position += size; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUInt8ArrayTagDataEntry(IccUInt8ArrayTagDataEntry value) => this.WriteArray(value.Data); - if (value.ScriptCode is null) - { - count += this.WriteUInt16(0); - count += this.WriteByte(0); - count += this.WriteEmpty(67); - } - else - { - this.dataStream.Position += 3; - count += size = this.WriteAsciiString(value.ScriptCode, 67, true); - this.dataStream.Position -= size + 3; - count += this.WriteUInt16(value.ScriptCodeCode); - count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1)); - this.dataStream.Position += size; - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteViewingConditionsTagDataEntry(IccViewingConditionsTagDataEntry value) + => this.WriteXyzNumber(value.IlluminantXyz) + + this.WriteXyzNumber(value.SurroundXyz) + + this.WriteUInt32((uint)value.Illuminant); - return count; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteXyzTagDataEntry(IccXyzTagDataEntry value) + { + int count = 0; + for (int i = 0; i < value.Data.Length; i++) + { + count += this.WriteXyzNumber(value.Data[i]); } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value) - { - int count = 0; - WriteString(value.PostScriptProductName); - WriteString(value.RenderingIntent0Crd); - WriteString(value.RenderingIntent1Crd); - WriteString(value.RenderingIntent2Crd); - WriteString(value.RenderingIntent3Crd); + return count; + } - return count; + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteTextDescriptionTagDataEntry(IccTextDescriptionTagDataEntry value) + { + int size, count = 0; - void WriteString(string text) - { - int textLength; - if (string.IsNullOrEmpty(text)) - { - textLength = 0; - } - else - { - textLength = text.Length + 1; // + 1 for null terminator - } + if (value.Ascii is null) + { + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 4; + count += size = this.WriteAsciiString(value.Ascii + '\0'); + this.dataStream.Position -= size + 4; + count += this.WriteUInt32((uint)size); + this.dataStream.Position += size; + } - count += this.WriteUInt32((uint)textLength); - count += this.WriteAsciiString(text, textLength, true); - } + if (value.Unicode is null) + { + count += this.WriteUInt32(0); + count += this.WriteUInt32(0); + } + else + { + this.dataStream.Position += 8; + count += size = this.WriteUnicodeString(value.Unicode + '\0'); + this.dataStream.Position -= size + 8; + count += this.WriteUInt32(value.UnicodeLanguageCode); + count += this.WriteUInt32((uint)value.Unicode.Length + 1); + this.dataStream.Position += size; } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value) + if (value.ScriptCode is null) + { + count += this.WriteUInt16(0); + count += this.WriteByte(0); + count += this.WriteEmpty(67); + } + else { - int count = 0; + this.dataStream.Position += 3; + count += size = this.WriteAsciiString(value.ScriptCode, 67, true); + this.dataStream.Position -= size + 3; + count += this.WriteUInt16(value.ScriptCodeCode); + count += this.WriteByte((byte)(value.ScriptCode.Length > 66 ? 67 : value.ScriptCode.Length + 1)); + this.dataStream.Position += size; + } + + return count; + } + + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteCrdInfoTagDataEntry(IccCrdInfoTagDataEntry value) + { + int count = 0; + WriteString(value.PostScriptProductName); + WriteString(value.RenderingIntent0Crd); + WriteString(value.RenderingIntent1Crd); + WriteString(value.RenderingIntent2Crd); + WriteString(value.RenderingIntent3Crd); + + return count; - count += this.WriteInt32((int)value.Flags); - count += this.WriteUInt32((uint)value.Channels.Length); - for (int i = 0; i < value.Channels.Length; i++) + void WriteString(string text) + { + int textLength; + if (string.IsNullOrEmpty(text)) + { + textLength = 0; + } + else { - count += this.WriteScreeningChannel(value.Channels[i]); + textLength = text.Length + 1; // + 1 for null terminator } - return count; + count += this.WriteUInt32((uint)textLength); + count += this.WriteAsciiString(text, textLength, true); } + } - /// - /// Writes a - /// - /// The entry to write - /// The number of bytes written - public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value) + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteScreeningTagDataEntry(IccScreeningTagDataEntry value) + { + int count = 0; + + count += this.WriteInt32((int)value.Flags); + count += this.WriteUInt32((uint)value.Channels.Length); + for (int i = 0; i < value.Channels.Length; i++) { - int count = 0; + count += this.WriteScreeningChannel(value.Channels[i]); + } - count += this.WriteUInt32((uint)value.UcrCurve.Length); - for (int i = 0; i < value.UcrCurve.Length; i++) - { - count += this.WriteUInt16(value.UcrCurve[i]); - } + return count; + } - count += this.WriteUInt32((uint)value.BgCurve.Length); - for (int i = 0; i < value.BgCurve.Length; i++) - { - count += this.WriteUInt16(value.BgCurve[i]); - } + /// + /// Writes a + /// + /// The entry to write + /// The number of bytes written + public int WriteUcrBgTagDataEntry(IccUcrBgTagDataEntry value) + { + int count = 0; - count += this.WriteAsciiString(value.Description + '\0'); + count += this.WriteUInt32((uint)value.UcrCurve.Length); + for (int i = 0; i < value.UcrCurve.Length; i++) + { + count += this.WriteUInt16(value.UcrCurve[i]); + } - return count; + count += this.WriteUInt32((uint)value.BgCurve.Length); + for (int i = 0; i < value.BgCurve.Length; i++) + { + count += this.WriteUInt16(value.BgCurve[i]); } + + count += this.WriteAsciiString(value.Description + '\0'); + + return count; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs index c7c91bcfa0..ce53325442 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -1,247 +1,243 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Provides methods to write ICC data types +/// +internal sealed partial class IccDataWriter : IDisposable { /// - /// Provides methods to write ICC data types + /// The underlying stream where the data is written to /// - internal sealed partial class IccDataWriter : IDisposable + private readonly MemoryStream dataStream; + + /// + /// To detect redundant calls + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + public IccDataWriter() { - /// - /// The underlying stream where the data is written to - /// - private readonly MemoryStream dataStream; + this.dataStream = new MemoryStream(); + } - /// - /// To detect redundant calls - /// - private bool isDisposed; + /// + /// Gets the currently written length in bytes + /// + public uint Length => (uint)this.dataStream.Length; - /// - /// Initializes a new instance of the class. - /// - public IccDataWriter() - { - this.dataStream = new MemoryStream(); - } + /// + /// Gets the written data bytes + /// + /// The written data + public byte[] GetData() + { + return this.dataStream.ToArray(); + } - /// - /// Gets the currently written length in bytes - /// - public uint Length => (uint)this.dataStream.Length; + /// + /// Sets the writing position to the given value + /// + /// The new index position + public void SetIndex(int index) + { + this.dataStream.Position = index; + } - /// - /// Gets the written data bytes - /// - /// The written data - public byte[] GetData() - { - return this.dataStream.ToArray(); - } + /// + /// Writes a byte array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(byte[] data) + { + this.dataStream.Write(data, 0, data.Length); + return data.Length; + } - /// - /// Sets the writing position to the given value - /// - /// The new index position - public void SetIndex(int index) + /// + /// Writes a ushort array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ushort[] data) + { + for (int i = 0; i < data.Length; i++) { - this.dataStream.Position = index; + this.WriteUInt16(data[i]); } - /// - /// Writes a byte array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(byte[] data) + return data.Length * 2; + } + + /// + /// Writes a short array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(short[] data) + { + for (int i = 0; i < data.Length; i++) { - this.dataStream.Write(data, 0, data.Length); - return data.Length; + this.WriteInt16(data[i]); } - /// - /// Writes a ushort array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(ushort[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt16(data[i]); - } + return data.Length * 2; + } - return data.Length * 2; + /// + /// Writes a uint array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(uint[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt32(data[i]); } - /// - /// Writes a short array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(short[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteInt16(data[i]); - } + return data.Length * 4; + } - return data.Length * 2; + /// + /// Writes an int array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(int[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteInt32(data[i]); } - /// - /// Writes a uint array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(uint[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt32(data[i]); - } + return data.Length * 4; + } - return data.Length * 4; + /// + /// Writes a ulong array + /// + /// The array to write + /// The number of bytes written + public int WriteArray(ulong[] data) + { + for (int i = 0; i < data.Length; i++) + { + this.WriteUInt64(data[i]); } - /// - /// Writes an int array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(int[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteInt32(data[i]); - } + return data.Length * 8; + } - return data.Length * 4; + /// + /// Write a number of empty bytes + /// + /// The number of bytes to write + /// The number of bytes written + public int WriteEmpty(int length) + { + for (int i = 0; i < length; i++) + { + this.dataStream.WriteByte(0); } - /// - /// Writes a ulong array - /// - /// The array to write - /// The number of bytes written - public int WriteArray(ulong[] data) - { - for (int i = 0; i < data.Length; i++) - { - this.WriteUInt64(data[i]); - } + return length; + } - return data.Length * 8; - } + /// + /// Writes empty bytes to a 4-byte margin + /// + /// The number of bytes written + public int WritePadding() + { + int p = 4 - ((int)this.dataStream.Position % 4); + return this.WriteEmpty(p >= 4 ? 0 : p); + } - /// - /// Write a number of empty bytes - /// - /// The number of bytes to write - /// The number of bytes written - public int WriteEmpty(int length) + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Writes given bytes from pointer + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytes(byte* data, int length) + { + if (BitConverter.IsLittleEndian) { - for (int i = 0; i < length; i++) + for (int i = length - 1; i >= 0; i--) { - this.dataStream.WriteByte(0); + this.dataStream.WriteByte(data[i]); } - - return length; } - - /// - /// Writes empty bytes to a 4-byte margin - /// - /// The number of bytes written - public int WritePadding() + else { - int p = 4 - ((int)this.dataStream.Position % 4); - return this.WriteEmpty(p >= 4 ? 0 : p); + this.WriteBytesDirect(data, length); } - /// - public void Dispose() - { - this.Dispose(true); - } + return length; + } - /// - /// Writes given bytes from pointer - /// - /// Pointer to the bytes to write - /// The number of bytes to write - /// The number of bytes written - private unsafe int WriteBytes(byte* data, int length) + /// + /// Writes given bytes from pointer ignoring endianness + /// + /// Pointer to the bytes to write + /// The number of bytes to write + /// The number of bytes written + private unsafe int WriteBytesDirect(byte* data, int length) + { + for (int i = 0; i < length; i++) { - if (BitConverter.IsLittleEndian) - { - for (int i = length - 1; i >= 0; i--) - { - this.dataStream.WriteByte(data[i]); - } - } - else - { - this.WriteBytesDirect(data, length); - } - - return length; + this.dataStream.WriteByte(data[i]); } - /// - /// Writes given bytes from pointer ignoring endianness - /// - /// Pointer to the bytes to write - /// The number of bytes to write - /// The number of bytes written - private unsafe int WriteBytesDirect(byte* data, int length) - { - for (int i = 0; i < length; i++) - { - this.dataStream.WriteByte(data[i]); - } - - return length; - } + return length; + } - /// - /// Writes curve data - /// - /// The curves to write - /// The number of bytes written - private int WriteCurves(IccTagDataEntry[] curves) + /// + /// Writes curve data + /// + /// The curves to write + /// The number of bytes written + private int WriteCurves(IccTagDataEntry[] curves) + { + int count = 0; + foreach (IccTagDataEntry curve in curves) { - int count = 0; - foreach (IccTagDataEntry curve in curves) + if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve) { - if (curve.Signature != IccTypeSignature.Curve && curve.Signature != IccTypeSignature.ParametricCurve) - { - throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + - $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); - } - - count += this.WriteTagDataEntry(curve); - count += this.WritePadding(); + throw new InvalidIccProfileException($"Curve has to be either \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.Curve)}\" or" + + $" \"{nameof(IccTypeSignature)}.{nameof(IccTypeSignature.ParametricCurve)}\" for LutAToB- and LutBToA-TagDataEntries"); } - return count; + count += this.WriteTagDataEntry(curve); + count += this.WritePadding(); } - private void Dispose(bool disposing) + return count; + } + + private void Dispose(bool disposing) + { + if (!this.isDisposed) { - if (!this.isDisposed) + if (disposing) { - if (disposing) - { - this.dataStream?.Dispose(); - } - - this.isDisposed = true; + this.dataStream?.Dispose(); } + + this.isDisposed = true; } } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs index 2222530843..3cc27b1f7a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccClutDataType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Color lookup table data type +/// +internal enum IccClutDataType { /// - /// Color lookup table data type + /// 32bit floating point /// - internal enum IccClutDataType - { - /// - /// 32bit floating point - /// - Float, + Float, - /// - /// 8bit unsigned integer (byte) - /// - UInt8, + /// + /// 8bit unsigned integer (byte) + /// + UInt8, - /// - /// 16bit unsigned integer (ushort) - /// - UInt16, - } + /// + /// 16bit unsigned integer (ushort) + /// + UInt16, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs index 262e39c63d..1dba5a819a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorSpaceType.cs @@ -1,136 +1,135 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Color Space Type +/// +public enum IccColorSpaceType : uint { /// - /// Color Space Type - /// - public enum IccColorSpaceType : uint - { - /// - /// CIE XYZ - /// - CieXyz = 0x58595A20, // XYZ - - /// - /// CIE Lab - /// - CieLab = 0x4C616220, // Lab - - /// - /// CIE Luv - /// - CieLuv = 0x4C757620, // Luv - - /// - /// YCbCr - /// - YCbCr = 0x59436272, // YCbr - - /// - /// CIE Yxy - /// - CieYxy = 0x59787920, // Yxy - - /// - /// RGB - /// - Rgb = 0x52474220, // RGB - - /// - /// Gray - /// - Gray = 0x47524159, // GRAY - - /// - /// HSV - /// - Hsv = 0x48535620, // HSV - - /// - /// HLS - /// - Hls = 0x484C5320, // HLS - - /// - /// CMYK - /// - Cmyk = 0x434D594B, // CMYK - - /// - /// CMY - /// - Cmy = 0x434D5920, // CMY - - /// - /// Generic 2 channel color - /// - Color2 = 0x32434C52, // 2CLR - - /// - /// Generic 3 channel color - /// - Color3 = 0x33434C52, // 3CLR - - /// - /// Generic 4 channel color - /// - Color4 = 0x34434C52, // 4CLR - - /// - /// Generic 5 channel color - /// - Color5 = 0x35434C52, // 5CLR - - /// - /// Generic 6 channel color - /// - Color6 = 0x36434C52, // 6CLR - - /// - /// Generic 7 channel color - /// - Color7 = 0x37434C52, // 7CLR - - /// - /// Generic 8 channel color - /// - Color8 = 0x38434C52, // 8CLR - - /// - /// Generic 9 channel color - /// - Color9 = 0x39434C52, // 9CLR - - /// - /// Generic 10 channel color - /// - Color10 = 0x41434C52, // ACLR - - /// - /// Generic 11 channel color - /// - Color11 = 0x42434C52, // BCLR - - /// - /// Generic 12 channel color - /// - Color12 = 0x43434C52, // CCLR - - /// - /// Generic 13 channel color - /// - Color13 = 0x44434C52, // DCLR - - /// - /// Generic 14 channel color - /// - Color14 = 0x45434C52, // ECLR - - /// - /// Generic 15 channel color - /// - Color15 = 0x46434C52, // FCLR - } + /// CIE XYZ + /// + CieXyz = 0x58595A20, // XYZ + + /// + /// CIE Lab + /// + CieLab = 0x4C616220, // Lab + + /// + /// CIE Luv + /// + CieLuv = 0x4C757620, // Luv + + /// + /// YCbCr + /// + YCbCr = 0x59436272, // YCbr + + /// + /// CIE Yxy + /// + CieYxy = 0x59787920, // Yxy + + /// + /// RGB + /// + Rgb = 0x52474220, // RGB + + /// + /// Gray + /// + Gray = 0x47524159, // GRAY + + /// + /// HSV + /// + Hsv = 0x48535620, // HSV + + /// + /// HLS + /// + Hls = 0x484C5320, // HLS + + /// + /// CMYK + /// + Cmyk = 0x434D594B, // CMYK + + /// + /// CMY + /// + Cmy = 0x434D5920, // CMY + + /// + /// Generic 2 channel color + /// + Color2 = 0x32434C52, // 2CLR + + /// + /// Generic 3 channel color + /// + Color3 = 0x33434C52, // 3CLR + + /// + /// Generic 4 channel color + /// + Color4 = 0x34434C52, // 4CLR + + /// + /// Generic 5 channel color + /// + Color5 = 0x35434C52, // 5CLR + + /// + /// Generic 6 channel color + /// + Color6 = 0x36434C52, // 6CLR + + /// + /// Generic 7 channel color + /// + Color7 = 0x37434C52, // 7CLR + + /// + /// Generic 8 channel color + /// + Color8 = 0x38434C52, // 8CLR + + /// + /// Generic 9 channel color + /// + Color9 = 0x39434C52, // 9CLR + + /// + /// Generic 10 channel color + /// + Color10 = 0x41434C52, // ACLR + + /// + /// Generic 11 channel color + /// + Color11 = 0x42434C52, // BCLR + + /// + /// Generic 12 channel color + /// + Color12 = 0x43434C52, // CCLR + + /// + /// Generic 13 channel color + /// + Color13 = 0x44434C52, // DCLR + + /// + /// Generic 14 channel color + /// + Color14 = 0x45434C52, // ECLR + + /// + /// Generic 15 channel color + /// + Color15 = 0x46434C52, // FCLR } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs index 140e6647e7..24a95da35b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccColorantEncoding.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Colorant Encoding +/// +internal enum IccColorantEncoding : ushort { /// - /// Colorant Encoding + /// Unknown colorant encoding /// - internal enum IccColorantEncoding : ushort - { - /// - /// Unknown colorant encoding - /// - Unknown = 0x0000, + Unknown = 0x0000, - /// - /// ITU-R BT.709-2 colorant encoding - /// - ItuRBt709_2 = 0x0001, + /// + /// ITU-R BT.709-2 colorant encoding + /// + ItuRBt709_2 = 0x0001, - /// - /// SMPTE RP145 colorant encoding - /// - SmpteRp145 = 0x0002, + /// + /// SMPTE RP145 colorant encoding + /// + SmpteRp145 = 0x0002, - /// - /// EBU Tech.3213-E colorant encoding - /// - EbuTech3213E = 0x0003, + /// + /// EBU Tech.3213-E colorant encoding + /// + EbuTech3213E = 0x0003, - /// - /// P22 colorant encoding - /// - P22 = 0x0004, - } + /// + /// P22 colorant encoding + /// + P22 = 0x0004, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs index 919dcc10ad..8f2826a6bb 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveMeasurementEncodings.cs @@ -1,60 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Curve Measurement Encodings +/// +internal enum IccCurveMeasurementEncodings : uint { /// - /// Curve Measurement Encodings - /// - internal enum IccCurveMeasurementEncodings : uint - { - /// - /// ISO 5-3 densitometer response. This is the accepted standard for - /// reflection densitometers for measuring photographic color prints - /// - StatusA = 0x53746141, // StaA - - /// - /// ISO 5-3 densitometer response which is the accepted standard in - /// Europe for color reflection densitometers - /// - StatusE = 0x53746145, // StaE - - /// - /// ISO 5-3 densitometer response commonly referred to as narrow band - /// or interference-type response. - /// - StatusI = 0x53746149, // StaI - - /// - /// ISO 5-3 wide band color reflection densitometer response which is - /// the accepted standard in the United States for color reflection densitometers - /// - StatusT = 0x53746154, // StaT - - /// - /// ISO 5-3 densitometer response for measuring color negatives - /// - StatusM = 0x5374614D, // StaM - - /// - /// DIN 16536-2 densitometer response, with no polarizing filter - /// - DinE = 0x434E2020, // DN - - /// - /// DIN 16536-2 densitometer response, with polarizing filter - /// - DinEPol = 0x434E2050, // DNP - - /// - /// DIN 16536-2 narrow band densitometer response, with no polarizing filter - /// - DinI = 0x434E4E20, // DNN - - /// - /// DIN 16536-2 narrow band densitometer response, with polarizing filter - /// - DinIPol = 0x434E4E50, // DNNP - } + /// ISO 5-3 densitometer response. This is the accepted standard for + /// reflection densitometers for measuring photographic color prints + /// + StatusA = 0x53746141, // StaA + + /// + /// ISO 5-3 densitometer response which is the accepted standard in + /// Europe for color reflection densitometers + /// + StatusE = 0x53746145, // StaE + + /// + /// ISO 5-3 densitometer response commonly referred to as narrow band + /// or interference-type response. + /// + StatusI = 0x53746149, // StaI + + /// + /// ISO 5-3 wide band color reflection densitometer response which is + /// the accepted standard in the United States for color reflection densitometers + /// + StatusT = 0x53746154, // StaT + + /// + /// ISO 5-3 densitometer response for measuring color negatives + /// + StatusM = 0x5374614D, // StaM + + /// + /// DIN 16536-2 densitometer response, with no polarizing filter + /// + DinE = 0x434E2020, // DN + + /// + /// DIN 16536-2 densitometer response, with polarizing filter + /// + DinEPol = 0x434E2050, // DNP + + /// + /// DIN 16536-2 narrow band densitometer response, with no polarizing filter + /// + DinI = 0x434E4E20, // DNN + + /// + /// DIN 16536-2 narrow band densitometer response, with polarizing filter + /// + DinIPol = 0x434E4E50, // DNNP } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs index 6c703796bb..6b59217687 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccCurveSegmentSignature.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Curve Segment Signature +/// +internal enum IccCurveSegmentSignature : uint { /// - /// Curve Segment Signature + /// Curve defined by a formula /// - internal enum IccCurveSegmentSignature : uint - { - /// - /// Curve defined by a formula - /// - FormulaCurve = 0x70617266, // parf + FormulaCurve = 0x70617266, // parf - /// - /// Curve defined by multiple segments - /// - SampledCurve = 0x73616D66, // samf - } + /// + /// Curve defined by multiple segments + /// + SampledCurve = 0x73616D66, // samf } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs index e7eba3d078..f6ca73bc95 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDataType.cs @@ -1,84 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 +/// Section 4.2 to 4.15 +/// +internal enum IccDataType { /// - /// Enumerates the basic data types as defined in ICC.1:2010 version 4.3.0.0 - /// Section 4.2 to 4.15 + /// A 12-byte value representation of the time and date /// - internal enum IccDataType - { - /// - /// A 12-byte value representation of the time and date - /// - DateTime, + DateTime, - /// - /// A single-precision 32-bit floating-point as specified in IEEE 754, - /// excluding un-normalized s, infinities, and not a "" (NaN) values - /// - Float32, + /// + /// A single-precision 32-bit floating-point as specified in IEEE 754, + /// excluding un-normalized s, infinities, and not a "" (NaN) values + /// + Float32, - /// - /// Positions of some data elements are indicated using a position offset with the data element's size. - /// - Position, + /// + /// Positions of some data elements are indicated using a position offset with the data element's size. + /// + Position, - /// - /// An 8-byte value, used to associate a normalized device code with a measurement value - /// - Response16, + /// + /// An 8-byte value, used to associate a normalized device code with a measurement value + /// + Response16, - /// - /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits - /// - S15Fixed16, + /// + /// A fixed signed 4-byte (32-bit) quantity which has 16 fractional bits + /// + S15Fixed16, - /// - /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits - /// - U16Fixed16, + /// + /// A fixed unsigned 4-byte (32-bit) quantity having 16 fractional bits + /// + U16Fixed16, - /// - /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits - /// - U1Fixed15, + /// + /// A fixed unsigned 2-byte (16-bit) quantity having15 fractional bits + /// + U1Fixed15, - /// - /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits - /// - U8Fixed8, + /// + /// A fixed unsigned 2-byte (16-bit) quantity having 8 fractional bits + /// + U8Fixed8, - /// - /// An unsigned 2-byte (16-bit) integer - /// - UInt16, + /// + /// An unsigned 2-byte (16-bit) integer + /// + UInt16, - /// - /// An unsigned 4-byte (32-bit) integer - /// - UInt32, + /// + /// An unsigned 4-byte (32-bit) integer + /// + UInt32, - /// - /// An unsigned 8-byte (64-bit) integer - /// - UInt64, + /// + /// An unsigned 8-byte (64-bit) integer + /// + UInt64, - /// - /// An unsigned 1-byte (8-bit) integer - /// - UInt8, + /// + /// An unsigned 1-byte (8-bit) integer + /// + UInt8, - /// - /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values - /// - Xyz, + /// + /// A set of three fixed signed 4-byte (32-bit) quantities used to encode CIEXYZ, nCIEXYZ, and PCSXYZ tristimulus values + /// + Xyz, - /// - /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for - /// Information Interchange (ASCII) specified in ISO/IEC 646. - /// - Ascii - } + /// + /// Alpha-numeric values, and other input and output codes, shall conform to the American Standard Code for + /// Information Interchange (ASCII) specified in ISO/IEC 646. + /// + Ascii } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs index 5212ae265b..007bed660e 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccDeviceAttribute.cs @@ -1,56 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Device attributes. Can be combined with a logical OR +/// The least-significant 32 bits are defined by the ICC, +/// the rest can be used for vendor specific values +/// +[Flags] +public enum IccDeviceAttribute : long { /// - /// Device attributes. Can be combined with a logical OR - /// The least-significant 32 bits are defined by the ICC, - /// the rest can be used for vendor specific values - /// - [Flags] - public enum IccDeviceAttribute : long - { - /// - /// Opacity transparent - /// - OpacityTransparent = 1 << 0, - - /// - /// Opacity reflective - /// - OpacityReflective = 0, - - /// - /// Reflectivity matte - /// - ReflectivityMatte = 1 << 1, - - /// - /// Reflectivity glossy - /// - ReflectivityGlossy = 0, - - /// - /// Polarity negative - /// - PolarityNegative = 1 << 2, - - /// - /// Polarity positive - /// - PolarityPositive = 0, - - /// - /// Chroma black and white - /// - ChromaBlackWhite = 1 << 3, - - /// - /// Chroma color - /// - ChromaColor = 0, - } + /// Opacity transparent + /// + OpacityTransparent = 1 << 0, + + /// + /// Opacity reflective + /// + OpacityReflective = 0, + + /// + /// Reflectivity matte + /// + ReflectivityMatte = 1 << 1, + + /// + /// Reflectivity glossy + /// + ReflectivityGlossy = 0, + + /// + /// Polarity negative + /// + PolarityNegative = 1 << 2, + + /// + /// Polarity positive + /// + PolarityPositive = 0, + + /// + /// Chroma black and white + /// + ChromaBlackWhite = 1 << 3, + + /// + /// Chroma color + /// + ChromaColor = 0, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs index 052012a7e8..e0c6f4c962 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccFormulaCurveType.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Formula curve segment type +/// +internal enum IccFormulaCurveType : ushort { /// - /// Formula curve segment type + /// Type 1: Y = (a * X + b)^γ + c /// - internal enum IccFormulaCurveType : ushort - { - /// - /// Type 1: Y = (a * X + b)^γ + c - /// - Type1 = 0, + Type1 = 0, - /// - /// Type 1: Y = a * log10 (b * X^γ + c) + d - /// - Type2 = 1, + /// + /// Type 1: Y = a * log10 (b * X^γ + c) + d + /// + Type2 = 1, - /// - /// Type 3: Y = a * b^(c * X + d) + e - /// - Type3 = 2 - } + /// + /// Type 3: Y = a * b^(c * X + d) + e + /// + Type3 = 2 } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs index 2eb12eb2da..3ffd2cf96e 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMeasurementGeometry.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Measurement Geometry +/// +internal enum IccMeasurementGeometry : uint { /// - /// Measurement Geometry + /// Unknown geometry /// - internal enum IccMeasurementGeometry : uint - { - /// - /// Unknown geometry - /// - Unknown = 0, + Unknown = 0, - /// - /// Geometry of 0°:45° or 45°:0° - /// - Degree0To45Or45To0 = 1, + /// + /// Geometry of 0°:45° or 45°:0° + /// + Degree0To45Or45To0 = 1, - /// - /// Geometry of 0°:d or d:0° - /// - Degree0ToDOrDTo0 = 2, - } + /// + /// Geometry of 0°:d or d:0° + /// + Degree0ToDOrDTo0 = 2, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs index 395654e1f5..e7068e8a22 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccMultiProcessElementSignature.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Multi process element signature +/// +internal enum IccMultiProcessElementSignature : uint { /// - /// Multi process element signature + /// Set of curves /// - internal enum IccMultiProcessElementSignature : uint - { - /// - /// Set of curves - /// - CurveSet = 0x6D666C74, // cvst + CurveSet = 0x6D666C74, // cvst - /// - /// Matrix transformation - /// - Matrix = 0x6D617466, // matf + /// + /// Matrix transformation + /// + Matrix = 0x6D617466, // matf - /// - /// Color lookup table - /// - Clut = 0x636C7574, // clut + /// + /// Color lookup table + /// + Clut = 0x636C7574, // clut - /// - /// Reserved for future expansion. Do not use! - /// - BAcs = 0x62414353, // bACS + /// + /// Reserved for future expansion. Do not use! + /// + BAcs = 0x62414353, // bACS - /// - /// Reserved for future expansion. Do not use! - /// - EAcs = 0x65414353, // eACS - } + /// + /// Reserved for future expansion. Do not use! + /// + EAcs = 0x65414353, // eACS } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs index 033434d3db..7196f252f2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccParametricCurveType.cs @@ -1,44 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Formula curve segment type +/// +internal enum IccParametricCurveType : ushort { /// - /// Formula curve segment type + /// Type 1: Y = X^g /// - internal enum IccParametricCurveType : ushort - { - /// - /// Type 1: Y = X^g - /// - Type1 = 0, + Type1 = 0, - /// - /// CIE 122-1996: - /// For X >= -b/a: Y =(a * X + b)^g - /// For X $lt; -b/a: Y = 0 - /// - Cie122_1996 = 1, + /// + /// CIE 122-1996: + /// For X >= -b/a: Y =(a * X + b)^g + /// For X $lt; -b/a: Y = 0 + /// + Cie122_1996 = 1, - /// - /// IEC 61966-3: - /// For X >= -b/a: Y =(a * X + b)^g + c - /// For X $lt; -b/a: Y = c - /// - Iec61966_3 = 2, + /// + /// IEC 61966-3: + /// For X >= -b/a: Y =(a * X + b)^g + c + /// For X $lt; -b/a: Y = c + /// + Iec61966_3 = 2, - /// - /// IEC 61966-2-1 (sRGB): - /// For X >= d: Y =(a * X + b)^g - /// For X $lt; d: Y = c * X - /// - SRgb = 3, + /// + /// IEC 61966-2-1 (sRGB): + /// For X >= d: Y =(a * X + b)^g + /// For X $lt; d: Y = c * X + /// + SRgb = 3, - /// - /// Type 5: - /// For X >= d: Y =(a * X + b)^g + c - /// For X $lt; d: Y = c * X + f - /// - Type5 = 4, - } + /// + /// Type 5: + /// For X >= d: Y =(a * X + b)^g + c + /// For X $lt; d: Y = c * X + f + /// + Type5 = 4, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs index b4db23fd84..65f57e63d8 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccPrimaryPlatformType.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Enumerates the primary platform/operating system framework for which the profile was created +/// +public enum IccPrimaryPlatformType : uint { /// - /// Enumerates the primary platform/operating system framework for which the profile was created + /// No platform identified /// - public enum IccPrimaryPlatformType : uint - { - /// - /// No platform identified - /// - NotIdentified = 0x00000000, + NotIdentified = 0x00000000, - /// - /// Apple Computer, Inc. - /// - AppleComputerInc = 0x4150504C, // APPL + /// + /// Apple Computer, Inc. + /// + AppleComputerInc = 0x4150504C, // APPL - /// - /// Microsoft Corporation - /// - MicrosoftCorporation = 0x4D534654, // MSFT + /// + /// Microsoft Corporation + /// + MicrosoftCorporation = 0x4D534654, // MSFT - /// - /// Silicon Graphics, Inc. - /// - SiliconGraphicsInc = 0x53474920, // SGI + /// + /// Silicon Graphics, Inc. + /// + SiliconGraphicsInc = 0x53474920, // SGI - /// - /// Sun Microsystems, Inc. - /// - SunMicrosystemsInc = 0x53554E57, // SUNW - } + /// + /// Sun Microsystems, Inc. + /// + SunMicrosystemsInc = 0x53554E57, // SUNW } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs index 18099d01c0..04e5af1f45 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileClass.cs @@ -1,63 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Profile Class Name +/// +public enum IccProfileClass : uint { /// - /// Profile Class Name + /// Input profiles are generally used with devices such as scanners and + /// digital cameras. The types of profiles available for use as Input + /// profiles are N-component LUT-based, Three-component matrix-based, + /// and monochrome. + /// + InputDevice = 0x73636E72, // scnr + + /// + /// This class of profiles represents display devices such as monitors. + /// The types of profiles available for use as Display profiles are + /// N-component LUT-based, Three-component matrix-based, and monochrome. + /// + DisplayDevice = 0x6D6E7472, // mntr + + /// + /// Output profiles are used to support devices such as printers and + /// film recorders. The types of profiles available for use as Output + /// profiles are N-component LUT-based and Monochrome. + /// + OutputDevice = 0x70727472, // prtr + + /// + /// This profile contains a pre-evaluated transform that cannot be undone, + /// which represents a one-way link or connection between devices. It does + /// not represent any device model nor can it be embedded into images. + /// + DeviceLink = 0x6C696E6B, // link + + /// + /// This profile provides the relevant information to perform a transformation + /// between color encodings and the PCS. This type of profile is based on + /// modeling rather than device measurement or characterization data. + /// ColorSpace profiles may be embedded in images. + /// + ColorSpace = 0x73706163, // spac + + /// + /// This profile represents abstract transforms and does not represent any + /// device model. Color transformations using Abstract profiles are performed + /// from PCS to PCS. Abstract profiles cannot be embedded in images. + /// + Abstract = 0x61627374, // abst + + /// + /// NamedColor profiles can be thought of as sibling profiles to device profiles. + /// For a given device there would be one or more device profiles to handle + /// process color conversions and one or more named color profiles to handle + /// named colors. /// - public enum IccProfileClass : uint - { - /// - /// Input profiles are generally used with devices such as scanners and - /// digital cameras. The types of profiles available for use as Input - /// profiles are N-component LUT-based, Three-component matrix-based, - /// and monochrome. - /// - InputDevice = 0x73636E72, // scnr - - /// - /// This class of profiles represents display devices such as monitors. - /// The types of profiles available for use as Display profiles are - /// N-component LUT-based, Three-component matrix-based, and monochrome. - /// - DisplayDevice = 0x6D6E7472, // mntr - - /// - /// Output profiles are used to support devices such as printers and - /// film recorders. The types of profiles available for use as Output - /// profiles are N-component LUT-based and Monochrome. - /// - OutputDevice = 0x70727472, // prtr - - /// - /// This profile contains a pre-evaluated transform that cannot be undone, - /// which represents a one-way link or connection between devices. It does - /// not represent any device model nor can it be embedded into images. - /// - DeviceLink = 0x6C696E6B, // link - - /// - /// This profile provides the relevant information to perform a transformation - /// between color encodings and the PCS. This type of profile is based on - /// modeling rather than device measurement or characterization data. - /// ColorSpace profiles may be embedded in images. - /// - ColorSpace = 0x73706163, // spac - - /// - /// This profile represents abstract transforms and does not represent any - /// device model. Color transformations using Abstract profiles are performed - /// from PCS to PCS. Abstract profiles cannot be embedded in images. - /// - Abstract = 0x61627374, // abst - - /// - /// NamedColor profiles can be thought of as sibling profiles to device profiles. - /// For a given device there would be one or more device profiles to handle - /// process color conversions and one or more named color profiles to handle - /// named colors. - /// - NamedColor = 0x6E6D636C, // nmcl - } + NamedColor = 0x6E6D636C, // nmcl } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs index 8a0c73882a..addc9f078f 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileFlag.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Profile flags. Can be combined with a logical OR. +/// The least-significant 16 bits are reserved for the ICC, +/// the rest can be used for vendor specific values +/// +[Flags] +public enum IccProfileFlag { /// - /// Profile flags. Can be combined with a logical OR. - /// The least-significant 16 bits are reserved for the ICC, - /// the rest can be used for vendor specific values + /// No flags (equivalent to NotEmbedded and Independent) /// - [Flags] - public enum IccProfileFlag - { - /// - /// No flags (equivalent to NotEmbedded and Independent) - /// - None = 0, + None = 0, - /// - /// Profile is embedded within another file - /// - Embedded = 1 << 0, + /// + /// Profile is embedded within another file + /// + Embedded = 1 << 0, - /// - /// Profile is not embedded within another file - /// - NotEmbedded = 0, + /// + /// Profile is not embedded within another file + /// + NotEmbedded = 0, - /// - /// Profile cannot be used independently of the embedded color data - /// - NotIndependent = 1 << 1, + /// + /// Profile cannot be used independently of the embedded color data + /// + NotIndependent = 1 << 1, - /// - /// Profile can be used independently of the embedded color data - /// - Independent = 0, - } + /// + /// Profile can be used independently of the embedded color data + /// + Independent = 0, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs index f141d4f62c..bcd110a063 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccProfileTag.cs @@ -2,361 +2,360 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 +/// Section 9 +/// +/// Each tag value represent the size of the tag in the profile. +/// +/// +public enum IccProfileTag : uint { /// - /// Enumerates the ICC Profile Tags as defined in ICC.1:2010 version 4.3.0.0 - /// Section 9 - /// - /// Each tag value represent the size of the tag in the profile. - /// - /// - public enum IccProfileTag : uint - { - /// - /// Unknown tag - /// - Unknown, - - /// - /// A2B0 - This tag defines a color transform from Device, Color Encoding or PCS, to PCS, or a color transform - /// from Device 1 to Device 2, using lookup table tag element structures - /// - AToB0 = 0x41324230, - - /// - /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures - /// - AToB1 = 0x41324231, - - /// - /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures - /// - AToB2 = 0x41324232, - - /// - /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms. - /// - BlueMatrixColumn = 0x6258595A, - - /// - /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or - /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue). - /// - BlueTrc = 0x62545243, - - /// - /// B2A0 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures - /// - BToA0 = 0x42324130, - - /// - /// B2A1 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. - /// - BToA1 = 0x42324131, - - /// - /// B2A2 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. - /// - BToA2 = 0x42324132, - - /// - /// B2D0 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA0 tag. - /// - BToD0 = 0x42324430, - - /// - /// B2D1 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA1 tag. - /// - BToD1 = 0x42324431, - - /// - /// B2D2 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA2 tag. - /// - BToD2 = 0x42324432, - - /// - /// B2D3 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and - /// provides a means to override the BToA1 tag. - /// - BToD3 = 0x42324433, - - /// - /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a - /// vendor's profile and how recently calibration has been performed. - /// - CalibrationDateTime = 0x63616C74, - - /// - /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement - /// data for a characterization target. - /// - CharTarget = 0x74617267, - - /// - /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ color, measured using the actual illumination - /// conditions and relative to the actual adopted white, to an nCIEXYZ color relative to the PCS adopted white - /// - ChromaticAdaptation = 0x63686164, - - /// - /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used. - /// - Chromaticity = 0x6368726D, - - /// - /// clro - This tag specifies the laydown order of colorants. - /// - ColorantOrder = 0x636C726F, - - /// - /// clrt - /// - ColorantTable = 0x636C7274, - - /// - /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values. - /// When used in DeviceLink profiles only the PCSLAB values shall be permitted. - /// - ColorantTableOut = 0x636C6F74, - - /// - /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms. - /// - ColorimetricIntentImageStat = 0x63696973, - - /// - /// cprt - This tag contains the text copyright information for the profile. - /// - Copyright = 0x63707274, - - /// - /// crdi - Removed in V4 - /// - CrdInfo = 0x63726469, - - /// - /// data - Removed in V4 - /// - Data = 0x64617461, - - /// - /// dtim - Removed in V4 - /// - DateTime = 0x6474696D, - - /// - /// dmnd - This tag describes the structure containing invariant and localizable - /// versions of the device manufacturer for display - /// - DeviceManufacturerDescription = 0x646D6E64, - - /// - /// dmdd - This tag describes the structure containing invariant and localizable - /// versions of the device model for display. - /// - DeviceModelDescription = 0x646D6464, - - /// - /// devs - Removed in V4 - /// - DeviceSettings = 0x64657673, - - /// - /// D2B0 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB0 tag - /// - DToB0 = 0x44324230, - - /// - /// D2B1 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB1 = 0x44324230, - - /// - /// D2B2 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB2 = 0x44324230, - - /// - /// D2B3 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded - /// input range, output range and transform, and provides a means to override the AToB1 tag - /// - DToB3 = 0x44324230, - - /// - /// gamt - This tag provides a table in which PCS values are the input and a single - /// output value for each input value is the output. If the output value is 0, the PCS color is in-gamut. - /// If the output is non-zero, the PCS color is out-of-gamut - /// - Gamut = 0x67616D74, - - /// - /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary - /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding. - /// - GrayTrc = 0x6b545243, - - /// - /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms. - /// - GreenMatrixColumn = 0x6758595A, - - /// - /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no - /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green). - /// - GreenTrc = 0x67545243, - - /// - /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square meter as described by the Y channel. - /// - Luminance = 0x6C756d69, - - /// - /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50. - /// - Measurement = 0x6D656173, - - /// - /// bkpt - Removed in V4 - /// - MediaBlackPoint = 0x626B7074, - - /// - /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically - /// adapted nCIEXYZ tristimulus values of the media white point. - /// - MediaWhitePoint = 0x77747074, - - /// - /// ncol - OBSOLETE, use - /// - NamedColor = 0x6E636f6C, - - /// - /// ncl2 - This tag contains the named color information providing a PCS and optional device representation - /// for a list of named colors. - /// - NamedColor2 = 0x6E636C32, - - /// - /// resp - This tag describes the structure containing a description of the device response for which the profile is intended. - /// - OutputResponse = 0x72657370, - - /// - /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3 - /// - PerceptualRenderingIntentGamut = 0x72696730, - - /// - /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS. - /// - Preview0 = 0x70726530, - - /// - /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS. - /// - Preview1 = 0x70726531, - - /// - /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS. - /// - Preview2 = 0x70726532, - - /// - /// desc - This tag describes the structure containing invariant and localizable versions of the profile - /// description for display. - /// - ProfileDescription = 0x64657363, - - /// - /// pseq - This tag describes the structure containing a description of the profile sequence from source to - /// destination, typically used with the DeviceLink profile. - /// - ProfileSequenceDescription = 0x70736571, - - /// - /// psd0 - Removed in V4 - /// - PostScript2Crd0 = 0x70736430, - - /// - /// psd1 - Removed in V4 - /// - PostScript2Crd1 = 0x70736431, - - /// - /// psd2 - Removed in V4 - /// - PostScript2Crd2 = 0x70736432, - - /// - /// psd3 - Removed in V4 - /// - PostScript2Crd3 = 0x70736433, - - /// - /// ps2s - Removed in V4 - /// - PostScript2Csa = 0x70733273, - - /// - /// psd2i- Removed in V4 - /// - PostScript2RenderingIntent = 0x70733269, - - /// - /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms. - /// - RedMatrixColumn = 0x7258595A, - - /// - /// This tag contains the red channel tone reproduction curve. The first element represents no colorant - /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red). - /// - RedTrc = 0x72545243, - - /// - /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3. - /// - SaturationRenderingIntentGamut = 0x72696732, - - /// - /// scrd - Removed in V4 - /// - ScreeningDescription = 0x73637264, - - /// - /// scrn - Removed in V4 - /// - Screening = 0x7363726E, - - /// - /// tech - The device technology signature - /// - Technology = 0x74656368, - - /// - /// bfd - Removed in V4 - /// - UcrBgSpecification = 0x62666420, - - /// - /// vued - This tag describes the structure containing invariant and localizable - /// versions of the viewing conditions. - /// - ViewingCondDescription = 0x76756564, - - /// - /// view - This tag defines the viewing conditions parameters - /// - ViewingConditions = 0x76696577, - } + /// Unknown tag + /// + Unknown, + + /// + /// A2B0 - This tag defines a color transform from Device, Color Encoding or PCS, to PCS, or a color transform + /// from Device 1 to Device 2, using lookup table tag element structures + /// + AToB0 = 0x41324230, + + /// + /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures + /// + AToB1 = 0x41324231, + + /// + /// A2B2 - This tag describes the color transform from Device or Color Encoding to PCS using lookup table tag element structures + /// + AToB2 = 0x41324232, + + /// + /// bXYZ - This tag contains the third column in the matrix used in matrix/TRC transforms. + /// + BlueMatrixColumn = 0x6258595A, + + /// + /// bTRC - This tag contains the blue channel tone reproduction curve. The first element represents no colorant (white) or + /// phosphor (black) and the last element represents 100 % colorant (blue) or 100 % phosphor (blue). + /// + BlueTrc = 0x62545243, + + /// + /// B2A0 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures + /// + BToA0 = 0x42324130, + + /// + /// B2A1 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. + /// + BToA1 = 0x42324131, + + /// + /// B2A2 - This tag defines a color transform from PCS to Device or Color Encoding using the lookup table tag element structures. + /// + BToA2 = 0x42324132, + + /// + /// B2D0 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA0 tag. + /// + BToD0 = 0x42324430, + + /// + /// B2D1 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD1 = 0x42324431, + + /// + /// B2D2 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA2 tag. + /// + BToD2 = 0x42324432, + + /// + /// B2D3 - This tag defines a color transform from PCS to Device. It supports float32Number-encoded input range, output range and transform, and + /// provides a means to override the BToA1 tag. + /// + BToD3 = 0x42324433, + + /// + /// calt - This tag contains the profile calibration date and time. This allows applications and utilities to verify if this profile matches a + /// vendor's profile and how recently calibration has been performed. + /// + CalibrationDateTime = 0x63616C74, + + /// + /// targ - This tag contains the name of the registered characterization data set, or it contains the measurement + /// data for a characterization target. + /// + CharTarget = 0x74617267, + + /// + /// chad - This tag contains a matrix, which shall be invertible, and which converts an nCIEXYZ color, measured using the actual illumination + /// conditions and relative to the actual adopted white, to an nCIEXYZ color relative to the PCS adopted white + /// + ChromaticAdaptation = 0x63686164, + + /// + /// chrm - This tag contains the type and the data of the phosphor/colorant chromaticity set used. + /// + Chromaticity = 0x6368726D, + + /// + /// clro - This tag specifies the laydown order of colorants. + /// + ColorantOrder = 0x636C726F, + + /// + /// clrt + /// + ColorantTable = 0x636C7274, + + /// + /// clot - This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values. + /// When used in DeviceLink profiles only the PCSLAB values shall be permitted. + /// + ColorantTableOut = 0x636C6F74, + + /// + /// ciis - This tag indicates the image state of PCS colorimetry produced using the colorimetric intent transforms. + /// + ColorimetricIntentImageStat = 0x63696973, + + /// + /// cprt - This tag contains the text copyright information for the profile. + /// + Copyright = 0x63707274, + + /// + /// crdi - Removed in V4 + /// + CrdInfo = 0x63726469, + + /// + /// data - Removed in V4 + /// + Data = 0x64617461, + + /// + /// dtim - Removed in V4 + /// + DateTime = 0x6474696D, + + /// + /// dmnd - This tag describes the structure containing invariant and localizable + /// versions of the device manufacturer for display + /// + DeviceManufacturerDescription = 0x646D6E64, + + /// + /// dmdd - This tag describes the structure containing invariant and localizable + /// versions of the device model for display. + /// + DeviceModelDescription = 0x646D6464, + + /// + /// devs - Removed in V4 + /// + DeviceSettings = 0x64657673, + + /// + /// D2B0 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB0 tag + /// + DToB0 = 0x44324230, + + /// + /// D2B1 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB1 = 0x44324230, + + /// + /// D2B2 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB2 = 0x44324230, + + /// + /// D2B3 - This tag defines a color transform from Device to PCS. It supports float32Number-encoded + /// input range, output range and transform, and provides a means to override the AToB1 tag + /// + DToB3 = 0x44324230, + + /// + /// gamt - This tag provides a table in which PCS values are the input and a single + /// output value for each input value is the output. If the output value is 0, the PCS color is in-gamut. + /// If the output is non-zero, the PCS color is out-of-gamut + /// + Gamut = 0x67616D74, + + /// + /// kTRC - This tag contains the grey tone reproduction curve. The tone reproduction curve provides the necessary + /// information to convert between a single device channel and the PCSXYZ or PCSLAB encoding. + /// + GrayTrc = 0x6b545243, + + /// + /// gXYZ - This tag contains the second column in the matrix, which is used in matrix/TRC transforms. + /// + GreenMatrixColumn = 0x6758595A, + + /// + /// gTRC - This tag contains the green channel tone reproduction curve. The first element represents no + /// colorant (white) or phosphor (black) and the last element represents 100 % colorant (green) or 100 % phosphor (green). + /// + GreenTrc = 0x67545243, + + /// + /// lumi - This tag contains the absolute luminance of emissive devices in candelas per square meter as described by the Y channel. + /// + Luminance = 0x6C756d69, + + /// + /// meas - This tag describes the alternative measurement specification, such as a D65 illuminant instead of the default D50. + /// + Measurement = 0x6D656173, + + /// + /// bkpt - Removed in V4 + /// + MediaBlackPoint = 0x626B7074, + + /// + /// wtpt - This tag, which is used for generating the ICC-absolute colorimetric intent, specifies the chromatically + /// adapted nCIEXYZ tristimulus values of the media white point. + /// + MediaWhitePoint = 0x77747074, + + /// + /// ncol - OBSOLETE, use + /// + NamedColor = 0x6E636f6C, + + /// + /// ncl2 - This tag contains the named color information providing a PCS and optional device representation + /// for a list of named colors. + /// + NamedColor2 = 0x6E636C32, + + /// + /// resp - This tag describes the structure containing a description of the device response for which the profile is intended. + /// + OutputResponse = 0x72657370, + + /// + /// rig0 - There is only one standard reference medium gamut, as defined in ISO 12640-3 + /// + PerceptualRenderingIntentGamut = 0x72696730, + + /// + /// pre0 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview0 = 0x70726530, + + /// + /// pre1 - This tag defines the preview transformation from PCS to device space and back to the PCS. + /// + Preview1 = 0x70726531, + + /// + /// pre2 - This tag contains the preview transformation from PCS to device space and back to the PCS. + /// + Preview2 = 0x70726532, + + /// + /// desc - This tag describes the structure containing invariant and localizable versions of the profile + /// description for display. + /// + ProfileDescription = 0x64657363, + + /// + /// pseq - This tag describes the structure containing a description of the profile sequence from source to + /// destination, typically used with the DeviceLink profile. + /// + ProfileSequenceDescription = 0x70736571, + + /// + /// psd0 - Removed in V4 + /// + PostScript2Crd0 = 0x70736430, + + /// + /// psd1 - Removed in V4 + /// + PostScript2Crd1 = 0x70736431, + + /// + /// psd2 - Removed in V4 + /// + PostScript2Crd2 = 0x70736432, + + /// + /// psd3 - Removed in V4 + /// + PostScript2Crd3 = 0x70736433, + + /// + /// ps2s - Removed in V4 + /// + PostScript2Csa = 0x70733273, + + /// + /// psd2i- Removed in V4 + /// + PostScript2RenderingIntent = 0x70733269, + + /// + /// rXYZ - This tag contains the first column in the matrix, which is used in matrix/TRC transforms. + /// + RedMatrixColumn = 0x7258595A, + + /// + /// This tag contains the red channel tone reproduction curve. The first element represents no colorant + /// (white) or phosphor (black) and the last element represents 100 % colorant (red) or 100 % phosphor (red). + /// + RedTrc = 0x72545243, + + /// + /// rig2 - There is only one standard reference medium gamut, as defined in ISO 12640-3. + /// + SaturationRenderingIntentGamut = 0x72696732, + + /// + /// scrd - Removed in V4 + /// + ScreeningDescription = 0x73637264, + + /// + /// scrn - Removed in V4 + /// + Screening = 0x7363726E, + + /// + /// tech - The device technology signature + /// + Technology = 0x74656368, + + /// + /// bfd - Removed in V4 + /// + UcrBgSpecification = 0x62666420, + + /// + /// vued - This tag describes the structure containing invariant and localizable + /// versions of the viewing conditions. + /// + ViewingCondDescription = 0x76756564, + + /// + /// view - This tag defines the viewing conditions parameters + /// + ViewingConditions = 0x76696577, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs index 3b1fed5bf0..cfdf81527c 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccRenderingIntent.cs @@ -1,42 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Rendering intent +/// +public enum IccRenderingIntent : uint { /// - /// Rendering intent + /// In perceptual transforms the PCS values represent hypothetical + /// measurements of a color reproduction on the reference reflective + /// medium. By extension, for the perceptual intent, the PCS represents + /// the appearance of that reproduction as viewed in the reference viewing + /// environment by a human observer adapted to that environment. The exact + /// color rendering of the perceptual intent is vendor specific. /// - public enum IccRenderingIntent : uint - { - /// - /// In perceptual transforms the PCS values represent hypothetical - /// measurements of a color reproduction on the reference reflective - /// medium. By extension, for the perceptual intent, the PCS represents - /// the appearance of that reproduction as viewed in the reference viewing - /// environment by a human observer adapted to that environment. The exact - /// color rendering of the perceptual intent is vendor specific. - /// - Perceptual = 0, + Perceptual = 0, - /// - /// Transformations for this intent shall re-scale the in-gamut, - /// chromatically adapted tristimulus values such that the white - /// point of the actual medium is mapped to the PCS white point - /// (for either input or output) - /// - MediaRelativeColorimetric = 1, + /// + /// Transformations for this intent shall re-scale the in-gamut, + /// chromatically adapted tristimulus values such that the white + /// point of the actual medium is mapped to the PCS white point + /// (for either input or output) + /// + MediaRelativeColorimetric = 1, - /// - /// The exact color rendering of the saturation intent is vendor - /// specific and involves compromises such as trading off - /// preservation of hue in order to preserve the vividness of pure colors. - /// - Saturation = 2, + /// + /// The exact color rendering of the saturation intent is vendor + /// specific and involves compromises such as trading off + /// preservation of hue in order to preserve the vividness of pure colors. + /// + Saturation = 2, - /// - /// Transformations for this intent shall leave the chromatically - /// adapted nCIEXYZ tristimulus values of the in-gamut colors unchanged. - /// - AbsoluteColorimetric = 3, - } + /// + /// Transformations for this intent shall leave the chromatically + /// adapted nCIEXYZ tristimulus values of the in-gamut colors unchanged. + /// + AbsoluteColorimetric = 3, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs index bcfc4f8c94..506de58b32 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningFlag.cs @@ -1,39 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Screening flags. Can be combined with a logical OR. +/// +[Flags] +internal enum IccScreeningFlag { /// - /// Screening flags. Can be combined with a logical OR. + /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm) /// - [Flags] - internal enum IccScreeningFlag - { - /// - /// No flags (equivalent to NotDefaultScreens and UnitLinesPerCm) - /// - None = 0, + None = 0, - /// - /// Use printer default screens - /// - DefaultScreens = 1 << 0, + /// + /// Use printer default screens + /// + DefaultScreens = 1 << 0, - /// - /// Don't use printer default screens - /// - NotDefaultScreens = 0, + /// + /// Don't use printer default screens + /// + NotDefaultScreens = 0, - /// - /// Frequency units in Lines/Inch - /// - UnitLinesPerInch = 1 << 1, + /// + /// Frequency units in Lines/Inch + /// + UnitLinesPerInch = 1 << 1, - /// - /// Frequency units in Lines/cm - /// - UnitLinesPerCm = 0, - } + /// + /// Frequency units in Lines/cm + /// + UnitLinesPerCm = 0, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs index 70f4fac023..20511fed10 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccScreeningSpotType.cs @@ -1,51 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Enumerates the screening spot types +/// +internal enum IccScreeningSpotType : int { /// - /// Enumerates the screening spot types - /// - internal enum IccScreeningSpotType : int - { - /// - /// Unknown spot type - /// - Unknown = 0, - - /// - /// Default printer spot type - /// - PrinterDefault = 1, - - /// - /// Round stop type - /// - Round = 2, - - /// - /// Diamond spot type - /// - Diamond = 3, - - /// - /// Ellipse spot type - /// - Ellipse = 4, - - /// - /// Line spot type - /// - Line = 5, - - /// - /// Square spot type - /// - Square = 6, - - /// - /// Cross spot type - /// - Cross = 7, - } + /// Unknown spot type + /// + Unknown = 0, + + /// + /// Default printer spot type + /// + PrinterDefault = 1, + + /// + /// Round stop type + /// + Round = 2, + + /// + /// Diamond spot type + /// + Diamond = 3, + + /// + /// Ellipse spot type + /// + Ellipse = 4, + + /// + /// Line spot type + /// + Line = 5, + + /// + /// Square spot type + /// + Square = 6, + + /// + /// Cross spot type + /// + Cross = 7, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs index 5d20f49ee9..ca4997ca4e 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccSignatureName.cs @@ -1,176 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Signature Name +/// +internal enum IccSignatureName : uint { /// - /// Signature Name - /// - internal enum IccSignatureName : uint - { - /// - /// Unknown signature - /// - Unknown = 0, - - /// - /// Scene Colorimetry Estimates - /// - SceneColorimetryEstimates = 0x73636F65, // scoe - - /// - /// Scene Appearance Estimates - /// - SceneAppearanceEstimates = 0x73617065, // sape - - /// - /// Focal Plane Colorimetry Estimates - /// - FocalPlaneColorimetryEstimates = 0x66706365, // fpce - - /// - /// Reflection Hardcopy Original Colorimetry - /// - ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc - - /// - /// Reflection Print Output Colorimetry - /// - ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc - - /// - /// Perceptual Reference Medium Gamut - /// - PerceptualReferenceMediumGamut = 0x70726D67, // prmg - - /// - /// Film Scanner - /// - FilmScanner = 0x6673636E, // fscn - - /// - /// Digital Camera - /// - DigitalCamera = 0x6463616D, // dcam - - /// - /// Reflective Scanner - /// - ReflectiveScanner = 0x7273636E, // rscn - - /// - /// InkJet Printer - /// - InkJetPrinter = 0x696A6574, // ijet - - /// - /// Thermal Wax Printer - /// - ThermalWaxPrinter = 0x74776178, // twax - - /// - /// Electrophotographic Printer - /// - ElectrophotographicPrinter = 0x6570686F, // epho - - /// - /// Electrostatic Printer - /// - ElectrostaticPrinter = 0x65737461, // esta - - /// - /// Dye Sublimation Printer - /// - DyeSublimationPrinter = 0x64737562, // dsub - - /// - /// Photographic Paper Printer - /// - PhotographicPaperPrinter = 0x7270686F, // rpho - - /// - /// Film Writer - /// - FilmWriter = 0x6670726E, // fprn - - /// - /// Video Monitor - /// - VideoMonitor = 0x7669646D, // vidm - - /// - /// Video Camera - /// - VideoCamera = 0x76696463, // vidc - - /// - /// Projection Television - /// - ProjectionTelevision = 0x706A7476, // pjtv - - /// - /// Cathode Ray Tube Display - /// - CathodeRayTubeDisplay = 0x43525420, // CRT - - /// - /// Passive Matrix Display - /// - PassiveMatrixDisplay = 0x504D4420, // PMD - - /// - /// Active Matrix Display - /// - ActiveMatrixDisplay = 0x414D4420, // AMD - - /// - /// Photo CD - /// - PhotoCD = 0x4B504344, // KPCD - - /// - /// Photographic Image Setter - /// - PhotographicImageSetter = 0x696D6773, // imgs - - /// - /// Gravure - /// - Gravure = 0x67726176, // grav - - /// - /// Offset Lithography - /// - OffsetLithography = 0x6F666673, // offs - - /// - /// Silkscreen - /// - Silkscreen = 0x73696C6B, // silk - - /// - /// Flexography - /// - Flexography = 0x666C6578, // flex - - /// - /// Motion Picture Film Scanner - /// - MotionPictureFilmScanner = 0x6D706673, // mpfs - - /// - /// Motion Picture Film Recorder - /// - MotionPictureFilmRecorder = 0x6D706672, // mpfr - - /// - /// Digital Motion Picture Camera - /// - DigitalMotionPictureCamera = 0x646D7063, // dmpc - - /// - /// Digital Cinema Projector - /// - DigitalCinemaProjector = 0x64636A70, // dcpj - } + /// Unknown signature + /// + Unknown = 0, + + /// + /// Scene Colorimetry Estimates + /// + SceneColorimetryEstimates = 0x73636F65, // scoe + + /// + /// Scene Appearance Estimates + /// + SceneAppearanceEstimates = 0x73617065, // sape + + /// + /// Focal Plane Colorimetry Estimates + /// + FocalPlaneColorimetryEstimates = 0x66706365, // fpce + + /// + /// Reflection Hardcopy Original Colorimetry + /// + ReflectionHardcopyOriginalColorimetry = 0x72686F63, // rhoc + + /// + /// Reflection Print Output Colorimetry + /// + ReflectionPrintOutputColorimetry = 0x72706F63, // rpoc + + /// + /// Perceptual Reference Medium Gamut + /// + PerceptualReferenceMediumGamut = 0x70726D67, // prmg + + /// + /// Film Scanner + /// + FilmScanner = 0x6673636E, // fscn + + /// + /// Digital Camera + /// + DigitalCamera = 0x6463616D, // dcam + + /// + /// Reflective Scanner + /// + ReflectiveScanner = 0x7273636E, // rscn + + /// + /// InkJet Printer + /// + InkJetPrinter = 0x696A6574, // ijet + + /// + /// Thermal Wax Printer + /// + ThermalWaxPrinter = 0x74776178, // twax + + /// + /// Electrophotographic Printer + /// + ElectrophotographicPrinter = 0x6570686F, // epho + + /// + /// Electrostatic Printer + /// + ElectrostaticPrinter = 0x65737461, // esta + + /// + /// Dye Sublimation Printer + /// + DyeSublimationPrinter = 0x64737562, // dsub + + /// + /// Photographic Paper Printer + /// + PhotographicPaperPrinter = 0x7270686F, // rpho + + /// + /// Film Writer + /// + FilmWriter = 0x6670726E, // fprn + + /// + /// Video Monitor + /// + VideoMonitor = 0x7669646D, // vidm + + /// + /// Video Camera + /// + VideoCamera = 0x76696463, // vidc + + /// + /// Projection Television + /// + ProjectionTelevision = 0x706A7476, // pjtv + + /// + /// Cathode Ray Tube Display + /// + CathodeRayTubeDisplay = 0x43525420, // CRT + + /// + /// Passive Matrix Display + /// + PassiveMatrixDisplay = 0x504D4420, // PMD + + /// + /// Active Matrix Display + /// + ActiveMatrixDisplay = 0x414D4420, // AMD + + /// + /// Photo CD + /// + PhotoCD = 0x4B504344, // KPCD + + /// + /// Photographic Image Setter + /// + PhotographicImageSetter = 0x696D6773, // imgs + + /// + /// Gravure + /// + Gravure = 0x67726176, // grav + + /// + /// Offset Lithography + /// + OffsetLithography = 0x6F666673, // offs + + /// + /// Silkscreen + /// + Silkscreen = 0x73696C6B, // silk + + /// + /// Flexography + /// + Flexography = 0x666C6578, // flex + + /// + /// Motion Picture Film Scanner + /// + MotionPictureFilmScanner = 0x6D706673, // mpfs + + /// + /// Motion Picture Film Recorder + /// + MotionPictureFilmRecorder = 0x6D706672, // mpfr + + /// + /// Digital Motion Picture Camera + /// + DigitalMotionPictureCamera = 0x646D7063, // dmpc + + /// + /// Digital Cinema Projector + /// + DigitalCinemaProjector = 0x64636A70, // dcpj } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs index 51ec32556a..e7257cc39d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardIlluminant.cs @@ -1,56 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Standard Illuminant +/// +internal enum IccStandardIlluminant : uint { /// - /// Standard Illuminant - /// - internal enum IccStandardIlluminant : uint - { - /// - /// Unknown illuminant - /// - Unknown = 0, - - /// - /// D50 illuminant - /// - D50 = 1, - - /// - /// D65 illuminant - /// - D65 = 2, - - /// - /// D93 illuminant - /// - D93 = 3, - - /// - /// F2 illuminant - /// - F2 = 4, - - /// - /// D55 illuminant - /// - D55 = 5, - - /// - /// A illuminant - /// - A = 6, - - /// - /// D50 illuminant - /// - EquiPowerE = 7, - - /// - /// F8 illuminant - /// - F8 = 8, - } + /// Unknown illuminant + /// + Unknown = 0, + + /// + /// D50 illuminant + /// + D50 = 1, + + /// + /// D65 illuminant + /// + D65 = 2, + + /// + /// D93 illuminant + /// + D93 = 3, + + /// + /// F2 illuminant + /// + F2 = 4, + + /// + /// D55 illuminant + /// + D55 = 5, + + /// + /// A illuminant + /// + A = 6, + + /// + /// D50 illuminant + /// + EquiPowerE = 7, + + /// + /// F8 illuminant + /// + F8 = 8, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs index 269481b25b..2c131f750c 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccStandardObserver.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Standard Observer +/// +internal enum IccStandardObserver : uint { /// - /// Standard Observer + /// Unknown observer /// - internal enum IccStandardObserver : uint - { - /// - /// Unknown observer - /// - Unknown = 0, + Unknown = 0, - /// - /// CIE 1931 observer - /// - Cie1931Observer = 1, + /// + /// CIE 1931 observer + /// + Cie1931Observer = 1, - /// - /// CIE 1964 observer - /// - Cie1964Observer = 2, - } + /// + /// CIE 1964 observer + /// + Cie1964Observer = 2, } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs index 1dae26e39e..1174b09ab9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Enums/IccTypeSignature.cs @@ -1,271 +1,270 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Type Signature +/// +public enum IccTypeSignature : uint { /// - /// Type Signature - /// - public enum IccTypeSignature : uint - { - /// - /// Unknown type signature - /// - Unknown, - - /// - /// The chromaticity tag type provides basic chromaticity data and type of - /// phosphors or colorants of a monitor to applications and utilities - /// - Chromaticity = 0x6368726D, - - /// - /// This is an optional tag which specifies the laydown order in which colorants - /// will be printed on an n-colorant device. The laydown order may be the same - /// as the channel generation order listed in the colorantTableTag or the channel - /// order of a color encoding type such as CMYK, in which case this tag is not - /// needed. When this is not the case (for example, ink-towers sometimes use - /// the order KCMY), this tag may be used to specify the laydown order of the - /// colorants - /// - ColorantOrder = 0x636c726f, - - /// - /// The purpose of this tag is to identify the colorants used in the profile - /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant - /// an unambiguous value. The first colorant listed is the colorant of the - /// first device channel of a LUT tag. The second colorant listed is the - /// colorant of the second device channel of a LUT tag, and so on - /// - ColorantTable = 0x636c7274, - - /// - /// The curveType embodies a one-dimensional function which maps an input - /// value in the domain of the function to an output value in the range - /// of the function - /// - Curve = 0x63757276, - - /// - /// The dataType is a simple data structure that contains either 7-bit ASCII - /// or binary data - /// - Data = 0x64617461, - - /// - /// Date and time defined by 6 unsigned 16bit integers - /// (year, month, day, hour, minute, second) - /// - DateTime = 0x6474696D, - - /// - /// This structure represents a color transform using tables with 16-bit - /// precision. This type contains four processing elements: a 3 × 3 matrix - /// (which shall be the identity matrix unless the input color space is - /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional - /// lookup table, and a set of one-dimensional output tables - /// - Lut16 = 0x6D667432, - - /// - /// This structure represents a color transform using tables of 8-bit - /// precision. This type contains four processing elements: a 3 × 3 matrix - /// (which shall be the identity matrix unless the input color space is - /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional - /// lookup table, and a set of one-dimensional output tables. - /// - Lut8 = 0x6D667431, - - /// - /// This structure represents a color transform. The type contains up - /// to five processing elements which are stored in the AToBTag tag - /// in the following order: a set of one-dimensional curves, a 3 × 3 - /// matrix with offset terms, a set of one-dimensional curves, a - /// multi-dimensional lookup table, and a set of one-dimensional - /// output curves - /// - LutAToB = 0x6D414220, - - /// - /// This structure represents a color transform. The type contains - /// up to five processing elements which are stored in the BToATag - /// in the following order: a set of one-dimensional curves, a 3 × 3 - /// matrix with offset terms, a set of one-dimensional curves, a - /// multi-dimensional lookup table, and a set of one-dimensional curves. - /// - LutBToA = 0x6D424120, - - /// - /// This information refers only to the internal - /// profile data and is meant to provide profile makers an alternative - /// to the default measurement specifications - /// - Measurement = 0x6D656173, - - /// - /// This tag structure contains a set of records each referencing a - /// multilingual Unicode string associated with a profile. Each string - /// is referenced in a separate record with the information about what - /// language and region the string is for. - /// - MultiLocalizedUnicode = 0x6D6C7563, - - /// - /// This structure represents a color transform, containing a sequence - /// of processing elements. The processing elements contained in the - /// structure are defined in the structure itself, allowing for a flexible - /// structure. Currently supported processing elements are: a set of one - /// dimensional curves, a matrix with offset terms, and a multidimensional - /// lookup table (CLUT). Other processing element types may be added in - /// the future. Each type of processing element may be contained any - /// number of times in the structure. - /// - MultiProcessElements = 0x6D706574, - - /// - /// This type is a count value and array of structures that provide color - /// coordinates for color names. For each named color, a PCS and optional - /// device representation of the color are given. Both representations are - /// 16-bit values and PCS values shall be relative colorimetric. The device - /// representation corresponds to the header’s "data color space" field. - /// This representation should be consistent with the "number of device - /// coordinates" field in the namedColor2Type. If this field is 0, device - /// coordinates are not provided. The PCS representation corresponds to the - /// header's PCS field. The PCS representation is always provided. Color - /// names are fixed-length, 32-byte fields including null termination. In - /// order to maintain maximum portability, it is strongly recommended that - /// special characters of the 7-bit ASCII set not be used. - /// - NamedColor2 = 0x6E636C32, - - /// - /// This type describes a one-dimensional curve by specifying one of a - /// predefined set of functions using the parameters. - /// - ParametricCurve = 0x70617261, - - /// - /// This type is an array of structures, each of which contains information - /// from the header fields and tags from the original profiles which were - /// combined to create the final profile. The order of the structures is - /// the order in which the profiles were combined and includes a structure - /// for the final profile. This provides a description of the profile - /// sequence from source to destination, typically used with the DeviceLink - /// profile. - /// - ProfileSequenceDesc = 0x70736571, - - /// - /// This type is an array of structures, each of which contains information - /// for identification of a profile used in a sequence. - /// - ProfileSequenceIdentifier = 0x70736964, - - /// - /// The purpose of this tag type is to provide a mechanism to relate physical - /// colorant amounts with the normalized device codes produced by lut8Type, - /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags - /// so that corrections can be made for variation in the device without - /// having to produce a new profile. The mechanism can be used by applications - /// to allow users with relatively inexpensive and readily available - /// instrumentation to apply corrections to individual output color - /// channels in order to achieve consistent results. - /// - ResponseCurveSet16 = 0x72637332, - - /// - /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits - /// - S15Fixed16Array = 0x73663332, - - /// - /// The signatureType contains a 4-byte sequence. Sequences of less than four - /// characters are padded at the end with spaces. Typically this type is used - /// for registered tags that can be displayed on many development systems as - /// a sequence of four characters. - /// - Signature = 0x73696720, - - /// - /// Simple ASCII text - /// - Text = 0x74657874, - - /// - /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits - /// - U16Fixed16Array = 0x75663332, - - /// - /// Array of unsigned 16bit integers (ushort) - /// - UInt16Array = 0x75693136, - - /// - /// Array of unsigned 32bit integers (uint) - /// - UInt32Array = 0x75693332, - - /// - /// Array of unsigned 64bit integers (ulong) - /// - UInt64Array = 0x75693634, - - /// - /// Array of unsigned 8bit integers (byte) - /// - UInt8Array = 0x75693038, - - /// - /// This type represents a set of viewing condition parameters. - /// - ViewingConditions = 0x76696577, - - /// - /// 3 floating point values describing a XYZ color value - /// - Xyz = 0x58595A20, - - /// - /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three - /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no - /// single standard method for specifying localizable character sets exists across - /// the major platform vendors, including all three provides access for the major - /// operating systems. The 7-bit ASCII description is to be an invariant, - /// nonlocalizable name for consistent reference. It is preferred that both the - /// Unicode and ScriptCode structures be properly localized. - /// - TextDescription = 0x64657363, - - /// - /// REMOVED IN V4 - This type contains the PostScript product name to which this - /// profile corresponds and the names of the companion CRDs - /// - CrdInfo = 0x63726469, - - /// - /// REMOVED IN V4 - The screeningType describes various screening parameters including - /// screen frequency, screening angle, and spot shape - /// - Screening = 0x7363726E, - - /// - /// REMOVED IN V4 - This type contains curves representing the under color removal and - /// black generation and a text string which is a general description of the method - /// used for the UCR and BG - /// - UcrBg = 0x62666420, - - /// - /// REMOVED IN V4 - This type is an array of structures each of which contains - /// platform-specific information about the settings of the device for which - /// this profile is valid. This type is not supported. - /// - DeviceSettings = 0x64657673, // not supported - - /// - /// REMOVED IN V2 - use instead. This type is not supported. - /// - NamedColor = 0x6E636F6C, // not supported - } + /// Unknown type signature + /// + Unknown, + + /// + /// The chromaticity tag type provides basic chromaticity data and type of + /// phosphors or colorants of a monitor to applications and utilities + /// + Chromaticity = 0x6368726D, + + /// + /// This is an optional tag which specifies the laydown order in which colorants + /// will be printed on an n-colorant device. The laydown order may be the same + /// as the channel generation order listed in the colorantTableTag or the channel + /// order of a color encoding type such as CMYK, in which case this tag is not + /// needed. When this is not the case (for example, ink-towers sometimes use + /// the order KCMY), this tag may be used to specify the laydown order of the + /// colorants + /// + ColorantOrder = 0x636c726f, + + /// + /// The purpose of this tag is to identify the colorants used in the profile + /// by a unique name and set of PCSXYZ or PCSLAB values to give the colorant + /// an unambiguous value. The first colorant listed is the colorant of the + /// first device channel of a LUT tag. The second colorant listed is the + /// colorant of the second device channel of a LUT tag, and so on + /// + ColorantTable = 0x636c7274, + + /// + /// The curveType embodies a one-dimensional function which maps an input + /// value in the domain of the function to an output value in the range + /// of the function + /// + Curve = 0x63757276, + + /// + /// The dataType is a simple data structure that contains either 7-bit ASCII + /// or binary data + /// + Data = 0x64617461, + + /// + /// Date and time defined by 6 unsigned 16bit integers + /// (year, month, day, hour, minute, second) + /// + DateTime = 0x6474696D, + + /// + /// This structure represents a color transform using tables with 16-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input color space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables + /// + Lut16 = 0x6D667432, + + /// + /// This structure represents a color transform using tables of 8-bit + /// precision. This type contains four processing elements: a 3 × 3 matrix + /// (which shall be the identity matrix unless the input color space is + /// PCSXYZ), a set of one-dimensional input tables, a multi-dimensional + /// lookup table, and a set of one-dimensional output tables. + /// + Lut8 = 0x6D667431, + + /// + /// This structure represents a color transform. The type contains up + /// to five processing elements which are stored in the AToBTag tag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional + /// output curves + /// + LutAToB = 0x6D414220, + + /// + /// This structure represents a color transform. The type contains + /// up to five processing elements which are stored in the BToATag + /// in the following order: a set of one-dimensional curves, a 3 × 3 + /// matrix with offset terms, a set of one-dimensional curves, a + /// multi-dimensional lookup table, and a set of one-dimensional curves. + /// + LutBToA = 0x6D424120, + + /// + /// This information refers only to the internal + /// profile data and is meant to provide profile makers an alternative + /// to the default measurement specifications + /// + Measurement = 0x6D656173, + + /// + /// This tag structure contains a set of records each referencing a + /// multilingual Unicode string associated with a profile. Each string + /// is referenced in a separate record with the information about what + /// language and region the string is for. + /// + MultiLocalizedUnicode = 0x6D6C7563, + + /// + /// This structure represents a color transform, containing a sequence + /// of processing elements. The processing elements contained in the + /// structure are defined in the structure itself, allowing for a flexible + /// structure. Currently supported processing elements are: a set of one + /// dimensional curves, a matrix with offset terms, and a multidimensional + /// lookup table (CLUT). Other processing element types may be added in + /// the future. Each type of processing element may be contained any + /// number of times in the structure. + /// + MultiProcessElements = 0x6D706574, + + /// + /// This type is a count value and array of structures that provide color + /// coordinates for color names. For each named color, a PCS and optional + /// device representation of the color are given. Both representations are + /// 16-bit values and PCS values shall be relative colorimetric. The device + /// representation corresponds to the header’s "data color space" field. + /// This representation should be consistent with the "number of device + /// coordinates" field in the namedColor2Type. If this field is 0, device + /// coordinates are not provided. The PCS representation corresponds to the + /// header's PCS field. The PCS representation is always provided. Color + /// names are fixed-length, 32-byte fields including null termination. In + /// order to maintain maximum portability, it is strongly recommended that + /// special characters of the 7-bit ASCII set not be used. + /// + NamedColor2 = 0x6E636C32, + + /// + /// This type describes a one-dimensional curve by specifying one of a + /// predefined set of functions using the parameters. + /// + ParametricCurve = 0x70617261, + + /// + /// This type is an array of structures, each of which contains information + /// from the header fields and tags from the original profiles which were + /// combined to create the final profile. The order of the structures is + /// the order in which the profiles were combined and includes a structure + /// for the final profile. This provides a description of the profile + /// sequence from source to destination, typically used with the DeviceLink + /// profile. + /// + ProfileSequenceDesc = 0x70736571, + + /// + /// This type is an array of structures, each of which contains information + /// for identification of a profile used in a sequence. + /// + ProfileSequenceIdentifier = 0x70736964, + + /// + /// The purpose of this tag type is to provide a mechanism to relate physical + /// colorant amounts with the normalized device codes produced by lut8Type, + /// lut16Type, lutAToBType, lutBToAType or multiProcessElementsType tags + /// so that corrections can be made for variation in the device without + /// having to produce a new profile. The mechanism can be used by applications + /// to allow users with relatively inexpensive and readily available + /// instrumentation to apply corrections to individual output color + /// channels in order to achieve consistent results. + /// + ResponseCurveSet16 = 0x72637332, + + /// + /// Array of signed floating point numbers with 1 sign bit, 15 value bits and 16 fractional bits + /// + S15Fixed16Array = 0x73663332, + + /// + /// The signatureType contains a 4-byte sequence. Sequences of less than four + /// characters are padded at the end with spaces. Typically this type is used + /// for registered tags that can be displayed on many development systems as + /// a sequence of four characters. + /// + Signature = 0x73696720, + + /// + /// Simple ASCII text + /// + Text = 0x74657874, + + /// + /// Array of unsigned floating point numbers with 16 value bits and 16 fractional bits + /// + U16Fixed16Array = 0x75663332, + + /// + /// Array of unsigned 16bit integers (ushort) + /// + UInt16Array = 0x75693136, + + /// + /// Array of unsigned 32bit integers (uint) + /// + UInt32Array = 0x75693332, + + /// + /// Array of unsigned 64bit integers (ulong) + /// + UInt64Array = 0x75693634, + + /// + /// Array of unsigned 8bit integers (byte) + /// + UInt8Array = 0x75693038, + + /// + /// This type represents a set of viewing condition parameters. + /// + ViewingConditions = 0x76696577, + + /// + /// 3 floating point values describing a XYZ color value + /// + Xyz = 0x58595A20, + + /// + /// REMOVED IN V4 - The textDescriptionType is a complex structure that contains three + /// types of text description structures: 7-bit ASCII, Unicode and ScriptCode. Since no + /// single standard method for specifying localizable character sets exists across + /// the major platform vendors, including all three provides access for the major + /// operating systems. The 7-bit ASCII description is to be an invariant, + /// nonlocalizable name for consistent reference. It is preferred that both the + /// Unicode and ScriptCode structures be properly localized. + /// + TextDescription = 0x64657363, + + /// + /// REMOVED IN V4 - This type contains the PostScript product name to which this + /// profile corresponds and the names of the companion CRDs + /// + CrdInfo = 0x63726469, + + /// + /// REMOVED IN V4 - The screeningType describes various screening parameters including + /// screen frequency, screening angle, and spot shape + /// + Screening = 0x7363726E, + + /// + /// REMOVED IN V4 - This type contains curves representing the under color removal and + /// black generation and a text string which is a general description of the method + /// used for the UCR and BG + /// + UcrBg = 0x62666420, + + /// + /// REMOVED IN V4 - This type is an array of structures each of which contains + /// platform-specific information about the settings of the device for which + /// this profile is valid. This type is not supported. + /// + DeviceSettings = 0x64657673, // not supported + + /// + /// REMOVED IN V2 - use instead. This type is not supported. + /// + NamedColor = 0x6E636F6C, // not supported } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs index 7b7653920e..c1cb3f10f0 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Exceptions/InvalidIccProfileException.cs @@ -1,33 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Represents an error that happened while reading or writing a corrupt/invalid ICC profile +/// +public class InvalidIccProfileException : Exception { /// - /// Represents an error that happened while reading or writing a corrupt/invalid ICC profile + /// Initializes a new instance of the class. /// - public class InvalidIccProfileException : Exception + /// The message that describes the error + public InvalidIccProfileException(string message) + : base(message) { - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error - public InvalidIccProfileException(string message) - : base(message) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error - /// The exception that is the cause of the current exception, or a null reference - /// (Nothing in Visual Basic) if no inner exception is specified - public InvalidIccProfileException(string message, Exception inner) - : base(message, inner) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified + public InvalidIccProfileException(string message, Exception inner) + : base(message, inner) + { } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index 39b726f903..e4403a47e2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -1,216 +1,214 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Security.Cryptography; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Represents an ICC profile +/// +public sealed class IccProfile : IDeepCloneable { /// - /// Represents an ICC profile + /// The byte array to read the ICC profile from /// - public sealed class IccProfile : IDeepCloneable + private readonly byte[] data; + + /// + /// The backing file for the property + /// + private IccTagDataEntry[] entries; + + /// + /// ICC profile header + /// + private IccProfileHeader header; + + /// + /// Initializes a new instance of the class. + /// + public IccProfile() + : this((byte[])null) { - /// - /// The byte array to read the ICC profile from - /// - private readonly byte[] data; - - /// - /// The backing file for the property - /// - private IccTagDataEntry[] entries; - - /// - /// ICC profile header - /// - private IccProfileHeader header; - - /// - /// Initializes a new instance of the class. - /// - public IccProfile() - : this((byte[])null) + } + + /// + /// Initializes a new instance of the class. + /// + /// The raw ICC profile data + public IccProfile(byte[] data) => this.data = data; + + /// + /// Initializes a new instance of the class. + /// + /// The profile header + /// The actual profile data + internal IccProfile(IccProfileHeader header, IccTagDataEntry[] entries) + { + this.header = header ?? throw new ArgumentNullException(nameof(header)); + this.entries = entries ?? throw new ArgumentNullException(nameof(entries)); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another ICC profile. + /// + /// The other ICC profile, where the clone should be made from. + /// is null.> + private IccProfile(IccProfile other) + { + Guard.NotNull(other, nameof(other)); + + this.data = other.ToByteArray(); + } + + /// + /// Gets or sets the profile header + /// + public IccProfileHeader Header + { + get { + this.InitializeHeader(); + return this.header; } - /// - /// Initializes a new instance of the class. - /// - /// The raw ICC profile data - public IccProfile(byte[] data) => this.data = data; - - /// - /// Initializes a new instance of the class. - /// - /// The profile header - /// The actual profile data - internal IccProfile(IccProfileHeader header, IccTagDataEntry[] entries) + set => this.header = value; + } + + /// + /// Gets the actual profile data + /// + public IccTagDataEntry[] Entries + { + get { - this.header = header ?? throw new ArgumentNullException(nameof(header)); - this.entries = entries ?? throw new ArgumentNullException(nameof(entries)); + this.InitializeEntries(); + return this.entries; } + } - /// - /// Initializes a new instance of the class - /// by making a copy from another ICC profile. - /// - /// The other ICC profile, where the clone should be made from. - /// is null.> - private IccProfile(IccProfile other) - { - Guard.NotNull(other, nameof(other)); + /// + public IccProfile DeepClone() => new(this); - this.data = other.ToByteArray(); - } + /// + /// Calculates the MD5 hash value of an ICC profile + /// + /// The data of which to calculate the hash value + /// The calculated hash + public static IccProfileId CalculateHash(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + + const int profileFlagPos = 44; + const int renderingIntentPos = 64; + const int profileIdPos = 84; + + // need to copy some values because they need to be zero for the hashing + byte[] temp = new byte[24]; + Buffer.BlockCopy(data, profileFlagPos, temp, 0, 4); + Buffer.BlockCopy(data, renderingIntentPos, temp, 4, 4); + Buffer.BlockCopy(data, profileIdPos, temp, 8, 16); - /// - /// Gets or sets the profile header - /// - public IccProfileHeader Header + try { - get - { - this.InitializeHeader(); - return this.header; - } + // Zero out some values + Array.Clear(data, profileFlagPos, 4); + Array.Clear(data, renderingIntentPos, 4); + Array.Clear(data, profileIdPos, 16); - set => this.header = value; - } + // Calculate hash +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + byte[] hash = MD5.HashData(data); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms - /// - /// Gets the actual profile data - /// - public IccTagDataEntry[] Entries + // Read values from hash + IccDataReader reader = new(hash); + return reader.ReadProfileId(); + } + finally { - get - { - this.InitializeEntries(); - return this.entries; - } + Buffer.BlockCopy(temp, 0, data, profileFlagPos, 4); + Buffer.BlockCopy(temp, 4, data, renderingIntentPos, 4); + Buffer.BlockCopy(temp, 8, data, profileIdPos, 16); } + } - /// - public IccProfile DeepClone() => new(this); + /// + /// Checks for signs of a corrupt profile. + /// + /// This is not an absolute proof of validity but should weed out most corrupt data. + /// True if the profile is valid; False otherwise + public bool CheckIsValid() + { + const int minSize = 128; + const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB - /// - /// Calculates the MD5 hash value of an ICC profile - /// - /// The data of which to calculate the hash value - /// The calculated hash - public static IccProfileId CalculateHash(byte[] data) + bool arrayValid = true; + if (this.data != null) { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); - - const int profileFlagPos = 44; - const int renderingIntentPos = 64; - const int profileIdPos = 84; - - // need to copy some values because they need to be zero for the hashing - byte[] temp = new byte[24]; - Buffer.BlockCopy(data, profileFlagPos, temp, 0, 4); - Buffer.BlockCopy(data, renderingIntentPos, temp, 4, 4); - Buffer.BlockCopy(data, profileIdPos, temp, 8, 16); - - try - { - // Zero out some values - Array.Clear(data, profileFlagPos, 4); - Array.Clear(data, renderingIntentPos, 4); - Array.Clear(data, profileIdPos, 16); - - // Calculate hash -#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms - byte[] hash = MD5.HashData(data); -#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + arrayValid = this.data.Length >= minSize && + this.data.Length >= this.Header.Size; + } + + return arrayValid && + Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) && + Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) && + Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) && + this.Header.Size >= minSize && + this.Header.Size < maxSize; + } - // Read values from hash - IccDataReader reader = new(hash); - return reader.ReadProfileId(); - } - finally - { - Buffer.BlockCopy(temp, 0, data, profileFlagPos, 4); - Buffer.BlockCopy(temp, 4, data, renderingIntentPos, 4); - Buffer.BlockCopy(temp, 8, data, profileIdPos, 16); - } + /// + /// Converts this instance to a byte array. + /// + /// The + public byte[] ToByteArray() + { + if (this.data != null) + { + byte[] copy = new byte[this.data.Length]; + Buffer.BlockCopy(this.data, 0, copy, 0, copy.Length); + return copy; } - /// - /// Checks for signs of a corrupt profile. - /// - /// This is not an absolute proof of validity but should weed out most corrupt data. - /// True if the profile is valid; False otherwise - public bool CheckIsValid() + IccWriter writer = new(); + return IccWriter.Write(this); + } + + private void InitializeHeader() + { + if (this.header != null) { - const int minSize = 128; - const int maxSize = 50_000_000; // it's unlikely there is a profile bigger than 50MB - - bool arrayValid = true; - if (this.data != null) - { - arrayValid = this.data.Length >= minSize && - this.data.Length >= this.Header.Size; - } - - return arrayValid && - Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) && - Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) && - Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) && - this.Header.Size >= minSize && - this.Header.Size < maxSize; + return; } - /// - /// Converts this instance to a byte array. - /// - /// The - public byte[] ToByteArray() + if (this.data is null) { - if (this.data != null) - { - byte[] copy = new byte[this.data.Length]; - Buffer.BlockCopy(this.data, 0, copy, 0, copy.Length); - return copy; - } - - IccWriter writer = new(); - return IccWriter.Write(this); + this.header = new IccProfileHeader(); + return; } - private void InitializeHeader() + IccReader reader = new(); + this.header = IccReader.ReadHeader(this.data); + } + + private void InitializeEntries() + { + if (this.entries != null) { - if (this.header != null) - { - return; - } - - if (this.data is null) - { - this.header = new IccProfileHeader(); - return; - } - - IccReader reader = new(); - this.header = IccReader.ReadHeader(this.data); + return; } - private void InitializeEntries() + if (this.data is null) { - if (this.entries != null) - { - return; - } - - if (this.data is null) - { - this.entries = Array.Empty(); - return; - } - - IccReader reader = new(); - this.entries = IccReader.ReadTagData(this.data); + this.entries = Array.Empty(); + return; } + + IccReader reader = new(); + this.entries = IccReader.ReadTagData(this.data); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs index 4b1db4d5c4..665a1d9c28 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Contains all values of an ICC profile header. +/// +public sealed class IccProfileHeader { /// - /// Contains all values of an ICC profile header. - /// - public sealed class IccProfileHeader - { - /// - /// Gets or sets the profile size in bytes (will be ignored when writing a profile). - /// - public uint Size { get; set; } - - /// - /// Gets or sets the preferred CMM (Color Management Module) type. - /// - public string CmmType { get; set; } - - /// - /// Gets or sets the profiles version number. - /// - public IccVersion Version { get; set; } - - /// - /// Gets or sets the type of the profile. - /// - public IccProfileClass Class { get; set; } - - /// - /// Gets or sets the data colorspace. - /// - public IccColorSpaceType DataColorSpace { get; set; } - - /// - /// Gets or sets the profile connection space. - /// - public IccColorSpaceType ProfileConnectionSpace { get; set; } - - /// - /// Gets or sets the date and time this profile was created. - /// - public DateTime CreationDate { get; set; } - - /// - /// Gets or sets the file signature. Should always be "acsp". - /// Value will be ignored when writing a profile. - /// - public string FileSignature { get; set; } - - /// - /// Gets or sets the primary platform this profile as created for - /// - public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; } - - /// - /// Gets or sets the profile flags to indicate various options for the CMM - /// such as distributed processing and caching options. - /// - public IccProfileFlag Flags { get; set; } - - /// - /// Gets or sets the device manufacturer of the device for which this profile is created. - /// - public uint DeviceManufacturer { get; set; } - - /// - /// Gets or sets the model of the device for which this profile is created. - /// - public uint DeviceModel { get; set; } - - /// - /// Gets or sets the device attributes unique to the particular device setup such as media type. - /// - public IccDeviceAttribute DeviceAttributes { get; set; } - - /// - /// Gets or sets the rendering Intent. - /// - public IccRenderingIntent RenderingIntent { get; set; } - - /// - /// Gets or sets The normalized XYZ values of the illuminant of the PCS. - /// - public Vector3 PcsIlluminant { get; set; } - - /// - /// Gets or sets profile creator signature. - /// - public string CreatorSignature { get; set; } - - /// - /// Gets or sets the profile ID (hash). - /// - public IccProfileId Id { get; set; } - } + /// Gets or sets the profile size in bytes (will be ignored when writing a profile). + /// + public uint Size { get; set; } + + /// + /// Gets or sets the preferred CMM (Color Management Module) type. + /// + public string CmmType { get; set; } + + /// + /// Gets or sets the profiles version number. + /// + public IccVersion Version { get; set; } + + /// + /// Gets or sets the type of the profile. + /// + public IccProfileClass Class { get; set; } + + /// + /// Gets or sets the data colorspace. + /// + public IccColorSpaceType DataColorSpace { get; set; } + + /// + /// Gets or sets the profile connection space. + /// + public IccColorSpaceType ProfileConnectionSpace { get; set; } + + /// + /// Gets or sets the date and time this profile was created. + /// + public DateTime CreationDate { get; set; } + + /// + /// Gets or sets the file signature. Should always be "acsp". + /// Value will be ignored when writing a profile. + /// + public string FileSignature { get; set; } + + /// + /// Gets or sets the primary platform this profile as created for + /// + public IccPrimaryPlatformType PrimaryPlatformSignature { get; set; } + + /// + /// Gets or sets the profile flags to indicate various options for the CMM + /// such as distributed processing and caching options. + /// + public IccProfileFlag Flags { get; set; } + + /// + /// Gets or sets the device manufacturer of the device for which this profile is created. + /// + public uint DeviceManufacturer { get; set; } + + /// + /// Gets or sets the model of the device for which this profile is created. + /// + public uint DeviceModel { get; set; } + + /// + /// Gets or sets the device attributes unique to the particular device setup such as media type. + /// + public IccDeviceAttribute DeviceAttributes { get; set; } + + /// + /// Gets or sets the rendering Intent. + /// + public IccRenderingIntent RenderingIntent { get; set; } + + /// + /// Gets or sets The normalized XYZ values of the illuminant of the PCS. + /// + public Vector3 PcsIlluminant { get; set; } + + /// + /// Gets or sets profile creator signature. + /// + public string CreatorSignature { get; set; } + + /// + /// Gets or sets the profile ID (hash). + /// + public IccProfileId Id { get; set; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs index 5e6e69fff6..0fe01fbdb2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs @@ -1,150 +1,146 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Reads and parses ICC data from a byte array +/// +internal sealed class IccReader { /// - /// Reads and parses ICC data from a byte array + /// Reads an ICC profile /// - internal sealed class IccReader + /// The raw ICC data + /// The read ICC profile + public static IccProfile Read(byte[] data) { - /// - /// Reads an ICC profile - /// - /// The raw ICC data - /// The read ICC profile - public static IccProfile Read(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); - IccDataReader reader = new(data); - IccProfileHeader header = ReadHeader(reader); - IccTagDataEntry[] tagData = ReadTagData(reader); + IccDataReader reader = new(data); + IccProfileHeader header = ReadHeader(reader); + IccTagDataEntry[] tagData = ReadTagData(reader); - return new IccProfile(header, tagData); - } + return new IccProfile(header, tagData); + } - /// - /// Reads an ICC profile header - /// - /// The raw ICC data - /// The read ICC profile header - public static IccProfileHeader ReadHeader(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); + /// + /// Reads an ICC profile header + /// + /// The raw ICC data + /// The read ICC profile header + public static IccProfileHeader ReadHeader(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid profile header"); - IccDataReader reader = new(data); - return ReadHeader(reader); - } + IccDataReader reader = new(data); + return ReadHeader(reader); + } - /// - /// Reads the ICC profile tag data - /// - /// The raw ICC data - /// The read ICC profile tag data - public static IccTagDataEntry[] ReadTagData(byte[] data) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); + /// + /// Reads the ICC profile tag data + /// + /// The raw ICC data + /// The read ICC profile tag data + public static IccTagDataEntry[] ReadTagData(byte[] data) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length >= 128, nameof(data), "Data length must be at least 128 to be a valid ICC profile"); - IccDataReader reader = new(data); - return ReadTagData(reader); - } + IccDataReader reader = new(data); + return ReadTagData(reader); + } - private static IccProfileHeader ReadHeader(IccDataReader reader) + private static IccProfileHeader ReadHeader(IccDataReader reader) + { + reader.SetIndex(0); + + return new IccProfileHeader { - reader.SetIndex(0); + Size = reader.ReadUInt32(), + CmmType = reader.ReadAsciiString(4), + Version = reader.ReadVersionNumber(), + Class = (IccProfileClass)reader.ReadUInt32(), + DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(), + ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(), + CreationDate = reader.ReadDateTime(), + FileSignature = reader.ReadAsciiString(4), + PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(), + Flags = (IccProfileFlag)reader.ReadInt32(), + DeviceManufacturer = reader.ReadUInt32(), + DeviceModel = reader.ReadUInt32(), + DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(), + RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(), + PcsIlluminant = reader.ReadXyzNumber(), + CreatorSignature = reader.ReadAsciiString(4), + Id = reader.ReadProfileId(), + }; + } - return new IccProfileHeader - { - Size = reader.ReadUInt32(), - CmmType = reader.ReadAsciiString(4), - Version = reader.ReadVersionNumber(), - Class = (IccProfileClass)reader.ReadUInt32(), - DataColorSpace = (IccColorSpaceType)reader.ReadUInt32(), - ProfileConnectionSpace = (IccColorSpaceType)reader.ReadUInt32(), - CreationDate = reader.ReadDateTime(), - FileSignature = reader.ReadAsciiString(4), - PrimaryPlatformSignature = (IccPrimaryPlatformType)reader.ReadUInt32(), - Flags = (IccProfileFlag)reader.ReadInt32(), - DeviceManufacturer = reader.ReadUInt32(), - DeviceModel = reader.ReadUInt32(), - DeviceAttributes = (IccDeviceAttribute)reader.ReadInt64(), - RenderingIntent = (IccRenderingIntent)reader.ReadUInt32(), - PcsIlluminant = reader.ReadXyzNumber(), - CreatorSignature = reader.ReadAsciiString(4), - Id = reader.ReadProfileId(), - }; - } + private static IccTagDataEntry[] ReadTagData(IccDataReader reader) + { + IccTagTableEntry[] tagTable = ReadTagTable(reader); + List entries = new(tagTable.Length); + Dictionary store = new(); - private static IccTagDataEntry[] ReadTagData(IccDataReader reader) + foreach (IccTagTableEntry tag in tagTable) { - IccTagTableEntry[] tagTable = ReadTagTable(reader); - List entries = new(tagTable.Length); - Dictionary store = new(); - - foreach (IccTagTableEntry tag in tagTable) + IccTagDataEntry entry; + if (store.ContainsKey(tag.Offset)) { - IccTagDataEntry entry; - if (store.ContainsKey(tag.Offset)) + entry = store[tag.Offset]; + } + else + { + try { - entry = store[tag.Offset]; + entry = reader.ReadTagDataEntry(tag); } - else + catch { - try - { - entry = reader.ReadTagDataEntry(tag); - } - catch - { - // Ignore tags that could not be read - continue; - } - - store.Add(tag.Offset, entry); + // Ignore tags that could not be read + continue; } - entry.TagSignature = tag.Signature; - entries.Add(entry); + store.Add(tag.Offset, entry); } - return entries.ToArray(); + entry.TagSignature = tag.Signature; + entries.Add(entry); } - private static IccTagTableEntry[] ReadTagTable(IccDataReader reader) - { - reader.SetIndex(128); // An ICC header is 128 bytes long + return entries.ToArray(); + } - uint tagCount = reader.ReadUInt32(); + private static IccTagTableEntry[] ReadTagTable(IccDataReader reader) + { + reader.SetIndex(128); // An ICC header is 128 bytes long - // Prevent creating huge arrays because of corrupt profiles. - // A normal profile usually has 5-15 entries - if (tagCount > 100) - { - return Array.Empty(); - } + uint tagCount = reader.ReadUInt32(); - List table = new((int)tagCount); - for (int i = 0; i < tagCount; i++) - { - uint tagSignature = reader.ReadUInt32(); - uint tagOffset = reader.ReadUInt32(); - uint tagSize = reader.ReadUInt32(); + // Prevent creating huge arrays because of corrupt profiles. + // A normal profile usually has 5-15 entries + if (tagCount > 100) + { + return Array.Empty(); + } - // Exclude entries that have nonsense values and could cause exceptions further on - if (tagOffset < reader.DataLength && tagSize < reader.DataLength - 128) - { - table.Add(new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize)); - } - } + List table = new((int)tagCount); + for (int i = 0; i < tagCount; i++) + { + uint tagSignature = reader.ReadUInt32(); + uint tagOffset = reader.ReadUInt32(); + uint tagSize = reader.ReadUInt32(); - return table.ToArray(); + // Exclude entries that have nonsense values and could cause exceptions further on + if (tagOffset < reader.DataLength && tagSize < reader.DataLength - 128) + { + table.Add(new IccTagTableEntry((IccProfileTag)tagSignature, tagOffset, tagSize)); + } } + + return table.ToArray(); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs index eac2ab4280..56d620ec32 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The data of an ICC tag entry +/// +public abstract class IccTagDataEntry : IEquatable { /// - /// The data of an ICC tag entry + /// Initializes a new instance of the class. + /// TagSignature will be /// - public abstract class IccTagDataEntry : IEquatable + /// Type Signature + protected IccTagDataEntry(IccTypeSignature signature) + : this(signature, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// TagSignature will be - /// - /// Type Signature - protected IccTagDataEntry(IccTypeSignature signature) - : this(signature, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Type Signature - /// Tag Signature - protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature) - { - this.Signature = signature; - this.TagSignature = tagSignature; - } + /// + /// Initializes a new instance of the class. + /// + /// Type Signature + /// Tag Signature + protected IccTagDataEntry(IccTypeSignature signature, IccProfileTag tagSignature) + { + this.Signature = signature; + this.TagSignature = tagSignature; + } + + /// + /// Gets the type Signature + /// + public IccTypeSignature Signature { get; } - /// - /// Gets the type Signature - /// - public IccTypeSignature Signature { get; } + /// + /// Gets or sets the tag Signature + /// + public IccProfileTag TagSignature { get; set; } - /// - /// Gets or sets the tag Signature - /// - public IccProfileTag TagSignature { get; set; } + /// + public override bool Equals(object obj) + { + return obj is IccTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(object obj) + /// + public virtual bool Equals(IccTagDataEntry other) + { + if (other is null) { - return obj is IccTagDataEntry entry && this.Equals(entry); + return false; } - /// - public virtual bool Equals(IccTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Signature == other.Signature; + return true; } - /// - public override int GetHashCode() => this.Signature.GetHashCode(); + return this.Signature == other.Signature; } + + /// + public override int GetHashCode() => this.Signature.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs index 4bf8b5eb93..5e73e2dd00 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs @@ -1,90 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Contains methods for writing ICC profiles. +/// +internal sealed class IccWriter { /// - /// Contains methods for writing ICC profiles. + /// Writes the ICC profile into a byte array /// - internal sealed class IccWriter + /// The ICC profile to write + /// The ICC profile as a byte array + public static byte[] Write(IccProfile profile) { - /// - /// Writes the ICC profile into a byte array - /// - /// The ICC profile to write - /// The ICC profile as a byte array - public static byte[] Write(IccProfile profile) - { - Guard.NotNull(profile, nameof(profile)); + Guard.NotNull(profile, nameof(profile)); - using IccDataWriter writer = new(); - IccTagTableEntry[] tagTable = WriteTagData(writer, profile.Entries); - WriteTagTable(writer, tagTable); - WriteHeader(writer, profile.Header); - return writer.GetData(); - } + using IccDataWriter writer = new(); + IccTagTableEntry[] tagTable = WriteTagData(writer, profile.Entries); + WriteTagTable(writer, tagTable); + WriteHeader(writer, profile.Header); + return writer.GetData(); + } - private static void WriteHeader(IccDataWriter writer, IccProfileHeader header) - { - writer.SetIndex(0); + private static void WriteHeader(IccDataWriter writer, IccProfileHeader header) + { + writer.SetIndex(0); - writer.WriteUInt32(writer.Length); - writer.WriteAsciiString(header.CmmType, 4, false); - writer.WriteVersionNumber(header.Version); - writer.WriteUInt32((uint)header.Class); - writer.WriteUInt32((uint)header.DataColorSpace); - writer.WriteUInt32((uint)header.ProfileConnectionSpace); - writer.WriteDateTime(header.CreationDate); - writer.WriteAsciiString("acsp"); - writer.WriteUInt32((uint)header.PrimaryPlatformSignature); - writer.WriteInt32((int)header.Flags); - writer.WriteUInt32(header.DeviceManufacturer); - writer.WriteUInt32(header.DeviceModel); - writer.WriteInt64((long)header.DeviceAttributes); - writer.WriteUInt32((uint)header.RenderingIntent); - writer.WriteXyzNumber(header.PcsIlluminant); - writer.WriteAsciiString(header.CreatorSignature, 4, false); + writer.WriteUInt32(writer.Length); + writer.WriteAsciiString(header.CmmType, 4, false); + writer.WriteVersionNumber(header.Version); + writer.WriteUInt32((uint)header.Class); + writer.WriteUInt32((uint)header.DataColorSpace); + writer.WriteUInt32((uint)header.ProfileConnectionSpace); + writer.WriteDateTime(header.CreationDate); + writer.WriteAsciiString("acsp"); + writer.WriteUInt32((uint)header.PrimaryPlatformSignature); + writer.WriteInt32((int)header.Flags); + writer.WriteUInt32(header.DeviceManufacturer); + writer.WriteUInt32(header.DeviceModel); + writer.WriteInt64((long)header.DeviceAttributes); + writer.WriteUInt32((uint)header.RenderingIntent); + writer.WriteXyzNumber(header.PcsIlluminant); + writer.WriteAsciiString(header.CreatorSignature, 4, false); - IccProfileId id = IccProfile.CalculateHash(writer.GetData()); - writer.WriteProfileId(id); - } + IccProfileId id = IccProfile.CalculateHash(writer.GetData()); + writer.WriteProfileId(id); + } - private static void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) - { - // 128 = size of ICC header - writer.SetIndex(128); + private static void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) + { + // 128 = size of ICC header + writer.SetIndex(128); - writer.WriteUInt32((uint)table.Length); - foreach (IccTagTableEntry entry in table) - { - writer.WriteUInt32((uint)entry.Signature); - writer.WriteUInt32(entry.Offset); - writer.WriteUInt32(entry.DataSize); - } + writer.WriteUInt32((uint)table.Length); + foreach (IccTagTableEntry entry in table) + { + writer.WriteUInt32((uint)entry.Signature); + writer.WriteUInt32(entry.Offset); + writer.WriteUInt32(entry.DataSize); } + } - private static IccTagTableEntry[] WriteTagData(IccDataWriter writer, IccTagDataEntry[] entries) - { - // TODO: Investigate cost of Linq GroupBy - IEnumerable> grouped = entries.GroupBy(t => t); + private static IccTagTableEntry[] WriteTagData(IccDataWriter writer, IccTagDataEntry[] entries) + { + // TODO: Investigate cost of Linq GroupBy + IEnumerable> grouped = entries.GroupBy(t => t); - // (Header size) + (entry count) + (nr of entries) * (size of table entry) - writer.SetIndex(128 + 4 + (entries.Length * 12)); + // (Header size) + (entry count) + (nr of entries) * (size of table entry) + writer.SetIndex(128 + 4 + (entries.Length * 12)); - List table = new(); - foreach (IGrouping group in grouped) + List table = new(); + foreach (IGrouping group in grouped) + { + writer.WriteTagDataEntry(group.Key, out IccTagTableEntry tableEntry); + foreach (IccTagDataEntry item in group) { - writer.WriteTagDataEntry(group.Key, out IccTagTableEntry tableEntry); - foreach (IccTagDataEntry item in group) - { - table.Add(new IccTagTableEntry(item.TagSignature, tableEntry.Offset, tableEntry.DataSize)); - } + table.Add(new IccTagTableEntry(item.TagSignature, tableEntry.Offset, tableEntry.DataSize)); } - - return table.ToArray(); } + + return table.ToArray(); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs index ef2e6eae0a..847f881aed 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccBAcsProcessElement.cs @@ -1,32 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A placeholder (might be used for future ICC versions) +/// +internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable { /// - /// A placeholder (might be used for future ICC versions) + /// Initializes a new instance of the class. /// - internal sealed class IccBAcsProcessElement : IccMultiProcessElement, IEquatable + /// Number of input channels + /// Number of output channels + public IccBAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount) { - /// - /// Initializes a new instance of the class. - /// - /// Number of input channels - /// Number of output channels - public IccBAcsProcessElement(int inChannelCount, int outChannelCount) - : base(IccMultiProcessElementSignature.BAcs, inChannelCount, outChannelCount) - { - } + } - /// - public bool Equals(IccBAcsProcessElement other) => base.Equals(other); + /// + public bool Equals(IccBAcsProcessElement other) => base.Equals(other); - /// - public override bool Equals(object obj) => this.Equals(obj as IccBAcsProcessElement); + /// + public override bool Equals(object obj) => this.Equals(obj as IccBAcsProcessElement); - /// - public override int GetHashCode() => base.GetHashCode(); - } + /// + public override int GetHashCode() => base.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs index 00183b6e85..1e8dd3d948 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccClutProcessElement.cs @@ -1,46 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A CLUT (color lookup table) element to process data +/// +internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable { /// - /// A CLUT (color lookup table) element to process data + /// Initializes a new instance of the class. /// - internal sealed class IccClutProcessElement : IccMultiProcessElement, IEquatable + /// The color lookup table of this element + public IccClutProcessElement(IccClut clutValue) + : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1) + => this.ClutValue = clutValue ?? throw new ArgumentNullException(nameof(clutValue)); + + /// + /// Gets the color lookup table of this element + /// + public IccClut ClutValue { get; } + + /// + public override bool Equals(IccMultiProcessElement other) { - /// - /// Initializes a new instance of the class. - /// - /// The color lookup table of this element - public IccClutProcessElement(IccClut clutValue) - : base(IccMultiProcessElementSignature.Clut, clutValue?.InputChannelCount ?? 1, clutValue?.OutputChannelCount ?? 1) - => this.ClutValue = clutValue ?? throw new ArgumentNullException(nameof(clutValue)); - - /// - /// Gets the color lookup table of this element - /// - public IccClut ClutValue { get; } - - /// - public override bool Equals(IccMultiProcessElement other) + if (base.Equals(other) && other is IccClutProcessElement element) { - if (base.Equals(other) && other is IccClutProcessElement element) - { - return this.ClutValue.Equals(element.ClutValue); - } - - return false; + return this.ClutValue.Equals(element.ClutValue); } - /// - public bool Equals(IccClutProcessElement other) => this.Equals((IccMultiProcessElement)other); + return false; + } + + /// + public bool Equals(IccClutProcessElement other) => this.Equals((IccMultiProcessElement)other); - /// - public override bool Equals(object obj) => this.Equals(obj as IccClutProcessElement); + /// + public override bool Equals(object obj) => this.Equals(obj as IccClutProcessElement); - /// - public override int GetHashCode() => base.GetHashCode(); - } + /// + public override int GetHashCode() => base.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs index 21b372ffa6..ee6b0445e3 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccCurveSetProcessElement.cs @@ -1,46 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A set of curves to process data +/// +internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable { /// - /// A set of curves to process data + /// Initializes a new instance of the class. /// - internal sealed class IccCurveSetProcessElement : IccMultiProcessElement, IEquatable + /// An array with one dimensional curves + public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves) + : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1) + => this.Curves = curves ?? throw new ArgumentNullException(nameof(curves)); + + /// + /// Gets an array of one dimensional curves + /// + public IccOneDimensionalCurve[] Curves { get; } + + /// + public override bool Equals(IccMultiProcessElement other) { - /// - /// Initializes a new instance of the class. - /// - /// An array with one dimensional curves - public IccCurveSetProcessElement(IccOneDimensionalCurve[] curves) - : base(IccMultiProcessElementSignature.CurveSet, curves?.Length ?? 1, curves?.Length ?? 1) - => this.Curves = curves ?? throw new ArgumentNullException(nameof(curves)); - - /// - /// Gets an array of one dimensional curves - /// - public IccOneDimensionalCurve[] Curves { get; } - - /// - public override bool Equals(IccMultiProcessElement other) + if (base.Equals(other) && other is IccCurveSetProcessElement element) { - if (base.Equals(other) && other is IccCurveSetProcessElement element) - { - return this.Curves.AsSpan().SequenceEqual(element.Curves); - } - - return false; + return this.Curves.AsSpan().SequenceEqual(element.Curves); } - /// - public bool Equals(IccCurveSetProcessElement other) => this.Equals((IccMultiProcessElement)other); + return false; + } + + /// + public bool Equals(IccCurveSetProcessElement other) => this.Equals((IccMultiProcessElement)other); - /// - public override bool Equals(object obj) => this.Equals(obj as IccCurveSetProcessElement); + /// + public override bool Equals(object obj) => this.Equals(obj as IccCurveSetProcessElement); - /// - public override int GetHashCode() => base.GetHashCode(); - } + /// + public override int GetHashCode() => base.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs index 5ce56bd11e..f7dbf25930 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccEAcsProcessElement.cs @@ -1,31 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A placeholder (might be used for future ICC versions) +/// +internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable { /// - /// A placeholder (might be used for future ICC versions) + /// Initializes a new instance of the class. /// - internal sealed class IccEAcsProcessElement : IccMultiProcessElement, IEquatable + /// Number of input channels + /// Number of output channels + public IccEAcsProcessElement(int inChannelCount, int outChannelCount) + : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount) { - /// - /// Initializes a new instance of the class. - /// - /// Number of input channels - /// Number of output channels - public IccEAcsProcessElement(int inChannelCount, int outChannelCount) - : base(IccMultiProcessElementSignature.EAcs, inChannelCount, outChannelCount) - { - } + } - /// - public bool Equals(IccEAcsProcessElement other) => base.Equals(other); + /// + public bool Equals(IccEAcsProcessElement other) => base.Equals(other); - public override bool Equals(object obj) => this.Equals(obj as IccEAcsProcessElement); + public override bool Equals(object obj) => this.Equals(obj as IccEAcsProcessElement); - /// - public override int GetHashCode() => base.GetHashCode(); - } + /// + public override int GetHashCode() => base.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs index c448a71cd7..8d973e5f02 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMatrixProcessElement.cs @@ -1,68 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A matrix element to process data +/// +internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable { /// - /// A matrix element to process data + /// Initializes a new instance of the class. /// - internal sealed class IccMatrixProcessElement : IccMultiProcessElement, IEquatable + /// Two dimensional matrix with size of Input-Channels x Output-Channels + /// One dimensional matrix with size of Output-Channels x 1 + public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) + : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) { - /// - /// Initializes a new instance of the class. - /// - /// Two dimensional matrix with size of Input-Channels x Output-Channels - /// One dimensional matrix with size of Output-Channels x 1 - public IccMatrixProcessElement(float[,] matrixIxO, float[] matrixOx1) - : base(IccMultiProcessElementSignature.Matrix, matrixIxO?.GetLength(0) ?? 1, matrixIxO?.GetLength(1) ?? 1) - { - Guard.NotNull(matrixIxO, nameof(matrixIxO)); - Guard.NotNull(matrixOx1, nameof(matrixOx1)); + Guard.NotNull(matrixIxO, nameof(matrixIxO)); + Guard.NotNull(matrixOx1, nameof(matrixOx1)); - bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; - Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); + bool matrixSizeCorrect = matrixIxO.GetLength(1) == matrixOx1.Length; + Guard.IsTrue(matrixSizeCorrect, $"{nameof(matrixIxO)},{nameof(matrixIxO)}", "Output channel length must match"); - this.MatrixIxO = matrixIxO; - this.MatrixOx1 = matrixOx1; - } + this.MatrixIxO = matrixIxO; + this.MatrixOx1 = matrixOx1; + } - /// - /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels - /// - public DenseMatrix MatrixIxO { get; } + /// + /// Gets the two dimensional matrix with size of Input-Channels x Output-Channels + /// + public DenseMatrix MatrixIxO { get; } - /// - /// Gets the one dimensional matrix with size of Output-Channels x 1 - /// - public float[] MatrixOx1 { get; } + /// + /// Gets the one dimensional matrix with size of Output-Channels x 1 + /// + public float[] MatrixOx1 { get; } - /// - public override bool Equals(IccMultiProcessElement other) + /// + public override bool Equals(IccMultiProcessElement other) + { + if (base.Equals(other) && other is IccMatrixProcessElement element) { - if (base.Equals(other) && other is IccMatrixProcessElement element) - { - return this.EqualsMatrix(element) - && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); - } - - return false; + return this.EqualsMatrix(element) + && this.MatrixOx1.AsSpan().SequenceEqual(element.MatrixOx1); } - /// - public bool Equals(IccMatrixProcessElement other) - => this.Equals((IccMultiProcessElement)other); + return false; + } - /// - public override bool Equals(object obj) - => this.Equals(obj as IccMatrixProcessElement); + /// + public bool Equals(IccMatrixProcessElement other) + => this.Equals((IccMultiProcessElement)other); - /// - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.MatrixIxO, this.MatrixOx1); + /// + public override bool Equals(object obj) + => this.Equals(obj as IccMatrixProcessElement); - private bool EqualsMatrix(IccMatrixProcessElement element) - => this.MatrixIxO.Equals(element.MatrixIxO); - } + /// + public override int GetHashCode() + => HashCode.Combine(base.GetHashCode(), this.MatrixIxO, this.MatrixOx1); + + private bool EqualsMatrix(IccMatrixProcessElement element) + => this.MatrixIxO.Equals(element.MatrixIxO); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs index 84d399459c..57a0536fd1 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/MultiProcessElements/IccMultiProcessElement.cs @@ -1,68 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// An element to process data +/// +internal abstract class IccMultiProcessElement : IEquatable { /// - /// An element to process data + /// Initializes a new instance of the class. /// - internal abstract class IccMultiProcessElement : IEquatable + /// The signature of this element + /// Number of input channels + /// Number of output channels + protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount) { - /// - /// Initializes a new instance of the class. - /// - /// The signature of this element - /// Number of input channels - /// Number of output channels - protected IccMultiProcessElement(IccMultiProcessElementSignature signature, int inChannelCount, int outChannelCount) - { - Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount)); - Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount)); + Guard.MustBeBetweenOrEqualTo(inChannelCount, 1, 15, nameof(inChannelCount)); + Guard.MustBeBetweenOrEqualTo(outChannelCount, 1, 15, nameof(outChannelCount)); - this.Signature = signature; - this.InputChannelCount = inChannelCount; - this.OutputChannelCount = outChannelCount; - } + this.Signature = signature; + this.InputChannelCount = inChannelCount; + this.OutputChannelCount = outChannelCount; + } - /// - /// Gets the signature of this element, - /// - public IccMultiProcessElementSignature Signature { get; } + /// + /// Gets the signature of this element, + /// + public IccMultiProcessElementSignature Signature { get; } - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } - /// - /// Gets the number of output channels. - /// - public int OutputChannelCount { get; } + /// + /// Gets the number of output channels. + /// + public int OutputChannelCount { get; } - /// - public virtual bool Equals(IccMultiProcessElement other) + /// + public virtual bool Equals(IccMultiProcessElement other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return this.Signature == other.Signature - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount; + return false; } - public override bool Equals(object obj) => this.Equals(obj as IccMultiProcessElement); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() - => HashCode.Combine(this.Signature, this.InputChannelCount, this.OutputChannelCount); + return this.Signature == other.Signature + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount; } + + public override bool Equals(object obj) => this.Equals(obj as IccMultiProcessElement); + + /// + public override int GetHashCode() + => HashCode.Combine(this.Signature, this.InputChannelCount, this.OutputChannelCount); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs index e98400cb11..aa90213a82 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -1,168 +1,164 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The chromaticity tag type provides basic chromaticity data +/// and type of phosphors or colorants of a monitor to applications and utilities. +/// +internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The chromaticity tag type provides basic chromaticity data - /// and type of phosphors or colorants of a monitor to applications and utilities. + /// Initializes a new instance of the class. /// - internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable + /// Colorant Type + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType) + : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Colorant Type - public IccChromaticityTagDataEntry(IccColorantEncoding colorantType) - : this(colorantType, GetColorantArray(colorantType), IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Values per channel - public IccChromaticityTagDataEntry(double[][] channelValues) - : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + public IccChromaticityTagDataEntry(double[][] channelValues) + : this(IccColorantEncoding.Unknown, channelValues, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Colorant Type - /// Tag Signature - public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature) - : this(colorantType, GetColorantArray(colorantType), tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Colorant Type + /// Tag Signature + public IccChromaticityTagDataEntry(IccColorantEncoding colorantType, IccProfileTag tagSignature) + : this(colorantType, GetColorantArray(colorantType), tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Values per channel - /// Tag Signature - public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature) - : this(IccColorantEncoding.Unknown, channelValues, tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Values per channel + /// Tag Signature + public IccChromaticityTagDataEntry(double[][] channelValues, IccProfileTag tagSignature) + : this(IccColorantEncoding.Unknown, channelValues, tagSignature) + { + } - private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Chromaticity, tagSignature) - { - Guard.NotNull(channelValues, nameof(channelValues)); - Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues)); + private IccChromaticityTagDataEntry(IccColorantEncoding colorantType, double[][] channelValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Chromaticity, tagSignature) + { + Guard.NotNull(channelValues, nameof(channelValues)); + Guard.MustBeBetweenOrEqualTo(channelValues.Length, 1, 15, nameof(channelValues)); - this.ColorantType = colorantType; - this.ChannelValues = channelValues; + this.ColorantType = colorantType; + this.ChannelValues = channelValues; - int channelLength = channelValues[0].Length; - bool channelsNotSame = channelValues.Any(t => t is null || t.Length != channelLength); - Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels"); - } + int channelLength = channelValues[0].Length; + bool channelsNotSame = channelValues.Any(t => t is null || t.Length != channelLength); + Guard.IsFalse(channelsNotSame, nameof(channelValues), "The number of values per channel is not the same for all channels"); + } - /// - /// Gets the number of channels - /// - public int ChannelCount => this.ChannelValues.Length; + /// + /// Gets the number of channels + /// + public int ChannelCount => this.ChannelValues.Length; - /// - /// Gets the colorant type - /// - public IccColorantEncoding ColorantType { get; } + /// + /// Gets the colorant type + /// + public IccColorantEncoding ColorantType { get; } + + /// + /// Gets the values per channel + /// + public double[][] ChannelValues { get; } - /// - /// Gets the values per channel - /// - public double[][] ChannelValues { get; } + /// + public override bool Equals(IccTagDataEntry other) => other is IccChromaticityTagDataEntry entry && this.Equals(entry); - /// - public override bool Equals(IccTagDataEntry other) => other is IccChromaticityTagDataEntry entry && this.Equals(entry); + /// + public bool Equals(IccChromaticityTagDataEntry other) + { + if (other is null) + { + return false; + } - /// - public bool Equals(IccChromaticityTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } + return true; + } - if (ReferenceEquals(this, other)) - { - return true; - } + return base.Equals(other) && this.ColorantType == other.ColorantType && this.EqualsChannelValues(other); + } - return base.Equals(other) && this.ColorantType == other.ColorantType && this.EqualsChannelValues(other); - } + /// + public override bool Equals(object obj) => obj is IccChromaticityTagDataEntry other && this.Equals(other); - /// - public override bool Equals(object obj) => obj is IccChromaticityTagDataEntry other && this.Equals(other); + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.ColorantType, + this.ChannelValues); + } - /// - public override int GetHashCode() + private static double[][] GetColorantArray(IccColorantEncoding colorantType) + { + switch (colorantType) { - return HashCode.Combine( - this.Signature, - this.ColorantType, - this.ChannelValues); + case IccColorantEncoding.EbuTech3213E: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.290, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.ItuRBt709_2: + return new[] + { + new[] { 0.640, 0.330 }, + new[] { 0.300, 0.600 }, + new[] { 0.150, 0.060 }, + }; + case IccColorantEncoding.P22: + return new[] + { + new[] { 0.625, 0.340 }, + new[] { 0.280, 0.605 }, + new[] { 0.155, 0.070 }, + }; + case IccColorantEncoding.SmpteRp145: + return new[] + { + new[] { 0.630, 0.340 }, + new[] { 0.310, 0.595 }, + new[] { 0.155, 0.070 }, + }; + default: + throw new ArgumentException("Unrecognized colorant encoding"); } + } - private static double[][] GetColorantArray(IccColorantEncoding colorantType) + private bool EqualsChannelValues(IccChromaticityTagDataEntry entry) + { + if (this.ChannelValues.Length != entry.ChannelValues.Length) { - switch (colorantType) - { - case IccColorantEncoding.EbuTech3213E: - return new[] - { - new[] { 0.640, 0.330 }, - new[] { 0.290, 0.600 }, - new[] { 0.150, 0.060 }, - }; - case IccColorantEncoding.ItuRBt709_2: - return new[] - { - new[] { 0.640, 0.330 }, - new[] { 0.300, 0.600 }, - new[] { 0.150, 0.060 }, - }; - case IccColorantEncoding.P22: - return new[] - { - new[] { 0.625, 0.340 }, - new[] { 0.280, 0.605 }, - new[] { 0.155, 0.070 }, - }; - case IccColorantEncoding.SmpteRp145: - return new[] - { - new[] { 0.630, 0.340 }, - new[] { 0.310, 0.595 }, - new[] { 0.155, 0.070 }, - }; - default: - throw new ArgumentException("Unrecognized colorant encoding"); - } + return false; } - private bool EqualsChannelValues(IccChromaticityTagDataEntry entry) + for (int i = 0; i < this.ChannelValues.Length; i++) { - if (this.ChannelValues.Length != entry.ChannelValues.Length) + if (!this.ChannelValues[i].AsSpan().SequenceEqual(entry.ChannelValues[i])) { return false; } - - for (int i = 0; i < this.ChannelValues.Length; i++) - { - if (!this.ChannelValues[i].AsSpan().SequenceEqual(entry.ChannelValues[i])) - { - return false; - } - } - - return true; } + + return true; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs index e551659855..6f2a4480a9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantOrderTagDataEntry.cs @@ -1,76 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This tag specifies the laydown order in which colorants +/// will be printed on an n-colorant device. +/// +internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This tag specifies the laydown order in which colorants - /// will be printed on an n-colorant device. + /// Initializes a new instance of the class. /// - internal sealed class IccColorantOrderTagDataEntry : IccTagDataEntry, IEquatable + /// Colorant order numbers + public IccColorantOrderTagDataEntry(byte[] colorantNumber) + : this(colorantNumber, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Colorant order numbers - public IccColorantOrderTagDataEntry(byte[] colorantNumber) - : this(colorantNumber, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Colorant order numbers - /// Tag Signature - public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature) - : base(IccTypeSignature.ColorantOrder, tagSignature) - { - Guard.NotNull(colorantNumber, nameof(colorantNumber)); - Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber)); + /// + /// Initializes a new instance of the class. + /// + /// Colorant order numbers + /// Tag Signature + public IccColorantOrderTagDataEntry(byte[] colorantNumber, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantOrder, tagSignature) + { + Guard.NotNull(colorantNumber, nameof(colorantNumber)); + Guard.MustBeBetweenOrEqualTo(colorantNumber.Length, 1, 15, nameof(colorantNumber)); - this.ColorantNumber = colorantNumber; - } + this.ColorantNumber = colorantNumber; + } - /// - /// Gets the colorant order numbers - /// - public byte[] ColorantNumber { get; } + /// + /// Gets the colorant order numbers + /// + public byte[] ColorantNumber { get; } - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccColorantOrderTagDataEntry entry && this.Equals(entry); - } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccColorantOrderTagDataEntry entry && this.Equals(entry); + } - /// - public bool Equals(IccColorantOrderTagDataEntry other) + /// + public bool Equals(IccColorantOrderTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.ColorantNumber.AsSpan().SequenceEqual(other.ColorantNumber); + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccColorantOrderTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.ColorantNumber); - } + return base.Equals(other) && this.ColorantNumber.AsSpan().SequenceEqual(other.ColorantNumber); + } + + /// + public override bool Equals(object obj) + { + return obj is IccColorantOrderTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.ColorantNumber); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs index 43ca9cdd31..0c64cfc67c 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccColorantTableTagDataEntry.cs @@ -1,68 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The purpose of this tag is to identify the colorants used in +/// the profile by a unique name and set of PCSXYZ or PCSLAB values +/// to give the colorant an unambiguous value. +/// +internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The purpose of this tag is to identify the colorants used in - /// the profile by a unique name and set of PCSXYZ or PCSLAB values - /// to give the colorant an unambiguous value. + /// Initializes a new instance of the class. /// - internal sealed class IccColorantTableTagDataEntry : IccTagDataEntry, IEquatable + /// Colorant Data + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData) + : this(colorantData, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Colorant Data - public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData) - : this(colorantData, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Colorant Data - /// Tag Signature - public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature) - : base(IccTypeSignature.ColorantTable, tagSignature) - { - Guard.NotNull(colorantData, nameof(colorantData)); - Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData)); + /// + /// Initializes a new instance of the class. + /// + /// Colorant Data + /// Tag Signature + public IccColorantTableTagDataEntry(IccColorantTableEntry[] colorantData, IccProfileTag tagSignature) + : base(IccTypeSignature.ColorantTable, tagSignature) + { + Guard.NotNull(colorantData, nameof(colorantData)); + Guard.MustBeBetweenOrEqualTo(colorantData.Length, 1, 15, nameof(colorantData)); - this.ColorantData = colorantData; - } + this.ColorantData = colorantData; + } - /// - /// Gets the colorant data - /// - public IccColorantTableEntry[] ColorantData { get; } + /// + /// Gets the colorant data + /// + public IccColorantTableEntry[] ColorantData { get; } - /// - public override bool Equals(IccTagDataEntry other) => other is IccColorantTableTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) => other is IccColorantTableTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccColorantTableTagDataEntry other) + /// + public bool Equals(IccColorantTableTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.ColorantData.AsSpan().SequenceEqual(other.ColorantData); + return false; } - /// - public override bool Equals(object obj) => obj is IccColorantTableTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.ColorantData); + return base.Equals(other) && this.ColorantData.AsSpan().SequenceEqual(other.ColorantData); } + + /// + public override bool Equals(object obj) => obj is IccColorantTableTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.ColorantData); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs index 8b40dd7832..403c5c57c7 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCrdInfoTagDataEntry.cs @@ -1,127 +1,124 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type contains the PostScript product name to which this profile +/// corresponds and the names of the companion CRDs +/// +internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type contains the PostScript product name to which this profile - /// corresponds and the names of the companion CRDs + /// Initializes a new instance of the class. /// - internal sealed class IccCrdInfoTagDataEntry : IccTagDataEntry, IEquatable + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd) + : this( + postScriptProductName, + renderingIntent0Crd, + renderingIntent1Crd, + renderingIntent2Crd, + renderingIntent3Crd, + IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// the PostScript product name - /// the rendering intent 0 CRD name - /// the rendering intent 1 CRD name - /// the rendering intent 2 CRD name - /// the rendering intent 3 CRD name - public IccCrdInfoTagDataEntry( - string postScriptProductName, - string renderingIntent0Crd, - string renderingIntent1Crd, - string renderingIntent2Crd, - string renderingIntent3Crd) - : this( - postScriptProductName, - renderingIntent0Crd, - renderingIntent1Crd, - renderingIntent2Crd, - renderingIntent3Crd, - IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// the PostScript product name - /// the rendering intent 0 CRD name - /// the rendering intent 1 CRD name - /// the rendering intent 2 CRD name - /// the rendering intent 3 CRD name - /// Tag Signature - public IccCrdInfoTagDataEntry( - string postScriptProductName, - string renderingIntent0Crd, - string renderingIntent1Crd, - string renderingIntent2Crd, - string renderingIntent3Crd, - IccProfileTag tagSignature) - : base(IccTypeSignature.CrdInfo, tagSignature) - { - this.PostScriptProductName = postScriptProductName; - this.RenderingIntent0Crd = renderingIntent0Crd; - this.RenderingIntent1Crd = renderingIntent1Crd; - this.RenderingIntent2Crd = renderingIntent2Crd; - this.RenderingIntent3Crd = renderingIntent3Crd; - } + /// + /// Initializes a new instance of the class. + /// + /// the PostScript product name + /// the rendering intent 0 CRD name + /// the rendering intent 1 CRD name + /// the rendering intent 2 CRD name + /// the rendering intent 3 CRD name + /// Tag Signature + public IccCrdInfoTagDataEntry( + string postScriptProductName, + string renderingIntent0Crd, + string renderingIntent1Crd, + string renderingIntent2Crd, + string renderingIntent3Crd, + IccProfileTag tagSignature) + : base(IccTypeSignature.CrdInfo, tagSignature) + { + this.PostScriptProductName = postScriptProductName; + this.RenderingIntent0Crd = renderingIntent0Crd; + this.RenderingIntent1Crd = renderingIntent1Crd; + this.RenderingIntent2Crd = renderingIntent2Crd; + this.RenderingIntent3Crd = renderingIntent3Crd; + } - /// - /// Gets the PostScript product name - /// - public string PostScriptProductName { get; } + /// + /// Gets the PostScript product name + /// + public string PostScriptProductName { get; } - /// - /// Gets the rendering intent 0 CRD name - /// - public string RenderingIntent0Crd { get; } + /// + /// Gets the rendering intent 0 CRD name + /// + public string RenderingIntent0Crd { get; } - /// - /// Gets the rendering intent 1 CRD name - /// - public string RenderingIntent1Crd { get; } + /// + /// Gets the rendering intent 1 CRD name + /// + public string RenderingIntent1Crd { get; } - /// - /// Gets the rendering intent 2 CRD name - /// - public string RenderingIntent2Crd { get; } + /// + /// Gets the rendering intent 2 CRD name + /// + public string RenderingIntent2Crd { get; } - /// - /// Gets the rendering intent 3 CRD name - /// - public string RenderingIntent3Crd { get; } + /// + /// Gets the rendering intent 3 CRD name + /// + public string RenderingIntent3Crd { get; } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccCrdInfoTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccCrdInfoTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccCrdInfoTagDataEntry other) + /// + public bool Equals(IccCrdInfoTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && string.Equals(this.PostScriptProductName, other.PostScriptProductName, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent0Crd, other.RenderingIntent0Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent1Crd, other.RenderingIntent1Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent2Crd, other.RenderingIntent2Crd, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.RenderingIntent3Crd, other.RenderingIntent3Crd, StringComparison.OrdinalIgnoreCase); + return false; } - /// - public override bool Equals(object obj) - => obj is IccCrdInfoTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.PostScriptProductName, - this.RenderingIntent0Crd, - this.RenderingIntent1Crd, - this.RenderingIntent2Crd, - this.RenderingIntent3Crd); + return base.Equals(other) + && string.Equals(this.PostScriptProductName, other.PostScriptProductName, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.RenderingIntent0Crd, other.RenderingIntent0Crd, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.RenderingIntent1Crd, other.RenderingIntent1Crd, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.RenderingIntent2Crd, other.RenderingIntent2Crd, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.RenderingIntent3Crd, other.RenderingIntent3Crd, StringComparison.OrdinalIgnoreCase); } + + /// + public override bool Equals(object obj) + => obj is IccCrdInfoTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.PostScriptProductName, + this.RenderingIntent0Crd, + this.RenderingIntent1Crd, + this.RenderingIntent2Crd, + this.RenderingIntent3Crd); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs index 9ff4cc2964..75f4cd52d3 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccCurveTagDataEntry.cs @@ -1,121 +1,118 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The type contains a one-dimensional table of double values. +/// +internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The type contains a one-dimensional table of double values. + /// Initializes a new instance of the class. /// - internal sealed class IccCurveTagDataEntry : IccTagDataEntry, IEquatable + public IccCurveTagDataEntry() + : this(Array.Empty(), IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - public IccCurveTagDataEntry() - : this(Array.Empty(), IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Gamma value - public IccCurveTagDataEntry(float gamma) - : this(new[] { gamma }, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + public IccCurveTagDataEntry(float gamma) + : this(new[] { gamma }, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Curve Data - public IccCurveTagDataEntry(float[] curveData) - : this(curveData, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + public IccCurveTagDataEntry(float[] curveData) + : this(curveData, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Tag Signature - public IccCurveTagDataEntry(IccProfileTag tagSignature) - : this(Array.Empty(), tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Tag Signature + public IccCurveTagDataEntry(IccProfileTag tagSignature) + : this(Array.Empty(), tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Gamma value - /// Tag Signature - public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature) - : this(new[] { gamma }, tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Gamma value + /// Tag Signature + public IccCurveTagDataEntry(float gamma, IccProfileTag tagSignature) + : this(new[] { gamma }, tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Curve Data - /// Tag Signature - public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature) - : base(IccTypeSignature.Curve, tagSignature) - { - this.CurveData = curveData ?? Array.Empty(); - } + /// + /// Initializes a new instance of the class. + /// + /// Curve Data + /// Tag Signature + public IccCurveTagDataEntry(float[] curveData, IccProfileTag tagSignature) + : base(IccTypeSignature.Curve, tagSignature) + { + this.CurveData = curveData ?? Array.Empty(); + } - /// - /// Gets the curve data - /// - public float[] CurveData { get; } - - /// - /// Gets the gamma value. - /// Only valid if is true - /// - public float Gamma => this.IsGamma ? this.CurveData[0] : 0; - - /// - /// Gets a value indicating whether the curve maps input directly to output. - /// - public bool IsIdentityResponse => this.CurveData.Length == 0; - - /// - /// Gets a value indicating whether the curve is a gamma curve. - /// - public bool IsGamma => this.CurveData.Length == 1; - - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccCurveTagDataEntry entry && this.Equals(entry); - } + /// + /// Gets the curve data + /// + public float[] CurveData { get; } - /// - public bool Equals(IccCurveTagDataEntry other) - { - if (other is null) - { - return false; - } + /// + /// Gets the gamma value. + /// Only valid if is true + /// + public float Gamma => this.IsGamma ? this.CurveData[0] : 0; - if (ReferenceEquals(this, other)) - { - return true; - } + /// + /// Gets a value indicating whether the curve maps input directly to output. + /// + public bool IsIdentityResponse => this.CurveData.Length == 0; + + /// + /// Gets a value indicating whether the curve is a gamma curve. + /// + public bool IsGamma => this.CurveData.Length == 1; + + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccCurveTagDataEntry entry && this.Equals(entry); + } - return base.Equals(other) && this.CurveData.AsSpan().SequenceEqual(other.CurveData); + /// + public bool Equals(IccCurveTagDataEntry other) + { + if (other is null) + { + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccCurveTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.CurveData); + return base.Equals(other) && this.CurveData.AsSpan().SequenceEqual(other.CurveData); + } + + /// + public override bool Equals(object obj) + { + return obj is IccCurveTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.CurveData); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs index e5f817e958..89af5286fe 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// The dataType is a simple data structure that contains +/// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes. +/// +internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The dataType is a simple data structure that contains - /// either 7-bit ASCII or binary data, i.e. textType data or transparent bytes. + /// Initializes a new instance of the class. /// - internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable + /// The raw data + public IccDataTagDataEntry(byte[] data) + : this(data, false, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The raw data - public IccDataTagDataEntry(byte[] data) - : this(data, false, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The raw data - /// True if the given data is 7bit ASCII encoded text - public IccDataTagDataEntry(byte[] data, bool isAscii) - : this(data, isAscii, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + public IccDataTagDataEntry(byte[] data, bool isAscii) + : this(data, isAscii, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The raw data - /// True if the given data is 7bit ASCII encoded text - /// Tag Signature - public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature) - : base(IccTypeSignature.Data, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - this.IsAscii = isAscii; - } + /// + /// Initializes a new instance of the class. + /// + /// The raw data + /// True if the given data is 7bit ASCII encoded text + /// Tag Signature + public IccDataTagDataEntry(byte[] data, bool isAscii, IccProfileTag tagSignature) + : base(IccTypeSignature.Data, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + this.IsAscii = isAscii; + } - /// - /// Gets the raw Data - /// - public byte[] Data { get; } + /// + /// Gets the raw Data + /// + public byte[] Data { get; } - /// - /// Gets a value indicating whether the represents 7bit ASCII encoded text - /// - public bool IsAscii { get; } + /// + /// Gets a value indicating whether the represents 7bit ASCII encoded text + /// + public bool IsAscii { get; } - /// - /// Gets the decoded as 7bit ASCII. - /// If is false, returns null - /// - public string AsciiString => this.IsAscii ? Encoding.ASCII.GetString(this.Data, 0, this.Data.Length) : null; + /// + /// Gets the decoded as 7bit ASCII. + /// If is false, returns null + /// + public string AsciiString => this.IsAscii ? Encoding.ASCII.GetString(this.Data, 0, this.Data.Length) : null; - /// - public override bool Equals(IccTagDataEntry other) - => other is IccDataTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccDataTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccDataTagDataEntry other) + /// + public bool Equals(IccDataTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data) && this.IsAscii == other.IsAscii; + return false; } - /// - public override bool Equals(object obj) - => obj is IccDataTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Data, - this.IsAscii); + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data) && this.IsAscii == other.IsAscii; } + + /// + public override bool Equals(object obj) + => obj is IccDataTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.Data, + this.IsAscii); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs index 8e16ed50a4..9763da73c7 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccDateTimeTagDataEntry.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type is a representation of the time and date. +/// +internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type is a representation of the time and date. + /// Initializes a new instance of the class. /// - internal sealed class IccDateTimeTagDataEntry : IccTagDataEntry, IEquatable + /// The DateTime value + public IccDateTimeTagDataEntry(DateTime value) + : this(value, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The DateTime value - public IccDateTimeTagDataEntry(DateTime value) - : this(value, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The DateTime value - /// Tag Signature - public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature) - : base(IccTypeSignature.DateTime, tagSignature) - { - this.Value = value; - } + /// + /// Initializes a new instance of the class. + /// + /// The DateTime value + /// Tag Signature + public IccDateTimeTagDataEntry(DateTime value, IccProfileTag tagSignature) + : base(IccTypeSignature.DateTime, tagSignature) + { + this.Value = value; + } - /// - /// Gets the date and time value - /// - public DateTime Value { get; } + /// + /// Gets the date and time value + /// + public DateTime Value { get; } - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccDateTimeTagDataEntry entry && this.Equals(entry); - } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccDateTimeTagDataEntry entry && this.Equals(entry); + } - /// - public bool Equals(IccDateTimeTagDataEntry other) + /// + public bool Equals(IccDateTimeTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Value.Equals(other.Value); + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccDateTimeTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.Value); - } + return base.Equals(other) && this.Value.Equals(other.Value); + } + + /// + public override bool Equals(object obj) + { + return obj is IccDateTimeTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.Value); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs index da2781c643..8e8918d887 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccFix16ArrayTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of doubles (from 32bit fixed point values). +/// +internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of doubles (from 32bit fixed point values). + /// Initializes a new instance of the class. /// - internal sealed class IccFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccFix16ArrayTagDataEntry(float[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.S15Fixed16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.S15Fixed16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public float[] Data { get; } - /// - /// Gets the array data - /// - public float[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccFix16ArrayTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccFix16ArrayTagDataEntry other) + { + if (other is null) { - return other is IccFix16ArrayTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccFix16ArrayTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccFix16ArrayTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccFix16ArrayTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs index bdd2f23628..aedecfdac3 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut16TagDataEntry.cs @@ -1,170 +1,168 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This structure represents a color transform using tables +/// with 16-bit precision. +/// +internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable { + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + /// - /// This structure represents a color transform using tables - /// with 16-bit precision. + /// Initializes a new instance of the class. /// - internal sealed class IccLut16TagDataEntry : IccTagDataEntry, IEquatable + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) { - private static readonly float[,] IdentityMatrix = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Lut16, tagSignature) - { - Guard.NotNull(matrix, nameof(matrix)); + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut16TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut16, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); - bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); - this.Matrix = CreateMatrix(matrix); - this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); - this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); - this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); + this.Matrix = CreateMatrix(matrix); + this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); + this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); + this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } - /// - /// Gets the number of input channels - /// - public int InputChannelCount => this.InputValues.Length; - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount => this.OutputValues.Length; - - /// - /// Gets the conversion matrix - /// - public Matrix4x4 Matrix { get; } - - /// - /// Gets the input lookup table - /// - public IccLut[] InputValues { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the output lookup table - /// - public IccLut[] OutputValues { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLut16TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLut16TagDataEntry other) - { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Matrix.Equals(other.Matrix) - && this.InputValues.AsSpan().SequenceEqual(other.InputValues) - && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); - } + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } - /// - public override bool Equals(object obj) => obj is IccLut16TagDataEntry other && this.Equals(other); + /// + public override bool Equals(IccTagDataEntry other) => other is IccLut16TagDataEntry entry && this.Equals(entry); - /// - public override int GetHashCode() + /// + public bool Equals(IccLut16TagDataEntry other) + { + if (other is null) { - return HashCode.Combine( - this.Signature, - this.Matrix, - this.InputValues, - this.ClutValues, - this.OutputValues); + return false; } - private static Matrix4x4 CreateMatrix(float[,] matrix) + if (ReferenceEquals(this, other)) { - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); + return true; } + + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.AsSpan().SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); + } + + /// + public override bool Equals(object obj) => obj is IccLut16TagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Matrix, + this.InputValues, + this.ClutValues, + this.OutputValues); + } + + private static Matrix4x4 CreateMatrix(float[,] matrix) + { + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs index aa63c3b350..42e5670db4 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLut8TagDataEntry.cs @@ -1,170 +1,167 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This structure represents a color transform using tables +/// with 8-bit precision. +/// +internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable { + private static readonly float[,] IdentityMatrix = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + /// - /// This structure represents a color transform using tables - /// with 8-bit precision. + /// Initializes a new instance of the class. /// - internal sealed class IccLut8TagDataEntry : IccTagDataEntry, IEquatable + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) { - private static readonly float[,] IdentityMatrix = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 } - }; - - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(IdentityMatrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : this(IdentityMatrix, inputValues, clutValues, outputValues, tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) - : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues) + : this(matrix, inputValues, clutValues, outputValues, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Conversion matrix (must be 3x3) - /// Input LUT - /// CLUT - /// Output LUT - /// Tag Signature - public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) - : base(IccTypeSignature.Lut8, tagSignature) - { - Guard.NotNull(matrix, nameof(matrix)); + /// + /// Initializes a new instance of the class. + /// + /// Conversion matrix (must be 3x3) + /// Input LUT + /// CLUT + /// Output LUT + /// Tag Signature + public IccLut8TagDataEntry(float[,] matrix, IccLut[] inputValues, IccClut clutValues, IccLut[] outputValues, IccProfileTag tagSignature) + : base(IccTypeSignature.Lut8, tagSignature) + { + Guard.NotNull(matrix, nameof(matrix)); - bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); + bool is3By3 = matrix.GetLength(0) == 3 && matrix.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix), "Matrix must have a size of three by three"); - this.Matrix = CreateMatrix(matrix); - this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); - this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); - this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); + this.Matrix = CreateMatrix(matrix); + this.InputValues = inputValues ?? throw new ArgumentNullException(nameof(inputValues)); + this.ClutValues = clutValues ?? throw new ArgumentNullException(nameof(clutValues)); + this.OutputValues = outputValues ?? throw new ArgumentNullException(nameof(outputValues)); - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256"); - Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256"); + Guard.IsFalse(inputValues.Any(t => t.Values.Length != 256), nameof(inputValues), "Input lookup table has to have a length of 256"); + Guard.IsFalse(outputValues.Any(t => t.Values.Length != 256), nameof(outputValues), "Output lookup table has to have a length of 256"); + } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount => this.InputValues.Length; + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount => this.OutputValues.Length; + + /// + /// Gets the conversion matrix + /// + public Matrix4x4 Matrix { get; } + + /// + /// Gets the input lookup table + /// + public IccLut[] InputValues { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the output lookup table + /// + public IccLut[] OutputValues { get; } + + /// + public override bool Equals(IccTagDataEntry other) => other is IccLut8TagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccLut8TagDataEntry other) + { + if (other is null) + { + return false; } - /// - /// Gets the number of input channels - /// - public int InputChannelCount => this.InputValues.Length; - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount => this.OutputValues.Length; - - /// - /// Gets the conversion matrix - /// - public Matrix4x4 Matrix { get; } - - /// - /// Gets the input lookup table - /// - public IccLut[] InputValues { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the output lookup table - /// - public IccLut[] OutputValues { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLut8TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLut8TagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Matrix.Equals(other.Matrix) - && this.InputValues.AsSpan().SequenceEqual(other.InputValues) - && this.ClutValues.Equals(other.ClutValues) - && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); + return true; } - /// - public override bool Equals(object obj) => obj is IccLut8TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Matrix, - this.InputValues, - this.ClutValues, - this.OutputValues); - - private static Matrix4x4 CreateMatrix(float[,] matrix) - => new( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); + return base.Equals(other) + && this.Matrix.Equals(other.Matrix) + && this.InputValues.AsSpan().SequenceEqual(other.InputValues) + && this.ClutValues.Equals(other.ClutValues) + && this.OutputValues.AsSpan().SequenceEqual(other.OutputValues); } + + /// + public override bool Equals(object obj) => obj is IccLut8TagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.Matrix, + this.InputValues, + this.ClutValues, + this.OutputValues); + + private static Matrix4x4 CreateMatrix(float[,] matrix) + => new( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index 0c338dabd2..823cd431cb 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -1,294 +1,291 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; // TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This structure represents a color transform. +/// +internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This structure represents a color transform. + /// Initializes a new instance of the class. /// - internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + } + + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + /// Tag Signature + public IccLutAToBTagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutAToB, tagSignature) + { + VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsAClutMMatrixB()) { - } + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutAToBTagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutAToB, tagSignature) + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = 3; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsMMatrixB()) { - VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsAClutMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = 3; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsMMatrixB()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsAClutB()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveA.Length; - this.OutputChannelCount = curveB.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; } + else if (this.IsAClutB()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutAToBTagDataEntry other) + this.InputChannelCount = curveA.Length; + this.OutputChannelCount = curveB.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && EqualsCurve(this.CurveB, other.CurveB) - && EqualsCurve(this.CurveM, other.CurveM) - && EqualsCurve(this.CurveA, other.CurveA); + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } - /// - public override bool Equals(object obj) => obj is IccLutAToBTagDataEntry other && this.Equals(other); + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } - /// - public override int GetHashCode() + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) => other is IccLutAToBTagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccLutAToBTagDataEntry other) + { + if (other is null) { - HashCode hashCode = default; - - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); + return false; } - private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + if (ReferenceEquals(this, other)) { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && EqualsCurve(this.CurveB, other.CurveB) + && EqualsCurve(this.CurveM, other.CurveM) + && EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) => obj is IccLutAToBTagDataEntry other && this.Equals(other); - if (thisNull && entryNull) - { - return true; - } + /// + public override int GetHashCode() + { + HashCode hashCode = default; + + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; - if (entryNull) - { - return false; - } + if (thisNull && entryNull) + { + return true; + } - return thisCurves.SequenceEqual(entryCurves); + if (entryNull) + { + return false; } - private bool IsAClutMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsAClutMMatrixB() + => this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null + && this.ClutValues != null + && this.CurveA != null; - private bool IsMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; + private bool IsMMatrixB() + => this.CurveB != null + && this.Matrix3x3 != null + && this.Matrix3x1 != null + && this.CurveM != null; - private bool IsAClutB() - => this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; + private bool IsAClutB() + => this.CurveB != null + && this.ClutValues != null + && this.CurveA != null; - private bool IsB() => this.CurveB != null; + private bool IsB() => this.CurveB != null; - private void VerifyCurve(IccTagDataEntry[] curves, string name) + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) { - if (curves != null) - { - bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } + bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); } + } - private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); } - private static Vector3? CreateMatrix3x1(float[] matrix) + if (matrix3x3 != null) { - if (matrix is null) - { - return null; - } + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } - return new Vector3(matrix[0], matrix[1], matrix[2]); + private static Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; } - private static Matrix4x4? CreateMatrix3x3(float[,] matrix) + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private static Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); + return null; } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs index 2c4487bcab..491d50df1a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -1,283 +1,280 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; // TODO: Review the use of base IccTagDataEntry comparison. -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This structure represents a color transform. +/// +internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable { /// - /// This structure represents a color transform. + /// Initializes a new instance of the class. /// - internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA) + : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA) - : this(curveB, matrix3x3, matrix3x1, curveM, clutValues, curveA, IccProfileTag.Unknown) + } + + /// + /// Initializes a new instance of the class. + /// + /// B Curve + /// Two dimensional conversion matrix (3x3) + /// One dimensional conversion matrix (3x1) + /// M Curve + /// CLUT + /// A Curve + /// Tag Signature + public IccLutBToATagDataEntry( + IccTagDataEntry[] curveB, + float[,] matrix3x3, + float[] matrix3x1, + IccTagDataEntry[] curveM, + IccClut clutValues, + IccTagDataEntry[] curveA, + IccProfileTag tagSignature) + : base(IccTypeSignature.LutBToA, tagSignature) + { + VerifyMatrix(matrix3x3, matrix3x1); + this.VerifyCurve(curveA, nameof(curveA)); + this.VerifyCurve(curveB, nameof(curveB)); + this.VerifyCurve(curveM, nameof(curveM)); + + this.Matrix3x3 = CreateMatrix3x3(matrix3x3); + this.Matrix3x1 = CreateMatrix3x1(matrix3x1); + this.CurveA = curveA; + this.CurveB = curveB; + this.CurveM = curveM; + this.ClutValues = clutValues; + + if (this.IsBMatrixMClutA()) { - } + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - /// - /// Initializes a new instance of the class. - /// - /// B Curve - /// Two dimensional conversion matrix (3x3) - /// One dimensional conversion matrix (3x1) - /// M Curve - /// CLUT - /// A Curve - /// Tag Signature - public IccLutBToATagDataEntry( - IccTagDataEntry[] curveB, - float[,] matrix3x3, - float[] matrix3x1, - IccTagDataEntry[] curveM, - IccClut clutValues, - IccTagDataEntry[] curveA, - IccProfileTag tagSignature) - : base(IccTypeSignature.LutBToA, tagSignature) + this.InputChannelCount = 3; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsBMatrixM()) { - VerifyMatrix(matrix3x3, matrix3x1); - this.VerifyCurve(curveA, nameof(curveA)); - this.VerifyCurve(curveB, nameof(curveB)); - this.VerifyCurve(curveM, nameof(curveM)); - - this.Matrix3x3 = CreateMatrix3x3(matrix3x3); - this.Matrix3x1 = CreateMatrix3x1(matrix3x1); - this.CurveA = curveA; - this.CurveB = curveB; - this.CurveM = curveM; - this.ClutValues = clutValues; - - if (this.IsBMatrixMClutA()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - - this.InputChannelCount = 3; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsBMatrixM()) - { - Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); - Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); - - this.InputChannelCount = this.OutputChannelCount = 3; - } - else if (this.IsBClutA()) - { - Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); - Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - - this.InputChannelCount = curveB.Length; - this.OutputChannelCount = curveA.Length; - - Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); - Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); - } - else if (this.IsB()) - { - this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; - } - else - { - throw new ArgumentException("Invalid combination of values given"); - } + Guard.IsTrue(this.CurveB.Length == 3, nameof(this.CurveB), $"{nameof(this.CurveB)} must have a length of three"); + Guard.IsTrue(this.CurveM.Length == 3, nameof(this.CurveM), $"{nameof(this.CurveM)} must have a length of three"); + + this.InputChannelCount = this.OutputChannelCount = 3; } + else if (this.IsBClutA()) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the two dimensional conversion matrix (3x3) - /// - public Matrix4x4? Matrix3x3 { get; } - - /// - /// Gets the one dimensional conversion matrix (3x1) - /// - public Vector3? Matrix3x1 { get; } - - /// - /// Gets the color lookup table - /// - public IccClut ClutValues { get; } - - /// - /// Gets the B Curve - /// - public IccTagDataEntry[] CurveB { get; } - - /// - /// Gets the M Curve - /// - public IccTagDataEntry[] CurveM { get; } - - /// - /// Gets the A Curve - /// - public IccTagDataEntry[] CurveA { get; } - - /// - public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccLutBToATagDataEntry other) + this.InputChannelCount = curveB.Length; + this.OutputChannelCount = curveA.Length; + + Guard.IsTrue(this.InputChannelCount == clutValues.InputChannelCount, nameof(clutValues), "Input channel count does not match the CLUT size"); + Guard.IsTrue(this.OutputChannelCount == clutValues.OutputChannelCount, nameof(clutValues), "Output channel count does not match the CLUT size"); + } + else if (this.IsB()) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Matrix3x3.Equals(other.Matrix3x3) - && this.Matrix3x1.Equals(other.Matrix3x1) - && this.ClutValues.Equals(other.ClutValues) - && EqualsCurve(this.CurveB, other.CurveB) - && EqualsCurve(this.CurveM, other.CurveM) - && EqualsCurve(this.CurveA, other.CurveA); + this.InputChannelCount = this.OutputChannelCount = this.CurveB.Length; } + else + { + throw new ArgumentException("Invalid combination of values given"); + } + } - /// - public override bool Equals(object obj) => obj is IccLutBToATagDataEntry other && this.Equals(other); + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } + + /// + /// Gets the two dimensional conversion matrix (3x3) + /// + public Matrix4x4? Matrix3x3 { get; } + + /// + /// Gets the one dimensional conversion matrix (3x1) + /// + public Vector3? Matrix3x1 { get; } + + /// + /// Gets the color lookup table + /// + public IccClut ClutValues { get; } + + /// + /// Gets the B Curve + /// + public IccTagDataEntry[] CurveB { get; } - /// - public override int GetHashCode() + /// + /// Gets the M Curve + /// + public IccTagDataEntry[] CurveM { get; } + + /// + /// Gets the A Curve + /// + public IccTagDataEntry[] CurveA { get; } + + /// + public override bool Equals(IccTagDataEntry other) => other is IccLutBToATagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccLutBToATagDataEntry other) + { + if (other is null) { - HashCode hashCode = default; - hashCode.Add(this.Signature); - hashCode.Add(this.InputChannelCount); - hashCode.Add(this.OutputChannelCount); - hashCode.Add(this.Matrix3x3); - hashCode.Add(this.Matrix3x1); - hashCode.Add(this.ClutValues); - hashCode.Add(this.CurveB); - hashCode.Add(this.CurveM); - hashCode.Add(this.CurveA); - - return hashCode.ToHashCode(); + return false; } - private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + if (ReferenceEquals(this, other)) { - bool thisNull = thisCurves is null; - bool entryNull = entryCurves is null; + return true; + } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Matrix3x3.Equals(other.Matrix3x3) + && this.Matrix3x1.Equals(other.Matrix3x1) + && this.ClutValues.Equals(other.ClutValues) + && EqualsCurve(this.CurveB, other.CurveB) + && EqualsCurve(this.CurveM, other.CurveM) + && EqualsCurve(this.CurveA, other.CurveA); + } + + /// + public override bool Equals(object obj) => obj is IccLutBToATagDataEntry other && this.Equals(other); - if (thisNull && entryNull) - { - return true; - } + /// + public override int GetHashCode() + { + HashCode hashCode = default; + hashCode.Add(this.Signature); + hashCode.Add(this.InputChannelCount); + hashCode.Add(this.OutputChannelCount); + hashCode.Add(this.Matrix3x3); + hashCode.Add(this.Matrix3x1); + hashCode.Add(this.ClutValues); + hashCode.Add(this.CurveB); + hashCode.Add(this.CurveM); + hashCode.Add(this.CurveA); + + return hashCode.ToHashCode(); + } + + private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) + { + bool thisNull = thisCurves is null; + bool entryNull = entryCurves is null; - if (entryNull) - { - return false; - } + if (thisNull && entryNull) + { + return true; + } - return thisCurves.SequenceEqual(entryCurves); + if (entryNull) + { + return false; } - private bool IsBMatrixMClutA() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null && this.ClutValues != null && this.CurveA != null; + return thisCurves.SequenceEqual(entryCurves); + } + + private bool IsBMatrixMClutA() + => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null && this.ClutValues != null && this.CurveA != null; - private bool IsBMatrixM() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null; + private bool IsBMatrixM() + => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null; - private bool IsBClutA() - => this.CurveB != null && this.ClutValues != null && this.CurveA != null; + private bool IsBClutA() + => this.CurveB != null && this.ClutValues != null && this.CurveA != null; - private bool IsB() => this.CurveB != null; + private bool IsB() => this.CurveB != null; - private void VerifyCurve(IccTagDataEntry[] curves, string name) + private void VerifyCurve(IccTagDataEntry[] curves, string name) + { + if (curves != null) { - if (curves != null) - { - bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); - Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); - } + bool isNotCurve = curves.Any(t => t is not IccParametricCurveTagDataEntry and not IccCurveTagDataEntry); + Guard.IsFalse(isNotCurve, nameof(name), $"{nameof(name)} must be of type {nameof(IccParametricCurveTagDataEntry)} or {nameof(IccCurveTagDataEntry)}"); } + } - private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) + { + if (matrix3x1 != null) { - if (matrix3x1 != null) - { - Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); - } - - if (matrix3x3 != null) - { - bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; - Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); - } + Guard.IsTrue(matrix3x1.Length == 3, nameof(matrix3x1), "Matrix must have a size of three"); } - private static Vector3? CreateMatrix3x1(float[] matrix) + if (matrix3x3 != null) { - if (matrix is null) - { - return null; - } + bool is3By3 = matrix3x3.GetLength(0) == 3 && matrix3x3.GetLength(1) == 3; + Guard.IsTrue(is3By3, nameof(matrix3x3), "Matrix must have a size of three by three"); + } + } - return new Vector3(matrix[0], matrix[1], matrix[2]); + private static Vector3? CreateMatrix3x1(float[] matrix) + { + if (matrix is null) + { + return null; } - private static Matrix4x4? CreateMatrix3x3(float[,] matrix) + return new Vector3(matrix[0], matrix[1], matrix[2]); + } + + private static Matrix4x4? CreateMatrix3x3(float[,] matrix) + { + if (matrix is null) { - if (matrix is null) - { - return null; - } - - return new Matrix4x4( - matrix[0, 0], - matrix[0, 1], - matrix[0, 2], - 0, - matrix[1, 0], - matrix[1, 1], - matrix[1, 2], - 0, - matrix[2, 0], - matrix[2, 1], - matrix[2, 2], - 0, - 0, - 0, - 0, - 1); + return null; } + + return new Matrix4x4( + matrix[0, 0], + matrix[0, 1], + matrix[0, 2], + 0, + matrix[1, 0], + matrix[1, 1], + matrix[1, 2], + 0, + matrix[2, 0], + matrix[2, 1], + matrix[2, 2], + 0, + 0, + 0, + 0, + 1); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs index 476428d734..fd27948d2f 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMeasurementTagDataEntry.cs @@ -1,118 +1,116 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// The measurementType information refers only to the internal +/// profile data and is meant to provide profile makers an alternative +/// to the default measurement specifications. +/// +internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The measurementType information refers only to the internal - /// profile data and is meant to provide profile makers an alternative - /// to the default measurement specifications. + /// Initializes a new instance of the class. /// - internal sealed class IccMeasurementTagDataEntry : IccTagDataEntry, IEquatable + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant) + : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Observer - /// XYZ Backing values - /// Geometry - /// Flare - /// Illuminant - public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant) - : this(observer, xyzBacking, geometry, flare, illuminant, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Observer - /// XYZ Backing values - /// Geometry - /// Flare - /// Illuminant - /// Tag Signature - public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature) - : base(IccTypeSignature.Measurement, tagSignature) - { - this.Observer = observer; - this.XyzBacking = xyzBacking; - this.Geometry = geometry; - this.Flare = flare; - this.Illuminant = illuminant; - } + /// + /// Initializes a new instance of the class. + /// + /// Observer + /// XYZ Backing values + /// Geometry + /// Flare + /// Illuminant + /// Tag Signature + public IccMeasurementTagDataEntry(IccStandardObserver observer, Vector3 xyzBacking, IccMeasurementGeometry geometry, float flare, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.Measurement, tagSignature) + { + this.Observer = observer; + this.XyzBacking = xyzBacking; + this.Geometry = geometry; + this.Flare = flare; + this.Illuminant = illuminant; + } - /// - /// Gets the observer - /// - public IccStandardObserver Observer { get; } + /// + /// Gets the observer + /// + public IccStandardObserver Observer { get; } - /// - /// Gets the XYZ Backing values - /// - public Vector3 XyzBacking { get; } + /// + /// Gets the XYZ Backing values + /// + public Vector3 XyzBacking { get; } - /// - /// Gets the geometry - /// - public IccMeasurementGeometry Geometry { get; } + /// + /// Gets the geometry + /// + public IccMeasurementGeometry Geometry { get; } - /// - /// Gets the flare - /// - public float Flare { get; } + /// + /// Gets the flare + /// + public float Flare { get; } - /// - /// Gets the illuminant - /// - public IccStandardIlluminant Illuminant { get; } + /// + /// Gets the illuminant + /// + public IccStandardIlluminant Illuminant { get; } - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccMeasurementTagDataEntry entry && this.Equals(entry); - } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccMeasurementTagDataEntry entry && this.Equals(entry); + } - /// - public bool Equals(IccMeasurementTagDataEntry other) + /// + public bool Equals(IccMeasurementTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Observer == other.Observer - && this.XyzBacking.Equals(other.XyzBacking) - && this.Geometry == other.Geometry - && this.Flare.Equals(other.Flare) - && this.Illuminant == other.Illuminant; + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccMeasurementTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.Observer, - this.XyzBacking, - this.Geometry, - this.Flare, - this.Illuminant); - } + return base.Equals(other) + && this.Observer == other.Observer + && this.XyzBacking.Equals(other.XyzBacking) + && this.Geometry == other.Geometry + && this.Flare.Equals(other.Flare) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + return obj is IccMeasurementTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.Observer, + this.XyzBacking, + this.Geometry, + this.Flare, + this.Illuminant); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs index 567e976395..95d338244d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiLocalizedUnicodeTagDataEntry.cs @@ -1,70 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This tag structure contains a set of records each referencing +/// a multilingual string associated with a profile. +/// +internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This tag structure contains a set of records each referencing - /// a multilingual string associated with a profile. + /// Initializes a new instance of the class. /// - internal sealed class IccMultiLocalizedUnicodeTagDataEntry : IccTagDataEntry, IEquatable + /// Localized Text + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts) + : this(texts, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Localized Text - public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts) - : this(texts, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Localized Text - /// Tag Signature - public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature) - : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature) - { - this.Texts = texts ?? throw new ArgumentNullException(nameof(texts)); - } + /// + /// Initializes a new instance of the class. + /// + /// Localized Text + /// Tag Signature + public IccMultiLocalizedUnicodeTagDataEntry(IccLocalizedString[] texts, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiLocalizedUnicode, tagSignature) + { + this.Texts = texts ?? throw new ArgumentNullException(nameof(texts)); + } + + /// + /// Gets the localized texts + /// + public IccLocalizedString[] Texts { get; } - /// - /// Gets the localized texts - /// - public IccLocalizedString[] Texts { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccMultiLocalizedUnicodeTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccMultiLocalizedUnicodeTagDataEntry other) + { + if (other is null) { - return other is IccMultiLocalizedUnicodeTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccMultiLocalizedUnicodeTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Texts.AsSpan().SequenceEqual(other.Texts); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccMultiLocalizedUnicodeTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Texts.AsSpan().SequenceEqual(other.Texts); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Texts); + /// + public override bool Equals(object obj) + { + return obj is IccMultiLocalizedUnicodeTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Texts); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs index 1552812ed7..3c785fce86 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccMultiProcessElementsTagDataEntry.cs @@ -1,94 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This structure represents a color transform, containing +/// a sequence of processing elements. +/// +internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This structure represents a color transform, containing - /// a sequence of processing elements. + /// Initializes a new instance of the class. /// - internal sealed class IccMultiProcessElementsTagDataEntry : IccTagDataEntry, IEquatable + /// Processing elements + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Processing elements - public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Processing elements - /// Tag Signature - public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.MultiProcessElements, tagSignature) - { - Guard.NotNull(data, nameof(data)); - Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element"); + /// + /// Initializes a new instance of the class. + /// + /// Processing elements + /// Tag Signature + public IccMultiProcessElementsTagDataEntry(IccMultiProcessElement[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.MultiProcessElements, tagSignature) + { + Guard.NotNull(data, nameof(data)); + Guard.IsTrue(data.Length > 0, nameof(data), $"{nameof(data)} must have at least one element"); - this.InputChannelCount = data[0].InputChannelCount; - this.OutputChannelCount = data[0].OutputChannelCount; - this.Data = data; + this.InputChannelCount = data[0].InputChannelCount; + this.OutputChannelCount = data[0].OutputChannelCount; + this.Data = data; - bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount); - Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements"); - } + bool channelsNotSame = data.Any(t => t.InputChannelCount != this.InputChannelCount || t.OutputChannelCount != this.OutputChannelCount); + Guard.IsFalse(channelsNotSame, nameof(data), "The number of input and output channels are not the same for all elements"); + } - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } - /// - /// Gets the processing elements - /// - public IccMultiProcessElement[] Data { get; } + /// + /// Gets the processing elements + /// + public IccMultiProcessElement[] Data { get; } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccMultiProcessElementsTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccMultiProcessElementsTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccMultiProcessElementsTagDataEntry other) + /// + public bool Equals(IccMultiProcessElementsTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.Data.AsSpan().SequenceEqual(other.Data); + return false; } - /// - public override bool Equals(object obj) => obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() + if (ReferenceEquals(this, other)) { - return HashCode.Combine( - this.Signature, - this.InputChannelCount, - this.OutputChannelCount, - this.Data); + return true; } + + return base.Equals(other) + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.Data.AsSpan().SequenceEqual(other.Data); + } + + /// + public override bool Equals(object obj) => obj is IccMultiProcessElementsTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.InputChannelCount, + this.OutputChannelCount, + this.Data); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs index 8541d287d8..37cd25d7c1 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccNamedColor2TagDataEntry.cs @@ -1,161 +1,157 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The namedColor2Type is a count value and array of structures +/// that provide color coordinates for color names. +/// +internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable { /// - /// The namedColor2Type is a count value and array of structures - /// that provide color coordinates for color names. + /// Initializes a new instance of the class. /// - internal sealed class IccNamedColor2TagDataEntry : IccTagDataEntry, IEquatable + /// The named colors + public IccNamedColor2TagDataEntry(IccNamedColor[] colors) + : this(0, null, null, colors, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The named colors - public IccNamedColor2TagDataEntry(IccNamedColor[] colors) - : this(0, null, null, colors, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Prefix - /// Suffix - /// /// The named colors - public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors) - : this(0, prefix, suffix, colors, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// /// The named colors + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors) + : this(0, prefix, suffix, colors, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Vendor specific flags - /// Prefix - /// Suffix - /// The named colors - public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors) - : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors) + : this(vendorFlags, prefix, suffix, colors, IccProfileTag.Unknown) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature) - : this(0, null, null, colors, tagSignature) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, null, null, colors, tagSignature) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : this(0, prefix, suffix, colors, tagSignature) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Prefix - /// Suffix - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) - : this(0, prefix, suffix, colors, tagSignature) + /// + /// Initializes a new instance of the class. + /// + /// Vendor specific flags + /// Prefix + /// Suffix + /// The named colors + /// Tag Signature + public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) + : base(IccTypeSignature.NamedColor2, tagSignature) + { + Guard.NotNull(colors, nameof(colors)); + + int coordinateCount = 0; + if (colors.Length > 0) { + coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0; + + Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors"); } - /// - /// Initializes a new instance of the class. - /// - /// Vendor specific flags - /// Prefix - /// Suffix - /// The named colors - /// Tag Signature - public IccNamedColor2TagDataEntry(int vendorFlags, string prefix, string suffix, IccNamedColor[] colors, IccProfileTag tagSignature) - : base(IccTypeSignature.NamedColor2, tagSignature) - { - Guard.NotNull(colors, nameof(colors)); + this.VendorFlags = vendorFlags; + this.CoordinateCount = coordinateCount; + this.Prefix = prefix; + this.Suffix = suffix; + this.Colors = colors; + } - int coordinateCount = 0; - if (colors.Length > 0) - { - coordinateCount = colors[0].DeviceCoordinates?.Length ?? 0; + /// + /// Gets the number of coordinates + /// + public int CoordinateCount { get; } - Guard.IsFalse(colors.Any(t => (t.DeviceCoordinates?.Length ?? 0) != coordinateCount), nameof(colors), "Device coordinate count must be the same for all colors"); - } + /// + /// Gets the prefix + /// + public string Prefix { get; } - this.VendorFlags = vendorFlags; - this.CoordinateCount = coordinateCount; - this.Prefix = prefix; - this.Suffix = suffix; - this.Colors = colors; + /// + /// Gets the suffix + /// + public string Suffix { get; } + + /// + /// Gets the vendor specific flags + /// + public int VendorFlags { get; } + + /// + /// Gets the named colors + /// + public IccNamedColor[] Colors { get; } + + /// + public override bool Equals(IccTagDataEntry other) + => other is IccNamedColor2TagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccNamedColor2TagDataEntry other) + { + if (other is null) + { + return false; } - /// - /// Gets the number of coordinates - /// - public int CoordinateCount { get; } - - /// - /// Gets the prefix - /// - public string Prefix { get; } - - /// - /// Gets the suffix - /// - public string Suffix { get; } - - /// - /// Gets the vendor specific flags - /// - public int VendorFlags { get; } - - /// - /// Gets the named colors - /// - public IccNamedColor[] Colors { get; } - - /// - public override bool Equals(IccTagDataEntry other) - => other is IccNamedColor2TagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccNamedColor2TagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.CoordinateCount == other.CoordinateCount - && string.Equals(this.Prefix, other.Prefix, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase) - && this.VendorFlags == other.VendorFlags - && this.Colors.AsSpan().SequenceEqual(other.Colors); + return true; } - /// - public override bool Equals(object obj) - => obj is IccNamedColor2TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.CoordinateCount, - this.Prefix, - this.Suffix, - this.VendorFlags, - this.Colors); + return base.Equals(other) + && this.CoordinateCount == other.CoordinateCount + && string.Equals(this.Prefix, other.Prefix, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.Suffix, other.Suffix, StringComparison.OrdinalIgnoreCase) + && this.VendorFlags == other.VendorFlags + && this.Colors.AsSpan().SequenceEqual(other.Colors); } + + /// + public override bool Equals(object obj) + => obj is IccNamedColor2TagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.CoordinateCount, + this.Prefix, + this.Suffix, + this.VendorFlags, + this.Colors); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs index a6df159b6b..d048990ac5 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccParametricCurveTagDataEntry.cs @@ -1,70 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The parametricCurveType describes a one-dimensional curve by +/// specifying one of a predefined set of functions using the parameters. +/// +internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The parametricCurveType describes a one-dimensional curve by - /// specifying one of a predefined set of functions using the parameters. + /// Initializes a new instance of the class. /// - internal sealed class IccParametricCurveTagDataEntry : IccTagDataEntry, IEquatable + /// The Curve + public IccParametricCurveTagDataEntry(IccParametricCurve curve) + : this(curve, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The Curve - public IccParametricCurveTagDataEntry(IccParametricCurve curve) - : this(curve, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The Curve - /// Tag Signature - public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature) - : base(IccTypeSignature.ParametricCurve, tagSignature) - { - this.Curve = curve ?? throw new ArgumentNullException(nameof(curve)); - } + /// + /// Initializes a new instance of the class. + /// + /// The Curve + /// Tag Signature + public IccParametricCurveTagDataEntry(IccParametricCurve curve, IccProfileTag tagSignature) + : base(IccTypeSignature.ParametricCurve, tagSignature) + { + this.Curve = curve ?? throw new ArgumentNullException(nameof(curve)); + } + + /// + /// Gets the Curve + /// + public IccParametricCurve Curve { get; } - /// - /// Gets the Curve - /// - public IccParametricCurve Curve { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccParametricCurveTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccParametricCurveTagDataEntry other) + { + if (other is null) { - return other is IccParametricCurveTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccParametricCurveTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Curve.Equals(other.Curve); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccParametricCurveTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Curve.Equals(other.Curve); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Curve); + /// + public override bool Equals(object obj) + { + return obj is IccParametricCurveTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Curve); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs index 947e0a8686..37cae5a00c 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceDescTagDataEntry.cs @@ -1,65 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This type is an array of structures, each of which contains information +/// from the header fields and tags from the original profiles which were +/// combined to create the final profile. +/// +internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type is an array of structures, each of which contains information - /// from the header fields and tags from the original profiles which were - /// combined to create the final profile. + /// Initializes a new instance of the class. /// - internal sealed class IccProfileSequenceDescTagDataEntry : IccTagDataEntry, IEquatable + /// Profile Descriptions + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions) + : this(descriptions, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Profile Descriptions - public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions) - : this(descriptions, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Profile Descriptions - /// Tag Signature - public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) - : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) - => this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); + /// + /// Initializes a new instance of the class. + /// + /// Profile Descriptions + /// Tag Signature + public IccProfileSequenceDescTagDataEntry(IccProfileDescription[] descriptions, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceDesc, tagSignature) + => this.Descriptions = descriptions ?? throw new ArgumentNullException(nameof(descriptions)); - /// - /// Gets the profile descriptions - /// - public IccProfileDescription[] Descriptions { get; } + /// + /// Gets the profile descriptions + /// + public IccProfileDescription[] Descriptions { get; } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccProfileSequenceDescTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccProfileSequenceDescTagDataEntry other) + /// + public bool Equals(IccProfileSequenceDescTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Descriptions.AsSpan().SequenceEqual(other.Descriptions); + return false; } - /// - public override bool Equals(object obj) - => obj is IccProfileSequenceDescTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Descriptions); + return base.Equals(other) && this.Descriptions.AsSpan().SequenceEqual(other.Descriptions); } + + /// + public override bool Equals(object obj) + => obj is IccProfileSequenceDescTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Descriptions); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs index 163544cbd2..351db6a252 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccProfileSequenceIdentifierTagDataEntry.cs @@ -1,70 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type is an array of structures, each of which contains information +/// for identification of a profile used in a sequence. +/// +internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type is an array of structures, each of which contains information - /// for identification of a profile used in a sequence. + /// Initializes a new instance of the class. /// - internal sealed class IccProfileSequenceIdentifierTagDataEntry : IccTagDataEntry, IEquatable + /// Profile Identifiers + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Profile Identifiers - public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Profile Identifiers - /// Tag Signature - public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// Profile Identifiers + /// Tag Signature + public IccProfileSequenceIdentifierTagDataEntry(IccProfileSequenceIdentifier[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.ProfileSequenceIdentifier, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the profile identifiers + /// + public IccProfileSequenceIdentifier[] Data { get; } - /// - /// Gets the profile identifiers - /// - public IccProfileSequenceIdentifier[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccProfileSequenceIdentifierTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccProfileSequenceIdentifierTagDataEntry other) + { + if (other is null) { - return other is IccProfileSequenceIdentifierTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccProfileSequenceIdentifierTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccProfileSequenceIdentifierTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccProfileSequenceIdentifierTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs index e1a09402d7..669f269582 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccResponseCurveSet16TagDataEntry.cs @@ -1,86 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// The purpose of this tag type is to provide a mechanism to relate physical +/// colorant amounts with the normalized device codes produced by lut8Type, lut16Type, +/// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can +/// be made for variation in the device without having to produce a new profile. +/// +internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable { /// - /// The purpose of this tag type is to provide a mechanism to relate physical - /// colorant amounts with the normalized device codes produced by lut8Type, lut16Type, - /// lutAToBType, lutBToAType or multiProcessElementsType tags so that corrections can - /// be made for variation in the device without having to produce a new profile. + /// Initializes a new instance of the class. /// - internal sealed class IccResponseCurveSet16TagDataEntry : IccTagDataEntry, IEquatable + /// The Curves + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves) + : this(curves, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The Curves - public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves) - : this(curves, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The Curves - /// Tag Signature - public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature) - : base(IccTypeSignature.ResponseCurveSet16, tagSignature) - { - Guard.NotNull(curves, nameof(curves)); - Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element"); + /// + /// Initializes a new instance of the class. + /// + /// The Curves + /// Tag Signature + public IccResponseCurveSet16TagDataEntry(IccResponseCurve[] curves, IccProfileTag tagSignature) + : base(IccTypeSignature.ResponseCurveSet16, tagSignature) + { + Guard.NotNull(curves, nameof(curves)); + Guard.IsTrue(curves.Length > 0, nameof(curves), $"{nameof(curves)} needs at least one element"); - this.Curves = curves; - this.ChannelCount = (ushort)curves[0].ResponseArrays.Length; + this.Curves = curves; + this.ChannelCount = (ushort)curves[0].ResponseArrays.Length; - Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels"); - } + Guard.IsFalse(curves.Any(t => t.ResponseArrays.Length != this.ChannelCount), nameof(curves), "All curves need to have the same number of channels"); + } - /// - /// Gets the number of channels - /// - public ushort ChannelCount { get; } + /// + /// Gets the number of channels + /// + public ushort ChannelCount { get; } - /// - /// Gets the curves - /// - public IccResponseCurve[] Curves { get; } + /// + /// Gets the curves + /// + public IccResponseCurve[] Curves { get; } - /// - public override bool Equals(IccTagDataEntry other) => other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) => other is IccResponseCurveSet16TagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccResponseCurveSet16TagDataEntry other) + /// + public bool Equals(IccResponseCurveSet16TagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.ChannelCount == other.ChannelCount - && this.Curves.AsSpan().SequenceEqual(other.Curves); + return false; } - /// - public override bool Equals(object obj) => obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() + if (ReferenceEquals(this, other)) { - return HashCode.Combine( - this.Signature, - this.ChannelCount, - this.Curves); + return true; } + + return base.Equals(other) + && this.ChannelCount == other.ChannelCount + && this.Curves.AsSpan().SequenceEqual(other.Curves); + } + + /// + public override bool Equals(object obj) => obj is IccResponseCurveSet16TagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.ChannelCount, + this.Curves); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs index 522e45f270..0f96b27838 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccScreeningTagDataEntry.cs @@ -1,83 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type describes various screening parameters including +/// screen frequency, screening angle, and spot shape. +/// +internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type describes various screening parameters including - /// screen frequency, screening angle, and spot shape. + /// Initializes a new instance of the class. /// - internal sealed class IccScreeningTagDataEntry : IccTagDataEntry, IEquatable + /// Screening flags + /// Channel information + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels) + : this(flags, channels, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// Screening flags - /// Channel information - public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels) - : this(flags, channels, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Screening flags - /// Channel information - /// Tag Signature - public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature) - : base(IccTypeSignature.Screening, tagSignature) - { - this.Flags = flags; - this.Channels = channels ?? throw new ArgumentNullException(nameof(channels)); - } + /// + /// Initializes a new instance of the class. + /// + /// Screening flags + /// Channel information + /// Tag Signature + public IccScreeningTagDataEntry(IccScreeningFlag flags, IccScreeningChannel[] channels, IccProfileTag tagSignature) + : base(IccTypeSignature.Screening, tagSignature) + { + this.Flags = flags; + this.Channels = channels ?? throw new ArgumentNullException(nameof(channels)); + } - /// - /// Gets the screening flags - /// - public IccScreeningFlag Flags { get; } + /// + /// Gets the screening flags + /// + public IccScreeningFlag Flags { get; } - /// - /// Gets the channel information - /// - public IccScreeningChannel[] Channels { get; } + /// + /// Gets the channel information + /// + public IccScreeningChannel[] Channels { get; } - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccScreeningTagDataEntry entry && this.Equals(entry); - } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccScreeningTagDataEntry entry && this.Equals(entry); + } - /// - public bool Equals(IccScreeningTagDataEntry other) + /// + public bool Equals(IccScreeningTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.Flags == other.Flags - && this.Channels.AsSpan().SequenceEqual(other.Channels); + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccScreeningTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Signature, this.Flags, this.Channels); - } + return base.Equals(other) + && this.Flags == other.Flags + && this.Channels.AsSpan().SequenceEqual(other.Channels); + } + + /// + public override bool Equals(object obj) + { + return obj is IccScreeningTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Signature, this.Flags, this.Channels); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs index 2924a5dc6a..bff0a17531 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccSignatureTagDataEntry.cs @@ -1,65 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Typically this type is used for registered tags that can +/// be displayed on many development systems as a sequence of four characters. +/// +internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable { /// - /// Typically this type is used for registered tags that can - /// be displayed on many development systems as a sequence of four characters. + /// Initializes a new instance of the class. /// - internal sealed class IccSignatureTagDataEntry : IccTagDataEntry, IEquatable + /// The Signature + public IccSignatureTagDataEntry(string signatureData) + : this(signatureData, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The Signature - public IccSignatureTagDataEntry(string signatureData) - : this(signatureData, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The Signature - /// Tag Signature - public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature) - : base(IccTypeSignature.Signature, tagSignature) - => this.SignatureData = signatureData ?? throw new ArgumentNullException(nameof(signatureData)); + /// + /// Initializes a new instance of the class. + /// + /// The Signature + /// Tag Signature + public IccSignatureTagDataEntry(string signatureData, IccProfileTag tagSignature) + : base(IccTypeSignature.Signature, tagSignature) + => this.SignatureData = signatureData ?? throw new ArgumentNullException(nameof(signatureData)); - /// - /// Gets the signature data - /// - public string SignatureData { get; } + /// + /// Gets the signature data + /// + public string SignatureData { get; } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccSignatureTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccSignatureTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccSignatureTagDataEntry other) + /// + public bool Equals(IccSignatureTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && string.Equals(this.SignatureData, other.SignatureData, StringComparison.OrdinalIgnoreCase); + return false; } - /// - public override bool Equals(object obj) - => obj is IccSignatureTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.SignatureData); + return base.Equals(other) + && string.Equals(this.SignatureData, other.SignatureData, StringComparison.OrdinalIgnoreCase); } + + /// + public override bool Equals(object obj) + => obj is IccSignatureTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.SignatureData); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs index 6fb2aca203..b951d7b56c 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -1,172 +1,170 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// The TextDescriptionType contains three types of text description. +/// +internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The TextDescriptionType contains three types of text description. + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode) + : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// ASCII text + /// Unicode text + /// ScriptCode text + /// Unicode Language-Code + /// ScriptCode Code + /// Tag Signature + public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature) + : base(IccTypeSignature.TextDescription, tagSignature) + { + this.Ascii = ascii; + this.Unicode = unicode; + this.ScriptCode = scriptCode; + this.UnicodeLanguageCode = unicodeLanguageCode; + this.ScriptCodeCode = scriptCodeCode; + } + + /// + /// Gets the ASCII text + /// + public string Ascii { get; } + + /// + /// Gets the Unicode text /// - internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatable + public string Unicode { get; } + + /// + /// Gets the ScriptCode text + /// + public string ScriptCode { get; } + + /// + /// Gets the Unicode Language-Code + /// + public uint UnicodeLanguageCode { get; } + + /// + /// Gets the ScriptCode Code + /// + public ushort ScriptCodeCode { get; } + + /// + /// Performs an explicit conversion from + /// to . + /// + /// The entry to convert + /// The converted entry + public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry) { - /// - /// Initializes a new instance of the class. - /// - /// ASCII text - /// Unicode text - /// ScriptCode text - /// Unicode Language-Code - /// ScriptCode Code - public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode) - : this(ascii, unicode, scriptCode, unicodeLanguageCode, scriptCodeCode, IccProfileTag.Unknown) + if (textEntry is null) { + return null; } - /// - /// Initializes a new instance of the class. - /// - /// ASCII text - /// Unicode text - /// ScriptCode text - /// Unicode Language-Code - /// ScriptCode Code - /// Tag Signature - public IccTextDescriptionTagDataEntry(string ascii, string unicode, string scriptCode, uint unicodeLanguageCode, ushort scriptCodeCode, IccProfileTag tagSignature) - : base(IccTypeSignature.TextDescription, tagSignature) + IccLocalizedString localString; + if (!string.IsNullOrEmpty(textEntry.Unicode)) + { + CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode); + localString = culture != null + ? new IccLocalizedString(culture, textEntry.Unicode) + : new IccLocalizedString(textEntry.Unicode); + } + else if (!string.IsNullOrEmpty(textEntry.Ascii)) { - this.Ascii = ascii; - this.Unicode = unicode; - this.ScriptCode = scriptCode; - this.UnicodeLanguageCode = unicodeLanguageCode; - this.ScriptCodeCode = scriptCodeCode; + localString = new IccLocalizedString(textEntry.Ascii); + } + else if (!string.IsNullOrEmpty(textEntry.ScriptCode)) + { + localString = new IccLocalizedString(textEntry.ScriptCode); + } + else + { + localString = new IccLocalizedString(string.Empty); } - /// - /// Gets the ASCII text - /// - public string Ascii { get; } - - /// - /// Gets the Unicode text - /// - public string Unicode { get; } - - /// - /// Gets the ScriptCode text - /// - public string ScriptCode { get; } - - /// - /// Gets the Unicode Language-Code - /// - public uint UnicodeLanguageCode { get; } - - /// - /// Gets the ScriptCode Code - /// - public ushort ScriptCodeCode { get; } - - /// - /// Performs an explicit conversion from - /// to . - /// - /// The entry to convert - /// The converted entry - public static explicit operator IccMultiLocalizedUnicodeTagDataEntry(IccTextDescriptionTagDataEntry textEntry) + return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); + + static CultureInfo GetCulture(uint value) { - if (textEntry is null) + if (value == 0) { return null; } - IccLocalizedString localString; - if (!string.IsNullOrEmpty(textEntry.Unicode)) - { - CultureInfo culture = GetCulture(textEntry.UnicodeLanguageCode); - localString = culture != null - ? new IccLocalizedString(culture, textEntry.Unicode) - : new IccLocalizedString(textEntry.Unicode); - } - else if (!string.IsNullOrEmpty(textEntry.Ascii)) - { - localString = new IccLocalizedString(textEntry.Ascii); - } - else if (!string.IsNullOrEmpty(textEntry.ScriptCode)) - { - localString = new IccLocalizedString(textEntry.ScriptCode); - } - else - { - localString = new IccLocalizedString(string.Empty); - } - - return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); + byte p1 = (byte)(value >> 24); + byte p2 = (byte)(value >> 16); + byte p3 = (byte)(value >> 8); + byte p4 = (byte)value; - static CultureInfo GetCulture(uint value) + // Check if the values are [a-z]{2}[A-Z]{2} + if (p1 >= 0x61 && p1 <= 0x7A + && p2 >= 0x61 && p2 <= 0x7A + && p3 >= 0x41 && p3 <= 0x5A + && p4 >= 0x41 && p4 <= 0x5A) { - if (value == 0) - { - return null; - } - - byte p1 = (byte)(value >> 24); - byte p2 = (byte)(value >> 16); - byte p3 = (byte)(value >> 8); - byte p4 = (byte)value; - - // Check if the values are [a-z]{2}[A-Z]{2} - if (p1 >= 0x61 && p1 <= 0x7A - && p2 >= 0x61 && p2 <= 0x7A - && p3 >= 0x41 && p3 <= 0x5A - && p4 >= 0x41 && p4 <= 0x5A) - { - string culture = new(new[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 }); - return new CultureInfo(culture); - } - - return null; + string culture = new(new[] { (char)p1, (char)p2, '-', (char)p3, (char)p4 }); + return new CultureInfo(culture); } + + return null; } + } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccTextDescriptionTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccTextDescriptionTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccTextDescriptionTagDataEntry other) + /// + public bool Equals(IccTextDescriptionTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } + return false; + } - return base.Equals(other) - && string.Equals(this.Ascii, other.Ascii, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.Unicode, other.Unicode, StringComparison.OrdinalIgnoreCase) - && string.Equals(this.ScriptCode, other.ScriptCode, StringComparison.OrdinalIgnoreCase) - && this.UnicodeLanguageCode == other.UnicodeLanguageCode - && this.ScriptCodeCode == other.ScriptCodeCode; + if (ReferenceEquals(this, other)) + { + return true; } - /// - public override bool Equals(object obj) - => obj is IccTextDescriptionTagDataEntry other && this.Equals(other); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.Ascii, - this.Unicode, - this.ScriptCode, - this.UnicodeLanguageCode, - this.ScriptCodeCode); + return base.Equals(other) + && string.Equals(this.Ascii, other.Ascii, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.Unicode, other.Unicode, StringComparison.OrdinalIgnoreCase) + && string.Equals(this.ScriptCode, other.ScriptCode, StringComparison.OrdinalIgnoreCase) + && this.UnicodeLanguageCode == other.UnicodeLanguageCode + && this.ScriptCodeCode == other.ScriptCodeCode; } + + /// + public override bool Equals(object obj) + => obj is IccTextDescriptionTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.Ascii, + this.Unicode, + this.ScriptCode, + this.UnicodeLanguageCode, + this.ScriptCodeCode); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs index 9463f665a8..feb5e4f4d1 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextTagDataEntry.cs @@ -1,63 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This is a simple text structure that contains a text string. +/// +internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This is a simple text structure that contains a text string. + /// Initializes a new instance of the class. /// - internal sealed class IccTextTagDataEntry : IccTagDataEntry, IEquatable + /// The Text + public IccTextTagDataEntry(string text) + : this(text, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The Text - public IccTextTagDataEntry(string text) - : this(text, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The Text - /// Tag Signature - public IccTextTagDataEntry(string text, IccProfileTag tagSignature) - : base(IccTypeSignature.Text, tagSignature) - => this.Text = text ?? throw new ArgumentNullException(nameof(text)); - - /// - /// Gets the Text - /// - public string Text { get; } - - /// - public override bool Equals(IccTagDataEntry other) - => other is IccTextTagDataEntry entry && this.Equals(entry); - - /// - public bool Equals(IccTextTagDataEntry other) - { - if (other is null) - { - return false; - } + /// + /// Initializes a new instance of the class. + /// + /// The Text + /// Tag Signature + public IccTextTagDataEntry(string text, IccProfileTag tagSignature) + : base(IccTypeSignature.Text, tagSignature) + => this.Text = text ?? throw new ArgumentNullException(nameof(text)); - if (ReferenceEquals(this, other)) - { - return true; - } + /// + /// Gets the Text + /// + public string Text { get; } - return base.Equals(other) && string.Equals(this.Text, other.Text, StringComparison.OrdinalIgnoreCase); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccTextTagDataEntry entry && this.Equals(entry); + + /// + public bool Equals(IccTextTagDataEntry other) + { + if (other is null) + { + return false; } - /// - public override bool Equals(object obj) - => obj is IccTextTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Text); + return base.Equals(other) && string.Equals(this.Text, other.Text, StringComparison.OrdinalIgnoreCase); } + + /// + public override bool Equals(object obj) + => obj is IccTextTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Text); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs index 839f7962b7..6763fc20c9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUFix16ArrayTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of doubles (from 32bit values). +/// +internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of doubles (from 32bit values). + /// Initializes a new instance of the class. /// - internal sealed class IccUFix16ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccUFix16ArrayTagDataEntry(float[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUFix16ArrayTagDataEntry(float[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.U16Fixed16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUFix16ArrayTagDataEntry(float[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.U16Fixed16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data. + /// + public float[] Data { get; } - /// - /// Gets the array data. - /// - public float[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUFix16ArrayTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccUFix16ArrayTagDataEntry other) + { + if (other is null) { - return other is IccUFix16ArrayTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccUFix16ArrayTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccUFix16ArrayTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccUFix16ArrayTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs index 072a45ea46..55d23976d9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt16ArrayTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of unsigned shorts. +/// +internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of unsigned shorts. + /// Initializes a new instance of the class. /// - internal sealed class IccUInt16ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccUInt16ArrayTagDataEntry(ushort[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt16ArrayTagDataEntry(ushort[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt16Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt16ArrayTagDataEntry(ushort[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt16Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public ushort[] Data { get; } - /// - /// Gets the array data - /// - public ushort[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt16ArrayTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccUInt16ArrayTagDataEntry other) + { + if (other is null) { - return other is IccUInt16ArrayTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccUInt16ArrayTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccUInt16ArrayTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccUInt16ArrayTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs index 40d6f2d0e9..ffc4589ec2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt32ArrayTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of unsigned 32bit integers. +/// +internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of unsigned 32bit integers. + /// Initializes a new instance of the class. /// - internal sealed class IccUInt32ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccUInt32ArrayTagDataEntry(uint[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt32ArrayTagDataEntry(uint[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt32Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt32ArrayTagDataEntry(uint[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt32Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data + /// + public uint[] Data { get; } - /// - /// Gets the array data - /// - public uint[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt32ArrayTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccUInt32ArrayTagDataEntry other) + { + if (other is null) { - return other is IccUInt32ArrayTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccUInt32ArrayTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccUInt32ArrayTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccUInt32ArrayTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs index 2ffa7dec2f..b5f94dfdf9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt64ArrayTagDataEntry.cs @@ -1,60 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of unsigned 64bit integers. +/// +internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of unsigned 64bit integers. + /// Initializes a new instance of the class. /// - internal sealed class IccUInt64ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccUInt64ArrayTagDataEntry(ulong[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt64ArrayTagDataEntry(ulong[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt64Array, tagSignature) => this.Data = data ?? throw new ArgumentNullException(nameof(data)); + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt64ArrayTagDataEntry(ulong[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt64Array, tagSignature) => this.Data = data ?? throw new ArgumentNullException(nameof(data)); - /// - /// Gets the array data - /// - public ulong[] Data { get; } + /// + /// Gets the array data + /// + public ulong[] Data { get; } - /// - public override bool Equals(IccTagDataEntry other) => other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) => other is IccUInt64ArrayTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccUInt64ArrayTagDataEntry other) + /// + public bool Equals(IccUInt64ArrayTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return false; } - /// - public override bool Equals(object obj) => obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); } + + /// + public override bool Equals(object obj) => obj is IccUInt64ArrayTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs index 0077d628f3..3e8a8c3edd 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUInt8ArrayTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type represents an array of bytes. +/// +internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents an array of bytes. + /// Initializes a new instance of the class. /// - internal sealed class IccUInt8ArrayTagDataEntry : IccTagDataEntry, IEquatable + /// The array data + public IccUInt8ArrayTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The array data - public IccUInt8ArrayTagDataEntry(byte[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The array data - /// Tag Signature - public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.UInt8Array, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The array data + /// Tag Signature + public IccUInt8ArrayTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.UInt8Array, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the array data. + /// + public byte[] Data { get; } - /// - /// Gets the array data. - /// - public byte[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUInt8ArrayTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccUInt8ArrayTagDataEntry other) + { + if (other is null) { - return other is IccUInt8ArrayTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccUInt8ArrayTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccUInt8ArrayTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccUInt8ArrayTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs index d806ca2a56..bfa76391e8 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUcrBgTagDataEntry.cs @@ -1,90 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This type contains curves representing the under color removal and black generation +/// and a text string which is a general description of the method used for the UCR and BG. +/// +internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type contains curves representing the under color removal and black generation - /// and a text string which is a general description of the method used for the UCR and BG. + /// Initializes a new instance of the class. /// - internal sealed class IccUcrBgTagDataEntry : IccTagDataEntry, IEquatable + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description) + : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// UCR (under color removal) curve values - /// BG (black generation) curve values - /// Description of the used UCR and BG method - public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description) - : this(ucrCurve, bgCurve, description, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// UCR (under color removal) curve values - /// BG (black generation) curve values - /// Description of the used UCR and BG method - /// Tag Signature - public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature) - : base(IccTypeSignature.UcrBg, tagSignature) - { - this.UcrCurve = ucrCurve ?? throw new ArgumentNullException(nameof(ucrCurve)); - this.BgCurve = bgCurve ?? throw new ArgumentNullException(nameof(bgCurve)); - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - } + /// + /// Initializes a new instance of the class. + /// + /// UCR (under color removal) curve values + /// BG (black generation) curve values + /// Description of the used UCR and BG method + /// Tag Signature + public IccUcrBgTagDataEntry(ushort[] ucrCurve, ushort[] bgCurve, string description, IccProfileTag tagSignature) + : base(IccTypeSignature.UcrBg, tagSignature) + { + this.UcrCurve = ucrCurve ?? throw new ArgumentNullException(nameof(ucrCurve)); + this.BgCurve = bgCurve ?? throw new ArgumentNullException(nameof(bgCurve)); + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + } - /// - /// Gets the UCR (under color removal) curve values - /// - public ushort[] UcrCurve { get; } + /// + /// Gets the UCR (under color removal) curve values + /// + public ushort[] UcrCurve { get; } - /// - /// Gets the BG (black generation) curve values - /// - public ushort[] BgCurve { get; } + /// + /// Gets the BG (black generation) curve values + /// + public ushort[] BgCurve { get; } - /// - /// Gets a description of the used UCR and BG method - /// - public string Description { get; } + /// + /// Gets a description of the used UCR and BG method + /// + public string Description { get; } - /// - public override bool Equals(IccTagDataEntry other) - => other is IccUcrBgTagDataEntry entry && this.Equals(entry); + /// + public override bool Equals(IccTagDataEntry other) + => other is IccUcrBgTagDataEntry entry && this.Equals(entry); - /// - public bool Equals(IccUcrBgTagDataEntry other) + /// + public bool Equals(IccUcrBgTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.UcrCurve.AsSpan().SequenceEqual(other.UcrCurve) - && this.BgCurve.AsSpan().SequenceEqual(other.BgCurve) - && string.Equals(this.Description, other.Description, StringComparison.OrdinalIgnoreCase); + return false; } - /// - public override bool Equals(object obj) - => obj is IccUcrBgTagDataEntry other && this.Equals(other); + if (ReferenceEquals(this, other)) + { + return true; + } - /// - public override int GetHashCode() - => HashCode.Combine( - this.Signature, - this.UcrCurve, - this.BgCurve, - this.Description); + return base.Equals(other) + && this.UcrCurve.AsSpan().SequenceEqual(other.UcrCurve) + && this.BgCurve.AsSpan().SequenceEqual(other.BgCurve) + && string.Equals(this.Description, other.Description, StringComparison.OrdinalIgnoreCase); } + + /// + public override bool Equals(object obj) + => obj is IccUcrBgTagDataEntry other && this.Equals(other); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Signature, + this.UcrCurve, + this.BgCurve, + this.Description); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs index 81c8bcb9d2..d580002adf 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccUnknownTagDataEntry.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// This tag stores data of an unknown tag data entry +/// +internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This tag stores data of an unknown tag data entry + /// Initializes a new instance of the class. /// - internal sealed class IccUnknownTagDataEntry : IccTagDataEntry, IEquatable + /// The raw data of the entry + public IccUnknownTagDataEntry(byte[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The raw data of the entry - public IccUnknownTagDataEntry(byte[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The raw data of the entry - /// Tag Signature - public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.Unknown, tagSignature) - { - this.Data = data ?? throw new ArgumentNullException(nameof(data)); - } + /// + /// Initializes a new instance of the class. + /// + /// The raw data of the entry + /// Tag Signature + public IccUnknownTagDataEntry(byte[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Unknown, tagSignature) + { + this.Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + /// + /// Gets the raw data of the entry. + /// + public byte[] Data { get; } - /// - /// Gets the raw data of the entry. - /// - public byte[] Data { get; } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccUnknownTagDataEntry entry && this.Equals(entry); + } - /// - public override bool Equals(IccTagDataEntry other) + /// + public bool Equals(IccUnknownTagDataEntry other) + { + if (other is null) { - return other is IccUnknownTagDataEntry entry && this.Equals(entry); + return false; } - /// - public bool Equals(IccUnknownTagDataEntry other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + return true; } - /// - public override bool Equals(object obj) - { - return obj is IccUnknownTagDataEntry other && this.Equals(other); - } + return base.Equals(other) && this.Data.AsSpan().SequenceEqual(other.Data); + } - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); + /// + public override bool Equals(object obj) + { + return obj is IccUnknownTagDataEntry other && this.Equals(other); } + + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs index 257b464149..685212ed1b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccViewingConditionsTagDataEntry.cs @@ -1,96 +1,94 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// This type represents a set of viewing condition parameters. +/// +internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable { /// - /// This type represents a set of viewing condition parameters. + /// Initializes a new instance of the class. /// - internal sealed class IccViewingConditionsTagDataEntry : IccTagDataEntry, IEquatable + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant) + : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// XYZ values of Illuminant - /// XYZ values of Surrounding - /// Illuminant - public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant) - : this(illuminantXyz, surroundXyz, illuminant, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// XYZ values of Illuminant - /// XYZ values of Surrounding - /// Illuminant - /// Tag Signature - public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature) - : base(IccTypeSignature.ViewingConditions, tagSignature) - { - this.IlluminantXyz = illuminantXyz; - this.SurroundXyz = surroundXyz; - this.Illuminant = illuminant; - } + /// + /// Initializes a new instance of the class. + /// + /// XYZ values of Illuminant + /// XYZ values of Surrounding + /// Illuminant + /// Tag Signature + public IccViewingConditionsTagDataEntry(Vector3 illuminantXyz, Vector3 surroundXyz, IccStandardIlluminant illuminant, IccProfileTag tagSignature) + : base(IccTypeSignature.ViewingConditions, tagSignature) + { + this.IlluminantXyz = illuminantXyz; + this.SurroundXyz = surroundXyz; + this.Illuminant = illuminant; + } - /// - /// Gets the XYZ values of illuminant. - /// - public Vector3 IlluminantXyz { get; } + /// + /// Gets the XYZ values of illuminant. + /// + public Vector3 IlluminantXyz { get; } - /// - /// Gets the XYZ values of Surrounding - /// - public Vector3 SurroundXyz { get; } + /// + /// Gets the XYZ values of Surrounding + /// + public Vector3 SurroundXyz { get; } - /// - /// Gets the illuminant. - /// - public IccStandardIlluminant Illuminant { get; } + /// + /// Gets the illuminant. + /// + public IccStandardIlluminant Illuminant { get; } - /// - public override bool Equals(IccTagDataEntry other) - { - return other is IccViewingConditionsTagDataEntry entry && this.Equals(entry); - } + /// + public override bool Equals(IccTagDataEntry other) + { + return other is IccViewingConditionsTagDataEntry entry && this.Equals(entry); + } - /// - public bool Equals(IccViewingConditionsTagDataEntry other) + /// + public bool Equals(IccViewingConditionsTagDataEntry other) + { + if (other is null) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - return base.Equals(other) - && this.IlluminantXyz.Equals(other.IlluminantXyz) - && this.SurroundXyz.Equals(other.SurroundXyz) - && this.Illuminant == other.Illuminant; + return false; } - /// - public override bool Equals(object obj) + if (ReferenceEquals(this, other)) { - return obj is IccViewingConditionsTagDataEntry other && this.Equals(other); + return true; } - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Signature, - this.IlluminantXyz, - this.SurroundXyz, - this.Illuminant); - } + return base.Equals(other) + && this.IlluminantXyz.Equals(other.IlluminantXyz) + && this.SurroundXyz.Equals(other.SurroundXyz) + && this.Illuminant == other.Illuminant; + } + + /// + public override bool Equals(object obj) + { + return obj is IccViewingConditionsTagDataEntry other && this.Equals(other); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Signature, + this.IlluminantXyz, + this.SurroundXyz, + this.Illuminant); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs index 8a30802406..0dcb12de9a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -1,59 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// The XYZType contains an array of XYZ values. +/// +internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable { /// - /// The XYZType contains an array of XYZ values. + /// Initializes a new instance of the class. /// - internal sealed class IccXyzTagDataEntry : IccTagDataEntry, IEquatable + /// The XYZ numbers. + public IccXyzTagDataEntry(Vector3[] data) + : this(data, IccProfileTag.Unknown) { - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers. - public IccXyzTagDataEntry(Vector3[] data) - : this(data, IccProfileTag.Unknown) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The XYZ numbers - /// Tag Signature - public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) - : base(IccTypeSignature.Xyz, tagSignature) - => this.Data = data ?? throw new ArgumentNullException(nameof(data)); - - /// - /// Gets the XYZ numbers. - /// - public Vector3[] Data { get; } - - /// - public override bool Equals(IccTagDataEntry other) - { - if (base.Equals(other) && other is IccXyzTagDataEntry entry) - { - return this.Data.AsSpan().SequenceEqual(entry.Data); - } + /// + /// Initializes a new instance of the class. + /// + /// The XYZ numbers + /// Tag Signature + public IccXyzTagDataEntry(Vector3[] data, IccProfileTag tagSignature) + : base(IccTypeSignature.Xyz, tagSignature) + => this.Data = data ?? throw new ArgumentNullException(nameof(data)); - return false; + /// + /// Gets the XYZ numbers. + /// + public Vector3[] Data { get; } + + /// + public override bool Equals(IccTagDataEntry other) + { + if (base.Equals(other) && other is IccXyzTagDataEntry entry) + { + return this.Data.AsSpan().SequenceEqual(entry.Data); } - /// - public bool Equals(IccXyzTagDataEntry other) - => this.Equals((IccTagDataEntry)other); + return false; + } - /// - public override bool Equals(object obj) - => this.Equals(obj as IccXyzTagDataEntry); + /// + public bool Equals(IccXyzTagDataEntry other) + => this.Equals((IccTagDataEntry)other); - public override int GetHashCode() - => HashCode.Combine(base.GetHashCode(), this.Data); - } + /// + public override bool Equals(object obj) + => this.Equals(obj as IccXyzTagDataEntry); + + public override int GetHashCode() + => HashCode.Combine(base.GetHashCode(), this.Data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs index 08b0948631..6afab48c7d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccClut.cs @@ -1,187 +1,183 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Color Lookup Table +/// +internal sealed class IccClut : IEquatable { /// - /// Color Lookup Table + /// Initializes a new instance of the class. /// - internal sealed class IccClut : IEquatable + /// The CLUT values + /// The gridpoint count + /// The data type of this CLUT + public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type) { - /// - /// Initializes a new instance of the class. - /// - /// The CLUT values - /// The gridpoint count - /// The data type of this CLUT - public IccClut(float[][] values, byte[] gridPointCount, IccClutDataType type) - { - Guard.NotNull(values, nameof(values)); - Guard.NotNull(gridPointCount, nameof(gridPointCount)); - - this.Values = values; - this.DataType = type; - this.InputChannelCount = gridPointCount.Length; - this.OutputChannelCount = values[0].Length; - this.GridPointCount = gridPointCount; - this.CheckValues(); - } + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); + + this.Values = values; + this.DataType = type; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } - /// - /// Initializes a new instance of the class. - /// - /// The CLUT values - /// The gridpoint count - public IccClut(ushort[][] values, byte[] gridPointCount) - { - Guard.NotNull(values, nameof(values)); - Guard.NotNull(gridPointCount, nameof(gridPointCount)); + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(ushort[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); - const float Max = ushort.MaxValue; + const float Max = ushort.MaxValue; - this.Values = new float[values.Length][]; - for (int i = 0; i < values.Length; i++) + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) { - this.Values[i] = new float[values[i].Length]; - for (int j = 0; j < values[i].Length; j++) - { - this.Values[i][j] = values[i][j] / Max; - } + this.Values[i][j] = values[i][j] / Max; } - - this.DataType = IccClutDataType.UInt16; - this.InputChannelCount = gridPointCount.Length; - this.OutputChannelCount = values[0].Length; - this.GridPointCount = gridPointCount; - this.CheckValues(); } - /// - /// Initializes a new instance of the class. - /// - /// The CLUT values - /// The gridpoint count - public IccClut(byte[][] values, byte[] gridPointCount) - { - Guard.NotNull(values, nameof(values)); - Guard.NotNull(gridPointCount, nameof(gridPointCount)); + this.DataType = IccClutDataType.UInt16; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The CLUT values + /// The gridpoint count + public IccClut(byte[][] values, byte[] gridPointCount) + { + Guard.NotNull(values, nameof(values)); + Guard.NotNull(gridPointCount, nameof(gridPointCount)); - const float Max = byte.MaxValue; + const float Max = byte.MaxValue; - this.Values = new float[values.Length][]; - for (int i = 0; i < values.Length; i++) + this.Values = new float[values.Length][]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = new float[values[i].Length]; + for (int j = 0; j < values[i].Length; j++) { - this.Values[i] = new float[values[i].Length]; - for (int j = 0; j < values[i].Length; j++) - { - this.Values[i][j] = values[i][j] / Max; - } + this.Values[i][j] = values[i][j] / Max; } + } + + this.DataType = IccClutDataType.UInt8; + this.InputChannelCount = gridPointCount.Length; + this.OutputChannelCount = values[0].Length; + this.GridPointCount = gridPointCount; + this.CheckValues(); + } + + /// + /// Gets the values that make up this table + /// + public float[][] Values { get; } + + /// + /// Gets the CLUT data type (important when writing a profile) + /// + public IccClutDataType DataType { get; } + + /// + /// Gets the number of input channels + /// + public int InputChannelCount { get; } + + /// + /// Gets the number of output channels + /// + public int OutputChannelCount { get; } - this.DataType = IccClutDataType.UInt8; - this.InputChannelCount = gridPointCount.Length; - this.OutputChannelCount = values[0].Length; - this.GridPointCount = gridPointCount; - this.CheckValues(); + /// + /// Gets the number of grid points per input channel + /// + public byte[] GridPointCount { get; } + + /// + public bool Equals(IccClut other) + { + if (other is null) + { + return false; } - /// - /// Gets the values that make up this table - /// - public float[][] Values { get; } - - /// - /// Gets the CLUT data type (important when writing a profile) - /// - public IccClutDataType DataType { get; } - - /// - /// Gets the number of input channels - /// - public int InputChannelCount { get; } - - /// - /// Gets the number of output channels - /// - public int OutputChannelCount { get; } - - /// - /// Gets the number of grid points per input channel - /// - public byte[] GridPointCount { get; } - - /// - public bool Equals(IccClut other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } + return true; + } - if (ReferenceEquals(this, other)) - { - return true; - } + return this.EqualsValuesArray(other) + && this.DataType == other.DataType + && this.InputChannelCount == other.InputChannelCount + && this.OutputChannelCount == other.OutputChannelCount + && this.GridPointCount.AsSpan().SequenceEqual(other.GridPointCount); + } - return this.EqualsValuesArray(other) - && this.DataType == other.DataType - && this.InputChannelCount == other.InputChannelCount - && this.OutputChannelCount == other.OutputChannelCount - && this.GridPointCount.AsSpan().SequenceEqual(other.GridPointCount); - } + /// + public override bool Equals(object obj) => obj is IccClut other && this.Equals(other); - /// - public override bool Equals(object obj) => obj is IccClut other && this.Equals(other); + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Values, + this.DataType, + this.InputChannelCount, + this.OutputChannelCount, + this.GridPointCount); + } - /// - public override int GetHashCode() + private bool EqualsValuesArray(IccClut other) + { + if (this.Values.Length != other.Values.Length) { - return HashCode.Combine( - this.Values, - this.DataType, - this.InputChannelCount, - this.OutputChannelCount, - this.GridPointCount); + return false; } - private bool EqualsValuesArray(IccClut other) + for (int i = 0; i < this.Values.Length; i++) { - if (this.Values.Length != other.Values.Length) + if (!this.Values[i].AsSpan().SequenceEqual(other.Values[i])) { return false; } - - for (int i = 0; i < this.Values.Length; i++) - { - if (!this.Values[i].AsSpan().SequenceEqual(other.Values[i])) - { - return false; - } - } - - return true; } - private void CheckValues() - { - Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount)); - Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount)); - - bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount); - Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies"); + return true; + } - int length = 0; - for (int i = 0; i < this.InputChannelCount; i++) - { - length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount); - } + private void CheckValues() + { + Guard.MustBeBetweenOrEqualTo(this.InputChannelCount, 1, 15, nameof(this.InputChannelCount)); + Guard.MustBeBetweenOrEqualTo(this.OutputChannelCount, 1, 15, nameof(this.OutputChannelCount)); - length /= this.InputChannelCount; + bool isLengthDifferent = this.Values.Any(t => t.Length != this.OutputChannelCount); + Guard.IsFalse(isLengthDifferent, nameof(this.Values), "The number of output values varies"); - Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points"); + int length = 0; + for (int i = 0; i < this.InputChannelCount; i++) + { + length += (int)Math.Pow(this.GridPointCount[i], this.InputChannelCount); } + + length /= this.InputChannelCount; + + Guard.IsTrue(this.Values.Length == length, nameof(this.Values), "Length of values array does not match the grid points"); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs index 6c2dfbdeca..46365316f8 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -1,115 +1,112 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Entry of ICC colorant table +/// +internal readonly struct IccColorantTableEntry : IEquatable { /// - /// Entry of ICC colorant table + /// Initializes a new instance of the struct. /// - internal readonly struct IccColorantTableEntry : IEquatable + /// Name of the colorant + public IccColorantTableEntry(string name) + : this(name, 0, 0, 0) { - /// - /// Initializes a new instance of the struct. - /// - /// Name of the colorant - public IccColorantTableEntry(string name) - : this(name, 0, 0, 0) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// Name of the colorant - /// First PCS value - /// Second PCS value - /// Third PCS value - public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3) - { - this.Name = name ?? throw new ArgumentNullException(nameof(name)); - this.Pcs1 = pcs1; - this.Pcs2 = pcs2; - this.Pcs3 = pcs3; - } + } - /// - /// Gets the colorant name. - /// - public string Name { get; } + /// + /// Initializes a new instance of the struct. + /// + /// Name of the colorant + /// First PCS value + /// Second PCS value + /// Third PCS value + public IccColorantTableEntry(string name, ushort pcs1, ushort pcs2, ushort pcs3) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.Pcs1 = pcs1; + this.Pcs2 = pcs2; + this.Pcs3 = pcs3; + } - /// - /// Gets the first PCS value. - /// - public ushort Pcs1 { get; } + /// + /// Gets the colorant name. + /// + public string Name { get; } - /// - /// Gets the second PCS value. - /// - public ushort Pcs2 { get; } + /// + /// Gets the first PCS value. + /// + public ushort Pcs1 { get; } - /// - /// Gets the third PCS value. - /// - public ushort Pcs3 { get; } + /// + /// Gets the second PCS value. + /// + public ushort Pcs2 { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right) - { - return left.Equals(right); - } + /// + /// Gets the third PCS value. + /// + public ushort Pcs3 { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccColorantTableEntry left, IccColorantTableEntry right) + { + return left.Equals(right); + } - /// - public override bool Equals(object obj) - { - return obj is IccColorantTableEntry other && this.Equals(other); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccColorantTableEntry left, IccColorantTableEntry right) + { + return !left.Equals(right); + } - /// - public bool Equals(IccColorantTableEntry other) - { - return this.Name == other.Name - && this.Pcs1 == other.Pcs1 - && this.Pcs2 == other.Pcs2 - && this.Pcs3 == other.Pcs3; - } + /// + public override bool Equals(object obj) + { + return obj is IccColorantTableEntry other && this.Equals(other); + } - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.Name, - this.Pcs1, - this.Pcs2, - this.Pcs3); - } + /// + public bool Equals(IccColorantTableEntry other) + { + return this.Name == other.Name + && this.Pcs1 == other.Pcs1 + && this.Pcs2 == other.Pcs2 + && this.Pcs3 == other.Pcs3; + } - /// - public override string ToString() => $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}"; + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.Name, + this.Pcs1, + this.Pcs2, + this.Pcs3); } + + /// + public override string ToString() => $"{this.Name}: {this.Pcs1}; {this.Pcs2}; {this.Pcs3}"; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs index 63f97d45c8..e97cd6b136 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLocalizedString.cs @@ -1,60 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// A string with a specific locale. +/// +internal readonly struct IccLocalizedString : IEquatable { /// - /// A string with a specific locale. + /// Initializes a new instance of the struct. + /// The culture will be + /// + /// The text value of this string + public IccLocalizedString(string text) + : this(CultureInfo.CurrentCulture, text) + { + } + + /// + /// Initializes a new instance of the struct. + /// The culture will be /// - internal readonly struct IccLocalizedString : IEquatable + /// The culture of this string + /// The text value of this string + public IccLocalizedString(CultureInfo culture, string text) { - /// - /// Initializes a new instance of the struct. - /// The culture will be - /// - /// The text value of this string - public IccLocalizedString(string text) - : this(CultureInfo.CurrentCulture, text) - { - } - - /// - /// Initializes a new instance of the struct. - /// The culture will be - /// - /// The culture of this string - /// The text value of this string - public IccLocalizedString(CultureInfo culture, string text) - { - this.Culture = culture ?? throw new ArgumentNullException(nameof(culture)); - this.Text = text ?? throw new ArgumentNullException(nameof(text)); - } - - /// - /// Gets the text value. - /// - public string Text { get; } - - /// - /// Gets the culture of text. - /// - public CultureInfo Culture { get; } - - /// - public bool Equals(IccLocalizedString other) => - this.Culture.Equals(other.Culture) && - this.Text == other.Text; - - /// - public override string ToString() => $"{this.Culture.Name}: {this.Text}"; - - public override bool Equals(object obj) - => obj is IccLocalizedString iccLocalizedString && this.Equals(iccLocalizedString); - - public override int GetHashCode() - => HashCode.Combine(this.Culture, this.Text); + this.Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + this.Text = text ?? throw new ArgumentNullException(nameof(text)); } + + /// + /// Gets the text value. + /// + public string Text { get; } + + /// + /// Gets the culture of text. + /// + public CultureInfo Culture { get; } + + /// + public bool Equals(IccLocalizedString other) => + this.Culture.Equals(other.Culture) && + this.Text == other.Text; + + /// + public override string ToString() => $"{this.Culture.Name}: {this.Text}"; + + public override bool Equals(object obj) + => obj is IccLocalizedString iccLocalizedString && this.Equals(iccLocalizedString); + + public override int GetHashCode() + => HashCode.Combine(this.Culture, this.Text); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs index b5709db4d7..93675099d9 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Lookup Table +/// +internal readonly struct IccLut : IEquatable { /// - /// Lookup Table + /// Initializes a new instance of the struct. /// - internal readonly struct IccLut : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(float[] values) - => this.Values = values ?? throw new ArgumentNullException(nameof(values)); + /// The LUT values + public IccLut(float[] values) + => this.Values = values ?? throw new ArgumentNullException(nameof(values)); - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(ushort[] values) - { - Guard.NotNull(values, nameof(values)); + /// + /// Initializes a new instance of the struct. + /// + /// The LUT values + public IccLut(ushort[] values) + { + Guard.NotNull(values, nameof(values)); - const float max = ushort.MaxValue; + const float max = ushort.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; } + } - /// - /// Initializes a new instance of the struct. - /// - /// The LUT values - public IccLut(byte[] values) - { - Guard.NotNull(values, nameof(values)); + /// + /// Initializes a new instance of the struct. + /// + /// The LUT values + public IccLut(byte[] values) + { + Guard.NotNull(values, nameof(values)); - const float max = byte.MaxValue; + const float max = byte.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + this.Values = new float[values.Length]; + for (int i = 0; i < values.Length; i++) + { + this.Values[i] = values[i] / max; } + } - /// - /// Gets the values that make up this table - /// - public float[] Values { get; } + /// + /// Gets the values that make up this table + /// + public float[] Values { get; } - /// - public bool Equals(IccLut other) + /// + public bool Equals(IccLut other) + { + if (ReferenceEquals(this.Values, other.Values)) { - if (ReferenceEquals(this.Values, other.Values)) - { - return true; - } - - return this.Values.AsSpan().SequenceEqual(other.Values); + return true; } - /// - public override bool Equals(object obj) - => obj is IccLut iccLut && this.Equals(iccLut); - - /// - public override int GetHashCode() - => this.Values.GetHashCode(); + return this.Values.AsSpan().SequenceEqual(other.Values); } + + /// + public override bool Equals(object obj) + => obj is IccLut iccLut && this.Equals(iccLut); + + /// + public override int GetHashCode() + => this.Values.GetHashCode(); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs index 8010f78bc0..e79a8cf516 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccNamedColor.cs @@ -1,88 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// A specific color with a name +/// +internal readonly struct IccNamedColor : IEquatable { /// - /// A specific color with a name + /// Initializes a new instance of the struct. /// - internal readonly struct IccNamedColor : IEquatable + /// Name of the color + /// Coordinates of the color in the profiles PCS + /// Coordinates of the color in the profiles Device-Space + public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates) { - /// - /// Initializes a new instance of the struct. - /// - /// Name of the color - /// Coordinates of the color in the profiles PCS - /// Coordinates of the color in the profiles Device-Space - public IccNamedColor(string name, ushort[] pcsCoordinates, ushort[] deviceCoordinates) - { - Guard.NotNull(name, nameof(name)); - Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates)); - Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3"); + Guard.NotNull(name, nameof(name)); + Guard.NotNull(pcsCoordinates, nameof(pcsCoordinates)); + Guard.IsTrue(pcsCoordinates.Length == 3, nameof(pcsCoordinates), "Must have a length of 3"); - this.Name = name; - this.PcsCoordinates = pcsCoordinates; - this.DeviceCoordinates = deviceCoordinates; - } + this.Name = name; + this.PcsCoordinates = pcsCoordinates; + this.DeviceCoordinates = deviceCoordinates; + } - /// - /// Gets the name of the color - /// - public string Name { get; } + /// + /// Gets the name of the color + /// + public string Name { get; } - /// - /// Gets the coordinates of the color in the profiles PCS - /// - public ushort[] PcsCoordinates { get; } + /// + /// Gets the coordinates of the color in the profiles PCS + /// + public ushort[] PcsCoordinates { get; } - /// - /// Gets the coordinates of the color in the profiles Device-Space - /// - public ushort[] DeviceCoordinates { get; } + /// + /// Gets the coordinates of the color in the profiles Device-Space + /// + public ushort[] DeviceCoordinates { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccNamedColor left, IccNamedColor right) => left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccNamedColor left, IccNamedColor right) => left.Equals(right); - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccNamedColor left, IccNamedColor right) => !left.Equals(right); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccNamedColor left, IccNamedColor right) => !left.Equals(right); - /// - public override bool Equals(object obj) => obj is IccNamedColor other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is IccNamedColor other && this.Equals(other); - /// - public bool Equals(IccNamedColor other) - => this.Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) - && this.PcsCoordinates.AsSpan().SequenceEqual(other.PcsCoordinates) - && this.DeviceCoordinates.AsSpan().SequenceEqual(other.DeviceCoordinates); + /// + public bool Equals(IccNamedColor other) + => this.Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) + && this.PcsCoordinates.AsSpan().SequenceEqual(other.PcsCoordinates) + && this.DeviceCoordinates.AsSpan().SequenceEqual(other.DeviceCoordinates); - /// - public override int GetHashCode() - => HashCode.Combine( - this.Name, - this.PcsCoordinates, - this.DeviceCoordinates); + /// + public override int GetHashCode() + => HashCode.Combine( + this.Name, + this.PcsCoordinates, + this.DeviceCoordinates); - /// - public override string ToString() => this.Name; - } + /// + public override string ToString() => this.Name; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs index 01f624705a..6a89fbda7e 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccPositionNumber.cs @@ -1,81 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Position of an object within an ICC profile +/// +internal readonly struct IccPositionNumber : IEquatable { /// - /// Position of an object within an ICC profile + /// Initializes a new instance of the struct. /// - internal readonly struct IccPositionNumber : IEquatable + /// Offset in bytes + /// Size in bytes + public IccPositionNumber(uint offset, uint size) { - /// - /// Initializes a new instance of the struct. - /// - /// Offset in bytes - /// Size in bytes - public IccPositionNumber(uint offset, uint size) - { - this.Offset = offset; - this.Size = size; - } + this.Offset = offset; + this.Size = size; + } - /// - /// Gets the offset in bytes - /// - public uint Offset { get; } + /// + /// Gets the offset in bytes + /// + public uint Offset { get; } - /// - /// Gets the size in bytes - /// - public uint Size { get; } + /// + /// Gets the size in bytes + /// + public uint Size { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccPositionNumber left, IccPositionNumber right) - { - return left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccPositionNumber left, IccPositionNumber right) + { + return left.Equals(right); + } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccPositionNumber left, IccPositionNumber right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccPositionNumber left, IccPositionNumber right) + { + return !left.Equals(right); + } - /// - public override bool Equals(object obj) - { - return obj is IccPositionNumber other && this.Equals(other); - } + /// + public override bool Equals(object obj) + { + return obj is IccPositionNumber other && this.Equals(other); + } - /// - public bool Equals(IccPositionNumber other) => - this.Offset == other.Offset && - this.Size == other.Size; + /// + public bool Equals(IccPositionNumber other) => + this.Offset == other.Offset && + this.Size == other.Size; - /// - public override int GetHashCode() => unchecked((int)(this.Offset ^ this.Size)); + /// + public override int GetHashCode() => unchecked((int)(this.Offset ^ this.Size)); - /// - public override string ToString() => $"{this.Offset}; {this.Size}"; - } + /// + public override string ToString() => $"{this.Offset}; {this.Size}"; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs index 4178127c68..ad29e99723 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileDescription.cs @@ -1,92 +1,89 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// ICC Profile description +/// +internal readonly struct IccProfileDescription : IEquatable { /// - /// ICC Profile description + /// Initializes a new instance of the struct. /// - internal readonly struct IccProfileDescription : IEquatable + /// Device Manufacturer + /// Device Model + /// Device Attributes + /// Technology Information + /// Device Manufacturer Info + /// Device Model Info + public IccProfileDescription( + uint deviceManufacturer, + uint deviceModel, + IccDeviceAttribute deviceAttributes, + IccProfileTag technologyInformation, + IccLocalizedString[] deviceManufacturerInfo, + IccLocalizedString[] deviceModelInfo) { - /// - /// Initializes a new instance of the struct. - /// - /// Device Manufacturer - /// Device Model - /// Device Attributes - /// Technology Information - /// Device Manufacturer Info - /// Device Model Info - public IccProfileDescription( - uint deviceManufacturer, - uint deviceModel, - IccDeviceAttribute deviceAttributes, - IccProfileTag technologyInformation, - IccLocalizedString[] deviceManufacturerInfo, - IccLocalizedString[] deviceModelInfo) - { - this.DeviceManufacturer = deviceManufacturer; - this.DeviceModel = deviceModel; - this.DeviceAttributes = deviceAttributes; - this.TechnologyInformation = technologyInformation; - this.DeviceManufacturerInfo = deviceManufacturerInfo ?? throw new ArgumentNullException(nameof(deviceManufacturerInfo)); - this.DeviceModelInfo = deviceModelInfo ?? throw new ArgumentNullException(nameof(deviceModelInfo)); - } + this.DeviceManufacturer = deviceManufacturer; + this.DeviceModel = deviceModel; + this.DeviceAttributes = deviceAttributes; + this.TechnologyInformation = technologyInformation; + this.DeviceManufacturerInfo = deviceManufacturerInfo ?? throw new ArgumentNullException(nameof(deviceManufacturerInfo)); + this.DeviceModelInfo = deviceModelInfo ?? throw new ArgumentNullException(nameof(deviceModelInfo)); + } - /// - /// Gets the device manufacturer. - /// - public uint DeviceManufacturer { get; } + /// + /// Gets the device manufacturer. + /// + public uint DeviceManufacturer { get; } - /// - /// Gets the device model. - /// - public uint DeviceModel { get; } + /// + /// Gets the device model. + /// + public uint DeviceModel { get; } - /// - /// Gets the device attributes. - /// - public IccDeviceAttribute DeviceAttributes { get; } + /// + /// Gets the device attributes. + /// + public IccDeviceAttribute DeviceAttributes { get; } - /// - /// Gets the technology information. - /// - public IccProfileTag TechnologyInformation { get; } + /// + /// Gets the technology information. + /// + public IccProfileTag TechnologyInformation { get; } - /// - /// Gets the device manufacturer info. - /// - public IccLocalizedString[] DeviceManufacturerInfo { get; } + /// + /// Gets the device manufacturer info. + /// + public IccLocalizedString[] DeviceManufacturerInfo { get; } - /// - /// Gets the device model info. - /// - public IccLocalizedString[] DeviceModelInfo { get; } + /// + /// Gets the device model info. + /// + public IccLocalizedString[] DeviceModelInfo { get; } - /// - public bool Equals(IccProfileDescription other) => - this.DeviceManufacturer == other.DeviceManufacturer - && this.DeviceModel == other.DeviceModel - && this.DeviceAttributes == other.DeviceAttributes - && this.TechnologyInformation == other.TechnologyInformation - && this.DeviceManufacturerInfo.AsSpan().SequenceEqual(other.DeviceManufacturerInfo) - && this.DeviceModelInfo.AsSpan().SequenceEqual(other.DeviceModelInfo); + /// + public bool Equals(IccProfileDescription other) => + this.DeviceManufacturer == other.DeviceManufacturer + && this.DeviceModel == other.DeviceModel + && this.DeviceAttributes == other.DeviceAttributes + && this.TechnologyInformation == other.TechnologyInformation + && this.DeviceManufacturerInfo.AsSpan().SequenceEqual(other.DeviceManufacturerInfo) + && this.DeviceModelInfo.AsSpan().SequenceEqual(other.DeviceModelInfo); - /// - public override bool Equals(object obj) => obj is IccProfileDescription other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is IccProfileDescription other && this.Equals(other); - /// - public override int GetHashCode() - { - return HashCode.Combine( - this.DeviceManufacturer, - this.DeviceModel, - this.DeviceAttributes, - this.TechnologyInformation, - this.DeviceManufacturerInfo, - this.DeviceModelInfo); - } + /// + public override int GetHashCode() + { + return HashCode.Combine( + this.DeviceManufacturer, + this.DeviceModel, + this.DeviceAttributes, + this.TechnologyInformation, + this.DeviceManufacturerInfo, + this.DeviceModelInfo); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs index b4e6e573b3..8b1c43d06d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileId.cs @@ -1,106 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// ICC Profile ID +/// +public readonly struct IccProfileId : IEquatable { /// - /// ICC Profile ID + /// A profile ID with all values set to zero + /// + public static readonly IccProfileId Zero; + + /// + /// Initializes a new instance of the struct. /// - public readonly struct IccProfileId : IEquatable + /// Part 1 of the ID + /// Part 2 of the ID + /// Part 3 of the ID + /// Part 4 of the ID + public IccProfileId(uint p1, uint p2, uint p3, uint p4) { - /// - /// A profile ID with all values set to zero - /// - public static readonly IccProfileId Zero; - - /// - /// Initializes a new instance of the struct. - /// - /// Part 1 of the ID - /// Part 2 of the ID - /// Part 3 of the ID - /// Part 4 of the ID - public IccProfileId(uint p1, uint p2, uint p3, uint p4) - { - this.Part1 = p1; - this.Part2 = p2; - this.Part3 = p3; - this.Part4 = p4; - } - - /// - /// Gets the first part of the ID. - /// - public uint Part1 { get; } - - /// - /// Gets the second part of the ID. - /// - public uint Part2 { get; } - - /// - /// Gets the third part of the ID. - /// - public uint Part3 { get; } - - /// - /// Gets the fourth part of the ID. - /// - public uint Part4 { get; } - - /// - /// Gets a value indicating whether the ID is set or just consists of zeros. - /// - public bool IsSet => !this.Equals(Zero); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccProfileId left, IccProfileId right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccProfileId left, IccProfileId right) => !left.Equals(right); - - /// - public override bool Equals(object obj) => obj is IccProfileId other && this.Equals(other); - - /// - public bool Equals(IccProfileId other) => - this.Part1 == other.Part1 && - this.Part2 == other.Part2 && - this.Part3 == other.Part3 && - this.Part4 == other.Part4; - - /// - public override int GetHashCode() - => HashCode.Combine( - this.Part1, - this.Part2, - this.Part3, - this.Part4); - - /// - public override string ToString() => $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; - - private static string ToHex(uint value) => value.ToString("X", CultureInfo.InvariantCulture).PadLeft(8, '0'); + this.Part1 = p1; + this.Part2 = p2; + this.Part3 = p3; + this.Part4 = p4; } + + /// + /// Gets the first part of the ID. + /// + public uint Part1 { get; } + + /// + /// Gets the second part of the ID. + /// + public uint Part2 { get; } + + /// + /// Gets the third part of the ID. + /// + public uint Part3 { get; } + + /// + /// Gets the fourth part of the ID. + /// + public uint Part4 { get; } + + /// + /// Gets a value indicating whether the ID is set or just consists of zeros. + /// + public bool IsSet => !this.Equals(Zero); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccProfileId left, IccProfileId right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccProfileId left, IccProfileId right) => !left.Equals(right); + + /// + public override bool Equals(object obj) => obj is IccProfileId other && this.Equals(other); + + /// + public bool Equals(IccProfileId other) => + this.Part1 == other.Part1 && + this.Part2 == other.Part2 && + this.Part3 == other.Part3 && + this.Part4 == other.Part4; + + /// + public override int GetHashCode() + => HashCode.Combine( + this.Part1, + this.Part2, + this.Part3, + this.Part4); + + /// + public override string ToString() => $"{ToHex(this.Part1)}-{ToHex(this.Part2)}-{ToHex(this.Part3)}-{ToHex(this.Part4)}"; + + private static string ToHex(uint value) => value.ToString("X", CultureInfo.InvariantCulture).PadLeft(8, '0'); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs index 61129d87d6..093286fe92 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccProfileSequenceIdentifier.cs @@ -1,45 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Description of a profile within a sequence. +/// +internal readonly struct IccProfileSequenceIdentifier : IEquatable { /// - /// Description of a profile within a sequence. + /// Initializes a new instance of the struct. /// - internal readonly struct IccProfileSequenceIdentifier : IEquatable + /// ID of the profile + /// Description of the profile + public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description) { - /// - /// Initializes a new instance of the struct. - /// - /// ID of the profile - /// Description of the profile - public IccProfileSequenceIdentifier(IccProfileId id, IccLocalizedString[] description) - { - this.Id = id; - this.Description = description ?? throw new ArgumentNullException(nameof(description)); - } + this.Id = id; + this.Description = description ?? throw new ArgumentNullException(nameof(description)); + } - /// - /// Gets the ID of the profile. - /// - public IccProfileId Id { get; } + /// + /// Gets the ID of the profile. + /// + public IccProfileId Id { get; } - /// - /// Gets the description of the profile. - /// - public IccLocalizedString[] Description { get; } + /// + /// Gets the description of the profile. + /// + public IccLocalizedString[] Description { get; } - /// - public bool Equals(IccProfileSequenceIdentifier other) => - this.Id.Equals(other.Id) - && this.Description.AsSpan().SequenceEqual(other.Description); + /// + public bool Equals(IccProfileSequenceIdentifier other) => + this.Id.Equals(other.Id) + && this.Description.AsSpan().SequenceEqual(other.Description); - /// - public override bool Equals(object obj) => obj is IccProfileSequenceIdentifier other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is IccProfileSequenceIdentifier other && this.Equals(other); - /// - public override int GetHashCode() => HashCode.Combine(this.Id, this.Description); - } + /// + public override int GetHashCode() => HashCode.Combine(this.Id, this.Description); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs index 9bea4b76a4..5e2d57ec33 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccResponseNumber.cs @@ -1,81 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Associates a normalized device code with a measurement value +/// +internal readonly struct IccResponseNumber : IEquatable { /// - /// Associates a normalized device code with a measurement value + /// Initializes a new instance of the struct. /// - internal readonly struct IccResponseNumber : IEquatable + /// Device Code + /// Measurement Value + public IccResponseNumber(ushort deviceCode, float measurementValue) { - /// - /// Initializes a new instance of the struct. - /// - /// Device Code - /// Measurement Value - public IccResponseNumber(ushort deviceCode, float measurementValue) - { - this.DeviceCode = deviceCode; - this.MeasurementValue = measurementValue; - } + this.DeviceCode = deviceCode; + this.MeasurementValue = measurementValue; + } - /// - /// Gets the device code - /// - public ushort DeviceCode { get; } + /// + /// Gets the device code + /// + public ushort DeviceCode { get; } - /// - /// Gets the measurement value - /// - public float MeasurementValue { get; } + /// + /// Gets the measurement value + /// + public float MeasurementValue { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccResponseNumber left, IccResponseNumber right) - { - return left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccResponseNumber left, IccResponseNumber right) + { + return left.Equals(right); + } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccResponseNumber left, IccResponseNumber right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccResponseNumber left, IccResponseNumber right) + { + return !left.Equals(right); + } - /// - public override bool Equals(object obj) - { - return obj is IccResponseNumber other && this.Equals(other); - } + /// + public override bool Equals(object obj) + { + return obj is IccResponseNumber other && this.Equals(other); + } - /// - public bool Equals(IccResponseNumber other) => - this.DeviceCode == other.DeviceCode && - this.MeasurementValue == other.MeasurementValue; + /// + public bool Equals(IccResponseNumber other) => + this.DeviceCode == other.DeviceCode && + this.MeasurementValue == other.MeasurementValue; - /// - public override int GetHashCode() => HashCode.Combine(this.DeviceCode, this.MeasurementValue); + /// + public override int GetHashCode() => HashCode.Combine(this.DeviceCode, this.MeasurementValue); - /// - public override string ToString() => $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}"; - } + /// + public override string ToString() => $"Code: {this.DeviceCode}; Value: {this.MeasurementValue}"; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs index 3b1a5e2e4d..0666da235e 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccScreeningChannel.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// A single channel of a +/// +[StructLayout(LayoutKind.Sequential)] +internal readonly struct IccScreeningChannel : IEquatable { /// - /// A single channel of a + /// Initializes a new instance of the struct. /// - [StructLayout(LayoutKind.Sequential)] - internal readonly struct IccScreeningChannel : IEquatable + /// Screen frequency + /// Angle in degrees + /// Spot shape + public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape) { - /// - /// Initializes a new instance of the struct. - /// - /// Screen frequency - /// Angle in degrees - /// Spot shape - public IccScreeningChannel(float frequency, float angle, IccScreeningSpotType spotShape) - { - this.Frequency = frequency; - this.Angle = angle; - this.SpotShape = spotShape; - } - - /// - /// Gets the screen frequency. - /// - public float Frequency { get; } + this.Frequency = frequency; + this.Angle = angle; + this.SpotShape = spotShape; + } - /// - /// Gets the angle in degrees. - /// - public float Angle { get; } + /// + /// Gets the screen frequency. + /// + public float Frequency { get; } - /// - /// Gets the spot shape - /// - public IccScreeningSpotType SpotShape { get; } + /// + /// Gets the angle in degrees. + /// + public float Angle { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right) - { - return left.Equals(right); - } + /// + /// Gets the spot shape + /// + public IccScreeningSpotType SpotShape { get; } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccScreeningChannel left, IccScreeningChannel right) + { + return left.Equals(right); + } - /// - public bool Equals(IccScreeningChannel other) => - this.Frequency == other.Frequency && - this.Angle == other.Angle && - this.SpotShape == other.SpotShape; + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccScreeningChannel left, IccScreeningChannel right) + { + return !left.Equals(right); + } - /// - public override bool Equals(object obj) - { - return obj is IccScreeningChannel other && this.Equals(other); - } + /// + public bool Equals(IccScreeningChannel other) => + this.Frequency == other.Frequency && + this.Angle == other.Angle && + this.SpotShape == other.SpotShape; - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Frequency, this.Angle, this.SpotShape); - } + /// + public override bool Equals(object obj) + { + return obj is IccScreeningChannel other && this.Equals(other); + } - /// - public override string ToString() => $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}"; + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Frequency, this.Angle, this.SpotShape); } + + /// + public override string ToString() => $"{this.Frequency}Hz; {this.Angle}°; {this.SpotShape}"; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs index 7022d83f04..539fe723e2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccTagTableEntry.cs @@ -1,86 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Entry of ICC tag table +/// +internal readonly struct IccTagTableEntry : IEquatable { /// - /// Entry of ICC tag table + /// Initializes a new instance of the struct. /// - internal readonly struct IccTagTableEntry : IEquatable + /// Signature of the tag + /// Offset of entry in bytes + /// Size of entry in bytes + public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize) { - /// - /// Initializes a new instance of the struct. - /// - /// Signature of the tag - /// Offset of entry in bytes - /// Size of entry in bytes - public IccTagTableEntry(IccProfileTag signature, uint offset, uint dataSize) - { - this.Signature = signature; - this.Offset = offset; - this.DataSize = dataSize; - } + this.Signature = signature; + this.Offset = offset; + this.DataSize = dataSize; + } - /// - /// Gets the signature of the tag. - /// - public IccProfileTag Signature { get; } + /// + /// Gets the signature of the tag. + /// + public IccProfileTag Signature { get; } - /// - /// Gets the offset of entry in bytes. - /// - public uint Offset { get; } + /// + /// Gets the offset of entry in bytes. + /// + public uint Offset { get; } - /// - /// Gets the size of entry in bytes. - /// - public uint DataSize { get; } + /// + /// Gets the size of entry in bytes. + /// + public uint DataSize { get; } - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right) - { - return left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + public static bool operator ==(IccTagTableEntry left, IccTagTableEntry right) + { + return left.Equals(right); + } - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right) - { - return !left.Equals(right); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + public static bool operator !=(IccTagTableEntry left, IccTagTableEntry right) + { + return !left.Equals(right); + } - /// - public override bool Equals(object obj) => obj is IccTagTableEntry other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is IccTagTableEntry other && this.Equals(other); - /// - public bool Equals(IccTagTableEntry other) => - this.Signature.Equals(other.Signature) && - this.Offset.Equals(other.Offset) && - this.DataSize.Equals(other.DataSize); + /// + public bool Equals(IccTagTableEntry other) => + this.Signature.Equals(other.Signature) && + this.Offset.Equals(other.Offset) && + this.DataSize.Equals(other.DataSize); - /// - public override int GetHashCode() => HashCode.Combine(this.Signature, this.Offset, this.DataSize); + /// + public override int GetHashCode() => HashCode.Combine(this.Signature, this.Offset, this.DataSize); - /// - public override string ToString() => $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})"; - } + /// + public override string ToString() => $"{this.Signature} (Offset: {this.Offset}; Size: {this.DataSize})"; } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs index 4b8cb91f7e..c280c8f372 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccVersion.cs @@ -1,77 +1,74 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Metadata.Profiles.Icc +/// +/// Represents the ICC profile version number. +/// +public readonly struct IccVersion : IEquatable { /// - /// Represents the ICC profile version number. + /// Initializes a new instance of the struct. /// - public readonly struct IccVersion : IEquatable + /// The major version number. + /// The minor version number. + /// The patch version number. + public IccVersion(int major, int minor, int patch) { - /// - /// Initializes a new instance of the struct. - /// - /// The major version number. - /// The minor version number. - /// The patch version number. - public IccVersion(int major, int minor, int patch) - { - this.Major = major; - this.Minor = minor; - this.Patch = patch; - } + this.Major = major; + this.Minor = minor; + this.Patch = patch; + } - /// - /// Gets the major version number. - /// - public int Major { get; } + /// + /// Gets the major version number. + /// + public int Major { get; } - /// - /// Gets the minor version number. - /// - public int Minor { get; } + /// + /// Gets the minor version number. + /// + public int Minor { get; } - /// - /// Gets the patch number. - /// - public int Patch { get; } + /// + /// Gets the patch number. + /// + public int Patch { get; } - /// - /// Returns a value indicating whether the two values are equal. - /// - /// The first value. - /// The second value. - /// if the two value are equal; otherwise, . - public static bool operator ==(IccVersion left, IccVersion right) - => left.Equals(right); + /// + /// Returns a value indicating whether the two values are equal. + /// + /// The first value. + /// The second value. + /// if the two value are equal; otherwise, . + public static bool operator ==(IccVersion left, IccVersion right) + => left.Equals(right); - /// - /// Returns a value indicating whether the two values are not equal. - /// - /// The first value. - /// The second value. - /// if the two value are not equal; otherwise, . - public static bool operator !=(IccVersion left, IccVersion right) - => !(left == right); + /// + /// Returns a value indicating whether the two values are not equal. + /// + /// The first value. + /// The second value. + /// if the two value are not equal; otherwise, . + public static bool operator !=(IccVersion left, IccVersion right) + => !(left == right); - /// - public override bool Equals(object obj) - => obj is IccVersion iccVersion && this.Equals(iccVersion); + /// + public override bool Equals(object obj) + => obj is IccVersion iccVersion && this.Equals(iccVersion); - /// - public bool Equals(IccVersion other) => - this.Major == other.Major && - this.Minor == other.Minor && - this.Patch == other.Patch; + /// + public bool Equals(IccVersion other) => + this.Major == other.Major && + this.Minor == other.Minor && + this.Patch == other.Patch; - /// - public override string ToString() - => string.Join(".", this.Major, this.Minor, this.Patch); + /// + public override string ToString() + => string.Join(".", this.Major, this.Minor, this.Patch); - /// - public override int GetHashCode() - => HashCode.Combine(this.Major, this.Minor, this.Patch); - } + /// + public override int GetHashCode() + => HashCode.Combine(this.Major, this.Minor, this.Patch); } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index bc94e95552..57039c42fa 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -1,374 +1,371 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Text; using SixLabors.ImageSharp.Metadata.Profiles.IPTC; -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +/// +/// Represents an IPTC profile providing access to the collection of values. +/// +public sealed class IptcProfile : IDeepCloneable { + private Collection values; + + private const byte IptcTagMarkerByte = 0x1c; + + private const uint MaxStandardDataTagSize = 0x7FFF; + /// - /// Represents an IPTC profile providing access to the collection of values. + /// 1:90 Coded Character Set. /// - public sealed class IptcProfile : IDeepCloneable - { - private Collection values; + private const byte IptcEnvelopeCodedCharacterSet = 0x5A; - private const byte IptcTagMarkerByte = 0x1c; + /// + /// Initializes a new instance of the class. + /// + public IptcProfile() + : this((byte[])null) + { + } - private const uint MaxStandardDataTagSize = 0x7FFF; + /// + /// Initializes a new instance of the class. + /// + /// The byte array to read the iptc profile from. + public IptcProfile(byte[] data) + { + this.Data = data; + this.Initialize(); + } - /// - /// 1:90 Coded Character Set. - /// - private const byte IptcEnvelopeCodedCharacterSet = 0x5A; + /// + /// Initializes a new instance of the class + /// by making a copy from another IPTC profile. + /// + /// The other IPTC profile, from which the clone should be made from. + private IptcProfile(IptcProfile other) + { + Guard.NotNull(other, nameof(other)); - /// - /// Initializes a new instance of the class. - /// - public IptcProfile() - : this((byte[])null) + if (other.values != null) { + this.values = new Collection(); + + foreach (IptcValue value in other.Values) + { + this.values.Add(value.DeepClone()); + } } - /// - /// Initializes a new instance of the class. - /// - /// The byte array to read the iptc profile from. - public IptcProfile(byte[] data) + if (other.Data != null) { - this.Data = data; - this.Initialize(); + this.Data = new byte[other.Data.Length]; + other.Data.AsSpan().CopyTo(this.Data); } + } - /// - /// Initializes a new instance of the class - /// by making a copy from another IPTC profile. - /// - /// The other IPTC profile, from which the clone should be made from. - private IptcProfile(IptcProfile other) - { - Guard.NotNull(other, nameof(other)); + /// + /// Gets a byte array marking that UTF-8 encoding is used in application records. + /// + private static ReadOnlySpan CodedCharacterSetUtf8Value => new byte[] { 0x1B, 0x25, 0x47 }; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs. - if (other.values != null) - { - this.values = new Collection(); + /// + /// Gets the byte data of the IPTC profile. + /// + public byte[] Data { get; private set; } - foreach (IptcValue value in other.Values) - { - this.values.Add(value.DeepClone()); - } - } + /// + /// Gets the values of this iptc profile. + /// + public IEnumerable Values + { + get + { + this.Initialize(); + return this.values; + } + } + + /// + public IptcProfile DeepClone() => new(this); - if (other.Data != null) + /// + /// Returns all values with the specified tag. + /// + /// The tag of the iptc value. + /// The values found with the specified tag. + public List GetValues(IptcTag tag) + { + List iptcValues = new(); + foreach (IptcValue iptcValue in this.Values) + { + if (iptcValue.Tag == tag) { - this.Data = new byte[other.Data.Length]; - other.Data.AsSpan().CopyTo(this.Data); + iptcValues.Add(iptcValue); } } - /// - /// Gets a byte array marking that UTF-8 encoding is used in application records. - /// - private static ReadOnlySpan CodedCharacterSetUtf8Value => new byte[] { 0x1B, 0x25, 0x47 }; // Uses C#'s optimization to refer to the data segment in the assembly directly, no allocation occurs. + return iptcValues; + } - /// - /// Gets the byte data of the IPTC profile. - /// - public byte[] Data { get; private set; } + /// + /// Removes all values with the specified tag. + /// + /// The tag of the iptc value to remove. + /// True when the value was found and removed. + public bool RemoveValue(IptcTag tag) + { + this.Initialize(); - /// - /// Gets the values of this iptc profile. - /// - public IEnumerable Values + bool removed = false; + for (int i = this.values.Count - 1; i >= 0; i--) { - get + if (this.values[i].Tag == tag) { - this.Initialize(); - return this.values; + this.values.RemoveAt(i); + removed = true; } } - /// - public IptcProfile DeepClone() => new(this); + return removed; + } - /// - /// Returns all values with the specified tag. - /// - /// The tag of the iptc value. - /// The values found with the specified tag. - public List GetValues(IptcTag tag) + /// + /// Removes values with the specified tag and value. + /// + /// The tag of the iptc value to remove. + /// The value of the iptc item to remove. + /// True when the value was found and removed. + public bool RemoveValue(IptcTag tag, string value) + { + this.Initialize(); + + bool removed = false; + for (int i = this.values.Count - 1; i >= 0; i--) { - List iptcValues = new(); - foreach (IptcValue iptcValue in this.Values) + if (this.values[i].Tag == tag && this.values[i].Value.Equals(value, StringComparison.OrdinalIgnoreCase)) { - if (iptcValue.Tag == tag) - { - iptcValues.Add(iptcValue); - } + this.values.RemoveAt(i); + removed = true; } - - return iptcValues; } - /// - /// Removes all values with the specified tag. - /// - /// The tag of the iptc value to remove. - /// True when the value was found and removed. - public bool RemoveValue(IptcTag tag) - { - this.Initialize(); + return removed; + } - bool removed = false; - for (int i = this.values.Count - 1; i >= 0; i--) - { - if (this.values[i].Tag == tag) - { - this.values.RemoveAt(i); - removed = true; - } - } + /// + /// Changes the encoding for all the values. + /// + /// The encoding to use when storing the bytes. + public void SetEncoding(Encoding encoding) + { + Guard.NotNull(encoding, nameof(encoding)); - return removed; + foreach (IptcValue value in this.Values) + { + value.Encoding = encoding; } + } - /// - /// Removes values with the specified tag and value. - /// - /// The tag of the iptc value to remove. - /// The value of the iptc item to remove. - /// True when the value was found and removed. - public bool RemoveValue(IptcTag tag, string value) - { - this.Initialize(); + /// + /// Sets the value for the specified tag. + /// + /// The tag of the iptc value. + /// The encoding to use when storing the bytes. + /// The value. + /// + /// Indicates if length restrictions from the specification should be followed strictly. + /// Defaults to true. + /// + public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true) + { + Guard.NotNull(encoding, nameof(encoding)); + Guard.NotNull(value, nameof(value)); - bool removed = false; - for (int i = this.values.Count - 1; i >= 0; i--) + if (!tag.IsRepeatable()) + { + foreach (IptcValue iptcValue in this.Values) { - if (this.values[i].Tag == tag && this.values[i].Value.Equals(value, StringComparison.OrdinalIgnoreCase)) + if (iptcValue.Tag == tag) { - this.values.RemoveAt(i); - removed = true; + iptcValue.Strict = strict; + iptcValue.Encoding = encoding; + iptcValue.Value = value; + return; } } - - return removed; } - /// - /// Changes the encoding for all the values. - /// - /// The encoding to use when storing the bytes. - public void SetEncoding(Encoding encoding) - { - Guard.NotNull(encoding, nameof(encoding)); + this.values.Add(new IptcValue(tag, encoding, value, strict)); + } - foreach (IptcValue value in this.Values) - { - value.Encoding = encoding; - } - } + /// + /// Sets the value of the specified tag. + /// + /// The tag of the iptc value. + /// The value. + /// + /// Indicates if length restrictions from the specification should be followed strictly. + /// Defaults to true. + /// + public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); - /// - /// Sets the value for the specified tag. - /// - /// The tag of the iptc value. - /// The encoding to use when storing the bytes. - /// The value. - /// - /// Indicates if length restrictions from the specification should be followed strictly. - /// Defaults to true. - /// - public void SetValue(IptcTag tag, Encoding encoding, string value, bool strict = true) + /// + /// Makes sure the datetime is formatted according to the iptc specification. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + /// The tag of the iptc value. + /// The datetime. + /// Iptc tag is not a time or date type. + public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset) + { + if (!tag.IsDate() && !tag.IsTime()) { - Guard.NotNull(encoding, nameof(encoding)); - Guard.NotNull(value, nameof(value)); + throw new ArgumentException("Iptc tag is not a time or date type."); + } - if (!tag.IsRepeatable()) - { - foreach (IptcValue iptcValue in this.Values) - { - if (iptcValue.Tag == tag) - { - iptcValue.Strict = strict; - iptcValue.Encoding = encoding; - iptcValue.Value = value; - return; - } - } - } + string formattedDate = tag.IsDate() + ? dateTimeOffset.ToString("yyyyMMdd", CultureInfo.InvariantCulture) + : dateTimeOffset.ToString("HHmmsszzzz", CultureInfo.InvariantCulture) + .Replace(":", string.Empty); - this.values.Add(new IptcValue(tag, encoding, value, strict)); - } + this.SetValue(tag, Encoding.UTF8, formattedDate); + } - /// - /// Sets the value of the specified tag. - /// - /// The tag of the iptc value. - /// The value. - /// - /// Indicates if length restrictions from the specification should be followed strictly. - /// Defaults to true. - /// - public void SetValue(IptcTag tag, string value, bool strict = true) => this.SetValue(tag, Encoding.UTF8, value, strict); - - /// - /// Makes sure the datetime is formatted according to the iptc specification. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - /// The tag of the iptc value. - /// The datetime. - /// Iptc tag is not a time or date type. - public void SetDateTimeValue(IptcTag tag, DateTimeOffset dateTimeOffset) + /// + /// Updates the data of the profile. + /// + public void UpdateData() + { + int length = 0; + foreach (IptcValue value in this.Values) { - if (!tag.IsDate() && !tag.IsTime()) - { - throw new ArgumentException("Iptc tag is not a time or date type."); - } + length += value.Length + 5; + } - string formattedDate = tag.IsDate() - ? dateTimeOffset.ToString("yyyyMMdd", CultureInfo.InvariantCulture) - : dateTimeOffset.ToString("HHmmsszzzz", CultureInfo.InvariantCulture) - .Replace(":", string.Empty); + bool hasValuesInUtf8 = this.HasValuesInUtf8(); - this.SetValue(tag, Encoding.UTF8, formattedDate); + if (hasValuesInUtf8) + { + // Additional length for UTF-8 Tag. + length += 5 + CodedCharacterSetUtf8Value.Length; } - /// - /// Updates the data of the profile. - /// - public void UpdateData() + this.Data = new byte[length]; + int offset = 0; + if (hasValuesInUtf8) { - int length = 0; - foreach (IptcValue value in this.Values) - { - length += value.Length + 5; - } + // Write Envelope Record. + offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet); + } - bool hasValuesInUtf8 = this.HasValuesInUtf8(); + foreach (IptcValue value in this.Values) + { + // Write Application Record. + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | Octet Pos | Name | Description | + // +==========-+================+=================================================================================+ + // | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the | + // | | | envelope record number is always 1, and that the application records are | + // | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record | + // | | | is 8, and the post - object descriptor record is 9. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in | + // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| + // | | | octet 4(most significant bit) always will be 0. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag); + } + } - if (hasValuesInUtf8) - { - // Additional length for UTF-8 Tag. - length += 5 + CodedCharacterSetUtf8Value.Length; - } + private int WriteRecord(int offset, ReadOnlySpan recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) + { + Span data = this.Data.AsSpan(offset, 5); + data[0] = IptcTagMarkerByte; + data[1] = (byte)recordNumber; + data[2] = recordBinaryRepresentation; + data[3] = (byte)(recordData.Length >> 8); + data[4] = (byte)recordData.Length; + offset += 5; + if (recordData.Length > 0) + { + recordData.CopyTo(this.Data.AsSpan(offset)); + offset += recordData.Length; + } - this.Data = new byte[length]; - int offset = 0; - if (hasValuesInUtf8) - { - // Write Envelope Record. - offset = this.WriteRecord(offset, CodedCharacterSetUtf8Value, IptcRecordNumber.Envelope, IptcEnvelopeCodedCharacterSet); - } + return offset; + } - foreach (IptcValue value in this.Values) - { - // Write Application Record. - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | Octet Pos | Name | Description | - // +==========-+================+=================================================================================+ - // | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the | - // | | | envelope record number is always 1, and that the application records are | - // | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record | - // | | | is 8, and the post - object descriptor record is 9. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - // | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in | - // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| - // | | | octet 4(most significant bit) always will be 0. | - // +-----------+----------------+---------------------------------------------------------------------------------+ - offset = this.WriteRecord(offset, value.ToByteArray(), IptcRecordNumber.Application, (byte)value.Tag); - } + private void Initialize() + { + if (this.values != null) + { + return; } - private int WriteRecord(int offset, ReadOnlySpan recordData, IptcRecordNumber recordNumber, byte recordBinaryRepresentation) - { - Span data = this.Data.AsSpan(offset, 5); - data[0] = IptcTagMarkerByte; - data[1] = (byte)recordNumber; - data[2] = recordBinaryRepresentation; - data[3] = (byte)(recordData.Length >> 8); - data[4] = (byte)recordData.Length; - offset += 5; - if (recordData.Length > 0) - { - recordData.CopyTo(this.Data.AsSpan(offset)); - offset += recordData.Length; - } + this.values = new Collection(); - return offset; + if (this.Data == null || this.Data[0] != IptcTagMarkerByte) + { + return; } - private void Initialize() + int offset = 0; + while (offset < this.Data.Length - 4) { - if (this.values != null) + bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; + byte recordNumber = this.Data[offset++]; + bool isValidRecordNumber = recordNumber is >= 1 and <= 9; + IptcTag tag = (IptcTag)this.Data[offset++]; + bool isValidEntry = isValidTagMarker && isValidRecordNumber; + bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application; + + uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); + offset += 2; + if (byteCount > MaxStandardDataTagSize) { - return; + // Extended data set tag's are not supported. + break; } - this.values = new Collection(); - - if (this.Data == null || this.Data[0] != IptcTagMarkerByte) + if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount)) { - return; + byte[] iptcData = new byte[byteCount]; + Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); + this.values.Add(new IptcValue(tag, iptcData, false)); } - int offset = 0; - while (offset < this.Data.Length - 4) - { - bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; - byte recordNumber = this.Data[offset++]; - bool isValidRecordNumber = recordNumber is >= 1 and <= 9; - IptcTag tag = (IptcTag)this.Data[offset++]; - bool isValidEntry = isValidTagMarker && isValidRecordNumber; - bool isApplicationRecord = recordNumber == (byte)IptcRecordNumber.Application; - - uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); - offset += 2; - if (byteCount > MaxStandardDataTagSize) - { - // Extended data set tag's are not supported. - break; - } - - if (isValidEntry && isApplicationRecord && byteCount > 0 && (offset <= this.Data.Length - byteCount)) - { - byte[] iptcData = new byte[byteCount]; - Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); - this.values.Add(new IptcValue(tag, iptcData, false)); - } - - offset += (int)byteCount; - } + offset += (int)byteCount; } + } - /// - /// Gets if any value has UTF-8 encoding. - /// - /// true if any value has UTF-8 encoding. - private bool HasValuesInUtf8() + /// + /// Gets if any value has UTF-8 encoding. + /// + /// true if any value has UTF-8 encoding. + private bool HasValuesInUtf8() + { + foreach (IptcValue value in this.values) { - foreach (IptcValue value in this.values) + if (value.Encoding == Encoding.UTF8) { - if (value.Encoding == Encoding.UTF8) - { - return true; - } + return true; } - - return false; } + + return false; } } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs index 52e4c47a45..2d5fe6a09a 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC +namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC; + +/// +/// Enum for the different record types of a IPTC value. +/// +internal enum IptcRecordNumber : byte { /// - /// Enum for the different record types of a IPTC value. + /// A Envelope Record. /// - internal enum IptcRecordNumber : byte - { - /// - /// A Envelope Record. - /// - Envelope = 0x01, + Envelope = 0x01, - /// - /// A Application Record. - /// - Application = 0x02 - } + /// + /// A Application Record. + /// + Application = 0x02 } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs index c8cd4a9c7d..41bfc2604d 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTag.cs @@ -1,397 +1,396 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +/// +/// Provides enumeration of all IPTC tags relevant for images. +/// +public enum IptcTag { /// - /// Provides enumeration of all IPTC tags relevant for images. - /// - public enum IptcTag - { - /// - /// Unknown. - /// - Unknown = -1, - - /// - /// Record version identifying the version of the Information Interchange Model. - /// Not repeatable. Max length is 2. - /// - RecordVersion = 0, - - /// - /// Object type, not repeatable. Max Length is 67. - /// - ObjectType = 3, - - /// - /// Object attribute. Max length is 68. - /// - ObjectAttribute = 4, - - /// - /// Object Name, not repeatable. Max length is 64. - /// - Name = 5, - - /// - /// Edit status, not repeatable. Max length is 64. - /// - EditStatus = 7, - - /// - /// Editorial update, not repeatable. Max length is 2. - /// - EditorialUpdate = 8, - - /// - /// Urgency, not repeatable. Max length is 2. - /// - Urgency = 10, - - /// - /// Subject Reference. Max length is 236. - /// - SubjectReference = 12, - - /// - /// Category, not repeatable. Max length is 3. - /// - Category = 15, - - /// - /// Supplemental categories. Max length is 32. - /// - SupplementalCategories = 20, - - /// - /// Fixture identifier, not repeatable. Max length is 32. - /// - FixtureIdentifier = 22, - - /// - /// Keywords. Max length is 64. - /// - Keywords = 25, - - /// - /// Location code. Max length is 3. - /// - LocationCode = 26, - - /// - /// Location name. Max length is 64. - /// - LocationName = 27, - - /// - /// Release date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ReleaseDate = 30, - - /// - /// Release time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - ReleaseTime = 35, - - /// - /// Expiration date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ExpirationDate = 37, - - /// - /// Expiration time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - ExpirationTime = 38, - - /// - /// Special instructions, not repeatable. Max length is 256. - /// - SpecialInstructions = 40, - - /// - /// Action advised, not repeatable. Max length is 2. - /// - ActionAdvised = 42, - - /// - /// Reference service. Max length is 10. - /// - ReferenceService = 45, - - /// - /// Reference date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - ReferenceDate = 47, - - /// - /// ReferenceNumber. Max length is 8. - /// - ReferenceNumber = 50, - - /// - /// Created date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - CreatedDate = 55, - - /// - /// Created time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - CreatedTime = 60, - - /// - /// Digital creation date. Format should be CCYYMMDD. - /// Not repeatable, max length is 8. - /// - /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. - /// - /// - DigitalCreationDate = 62, - - /// - /// Digital creation time. Format should be HHMMSS±HHMM. - /// Not repeatable, max length is 11. - /// - /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, - /// two hours ahead of UTC. - /// - /// - DigitalCreationTime = 63, - - /// - /// Originating program, not repeatable. Max length is 32. - /// - OriginatingProgram = 65, - - /// - /// Program version, not repeatable. Max length is 10. - /// - ProgramVersion = 70, - - /// - /// Object cycle, not repeatable. Max length is 1. - /// - ObjectCycle = 75, - - /// - /// Byline. Max length is 32. - /// - Byline = 80, - - /// - /// Byline title. Max length is 32. - /// - BylineTitle = 85, - - /// - /// City, not repeatable. Max length is 32. - /// - City = 90, - - /// - /// Sub location, not repeatable. Max length is 32. - /// - SubLocation = 92, - - /// - /// Province/State, not repeatable. Max length is 32. - /// - ProvinceState = 95, - - /// - /// Country code, not repeatable. Max length is 3. - /// - CountryCode = 100, - - /// - /// Country, not repeatable. Max length is 64. - /// - Country = 101, - - /// - /// Original transmission reference, not repeatable. Max length is 32. - /// - OriginalTransmissionReference = 103, - - /// - /// Headline, not repeatable. Max length is 256. - /// - Headline = 105, - - /// - /// Credit, not repeatable. Max length is 32. - /// - Credit = 110, - - /// - /// Source, not repeatable. Max length is 32. - /// - Source = 115, - - /// - /// Copyright notice, not repeatable. Max length is 128. - /// - CopyrightNotice = 116, - - /// - /// Contact. Max length 128. - /// - Contact = 118, - - /// - /// Caption, not repeatable. Max length is 2000. - /// - Caption = 120, - - /// - /// Local caption. - /// - LocalCaption = 121, - - /// - /// Caption writer. Max length is 32. - /// - CaptionWriter = 122, - - /// - /// Image type, not repeatable. Max length is 2. - /// - ImageType = 130, - - /// - /// Image orientation, not repeatable. Max length is 1. - /// - ImageOrientation = 131, - - /// - /// Custom field 1 - /// - CustomField1 = 200, - - /// - /// Custom field 2 - /// - CustomField2 = 201, - - /// - /// Custom field 3 - /// - CustomField3 = 202, - - /// - /// Custom field 4 - /// - CustomField4 = 203, - - /// - /// Custom field 5 - /// - CustomField5 = 204, - - /// - /// Custom field 6 - /// - CustomField6 = 205, - - /// - /// Custom field 7 - /// - CustomField7 = 206, - - /// - /// Custom field 8 - /// - CustomField8 = 207, - - /// - /// Custom field 9 - /// - CustomField9 = 208, - - /// - /// Custom field 10 - /// - CustomField10 = 209, - - /// - /// Custom field 11 - /// - CustomField11 = 210, - - /// - /// Custom field 12 - /// - CustomField12 = 211, - - /// - /// Custom field 13 - /// - CustomField13 = 212, - - /// - /// Custom field 14 - /// - CustomField14 = 213, - - /// - /// Custom field 15 - /// - CustomField15 = 214, - - /// - /// Custom field 16 - /// - CustomField16 = 215, - - /// - /// Custom field 17 - /// - CustomField17 = 216, - - /// - /// Custom field 18 - /// - CustomField18 = 217, - - /// - /// Custom field 19 - /// - CustomField19 = 218, - - /// - /// Custom field 20 - /// - CustomField20 = 219, - } + /// Unknown. + /// + Unknown = -1, + + /// + /// Record version identifying the version of the Information Interchange Model. + /// Not repeatable. Max length is 2. + /// + RecordVersion = 0, + + /// + /// Object type, not repeatable. Max Length is 67. + /// + ObjectType = 3, + + /// + /// Object attribute. Max length is 68. + /// + ObjectAttribute = 4, + + /// + /// Object Name, not repeatable. Max length is 64. + /// + Name = 5, + + /// + /// Edit status, not repeatable. Max length is 64. + /// + EditStatus = 7, + + /// + /// Editorial update, not repeatable. Max length is 2. + /// + EditorialUpdate = 8, + + /// + /// Urgency, not repeatable. Max length is 2. + /// + Urgency = 10, + + /// + /// Subject Reference. Max length is 236. + /// + SubjectReference = 12, + + /// + /// Category, not repeatable. Max length is 3. + /// + Category = 15, + + /// + /// Supplemental categories. Max length is 32. + /// + SupplementalCategories = 20, + + /// + /// Fixture identifier, not repeatable. Max length is 32. + /// + FixtureIdentifier = 22, + + /// + /// Keywords. Max length is 64. + /// + Keywords = 25, + + /// + /// Location code. Max length is 3. + /// + LocationCode = 26, + + /// + /// Location name. Max length is 64. + /// + LocationName = 27, + + /// + /// Release date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ReleaseDate = 30, + + /// + /// Release time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + ReleaseTime = 35, + + /// + /// Expiration date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ExpirationDate = 37, + + /// + /// Expiration time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + ExpirationTime = 38, + + /// + /// Special instructions, not repeatable. Max length is 256. + /// + SpecialInstructions = 40, + + /// + /// Action advised, not repeatable. Max length is 2. + /// + ActionAdvised = 42, + + /// + /// Reference service. Max length is 10. + /// + ReferenceService = 45, + + /// + /// Reference date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + ReferenceDate = 47, + + /// + /// ReferenceNumber. Max length is 8. + /// + ReferenceNumber = 50, + + /// + /// Created date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + CreatedDate = 55, + + /// + /// Created time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + CreatedTime = 60, + + /// + /// Digital creation date. Format should be CCYYMMDD. + /// Not repeatable, max length is 8. + /// + /// A date will be formatted as CCYYMMDD, e.g. "19890317" for 17 March 1989. + /// + /// + DigitalCreationDate = 62, + + /// + /// Digital creation time. Format should be HHMMSS±HHMM. + /// Not repeatable, max length is 11. + /// + /// A time value will be formatted as HHMMSS±HHMM, e.g. "090000+0200" for 9 o'clock Berlin time, + /// two hours ahead of UTC. + /// + /// + DigitalCreationTime = 63, + + /// + /// Originating program, not repeatable. Max length is 32. + /// + OriginatingProgram = 65, + + /// + /// Program version, not repeatable. Max length is 10. + /// + ProgramVersion = 70, + + /// + /// Object cycle, not repeatable. Max length is 1. + /// + ObjectCycle = 75, + + /// + /// Byline. Max length is 32. + /// + Byline = 80, + + /// + /// Byline title. Max length is 32. + /// + BylineTitle = 85, + + /// + /// City, not repeatable. Max length is 32. + /// + City = 90, + + /// + /// Sub location, not repeatable. Max length is 32. + /// + SubLocation = 92, + + /// + /// Province/State, not repeatable. Max length is 32. + /// + ProvinceState = 95, + + /// + /// Country code, not repeatable. Max length is 3. + /// + CountryCode = 100, + + /// + /// Country, not repeatable. Max length is 64. + /// + Country = 101, + + /// + /// Original transmission reference, not repeatable. Max length is 32. + /// + OriginalTransmissionReference = 103, + + /// + /// Headline, not repeatable. Max length is 256. + /// + Headline = 105, + + /// + /// Credit, not repeatable. Max length is 32. + /// + Credit = 110, + + /// + /// Source, not repeatable. Max length is 32. + /// + Source = 115, + + /// + /// Copyright notice, not repeatable. Max length is 128. + /// + CopyrightNotice = 116, + + /// + /// Contact. Max length 128. + /// + Contact = 118, + + /// + /// Caption, not repeatable. Max length is 2000. + /// + Caption = 120, + + /// + /// Local caption. + /// + LocalCaption = 121, + + /// + /// Caption writer. Max length is 32. + /// + CaptionWriter = 122, + + /// + /// Image type, not repeatable. Max length is 2. + /// + ImageType = 130, + + /// + /// Image orientation, not repeatable. Max length is 1. + /// + ImageOrientation = 131, + + /// + /// Custom field 1 + /// + CustomField1 = 200, + + /// + /// Custom field 2 + /// + CustomField2 = 201, + + /// + /// Custom field 3 + /// + CustomField3 = 202, + + /// + /// Custom field 4 + /// + CustomField4 = 203, + + /// + /// Custom field 5 + /// + CustomField5 = 204, + + /// + /// Custom field 6 + /// + CustomField6 = 205, + + /// + /// Custom field 7 + /// + CustomField7 = 206, + + /// + /// Custom field 8 + /// + CustomField8 = 207, + + /// + /// Custom field 9 + /// + CustomField9 = 208, + + /// + /// Custom field 10 + /// + CustomField10 = 209, + + /// + /// Custom field 11 + /// + CustomField11 = 210, + + /// + /// Custom field 12 + /// + CustomField12 = 211, + + /// + /// Custom field 13 + /// + CustomField13 = 212, + + /// + /// Custom field 14 + /// + CustomField14 = 213, + + /// + /// Custom field 15 + /// + CustomField15 = 214, + + /// + /// Custom field 16 + /// + CustomField16 = 215, + + /// + /// Custom field 17 + /// + CustomField17 = 216, + + /// + /// Custom field 18 + /// + CustomField18 = 217, + + /// + /// Custom field 19 + /// + CustomField19 = 218, + + /// + /// Custom field 20 + /// + CustomField20 = 219, } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs index 6a0719b13d..a50f903c11 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs @@ -1,159 +1,158 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +/// +/// Extension methods for IPTC tags. +/// +public static class IptcTagExtensions { /// - /// Extension methods for IPTC tags. + /// Maximum length of the IPTC value with the given tag according to the specification. /// - public static class IptcTagExtensions + /// The tag to check the max length for. + /// The maximum length. + public static int MaxLength(this IptcTag tag) => tag switch { - /// - /// Maximum length of the IPTC value with the given tag according to the specification. - /// - /// The tag to check the max length for. - /// The maximum length. - public static int MaxLength(this IptcTag tag) => tag switch - { - IptcTag.RecordVersion => 2, - IptcTag.ObjectType => 67, - IptcTag.ObjectAttribute => 68, - IptcTag.Name => 64, - IptcTag.EditStatus => 64, - IptcTag.EditorialUpdate => 2, - IptcTag.Urgency => 1, - IptcTag.SubjectReference => 236, - IptcTag.Category => 3, - IptcTag.SupplementalCategories => 32, - IptcTag.FixtureIdentifier => 32, - IptcTag.Keywords => 64, - IptcTag.LocationCode => 3, - IptcTag.LocationName => 64, - IptcTag.ReleaseDate => 8, - IptcTag.ReleaseTime => 11, - IptcTag.ExpirationDate => 8, - IptcTag.ExpirationTime => 11, - IptcTag.SpecialInstructions => 256, - IptcTag.ActionAdvised => 2, - IptcTag.ReferenceService => 10, - IptcTag.ReferenceDate => 8, - IptcTag.ReferenceNumber => 8, - IptcTag.CreatedDate => 8, - IptcTag.CreatedTime => 11, - IptcTag.DigitalCreationDate => 8, - IptcTag.DigitalCreationTime => 11, - IptcTag.OriginatingProgram => 32, - IptcTag.ProgramVersion => 10, - IptcTag.ObjectCycle => 1, - IptcTag.Byline => 32, - IptcTag.BylineTitle => 32, - IptcTag.City => 32, - IptcTag.SubLocation => 32, - IptcTag.ProvinceState => 32, - IptcTag.CountryCode => 3, - IptcTag.Country => 64, - IptcTag.OriginalTransmissionReference => 32, - IptcTag.Headline => 256, - IptcTag.Credit => 32, - IptcTag.Source => 32, - IptcTag.CopyrightNotice => 128, - IptcTag.Contact => 128, - IptcTag.Caption => 2000, - IptcTag.CaptionWriter => 32, - IptcTag.ImageType => 2, - IptcTag.ImageOrientation => 1, - _ => 256 - }; + IptcTag.RecordVersion => 2, + IptcTag.ObjectType => 67, + IptcTag.ObjectAttribute => 68, + IptcTag.Name => 64, + IptcTag.EditStatus => 64, + IptcTag.EditorialUpdate => 2, + IptcTag.Urgency => 1, + IptcTag.SubjectReference => 236, + IptcTag.Category => 3, + IptcTag.SupplementalCategories => 32, + IptcTag.FixtureIdentifier => 32, + IptcTag.Keywords => 64, + IptcTag.LocationCode => 3, + IptcTag.LocationName => 64, + IptcTag.ReleaseDate => 8, + IptcTag.ReleaseTime => 11, + IptcTag.ExpirationDate => 8, + IptcTag.ExpirationTime => 11, + IptcTag.SpecialInstructions => 256, + IptcTag.ActionAdvised => 2, + IptcTag.ReferenceService => 10, + IptcTag.ReferenceDate => 8, + IptcTag.ReferenceNumber => 8, + IptcTag.CreatedDate => 8, + IptcTag.CreatedTime => 11, + IptcTag.DigitalCreationDate => 8, + IptcTag.DigitalCreationTime => 11, + IptcTag.OriginatingProgram => 32, + IptcTag.ProgramVersion => 10, + IptcTag.ObjectCycle => 1, + IptcTag.Byline => 32, + IptcTag.BylineTitle => 32, + IptcTag.City => 32, + IptcTag.SubLocation => 32, + IptcTag.ProvinceState => 32, + IptcTag.CountryCode => 3, + IptcTag.Country => 64, + IptcTag.OriginalTransmissionReference => 32, + IptcTag.Headline => 256, + IptcTag.Credit => 32, + IptcTag.Source => 32, + IptcTag.CopyrightNotice => 128, + IptcTag.Contact => 128, + IptcTag.Caption => 2000, + IptcTag.CaptionWriter => 32, + IptcTag.ImageType => 2, + IptcTag.ImageOrientation => 1, + _ => 256 + }; - /// - /// Determines if the given tag can be repeated according to the specification. - /// - /// The tag to check. - /// True, if the tag can occur multiple times. - public static bool IsRepeatable(this IptcTag tag) + /// + /// Determines if the given tag can be repeated according to the specification. + /// + /// The tag to check. + /// True, if the tag can occur multiple times. + public static bool IsRepeatable(this IptcTag tag) + { + switch (tag) { - switch (tag) - { - case IptcTag.RecordVersion: - case IptcTag.ObjectType: - case IptcTag.Name: - case IptcTag.EditStatus: - case IptcTag.EditorialUpdate: - case IptcTag.Urgency: - case IptcTag.Category: - case IptcTag.FixtureIdentifier: - case IptcTag.ReleaseDate: - case IptcTag.ReleaseTime: - case IptcTag.ExpirationDate: - case IptcTag.ExpirationTime: - case IptcTag.SpecialInstructions: - case IptcTag.ActionAdvised: - case IptcTag.CreatedDate: - case IptcTag.CreatedTime: - case IptcTag.DigitalCreationDate: - case IptcTag.DigitalCreationTime: - case IptcTag.OriginatingProgram: - case IptcTag.ProgramVersion: - case IptcTag.ObjectCycle: - case IptcTag.City: - case IptcTag.SubLocation: - case IptcTag.ProvinceState: - case IptcTag.CountryCode: - case IptcTag.Country: - case IptcTag.OriginalTransmissionReference: - case IptcTag.Headline: - case IptcTag.Credit: - case IptcTag.Source: - case IptcTag.CopyrightNotice: - case IptcTag.Caption: - case IptcTag.ImageType: - case IptcTag.ImageOrientation: - return false; + case IptcTag.RecordVersion: + case IptcTag.ObjectType: + case IptcTag.Name: + case IptcTag.EditStatus: + case IptcTag.EditorialUpdate: + case IptcTag.Urgency: + case IptcTag.Category: + case IptcTag.FixtureIdentifier: + case IptcTag.ReleaseDate: + case IptcTag.ReleaseTime: + case IptcTag.ExpirationDate: + case IptcTag.ExpirationTime: + case IptcTag.SpecialInstructions: + case IptcTag.ActionAdvised: + case IptcTag.CreatedDate: + case IptcTag.CreatedTime: + case IptcTag.DigitalCreationDate: + case IptcTag.DigitalCreationTime: + case IptcTag.OriginatingProgram: + case IptcTag.ProgramVersion: + case IptcTag.ObjectCycle: + case IptcTag.City: + case IptcTag.SubLocation: + case IptcTag.ProvinceState: + case IptcTag.CountryCode: + case IptcTag.Country: + case IptcTag.OriginalTransmissionReference: + case IptcTag.Headline: + case IptcTag.Credit: + case IptcTag.Source: + case IptcTag.CopyrightNotice: + case IptcTag.Caption: + case IptcTag.ImageType: + case IptcTag.ImageOrientation: + return false; - default: - return true; - } + default: + return true; } + } - /// - /// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD. - /// - /// The tag to check. - /// True, if its a datetime tag. - public static bool IsDate(this IptcTag tag) + /// + /// Determines if the tag is a datetime tag which needs to be formatted as CCYYMMDD. + /// + /// The tag to check. + /// True, if its a datetime tag. + public static bool IsDate(this IptcTag tag) + { + switch (tag) { - switch (tag) - { - case IptcTag.CreatedDate: - case IptcTag.DigitalCreationDate: - case IptcTag.ExpirationDate: - case IptcTag.ReferenceDate: - case IptcTag.ReleaseDate: - return true; + case IptcTag.CreatedDate: + case IptcTag.DigitalCreationDate: + case IptcTag.ExpirationDate: + case IptcTag.ReferenceDate: + case IptcTag.ReleaseDate: + return true; - default: - return false; - } + default: + return false; } + } - /// - /// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM. - /// - /// The tag to check. - /// True, if its a time tag. - public static bool IsTime(this IptcTag tag) + /// + /// Determines if the tag is a time tag which need to be formatted as HHMMSS±HHMM. + /// + /// The tag to check. + /// True, if its a time tag. + public static bool IsTime(this IptcTag tag) + { + switch (tag) { - switch (tag) - { - case IptcTag.CreatedTime: - case IptcTag.DigitalCreationTime: - case IptcTag.ExpirationTime: - case IptcTag.ReleaseTime: - return true; + case IptcTag.CreatedTime: + case IptcTag.DigitalCreationTime: + case IptcTag.ExpirationTime: + case IptcTag.ReleaseTime: + return true; - default: - return false; - } + default: + return false; } } } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs index 1a74ea4183..af0c319b81 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -1,219 +1,217 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc +namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; + +/// +/// Represents a single value of the IPTC profile. +/// +public sealed class IptcValue : IDeepCloneable { - /// - /// Represents a single value of the IPTC profile. - /// - public sealed class IptcValue : IDeepCloneable - { - private byte[] data = Array.Empty(); - private Encoding encoding; + private byte[] data = Array.Empty(); + private Encoding encoding; - internal IptcValue(IptcValue other) + internal IptcValue(IptcValue other) + { + if (other.data != null) { - if (other.data != null) - { - this.data = new byte[other.data.Length]; - other.data.AsSpan().CopyTo(this.data); - } - - if (other.Encoding != null) - { - this.Encoding = (Encoding)other.Encoding.Clone(); - } - - this.Tag = other.Tag; - this.Strict = other.Strict; + this.data = new byte[other.data.Length]; + other.data.AsSpan().CopyTo(this.data); } - internal IptcValue(IptcTag tag, byte[] value, bool strict) + if (other.Encoding != null) { - Guard.NotNull(value, nameof(value)); - - this.Strict = strict; - this.Tag = tag; - this.data = value; - this.encoding = Encoding.UTF8; + this.Encoding = (Encoding)other.Encoding.Clone(); } - internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict) - { - this.Strict = strict; - this.Tag = tag; - this.encoding = encoding; - this.Value = value; - } + this.Tag = other.Tag; + this.Strict = other.Strict; + } - internal IptcValue(IptcTag tag, string value, bool strict) - { - this.Strict = strict; - this.Tag = tag; - this.encoding = Encoding.UTF8; - this.Value = value; - } + internal IptcValue(IptcTag tag, byte[] value, bool strict) + { + Guard.NotNull(value, nameof(value)); - /// - /// Gets or sets the encoding to use for the Value. - /// - public Encoding Encoding + this.Strict = strict; + this.Tag = tag; + this.data = value; + this.encoding = Encoding.UTF8; + } + + internal IptcValue(IptcTag tag, Encoding encoding, string value, bool strict) + { + this.Strict = strict; + this.Tag = tag; + this.encoding = encoding; + this.Value = value; + } + + internal IptcValue(IptcTag tag, string value, bool strict) + { + this.Strict = strict; + this.Tag = tag; + this.encoding = Encoding.UTF8; + this.Value = value; + } + + /// + /// Gets or sets the encoding to use for the Value. + /// + public Encoding Encoding + { + get => this.encoding; + set { - get => this.encoding; - set + if (value != null) { - if (value != null) - { - this.encoding = value; - } + this.encoding = value; } } + } + + /// + /// Gets the tag of the iptc value. + /// + public IptcTag Tag { get; } + + /// + /// Gets or sets a value indicating whether to be enforce value length restrictions according + /// to the specification. + /// + public bool Strict { get; set; } - /// - /// Gets the tag of the iptc value. - /// - public IptcTag Tag { get; } - - /// - /// Gets or sets a value indicating whether to be enforce value length restrictions according - /// to the specification. - /// - public bool Strict { get; set; } - - /// - /// Gets or sets the value. - /// - public string Value + /// + /// Gets or sets the value. + /// + public string Value + { + get => this.encoding.GetString(this.data); + set { - get => this.encoding.GetString(this.data); - set + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(value)) + this.data = Array.Empty(); + } + else + { + int maxLength = this.Tag.MaxLength(); + byte[] valueBytes; + if (this.Strict && value.Length > maxLength) { - this.data = Array.Empty(); + string cappedValue = value[..maxLength]; + valueBytes = this.encoding.GetBytes(cappedValue); + + // It is still possible that the bytes of the string exceed the limit. + if (valueBytes.Length > maxLength) + { + throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}"); + } } else { - int maxLength = this.Tag.MaxLength(); - byte[] valueBytes; - if (this.Strict && value.Length > maxLength) - { - string cappedValue = value[..maxLength]; - valueBytes = this.encoding.GetBytes(cappedValue); - - // It is still possible that the bytes of the string exceed the limit. - if (valueBytes.Length > maxLength) - { - throw new ArgumentException($"The iptc value exceeds the limit of {maxLength} bytes for the tag {this.Tag}"); - } - } - else - { - valueBytes = this.encoding.GetBytes(value); - } - - this.data = valueBytes; + valueBytes = this.encoding.GetBytes(value); } + + this.data = valueBytes; } } + } - /// - /// Gets the length of the value. - /// - public int Length => this.data.Length; + /// + /// Gets the length of the value. + /// + public int Length => this.data.Length; - /// - public IptcValue DeepClone() => new IptcValue(this); + /// + public IptcValue DeepClone() => new IptcValue(this); - /// - /// Determines whether the specified object is equal to the current . - /// - /// The object to compare this with. - /// True when the specified object is equal to the current . - public override bool Equals(object obj) + /// + /// Determines whether the specified object is equal to the current . + /// + /// The object to compare this with. + /// True when the specified object is equal to the current . + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(this, obj)) - { - return true; - } + return true; + } + + return this.Equals(obj as IptcValue); + } - return this.Equals(obj as IptcValue); + /// + /// Determines whether the specified iptc value is equal to the current . + /// + /// The iptc value to compare this with. + /// True when the specified iptc value is equal to the current . + public bool Equals(IptcValue other) + { + if (other is null) + { + return false; } - /// - /// Determines whether the specified iptc value is equal to the current . - /// - /// The iptc value to compare this with. - /// True when the specified iptc value is equal to the current . - public bool Equals(IptcValue other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } + return true; + } - if (ReferenceEquals(this, other)) - { - return true; - } + if (this.Tag != other.Tag) + { + return false; + } - if (this.Tag != other.Tag) - { - return false; - } + if (this.data.Length != other.data.Length) + { + return false; + } - if (this.data.Length != other.data.Length) + for (int i = 0; i < this.data.Length; i++) + { + if (this.data[i] != other.data[i]) { return false; } + } - for (int i = 0; i < this.data.Length; i++) - { - if (this.data[i] != other.data[i]) - { - return false; - } - } + return true; + } - return true; - } + /// + /// Serves as a hash of this type. + /// + /// A hash code for the current instance. + public override int GetHashCode() => HashCode.Combine(this.data, this.Tag); - /// - /// Serves as a hash of this type. - /// - /// A hash code for the current instance. - public override int GetHashCode() => HashCode.Combine(this.data, this.Tag); - - /// - /// Converts this instance to a byte array. - /// - /// A array. - public byte[] ToByteArray() - { - var result = new byte[this.data.Length]; - this.data.CopyTo(result, 0); - return result; - } + /// + /// Converts this instance to a byte array. + /// + /// A array. + public byte[] ToByteArray() + { + var result = new byte[this.data.Length]; + this.data.CopyTo(result, 0); + return result; + } - /// - /// Returns a string that represents the current value. - /// - /// A string that represents the current value. - public override string ToString() => this.Value; - - /// - /// Returns a string that represents the current value with the specified encoding. - /// - /// The encoding to use. - /// A string that represents the current value with the specified encoding. - public string ToString(Encoding encoding) - { - Guard.NotNull(encoding, nameof(encoding)); + /// + /// Returns a string that represents the current value. + /// + /// A string that represents the current value. + public override string ToString() => this.Value; - return encoding.GetString(this.data); - } + /// + /// Returns a string that represents the current value with the specified encoding. + /// + /// The encoding to use. + /// A string that represents the current value with the specified encoding. + public string ToString(Encoding encoding) + { + Guard.NotNull(encoding, nameof(encoding)); + + return encoding.GetString(this.data); } } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 095beb24af..0b22017aec 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -1,89 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Text; using System.Xml.Linq; -namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp +namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp; + +/// +/// Represents an XMP profile, providing access to the raw XML. +/// See for the full specification. +/// +public sealed class XmpProfile : IDeepCloneable { /// - /// Represents an XMP profile, providing access to the raw XML. - /// See for the full specification. + /// Initializes a new instance of the class. /// - public sealed class XmpProfile : IDeepCloneable + public XmpProfile() + : this((byte[])null) { - /// - /// Initializes a new instance of the class. - /// - public XmpProfile() - : this((byte[])null) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[] data) => this.Data = data; + /// + /// Initializes a new instance of the class. + /// + /// The UTF8 encoded byte array to read the XMP profile from. + public XmpProfile(byte[] data) => this.Data = data; - /// - /// Initializes a new instance of the class - /// by making a copy from another XMP profile. - /// - /// The other XMP profile, from which the clone should be made from. - private XmpProfile(XmpProfile other) - { - Guard.NotNull(other, nameof(other)); + /// + /// Initializes a new instance of the class + /// by making a copy from another XMP profile. + /// + /// The other XMP profile, from which the clone should be made from. + private XmpProfile(XmpProfile other) + { + Guard.NotNull(other, nameof(other)); - this.Data = other.Data; - } + this.Data = other.Data; + } - /// - /// Gets the XMP raw data byte array. - /// - internal byte[] Data { get; private set; } + /// + /// Gets the XMP raw data byte array. + /// + internal byte[] Data { get; private set; } - /// - /// Gets the raw XML document containing the XMP profile. - /// - /// The - public XDocument GetDocument() + /// + /// Gets the raw XML document containing the XMP profile. + /// + /// The + public XDocument GetDocument() + { + byte[] byteArray = this.Data; + if (byteArray is null) { - byte[] byteArray = this.Data; - if (byteArray is null) - { - return null; - } + return null; + } - // Strip leading whitespace, as the XmlReader doesn't like them. - int count = byteArray.Length; - for (int i = count - 1; i > 0; i--) + // Strip leading whitespace, as the XmlReader doesn't like them. + int count = byteArray.Length; + for (int i = count - 1; i > 0; i--) + { + if (byteArray[i] is 0 or 0x0f) { - if (byteArray[i] is 0 or 0x0f) - { - count--; - } + count--; } - - using MemoryStream stream = new(byteArray, 0, count); - using StreamReader reader = new(stream, Encoding.UTF8); - return XDocument.Load(reader); } - /// - /// Convert the content of this into a byte array. - /// - /// The - public byte[] ToByteArray() - { - byte[] result = new byte[this.Data.Length]; - this.Data.AsSpan().CopyTo(result); - return result; - } + using MemoryStream stream = new(byteArray, 0, count); + using StreamReader reader = new(stream, Encoding.UTF8); + return XDocument.Load(reader); + } - /// - public XmpProfile DeepClone() => new(this); + /// + /// Convert the content of this into a byte array. + /// + /// The + public byte[] ToByteArray() + { + byte[] result = new byte[this.Data.Length]; + this.Data.AsSpan().CopyTo(result); + return result; } + + /// + public XmpProfile DeepClone() => new(this); } diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs index a0c2ab113b..336f428a28 100644 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -1,72 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// A delegate to be executed on a . +/// +/// The pixel type. +public delegate void PixelAccessorAction(PixelAccessor pixelAccessor) + where TPixel : unmanaged, IPixel; + +/// +/// A delegate to be executed on two instances of . +/// +/// The first pixel type. +/// The second pixel type. +public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel; + +/// +/// A delegate to be executed on three instances of . +/// +/// The first pixel type. +/// The second pixel type. +/// The third pixel type. +public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2, + PixelAccessor pixelAccessor3) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel; + +/// +/// Provides efficient access the pixel buffers of an . +/// +/// The pixel type. +public ref struct PixelAccessor + where TPixel : unmanaged, IPixel { - /// - /// A delegate to be executed on a . - /// - /// The pixel type. - public delegate void PixelAccessorAction(PixelAccessor pixelAccessor) - where TPixel : unmanaged, IPixel; + private Buffer2D buffer; + + internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; /// - /// A delegate to be executed on two instances of . + /// Gets the width of the backing . /// - /// The first pixel type. - /// The second pixel type. - public delegate void PixelAccessorAction( - PixelAccessor pixelAccessor1, - PixelAccessor pixelAccessor2) - where TPixel1 : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel; + public int Width => this.buffer.Width; /// - /// A delegate to be executed on three instances of . + /// Gets the height of the backing . /// - /// The first pixel type. - /// The second pixel type. - /// The third pixel type. - public delegate void PixelAccessorAction( - PixelAccessor pixelAccessor1, - PixelAccessor pixelAccessor2, - PixelAccessor pixelAccessor3) - where TPixel1 : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel - where TPixel3 : unmanaged, IPixel; + public int Height => this.buffer.Height; /// - /// Provides efficient access the pixel buffers of an . + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. /// - /// The pixel type. - public ref struct PixelAccessor - where TPixel : unmanaged, IPixel - { - private Buffer2D buffer; - - internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; - - /// - /// Gets the width of the backing . - /// - public int Width => this.buffer.Width; - - /// - /// Gets the height of the backing . - /// - public int Height => this.buffer.Height; - - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// The row index. - /// The . - /// Thrown when row index is out of range. - public Span GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex); - } + /// The row index. + /// The . + /// Thrown when row index is out of range. + public Span GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex); } diff --git a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs index c8cb7d15ba..30b23c3667 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -4,142 +4,141 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Helper methods for packing and unpacking floating point values +/// +internal static class HalfTypeHelper { /// - /// Helper methods for packing and unpacking floating point values + /// Packs a into an /// - internal static class HalfTypeHelper + /// The float to pack + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ushort Pack(float value) { - /// - /// Packs a into an - /// - /// The float to pack - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ushort Pack(float value) - { - var uif = new Uif { F = value }; - return Pack(uif.I); - } + var uif = new Uif { F = value }; + return Pack(uif.I); + } - /// - /// Packs an into a - /// - /// The integer to pack. - /// The - internal static ushort Pack(int value) - { - int s = (value >> 16) & 0x00008000; - int e = ((value >> 23) & 0x000000ff) - (127 - 15); - int m = value & 0x007fffff; + /// + /// Packs an into a + /// + /// The integer to pack. + /// The + internal static ushort Pack(int value) + { + int s = (value >> 16) & 0x00008000; + int e = ((value >> 23) & 0x000000ff) - (127 - 15); + int m = value & 0x007fffff; - if (e <= 0) + if (e <= 0) + { + if (e < -10) { - if (e < -10) - { - return (ushort)s; - } - - m |= 0x00800000; - - int t = 14 - e; - int a = (1 << (t - 1)) - 1; - int b = (m >> t) & 1; - - m = (m + a + b) >> t; - - return (ushort)(s | m); + return (ushort)s; } - if (e == 0xff - (127 - 15)) - { - if (m == 0) - { - return (ushort)(s | 0x7c00); - } + m |= 0x00800000; - m >>= 13; - return (ushort)(s | 0x7c00 | m | ((m == 0) ? 1 : 0)); - } + int t = 14 - e; + int a = (1 << (t - 1)) - 1; + int b = (m >> t) & 1; - m = m + 0x00000fff + ((m >> 13) & 1); + m = (m + a + b) >> t; - if ((m & 0x00800000) != 0) - { - m = 0; - e++; - } + return (ushort)(s | m); + } - if (e > 30) + if (e == 0xff - (127 - 15)) + { + if (m == 0) { return (ushort)(s | 0x7c00); } - return (ushort)(s | (e << 10) | (m >> 13)); + m >>= 13; + return (ushort)(s | 0x7c00 | m | ((m == 0) ? 1 : 0)); } - /// - /// Unpacks a into a . - /// - /// The value. - /// The . - internal static float Unpack(ushort value) + m = m + 0x00000fff + ((m >> 13) & 1); + + if ((m & 0x00800000) != 0) { - uint result; - uint mantissa = (uint)(value & 1023); - uint exponent = 0xfffffff2; + m = 0; + e++; + } + + if (e > 30) + { + return (ushort)(s | 0x7c00); + } - if ((value & -33792) == 0) + return (ushort)(s | (e << 10) | (m >> 13)); + } + + /// + /// Unpacks a into a . + /// + /// The value. + /// The . + internal static float Unpack(ushort value) + { + uint result; + uint mantissa = (uint)(value & 1023); + uint exponent = 0xfffffff2; + + if ((value & -33792) == 0) + { + if (mantissa != 0) { - if (mantissa != 0) + while ((mantissa & 1024) == 0) { - while ((mantissa & 1024) == 0) - { - exponent--; - mantissa <<= 1; - } - - mantissa &= 0xfffffbff; - result = (((uint)value & 0x8000) << 16) | ((exponent + 127) << 23) | (mantissa << 13); - } - else - { - result = (uint)((value & 0x8000) << 16); + exponent--; + mantissa <<= 1; } + + mantissa &= 0xfffffbff; + result = (((uint)value & 0x8000) << 16) | ((exponent + 127) << 23) | (mantissa << 13); } else { - result = (((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0x1f) - 15) + 127) << 23) | (mantissa << 13); + result = (uint)((value & 0x8000) << 16); } - - var uif = new Uif { U = result }; - return uif.F; } + else + { + result = (((uint)value & 0x8000) << 16) | ((((((uint)value >> 10) & 0x1f) - 15) + 127) << 23) | (mantissa << 13); + } + + var uif = new Uif { U = result }; + return uif.F; + } + /// + /// Maps the position of number types in memory + /// + [StructLayout(LayoutKind.Explicit)] + private struct Uif + { /// - /// Maps the position of number types in memory + /// The float. /// - [StructLayout(LayoutKind.Explicit)] - private struct Uif - { - /// - /// The float. - /// - [FieldOffset(0)] - public float F; - - /// - /// The integer. - /// - [FieldOffset(0)] - public int I; - - /// - /// The unsigned integer. - /// - [FieldOffset(0)] - public uint U; - } + [FieldOffset(0)] + public float F; + + /// + /// The integer. + /// + [FieldOffset(0)] + public int I; + + /// + /// The unsigned integer. + /// + [FieldOffset(0)] + public uint U; } } diff --git a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs index 8bb5e6d27d..b74c0ff44e 100644 --- a/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs +++ b/src/ImageSharp/PixelFormats/IPackedVector{TPacked}.cs @@ -1,21 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.PixelFormats +/// +/// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. +/// +/// +/// The packed format. uint, long, float. +public interface IPackedVector : IPixel + where TPacked : struct, IEquatable { /// - /// This interface exists for ensuring signature compatibility to MonoGame and XNA packed color types. - /// + /// Gets or sets the packed representation of the value. /// - /// The packed format. uint, long, float. - public interface IPackedVector : IPixel - where TPacked : struct, IEquatable - { - /// - /// Gets or sets the packed representation of the value. - /// - TPacked PackedValue { get; set; } - } + TPacked PackedValue { get; set; } } diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 7c7dcd2469..0994444668 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -1,142 +1,140 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// An interface that represents a generic pixel type. +/// The naming convention of each pixel format is to order the color components from least significant to most significant, reading from left to right. +/// For example in the pixel format the R component is the least significant byte, and the A component is the most significant. +/// +/// The type implementing this interface +public interface IPixel : IPixel, IEquatable + where TSelf : unmanaged, IPixel +{ + /// + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. + /// + /// The instance. + PixelOperations CreatePixelOperations(); +} + +/// +/// A base interface for all pixels, defining the mandatory operations to be implemented by a pixel type. +/// +public interface IPixel { /// - /// An interface that represents a generic pixel type. - /// The naming convention of each pixel format is to order the color components from least significant to most significant, reading from left to right. - /// For example in the pixel format the R component is the least significant byte, and the A component is the most significant. - /// - /// The type implementing this interface - public interface IPixel : IPixel, IEquatable - where TSelf : unmanaged, IPixel - { - /// - /// Creates a instance for this pixel type. - /// This method is not intended to be consumed directly. Use instead. - /// - /// The instance. - PixelOperations CreatePixelOperations(); - } - - /// - /// A base interface for all pixels, defining the mandatory operations to be implemented by a pixel type. - /// - public interface IPixel - { - /// - /// Initializes the pixel instance from a generic ("scaled") . - /// - /// The vector to load the pixel from. - void FromScaledVector4(Vector4 vector); - - /// - /// Expands the pixel into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToScaledVector4(); - - /// - /// Initializes the pixel instance from a which is specific to the current pixel type. - /// - /// The vector to load the pixel from. - void FromVector4(Vector4 vector); - - /// - /// Expands the pixel into a which is specific to the current pixel type. - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToVector4(); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromArgb32(Argb32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromBgra5551(Bgra5551 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromBgr24(Bgr24 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromBgra32(Bgra32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromAbgr32(Abgr32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromL8(L8 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromL16(L16 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromLa16(La16 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromLa32(La32 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromRgb24(Rgb24 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromRgba32(Rgba32 source); - - /// - /// Convert the pixel instance into representation. - /// - /// The reference to the destination pixel - void ToRgba32(ref Rgba32 dest); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromRgb48(Rgb48 source); - - /// - /// Initializes the pixel instance from an value. - /// - /// The value. - void FromRgba64(Rgba64 source); - } + /// Initializes the pixel instance from a generic ("scaled") . + /// + /// The vector to load the pixel from. + void FromScaledVector4(Vector4 vector); + + /// + /// Expands the pixel into a generic ("scaled") representation + /// with values scaled and clamped between 0 and 1. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToScaledVector4(); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type. + /// + /// The vector to load the pixel from. + void FromVector4(Vector4 vector); + + /// + /// Expands the pixel into a which is specific to the current pixel type. + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromArgb32(Argb32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgra5551(Bgra5551 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgr24(Bgr24 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromBgra32(Bgra32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromAbgr32(Abgr32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromL8(L8 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromL16(L16 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromLa16(La16 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromLa32(La32 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgb24(Rgb24 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgba32(Rgba32 source); + + /// + /// Convert the pixel instance into representation. + /// + /// The reference to the destination pixel + void ToRgba32(ref Rgba32 dest); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgb48(Rgb48 source); + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromRgba64(Rgba64 source); } diff --git a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs index ad5dce2a41..f42a264dbc 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs @@ -1,71 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Enumerates the various alpha composition modes. +/// +public enum PixelAlphaCompositionMode { /// - /// Enumerates the various alpha composition modes. + /// Returns the destination over the source. /// - public enum PixelAlphaCompositionMode - { - /// - /// Returns the destination over the source. - /// - SrcOver = 0, + SrcOver = 0, - /// - /// Returns the source colors. - /// - Src, + /// + /// Returns the source colors. + /// + Src, - /// - /// Returns the source over the destination. - /// - SrcAtop, + /// + /// Returns the source over the destination. + /// + SrcAtop, - /// - /// The source where the destination and source overlap. - /// - SrcIn, + /// + /// The source where the destination and source overlap. + /// + SrcIn, - /// - /// The destination where the destination and source overlap. - /// - SrcOut, + /// + /// The destination where the destination and source overlap. + /// + SrcOut, - /// - /// The destination where the source does not overlap it. - /// - Dest, + /// + /// The destination where the source does not overlap it. + /// + Dest, - /// - /// The source where they don't overlap otherwise dest in overlapping parts. - /// - DestAtop, + /// + /// The source where they don't overlap otherwise dest in overlapping parts. + /// + DestAtop, - /// - /// The destination over the source. - /// - DestOver, + /// + /// The destination over the source. + /// + DestOver, - /// - /// The destination where the destination and source overlap. - /// - DestIn, + /// + /// The destination where the destination and source overlap. + /// + DestIn, - /// - /// The source where the destination and source overlap. - /// - DestOut, + /// + /// The source where the destination and source overlap. + /// + DestOut, - /// - /// The clear. - /// - Clear, + /// + /// The clear. + /// + Clear, - /// - /// Clear where they overlap. - /// - Xor - } + /// + /// Clear where they overlap. + /// + Xor } diff --git a/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs b/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs index d0dec3b729..4ce7c791d3 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaRepresentation.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides enumeration of the alpha value transparency behavior of a pixel format. +/// +public enum PixelAlphaRepresentation { /// - /// Provides enumeration of the alpha value transparency behavior of a pixel format. + /// Indicates that the pixel format does not contain an alpha channel. /// - public enum PixelAlphaRepresentation - { - /// - /// Indicates that the pixel format does not contain an alpha channel. - /// - None, + None, - /// - /// Indicates that the transparency behavior is premultiplied. - /// Each color is first scaled by the alpha value. The alpha value itself is the same - /// in both straight and premultiplied alpha. Typically, no color channel value is - /// greater than the alpha channel value. - /// If a color channel value in a premultiplied format is greater than the alpha - /// channel, the standard source-over blending math results in an additive blend. - /// - Associated, + /// + /// Indicates that the transparency behavior is premultiplied. + /// Each color is first scaled by the alpha value. The alpha value itself is the same + /// in both straight and premultiplied alpha. Typically, no color channel value is + /// greater than the alpha channel value. + /// If a color channel value in a premultiplied format is greater than the alpha + /// channel, the standard source-over blending math results in an additive blend. + /// + Associated, - /// - /// Indicates that the transparency behavior is not premultiplied. - /// The alpha channel indicates the transparency of the color. - /// - Unassociated - } + /// + /// Indicates that the transparency behavior is not premultiplied. + /// The alpha channel indicates the transparency of the color. + /// + Unassociated } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 63bae2efbf..cf19101211 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -2,4129 +2,4127 @@ // Licensed under the Six Labors Split License. // -using System; using System.Numerics; -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Collection of Porter Duff alpha blending functions applying different composition models. +/// +/// +/// These functions are designed to be a general solution for all color cases, +/// that is, they take in account the alpha value of both the backdrop +/// and source, and there's no need to alpha-premultiply neither the backdrop +/// nor the source. +/// Note there are faster functions for when the backdrop color is known +/// to be opaque +/// +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel { + + /// + /// A pixel blender that implements the "NormalSrc" composition equation. + /// + public class NormalSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrc Instance { get; } = new NormalSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrc" composition equation. + /// + public class MultiplySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrc Instance { get; } = new MultiplySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddSrc" composition equation. + /// + public class AddSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrc Instance { get; } = new AddSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrc" composition equation. + /// + public class SubtractSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrc Instance { get; } = new SubtractSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrc" composition equation. + /// + public class ScreenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrc Instance { get; } = new ScreenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrc" composition equation. + /// + public class DarkenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrc Instance { get; } = new DarkenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenSrc" composition equation. + /// + public class LightenSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrc Instance { get; } = new LightenSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrc" composition equation. + /// + public class OverlaySrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrc Instance { get; } = new OverlaySrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrc" composition equation. + /// + public class HardLightSrc : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrc Instance { get; } = new HardLightSrc(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcAtop" composition equation. + /// + public class NormalSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcAtop" composition equation. + /// + public class MultiplySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddSrcAtop" composition equation. + /// + public class AddSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcAtop Instance { get; } = new AddSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcAtop" composition equation. + /// + public class SubtractSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcAtop" composition equation. + /// + public class ScreenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcAtop" composition equation. + /// + public class DarkenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcAtop" composition equation. + /// + public class LightenSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcAtop" composition equation. + /// + public class OverlaySrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcAtop" composition equation. + /// + public class HardLightSrcAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOver" composition equation. + /// + public class NormalSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOver Instance { get; } = new NormalSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOver" composition equation. + /// + public class MultiplySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOver" composition equation. + /// + public class AddSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOver Instance { get; } = new AddSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOver" composition equation. + /// + public class SubtractSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOver" composition equation. + /// + public class ScreenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOver" composition equation. + /// + public class DarkenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOver" composition equation. + /// + public class LightenSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOver Instance { get; } = new LightenSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOver" composition equation. + /// + public class OverlaySrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOver" composition equation. + /// + public class HardLightSrcOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcIn" composition equation. + /// + public class NormalSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcIn Instance { get; } = new NormalSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcIn" composition equation. + /// + public class MultiplySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddSrcIn" composition equation. + /// + public class AddSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcIn Instance { get; } = new AddSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcIn" composition equation. + /// + public class SubtractSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcIn" composition equation. + /// + public class ScreenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcIn" composition equation. + /// + public class DarkenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcIn" composition equation. + /// + public class LightenSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcIn Instance { get; } = new LightenSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcIn" composition equation. + /// + public class OverlaySrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcIn" composition equation. + /// + public class HardLightSrcIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalSrcOut" composition equation. + /// + public class NormalSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalSrcOut Instance { get; } = new NormalSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplySrcOut" composition equation. + /// + public class MultiplySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddSrcOut" composition equation. + /// + public class AddSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddSrcOut Instance { get; } = new AddSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractSrcOut" composition equation. + /// + public class SubtractSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenSrcOut" composition equation. + /// + public class ScreenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenSrcOut" composition equation. + /// + public class DarkenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenSrcOut" composition equation. + /// + public class LightenSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenSrcOut Instance { get; } = new LightenSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlaySrcOut" composition equation. + /// + public class OverlaySrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightSrcOut" composition equation. + /// + public class HardLightSrcOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalDest" composition equation. + /// + public class NormalDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDest Instance { get; } = new NormalDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDest" composition equation. + /// + public class MultiplyDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDest Instance { get; } = new MultiplyDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddDest" composition equation. + /// + public class AddDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDest Instance { get; } = new AddDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractDest" composition equation. + /// + public class SubtractDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDest Instance { get; } = new SubtractDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenDest" composition equation. + /// + public class ScreenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDest Instance { get; } = new ScreenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenDest" composition equation. + /// + public class DarkenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDest Instance { get; } = new DarkenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenDest" composition equation. + /// + public class LightenDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDest Instance { get; } = new LightenDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayDest" composition equation. + /// + public class OverlayDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDest Instance { get; } = new OverlayDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightDest" composition equation. + /// + public class HardLightDest : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDest Instance { get; } = new HardLightDest(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalDestAtop" composition equation. + /// + public class NormalDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestAtop Instance { get; } = new NormalDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestAtop" composition equation. + /// + public class MultiplyDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddDestAtop" composition equation. + /// + public class AddDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestAtop Instance { get; } = new AddDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestAtop" composition equation. + /// + public class SubtractDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestAtop" composition equation. + /// + public class ScreenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestAtop" composition equation. + /// + public class DarkenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenDestAtop" composition equation. + /// + public class LightenDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestAtop Instance { get; } = new LightenDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestAtop" composition equation. + /// + public class OverlayDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestAtop" composition equation. + /// + public class HardLightDestAtop : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalDestOver" composition equation. + /// + public class NormalDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOver Instance { get; } = new NormalDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestOver" composition equation. + /// + public class MultiplyDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddDestOver" composition equation. + /// + public class AddDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOver Instance { get; } = new AddDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestOver" composition equation. + /// + public class SubtractDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOver Instance { get; } = new SubtractDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestOver" composition equation. + /// + public class ScreenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOver Instance { get; } = new ScreenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestOver" composition equation. + /// + public class DarkenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOver Instance { get; } = new DarkenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenDestOver" composition equation. + /// + public class LightenDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOver Instance { get; } = new LightenDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestOver" composition equation. + /// + public class OverlayDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOver Instance { get; } = new OverlayDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestOver" composition equation. + /// + public class HardLightDestOver : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOver Instance { get; } = new HardLightDestOver(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalDestIn" composition equation. + /// + public class NormalDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestIn Instance { get; } = new NormalDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestIn" composition equation. + /// + public class MultiplyDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddDestIn" composition equation. + /// + public class AddDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestIn Instance { get; } = new AddDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestIn" composition equation. + /// + public class SubtractDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestIn Instance { get; } = new SubtractDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestIn" composition equation. + /// + public class ScreenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestIn Instance { get; } = new ScreenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestIn" composition equation. + /// + public class DarkenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestIn Instance { get; } = new DarkenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenDestIn" composition equation. + /// + public class LightenDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestIn Instance { get; } = new LightenDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestIn" composition equation. + /// + public class OverlayDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestIn Instance { get; } = new OverlayDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestIn" composition equation. + /// + public class HardLightDestIn : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestIn Instance { get; } = new HardLightDestIn(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalDestOut" composition equation. + /// + public class NormalDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalDestOut Instance { get; } = new NormalDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyDestOut" composition equation. + /// + public class MultiplyDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddDestOut" composition equation. + /// + public class AddDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddDestOut Instance { get; } = new AddDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractDestOut" composition equation. + /// + public class SubtractDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractDestOut Instance { get; } = new SubtractDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenDestOut" composition equation. + /// + public class ScreenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenDestOut Instance { get; } = new ScreenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenDestOut" composition equation. + /// + public class DarkenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenDestOut Instance { get; } = new DarkenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenDestOut" composition equation. + /// + public class LightenDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenDestOut Instance { get; } = new LightenDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayDestOut" composition equation. + /// + public class OverlayDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayDestOut Instance { get; } = new OverlayDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightDestOut" composition equation. + /// + public class HardLightDestOut : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightDestOut Instance { get; } = new HardLightDestOut(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalClear" composition equation. + /// + public class NormalClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalClear Instance { get; } = new NormalClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyClear" composition equation. + /// + public class MultiplyClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyClear Instance { get; } = new MultiplyClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddClear" composition equation. + /// + public class AddClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddClear Instance { get; } = new AddClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractClear" composition equation. + /// + public class SubtractClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractClear Instance { get; } = new SubtractClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenClear" composition equation. + /// + public class ScreenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenClear Instance { get; } = new ScreenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenClear" composition equation. + /// + public class DarkenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenClear Instance { get; } = new DarkenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenClear" composition equation. + /// + public class LightenClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenClear Instance { get; } = new LightenClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "OverlayClear" composition equation. + /// + public class OverlayClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static OverlayClear Instance { get; } = new OverlayClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "HardLightClear" composition equation. + /// + public class HardLightClear : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightClear Instance { get; } = new HardLightClear(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "NormalXor" composition equation. + /// + public class NormalXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static NormalXor Instance { get; } = new NormalXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "MultiplyXor" composition equation. + /// + public class MultiplyXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static MultiplyXor Instance { get; } = new MultiplyXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "AddXor" composition equation. + /// + public class AddXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static AddXor Instance { get; } = new AddXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "SubtractXor" composition equation. + /// + public class SubtractXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static SubtractXor Instance { get; } = new SubtractXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "ScreenXor" composition equation. + /// + public class ScreenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static ScreenXor Instance { get; } = new ScreenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "DarkenXor" composition equation. + /// + public class DarkenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static DarkenXor Instance { get; } = new DarkenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + + /// + /// A pixel blender that implements the "LightenXor" composition equation. + /// + public class LightenXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static LightenXor Instance { get; } = new LightenXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } + /// - /// Collection of Porter Duff alpha blending functions applying different composition models. + /// A pixel blender that implements the "OverlayXor" composition equation. /// - /// - /// These functions are designed to be a general solution for all color cases, - /// that is, they take in account the alpha value of both the backdrop - /// and source, and there's no need to alpha-premultiply neither the backdrop - /// nor the source. - /// Note there are faster functions for when the backdrop color is known - /// to be opaque - /// - internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel + public class OverlayXor : PixelBlender { + /// + /// Gets the static instance of this blender. + /// + public static OverlayXor Instance { get; } = new OverlayXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + } + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } + } - /// - /// A pixel blender that implements the "NormalSrc" composition equation. - /// - public class NormalSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrc Instance { get; } = new NormalSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrc" composition equation. - /// - public class MultiplySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrc Instance { get; } = new MultiplySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddSrc" composition equation. - /// - public class AddSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrc Instance { get; } = new AddSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrc" composition equation. - /// - public class SubtractSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrc Instance { get; } = new SubtractSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrc" composition equation. - /// - public class ScreenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrc" composition equation. - /// - public class DarkenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrc Instance { get; } = new DarkenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenSrc" composition equation. - /// - public class LightenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrc Instance { get; } = new LightenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrc" composition equation. - /// - public class OverlaySrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrc Instance { get; } = new OverlaySrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrc" composition equation. - /// - public class HardLightSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrc Instance { get; } = new HardLightSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcAtop" composition equation. - /// - public class NormalSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcAtop Instance { get; } = new NormalSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcAtop" composition equation. - /// - public class MultiplySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddSrcAtop" composition equation. - /// - public class AddSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcAtop Instance { get; } = new AddSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcAtop" composition equation. - /// - public class SubtractSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. - /// - public class DarkenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. - /// - public class OverlaySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. - /// - public class NormalSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. - /// - public class MultiplySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOver" composition equation. - /// - public class AddSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOver Instance { get; } = new AddSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. - /// - public class SubtractSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. - /// - public class ScreenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. - /// - public class DarkenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. - /// - public class LightenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. - /// - public class HardLightSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcIn Instance { get; } = new NormalSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. - /// - public class MultiplySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcIn Instance { get; } = new AddSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. - /// - public class SubtractSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. - /// - public class DarkenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcIn Instance { get; } = new LightenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. - /// - public class OverlaySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. - /// - public class NormalSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOut" composition equation. - /// - public class AddSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. - /// - public class ScreenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. - /// - public class LightenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. - /// - public class HardLightSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDest Instance { get; } = new NormalDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDest" composition equation. - /// - public class MultiplyDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDest Instance { get; } = new AddDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractDest" composition equation. - /// - public class SubtractDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDest Instance { get; } = new SubtractDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDest Instance { get; } = new ScreenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenDest" composition equation. - /// - public class DarkenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDest Instance { get; } = new DarkenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDest Instance { get; } = new LightenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayDest" composition equation. - /// - public class OverlayDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDest Instance { get; } = new OverlayDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDest Instance { get; } = new HardLightDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. - /// - public class NormalDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddDestAtop" composition equation. - /// - public class AddDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. - /// - public class ScreenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. - /// - public class LightenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. - /// - public class HardLightDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOver Instance { get; } = new NormalDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. - /// - public class MultiplyDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOver Instance { get; } = new AddDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. - /// - public class SubtractDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOver" composition equation. - /// - public class ScreenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOver Instance { get; } = new ScreenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. - /// - public class DarkenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOver" composition equation. - /// - public class LightenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOver Instance { get; } = new LightenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. - /// - public class OverlayDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOver" composition equation. - /// - public class HardLightDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOver Instance { get; } = new HardLightDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalDestIn" composition equation. - /// - public class NormalDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestIn" composition equation. - /// - public class MultiplyDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestIn Instance { get; } = new MultiplyDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddDestIn" composition equation. - /// - public class AddDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestIn Instance { get; } = new AddDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestIn" composition equation. - /// - public class SubtractDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestIn Instance { get; } = new SubtractDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. - /// - public class ScreenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestIn" composition equation. - /// - public class DarkenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestIn Instance { get; } = new DarkenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenDestIn" composition equation. - /// - public class LightenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestIn" composition equation. - /// - public class OverlayDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestIn Instance { get; } = new OverlayDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. - /// - public class HardLightDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOut" composition equation. - /// - public class NormalDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOut Instance { get; } = new NormalDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. - /// - public class MultiplyDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddDestOut" composition equation. - /// - public class AddDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOut Instance { get; } = new AddDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. - /// - public class SubtractDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestOut" composition equation. - /// - public class ScreenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestOut Instance { get; } = new ScreenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. - /// - public class DarkenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenDestOut" composition equation. - /// - public class LightenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestOut Instance { get; } = new LightenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. - /// - public class OverlayDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestOut" composition equation. - /// - public class HardLightDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestOut Instance { get; } = new HardLightDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalClear" composition equation. - /// - public class NormalClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalClear Instance { get; } = new NormalClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyClear" composition equation. - /// - public class MultiplyClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyClear Instance { get; } = new MultiplyClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddClear" composition equation. - /// - public class AddClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddClear Instance { get; } = new AddClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractClear" composition equation. - /// - public class SubtractClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractClear Instance { get; } = new SubtractClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenClear" composition equation. - /// - public class ScreenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenClear Instance { get; } = new ScreenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenClear Instance { get; } = new DarkenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenClear" composition equation. - /// - public class LightenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenClear Instance { get; } = new LightenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayClear" composition equation. - /// - public class OverlayClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayClear Instance { get; } = new OverlayClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightClear" composition equation. - /// - public class HardLightClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightClear Instance { get; } = new HardLightClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "NormalXor" composition equation. - /// - public class NormalXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalXor Instance { get; } = new NormalXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "MultiplyXor" composition equation. - /// - public class MultiplyXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "AddXor" composition equation. - /// - public class AddXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddXor Instance { get; } = new AddXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "SubtractXor" composition equation. - /// - public class SubtractXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractXor Instance { get; } = new SubtractXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "ScreenXor" composition equation. - /// - public class ScreenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenXor Instance { get; } = new ScreenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "DarkenXor" composition equation. - /// - public class DarkenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenXor Instance { get; } = new DarkenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "LightenXor" composition equation. - /// - public class LightenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenXor Instance { get; } = new LightenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "OverlayXor" composition equation. - /// - public class OverlayXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayXor Instance { get; } = new OverlayXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } - } - - /// - /// A pixel blender that implements the "HardLightXor" composition equation. - /// - public class HardLightXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightXor Instance { get; } = new HardLightXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } + /// + /// A pixel blender that implements the "HardLightXor" composition equation. + /// + public class HardLightXor : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static HardLightXor Instance { get; } = new HardLightXor(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } + + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); } + } + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); + } + } } + } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 3c9bb339d0..7bd51439ce 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -12,101 +12,99 @@ // Licensed under the Six Labors Split License. // -using System; using System.Numerics; -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Collection of Porter Duff alpha blending functions applying different composition models. +/// +/// +/// These functions are designed to be a general solution for all color cases, +/// that is, they take in account the alpha value of both the backdrop +/// and source, and there's no need to alpha-premultiply neither the backdrop +/// nor the source. +/// Note there are faster functions for when the backdrop color is known +/// to be opaque +/// +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel { - /// - /// Collection of Porter Duff alpha blending functions applying different composition models. - /// - /// - /// These functions are designed to be a general solution for all color cases, - /// that is, they take in account the alpha value of both the backdrop - /// and source, and there's no need to alpha-premultiply neither the backdrop - /// nor the source. - /// Note there are faster functions for when the backdrop color is known - /// to be opaque - /// - internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel - { <# - var composers = new []{ - "Src", - "SrcAtop", - "SrcOver", - "SrcIn", - "SrcOut", - "Dest", - "DestAtop", - "DestOver", - "DestIn", - "DestOut", - "Clear", - "Xor", - }; +var composers = new []{ + "Src", + "SrcAtop", + "SrcOver", + "SrcIn", + "SrcOut", + "Dest", + "DestAtop", + "DestOver", + "DestIn", + "DestOut", + "Clear", + "Xor", +}; - var blenders = new []{ - "Normal", - "Multiply", - "Add", - "Subtract", - "Screen", - "Darken", - "Lighten", - "Overlay", - "HardLight" - }; +var blenders = new []{ + "Normal", + "Multiply", + "Add", + "Subtract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight" +}; - foreach(var composer in composers) { - foreach(var blender in blenders) { + foreach(var composer in composers) { + foreach(var blender in blenders) { - var blender_composer= $"{blender}{composer}"; + var blender_composer= $"{blender}{composer}"; #> - /// - /// A pixel blender that implements the "<#= blender_composer#>" composition equation. - /// - public class <#= blender_composer#> : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); + /// + /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// + public class <#= blender_composer#> : PixelBlender + { + /// + /// Gets the static instance of this blender. + /// + public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - TPixel dest = default; - dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - return dest; - } + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + TPixel dest = default; + dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); + return dest; + } - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + amount = Numerics.Clamp(amount, 0, 1); + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + } + } - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); - } - } + /// + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1)); } + } + } <# - } } +} #> - } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 4c9afecfd4..ff41e70b20 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -6,3657 +6,3563 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +internal static partial class PorterDuffFunctions { - internal static partial class PorterDuffFunctions - { - - - - /// - /// Returns the result of the "NormalSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "NormalSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Normal(backdrop, source)); - } - - /// - /// Returns the result of the "NormalSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "NormalSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "NormalDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "NormalDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Normal(source, backdrop)); - } - - /// - /// Returns the result of the "NormalDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "NormalDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "NormalXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "NormalClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "NormalSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "NormalXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "MultiplySrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "MultiplySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Multiply(backdrop, source)); - } - - /// - /// Returns the result of the "MultiplySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "MultiplySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "MultiplyDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "MultiplyDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Multiply(source, backdrop)); - } - - /// - /// Returns the result of the "MultiplyDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "MultiplyDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "MultiplyXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "MultiplyClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "MultiplySrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplySrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplySrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplySrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplySrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "MultiplyXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "AddSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "AddSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Add(backdrop, source)); - } - - /// - /// Returns the result of the "AddSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "AddSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "AddDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "AddDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Add(source, backdrop)); - } - - /// - /// Returns the result of the "AddDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "AddDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "AddXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "AddClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "AddSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "AddXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "SubtractSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "SubtractSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Subtract(backdrop, source)); - } - - /// - /// Returns the result of the "SubtractSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "SubtractSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "SubtractDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "SubtractDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Subtract(source, backdrop)); - } - - /// - /// Returns the result of the "SubtractDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "SubtractDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "SubtractXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "SubtractClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "SubtractSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "SubtractXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "ScreenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "ScreenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Screen(backdrop, source)); - } - - /// - /// Returns the result of the "ScreenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "ScreenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "ScreenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "ScreenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Screen(source, backdrop)); - } - - /// - /// Returns the result of the "ScreenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "ScreenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "ScreenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "ScreenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "ScreenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "ScreenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "DarkenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "DarkenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Darken(backdrop, source)); - } - - /// - /// Returns the result of the "DarkenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "DarkenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "DarkenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "DarkenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Darken(source, backdrop)); - } - - /// - /// Returns the result of the "DarkenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "DarkenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "DarkenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "DarkenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "DarkenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "DarkenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "LightenSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "LightenSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Lighten(backdrop, source)); - } - - /// - /// Returns the result of the "LightenSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "LightenSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "LightenDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "LightenDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Lighten(source, backdrop)); - } - - /// - /// Returns the result of the "LightenDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "LightenDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "LightenXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "LightenClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "LightenSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "LightenXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "OverlaySrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, Overlay(backdrop, source)); - } - - /// - /// Returns the result of the "OverlaySrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "OverlaySrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "OverlayDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "OverlayDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, Overlay(source, backdrop)); - } - - /// - /// Returns the result of the "OverlayDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "OverlayDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "OverlayXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "OverlayClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "OverlaySrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlaySrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlaySrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlaySrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "OverlayXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - /// - /// Returns the result of the "HardLightSrc" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "HardLightSrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, HardLight(backdrop, source)); - } - - /// - /// Returns the result of the "HardLightSrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "HardLightSrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "HardLightDest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "HardLightDestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, HardLight(source, backdrop)); - } - - /// - /// Returns the result of the "HardLightDestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "HardLightDestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "HardLightXor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "HardLightClear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } - - /// - /// Returns the result of the "HardLightSrc" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightSrcAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightSrcOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightSrcIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightSrcOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightDest" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightDestAtop" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightDestOver" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightDestIn" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightDestOut" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightClear" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } - - - /// - /// Returns the result of the "HardLightXor" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } + /// + /// Returns the result of the "NormalSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Normal(source, backdrop)); + } + + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Normal(source, backdrop)); + } + + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "NormalSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "MultiplyDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "MultiplySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "AddSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "SubtractSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "ScreenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "DarkenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "LightenSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return source; + } + + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + + + /// + /// Returns the result of the "HardLightSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } + + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 0cd4e637c4..40d8b89970 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -16,215 +16,212 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders -{ - internal static partial class PorterDuffFunctions +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +internal static partial class PorterDuffFunctions +{<# void GeneratePixelBlenders(string blender) { #> + + /// + /// Returns the result of the "<#=blender#>Src" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) { -<# void GeneratePixelBlenders(string blender) { #> - /// - /// Returns the result of the "<#=blender#>Src" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return source; - } - - /// - /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(backdrop, source, <#=blender#>(backdrop, source)); - } - - /// - /// Returns the result of the "<#=blender#>SrcIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>SrcOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>Dest" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) - { - return backdrop; - } - - /// - /// Returns the result of the "<#=blender#>DestAtop" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Atop(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestOver" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Over(source, backdrop, <#=blender#>(source, backdrop)); - } - - /// - /// Returns the result of the "<#=blender#>DestIn" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return In(source, backdrop); - } - - /// - /// Returns the result of the "<#=blender#>DestOut" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Out(source, backdrop); - } - - /// - /// Returns the result of the "<#=blender#>Xor" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Xor(backdrop, source); - } - - /// - /// Returns the result of the "<#=blender#>Clear" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) - { - source.W *= opacity; - - return Clear(backdrop, source); - } -<# } #> + source.W *= opacity; + return source; + } -<# void GenerateGenericPixelBlender(string blender, string composer) { #> + /// + /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; - /// - /// Returns the result of the "<#=blender#><#=composer#>" compositing equation. - /// - /// The pixel format. - /// The backdrop vector. - /// The source vector. - /// The source opacity. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel - { - opacity = Numerics.Clamp(opacity, 0, 1); - TPixel dest = default; - dest.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); - return dest; - } + return Atop(backdrop, source, <#=blender#>(backdrop, source)); + } -<# } #> + /// + /// Returns the result of the "<#=blender#>SrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; -<# + return Over(backdrop, source, <#=blender#>(backdrop, source)); + } + + /// + /// Returns the result of the "<#=blender#>SrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the result of the "<#=blender#>SrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "<#=blender#>Dest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "<#=blender#>DestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Atop(source, backdrop, <#=blender#>(source, backdrop)); + } + + /// + /// Returns the result of the "<#=blender#>DestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Over(source, backdrop, <#=blender#>(source, backdrop)); + } + /// + /// Returns the result of the "<#=blender#>DestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the result of the "<#=blender#>DestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "<#=blender#>Xor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "<#=blender#>Clear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + + return Clear(backdrop, source); + } + +<#} #> + +<# void GenerateGenericPixelBlender(string blender, string composer) { #> + + /// + /// Returns the result of the "<#=blender#><#=composer#>" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + TPixel dest = default; + dest.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return dest; + } +<# } #> +<# var composers = new []{ "Src", "SrcAtop", @@ -252,16 +249,13 @@ var blenders = new []{ "HardLight" }; - foreach(var blender in blenders) +foreach(var blender in blenders) +{ + GeneratePixelBlenders(blender); + foreach(var composer in composers) { - GeneratePixelBlenders(blender); - - foreach(var composer in composers) - { - GenerateGenericPixelBlender(blender,composer); - } + GenerateGenericPixelBlender(blender,composer); } - +} #> - } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index e6915b7baf..9bc7e35f30 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -1,270 +1,268 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Collection of Porter Duff Color Blending and Alpha Composition Functions. +/// +/// +/// These functions are designed to be a general solution for all color cases, +/// that is, they take in account the alpha value of both the backdrop +/// and source, and there's no need to alpha-premultiply neither the backdrop +/// nor the source. +/// Note there are faster functions for when the backdrop color is known +/// to be opaque +/// +internal static partial class PorterDuffFunctions { /// - /// Collection of Porter Duff Color Blending and Alpha Composition Functions. + /// Returns the result of the "Normal" compositing equation. /// - /// - /// These functions are designed to be a general solution for all color cases, - /// that is, they take in account the alpha value of both the backdrop - /// and source, and there's no need to alpha-premultiply neither the backdrop - /// nor the source. - /// Note there are faster functions for when the backdrop color is known - /// to be opaque - /// - internal static partial class PorterDuffFunctions + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Normal(Vector4 backdrop, Vector4 source) { - /// - /// Returns the result of the "Normal" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Normal(Vector4 backdrop, Vector4 source) - { - return source; - } - - /// - /// Returns the result of the "Multiply" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Multiply(Vector4 backdrop, Vector4 source) - { - return backdrop * source; - } - - /// - /// Returns the result of the "Add" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Add(Vector4 backdrop, Vector4 source) - { - return Vector4.Min(Vector4.One, backdrop + source); - } - - /// - /// Returns the result of the "Subtract" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Subtract(Vector4 backdrop, Vector4 source) - { - return Vector4.Max(Vector4.Zero, backdrop - source); - } - - /// - /// Returns the result of the "Screen" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Screen(Vector4 backdrop, Vector4 source) - { - return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); - } - - /// - /// Returns the result of the "Darken" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Darken(Vector4 backdrop, Vector4 source) - { - return Vector4.Min(backdrop, source); - } - - /// - /// Returns the result of the "Lighten" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Lighten(Vector4 backdrop, Vector4 source) - { - return Vector4.Max(backdrop, source); - } - - /// - /// Returns the result of the "Overlay" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Overlay(Vector4 backdrop, Vector4 source) - { - float cr = OverlayValueFunction(backdrop.X, source.X); - float cg = OverlayValueFunction(backdrop.Y, source.Y); - float cb = OverlayValueFunction(backdrop.Z, source.Z); - - return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); - } - - /// - /// Returns the result of the "HardLight" compositing equation. - /// - /// The backdrop vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 HardLight(Vector4 backdrop, Vector4 source) - { - float cr = OverlayValueFunction(source.X, backdrop.X); - float cg = OverlayValueFunction(source.Y, backdrop.Y); - float cb = OverlayValueFunction(source.Z, backdrop.Z); - - return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); - } - - /// - /// Helper function for Overlay andHardLight modes - /// - /// Backdrop color element - /// Source color element - /// Overlay value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float OverlayValueFunction(float backdrop, float source) - { - return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop)); - } - - /// - /// Returns the result of the "Over" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend) - { - // calculate weights - float blendW = destination.W * source.W; - float dstW = destination.W - blendW; - float srcW = source.W - blendW; - - // calculate final alpha - float alpha = dstW + source.W; - - // calculate final color - Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW); - - // unpremultiply - color /= MathF.Max(alpha, Constants.Epsilon); - color.W = alpha; - - return color; - } - - /// - /// Returns the result of the "Atop" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The amount to blend. Range 0..1 - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend) - { - // calculate weights - float blendW = destination.W * source.W; - float dstW = destination.W - blendW; - - // calculate final alpha - float alpha = destination.W; - - // calculate final color - Vector4 color = (destination * dstW) + (blend * blendW); - - // unpremultiply - color /= MathF.Max(alpha, Constants.Epsilon); - color.W = alpha; - - return color; - } - - /// - /// Returns the result of the "In" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 In(Vector4 destination, Vector4 source) - { - float alpha = destination.W * source.W; - - Vector4 color = source * alpha; // premultiply - color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply - color.W = alpha; - - return color; - } - - /// - /// Returns the result of the "Out" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Out(Vector4 destination, Vector4 source) - { - float alpha = (1 - destination.W) * source.W; - - Vector4 color = source * alpha; // premultiply - color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply - color.W = alpha; - - return color; - } - - /// - /// Returns the result of the "XOr" compositing equation. - /// - /// The destination vector. - /// The source vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Xor(Vector4 destination, Vector4 source) - { - float srcW = 1 - destination.W; - float dstW = 1 - source.W; - - float alpha = (source.W * srcW) + (destination.W * dstW); - Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW); - - // unpremultiply - color /= MathF.Max(alpha, Constants.Epsilon); - color.W = alpha; - - return color; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 Clear(Vector4 backdrop, Vector4 source) - { - return Vector4.Zero; - } + return source; + } + + /// + /// Returns the result of the "Multiply" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Multiply(Vector4 backdrop, Vector4 source) + { + return backdrop * source; + } + + /// + /// Returns the result of the "Add" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Add(Vector4 backdrop, Vector4 source) + { + return Vector4.Min(Vector4.One, backdrop + source); + } + + /// + /// Returns the result of the "Subtract" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Subtract(Vector4 backdrop, Vector4 source) + { + return Vector4.Max(Vector4.Zero, backdrop - source); + } + + /// + /// Returns the result of the "Screen" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Screen(Vector4 backdrop, Vector4 source) + { + return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); + } + + /// + /// Returns the result of the "Darken" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Darken(Vector4 backdrop, Vector4 source) + { + return Vector4.Min(backdrop, source); + } + + /// + /// Returns the result of the "Lighten" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Lighten(Vector4 backdrop, Vector4 source) + { + return Vector4.Max(backdrop, source); + } + + /// + /// Returns the result of the "Overlay" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Overlay(Vector4 backdrop, Vector4 source) + { + float cr = OverlayValueFunction(backdrop.X, source.X); + float cg = OverlayValueFunction(backdrop.Y, source.Y); + float cb = OverlayValueFunction(backdrop.Z, source.Z); + + return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); + } + + /// + /// Returns the result of the "HardLight" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLight(Vector4 backdrop, Vector4 source) + { + float cr = OverlayValueFunction(source.X, backdrop.X); + float cg = OverlayValueFunction(source.Y, backdrop.Y); + float cb = OverlayValueFunction(source.Z, backdrop.Z); + + return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - (2 * (1 - source) * (1 - backdrop)); + } + + /// + /// Returns the result of the "Over" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 blend) + { + // calculate weights + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; + float srcW = source.W - blendW; + + // calculate final alpha + float alpha = dstW + source.W; + + // calculate final color + Vector4 color = (destination * dstW) + (source * srcW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + /// + /// Returns the result of the "Atop" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 blend) + { + // calculate weights + float blendW = destination.W * source.W; + float dstW = destination.W - blendW; + + // calculate final alpha + float alpha = destination.W; + + // calculate final color + Vector4 color = (destination * dstW) + (blend * blendW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + /// + /// Returns the result of the "In" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 In(Vector4 destination, Vector4 source) + { + float alpha = destination.W * source.W; + + Vector4 color = source * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + /// + /// Returns the result of the "Out" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Out(Vector4 destination, Vector4 source) + { + float alpha = (1 - destination.W) * source.W; + + Vector4 color = source * alpha; // premultiply + color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply + color.W = alpha; + + return color; + } + + /// + /// Returns the result of the "XOr" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Xor(Vector4 destination, Vector4 source) + { + float srcW = 1 - destination.W; + float dstW = 1 - source.W; + + float alpha = (source.W * srcW) + (destination.W * dstW); + Vector4 color = (source.W * source * srcW) + (destination.W * destination * dstW); + + // unpremultiply + color /= MathF.Max(alpha, Constants.Epsilon); + color.W = alpha; + + return color; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Clear(Vector4 backdrop, Vector4 source) + { + return Vector4.Zero; } } diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 43bcdf4261..4ed6174386 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -1,157 +1,155 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Abstract base class for calling pixel composition functions +/// +/// The type of the pixel +public abstract class PixelBlender + where TPixel : unmanaged, IPixel { /// - /// Abstract base class for calling pixel composition functions + /// Blend 2 pixels together. + /// + /// The background color. + /// The source color. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// The final pixel value after composition. + public abstract TPixel Blend(TPixel background, TPixel source, float amount); + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); + Span destinationVectors = buffer.Slice(0, maxLength); + Span backgroundVectors = buffer.Slice(maxLength, maxLength); + Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + } + + /// + /// Blends 2 rows together /// - /// The type of the pixel - public abstract class PixelBlender - where TPixel : unmanaged, IPixel + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + => this.Blend(configuration, destination, background, source, amount); + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount) + where TPixelSrc : unmanaged, IPixel { - /// - /// Blend 2 pixels together. - /// - /// The background color. - /// The source color. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - /// The final pixel value after composition. - public abstract TPixel Blend(TPixel background, TPixel source, float amount); - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount) - where TPixelSrc : unmanaged, IPixel - { - int maxLength = destination.Length; - Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - - using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); - - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); - - this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); - } - - /// - /// Blends 2 rows together - /// - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - => this.Blend(configuration, destination, background, source, amount); - - /// - /// Blends 2 rows together - /// - /// the pixel format of the source span - /// to use internally - /// the destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - public void Blend( - Configuration configuration, - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount) - where TPixelSrc : unmanaged, IPixel - { - int maxLength = destination.Length; - Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); - - using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); - - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); - - this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); - } - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - float amount); - - /// - /// Blend 2 rows together. - /// - /// destination span - /// the background span - /// the source span - /// - /// A span with values between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. - /// - protected abstract void BlendFunction( - Span destination, - ReadOnlySpan background, - ReadOnlySpan source, - ReadOnlySpan amount); + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); + Span destinationVectors = buffer.Slice(0, maxLength); + Span backgroundVectors = buffer.Slice(maxLength, maxLength); + Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + + PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); + + this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); + + PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); } + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount); + + /// + /// Blend 2 rows together. + /// + /// destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount); } diff --git a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs index 2d669ee6bc..7aa07cf51a 100644 --- a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs +++ b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs @@ -1,56 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Enumerates the various color blending modes. +/// +public enum PixelColorBlendingMode { /// - /// Enumerates the various color blending modes. - /// - public enum PixelColorBlendingMode - { - /// - /// Default blending mode, also known as "Normal" or "Alpha Blending" - /// - Normal = 0, - - /// - /// Blends the 2 values by multiplication. - /// - Multiply, - - /// - /// Blends the 2 values by addition. - /// - Add, - - /// - /// Blends the 2 values by subtraction. - /// - Subtract, - - /// - /// Multiplies the complements of the backdrop and source values, then complements the result. - /// - Screen, - - /// - /// Selects the minimum of the backdrop and source values. - /// - Darken, - - /// - /// Selects the max of the backdrop and source values. - /// - Lighten, - - /// - /// Multiplies or screens the values, depending on the backdrop vector values. - /// - Overlay, - - /// - /// Multiplies or screens the colors, depending on the source value. - /// - HardLight, - } + /// Default blending mode, also known as "Normal" or "Alpha Blending" + /// + Normal = 0, + + /// + /// Blends the 2 values by multiplication. + /// + Multiply, + + /// + /// Blends the 2 values by addition. + /// + Add, + + /// + /// Blends the 2 values by subtraction. + /// + Subtract, + + /// + /// Multiplies the complements of the backdrop and source values, then complements the result. + /// + Screen, + + /// + /// Selects the minimum of the backdrop and source values. + /// + Darken, + + /// + /// Selects the max of the backdrop and source values. + /// + Lighten, + + /// + /// Multiplies or screens the values, depending on the backdrop vector values. + /// + Overlay, + + /// + /// Multiplies or screens the colors, depending on the source value. + /// + HardLight, } diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs index 375f7bee34..9db3d30045 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Flags responsible to select additional operations which could be efficiently applied in +/// +/// or +/// +/// knowing the pixel type. +/// +[Flags] +public enum PixelConversionModifiers { /// - /// Flags responsible to select additional operations which could be efficiently applied in - /// - /// or - /// - /// knowing the pixel type. + /// No special operation is selected /// - [Flags] - public enum PixelConversionModifiers - { - /// - /// No special operation is selected - /// - None = 0, + None = 0, - /// - /// Select and instead the standard (non scaled) variants. - /// - Scale = 1 << 0, + /// + /// Select and instead the standard (non scaled) variants. + /// + Scale = 1 << 0, - /// - /// Enable alpha premultiplication / unpremultiplication - /// - Premultiply = 1 << 1, + /// + /// Enable alpha premultiplication / unpremultiplication + /// + Premultiply = 1 << 1, - /// - /// Enable SRGB companding (defined in ). - /// - SRgbCompand = 1 << 2, - } + /// + /// Enable SRGB companding (defined in ). + /// + SRgbCompand = 1 << 2, } diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs index e74ba2731b..2e05b2f8b4 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiersExtensions.cs @@ -3,36 +3,35 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Extension and utility methods for . +/// +internal static class PixelConversionModifiersExtensions { - /// - /// Extension and utility methods for . - /// - internal static class PixelConversionModifiersExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDefined(this PixelConversionModifiers modifiers, PixelConversionModifiers expected) => - (modifiers & expected) == expected; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDefined(this PixelConversionModifiers modifiers, PixelConversionModifiers expected) => + (modifiers & expected) == expected; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PixelConversionModifiers Remove( - this PixelConversionModifiers modifiers, - PixelConversionModifiers removeThis) => - modifiers & ~removeThis; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelConversionModifiers Remove( + this PixelConversionModifiers modifiers, + PixelConversionModifiers removeThis) => + modifiers & ~removeThis; - /// - /// Applies the union of and , - /// if is true, returns unmodified otherwise. - /// - /// - /// and - /// should be always used together! - /// - public static PixelConversionModifiers ApplyCompanding( - this PixelConversionModifiers originalModifiers, - bool compand) => - compand - ? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand - : originalModifiers; - } + /// + /// Applies the union of and , + /// if is true, returns unmodified otherwise. + /// + /// + /// and + /// should be always used together! + /// + public static PixelConversionModifiers ApplyCompanding( + this PixelConversionModifiers originalModifiers, + bool compand) => + compand + ? originalModifiers | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand + : originalModifiers; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index 8d6ffe4f0e..db4c5921c8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -1,171 +1,169 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing a single 8-bit normalized alpha value. +/// +/// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. +/// +/// +public partial struct A8 : IPixel, IPackedVector { /// - /// Packed pixel type containing a single 8-bit normalized alpha value. - /// - /// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The alpha component. + public A8(byte alpha) => this.PackedValue = alpha; + + /// + /// Initializes a new instance of the struct. /// - public partial struct A8 : IPixel, IPackedVector + /// The alpha component. + public A8(float alpha) => this.PackedValue = Pack(alpha); + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(A8 left, A8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(A8 left, A8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(0, 0, 0, this.PackedValue / 255F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = source.A; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) { - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public A8(byte alpha) => this.PackedValue = alpha; - - /// - /// Initializes a new instance of the struct. - /// - /// The alpha component. - public A8(float alpha) => this.PackedValue = Pack(alpha); - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(A8 left, A8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(A8 left, A8 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.W); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(0, 0, 0, this.PackedValue / 255F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.PackedValue = source.A; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest = default; - dest.A = this.PackedValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Compares an object with the packed vector. - /// - /// The object to compare. - /// True if the object is equal to the packed vector. - public override readonly bool Equals(object obj) => obj is A8 other && this.Equals(other); - - /// - /// Compares another A8 packed vector with the packed vector. - /// - /// The A8 packed vector to compare. - /// True if the packed vectors are equal. - [MethodImpl(InliningOptions.ShortMethod)] - 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 readonly string ToString() => $"A8({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - /// Packs a into a byte. - /// - /// The float containing the value to pack. - /// The containing the packed values. - [MethodImpl(InliningOptions.ShortMethod)] - private static byte Pack(float alpha) => (byte)Math.Round(Numerics.Clamp(alpha, 0, 1F) * 255F); + dest = default; + dest.A = this.PackedValue; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = byte.MaxValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Compares an object with the packed vector. + /// + /// The object to compare. + /// True if the object is equal to the packed vector. + public override readonly bool Equals(object obj) => obj is A8 other && this.Equals(other); + + /// + /// Compares another A8 packed vector with the packed vector. + /// + /// The A8 packed vector to compare. + /// True if the packed vectors are equal. + [MethodImpl(InliningOptions.ShortMethod)] + 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 readonly string ToString() => $"A8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a byte. + /// + /// The float containing the value to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static byte Pack(float alpha) => (byte)Math.Round(Numerics.Clamp(alpha, 0, 1F) * 255F); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 110d6f9633..c1c10f09a5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -1,396 +1,394 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Abgr32 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the alpha component. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Abgr32 : IPixel, IPackedVector + public byte A; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b) { - /// - /// Gets or sets the alpha component. - /// - public byte A; - - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// The maximum byte value. - /// - private static readonly Vector4 MaxBytes = new(255); - - /// - /// The half vector value. - /// - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(float r, float g, float b, float a = 1) - : this() => this.Pack(r, g, b, a); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(Vector3 vector) - : this() => this.Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(Vector4 vector) - : this() => this.Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Abgr32(uint packed) - : this() => this.Abgr = packed; - - /// - /// Gets or sets the packed representation of the Abgrb32 struct. - /// - public uint Abgr - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => this.Abgr; - - [MethodImpl(InliningOptions.ShortMethod)] - set => this.Abgr = value; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Abgr32 source) => new(source); + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Abgr32(Color color) => color.ToAbgr32(); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Abgr32 left, Abgr32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Abgr32 left, Abgr32 right) => !left.Equals(right); + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector3 vector) + : this() => this.Pack(ref vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector4 vector) + : this() => this.Pack(ref vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.Pack(ref vector); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(uint packed) + : this() => this.Abgr = packed; - /// + /// + /// Gets or sets the packed representation of the Abgrb32 struct. + /// + public uint Abgr + { [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this = source; + set => Unsafe.As(ref this) = value; + } - /// + /// + public uint PackedValue + { [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + readonly get => this.Abgr; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - // We can assign the Bgr24 value directly to last three bytes of this instance. - ref byte thisRef = ref Unsafe.As(ref this); - ref byte thisRefFromB = ref Unsafe.AddByteOffset(ref thisRef, new IntPtr(1)); - Unsafe.As(ref thisRefFromB) = source; - this.A = byte.MaxValue; - } - - /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = this.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - public override readonly bool Equals(object obj) => obj is Abgr32 abgr32 && this.Equals(abgr32); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Abgr32 other) => this.Abgr == other.Abgr; + set => this.Abgr = value; + } - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override readonly string ToString() => $"Abgr({this.A}, {this.B}, {this.G}, {this.R})"; + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Abgr32 source) => new(source); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.Abgr.GetHashCode(); - - /// - /// Packs the four floats into a color. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(float x, float y, float z, float w) - { - var value = new Vector4(x, y, z, w); - this.Pack(ref value); - } - - /// - /// Packs a into a uint. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector3 vector) - { - var value = new Vector4(vector, 1); - this.Pack(ref value); - } - - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - this.R = (byte)vector.X; - this.G = (byte)vector.Y; - this.B = (byte)vector.Z; - this.A = (byte)vector.W; - } + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Abgr32(Color color) => color.ToAbgr32(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Abgr32 left, Abgr32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Abgr32 left, Abgr32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + // We can assign the Bgr24 value directly to last three bytes of this instance. + ref byte thisRef = ref Unsafe.As(ref this); + ref byte thisRefFromB = ref Unsafe.AddByteOffset(ref thisRef, new IntPtr(1)); + Unsafe.As(ref thisRefFromB) = source; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override readonly bool Equals(object obj) => obj is Abgr32 abgr32 && this.Equals(abgr32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Abgr32 other) => this.Abgr == other.Abgr; + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override readonly string ToString() => $"Abgr({this.A}, {this.B}, {this.G}, {this.R})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.Abgr.GetHashCode(); + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index dd36616080..a5e2b63c8b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -5,390 +5,389 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Argb32 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the alpha component. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Argb32 : IPixel, IPackedVector + public byte A; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(byte r, byte g, byte b) { - /// - /// Gets or sets the alpha component. - /// - public byte A; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// The maximum byte value. - /// - private static readonly Vector4 MaxBytes = new(255); - - /// - /// The half vector value. - /// - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(float r, float g, float b, float a = 1) - : this() => this.Pack(r, g, b, a); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(Vector3 vector) - : this() => this.Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(Vector4 vector) - : this() => this.Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Argb32(uint packed) - : this() => this.Argb = packed; - - /// - /// Gets or sets the packed representation of the Argb32 struct. - /// - public uint Argb - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => this.Argb; - - [MethodImpl(InliningOptions.ShortMethod)] - set => this.Argb = value; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Argb32 source) => new(source); + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Argb32(Color color) => color.ToArgb32(); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Argb32 left, Argb32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(Vector3 vector) + : this() => this.Pack(ref vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(Vector4 vector) + : this() => this.Pack(ref vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.Pack(ref vector); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Argb32(uint packed) + : this() => this.Argb = packed; - /// + /// + /// Gets or sets the packed representation of the Argb32 struct. + /// + public uint Argb + { [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = source.PackedValue; + set => Unsafe.As(ref this) = value; + } - /// + /// + public uint PackedValue + { [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + readonly get => this.Argb; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = byte.MaxValue; - } - - /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = this.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - public override readonly bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Argb32 other) => this.Argb == other.Argb; + set => this.Argb = value; + } - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Argb32 source) => new(source); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.Argb.GetHashCode(); - - /// - /// Packs the four floats into a color. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(float x, float y, float z, float w) - { - var value = new Vector4(x, y, z, w); - this.Pack(ref value); - } - - /// - /// Packs a into a uint. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector3 vector) - { - var value = new Vector4(vector, 1); - this.Pack(ref value); - } - - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - this.R = (byte)vector.X; - this.G = (byte)vector.Y; - this.B = (byte)vector.Z; - this.A = (byte)vector.W; - } + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Argb32(Color color) => color.ToArgb32(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Argb32 left, Argb32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = source.PackedValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override readonly bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.Argb.GetHashCode(); + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 3c8d24ef4d..dbf02a77b9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -1,244 +1,242 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in blue, green, red order (least significant to most significant byte). +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Bgr24 : IPixel { /// - /// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in blue, green, red order (least significant to most significant byte). - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// The blue component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Bgr24 : IPixel + [FieldOffset(0)] + public byte B; + + /// + /// The green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// The red component. + /// + [FieldOffset(2)] + public byte R; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgr24(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Bgr24 source) => new(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Bgr24(Color color) => color.ToBgr24(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgr24 left, Bgr24 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + Rgba32 rgba = default; + rgba.FromVector4(vector); + this.FromRgba32(rgba); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) { - /// - /// The blue component. - /// - [FieldOffset(0)] - public byte B; - - /// - /// The green component. - /// - [FieldOffset(1)] - public byte G; - - /// - /// The red component. - /// - [FieldOffset(2)] - public byte R; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Bgr24(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Bgr24 source) => new(source); - - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Bgr24(Color color) => color.ToBgr24(); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Bgr24 left, Bgr24 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - Rgba32 rgba = default; - rgba.FromVector4(vector); - this.FromRgba32(rgba); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - // We can assign this instances value directly to last three bytes of the Abgr32. - ref byte sourceRef = ref Unsafe.As(ref source); - ref byte sourceRefFromB = ref Unsafe.AddByteOffset(ref sourceRef, new IntPtr(1)); - this = Unsafe.As(ref sourceRefFromB); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this = source.Bgr; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override readonly bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); - - /// - public override readonly string ToString() => $"Bgr24({this.B}, {this.G}, {this.R})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + this.R = source.R; + this.G = source.G; + this.B = source.B; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + // We can assign this instances value directly to last three bytes of the Abgr32. + ref byte sourceRef = ref Unsafe.As(ref source); + ref byte sourceRefFromB = ref Unsafe.AddByteOffset(ref sourceRef, new IntPtr(1)); + this = Unsafe.As(ref sourceRefFromB); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source.Bgr; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override readonly bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); + + /// + public override readonly string ToString() => $"Bgr24({this.B}, {this.G}, {this.R})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 1fb7800929..853ebaa63b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -1,181 +1,179 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x and z components use 5 bits, and the y component uses 6 bits. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Bgr565 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x and z components use 5 bits, and the y component uses 6 bits. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + public Bgr565(float x, float y, float z) + : this(new Vector3(x, y, z)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed value. + /// + public Bgr565(Vector3 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgr565 left, Bgr565 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector3 = new Vector3(vector.X, vector.Y, vector.Z); + this.PackedValue = Pack(ref vector3); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.ToVector3(), 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromVector4(source.ToVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. /// - public partial struct Bgr565 : IPixel, IPackedVector + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector3 ToVector3() => new( + ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), + ((this.PackedValue >> 5) & 0x3F) * (1F / 63F), + (this.PackedValue & 0x1F) * (1F / 31F)); + + /// + public override readonly bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - public Bgr565(float x, float y, float z) - : this(new Vector3(x, y, z)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed value. - /// - public Bgr565(Vector3 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Bgr565 left, Bgr565 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - var vector3 = new Vector3(vector.X, vector.Y, vector.Z); - this.PackedValue = Pack(ref vector3); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.ToVector3(), 1F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromVector4(source.ToVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromVector4(source.ToVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromVector4(source.ToVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromVector4(source.ToVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromVector4(source.ToVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector3 ToVector3() => new( - ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), - ((this.PackedValue >> 5) & 0x3F) * (1F / 63F), - (this.PackedValue & 0x1F) * (1F / 31F)); - - /// - public override readonly bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - var vector = this.ToVector3(); - return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static ushort Pack(ref Vector3 vector) - { - vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); - - return (ushort)((((int)Math.Round(vector.X * 31F) & 0x1F) << 11) - | (((int)Math.Round(vector.Y * 63F) & 0x3F) << 5) - | ((int)Math.Round(vector.Z * 31F) & 0x1F)); - } + var vector = this.ToVector3(); + return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector3 vector) + { + vector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + + return (ushort)((((int)Math.Round(vector.X * 31F) & 0x1F) << 11) + | (((int)Math.Round(vector.Y * 63F) & 0x3F) << 5) + | ((int)Math.Round(vector.Z * 31F) & 0x1F)); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index b8e0e4addf..02d2c24d5e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -5,313 +5,312 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte). +/// The format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Bgra32 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in blue, green, red, and alpha order (least significant to most significant byte). - /// The format is binary compatible with System.Drawing.Imaging.PixelFormat.Format32bppArgb - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the blue component. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Bgra32 : IPixel, IPackedVector + public byte B; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgra32(byte r, byte g, byte b) { - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// Gets or sets the alpha component. - /// - public byte A; - - /// - /// The maximum byte value. - /// - private static readonly Vector4 MaxBytes = new(255); - - /// - /// The half vector value. - /// - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Bgra32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Bgra32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Gets or sets the packed representation of the Bgra32 struct. - /// - public uint Bgra - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - public uint PackedValue - { - readonly get => this.Bgra; - set => this.Bgra = value; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Bgra32 source) => new(source); + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Bgra32(Color color) => color.ToBgra32(); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Bgra32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Gets or sets the packed representation of the Bgra32 struct. + /// + public uint Bgra + { [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Bgra32 left, Bgra32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); + set => Unsafe.As(ref this) = value; + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + public uint PackedValue + { + readonly get => this.Bgra; + set => this.Bgra = value; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Bgra32 source) => new(source); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Bgra32(Color color) => color.ToBgra32(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.Pack(ref vector); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra32 left, Bgra32 right) => left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this = source; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = this.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - public override readonly bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); - - /// - public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); - - /// - public override readonly int GetHashCode() => this.Bgra.GetHashCode(); - - /// - public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; - - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - this.R = (byte)vector.X; - this.G = (byte)vector.Y; - this.B = (byte)vector.Z; - this.A = (byte)vector.W; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override readonly bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); + + /// + public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); + + /// + public override readonly int GetHashCode() => this.Bgra.GetHashCode(); + + /// + public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 0e96b7e6e1..2e3e53eecc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -1,173 +1,171 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Bgra4444 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra4444(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the components for the packed vector. + public Bgra4444(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct Bgra4444 : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra4444 left, Bgra4444 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + const float Max = 1 / 15F; + + return new Vector4( + (this.PackedValue >> 8) & 0x0F, + (this.PackedValue >> 4) & 0x0F, + this.PackedValue & 0x0F, + (this.PackedValue >> 12) & 0x0F) * Max; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); + + /// + 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.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Bgra4444(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the components for the packed vector. - public Bgra4444(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Bgra4444 left, Bgra4444 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - const float Max = 1 / 15F; - - return new Vector4( - (this.PackedValue >> 8) & 0x0F, - (this.PackedValue >> 4) & 0x0F, - this.PackedValue & 0x0F, - (this.PackedValue >> 12) & 0x0F) * Max; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); - - /// - 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.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static ushort Pack(ref Vector4 vector) - { - vector = Numerics.Clamp(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) - | ((int)Math.Round(vector.Z * 15F) & 0x0F)); - } + vector = Numerics.Clamp(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) + | ((int)Math.Round(vector.Z * 15F) & 0x0F)); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index c9bafdce57..03660df7ae 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -1,172 +1,170 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// The x , y and z components use 5 bits, and the w component uses 1 bit. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Bgra5551 : IPixel, IPackedVector { /// - /// Packed pixel type containing unsigned normalized values ranging from 0 to 1. - /// The x , y and z components use 5 bits, and the w component uses 1 bit. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// - public partial struct Bgra5551 : IPixel, IPackedVector + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Bgra5551(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra5551(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Bgra5551 left, Bgra5551 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new( + ((this.PackedValue >> 10) & 0x1F) / 31F, + ((this.PackedValue >> 5) & 0x1F) / 31F, + ((this.PackedValue >> 0) & 0x1F) / 31F, + (this.PackedValue >> 15) & 0x01); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override bool Equals(object obj) => obj is Bgra5551 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); + + /// + 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.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Bgra5551(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Bgra5551(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Bgra5551 left, Bgra5551 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - ((this.PackedValue >> 10) & 0x1F) / 31F, - ((this.PackedValue >> 5) & 0x1F) / 31F, - ((this.PackedValue >> 0) & 0x1F) / 31F, - (this.PackedValue >> 15) & 0x01); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override bool Equals(object obj) => obj is Bgra5551 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); - - /// - 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.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static ushort Pack(ref Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - return (ushort)( - (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) - | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) - | (((int)Math.Round(vector.Z * 31F) & 0x1F) << 0) - | (((int)Math.Round(vector.W) & 0x1) << 15)); - } + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + return (ushort)( + (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) + | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) + | (((int)Math.Round(vector.Z * 31F) & 0x1F) << 0) + | (((int)Math.Round(vector.W) & 0x1) << 15)); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index f197afb3ec..1797483b4c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -1,182 +1,180 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. +/// +/// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. +/// +/// +public partial struct Byte4 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. - /// - /// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// + /// A vector containing the initial values for the components of the Byte4 structure. + /// + public Byte4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + /// Initializes a new instance of the struct. /// - public partial struct Byte4 : IPixel, IPackedVector + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Byte4(float x, float y, float z, float w) { - /// - /// Initializes a new instance of the struct. - /// - /// - /// A vector containing the initial values for the components of the Byte4 structure. - /// - public Byte4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Byte4(float x, float y, float z, float w) - { - var vector = new Vector4(x, y, z, w); - this.PackedValue = Pack(ref vector); - } - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Byte4 left, Byte4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector * 255F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255F; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - this.PackedValue & 0xFF, - (this.PackedValue >> 0x8) & 0xFF, - (this.PackedValue >> 0x10) & 0xFF, - (this.PackedValue >> 0x18) & 0xFF); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - 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.##})"); - } - - /// - /// Packs a vector into a uint. - /// - /// The vector containing the values to pack. - /// The containing the packed values. - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(ref Vector4 vector) - { - const float Max = 255F; - - // Clamp the value between min and max values - vector = Numerics.Clamp(vector, Vector4.Zero, new Vector4(Max)); - - uint byte4 = (uint)Math.Round(vector.X) & 0xFF; - uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; - uint byte2 = ((uint)Math.Round(vector.Z) & 0xFF) << 0x10; - uint byte1 = ((uint)Math.Round(vector.W) & 0xFF) << 0x18; - - return byte4 | byte3 | byte2 | byte1; - } + var vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Byte4 left, Byte4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector * 255F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255F; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new( + this.PackedValue & 0xFF, + (this.PackedValue >> 0x8) & 0xFF, + (this.PackedValue >> 0x10) & 0xFF, + (this.PackedValue >> 0x18) & 0xFF); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + /// + /// Packs a vector into a uint. + /// + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + const float Max = 255F; + + // Clamp the value between min and max values + vector = Numerics.Clamp(vector, Vector4.Zero, new Vector4(Max)); + + uint byte4 = (uint)Math.Round(vector.X) & 0xFF; + uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; + uint byte2 = ((uint)Math.Round(vector.Z) & 0xFF) << 0x10; + uint byte1 = ((uint)Math.Round(vector.W) & 0xFF) << 0x18; + + return byte4 | byte3 | byte2 | byte1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 84354a5c28..56f6e35280 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -1,156 +1,154 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing a single 16 bit floating point value. +/// +/// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. +/// +/// +public partial struct HalfSingle : IPixel, IPackedVector { /// - /// Packed pixel type containing a single 16 bit floating point value. - /// - /// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The single component value. + public HalfSingle(float value) => this.PackedValue = HalfTypeHelper.Pack(value); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. /// - public partial struct HalfSingle : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfSingle left, HalfSingle right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfSingle left, HalfSingle right) => !left.Equals(right); + + /// + public PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The single component value. - public HalfSingle(float value) => this.PackedValue = HalfTypeHelper.Pack(value); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(HalfSingle left, HalfSingle right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(HalfSingle left, HalfSingle right) => !left.Equals(right); - - /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - float scaled = vector.X; - scaled *= 2F; - scaled--; - this.PackedValue = HalfTypeHelper.Pack(scaled); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - float single = this.ToSingle() + 1F; - single /= 2F; - return new Vector4(single, 0, 0, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = HalfTypeHelper.Pack(vector.X); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.ToSingle(), 0, 0, 1F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); - - /// - public override readonly bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + float scaled = vector.X; + scaled *= 2F; + scaled--; + this.PackedValue = HalfTypeHelper.Pack(scaled); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + float single = this.ToSingle() + 1F; + single /= 2F; + return new Vector4(single, 0, 0, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = HalfTypeHelper.Pack(vector.X); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.ToSingle(), 0, 0, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 3c2efb15a4..57864f990b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -1,185 +1,183 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit floating-point values. +/// +/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public partial struct HalfVector2 : IPixel, IPackedVector { /// - /// Packed pixel type containing two 16-bit floating-point values. - /// - /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public HalfVector2(float x, float y) => this.PackedValue = Pack(x, y); + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public HalfVector2(Vector2 vector) => this.PackedValue = Pack(vector.X, vector.Y); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfVector2 left, HalfVector2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled.X, scaled.Y); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.X, vector.Y); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + var vector = this.ToVector2(); + return new Vector4(vector.X, vector.Y, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . /// - public partial struct HalfVector2 : IPixel, IPackedVector + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector2 ToVector2() + { + Vector2 vector; + vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); + vector.Y = HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)); + return vector; + } + + /// + public override readonly bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(float x, float y) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public HalfVector2(float x, float y) => this.PackedValue = Pack(x, y); - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components. - public HalfVector2(Vector2 vector) => this.PackedValue = Pack(vector.X, vector.Y); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(HalfVector2 left, HalfVector2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; - scaled -= Vector2.One; - this.PackedValue = Pack(scaled.X, scaled.Y); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; - return new Vector4(scaled, 0F, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(vector.X, vector.Y); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - var vector = this.ToVector2(); - return new Vector4(vector.X, vector.Y, 0F, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector2 ToVector2() - { - Vector2 vector; - vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); - vector.Y = HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)); - return vector; - } - - /// - public override readonly bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(float x, float y) - { - uint num2 = HalfTypeHelper.Pack(x); - uint num = (uint)(HalfTypeHelper.Pack(y) << 0x10); - return num2 | num; - } + uint num2 = HalfTypeHelper.Pack(x); + uint num = (uint)(HalfTypeHelper.Pack(y) << 0x10); + return num2 | num; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 82c64b1ae7..bd25991194 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -1,184 +1,182 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 16-bit floating-point values. +/// +/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct HalfVector4 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 16-bit floating-point values. - /// - /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public HalfVector4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components + public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(HalfVector4 left, HalfVector4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new( + HalfTypeHelper.Unpack((ushort)this.PackedValue), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + 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.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + /// Packs a into a . /// - public partial struct HalfVector4 : IPixel, IPackedVector + /// The vector containing the values to pack. + /// The containing the packed values. + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) { - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public HalfVector4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components - public HalfVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(HalfVector4 left, HalfVector4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - vector *= 2F; - vector -= Vector4.One; - this.FromVector4(vector); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2F; - return scaled; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - HalfTypeHelper.Unpack((ushort)this.PackedValue), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), - HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - 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.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - /// Packs a into a . - /// - /// The vector containing the values to pack. - /// The containing the packed values. - [MethodImpl(InliningOptions.ShortMethod)] - private static ulong Pack(ref Vector4 vector) - { - ulong num4 = HalfTypeHelper.Pack(vector.X); - ulong num3 = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; - ulong num2 = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; - ulong num1 = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; - return num4 | num3 | num2 | num1; - } + ulong num4 = HalfTypeHelper.Pack(vector.X); + ulong num3 = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; + ulong num2 = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; + ulong num1 = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; + return num4 | num3 | num2 | num1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index 7771fe3db2..f826863331 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -4,175 +4,174 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing a single 16-bit normalized luminance value. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct L16 : IPixel, IPackedVector { + private const float Max = ushort.MaxValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component + public L16(ushort luminance) => this.PackedValue = luminance; + + /// + public ushort PackedValue { get; set; } + /// - /// Packed pixel type containing a single 16-bit normalized luminance value. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// Compares two objects for equality. /// - public partial struct L16 : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(L16 left, L16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(L16 left, L16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float scaled = this.PackedValue / Max; + return new Vector4(scaled, scaled, scaled, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.PackedValue = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = source.L; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(this.PackedValue); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); + + /// + public override readonly bool Equals(object obj) => obj is L16 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L16({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) { - private const float Max = ushort.MaxValue; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component - public L16(ushort luminance) => this.PackedValue = luminance; - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(L16 left, L16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(L16 left, L16 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - float scaled = this.PackedValue / Max; - return new Vector4(scaled, scaled, scaled, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.PackedValue = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.PackedValue = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.PackedValue = source.L; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(this.PackedValue); - dest.R = rgb; - dest.G = rgb; - dest.B = rgb; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - - /// - public override readonly bool Equals(object obj) => obj is L16 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"L16({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - this.PackedValue = ColorNumerics.Get16BitBT709Luminance( - vector.X, - vector.Y, - vector.Z); - } + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index 98701956d8..d08eee0170 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -4,164 +4,163 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing a single 8-bit normalized luminance value. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct L8 : IPixel, IPackedVector { + private static readonly Vector4 MaxBytes = new(255F); + private static readonly Vector4 Half = new(0.5F); + /// - /// Packed pixel type containing a single 8-bit normalized luminance value. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// - public partial struct L8 : IPixel, IPackedVector + /// The luminance component. + public L8(byte luminance) => this.PackedValue = luminance; + + /// + public byte PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(L8 left, L8 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(L8 left, L8 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float rgb = this.PackedValue / 255F; + return new Vector4(rgb, rgb, rgb, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.PackedValue = source.L; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.PackedValue; + dest.G = this.PackedValue; + dest.B = this.PackedValue; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + => this.PackedValue = ColorNumerics.Get8BitBT709Luminance( + ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + => this.PackedValue = ColorNumerics.Get8BitBT709Luminance( + ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); + + /// + public override readonly bool Equals(object obj) => obj is L8 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"L8({this.PackedValue})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) { - private static readonly Vector4 MaxBytes = new(255F); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - public L8(byte luminance) => this.PackedValue = luminance; - - /// - public byte PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(L8 left, L8 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(L8 left, L8 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - float rgb = this.PackedValue / 255F; - return new Vector4(rgb, rgb, rgb, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.PackedValue = source.L; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.PackedValue = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.PackedValue; - dest.G = this.PackedValue; - dest.B = this.PackedValue; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - => this.PackedValue = ColorNumerics.Get8BitBT709Luminance( - ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - => this.PackedValue = ColorNumerics.Get8BitBT709Luminance( - ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); - - /// - public override readonly bool Equals(object obj) => obj is L8 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() => $"L8({this.PackedValue})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - this.PackedValue = ColorNumerics.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); - } + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + this.PackedValue = ColorNumerics.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 cf102a3eb1..e44ca56344 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -5,231 +5,230 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 8-bit normalized values representing luminance and alpha. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct La16 : IPixel, IPackedVector { + private static readonly Vector4 MaxBytes = new(255F); + private static readonly Vector4 Half = new(0.5F); + /// - /// Packed pixel type containing two 8-bit normalized values representing luminance and alpha. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the luminance component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct La16 : IPixel, IPackedVector - { - private static readonly Vector4 MaxBytes = new(255F); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Gets or sets the luminance component. - /// - [FieldOffset(0)] - public byte L; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(1)] - public byte A; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - /// The alpha component. - public La16(byte l, byte a) - { - this.L = l; - this.A = a; - } - - /// - public ushort PackedValue - { - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(La16 left, La16 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(La16 left, La16 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly bool Equals(object obj) => obj is La16 other && this.Equals(other); - - /// - public override readonly string ToString() => $"La16({this.L}, {this.A})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - this.L = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.L = source.PackedValue; - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - this.L = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance( - ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); - - this.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); - this.A = source.A; - } - - /// - public void FromRgba64(Rgba64 source) - { - this.L = ColorNumerics.Get8BitBT709Luminance( - ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), - ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); - - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.L; - dest.G = this.L; - dest.B = this.L; - dest.A = this.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - const float Max = 255F; - float rgb = this.L / Max; - return new Vector4(rgb, rgb, rgb, this.A / Max); - } - - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - this.L = ColorNumerics.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); - this.A = (byte)vector.W; - } + [FieldOffset(0)] + public byte L; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(1)] + public byte A; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + /// The alpha component. + public La16(byte l, byte a) + { + this.L = l; + this.A = a; + } + + /// + public ushort PackedValue + { + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(La16 left, La16 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(La16 left, La16 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La16 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La16({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.L = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.L = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance( + ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + + /// + public void FromRgba64(Rgba64 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance( + ColorNumerics.DownScaleFrom16BitTo8Bit(source.R), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.G), + ColorNumerics.DownScaleFrom16BitTo8Bit(source.B)); + + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.L; + dest.G = this.L; + dest.B = this.L; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + const float Max = 255F; + float rgb = this.L / Max; + return new Vector4(rgb, rgb, rgb, this.A / Max); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + this.L = ColorNumerics.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 03f8d188b5..46dce621b2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -1,257 +1,255 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit normalized values representing luminance and alpha. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct La32 : IPixel, IPackedVector { + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the luminance component. + /// + [FieldOffset(0)] + public ushort L; + /// - /// Packed pixel type containing two 16-bit normalized values representing luminance and alpha. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the alpha component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct La32 : IPixel, IPackedVector + [FieldOffset(2)] + public ushort A; + + /// + /// Initializes a new instance of the struct. + /// + /// The luminance component. + /// The alpha component. + public La32(ushort l, ushort a) { - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the luminance component. - /// - [FieldOffset(0)] - public ushort L; - - /// - /// Gets or sets the alpha component. - /// - [FieldOffset(2)] - public ushort A; - - /// - /// Initializes a new instance of the struct. - /// - /// The luminance component. - /// The alpha component. - public La32(ushort l, ushort a) - { - this.L = l; - this.A = a; - } - - /// - public uint PackedValue - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(La32 left, La32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(La32 left, La32 right) => !left.Equals(right); + this.L = l; + this.A = a; + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); + set => Unsafe.As(ref this) = value; + } - /// - public override readonly bool Equals(object obj) => obj is La32 other && this.Equals(other); + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(La32 left, La32 right) => left.Equals(right); - /// - public override readonly string ToString() => $"La32({this.L}, {this.A})"; + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(La32 left, La32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly bool Equals(object obj) => obj is La32 other && this.Equals(other); + + /// + public override readonly string ToString() => $"La32({this.L}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - this.A = ushort.MaxValue; - } + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.L = source.PackedValue; + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.L = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - this.L = source.PackedValue; - this.A = ushort.MaxValue; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.L = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.L = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); - this.A = ushort.MaxValue; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this = source; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.L = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this = source; + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = ushort.MaxValue; + } - this.A = ushort.MaxValue; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - this.A = ushort.MaxValue; - } + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance( - ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), - ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.L = ColorNumerics.Get16BitBT709Luminance(source.R, source.G, source.B); - this.A = source.A; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(this.L); + dest.R = rgb; + dest.G = rgb; + dest.B = rgb; + dest.A = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.ConvertFromRgbaScaledVector4(vector); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(this.L); - dest.R = rgb; - dest.G = rgb; - dest.B = rgb; - dest.A = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + float scaled = this.L / Max; + return new Vector4(scaled, scaled, scaled, this.A / Max); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - float scaled = this.L / Max; - return new Vector4(scaled, scaled, scaled, this.A / Max); - } + [MethodImpl(InliningOptions.ShortMethod)] + internal void ConvertFromRgbaScaledVector4(Vector4 vector) + { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + this.L = ColorNumerics.Get16BitBT709Luminance( + vector.X, + vector.Y, + vector.Z); - [MethodImpl(InliningOptions.ShortMethod)] - internal void ConvertFromRgbaScaledVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Max; - this.L = ColorNumerics.Get16BitBT709Luminance( - vector.X, - vector.Y, - vector.Z); - - this.A = (ushort)MathF.Round(vector.W); - } + this.A = (ushort)MathF.Round(vector.W); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index f3eb9a8c4d..f5ce19340d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -1,193 +1,191 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. +/// +/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public partial struct NormalizedByte2 : IPixel, IPackedVector { + private const float MaxPos = 127F; + + private static readonly Vector2 Half = new(MaxPos); + private static readonly Vector2 MinusOne = new(-1F); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedByte2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public ushort PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// - /// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. - /// - /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. /// - public partial struct NormalizedByte2 : IPixel, IPackedVector + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector2 ToVector2() => new( + (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, + (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos); + + /// + public override readonly bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ushort Pack(Vector2 vector) { - private const float MaxPos = 127F; - - private static readonly Vector2 Half = new(MaxPos); - private static readonly Vector2 MinusOne = new(-1F); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public NormalizedByte2(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedByte2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public ushort PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(NormalizedByte2 left, NormalizedByte2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; - scaled -= Vector2.One; - this.PackedValue = Pack(scaled); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; - return new Vector4(scaled, 0F, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(vector2); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector2 ToVector2() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos); - - /// - public override readonly bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static ushort Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, MinusOne, Vector2.One) * Half; - - int byte2 = ((ushort)Convert.ToInt16(Math.Round(vector.X)) & 0xFF) << 0; - int byte1 = ((ushort)Convert.ToInt16(Math.Round(vector.Y)) & 0xFF) << 8; - - return (ushort)(byte2 | byte1); - } + vector = Vector2.Clamp(vector, MinusOne, Vector2.One) * Half; + + int byte2 = ((ushort)Convert.ToInt16(Math.Round(vector.X)) & 0xFF) << 0; + int byte1 = ((ushort)Convert.ToInt16(Math.Round(vector.Y)) & 0xFF) << 8; + + return (ushort)(byte2 | byte1); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index a46b4b72d6..fe4054c8b6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -1,187 +1,185 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. +/// +/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct NormalizedByte4 : IPixel, IPackedVector { + private const float MaxPos = 127F; + + private static readonly Vector4 Half = new(MaxPos); + private static readonly Vector4 MinusOne = new(-1F); + /// - /// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. - /// - /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// - public partial struct NormalizedByte4 : IPixel, IPackedVector + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedByte4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) { - private const float MaxPos = 127F; - - private static readonly Vector4 Half = new(MaxPos); - private static readonly Vector4 MinusOne = new(-1F); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public NormalizedByte4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedByte4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - vector *= 2F; - vector -= Vector4.One; - this.FromVector4(vector); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2F; - return scaled; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - 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.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(ref Vector4 vector) - { - vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half; - - uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0; - uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8; - uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16; - uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24; - - return byte4 | byte3 | byte2 | byte1; - } + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedByte4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedByte4 left, NormalizedByte4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new( + (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, + (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos, + (sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, + (sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half; + + uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0; + uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8; + uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16; + uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24; + + return byte4 | byte3 | byte2 | byte1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 146851e3b1..8502013e2d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -1,196 +1,194 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. +/// +/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public partial struct NormalizedShort2 : IPixel, IPackedVector { + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + private static readonly Vector2 Max = new(MaxPos); + private static readonly Vector2 Min = Vector2.Negate(Max); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public NormalizedShort2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; + scaled -= Vector2.One; + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += Vector2.One; + scaled /= 2F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// - /// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. - /// - /// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. /// - public partial struct NormalizedShort2 : IPixel, IPackedVector + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector2 ToVector2() => new( + (short)(this.PackedValue & 0xFFFF) / MaxPos, + (short)(this.PackedValue >> 0x10) / MaxPos); + + /// + public override readonly bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - private static readonly Vector2 Max = new(MaxPos); - private static readonly Vector2 Min = Vector2.Negate(Max); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public NormalizedShort2(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedShort2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(NormalizedShort2 left, NormalizedShort2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; - scaled -= Vector2.One; - this.PackedValue = Pack(scaled); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; - return new Vector4(scaled, 0F, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(vector2); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector2 ToVector2() => new( - (short)(this.PackedValue & 0xFFFF) / MaxPos, - (short)(this.PackedValue >> 0x10) / MaxPos); - - /// - public override readonly bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(Vector2 vector) - { - vector *= Max; - vector = Vector2.Clamp(vector, Min, Max); - - // Round rather than truncate. - uint word2 = (uint)((int)MathF.Round(vector.X) & 0xFFFF); - uint word1 = (uint)(((int)MathF.Round(vector.Y) & 0xFFFF) << 0x10); - - return word2 | word1; - } + vector *= Max; + vector = Vector2.Clamp(vector, Min, Max); + + // Round rather than truncate. + uint word2 = (uint)((int)MathF.Round(vector.X) & 0xFFFF); + uint word1 = (uint)(((int)MathF.Round(vector.Y) & 0xFFFF) << 0x10); + + return word2 | word1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index a85bd30ec7..dcc6046e70 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -1,190 +1,188 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. +/// +/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct NormalizedShort4 : IPixel, IPackedVector { + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + private static readonly Vector4 Max = new(MaxPos); + private static readonly Vector4 Min = Vector4.Negate(Max); + /// - /// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. - /// - /// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// - public partial struct NormalizedShort4 : IPixel, IPackedVector + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public NormalizedShort4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - private static readonly Vector4 Max = new(MaxPos); - private static readonly Vector4 Min = Vector4.Negate(Max); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public NormalizedShort4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public NormalizedShort4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - vector *= 2F; - vector -= Vector4.One; - this.FromVector4(vector); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2F; - return scaled; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new( - (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxPos); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - 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.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static ulong Pack(ref Vector4 vector) - { - vector *= Max; - vector = Numerics.Clamp(vector, Min, Max); - - // Round rather than truncate. - ulong word4 = ((ulong)Convert.ToInt32(MathF.Round(vector.X)) & 0xFFFF) << 0x00; - ulong word3 = ((ulong)Convert.ToInt32(MathF.Round(vector.Y)) & 0xFFFF) << 0x10; - ulong word2 = ((ulong)Convert.ToInt32(MathF.Round(vector.Z)) & 0xFFFF) << 0x20; - ulong word1 = ((ulong)Convert.ToInt32(MathF.Round(vector.W)) & 0xFFFF) << 0x30; - - return word4 | word3 | word2 | word1; - } + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public NormalizedShort4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(NormalizedShort4 left, NormalizedShort4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 2F; + vector -= Vector4.One; + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += Vector4.One; + scaled /= 2F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new( + (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxPos, + (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxPos, + (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxPos, + (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxPos); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) + { + vector *= Max; + vector = Numerics.Clamp(vector, Min, Max); + + // Round rather than truncate. + ulong word4 = ((ulong)Convert.ToInt32(MathF.Round(vector.X)) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)Convert.ToInt32(MathF.Round(vector.Y)) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)Convert.ToInt32(MathF.Round(vector.Z)) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)Convert.ToInt32(MathF.Round(vector.W)) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs index 0348552dad..a7b4b5df00 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct A8 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct A8 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs index d1054072c9..66f3ecb245 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Abgr32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs index 36c8a97f32..894e92963e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Argb32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs index 2a76a56aa1..a8f6ab1556 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr24.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgr24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgr24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs index 0a687bd554..de96903256 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgr565 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgr565 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs index 8089130e28..1a62b0809c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs index 000981cbb9..8ffdaf6cb5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra4444.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra4444 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra4444 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs index 44debeaa9f..97f5d805e6 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra5551.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra5551 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra5551 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs index 816a16e1b6..f6e0b833c2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Byte4 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Byte4 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs index 69757f71b4..3e1dc89419 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -3,344 +3,355 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Abgr32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToRgba32(source, dest); - } + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToAbgr32(source, dest); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToArgb32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToAbgr32(source, dest); - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToBgra32(source, dest); - } + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToAbgr32(source, dest); + dp.FromAbgr32(sp); } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToRgb24(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToAbgr32(source, dest); - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToBgr24(source, dest); + dp.FromAbgr32(sp); } + } - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToAbgr32(source, dest); + dp.FromAbgr32(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromAbgr32(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - dp.FromAbgr32(sp); - } + dp.FromAbgr32(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromAbgr32(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromAbgr32(sp); - } + dp.FromAbgr32(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromAbgr32(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromAbgr32(sp); - } + dp.FromAbgr32(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromAbgr32(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToAbgr32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToAbgr32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt index 071c74fbb4..2b57f247db 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Abgr32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Abgr32"); #> - } + <# GenerateAllDefaultConversionMethods("Abgr32"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index 3f66a3b5c5..4185444eb4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -3,344 +3,355 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Argb32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToRgba32(source, dest); - } + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToArgb32(source, dest); + } + + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToArgb32(source, dest); - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToAbgr32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToArgb32(source, dest); + } + + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToArgb32(source, dest); + } + + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToArgb32(source, dest); - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToArgb32(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToBgra32(source, dest); - } + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToArgb32(source, dest); + dp.FromArgb32(sp); } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToRgb24(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToArgb32(source, dest); - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToBgr24(source, dest); + dp.FromArgb32(sp); } + } - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToArgb32(source, dest); + dp.FromArgb32(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromArgb32(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - dp.FromArgb32(sp); - } + dp.FromArgb32(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromArgb32(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromArgb32(sp); - } + dp.FromArgb32(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromArgb32(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromArgb32(sp); - } + dp.FromArgb32(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromArgb32(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToArgb32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromArgb32(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToArgb32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt index bbee95f24c..58b5445961 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Argb32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Argb32"); #> - } + <# GenerateAllDefaultConversionMethods("Argb32"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index a149c6ee8e..b8e0bbb3f1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -3,344 +3,355 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgr24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgr24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToRgba32(source, dest); - } + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToBgr24(source, dest); + } + + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToBgr24(source, dest); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToBgr24(source, dest); + } + + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToArgb32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToBgr24(source, dest); + } + + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToBgr24(source, dest); - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToBgr24(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToAbgr32(source, dest); - } + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToBgr24(source, dest); + dp.FromBgr24(sp); } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToBgra32(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToBgr24(source, dest); - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToRgb24(source, dest); + dp.FromBgr24(sp); } + } - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToBgr24(source, dest); + dp.FromBgr24(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgr24(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - dp.FromBgr24(sp); - } + dp.FromBgr24(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgr24(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromBgr24(sp); - } + dp.FromBgr24(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgr24(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromBgr24(sp); - } + dp.FromBgr24(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgr24(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToBgr24(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgr24(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgr24(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt index 48b6daa3aa..b171235347 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgr24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgr24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgr24"); #> - } + <# GenerateAllDefaultConversionMethods("Bgr24"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index b721668577..1c1b5c97c3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -3,344 +3,355 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToRgba32(source, dest); - } + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToBgra32(source, dest); + } + + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToBgra32(source, dest); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToBgra32(source, dest); + } + + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToArgb32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToBgra32(source, dest); + } + + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToBgra32(source, dest); - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToBgra32(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToAbgr32(source, dest); - } + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToBgra32(source, dest); + dp.FromBgra32(sp); } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToRgb24(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToBgra32(source, dest); - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToBgr24(source, dest); + dp.FromBgra32(sp); } + } - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToBgra32(source, dest); + dp.FromBgra32(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra32(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - dp.FromBgra32(sp); - } + dp.FromBgra32(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra32(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromBgra32(sp); - } + dp.FromBgra32(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra32(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromBgra32(sp); - } + dp.FromBgra32(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra32(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToBgra32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra32(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt index 7e15979890..a5ba5ed7db 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgra32"); #> - } + <# GenerateAllDefaultConversionMethods("Bgra32"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs index d2a23c940c..52022b0c78 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra5551 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra5551 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra5551(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra5551(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra5551(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra5551(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } + + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromBgra5551(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromBgra5551(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromBgra5551(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToBgra5551(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToBgra5551(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt index c74dfb4bd1..fa0de241a2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra5551 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Bgra5551 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Bgra5551"); #> - } + <# GenerateAllDefaultConversionMethods("Bgra5551"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs index 3eaf238d4a..a1a59727ff 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL16(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL16(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL16(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL16(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } + + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL16(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL16(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToL16(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL16(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt index 9c0f725e62..8dfded13c2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("L16"); #> - } + <# GenerateAllDefaultConversionMethods("L16"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs index bd2445e98a..f0ada6459c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L8 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L8 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL8(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } + + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL8(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL8(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL8(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } + + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromL8(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromL8(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromL8(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToL8(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToL8(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt index e6d4a45efc..d681d35ee7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L8 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L8 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("L8"); #> - } + <# GenerateAllDefaultConversionMethods("L8"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs index 659c5585f9..d0610037d5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa16(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa16(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa16(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa16(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } + + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa16(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa16(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa16(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToLa16(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa16(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt index 7b88fd4f1a..8c4e2fbeac 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("La16"); #> - } + <# GenerateAllDefaultConversionMethods("La16"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs index 7616255b48..76da66a1ad 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa32(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa32(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa32(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa32(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } + + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromLa32(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromLa32(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromLa32(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToLa32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToLa32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt index f85652cad9..11be821e40 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("La32"); #> - } + <# GenerateAllDefaultConversionMethods("La32"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index b72ea73387..ab7dafa04b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -3,344 +3,355 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToRgba32(source, dest); - } + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToRgb24(source, dest); + } + + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToRgb24(source, dest); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToRgb24(source, dest); + } + + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToArgb32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToRgb24(source, dest); + } + + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToRgb24(source, dest); - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToRgb24(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToAbgr32(source, dest); - } + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToRgb24(source, dest); + dp.FromRgb24(sp); } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToBgra32(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToRgb24(source, dest); - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToBgr24(source, dest); + dp.FromRgb24(sp); } + } - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToRgb24(source, dest); + dp.FromRgb24(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb24(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - dp.FromRgb24(sp); - } + dp.FromRgb24(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb24(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromRgb24(sp); - } + dp.FromRgb24(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb24(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromRgb24(sp); - } + dp.FromRgb24(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb24(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToRgb24(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb24(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb24(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt index 056c2a2797..d9e7dc51b8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgb24"); #> - } + <# GenerateAllDefaultConversionMethods("Rgb24"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs index 1e8d7d389a..417ed34334 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb48 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb48 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb48(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb48(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb48(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb48(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } + + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgb48(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgb48(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgb48(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToRgb48(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgb48(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt index f46493e621..1b22d96c8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb48 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb48 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgb48"); #> - } + <# GenerateAllDefaultConversionMethods("Rgb48"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs index a1ea3971c3..8cd2c91261 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs @@ -3,325 +3,335 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } - - /// - public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToArgb32(source, dest); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } + + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToRgba32(source, dest); + } + + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromArgb32.ToRgba32(source, dest); - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToAbgr32(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToRgba32(source, dest); + } + + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToRgba32(source, dest); + } + + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromAbgr32.ToRgba32(source, dest); - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToRgba32(source, dest); + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToBgra32(source, dest); - } + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - /// - public override void FromBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgra32.ToRgba32(source, dest); + dp.FromRgba32(sp); } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToRgb24(source, dest); - } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgb24.ToRgba32(source, dest); - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromRgba32.ToBgr24(source, dest); + dp.FromRgba32(sp); } + } + + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void FromBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); - Span dest = MemoryMarshal.Cast(destinationPixels); - PixelConverter.FromBgr24.ToRgba32(source, dest); + dp.FromRgba32(sp); } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba32(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromRgba32(sp); - } + dp.FromRgba32(sp); } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba32(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromRgba32(sp); - } + dp.FromRgba32(sp); } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba32(sp); - } - } - /// - public override void ToRgba64( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); - - dp.FromRgba32(sp); - } + dp.FromRgba32(sp); } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + } - ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba32(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToRgba32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba32(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt index 6a6cac4e36..e2a3c1c6ea 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgba32"); #> - } + <# GenerateAllDefaultConversionMethods("Rgba32"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs index 9c75a68bb5..395f7e88e0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs @@ -3,290 +3,300 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba64 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba64 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations + /// + public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) { - /// - public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } - /// - public override void ToArgb32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba64(sp); - } - } - /// - public override void ToAbgr32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToBgr24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToBgra32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } + + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba64(sp); - } - } - /// - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToLa16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToLa32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void ToRgb24( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba64(sp); - } - } - /// - public override void ToRgba32( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba64(sp); - } - } - /// - public override void ToRgb48( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } + + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - dp.FromRgba64(sp); - } - } - /// - public override void ToBgra5551( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + for (nint i = 0; i < sourcePixels.Length; i++) { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); - ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + dp.FromRgba64(sp); + } + } - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - dp.FromRgba64(sp); - } - } - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (nint i = 0; i < sourcePixels.Length; i++) { - PixelOperations.Instance.ToRgba64(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToRgba64(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt index cf02d38ee0..f135bf2044 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.tt @@ -1,18 +1,17 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba64 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba64 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - <# GenerateAllDefaultConversionMethods("Rgba64"); #> - } + <# GenerateAllDefaultConversionMethods("Rgba64"); #> } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index a01bbfcbe9..ab08bfa570 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -8,7 +8,6 @@ // -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -54,97 +53,98 @@ using SixLabors.ImageSharp.PixelFormats.Utils; void GenerateGenericConverterMethods(string pixelType) { #> - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span<<#=pixelType#>> destinationPixels) - { - PixelOperations.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); - } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span<<#=pixelType#>> destinationPixels) + { + PixelOperations.Instance.To<#=pixelType#>(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + } <#+ } void GenerateDefaultSelfConversionMethods(string pixelType) { -#> - /// - public override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); +#>/// + public override void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<<#=pixelType#>> destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - source.CopyTo(destinationPixels.Slice(0, source.Length)); - } + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } - /// - public override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + public override void To<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> sourcePixels, Span<<#=pixelType#>> destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); - } + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } <#+ } void GenerateDefaultConvertToMethod(string fromPixelType, string toPixelType) { #> - /// - public override void To<#=toPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=fromPixelType#>> sourcePixels, - Span<<#=toPixelType#>> destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); + /// + public override void To<#=toPixelType#>( + Configuration configuration, + ReadOnlySpan<<#=fromPixelType#>> sourcePixels, + Span<<#=toPixelType#>> destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (nint i = 0; i < sourcePixels.Length; i++) - { - ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); - ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); + for (nint i = 0; i < sourcePixels.Length; i++) + { + ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); + ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); - dp.From<#=fromPixelType#>(sp); - } + dp.From<#=fromPixelType#>(sp); } + } <#+ } void GenerateOptimized32BitConversionMethods(string thisPixelType, string otherPixelType) { -#> - /// - public override void To<#=otherPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=thisPixelType#>> sourcePixels, - Span<<#=otherPixelType#>> destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - - ReadOnlySpan source = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(sourcePixels); - Span dest = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(destinationPixels); - PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(source, dest); - } + #> + + /// + public override void To<#=otherPixelType#>( + Configuration configuration, + ReadOnlySpan<<#=thisPixelType#>> sourcePixels, + Span<<#=otherPixelType#>> destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - /// - public override void From<#=otherPixelType#>( - Configuration configuration, - ReadOnlySpan<<#=otherPixelType#>> sourcePixels, - Span<<#=thisPixelType#>> destinationPixels) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + ReadOnlySpan source = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(sourcePixels); + Span dest = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(destinationPixels); + PixelConverter.From<#=thisPixelType#>.To<#=otherPixelType#>(source, dest); + } + + /// + public override void From<#=otherPixelType#>( + Configuration configuration, + ReadOnlySpan<<#=otherPixelType#>> sourcePixels, + Span<<#=thisPixelType#>> destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ReadOnlySpan source = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(sourcePixels); - Span dest = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(destinationPixels); - PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(source, dest); - } + ReadOnlySpan source = MemoryMarshal.Cast<<#=otherPixelType#>, byte>(sourcePixels); + Span dest = MemoryMarshal.Cast<<#=thisPixelType#>, byte>(destinationPixels); + PixelConverter.From<#=otherPixelType#>.To<#=thisPixelType#>(source, dest); + } <#+ } @@ -156,25 +156,26 @@ using SixLabors.ImageSharp.PixelFormats.Utils; removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; } #> - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span<<#=pixelType#>> destinationPixels, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(<#=removeTheseModifiers#>)); - } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan<<#=pixelType#>> sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - { - Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); - } + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span<<#=pixelType#>> destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(<#=removeTheseModifiers#>)); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan<<#=pixelType#>> sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(<#=removeTheseModifiers#>)); + } <#+ } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs index 92b5351980..c8c4110c73 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct HalfSingle { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct HalfSingle + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs index c062edcad3..bdf58145fd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct HalfVector2 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct HalfVector2 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs index 1b0ebc5595..c3fe598045 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct HalfVector4 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct HalfVector4 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs index abd8a20027..7495cee53d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs index 5520b90830..5dd98c3a66 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct L8 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct L8 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs index 3b279c1f94..d9bda3e0fc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La16.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La16 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La16 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs index b42bc4d330..1fb5adfc8f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/La32.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct La32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct La32 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs index 80e0cce59d..7176295869 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedByte2 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct NormalizedByte2 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs index 296713bf83..9bb48f5924 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedByte4 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct NormalizedByte4 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs index 02833878b5..3913f64bb5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedShort2 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct NormalizedShort2 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs index c0966217d9..6334f4e7e4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedShort4 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct NormalizedShort4 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs index afc44eda14..a5b803f768 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rg32 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rg32 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs index 50ea3533bf..2a8f80ebe0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb24.PixelOperations.cs @@ -1,47 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb24 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb24 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; + private static readonly Lazy LazyInfo = + new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - internal override void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); - } + /// + internal override void PackFromRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + int count = redChannel.Length; + GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - /// - internal override void UnpackIntoRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) - => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); + SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); } + + /// + internal override void UnpackIntoRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + => SimdUtils.UnpackToRgbPlanes(redChannel, greenChannel, blueChannel, source); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs index 57e5e8513d..56a052a7dd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgb48 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgb48 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs index d9d1842de8..f550396275 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba1010102.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba1010102 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba1010102 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs index ed3a53758b..a4887b393c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32.PixelOperations.cs @@ -1,74 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32 { - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba32 + /// + /// implementation optimized for . + /// + internal partial class PixelOperations : PixelOperations { - /// - /// implementation optimized for . - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - destinationVectors = destinationVectors[..sourcePixels.Length]; - SimdUtils.ByteToNormalizedFloat( - MemoryMarshal.Cast(sourcePixels), - MemoryMarshal.Cast(destinationVectors)); - Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); - } + destinationVectors = destinationVectors[..sourcePixels.Length]; + SimdUtils.ByteToNormalizedFloat( + MemoryMarshal.Cast(sourcePixels), + MemoryMarshal.Cast(destinationVectors)); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); + } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - destinationPixels = destinationPixels[..sourceVectors.Length]; - Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - SimdUtils.NormalizedFloatToByteSaturate( - MemoryMarshal.Cast(sourceVectors), - MemoryMarshal.Cast(destinationPixels)); - } + destinationPixels = destinationPixels[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + MemoryMarshal.Cast(destinationPixels)); + } - /// - internal override void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) - { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); + /// + internal override void PackFromRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + int count = redChannel.Length; + GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); - } + SimdUtils.PackFromRgbPlanes(redChannel, greenChannel, blueChannel, destination); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs index f68c43fe6b..56bbc6b25b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba64.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba64 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Rgba64 + /// + internal partial class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal partial class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index 6859f0f1db..3fcb4bc610 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -1,104 +1,102 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats.Utils; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct RgbaVector { - /// - /// Provides optimized overrides for bulk operations. - /// - public partial struct RgbaVector + /// + /// implementation optimized for . + /// + internal class PixelOperations : PixelOperations { - /// - /// implementation optimized for . - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - /// - public override void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Span destinationVectors = MemoryMarshal.Cast(destinationPixels); + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); - } + PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); + } - /// - public override void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); - Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); - } + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); + } - /// - public override void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); - MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); - Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); - } + MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); + Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); + } - public override void ToL8( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.ConvertFromRgbaScaledVector4(sp); - } + dp.ConvertFromRgbaScaledVector4(sp); } + } - public override void ToL16( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); - ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Vector4 sourceBaseRef = ref Unsafe.As(ref MemoryMarshal.GetReference(sourcePixels)); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Vector4 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.ConvertFromRgbaScaledVector4(sp); - } + dp.ConvertFromRgbaScaledVector4(sp); } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs index 6ccd331ebd..435a521ba7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Short2 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Short2 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.None), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs index 7c9923180e..546da9c57d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs @@ -1,26 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Short4 { - /// + /// /// Provides optimized overrides for bulk operations. - /// - public partial struct Short4 + /// + internal class PixelOperations : PixelOperations { - /// - /// Provides optimized overrides for bulk operations. - /// - internal class PixelOperations : PixelOperations - { - private static readonly Lazy LazyInfo = - new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); - /// - public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - } + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 756ee7e4aa..765349fc00 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -1,173 +1,171 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. +/// +/// +public partial struct Rg32 : IPixel, IPackedVector { + private static readonly Vector2 Max = new(ushort.MaxValue); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component + /// The y-component + public Rg32(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + /// - /// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. - /// + /// Compares two objects for equality. /// - public partial struct Rg32 : IPixel, IPackedVector + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; + + /// + public override readonly bool Equals(object obj) => obj is Rg32 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) { - private static readonly Vector2 Max = new(ushort.MaxValue); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - public Rg32(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rg32(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rg32 left, Rg32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(vector2); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.ToVector2(), 0F, 1F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; - - /// - public override readonly bool Equals(object obj) => obj is Rg32 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); - - /// - public override readonly string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; - return (uint)(((int)Math.Round(vector.X) & 0xFFFF) | (((int)Math.Round(vector.Y) & 0xFFFF) << 16)); - } + vector = Vector2.Clamp(vector, Vector2.Zero, Vector2.One) * Max; + return (uint)(((int)Math.Round(vector.X) & 0xFFFF) | (((int)Math.Round(vector.Y) & 0xFFFF) << 16)); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 240e85ff15..e090b6ba2d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -1,273 +1,271 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in red, green, blue order (least significant to most significant byte). +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Explicit)] +public partial struct Rgb24 : IPixel { /// - /// Pixel type containing three 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in red, green, blue order (least significant to most significant byte). - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// The red component. /// - [StructLayout(LayoutKind.Explicit)] - public partial struct Rgb24 : IPixel + [FieldOffset(0)] + public byte R; + + /// + /// The green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// The blue component. + /// + [FieldOffset(2)] + public byte B; + + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb24(byte r, byte g, byte b) { - /// - /// The red component. - /// - [FieldOffset(0)] - public byte R; - - /// - /// The green component. - /// - [FieldOffset(1)] - public byte G; - - /// - /// The blue component. - /// - [FieldOffset(2)] - public byte B; - - private static readonly Vector4 MaxBytes = new(byte.MaxValue); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgb24(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Rgb24 source) => new(source); - - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgb24(Color color) => color.ToRgb24(); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgb24(ColorSpaces.Rgb color) - { - var vector = new Vector4(color.ToVector3(), 1F); - - Rgb24 rgb = default; - rgb.FromScaledVector4(vector); - return rgb; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgb24 left, Rgb24 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this = source; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this = source.Rgb; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - } - - /// - public override readonly bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - 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 readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); - - /// - public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; - - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - this.R = (byte)vector.X; - this.G = (byte)vector.Y; - this.B = (byte)vector.Z; - } + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgb24 source) => new(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb24(Color color) => color.ToRgb24(); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb24(ColorSpaces.Rgb color) + { + var vector = new Vector4(color.ToVector3(), 1F); + + Rgb24 rgb = default; + rgb.FromScaledVector4(vector); + return rgb; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb24 left, Rgb24 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source.Rgb; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + } + + /// + public override readonly bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + + /// + public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index 3adc6130bf..40ece1932e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -1,226 +1,224 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 635535. +/// +/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgb48 : IPixel { + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public Rgb48(ushort r, ushort g, ushort b) + : this() + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgb48 left, Rgb48 right) => left.Equals(right); + /// - /// Packed pixel type containing three 16-bit unsigned normalized values ranging from 0 to 635535. - /// - /// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. - /// + /// Compares two objects for equality. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Rgb48 : IPixel + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Numerics.Clamp(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); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.R / Max, this.G / Max, this.B / Max, 1F); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this = source.Rgb; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) { - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the red component. - /// - public ushort R; - - /// - /// Gets or sets the green component. - /// - public ushort G; - - /// - /// Gets or sets the blue component. - /// - public ushort B; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - public Rgb48(ushort r, ushort g, ushort b) - : this() - { - this.R = r; - this.G = g; - this.B = b; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgb48 left, Rgb48 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(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); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.R / Max, this.G / Max, this.B / Max, 1F); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this = source.Rgb; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - dest.G = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - dest.B = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - dest.A = byte.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this = source; - - /// - public override readonly bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); - - /// - public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + dest.G = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + dest.B = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + dest.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this = source; + + /// + public override readonly bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + + /// + public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 202386d2ab..a96cded500 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -1,173 +1,171 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed vector type containing unsigned normalized values ranging from 0 to 1. +/// The x, y and z components use 10 bits, and the w component uses 2 bits. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +public partial struct Rgba1010102 : IPixel, IPackedVector { + private static readonly Vector4 Multiplier = new(1023F, 1023F, 1023F, 3F); + /// - /// Packed vector type containing unsigned normalized values ranging from 0 to 1. - /// The x, y and z components use 10 bits, and the w component uses 2 bits. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Initializes a new instance of the struct. /// - public partial struct Rgba1010102 : IPixel, IPackedVector + /// The x-component + /// The y-component + /// The z-component + /// The w-component + public Rgba1010102(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) { - private static readonly Vector4 Multiplier = new(1023F, 1023F, 1023F, 3F); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - public Rgba1010102(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Rgba1010102(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgba1010102 left, Rgba1010102 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4( - (this.PackedValue >> 0) & 0x03FF, - (this.PackedValue >> 10) & 0x03FF, - (this.PackedValue >> 20) & 0x03FF, - (this.PackedValue >> 30) & 0x03) / Multiplier; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; - - /// - 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.##})"); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(ref Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; - - return (uint)( - (((int)Math.Round(vector.X) & 0x03FF) << 0) - | (((int)Math.Round(vector.Y) & 0x03FF) << 10) - | (((int)Math.Round(vector.Z) & 0x03FF) << 20) - | (((int)Math.Round(vector.W) & 0x03) << 30)); - } + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Rgba1010102(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba1010102 left, Rgba1010102 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4( + (this.PackedValue >> 0) & 0x03FF, + (this.PackedValue >> 10) & 0x03FF, + (this.PackedValue >> 20) & 0x03FF, + (this.PackedValue >> 30) & 0x03) / Multiplier; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; + + /// + 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.##})"); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(ref Vector4 vector) + { + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; + + return (uint)( + (((int)Math.Round(vector.X) & 0x03FF) << 0) + | (((int)Math.Round(vector.Y) & 0x03FF) << 10) + | (((int)Math.Round(vector.Z) & 0x03FF) << 20) + | (((int)Math.Round(vector.W) & 0x03) << 30)); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index d15a46d698..ea35cccbf7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -1,547 +1,545 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. +/// The color components are stored in red, green, blue, and alpha order (least significant to most significant byte). +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba32 : IPixel, IPackedVector { /// - /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. - /// The color components are stored in red, green, blue, and alpha order (least significant to most significant byte). - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Rgba32 : IPixel, IPackedVector - { - /// - /// Gets or sets the red component. - /// - public byte R; - - /// - /// Gets or sets the green component. - /// - public byte G; - - /// - /// Gets or sets the blue component. - /// - public byte B; - - /// - /// Gets or sets the alpha component. - /// - public byte A; - - private static readonly Vector4 MaxBytes = new(byte.MaxValue); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(byte r, byte g, byte b) - { - this.R = r; - this.G = g; - this.B = b; - this.A = byte.MaxValue; - } + public byte R; - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + /// + /// Gets or sets the green component. + /// + public byte G; - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(float r, float g, float b, float a = 1) - : this() => this.Pack(r, g, b, a); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(Vector3 vector) - : this() => this.Pack(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(Vector4 vector) - : this() => this = PackNew(ref vector); - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The packed value. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32(uint packed) - : this() => this.Rgba = packed; + /// + /// Gets or sets the blue component. + /// + public byte B; - /// - /// Gets or sets the packed representation of the Rgba32 struct. - /// - public uint Rgba - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + /// + /// Gets or sets the alpha component. + /// + public byte A; - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + private static readonly Vector4 Half = new(0.5F); - /// - /// Gets or sets the RGB components of this struct as - /// - public Rgb24 Rgb - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => new(this.R, this.G, this.B); - - [MethodImpl(InliningOptions.ShortMethod)] - set - { - this.R = value.R; - this.G = value.G; - this.B = value.B; - } - } + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } - /// - /// Gets or sets the RGB components of this struct as reverting the component order. - /// - public Bgr24 Bgr - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => new(this.R, this.G, this.B); - - [MethodImpl(InliningOptions.ShortMethod)] - set - { - this.R = value.R; - this.G = value.G; - this.B = value.B; - } - } + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - public uint PackedValue - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => this.Rgba; + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); - [MethodImpl(InliningOptions.ShortMethod)] - set => this.Rgba = value; - } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(Vector3 vector) + : this() => this.Pack(ref vector); - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Rgba32 source) => new(source); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(Vector4 vector) + : this() => this = PackNew(ref vector); - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgba32(Color color) => color.ToRgba32(); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// An instance of . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgba32(ColorSpaces.Rgb color) - { - var vector = new Vector4(color.ToVector3(), 1F); + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba32(uint packed) + : this() => this.Rgba = packed; - Rgba32 rgba = default; - rgba.FromScaledVector4(vector); - return rgba; - } + /// + /// Gets or sets the packed representation of the Rgba32 struct. + /// + public uint Rgba + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgba32 left, Rgba32 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// + set => Unsafe.As(ref this) = value; + } + + /// + /// Gets or sets the RGB components of this struct as + /// + public Rgb24 Rgb + { [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); - - /// - /// 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. - /// - /// - /// The . - /// - /// Hexadecimal string is not in the correct format. + readonly get => new(this.R, this.G, this.B); + [MethodImpl(InliningOptions.ShortMethod)] - public static Rgba32 ParseHex(string hex) + set { - 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; + this.R = value.R; + this.G = value.G; + this.B = value.B; } + } - /// - /// 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 . - /// + /// + /// Gets or sets the RGB components of this struct as reverting the component order. + /// + public Bgr24 Bgr + { [MethodImpl(InliningOptions.ShortMethod)] - public static bool TryParseHex(string hex, out Rgba32 result) + readonly get => new(this.R, this.G, this.B); + + [MethodImpl(InliningOptions.ShortMethod)] + set { - 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)) - { - return false; - } - - packedValue = BinaryPrimitives.ReverseEndianness(packedValue); - result = Unsafe.As(ref packedValue); - return true; + this.R = value.R; + this.G = value.G; + this.B = value.B; } + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// + /// + public uint PackedValue + { [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + readonly get => this.Rgba; - /// [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + set => this.Rgba = value; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.Pack(ref vector); + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgba32 source) => new(source); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba32(Color color) => color.ToRgba32(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba32(ColorSpaces.Rgb color) + { + var vector = new Vector4(color.ToVector3(), 1F); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.Bgr = source; - this.A = byte.MaxValue; - } + Rgba32 rgba = default; + rgba.FromScaledVector4(vector); + return rgba; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba32 left, Rgba32 right) => left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = source.R; - this.G = source.G; - this.B = source.B; - this.A = source.A; - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + /// 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. + /// + /// + /// The . + /// + /// Hexadecimal string is not in the correct format. + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgba32 ParseHex(string hex) + { + Guard.NotNull(hex, nameof(hex)); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) + if (!TryParseHex(hex, out Rgba32 rgba)) { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - this.A = byte.MaxValue; + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = byte.MaxValue; - } + return rgba; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) + /// + /// 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) + { + result = default; + if (string.IsNullOrWhiteSpace(hex)) { - this.R = source.L; - this.G = source.L; - this.B = source.L; - this.A = source.A; + return false; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } + hex = ToRgbaHex(hex); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) + if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) { - this.Rgb = source; - this.A = byte.MaxValue; + return false; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this = source; + packedValue = BinaryPrimitives.ReverseEndianness(packedValue); + result = Unsafe.As(ref packedValue); + return true; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest = this; + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = byte.MaxValue; - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) - { - this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); - this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); - this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); - this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); - /// - /// Converts the value of this instance to a hexadecimal string. - /// - /// A hexadecimal string representation of the value. - public readonly string ToHex() - { - uint hexOrder = (uint)((this.A << 0) | (this.B << 8) | (this.G << 16) | (this.R << 24)); - return hexOrder.ToString("X8", CultureInfo.InvariantCulture); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); - /// - public override readonly bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - /// - public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.Bgr = source; + this.A = byte.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.Rgba.GetHashCode(); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - /// - /// Packs a into a color returning a new instance as a result. - /// - /// The vector containing the values to pack. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - private static Rgba32 PackNew(ref Vector4 vector) - { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } - return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - /// - /// Packs the four floats into a color. - /// - /// The x-component - /// The y-component - /// The z-component - /// The w-component - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(float x, float y, float z, float w) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.Rgb = source; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this = source; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest = this; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public readonly string ToHex() + { + uint hexOrder = (uint)((this.A << 0) | (this.B << 8) | (this.G << 16) | (this.R << 24)); + return hexOrder.ToString("X8", CultureInfo.InvariantCulture); + } + + /// + public override readonly bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + + /// + public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.Rgba.GetHashCode(); + + /// + /// Packs a into a color returning a new instance as a result. + /// + /// The vector containing the values to pack. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + private static Rgba32 PackNew(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); + } + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1F); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } + + /// + /// Converts the specified hex value to an rrggbbaa hex value. + /// + /// The hex value to convert. + /// + /// A rrggbbaa hex value. + /// + private static string ToRgbaHex(string hex) + { + if (hex[0] == '#') { - var value = new Vector4(x, y, z, w); - this.Pack(ref value); + hex = hex[1..]; } - /// - /// Packs a into a uint. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector3 vector) + if (hex.Length == 8) { - var value = new Vector4(vector, 1F); - this.Pack(ref value); + return hex; } - /// - /// Packs a into a color. - /// - /// The vector containing the values to pack. - [MethodImpl(InliningOptions.ShortMethod)] - private void Pack(ref Vector4 vector) + if (hex.Length == 6) { - vector *= MaxBytes; - vector += Half; - vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); - - this.R = (byte)vector.X; - this.G = (byte)vector.Y; - this.B = (byte)vector.Z; - this.A = (byte)vector.W; + return hex + "FF"; } - /// - /// Converts the specified hex value to an rrggbbaa hex value. - /// - /// The hex value to convert. - /// - /// A rrggbbaa hex value. - /// - private static string ToRgbaHex(string hex) + if (hex.Length is < 3 or > 4) { - if (hex[0] == '#') - { - hex = hex[1..]; - } - - if (hex.Length == 8) - { - return hex; - } - - if (hex.Length == 6) - { - return hex + "FF"; - } - - if (hex.Length is < 3 or > 4) - { - return null; - } - - char r = hex[0]; - char g = hex[1]; - char b = hex[2]; - char a = hex.Length == 3 ? 'F' : hex[3]; - - return new string(new[] { r, r, g, g, b, b, a, a }); + return null; } + + char r = hex[0]; + char g = hex[1]; + char b = hex[2]; + char a = hex.Length == 3 ? 'F' : hex[3]; + + return new string(new[] { r, r, g, g, b, b, a, a }); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 973e337f4c..21591f7d1a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -1,460 +1,458 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba64 : IPixel, IPackedVector { + private const float Max = ushort.MaxValue; + + /// + /// Gets or sets the red component. + /// + public ushort R; + + /// + /// Gets or sets the green component. + /// + public ushort G; + + /// + /// Gets or sets the blue component. + /// + public ushort B; + /// - /// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the alpha component. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct Rgba64 : IPixel, IPackedVector + public ushort A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(ushort r, ushort g, ushort b, ushort a) { - private const float Max = ushort.MaxValue; - - /// - /// Gets or sets the red component. - /// - public ushort R; - - /// - /// Gets or sets the green component. - /// - public ushort G; - - /// - /// Gets or sets the blue component. - /// - public ushort B; - - /// - /// Gets or sets the alpha component. - /// - public ushort A; - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(ushort r, ushort g, ushort b, ushort a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in RGBA byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Rgba32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in BGRA byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Bgra32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in ARGB byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Argb32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 4 bytes in ABGR byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Abgr32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 3 bytes in RGB byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Rgb24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// A structure of 3 bytes in BGR byte order. - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Bgr24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public Rgba64(Vector4 vector) - { - vector = Numerics.Clamp(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); - this.A = (ushort)MathF.Round(vector.W); - } - - /// - /// Gets or sets the RGB components of this struct as . - /// - public Rgb48 Rgb - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - public ulong PackedValue - { - [MethodImpl(InliningOptions.ShortMethod)] - readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - - [MethodImpl(InliningOptions.ShortMethod)] - set => Unsafe.As(ref this) = value; - } - - /// - /// Converts an to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Color(Rgba64 source) => new(source); + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - /// Converts a to . - /// - /// The . - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static implicit operator Rgba64(Color color) => color.ToPixel(); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Rgba64 left, Rgba64 right) => left.PackedValue == right.PackedValue; - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in RGBA byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Rgba32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in BGRA byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Bgra32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in ARGB byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Argb32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in ABGR byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(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); - this.A = (ushort)MathF.Round(vector.W); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 3 bytes in RGB byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Rgb24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 3 bytes in BGR byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Bgr24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) - { - ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ushort.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) - { - this.R = source.PackedValue; - this.G = source.PackedValue; - this.B = source.PackedValue; - this.A = ushort.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) - { - ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); - this.R = rgb; - this.G = rgb; - this.B = rgb; - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) - { - this.R = source.L; - this.G = source.L; - this.B = source.L; - this.A = source.A; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ushort.MaxValue; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) - { - this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); - this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); - this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); - this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.R = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - dest.G = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - dest.B = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - dest.A = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) - { - this.Rgb = source; - this.A = ushort.MaxValue; - } + /// + /// Initializes a new instance of the struct. + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Vector4 vector) + { + vector = Numerics.Clamp(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); + this.A = (ushort)MathF.Round(vector.W); + } - /// + /// + /// Gets or sets the RGB components of this struct as . + /// + public Rgb48 Rgb + { [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this = source; + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); - /// - /// Convert to . - /// - /// The . [MethodImpl(InliningOptions.ShortMethod)] - public readonly Rgba32 ToRgba32() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - return new Rgba32(r, g, b, a); - } - - /// - /// Convert to . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Bgra32 ToBgra32() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - return new Bgra32(r, g, b, a); - } - - /// - /// Convert to . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Argb32 ToArgb32() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - return new Argb32(r, g, b, a); - } - - /// - /// Convert to . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Abgr32 ToAbgr32() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); - return new Abgr32(r, g, b, a); - } - - /// - /// Convert to . - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Rgb24 ToRgb24() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - return new Rgb24(r, g, b); - } - - /// - /// Convert to . - /// - /// The . + set => Unsafe.As(ref this) = value; + } + + /// + public ulong PackedValue + { [MethodImpl(InliningOptions.ShortMethod)] - public readonly Bgr24 ToBgr24() - { - byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); - byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); - byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); - return new Bgr24(r, g, b); - } - - /// - public override readonly bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); - - /// + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); + set => Unsafe.As(ref this) = value; + } - /// - public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Rgba64 source) => new(source); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgba64(Color color) => color.ToPixel(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Rgba64 left, Rgba64 right) => left.PackedValue == right.PackedValue; + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + vector = Numerics.Clamp(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); + this.A = (ushort)MathF.Round(vector.W); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + ushort rgb = ColorNumerics.UpscaleFrom8BitTo16Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + dest.G = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + dest.B = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + dest.A = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.Rgb = source; + this.A = ushort.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this = source; + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Rgba32 ToRgba32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Rgba32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Bgra32 ToBgra32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Bgra32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Argb32 ToArgb32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Argb32(r, g, b, a); } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Abgr32 ToAbgr32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Abgr32(r, g, b, a); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Rgb24 ToRgb24() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + return new Rgb24(r, g, b); + } + + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Bgr24 ToBgr24() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + return new Bgr24(r, g, b); + } + + /// + public override readonly bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 7973ee3dc9..96b87bcf4a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -1,212 +1,210 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Unpacked pixel type containing four 32-bit floating-point values typically ranging from 0 to 1. +/// The color components are stored in red, green, blue, and alpha order. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct RgbaVector : IPixel { /// - /// Unpacked pixel type containing four 32-bit floating-point values typically ranging from 0 to 1. - /// The color components are stored in red, green, blue, and alpha order. - /// - /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. - /// + /// Gets or sets the red component. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - [StructLayout(LayoutKind.Sequential)] - public partial struct RgbaVector : IPixel + public float R; + + /// + /// Gets or sets the green component. + /// + public float G; + + /// + /// Gets or sets the blue component. + /// + public float B; + + /// + /// Gets or sets the alpha component. + /// + public float A; + + private const float MaxBytes = byte.MaxValue; + private static readonly Vector4 Max = new(MaxBytes); + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public RgbaVector(float r, float g, float b, float a = 1) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(RgbaVector left, RgbaVector right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(RgbaVector left, RgbaVector right) => !left.Equals(right); + + /// + /// Creates a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) { - /// - /// Gets or sets the red component. - /// - public float R; - - /// - /// Gets or sets the green component. - /// - public float G; - - /// - /// Gets or sets the blue component. - /// - public float B; - - /// - /// Gets or sets the alpha component. - /// - public float A; - - private const float MaxBytes = byte.MaxValue; - private static readonly Vector4 Max = new(MaxBytes); - private static readonly Vector4 Half = new(0.5F); - - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - [MethodImpl(InliningOptions.ShortMethod)] - public RgbaVector(float r, float g, float b, float a = 1) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(RgbaVector left, RgbaVector right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(RgbaVector left, RgbaVector right) => !left.Equals(right); - - /// - /// Creates a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. - /// - /// - /// The . - /// - public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() => this.ToVector4(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); - this.R = vector.X; - this.G = vector.Y; - this.B = vector.Z; - this.A = vector.W; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Converts the value of this instance to a hexadecimal string. - /// - /// A hexadecimal string representation of the value. - public readonly string ToHex() - { - // Hex is RRGGBBAA - Vector4 vector = this.ToVector4() * Max; - vector += Half; - uint hexOrder = (uint)((byte)vector.W | ((byte)vector.Z << 8) | ((byte)vector.Y << 16) | ((byte)vector.X << 24)); - return hexOrder.ToString("X8", CultureInfo.InvariantCulture); - } - - /// - public override readonly bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - 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 readonly string ToString() => FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); - - /// - public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + this.R = vector.X; + this.G = vector.Y; + this.B = vector.Z; + this.A = vector.W; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new(this.R, this.G, this.B, this.A); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + /// Converts the value of this instance to a hexadecimal string. + /// + /// A hexadecimal string representation of the value. + public readonly string ToHex() + { + // Hex is RRGGBBAA + Vector4 vector = this.ToVector4() * Max; + vector += Half; + uint hexOrder = (uint)((byte)vector.W | ((byte)vector.Z << 8) | ((byte)vector.Y << 16) | ((byte)vector.X << 24)); + return hexOrder.ToString("X8", CultureInfo.InvariantCulture); + } + + /// + public override readonly bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + 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 readonly string ToString() => FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); + + /// + 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 1fa9a148ae..6f657e17c0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -1,194 +1,192 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing two 16-bit signed integer values. +/// +/// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. +/// +/// +public partial struct Short2 : IPixel, IPackedVector { + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + // Two's complement + private const float MinNeg = ~(int)MaxPos; + + private static readonly Vector2 Max = new(MaxPos); + private static readonly Vector2 Min = new(MinNeg); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + public Short2(float x, float y) + : this(new Vector2(x, y)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public Short2(Vector2 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Short2 left, Short2 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + Vector2 scaled = new Vector2(vector.X, vector.Y) * 65534F; + scaled -= new Vector2(32767F); + this.PackedValue = Pack(scaled); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector2(); + scaled += new Vector2(32767F); + scaled /= 65534F; + return new Vector4(scaled, 0F, 1F); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) + { + var vector2 = new Vector2(vector.X, vector.Y); + this.PackedValue = Pack(vector2); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// - /// Packed pixel type containing two 16-bit signed integer values. - /// - /// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. - /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. /// - public partial struct Short2 : IPixel, IPackedVector + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector2 ToVector2() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + + /// + public override readonly bool Equals(object obj) => obj is Short2 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() + { + var vector = this.ToVector2(); + return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static uint Pack(Vector2 vector) { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - // Two's complement - private const float MinNeg = ~(int)MaxPos; - - private static readonly Vector2 Max = new(MaxPos); - private static readonly Vector2 Min = new(MinNeg); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - public Short2(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the component values. - public Short2(Vector2 vector) => this.PackedValue = Pack(vector); - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Short2 left, Short2 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - Vector2 scaled = new Vector2(vector.X, vector.Y) * 65534F; - scaled -= new Vector2(32767F); - this.PackedValue = Pack(scaled); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector2(); - scaled += new Vector2(32767F); - scaled /= 65534F; - return new Vector4(scaled, 0F, 1F); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) - { - var vector2 = new Vector2(vector.X, vector.Y); - this.PackedValue = Pack(vector2); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector2 ToVector2() => new((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); - - /// - public override readonly bool Equals(object obj) => obj is Short2 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - public override readonly string ToString() - { - var vector = this.ToVector2(); - return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static uint Pack(Vector2 vector) - { - vector = Vector2.Clamp(vector, Min, Max); - uint word2 = (uint)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF; - uint word1 = ((uint)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; - - return word2 | word1; - } + vector = Vector2.Clamp(vector, Min, Max); + uint word2 = (uint)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF; + uint word1 = ((uint)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; + + return word2 | word1; } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index 5953f7c14f..1fbc145fea 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -1,201 +1,199 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 16-bit signed integer values. +/// +/// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. +/// +/// +public partial struct Short4 : IPixel, IPackedVector { + // Largest two byte positive number 0xFFFF >> 1; + private const float MaxPos = 0x7FFF; + + // Two's complement + private const float MinNeg = ~(int)MaxPos; + + private static readonly Vector4 Max = new Vector4(MaxPos); + private static readonly Vector4 Min = new Vector4(MinNeg); + + /// + /// Initializes a new instance of the struct. + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + public Short4(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// A vector containing the initial values for the components. + public Short4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Short4 left, Short4 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) + { + vector *= 65534F; + vector -= new Vector4(32767F); + this.FromVector4(vector); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() + { + var scaled = this.ToVector4(); + scaled += new Vector4(32767F); + scaled /= 65534F; + return scaled; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() + { + return new Vector4( + (short)(this.PackedValue & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.FromScaledVector4(this.ToScaledVector4()); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object obj) => obj is Short4 other && this.Equals(other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Short4 other) => this.PackedValue.Equals(other.PackedValue); + /// - /// Packed pixel type containing four 16-bit signed integer values. - /// - /// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. - /// + /// Gets the hash code for the current instance. /// - public partial struct Short4 : IPixel, IPackedVector + /// Hash code for the instance. + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + 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.##})"); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static ulong Pack(ref Vector4 vector) { - // Largest two byte positive number 0xFFFF >> 1; - private const float MaxPos = 0x7FFF; - - // Two's complement - private const float MinNeg = ~(int)MaxPos; - - private static readonly Vector4 Max = new Vector4(MaxPos); - private static readonly Vector4 Min = new Vector4(MinNeg); - - /// - /// Initializes a new instance of the struct. - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - public Short4(float x, float y, float z, float w) - : this(new Vector4(x, y, z, w)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// A vector containing the initial values for the components. - public Short4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator ==(Short4 left, Short4 right) => left.Equals(right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the parameter is not equal to the parameter; otherwise, false. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); - - /// - public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromScaledVector4(Vector4 vector) - { - vector *= 65534F; - vector -= new Vector4(32767F); - this.FromVector4(vector); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToScaledVector4() - { - var scaled = this.ToVector4(); - scaled += new Vector4(32767F); - scaled /= 65534F; - return scaled; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromVector4(Vector4 vector) => this.PackedValue = Pack(ref vector); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 ToVector4() - { - return new Vector4( - (short)(this.PackedValue & 0xFFFF), - (short)((this.PackedValue >> 0x10) & 0xFFFF), - (short)((this.PackedValue >> 0x20) & 0xFFFF), - (short)((this.PackedValue >> 0x30) & 0xFFFF)); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromArgb32(Argb32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgr24(Bgr24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromL16(L16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa16(La16 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromLa32(La32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb24(Rgb24 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ToRgba32(ref Rgba32 dest) - { - dest.FromScaledVector4(this.ToScaledVector4()); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgb48(Rgb48 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); - - /// - public override readonly bool Equals(object obj) => obj is Short4 other && this.Equals(other); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool Equals(Short4 other) => this.PackedValue.Equals(other.PackedValue); - - /// - /// Gets the hash code for the current instance. - /// - /// Hash code for the instance. - [MethodImpl(InliningOptions.ShortMethod)] - public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); - - /// - 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.##})"); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static ulong Pack(ref Vector4 vector) - { - vector = Numerics.Clamp(vector, Min, Max); - - // Clamp the value between min and max values - ulong word4 = ((ulong)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF) << 0x00; - ulong word3 = ((ulong)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; - ulong word2 = ((ulong)Convert.ToInt32(Math.Round(vector.Z)) & 0xFFFF) << 0x20; - ulong word1 = ((ulong)Convert.ToInt32(Math.Round(vector.W)) & 0xFFFF) << 0x30; - - return word4 | word3 | word2 | word1; - } + vector = Numerics.Clamp(vector, Min, Max); + + // Clamp the value between min and max values + ulong word4 = ((ulong)Convert.ToInt32(Math.Round(vector.X)) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)Convert.ToInt32(Math.Round(vector.Y)) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)Convert.ToInt32(Math.Round(vector.Z)) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)Convert.ToInt32(Math.Round(vector.W)) & 0xFFFF) << 0x30; + + return word4 | word3 | word2 | word1; } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 8a85856a09..a4c2fb7a0c 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -2,948 +2,946 @@ // Licensed under the Six Labors Split License. // -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +public partial class PixelOperations { - public partial class PixelOperations - { - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Argb32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Argb32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Argb32 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Argb32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromArgb32(sp); - } + dp.FromArgb32(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromArgb32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromArgb32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToArgb32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Argb32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToArgb32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Abgr32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Abgr32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Abgr32 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromAbgr32(sp); - } + dp.FromAbgr32(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromAbgr32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromAbgr32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromAbgr32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Abgr32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToAbgr32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToAbgr32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToAbgr32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToAbgr32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Bgr24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Bgr24 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Bgr24 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Bgr24 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromBgr24(sp); - } + dp.FromBgr24(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgr24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromBgr24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToBgr24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToBgr24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgr24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToBgr24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Bgra32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Bgra32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Bgra32 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Bgra32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromBgra32(sp); - } + dp.FromBgra32(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromBgra32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToBgra32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToBgra32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgra32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToBgra32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromL8(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref L8 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref L8 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref L8 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromL8(sp); - } + dp.FromL8(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromL8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL8Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromL8(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToL8(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L8 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToL8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToL8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToL8Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToL8(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromL16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref L16 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref L16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref L16 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromL16(sp); - } + dp.FromL16(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromL16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromL16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromL16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromL16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToL16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref L16 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToL16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToL16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToL16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToL16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromLa16(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref La16 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref La16 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref La16 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromLa16(sp); - } + dp.FromLa16(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromLa16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa16Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromLa16(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToLa16(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref La16 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La16 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToLa16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToLa16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToLa16Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToLa16(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromLa32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref La32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref La32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref La32 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromLa32(sp); - } + dp.FromLa32(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromLa32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromLa32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromLa32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromLa32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToLa32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref La32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref La32 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref La32 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToLa32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToLa32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToLa32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToLa32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgb24 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Rgb24 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Rgb24 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromRgb24(sp); - } + dp.FromRgb24(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb24Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromRgb24(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToRgb24(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToRgb24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb24Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgb24(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgba32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Rgba32 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Rgba32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromRgba32(sp); - } + dp.FromRgba32(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromRgba32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToRgba32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToRgba32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgba32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Rgb48 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgb48 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Rgb48 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromRgb48(sp); - } + dp.FromRgb48(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgb48Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromRgb48(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToRgb48(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToRgb48(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgb48Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgb48(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Rgba64 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Rgba64 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Rgba64 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromRgba64(sp); - } + dp.FromRgba64(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba64Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromRgba64(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToRgba64(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToRgba64(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToRgba64Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToRgba64(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref Bgra5551 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromBgra5551(sp); - } + dp.FromBgra5551(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBgra5551Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromBgra5551(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToBgra5551(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.ToBgra5551(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToBgra5551Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToBgra5551(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index 14b01e1d55..31bd0ce6d0 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -15,41 +15,41 @@ { #> - /// - /// Converts all pixels in 'source` span of into a span of -s. - /// - /// A to configure internal operations. - /// The source of data. - /// The to the destination pixels. - public virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); - ref <#=pixelType#> sourceBaseRef = ref MemoryMarshal.GetReference(source); - ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref <#=pixelType#> sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < source.Length; i++) - { - ref <#=pixelType#> sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < source.Length; i++) + { + ref <#=pixelType#> sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); - dp.From<#=pixelType#>(sp); - } + dp.From<#=pixelType#>(sp); } + } - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// A to configure internal operations. - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) - { - this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destinationPixels); - } + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void From<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.From<#=pixelType#>(configuration, MemoryMarshal.Cast>(sourceBytes).Slice(0, count), destinationPixels); + } <# } @@ -57,41 +57,41 @@ void GenerateToDestFormatMethods(string pixelType) { #> - /// - /// Converts all pixels of the 'sourcePixels` span to a span of -s. - /// - /// A to configure internal operations - /// The span of source pixels - /// The destination span of data. - public virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destinationPixels) - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void To<#=pixelType#>(Configuration configuration, ReadOnlySpan sourcePixels, Span<<#=pixelType#>> destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i); + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); - } + dp.FromScaledVector4(sp.ToScaledVector4()); } + } - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// A to configure internal operations - /// The to the source pixels. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) - { - this.To<#=pixelType#>(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast>(destBytes)); - } + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void To<#=pixelType#>Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.To<#=pixelType#>(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast>(destBytes)); + } <# } @@ -100,53 +100,51 @@ // Licensed under the Six Labors Split License. // -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats -{ - public partial class PixelOperations - {<# +namespace SixLabors.ImageSharp.PixelFormats; + +public partial class PixelOperations +{<# - GenerateFromMethods("Argb32"); - GenerateToDestFormatMethods("Argb32"); +GenerateFromMethods("Argb32"); +GenerateToDestFormatMethods("Argb32"); - GenerateFromMethods("Abgr32"); - GenerateToDestFormatMethods("Abgr32"); +GenerateFromMethods("Abgr32"); +GenerateToDestFormatMethods("Abgr32"); - GenerateFromMethods("Bgr24"); - GenerateToDestFormatMethods("Bgr24"); +GenerateFromMethods("Bgr24"); +GenerateToDestFormatMethods("Bgr24"); - GenerateFromMethods("Bgra32"); - GenerateToDestFormatMethods("Bgra32"); +GenerateFromMethods("Bgra32"); +GenerateToDestFormatMethods("Bgra32"); - GenerateFromMethods("L8"); - GenerateToDestFormatMethods("L8"); +GenerateFromMethods("L8"); +GenerateToDestFormatMethods("L8"); - GenerateFromMethods("L16"); - GenerateToDestFormatMethods("L16"); +GenerateFromMethods("L16"); +GenerateToDestFormatMethods("L16"); - GenerateFromMethods("La16"); - GenerateToDestFormatMethods("La16"); +GenerateFromMethods("La16"); +GenerateToDestFormatMethods("La16"); - GenerateFromMethods("La32"); - GenerateToDestFormatMethods("La32"); +GenerateFromMethods("La32"); +GenerateToDestFormatMethods("La32"); - GenerateFromMethods("Rgb24"); - GenerateToDestFormatMethods("Rgb24"); +GenerateFromMethods("Rgb24"); +GenerateToDestFormatMethods("Rgb24"); - GenerateFromMethods("Rgba32"); - GenerateToDestFormatMethods("Rgba32"); +GenerateFromMethods("Rgba32"); +GenerateToDestFormatMethods("Rgba32"); - GenerateFromMethods("Rgb48"); - GenerateToDestFormatMethods("Rgb48"); +GenerateFromMethods("Rgb48"); +GenerateToDestFormatMethods("Rgb48"); - GenerateFromMethods("Rgba64"); - GenerateToDestFormatMethods("Rgba64"); +GenerateFromMethods("Rgba64"); +GenerateToDestFormatMethods("Rgba64"); - GenerateFromMethods("Bgra5551"); - GenerateToDestFormatMethods("Bgra5551"); +GenerateFromMethods("Bgra5551"); +GenerateToDestFormatMethods("Bgra5551"); -#> } -} +#>} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index 3089a09f65..bf1391990f 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -3,215 +3,214 @@ using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides access to pixel blenders +/// +public partial class PixelOperations + where TPixel : unmanaged, IPixel { - /// - /// Provides access to pixel blenders - /// - public partial class PixelOperations - where TPixel : unmanaged, IPixel + /// + /// Find an instance of the pixel blender. + /// + /// the blending and composition to apply + /// A . + public PixelBlender GetPixelBlender(GraphicsOptions options) { - /// - /// Find an instance of the pixel blender. - /// - /// the blending and composition to apply - /// A . - public PixelBlender GetPixelBlender(GraphicsOptions options) - { - return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); - } + return this.GetPixelBlender(options.ColorBlendingMode, options.AlphaCompositionMode); + } - /// - /// Find an instance of the pixel blender. - /// - /// The color blending mode to apply - /// The alpha composition mode to apply - /// A . - public virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + /// + /// Find an instance of the pixel blender. + /// + /// The color blending mode to apply + /// The alpha composition mode to apply + /// A . + public virtual PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + { + switch (alphaMode) { - switch (alphaMode) - { - case PixelAlphaCompositionMode.Clear: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalClear.Instance; - } + case PixelAlphaCompositionMode.Clear: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalClear.Instance; + } - case PixelAlphaCompositionMode.Xor: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalXor.Instance; - } + case PixelAlphaCompositionMode.Xor: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalXor.Instance; + } - case PixelAlphaCompositionMode.Src: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrc.Instance; - } + case PixelAlphaCompositionMode.Src: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrc.Instance; + } - case PixelAlphaCompositionMode.SrcAtop: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcAtop.Instance; - } + case PixelAlphaCompositionMode.SrcAtop: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcAtop.Instance; + } - case PixelAlphaCompositionMode.SrcIn: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcIn.Instance; - } + case PixelAlphaCompositionMode.SrcIn: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcIn.Instance; + } - case PixelAlphaCompositionMode.SrcOut: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOut.Instance; - } + case PixelAlphaCompositionMode.SrcOut: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcOut.Instance; + } - case PixelAlphaCompositionMode.Dest: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDest.Instance; - } + case PixelAlphaCompositionMode.Dest: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDest.Instance; + } - case PixelAlphaCompositionMode.DestAtop: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestAtop.Instance; - } + case PixelAlphaCompositionMode.DestAtop: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestAtop.Instance; + } - case PixelAlphaCompositionMode.DestIn: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestIn.Instance; - } + case PixelAlphaCompositionMode.DestIn: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestIn.Instance; + } - case PixelAlphaCompositionMode.DestOut: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOut.Instance; - } + case PixelAlphaCompositionMode.DestOut: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestOut.Instance; + } - case PixelAlphaCompositionMode.DestOver: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOver.Instance; - } + case PixelAlphaCompositionMode.DestOver: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalDestOver.Instance; + } - case PixelAlphaCompositionMode.SrcOver: - default: - switch (colorMode) - { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; - case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOver.Instance; - } - } + case PixelAlphaCompositionMode.SrcOver: + default: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalSrcOver.Instance; + } } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 0662ffb71a..2465493f58 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,231 +8,230 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.PixelFormats +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations +/// for pixel buffers of type . +/// +/// The pixel format. +public partial class PixelOperations + where TPixel : unmanaged, IPixel { + private static readonly Lazy LazyInfo = new(() => PixelTypeInfo.Create(), true); + private static readonly Lazy> LazyInstance = new(() => default(TPixel).CreatePixelOperations(), true); + /// - /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations - /// for pixel buffers of type . + /// Gets the global instance for the pixel type /// - /// The pixel format. - public partial class PixelOperations - where TPixel : unmanaged, IPixel - { - private static readonly Lazy LazyInfo = new(() => PixelTypeInfo.Create(), true); - private static readonly Lazy> LazyInstance = new(() => default(TPixel).CreatePixelOperations(), true); - - /// - /// Gets the global instance for the pixel type - /// #pragma warning disable CA1000 // Do not declare static members on generic types - public static PixelOperations Instance => LazyInstance.Value; + public static PixelOperations Instance => LazyInstance.Value; #pragma warning restore CA1000 // Do not declare static members on generic types - /// - /// Gets the pixel type info for the given . - /// - /// The . - public virtual PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - - /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . - /// - /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. - /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - /// The to apply during the conversion - public virtual void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels, - PixelConversionModifiers modifiers) - { - Guard.NotNull(configuration, nameof(configuration)); + /// + /// Gets the pixel type info for the given . + /// + /// The . + public virtual PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destinationPixels, modifiers); - } + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + /// The to apply during the conversion + public virtual void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); - /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . - /// - /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. - /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - public void FromVector4Destructive( - Configuration configuration, - Span sourceVectors, - Span destinationPixels) - => this.FromVector4Destructive(configuration, sourceVectors, destinationPixels, PixelConversionModifiers.None); - - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. - /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - /// The to apply during the conversion - public virtual void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationVectors, - PixelConversionModifiers modifiers) - { - Guard.NotNull(configuration, nameof(configuration)); + Utils.Vector4Converters.Default.FromVector4(sourceVectors, destinationPixels, modifiers); + } - Utils.Vector4Converters.Default.ToVector4(sourcePixels, destinationVectors, modifiers); - } + /// + /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. + /// The method is DESTRUCTIVE altering the contents of . + /// + /// + /// The destructive behavior is a design choice for performance reasons. + /// In a typical use case the contents of are abandoned after the conversion. + /// + /// A to configure internal operations + /// The to the source vectors. + /// The to the destination colors. + public void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels) + => this.FromVector4Destructive(configuration, sourceVectors, destinationPixels, PixelConversionModifiers.None); - /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. - /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - public void ToVector4( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationVectors) - => this.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.None); - - /// - /// Bulk operation that copies the to in - /// format. - /// - /// The destination pixel type. - /// A to configure internal operations. - /// The to the source pixels. - /// The to the destination pixels. - public virtual void From( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - where TSourcePixel : unmanaged, IPixel - { - const int sliceLength = 1024; - int numberOfSlices = sourcePixels.Length / sliceLength; - - using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); - Span vectorSpan = tempVectors.GetSpan(); - for (int i = 0; i < numberOfSlices; i++) - { - int start = i * sliceLength; - ReadOnlySpan s = sourcePixels.Slice(start, sliceLength); - Span d = destinationPixels.Slice(start, sliceLength); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); - } - - int endOfCompleteSlices = numberOfSlices * sliceLength; - int remainder = sourcePixels.Length - endOfCompleteSlices; - if (remainder > 0) - { - ReadOnlySpan s = sourcePixels[endOfCompleteSlices..]; - Span d = destinationPixels[endOfCompleteSlices..]; - vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); - } - } + /// + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// + /// A to configure internal operations + /// The to the source colors. + /// The to the destination vectors. + /// The to apply during the conversion + public virtual void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); - /// - /// Bulk operation that copies the to in - /// format. - /// - /// The destination pixel type. - /// A to configure internal operations. - /// The to the source pixels. - /// The to the destination pixels. - public virtual void To( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - where TDestinationPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + Utils.Vector4Converters.Default.ToVector4(sourcePixels, destinationVectors, modifiers); + } + + /// + /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// + /// A to configure internal operations + /// The to the source colors. + /// The to the destination vectors. + public void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationVectors) + => this.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.None); - PixelOperations.Instance.From(configuration, sourcePixels, destinationPixels); + /// + /// Bulk operation that copies the to in + /// format. + /// + /// The destination pixel type. + /// A to configure internal operations. + /// The to the source pixels. + /// The to the destination pixels. + public virtual void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : unmanaged, IPixel + { + const int sliceLength = 1024; + int numberOfSlices = sourcePixels.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan(); + for (int i = 0; i < numberOfSlices; i++) + { + int start = i * sliceLength; + ReadOnlySpan s = sourcePixels.Slice(start, sliceLength); + Span d = destinationPixels.Slice(start, sliceLength); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } - /// - /// Bulk operation that packs 3 seperate RGB channels to . - /// The destination must have a padding of 3. - /// - /// A to the red values. - /// A to the green values. - /// A to the blue values. - /// A to the destination pixels. - internal virtual void PackFromRgbPlanes( - ReadOnlySpan redChannel, - ReadOnlySpan greenChannel, - ReadOnlySpan blueChannel, - Span destination) + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = sourcePixels.Length - endOfCompleteSlices; + if (remainder > 0) { - int count = redChannel.Length; - GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); - - Rgb24 rgb24 = default; - ref byte r = ref MemoryMarshal.GetReference(redChannel); - ref byte g = ref MemoryMarshal.GetReference(greenChannel); - ref byte b = ref MemoryMarshal.GetReference(blueChannel); - ref TPixel d = ref MemoryMarshal.GetReference(destination); - - for (int i = 0; i < count; i++) - { - rgb24.R = Unsafe.Add(ref r, i); - rgb24.G = Unsafe.Add(ref g, i); - rgb24.B = Unsafe.Add(ref b, i); - Unsafe.Add(ref d, i).FromRgb24(rgb24); - } + ReadOnlySpan s = sourcePixels[endOfCompleteSlices..]; + Span d = destinationPixels[endOfCompleteSlices..]; + vectorSpan = vectorSpan[..remainder]; + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); } + } + + /// + /// Bulk operation that copies the to in + /// format. + /// + /// The destination pixel type. + /// A to configure internal operations. + /// The to the source pixels. + /// The to the destination pixels. + public virtual void To( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TDestinationPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + PixelOperations.Instance.From(configuration, sourcePixels, destinationPixels); + } + + /// + /// Bulk operation that packs 3 seperate RGB channels to . + /// The destination must have a padding of 3. + /// + /// A to the red values. + /// A to the green values. + /// A to the blue values. + /// A to the destination pixels. + internal virtual void PackFromRgbPlanes( + ReadOnlySpan redChannel, + ReadOnlySpan greenChannel, + ReadOnlySpan blueChannel, + Span destination) + { + int count = redChannel.Length; + GuardPackFromRgbPlanes(greenChannel, blueChannel, destination, count); + + Rgb24 rgb24 = default; + ref byte r = ref MemoryMarshal.GetReference(redChannel); + ref byte g = ref MemoryMarshal.GetReference(greenChannel); + ref byte b = ref MemoryMarshal.GetReference(blueChannel); + ref TPixel d = ref MemoryMarshal.GetReference(destination); - /// - /// Bulk operation that unpacks pixels from - /// into 3 seperate RGB channels. The destination must have a padding of 3. - /// - /// A to the red values. - /// A to the green values. - /// A to the blue values. - /// A to the destination pixels. - internal virtual void UnpackIntoRgbPlanes( - Span redChannel, - Span greenChannel, - Span blueChannel, - ReadOnlySpan source) + for (int i = 0; i < count; i++) { - int count = redChannel.Length; - - Rgba32 rgba32 = default; - - ref float r = ref MemoryMarshal.GetReference(redChannel); - ref float g = ref MemoryMarshal.GetReference(greenChannel); - ref float b = ref MemoryMarshal.GetReference(blueChannel); - ref TPixel src = ref MemoryMarshal.GetReference(source); - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref src, i).ToRgba32(ref rgba32); - Unsafe.Add(ref r, i) = rgba32.R; - Unsafe.Add(ref g, i) = rgba32.G; - Unsafe.Add(ref b, i) = rgba32.B; - } + rgb24.R = Unsafe.Add(ref r, i); + rgb24.G = Unsafe.Add(ref g, i); + rgb24.B = Unsafe.Add(ref b, i); + Unsafe.Add(ref d, i).FromRgb24(rgb24); } + } - [MethodImpl(InliningOptions.ShortMethod)] - internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) + /// + /// Bulk operation that unpacks pixels from + /// into 3 seperate RGB channels. The destination must have a padding of 3. + /// + /// A to the red values. + /// A to the green values. + /// A to the blue values. + /// A to the destination pixels. + internal virtual void UnpackIntoRgbPlanes( + Span redChannel, + Span greenChannel, + Span blueChannel, + ReadOnlySpan source) + { + int count = redChannel.Length; + + Rgba32 rgba32 = default; + + ref float r = ref MemoryMarshal.GetReference(redChannel); + ref float g = ref MemoryMarshal.GetReference(greenChannel); + ref float b = ref MemoryMarshal.GetReference(blueChannel); + ref TPixel src = ref MemoryMarshal.GetReference(source); + for (int i = 0; i < count; i++) { - Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!"); - Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!"); - Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); + Unsafe.Add(ref src, i).ToRgba32(ref rgba32); + Unsafe.Add(ref r, i) = rgba32.R; + Unsafe.Add(ref g, i) = rgba32.G; + Unsafe.Add(ref b, i) = rgba32.B; } } + + [MethodImpl(InliningOptions.ShortMethod)] + internal static void GuardPackFromRgbPlanes(ReadOnlySpan greenChannel, ReadOnlySpan blueChannel, Span destination, int count) + { + Guard.IsTrue(greenChannel.Length == count, nameof(greenChannel), "Channels must be of same size!"); + Guard.IsTrue(blueChannel.Length == count, nameof(blueChannel), "Channels must be of same size!"); + Guard.IsTrue(destination.Length > count + 2, nameof(destination), "'destination' must contain a padding of 3 elements!"); + } } diff --git a/src/ImageSharp/PixelFormats/RgbaComponent.cs b/src/ImageSharp/PixelFormats/RgbaComponent.cs index 0eebed7ec7..16eeb9b812 100644 --- a/src/ImageSharp/PixelFormats/RgbaComponent.cs +++ b/src/ImageSharp/PixelFormats/RgbaComponent.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Enumerates the RGBA (red, green, blue, alpha) color components. +/// +internal enum RgbaComponent { /// - /// Enumerates the RGBA (red, green, blue, alpha) color components. + /// The red component. /// - internal enum RgbaComponent - { - /// - /// The red component. - /// - R = 0, + R = 0, - /// - /// The green component. - /// - G = 1, + /// + /// The green component. + /// + G = 1, - /// - /// The blue component. - /// - B = 2, + /// + /// The blue component. + /// + B = 2, - /// - /// The alpha component. - /// - A = 3 - } + /// + /// The alpha component. + /// + A = 3 } diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 422e21bfdb..3ae5a3b5e4 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -1,339 +1,337 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.PixelFormats.Utils +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Contains optimized implementations for conversion between pixel formats. +/// +/// +/// Implementations are based on ideas in: +/// https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/Binary/Reader.cs#L84 +/// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) +/// and ROTR (Rotate Right) emitting efficient CPU instructions: +/// https://github.com/dotnet/coreclr/pull/1830 +/// +internal static class PixelConverter { /// - /// Contains optimized implementations for conversion between pixel formats. + /// Optimized converters from . /// - /// - /// Implementations are based on ideas in: - /// https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Buffers/Binary/Reader.cs#L84 - /// The JIT can detect and optimize rotation idioms ROTL (Rotate Left) - /// and ROTR (Rotate Right) emitting efficient CPU instructions: - /// https://github.com/dotnet/coreclr/pull/1830 - /// - internal static class PixelConverter + public static class FromRgba32 { + // Input pixels have: X = R, Y = G, Z = B and W = A. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); + } + + /// + /// Optimized converters from . + /// + public static class FromArgb32 + { + // Input pixels have: X = A, Y = R, Z = G and W = B. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); + } + + /// + /// Optimized converters from . + /// + public static class FromBgra32 + { + // Input pixels have: X = B, Y = G, Z = R and W = A. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); + /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromRgba32 - { - // Input pixels have: X = R, Y = G, Z = B and W = A. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, default); + } + + /// + /// Optimized converters from . + /// + public static class FromAbgr32 + { + // Input pixels have: X = A, Y = B, Z = G and W = R. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); + } + + /// + /// Optimized converters from . + /// + public static class FromRgb24 + { + // Input pixels have: X = R, Y = G and Z = B. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); + } + + /// + /// Optimized converters from . + /// + public static class FromBgr24 + { + // Input pixels have: X = B, Y = G and Z = R. /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromArgb32 - { - // Input pixels have: X = A, Y = R, Z = G and W = B. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromBgra32 - { - // Input pixels have: X = B, Y = G, Z = R and W = A. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromAbgr32 - { - // Input pixels have: X = A, Y = B, Z = G and W = R. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, default); /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromRgb24 - { - // Input pixels have: X = R, Y = G and Z = B. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); /// - /// Optimized converters from . + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. /// - public static class FromBgr24 - { - // Input pixels have: X = B, Y = G and Z = R. - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); - - /// - /// Converts a representing a collection of - /// pixels to a representing - /// a collection of pixels. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); } } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index b0fdac4ab7..251f8d64db 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -1,161 +1,159 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats.Utils +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Helper class for (bulk) conversion of buffers to/from other buffer types. +/// +internal static partial class Vector4Converters { /// - /// Helper class for (bulk) conversion of buffers to/from other buffer types. + /// Provides default implementations for batched to/from conversion. + /// WARNING: The methods prefixed with "Unsafe" are operating without bounds checking and input validation! + /// Input validation is the responsibility of the caller! /// - internal static partial class Vector4Converters + public static class Default { - /// - /// Provides default implementations for batched to/from conversion. - /// WARNING: The methods prefixed with "Unsafe" are operating without bounds checking and input validation! - /// Input validation is the responsibility of the caller! - /// - public static class Default + [MethodImpl(InliningOptions.ShortMethod)] + public static void FromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel { - [MethodImpl(InliningOptions.ShortMethod)] - public static void FromVector4( - Span sourceVectors, - Span destPixels, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - UnsafeFromVector4(sourceVectors, destPixels, modifiers); - } + UnsafeFromVector4(sourceVectors, destPixels, modifiers); + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void ToVector4( - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - UnsafeToVector4(sourcePixels, destVectors, modifiers); - } + UnsafeToVector4(sourcePixels, destVectors, modifiers); + } - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnsafeFromVector4( - Span sourceVectors, - Span destPixels, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeFromVector4( + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + ApplyBackwardConversionModifiers(sourceVectors, modifiers); + + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) { - ApplyBackwardConversionModifiers(sourceVectors, modifiers); - - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) - { - UnsafeFromScaledVector4Core(sourceVectors, destPixels); - } - else - { - UnsafeFromVector4Core(sourceVectors, destPixels); - } + UnsafeFromScaledVector4Core(sourceVectors, destPixels); } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void UnsafeToVector4( - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel + else { - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) - { - UnsafeToScaledVector4Core(sourcePixels, destVectors); - } - else - { - UnsafeToVector4Core(sourcePixels, destVectors); - } - - ApplyForwardConversionModifiers(destVectors, modifiers); + UnsafeFromVector4Core(sourceVectors, destPixels); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromVector4Core( - ReadOnlySpan sourceVectors, - Span destPixels) - where TPixel : unmanaged, IPixel + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnsafeToVector4( + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + if (modifiers.IsDefined(PixelConversionModifiers.Scale)) { - ref Vector4 sourceStart = ref MemoryMarshal.GetReference(sourceVectors); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, sourceVectors.Length); - ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + UnsafeToScaledVector4Core(sourcePixels, destVectors); + } + else + { + UnsafeToVector4Core(sourcePixels, destVectors); + } - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) - { - destRef.FromVector4(sourceStart); + ApplyForwardConversionModifiers(destVectors, modifiers); + } - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destRef = ref Unsafe.Add(ref destRef, 1); - } - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromVector4Core( + ReadOnlySpan sourceVectors, + Span destPixels) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(sourceVectors); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, sourceVectors.Length); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToVector4Core( - ReadOnlySpan sourcePixels, - Span destVectors) - where TPixel : unmanaged, IPixel + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - ref TPixel sourceStart = ref MemoryMarshal.GetReference(sourcePixels); - ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, sourcePixels.Length); - ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); + destRef.FromVector4(sourceStart); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) - { - destRef = sourceStart.ToVector4(); - - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destRef = ref Unsafe.Add(ref destRef, 1); - } + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destRef = ref Unsafe.Add(ref destRef, 1); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromScaledVector4Core( - ReadOnlySpan sourceVectors, - Span destinationColors) - where TPixel : unmanaged, IPixel - { - ref Vector4 sourceStart = ref MemoryMarshal.GetReference(sourceVectors); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, sourceVectors.Length); - ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToVector4Core( + ReadOnlySpan sourcePixels, + Span destVectors) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(sourcePixels); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, sourcePixels.Length); + ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) - { - destRef.FromScaledVector4(sourceStart); + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destRef = sourceStart.ToVector4(); - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destRef = ref Unsafe.Add(ref destRef, 1); - } + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destRef = ref Unsafe.Add(ref destRef, 1); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToScaledVector4Core( - ReadOnlySpan sourceColors, - Span destinationVectors) - where TPixel : unmanaged, IPixel + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromScaledVector4Core( + ReadOnlySpan sourceVectors, + Span destinationColors) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(sourceVectors); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, sourceVectors.Length); + ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - ref TPixel sourceStart = ref MemoryMarshal.GetReference(sourceColors); - ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, sourceColors.Length); - ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); + destRef.FromScaledVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destRef = ref Unsafe.Add(ref destRef, 1); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToScaledVector4Core( + ReadOnlySpan sourceColors, + Span destinationVectors) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(sourceColors); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, sourceColors.Length); + ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); - while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) - { - destRef = sourceStart.ToScaledVector4(); + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destRef = sourceStart.ToScaledVector4(); - sourceStart = ref Unsafe.Add(ref sourceStart, 1); - destRef = ref Unsafe.Add(ref destRef, 1); - } + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destRef = ref Unsafe.Add(ref destRef, 1); } } } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 92007732dc..3442a08075 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,129 +1,127 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.PixelFormats.Utils +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Contains +/// +internal static partial class Vector4Converters { - /// - /// Contains - /// - internal static partial class Vector4Converters + /// + /// Provides efficient implementations for batched to/from conversion. + /// which is applicable for -compatible pixel types where + /// returns the same scaled result as . + /// The method is works by internally converting to a therefore it's not applicable for that type! + /// + public static class RgbaCompatible { /// - /// Provides efficient implementations for batched to/from conversion. - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . - /// The method is works by internally converting to a therefore it's not applicable for that type! + /// It's not worth to bother the transitive pixel conversion method below this limit. + /// The value depends on the actual gain brought by the SIMD characteristics of the executing CPU and JIT. + /// + private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); + + /// + /// Provides an efficient default implementation for + /// The method works by internally converting to a therefore it's not applicable for that type! /// - public static class RgbaCompatible + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ToVector4( + Configuration configuration, + PixelOperations pixelOperations, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel { - /// - /// It's not worth to bother the transitive pixel conversion method below this limit. - /// The value depends on the actual gain brought by the SIMD characteristics of the executing CPU and JIT. - /// - private static readonly int Vector4ConversionThreshold = CalculateVector4ConversionThreshold(); - - /// - /// Provides an efficient default implementation for - /// The method works by internally converting to a therefore it's not applicable for that type! - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ToVector4( - Configuration configuration, - PixelOperations pixelOperations, - ReadOnlySpan sourcePixels, - Span destVectors, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); - int count = sourcePixels.Length; + int count = sourcePixels.Length; - // Not worth for small buffers: - if (count < Vector4ConversionThreshold) - { - Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); + // Not worth for small buffers: + if (count < Vector4ConversionThreshold) + { + Default.UnsafeToVector4(sourcePixels, destVectors, modifiers); - return; - } + return; + } - // Using the last quarter of 'destVectors' as a temporary buffer to avoid allocation: - int countWithoutLastItem = count - 1; - ReadOnlySpan reducedSource = sourcePixels[..countWithoutLastItem]; - Span lastQuarterOfDestBuffer = MemoryMarshal.Cast(destVectors).Slice((3 * count) + 1, countWithoutLastItem); - pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestBuffer); + // Using the last quarter of 'destVectors' as a temporary buffer to avoid allocation: + int countWithoutLastItem = count - 1; + ReadOnlySpan reducedSource = sourcePixels[..countWithoutLastItem]; + Span lastQuarterOfDestBuffer = MemoryMarshal.Cast(destVectors).Slice((3 * count) + 1, countWithoutLastItem); + pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestBuffer); - // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, - // but we are always reading/writing at different positions: - SimdUtils.ByteToNormalizedFloat( - MemoryMarshal.Cast(lastQuarterOfDestBuffer), - MemoryMarshal.Cast(destVectors[..countWithoutLastItem])); + // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, + // but we are always reading/writing at different positions: + SimdUtils.ByteToNormalizedFloat( + MemoryMarshal.Cast(lastQuarterOfDestBuffer), + MemoryMarshal.Cast(destVectors[..countWithoutLastItem])); - destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); + destVectors[countWithoutLastItem] = sourcePixels[countWithoutLastItem].ToVector4(); - // TODO: Investigate optimized 1-pass approach! - ApplyForwardConversionModifiers(destVectors, modifiers); - } + // TODO: Investigate optimized 1-pass approach! + ApplyForwardConversionModifiers(destVectors, modifiers); + } - /// - /// Provides an efficient default implementation for - /// The method is works by internally converting to a therefore it's not applicable for that type! - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void FromVector4( - Configuration configuration, - PixelOperations pixelOperations, - Span sourceVectors, - Span destPixels, - PixelConversionModifiers modifiers) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); + /// + /// Provides an efficient default implementation for + /// The method is works by internally converting to a therefore it's not applicable for that type! + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void FromVector4( + Configuration configuration, + PixelOperations pixelOperations, + Span sourceVectors, + Span destPixels, + PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); - int count = sourceVectors.Length; + int count = sourceVectors.Length; - // Not worth for small buffers: - if (count < Vector4ConversionThreshold) - { - Default.UnsafeFromVector4(sourceVectors, destPixels, modifiers); + // Not worth for small buffers: + if (count < Vector4ConversionThreshold) + { + Default.UnsafeFromVector4(sourceVectors, destPixels, modifiers); - return; - } + return; + } - // TODO: Investigate optimized 1-pass approach! - ApplyBackwardConversionModifiers(sourceVectors, modifiers); + // TODO: Investigate optimized 1-pass approach! + ApplyBackwardConversionModifiers(sourceVectors, modifiers); - // For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion, - // so let's allocate a temporary buffer as usually: - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(count)) - { - Span tempSpan = tempBuffer.Memory.Span; + // For the opposite direction it's not easy to implement the trick used in RunRgba32CompatibleToVector4Conversion, + // so let's allocate a temporary buffer as usually: + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(count)) + { + Span tempSpan = tempBuffer.Memory.Span; - SimdUtils.NormalizedFloatToByteSaturate( - MemoryMarshal.Cast(sourceVectors), - MemoryMarshal.Cast(tempSpan)); + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + MemoryMarshal.Cast(tempSpan)); - pixelOperations.FromRgba32(configuration, tempSpan, destPixels); - } + pixelOperations.FromRgba32(configuration, tempSpan, destPixels); } + } - private static int CalculateVector4ConversionThreshold() + private static int CalculateVector4ConversionThreshold() + { + if (!Vector.IsHardwareAccelerated) { - if (!Vector.IsHardwareAccelerated) - { - return int.MaxValue; - } - - return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.HasVector8 ? 256 : 128; + return int.MaxValue; } + + return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.HasVector8 ? 256 : 128; } } } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 145cc175df..8f682ae8f6 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -1,47 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.ColorSpaces.Companding; -namespace SixLabors.ImageSharp.PixelFormats.Utils +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +internal static partial class Vector4Converters { - internal static partial class Vector4Converters + /// + /// Apply modifiers used requested by ToVector4() conversion. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) { - /// - /// Apply modifiers used requested by ToVector4() conversion. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ApplyForwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { - if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) - { - SRgbCompanding.Expand(vectors); - } + SRgbCompanding.Expand(vectors); + } - if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) - { - Numerics.Premultiply(vectors); - } + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + Numerics.Premultiply(vectors); } + } - /// - /// Apply modifiers used requested by FromVector4() conversion. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + /// + /// Apply modifiers used requested by FromVector4() conversion. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void ApplyBackwardConversionModifiers(Span vectors, PixelConversionModifiers modifiers) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { - if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) - { - Numerics.UnPremultiply(vectors); - } + Numerics.UnPremultiply(vectors); + } - if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) - { - SRgbCompanding.Compress(vectors); - } + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + SRgbCompanding.Compress(vectors); } } } diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs index 915ba38bf4..ce3acb2b3f 100644 --- a/src/ImageSharp/Primitives/ColorMatrix.cs +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -2,458 +2,456 @@ // Licensed under the Six Labors Split License. #pragma warning disable SA1117 // Parameters should be on same line or separate lines -using System; using System.Globalization; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. +/// +[StructLayout(LayoutKind.Sequential)] +public struct ColorMatrix : IEquatable { /// - /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image. + /// Value at row 1, column 1 of the matrix. + /// + public float M11; + + /// + /// Value at row 1, column 2 of the matrix. /// - [StructLayout(LayoutKind.Sequential)] - public struct ColorMatrix : IEquatable + public float M12; + + /// + /// Value at row 1, column 3 of the matrix. + /// + public float M13; + + /// + /// Value at row 1, column 4 of the matrix. + /// + public float M14; + + /// + /// Value at row 2, column 1 of the matrix. + /// + public float M21; + + /// + /// Value at row 2, column 2 of the matrix. + /// + public float M22; + + /// + /// Value at row 2, column 3 of the matrix. + /// + public float M23; + + /// + /// Value at row 2, column 4 of the matrix. + /// + public float M24; + + /// + /// Value at row 3, column 1 of the matrix. + /// + public float M31; + + /// + /// Value at row 3, column 2 of the matrix. + /// + public float M32; + + /// + /// Value at row 3, column 3 of the matrix. + /// + public float M33; + + /// + /// Value at row 3, column 4 of the matrix. + /// + public float M34; + + /// + /// Value at row 4, column 1 of the matrix. + /// + public float M41; + + /// + /// Value at row 4, column 2 of the matrix. + /// + public float M42; + + /// + /// Value at row 4, column 3 of the matrix. + /// + public float M43; + + /// + /// Value at row 4, column 4 of the matrix. + /// + public float M44; + + /// + /// Value at row 5, column 1 of the matrix. + /// + public float M51; + + /// + /// Value at row 5, column 2 of the matrix. + /// + public float M52; + + /// + /// Value at row 5, column 3 of the matrix. + /// + public float M53; + + /// + /// Value at row 5, column 4 of the matrix. + /// + public float M54; + + /// + /// Initializes a new instance of the struct. + /// + /// The value at row 1, column 1 of the matrix. + /// The value at row 1, column 2 of the matrix. + /// The value at row 1, column 3 of the matrix. + /// The value at row 1, column 4 of the matrix. + /// The value at row 2, column 1 of the matrix. + /// The value at row 2, column 2 of the matrix. + /// The value at row 2, column 3 of the matrix. + /// The value at row 2, column 4 of the matrix. + /// The value at row 3, column 1 of the matrix. + /// The value at row 3, column 2 of the matrix. + /// The value at row 3, column 3 of the matrix. + /// The value at row 3, column 4 of the matrix. + /// The value at row 4, column 1 of the matrix. + /// The value at row 4, column 2 of the matrix. + /// The value at row 4, column 3 of the matrix. + /// The value at row 4, column 4 of the matrix. + /// The value at row 5, column 1 of the matrix. + /// The value at row 5, column 2 of the matrix. + /// The value at row 5, column 3 of the matrix. + /// The value at row 5, column 4 of the matrix. + public ColorMatrix(float m11, float m12, float m13, float m14, + float m21, float m22, float m23, float m24, + float m31, float m32, float m33, float m34, + float m41, float m42, float m43, float m44, + float m51, float m52, float m53, float m54) { - /// - /// Value at row 1, column 1 of the matrix. - /// - public float M11; - - /// - /// Value at row 1, column 2 of the matrix. - /// - public float M12; - - /// - /// Value at row 1, column 3 of the matrix. - /// - public float M13; - - /// - /// Value at row 1, column 4 of the matrix. - /// - public float M14; - - /// - /// Value at row 2, column 1 of the matrix. - /// - public float M21; - - /// - /// Value at row 2, column 2 of the matrix. - /// - public float M22; - - /// - /// Value at row 2, column 3 of the matrix. - /// - public float M23; - - /// - /// Value at row 2, column 4 of the matrix. - /// - public float M24; - - /// - /// Value at row 3, column 1 of the matrix. - /// - public float M31; - - /// - /// Value at row 3, column 2 of the matrix. - /// - public float M32; - - /// - /// Value at row 3, column 3 of the matrix. - /// - public float M33; - - /// - /// Value at row 3, column 4 of the matrix. - /// - public float M34; - - /// - /// Value at row 4, column 1 of the matrix. - /// - public float M41; - - /// - /// Value at row 4, column 2 of the matrix. - /// - public float M42; - - /// - /// Value at row 4, column 3 of the matrix. - /// - public float M43; - - /// - /// Value at row 4, column 4 of the matrix. - /// - public float M44; - - /// - /// Value at row 5, column 1 of the matrix. - /// - public float M51; - - /// - /// Value at row 5, column 2 of the matrix. - /// - public float M52; - - /// - /// Value at row 5, column 3 of the matrix. - /// - public float M53; - - /// - /// Value at row 5, column 4 of the matrix. - /// - public float M54; - - /// - /// Initializes a new instance of the struct. - /// - /// The value at row 1, column 1 of the matrix. - /// The value at row 1, column 2 of the matrix. - /// The value at row 1, column 3 of the matrix. - /// The value at row 1, column 4 of the matrix. - /// The value at row 2, column 1 of the matrix. - /// The value at row 2, column 2 of the matrix. - /// The value at row 2, column 3 of the matrix. - /// The value at row 2, column 4 of the matrix. - /// The value at row 3, column 1 of the matrix. - /// The value at row 3, column 2 of the matrix. - /// The value at row 3, column 3 of the matrix. - /// The value at row 3, column 4 of the matrix. - /// The value at row 4, column 1 of the matrix. - /// The value at row 4, column 2 of the matrix. - /// The value at row 4, column 3 of the matrix. - /// The value at row 4, column 4 of the matrix. - /// The value at row 5, column 1 of the matrix. - /// The value at row 5, column 2 of the matrix. - /// The value at row 5, column 3 of the matrix. - /// The value at row 5, column 4 of the matrix. - public ColorMatrix(float m11, float m12, float m13, float m14, - float m21, float m22, float m23, float m24, - float m31, float m32, float m33, float m34, - float m41, float m42, float m43, float m44, - float m51, float m52, float m53, float m54) - { - this.M11 = m11; - this.M12 = m12; - this.M13 = m13; - this.M14 = m14; - - this.M21 = m21; - this.M22 = m22; - this.M23 = m23; - this.M24 = m24; - - this.M31 = m31; - this.M32 = m32; - this.M33 = m33; - this.M34 = m34; - - this.M41 = m41; - this.M42 = m42; - this.M43 = m43; - this.M44 = m44; - - this.M51 = m51; - this.M52 = m52; - this.M53 = m53; - this.M54 = m54; - } + this.M11 = m11; + this.M12 = m12; + this.M13 = m13; + this.M14 = m14; + + this.M21 = m21; + this.M22 = m22; + this.M23 = m23; + this.M24 = m24; + + this.M31 = m31; + this.M32 = m32; + this.M33 = m33; + this.M34 = m34; + + this.M41 = m41; + this.M42 = m42; + this.M43 = m43; + this.M44 = m44; + + this.M51 = m51; + this.M52 = m52; + this.M53 = m53; + this.M54 = m54; + } - /// - /// Gets the multiplicative identity matrix. - /// - public static ColorMatrix Identity { get; } = - new ColorMatrix(1F, 0F, 0F, 0F, - 0F, 1F, 0F, 0F, - 0F, 0F, 1F, 0F, - 0F, 0F, 0F, 1F, - 0F, 0F, 0F, 0F); - - /// - /// Gets a value indicating whether the matrix is the identity matrix. - /// - public bool IsIdentity - { - get - { - // Check diagonal element first for early out. - return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F - && this.M12 == 0F && this.M13 == 0F && this.M14 == 0F - && this.M21 == 0F && this.M23 == 0F && this.M24 == 0F - && this.M31 == 0F && this.M32 == 0F && this.M34 == 0F - && this.M41 == 0F && this.M42 == 0F && this.M43 == 0F - && this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F; - } - } + /// + /// Gets the multiplicative identity matrix. + /// + public static ColorMatrix Identity { get; } = + new ColorMatrix(1F, 0F, 0F, 0F, + 0F, 1F, 0F, 0F, + 0F, 0F, 1F, 0F, + 0F, 0F, 0F, 1F, + 0F, 0F, 0F, 0F); - /// - /// Adds two matrices together. - /// - /// The first source matrix. - /// The second source matrix. - /// The resulting matrix. - public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) + /// + /// Gets a value indicating whether the matrix is the identity matrix. + /// + public bool IsIdentity + { + get { - var m = default(ColorMatrix); - - m.M11 = value1.M11 + value2.M11; - m.M12 = value1.M12 + value2.M12; - m.M13 = value1.M13 + value2.M13; - m.M14 = value1.M14 + value2.M14; - m.M21 = value1.M21 + value2.M21; - m.M22 = value1.M22 + value2.M22; - m.M23 = value1.M23 + value2.M23; - m.M24 = value1.M24 + value2.M24; - m.M31 = value1.M31 + value2.M31; - m.M32 = value1.M32 + value2.M32; - m.M33 = value1.M33 + value2.M33; - m.M34 = value1.M34 + value2.M34; - m.M41 = value1.M41 + value2.M41; - m.M42 = value1.M42 + value2.M42; - m.M43 = value1.M43 + value2.M43; - m.M44 = value1.M44 + value2.M44; - m.M51 = value1.M51 + value2.M51; - m.M52 = value1.M52 + value2.M52; - m.M53 = value1.M53 + value2.M53; - m.M54 = value1.M54 + value2.M54; - - return m; + // Check diagonal element first for early out. + return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F + && this.M12 == 0F && this.M13 == 0F && this.M14 == 0F + && this.M21 == 0F && this.M23 == 0F && this.M24 == 0F + && this.M31 == 0F && this.M32 == 0F && this.M34 == 0F + && this.M41 == 0F && this.M42 == 0F && this.M43 == 0F + && this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F; } + } - /// - /// Subtracts the second matrix from the first. - /// - /// The first source matrix. - /// The second source matrix. - /// The result of the subtraction. - public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) - { - var m = default(ColorMatrix); - - m.M11 = value1.M11 - value2.M11; - m.M12 = value1.M12 - value2.M12; - m.M13 = value1.M13 - value2.M13; - m.M14 = value1.M14 - value2.M14; - m.M21 = value1.M21 - value2.M21; - m.M22 = value1.M22 - value2.M22; - m.M23 = value1.M23 - value2.M23; - m.M24 = value1.M24 - value2.M24; - m.M31 = value1.M31 - value2.M31; - m.M32 = value1.M32 - value2.M32; - m.M33 = value1.M33 - value2.M33; - m.M34 = value1.M34 - value2.M34; - m.M41 = value1.M41 - value2.M41; - m.M42 = value1.M42 - value2.M42; - m.M43 = value1.M43 - value2.M43; - m.M44 = value1.M44 - value2.M44; - m.M51 = value1.M51 - value2.M51; - m.M52 = value1.M52 - value2.M52; - m.M53 = value1.M53 - value2.M53; - m.M54 = value1.M54 - value2.M54; - - return m; - } + /// + /// Adds two matrices together. + /// + /// The first source matrix. + /// The second source matrix. + /// The resulting matrix. + public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) + { + var m = default(ColorMatrix); + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M13 = value1.M13 + value2.M13; + m.M14 = value1.M14 + value2.M14; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + m.M23 = value1.M23 + value2.M23; + m.M24 = value1.M24 + value2.M24; + m.M31 = value1.M31 + value2.M31; + m.M32 = value1.M32 + value2.M32; + m.M33 = value1.M33 + value2.M33; + m.M34 = value1.M34 + value2.M34; + m.M41 = value1.M41 + value2.M41; + m.M42 = value1.M42 + value2.M42; + m.M43 = value1.M43 + value2.M43; + m.M44 = value1.M44 + value2.M44; + m.M51 = value1.M51 + value2.M51; + m.M52 = value1.M52 + value2.M52; + m.M53 = value1.M53 + value2.M53; + m.M54 = value1.M54 + value2.M54; + + return m; + } - /// - /// Returns a new matrix with the negated elements of the given matrix. - /// - /// The source matrix. - /// The negated matrix. - public static ColorMatrix operator -(ColorMatrix value) - { - var m = default(ColorMatrix); - - m.M11 = -value.M11; - m.M12 = -value.M12; - m.M13 = -value.M13; - m.M14 = -value.M14; - m.M21 = -value.M21; - m.M22 = -value.M22; - m.M23 = -value.M23; - m.M24 = -value.M24; - m.M31 = -value.M31; - m.M32 = -value.M32; - m.M33 = -value.M33; - m.M34 = -value.M34; - m.M41 = -value.M41; - m.M42 = -value.M42; - m.M43 = -value.M43; - m.M44 = -value.M44; - m.M51 = -value.M51; - m.M52 = -value.M52; - m.M53 = -value.M53; - m.M54 = -value.M54; - - return m; - } + /// + /// Subtracts the second matrix from the first. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the subtraction. + public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) + { + var m = default(ColorMatrix); + + m.M11 = value1.M11 - value2.M11; + m.M12 = value1.M12 - value2.M12; + m.M13 = value1.M13 - value2.M13; + m.M14 = value1.M14 - value2.M14; + m.M21 = value1.M21 - value2.M21; + m.M22 = value1.M22 - value2.M22; + m.M23 = value1.M23 - value2.M23; + m.M24 = value1.M24 - value2.M24; + m.M31 = value1.M31 - value2.M31; + m.M32 = value1.M32 - value2.M32; + m.M33 = value1.M33 - value2.M33; + m.M34 = value1.M34 - value2.M34; + m.M41 = value1.M41 - value2.M41; + m.M42 = value1.M42 - value2.M42; + m.M43 = value1.M43 - value2.M43; + m.M44 = value1.M44 - value2.M44; + m.M51 = value1.M51 - value2.M51; + m.M52 = value1.M52 - value2.M52; + m.M53 = value1.M53 - value2.M53; + m.M54 = value1.M54 - value2.M54; + + return m; + } - /// - /// Multiplies a matrix by another matrix. - /// - /// The first source matrix. - /// The second source matrix. - /// The result of the multiplication. - public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) - { - var m = default(ColorMatrix); - - // First row - m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); - m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); - m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); - m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); - - // Second row - m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); - m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); - m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); - m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); - - // Third row - m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); - m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); - m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); - m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); - - // Fourth row - m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); - m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); - m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); - m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); - - // Fifth row - m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; - m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; - 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; - - return m; - } + /// + /// Returns a new matrix with the negated elements of the given matrix. + /// + /// The source matrix. + /// The negated matrix. + public static ColorMatrix operator -(ColorMatrix value) + { + var m = default(ColorMatrix); + + m.M11 = -value.M11; + m.M12 = -value.M12; + m.M13 = -value.M13; + m.M14 = -value.M14; + m.M21 = -value.M21; + m.M22 = -value.M22; + m.M23 = -value.M23; + m.M24 = -value.M24; + m.M31 = -value.M31; + m.M32 = -value.M32; + m.M33 = -value.M33; + m.M34 = -value.M34; + m.M41 = -value.M41; + m.M42 = -value.M42; + m.M43 = -value.M43; + m.M44 = -value.M44; + m.M51 = -value.M51; + m.M52 = -value.M52; + m.M53 = -value.M53; + m.M54 = -value.M54; + + return m; + } - /// - /// Multiplies a matrix by a scalar value. - /// - /// The source matrix. - /// The scaling factor. - /// The scaled matrix. - public static ColorMatrix operator *(ColorMatrix value1, float value2) - { - var m = default(ColorMatrix); - - m.M11 = value1.M11 * value2; - m.M12 = value1.M12 * value2; - m.M13 = value1.M13 * value2; - m.M14 = value1.M14 * value2; - m.M21 = value1.M21 * value2; - m.M22 = value1.M22 * value2; - m.M23 = value1.M23 * value2; - m.M24 = value1.M24 * value2; - m.M31 = value1.M31 * value2; - m.M32 = value1.M32 * value2; - m.M33 = value1.M33 * value2; - m.M34 = value1.M34 * value2; - m.M41 = value1.M41 * value2; - m.M42 = value1.M42 * value2; - m.M43 = value1.M43 * value2; - m.M44 = value1.M44 * value2; - m.M51 = value1.M51 * value2; - m.M52 = value1.M52 * value2; - m.M53 = value1.M53 * value2; - m.M54 = value1.M54 * value2; - - return m; - } + /// + /// Multiplies a matrix by another matrix. + /// + /// The first source matrix. + /// The second source matrix. + /// The result of the multiplication. + public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) + { + var m = default(ColorMatrix); + + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + 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; + + return m; + } - /// - /// Returns a boolean indicating whether the given two matrices are equal. - /// - /// The first matrix to compare. - /// The second matrix to compare. - /// True if the given matrices are equal; False otherwise. - public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2); - - /// - /// Returns a boolean indicating whether the given two matrices are not equal. - /// - /// The first matrix to compare. - /// The second matrix to compare. - /// True if the given matrices are equal; False otherwise. - public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2); - - /// - public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix); - - /// - public bool Equals(ColorMatrix other) => - this.M11 == other.M11 - && this.M12 == other.M12 - && this.M13 == other.M13 - && this.M14 == other.M14 - && this.M21 == other.M21 - && this.M22 == other.M22 - && this.M23 == other.M23 - && this.M24 == other.M24 - && this.M31 == other.M31 - && this.M32 == other.M32 - && this.M33 == other.M33 - && this.M34 == other.M34 - && this.M41 == other.M41 - && this.M42 == other.M42 - && this.M43 == other.M43 - && this.M44 == other.M44 - && this.M51 == other.M51 - && this.M52 == other.M52 - && this.M53 == other.M53 - && this.M54 == other.M54; - - /// - public override int GetHashCode() - { - HashCode hash = default; - hash.Add(this.M11); - hash.Add(this.M12); - hash.Add(this.M13); - hash.Add(this.M14); - hash.Add(this.M21); - hash.Add(this.M22); - hash.Add(this.M23); - hash.Add(this.M24); - hash.Add(this.M31); - hash.Add(this.M32); - hash.Add(this.M33); - hash.Add(this.M34); - hash.Add(this.M41); - hash.Add(this.M42); - hash.Add(this.M43); - hash.Add(this.M44); - hash.Add(this.M51); - hash.Add(this.M52); - hash.Add(this.M53); - hash.Add(this.M54); - return hash.ToHashCode(); - } + /// + /// Multiplies a matrix by a scalar value. + /// + /// The source matrix. + /// The scaling factor. + /// The scaled matrix. + public static ColorMatrix operator *(ColorMatrix value1, float value2) + { + var m = default(ColorMatrix); + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M13 = value1.M13 * value2; + m.M14 = value1.M14 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + m.M23 = value1.M23 * value2; + m.M24 = value1.M24 * value2; + m.M31 = value1.M31 * value2; + m.M32 = value1.M32 * value2; + m.M33 = value1.M33 * value2; + m.M34 = value1.M34 * value2; + m.M41 = value1.M41 * value2; + m.M42 = value1.M42 * value2; + m.M43 = value1.M43 * value2; + m.M44 = value1.M44 * value2; + m.M51 = value1.M51 * value2; + m.M52 = value1.M52 * value2; + m.M53 = value1.M53 * value2; + m.M54 = value1.M54 * value2; + + return m; + } - /// - public override string ToString() - { - CultureInfo ci = CultureInfo.CurrentCulture; - - return 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}}} }}", - this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci), - this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci), - this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci), - this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci), - this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci)); - } + /// + /// Returns a boolean indicating whether the given two matrices are equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2); + + /// + /// Returns a boolean indicating whether the given two matrices are not equal. + /// + /// The first matrix to compare. + /// The second matrix to compare. + /// True if the given matrices are equal; False otherwise. + public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2); + + /// + public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix); + + /// + public bool Equals(ColorMatrix other) => + this.M11 == other.M11 + && this.M12 == other.M12 + && this.M13 == other.M13 + && this.M14 == other.M14 + && this.M21 == other.M21 + && this.M22 == other.M22 + && this.M23 == other.M23 + && this.M24 == other.M24 + && this.M31 == other.M31 + && this.M32 == other.M32 + && this.M33 == other.M33 + && this.M34 == other.M34 + && this.M41 == other.M41 + && this.M42 == other.M42 + && this.M43 == other.M43 + && this.M44 == other.M44 + && this.M51 == other.M51 + && this.M52 == other.M52 + && this.M53 == other.M53 + && this.M54 == other.M54; + + /// + public override int GetHashCode() + { + HashCode hash = default; + hash.Add(this.M11); + hash.Add(this.M12); + hash.Add(this.M13); + hash.Add(this.M14); + hash.Add(this.M21); + hash.Add(this.M22); + hash.Add(this.M23); + hash.Add(this.M24); + hash.Add(this.M31); + hash.Add(this.M32); + hash.Add(this.M33); + hash.Add(this.M34); + hash.Add(this.M41); + hash.Add(this.M42); + hash.Add(this.M43); + hash.Add(this.M44); + hash.Add(this.M51); + hash.Add(this.M52); + hash.Add(this.M53); + hash.Add(this.M54); + return hash.ToHashCode(); + } + + /// + public override string ToString() + { + CultureInfo ci = CultureInfo.CurrentCulture; + + return 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}}} }}", + this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci), + this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci), + this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci), + this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci), + this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci)); } } diff --git a/src/ImageSharp/Primitives/Complex64.cs b/src/ImageSharp/Primitives/Complex64.cs index d607c100ba..b5544bcf56 100644 --- a/src/ImageSharp/Primitives/Complex64.cs +++ b/src/ImageSharp/Primitives/Complex64.cs @@ -1,95 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a complex number, where the real and imaginary parts are stored as values. +/// +/// +/// This is a more efficient version of the type. +/// +internal readonly struct Complex64 : IEquatable { /// - /// Represents a complex number, where the real and imaginary parts are stored as values. + /// The real part of the complex number /// - /// - /// This is a more efficient version of the type. - /// - internal readonly struct Complex64 : IEquatable - { - /// - /// The real part of the complex number - /// - public readonly float Real; + public readonly float Real; - /// - /// The imaginary part of the complex number - /// - public readonly float Imaginary; + /// + /// The imaginary part of the complex number + /// + public readonly float Imaginary; - /// - /// Initializes a new instance of the struct. - /// - /// The real part in the complex number. - /// The imaginary part in the complex number. - public Complex64(float real, float imaginary) - { - this.Real = real; - this.Imaginary = imaginary; - } + /// + /// Initializes a new instance of the struct. + /// + /// The real part in the complex number. + /// The imaginary part in the complex number. + public Complex64(float real, float imaginary) + { + this.Real = real; + this.Imaginary = imaginary; + } - /// - /// Performs the multiplication operation between a instance and a scalar. - /// - /// The value to multiply. - /// The scalar to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static Complex64 operator *(Complex64 value, float scalar) => new Complex64(value.Real * scalar, value.Imaginary * scalar); + /// + /// Performs the multiplication operation between a instance and a scalar. + /// + /// The value to multiply. + /// The scalar to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static Complex64 operator *(Complex64 value, float scalar) => new Complex64(value.Real * scalar, value.Imaginary * scalar); - /// - /// Performs the multiplication operation between a instance and a . - /// - /// The value to multiply. - /// The instance to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static ComplexVector4 operator *(Complex64 value, Vector4 vector) - { - return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary }; - } + /// + /// Performs the multiplication operation between a instance and a . + /// + /// The value to multiply. + /// The instance to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static ComplexVector4 operator *(Complex64 value, Vector4 vector) + { + return new ComplexVector4 { Real = vector * value.Real, Imaginary = vector * value.Imaginary }; + } - /// - /// Performs the multiplication operation between a instance and a . - /// - /// The value to multiply. - /// The instance to use to multiply the value. - /// The result - [MethodImpl(InliningOptions.ShortMethod)] - public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector) - { - Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary); - Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real); - return new ComplexVector4 { Real = real, Imaginary = imaginary }; - } + /// + /// Performs the multiplication operation between a instance and a . + /// + /// The value to multiply. + /// The instance to use to multiply the value. + /// The result + [MethodImpl(InliningOptions.ShortMethod)] + public static ComplexVector4 operator *(Complex64 value, ComplexVector4 vector) + { + Vector4 real = (value.Real * vector.Real) - (value.Imaginary * vector.Imaginary); + Vector4 imaginary = (value.Real * vector.Imaginary) + (value.Imaginary * vector.Real); + return new ComplexVector4 { Real = real, Imaginary = imaginary }; + } - /// - public bool Equals(Complex64 other) - { - return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); - } + /// + public bool Equals(Complex64 other) + { + return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); + } - /// - public override bool Equals(object obj) => obj is Complex64 other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Complex64 other && this.Equals(other); - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked { - unchecked - { - return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); - } + return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); } - - /// - public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j"; } + + /// + public override string ToString() => $"{this.Real}{(this.Imaginary >= 0 ? "+" : string.Empty)}{this.Imaginary}j"; } diff --git a/src/ImageSharp/Primitives/ComplexVector4.cs b/src/ImageSharp/Primitives/ComplexVector4.cs index 0731924f25..0feffdb36f 100644 --- a/src/ImageSharp/Primitives/ComplexVector4.cs +++ b/src/ImageSharp/Primitives/ComplexVector4.cs @@ -1,63 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// A vector with 4 values of type . +/// +internal struct ComplexVector4 : IEquatable { /// - /// A vector with 4 values of type . + /// The real part of the complex vector /// - internal struct ComplexVector4 : IEquatable + public Vector4 Real; + + /// + /// The imaginary part of the complex number + /// + public Vector4 Imaginary; + + /// + /// Sums the values in the input to the current instance + /// + /// The input to sum + [MethodImpl(InliningOptions.ShortMethod)] + public void Sum(ComplexVector4 value) { - /// - /// The real part of the complex vector - /// - public Vector4 Real; - - /// - /// The imaginary part of the complex number - /// - public Vector4 Imaginary; - - /// - /// Sums the values in the input to the current instance - /// - /// The input to sum - [MethodImpl(InliningOptions.ShortMethod)] - public void Sum(ComplexVector4 value) - { - this.Real += value.Real; - this.Imaginary += value.Imaginary; - } + this.Real += value.Real; + this.Imaginary += value.Imaginary; + } - /// - /// Performs a weighted sum on the current instance according to the given parameters - /// - /// The 'a' parameter, for the real component - /// The 'b' parameter, for the imaginary component - /// The resulting value - [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b); - - /// - public bool Equals(ComplexVector4 other) - { - return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); - } + /// + /// Performs a weighted sum on the current instance according to the given parameters + /// + /// The 'a' parameter, for the real component + /// The 'b' parameter, for the imaginary component + /// The resulting value + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 WeightedSum(float a, float b) => (this.Real * a) + (this.Imaginary * b); + + /// + public bool Equals(ComplexVector4 other) + { + return this.Real.Equals(other.Real) && this.Imaginary.Equals(other.Imaginary); + } - /// - public override bool Equals(object obj) => obj is ComplexVector4 other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is ComplexVector4 other && this.Equals(other); - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked { - unchecked - { - return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); - } + return (this.Real.GetHashCode() * 397) ^ this.Imaginary.GetHashCode(); } } } diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 63ca6f7564..4b00323640 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -1,280 +1,278 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a dense matrix with arbitrary elements. +/// Components that are adjacent in a column of the matrix are adjacent in the storage array. +/// The components are said to be stored in column major order. +/// +/// The type of elements in the matrix. +public readonly struct DenseMatrix : IEquatable> + where T : struct, IEquatable { /// - /// Represents a dense matrix with arbitrary elements. - /// Components that are adjacent in a column of the matrix are adjacent in the storage array. - /// The components are said to be stored in column major order. + /// Initializes a new instance of the struct. /// - /// The type of elements in the matrix. - public readonly struct DenseMatrix : IEquatable> - where T : struct, IEquatable + /// The length of each side in the matrix. + public DenseMatrix(int length) + : this(length, length) { - /// - /// Initializes a new instance of the struct. - /// - /// The length of each side in the matrix. - public DenseMatrix(int length) - : this(length, length) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The number of columns. - /// The number of rows. - public DenseMatrix(int columns, int rows) - { - Guard.MustBeGreaterThan(columns, 0, nameof(columns)); - Guard.MustBeGreaterThan(rows, 0, nameof(rows)); - - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = columns * rows; - this.Data = new T[this.Columns * this.Rows]; - } + /// + /// Initializes a new instance of the struct. + /// + /// The number of columns. + /// The number of rows. + public DenseMatrix(int columns, int rows) + { + Guard.MustBeGreaterThan(columns, 0, nameof(columns)); + Guard.MustBeGreaterThan(rows, 0, nameof(rows)); + + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = columns * rows; + this.Data = new T[this.Columns * this.Rows]; + } - /// - /// Initializes a new instance of the struct. - /// - /// The 2D array to provide access to. - public DenseMatrix(T[,] data) - { - Guard.NotNull(data, nameof(data)); - int rows = data.GetLength(0); - int columns = data.GetLength(1); + /// + /// Initializes a new instance of the struct. + /// + /// The 2D array to provide access to. + public DenseMatrix(T[,] data) + { + Guard.NotNull(data, nameof(data)); + int rows = data.GetLength(0); + int columns = data.GetLength(1); - Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); - Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); + Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); + Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = this.Columns * this.Rows; - this.Data = new T[this.Columns * this.Rows]; + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = this.Columns * this.Rows; + this.Data = new T[this.Columns * this.Rows]; - for (int y = 0; y < this.Rows; y++) + for (int y = 0; y < this.Rows; y++) + { + for (int x = 0; x < this.Columns; x++) { - for (int x = 0; x < this.Columns; x++) - { - ref T value = ref this[y, x]; - value = data[y, x]; - } + ref T value = ref this[y, x]; + value = data[y, x]; } } + } - /// - /// Initializes a new instance of the struct. - /// - /// The number of columns. - /// The number of rows. - /// The array to provide access to. - public DenseMatrix(int columns, int rows, Span data) - { - Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); - Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); - Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns"); + /// + /// Initializes a new instance of the struct. + /// + /// The number of columns. + /// The number of rows. + /// The array to provide access to. + public DenseMatrix(int columns, int rows, Span data) + { + Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows)); + Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns)); + Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns"); - this.Rows = rows; - this.Columns = columns; - this.Size = new Size(columns, rows); - this.Count = this.Columns * this.Rows; - this.Data = new T[this.Columns * this.Rows]; + this.Rows = rows; + this.Columns = columns; + this.Size = new Size(columns, rows); + this.Count = this.Columns * this.Rows; + this.Data = new T[this.Columns * this.Rows]; - data.CopyTo(this.Data); - } + data.CopyTo(this.Data); + } + + /// + /// Gets the 1D representation of the dense matrix. + /// + public readonly T[] Data { get; } - /// - /// Gets the 1D representation of the dense matrix. - /// - public readonly T[] Data { get; } - - /// - /// Gets the number of columns in the dense matrix. - /// - public readonly int Columns { get; } - - /// - /// Gets the number of rows in the dense matrix. - /// - public readonly int Rows { get; } - - /// - /// Gets the size of the dense matrix. - /// - public readonly Size Size { get; } - - /// - /// Gets the number of items in the array. - /// - public readonly int Count { get; } - - /// - /// Gets a span wrapping the . - /// - public Span Span => new(this.Data); - - /// - /// Gets or sets the item at the specified position. - /// - /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. - /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. - /// The at the specified position. - public ref T this[int row, int column] + /// + /// Gets the number of columns in the dense matrix. + /// + public readonly int Columns { get; } + + /// + /// Gets the number of rows in the dense matrix. + /// + public readonly int Rows { get; } + + /// + /// Gets the size of the dense matrix. + /// + public readonly Size Size { get; } + + /// + /// Gets the number of items in the array. + /// + public readonly int Count { get; } + + /// + /// Gets a span wrapping the . + /// + public Span Span => new(this.Data); + + /// + /// Gets or sets the item at the specified position. + /// + /// The row-coordinate of the item. Must be greater than or equal to zero and less than the height of the array. + /// The column-coordinate of the item. Must be greater than or equal to zero and less than the width of the array. + /// The at the specified position. + public ref T this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.CheckCoordinates(row, column); - return ref this.Data[(row * this.Columns) + column]; - } + this.CheckCoordinates(row, column); + return ref this.Data[(row * this.Columns) + column]; } + } - /// - /// Performs an implicit conversion from a to a . - /// - /// The source array. - /// - /// The representation on the source data. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator DenseMatrix(T[,] data) => new(data); - - /// - /// Performs an implicit conversion from a to a . - /// - /// The source array. - /// - /// The representation on the source data. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs an implicit conversion from a to a . + /// + /// The source array. + /// + /// The representation on the source data. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator DenseMatrix(T[,] data) => new(data); + + /// + /// Performs an implicit conversion from a to a . + /// + /// The source array. + /// + /// The representation on the source data. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] #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 - { - T[,] result = new T[data.Rows, data.Columns]; + { + T[,] result = new T[data.Rows, data.Columns]; - for (int y = 0; y < data.Rows; y++) + for (int y = 0; y < data.Rows; y++) + { + for (int x = 0; x < data.Columns; x++) { - for (int x = 0; x < data.Columns; x++) - { - ref T value = ref result[y, x]; - value = data[y, x]; - } + ref T value = ref result[y, x]; + value = data[y, x]; } - - 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. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DenseMatrix Transpose() - { - DenseMatrix result = new DenseMatrix(this.Rows, this.Columns); + return result; + } - for (int y = 0; y < this.Rows; y++) - { - for (int x = 0; x < this.Columns; x++) - { - ref T value = ref result[x, y]; - value = this[y, x]; - } - } + /// + /// 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); - return result; - } + /// + /// 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); - /// - /// Fills the matrix with the given value - /// - /// The value to fill each item with - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fill(T value) => this.Span.Fill(value); + /// + /// Transposes the rows and columns of the dense matrix. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DenseMatrix Transpose() + { + DenseMatrix result = new DenseMatrix(this.Rows, this.Columns); - /// - /// Clears the matrix setting each value to the default value for the element type - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() => this.Span.Clear(); - - /// - /// Checks the coordinates to ensure they are within bounds. - /// - /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the matrix. - /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the matrix. - /// - /// Thrown if the coordinates are not within the bounds of the array. - /// - [Conditional("DEBUG")] - private void CheckCoordinates(int row, int column) + for (int y = 0; y < this.Rows; y++) { - if (row < 0 || row >= this.Rows) + for (int x = 0; x < this.Columns; x++) { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); - } - - if (column < 0 || column >= this.Columns) - { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); + ref T value = ref result[x, y]; + value = this[y, x]; } } - /// - public override bool Equals(object obj) - => obj is DenseMatrix other && this.Equals(other); + return result; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(DenseMatrix other) => - this.Columns == other.Columns - && this.Rows == other.Rows - && this.Span.SequenceEqual(other.Span); + /// + /// Fills the matrix with the given value + /// + /// The value to fill each item with + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Fill(T value) => this.Span.Fill(value); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() + /// + /// Clears the matrix setting each value to the default value for the element type + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() => this.Span.Clear(); + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the item. Must be greater than zero and smaller than the height of the matrix. + /// The x-coordinate of the item. Must be greater than zero and smaller than the width of the matrix. + /// + /// Thrown if the coordinates are not within the bounds of the array. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) { - HashCode code = default; + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); + } - code.Add(this.Columns); - code.Add(this.Rows); + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); + } + } - Span span = this.Span; - for (int i = 0; i < span.Length; i++) - { - code.Add(span[i]); - } + /// + public override bool Equals(object obj) + => obj is DenseMatrix other && this.Equals(other); - return code.ToHashCode(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(DenseMatrix other) => + this.Columns == other.Columns + && this.Rows == other.Rows + && this.Span.SequenceEqual(other.Span); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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/LongRational.cs b/src/ImageSharp/Primitives/LongRational.cs index 062036a57f..baf01364d9 100644 --- a/src/ImageSharp/Primitives/LongRational.cs +++ b/src/ImageSharp/Primitives/LongRational.cs @@ -1,225 +1,223 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Text; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a number that can be expressed as a fraction. +/// +/// +/// This is a very simplified implementation of a rational number designed for use with metadata only. +/// +internal readonly struct LongRational : IEquatable { /// - /// Represents a number that can be expressed as a fraction. + /// Initializes a new instance of the struct. /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - internal readonly struct LongRational : IEquatable + /// + /// The number above the line in a vulgar fraction showing how many of the parts + /// indicated by the denominator are taken. + /// + /// + /// The number below the line in a vulgar fraction; a divisor. + /// + public LongRational(long numerator, long denominator) { - /// - /// Initializes a new instance of the struct. - /// - /// - /// The number above the line in a vulgar fraction showing how many of the parts - /// indicated by the denominator are taken. - /// - /// - /// The number below the line in a vulgar fraction; a divisor. - /// - public LongRational(long numerator, long denominator) - { - this.Numerator = numerator; - this.Denominator = denominator; - } + this.Numerator = numerator; + this.Denominator = denominator; + } - /// - /// Gets the numerator of a number. - /// - public long Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public long Denominator { get; } - - /// - /// Gets a value indicating whether this instance is indeterminate. - /// - public bool IsIndeterminate => this.Denominator == 0 && this.Numerator == 0; - - /// - /// Gets a value indicating whether this instance is an integer (n, 1) - /// - public bool IsInteger => this.Denominator == 1; - - /// - /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) - /// - public bool IsNegativeInfinity => this.Denominator == 0 && this.Numerator == -1; - - /// - /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) - /// - public bool IsPositiveInfinity => this.Denominator == 0 && this.Numerator == 1; - - /// - /// Gets a value indicating whether this instance is equal to 0 (0, 1) - /// - public bool IsZero => this.Denominator == 1 && this.Numerator == 0; - - /// - public override bool Equals(object obj) - => obj is LongRational longRational && this.Equals(longRational); - - /// - public bool Equals(LongRational other) - => this.Numerator == other.Numerator && this.Denominator == other.Denominator; - - /// - public override int GetHashCode() - => HashCode.Combine(this.Numerator, this.Denominator); - - /// - public override string ToString() - => this.ToString(CultureInfo.InvariantCulture); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - if (this.IsIndeterminate) - { - return "[ Indeterminate ]"; - } + /// + /// Gets the numerator of a number. + /// + public long Numerator { get; } - if (this.IsPositiveInfinity) - { - return "[ PositiveInfinity ]"; - } + /// + /// Gets the denominator of a number. + /// + public long Denominator { get; } - if (this.IsNegativeInfinity) - { - return "[ NegativeInfinity ]"; - } + /// + /// Gets a value indicating whether this instance is indeterminate. + /// + public bool IsIndeterminate => this.Denominator == 0 && this.Numerator == 0; - if (this.IsZero) - { - return "0"; - } + /// + /// Gets a value indicating whether this instance is an integer (n, 1) + /// + public bool IsInteger => this.Denominator == 1; - if (this.IsInteger) - { - return this.Numerator.ToString(provider); - } + /// + /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0) + /// + public bool IsNegativeInfinity => this.Denominator == 0 && this.Numerator == -1; + + /// + /// Gets a value indicating whether this instance is equal to positive infinity (1, 0) + /// + public bool IsPositiveInfinity => this.Denominator == 0 && this.Numerator == 1; + + /// + /// Gets a value indicating whether this instance is equal to 0 (0, 1) + /// + public bool IsZero => this.Denominator == 1 && this.Numerator == 0; + + /// + public override bool Equals(object obj) + => obj is LongRational longRational && this.Equals(longRational); + + /// + public bool Equals(LongRational other) + => this.Numerator == other.Numerator && this.Denominator == other.Denominator; - StringBuilder sb = new(); - sb.Append(this.Numerator.ToString(provider)) - .Append('/') - .Append(this.Denominator.ToString(provider)); + /// + public override int GetHashCode() + => HashCode.Combine(this.Numerator, this.Denominator); - return sb.ToString(); + /// + public override string ToString() + => this.ToString(CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + if (this.IsIndeterminate) + { + return "[ Indeterminate ]"; } - /// - /// Create a new instance of the struct from a double value. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public static LongRational FromDouble(double value, bool bestPrecision) + if (this.IsPositiveInfinity) { - if (double.IsNaN(value)) - { - return new LongRational(0, 0); - } + return "[ PositiveInfinity ]"; + } - if (double.IsPositiveInfinity(value)) - { - return new LongRational(1, 0); - } + if (this.IsNegativeInfinity) + { + return "[ NegativeInfinity ]"; + } - if (double.IsNegativeInfinity(value)) - { - return new LongRational(-1, 0); - } + if (this.IsZero) + { + return "0"; + } - long numerator = 1; - long denominator = 1; + if (this.IsInteger) + { + return this.Numerator.ToString(provider); + } - double val = Math.Abs(value); - double df = numerator / (double)denominator; - double epsilon = bestPrecision ? double.Epsilon : .000001; + StringBuilder sb = new(); + sb.Append(this.Numerator.ToString(provider)) + .Append('/') + .Append(this.Denominator.ToString(provider)); - while (Math.Abs(df - val) > epsilon) - { - if (df < val) - { - numerator++; - } - else - { - denominator++; - numerator = (int)(val * denominator); - } - - df = numerator / (double)denominator; - } + return sb.ToString(); + } - if (value < 0.0) - { - numerator *= -1; - } + /// + /// Create a new instance of the struct from a double value. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public static LongRational FromDouble(double value, bool bestPrecision) + { + if (double.IsNaN(value)) + { + return new LongRational(0, 0); + } - return new LongRational(numerator, denominator).Simplify(); + if (double.IsPositiveInfinity(value)) + { + return new LongRational(1, 0); } - /// - /// Finds the greatest common divisor of two values. - /// - /// The first value - /// The second value - /// The - private static long GreatestCommonDivisor(long left, long right) + if (double.IsNegativeInfinity(value)) { - return right == 0 ? left : GreatestCommonDivisor(right, left % right); + return new LongRational(-1, 0); } - /// - /// Simplifies the - /// - public LongRational Simplify() + long numerator = 1; + long denominator = 1; + + double val = Math.Abs(value); + double df = numerator / (double)denominator; + double epsilon = bestPrecision ? double.Epsilon : .000001; + + while (Math.Abs(df - val) > epsilon) { - if (this.IsIndeterminate || - this.IsNegativeInfinity || - this.IsPositiveInfinity || - this.IsInteger || - this.IsZero) + if (df < val) { - return this; + numerator++; } - - if (this.Numerator == 0) + else { - return new LongRational(0, 0); + denominator++; + numerator = (int)(val * denominator); } - if (this.Numerator == this.Denominator) - { - return new LongRational(1, 1); - } + df = numerator / (double)denominator; + } + + if (value < 0.0) + { + numerator *= -1; + } - long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); + return new LongRational(numerator, denominator).Simplify(); + } - if (gcd > 1) - { - return new LongRational(this.Numerator / gcd, this.Denominator / gcd); - } + /// + /// Finds the greatest common divisor of two values. + /// + /// The first value + /// The second value + /// The + private static long GreatestCommonDivisor(long left, long right) + { + return right == 0 ? left : GreatestCommonDivisor(right, left % right); + } + /// + /// Simplifies the + /// + public LongRational Simplify() + { + if (this.IsIndeterminate || + this.IsNegativeInfinity || + this.IsPositiveInfinity || + this.IsInteger || + this.IsZero) + { return this; } + + if (this.Numerator == 0) + { + return new LongRational(0, 0); + } + + if (this.Numerator == this.Denominator) + { + return new LongRational(1, 1); + } + + long gcd = GreatestCommonDivisor(Math.Abs(this.Numerator), Math.Abs(this.Denominator)); + + if (gcd > 1) + { + return new LongRational(this.Numerator / gcd, this.Denominator / gcd); + } + + return this; } } diff --git a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs index ab7cd223d2..9843b275f4 100644 --- a/src/ImageSharp/Primitives/Matrix3x2Extensions.cs +++ b/src/ImageSharp/Primitives/Matrix3x2Extensions.cs @@ -3,99 +3,98 @@ using System.Numerics; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the struct. +/// +public static class Matrix3x2Extensions { /// - /// Extension methods for the struct. + /// Creates a translation matrix from the given vector. /// - public static class Matrix3x2Extensions - { - /// - /// Creates a translation matrix from the given vector. - /// - /// The translation position. - /// A translation matrix. - public static Matrix3x2 CreateTranslation(PointF position) => Matrix3x2.CreateTranslation(position); + /// The translation position. + /// A translation matrix. + public static Matrix3x2 CreateTranslation(PointF position) => Matrix3x2.CreateTranslation(position); - /// - /// Creates a scale matrix that is offset by a given center point. - /// - /// Value to scale by on the X-axis. - /// Value to scale by on the Y-axis. - /// The center point. - /// A scaling matrix. - public static Matrix3x2 CreateScale(float xScale, float yScale, PointF centerPoint) => Matrix3x2.CreateScale(xScale, yScale, centerPoint); + /// + /// Creates a scale matrix that is offset by a given center point. + /// + /// Value to scale by on the X-axis. + /// Value to scale by on the Y-axis. + /// The center point. + /// A scaling matrix. + public static Matrix3x2 CreateScale(float xScale, float yScale, PointF centerPoint) => Matrix3x2.CreateScale(xScale, yScale, centerPoint); - /// - /// Creates a scale matrix from the given vector scale. - /// - /// The scale to use. - /// A scaling matrix. - public static Matrix3x2 CreateScale(SizeF scales) => Matrix3x2.CreateScale(scales); + /// + /// Creates a scale matrix from the given vector scale. + /// + /// The scale to use. + /// A scaling matrix. + public static Matrix3x2 CreateScale(SizeF scales) => Matrix3x2.CreateScale(scales); - /// - /// Creates a scale matrix from the given vector scale with an offset from the given center point. - /// - /// The scale to use. - /// The center offset. - /// A scaling matrix. - public static Matrix3x2 CreateScale(SizeF scales, PointF centerPoint) => Matrix3x2.CreateScale(scales, centerPoint); + /// + /// Creates a scale matrix from the given vector scale with an offset from the given center point. + /// + /// The scale to use. + /// The center offset. + /// A scaling matrix. + public static Matrix3x2 CreateScale(SizeF scales, PointF centerPoint) => Matrix3x2.CreateScale(scales, centerPoint); - /// - /// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center. - /// - /// The uniform scale to use. - /// The center offset. - /// A scaling matrix. - public static Matrix3x2 CreateScale(float scale, PointF centerPoint) => Matrix3x2.CreateScale(scale, centerPoint); + /// + /// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center. + /// + /// The uniform scale to use. + /// The center offset. + /// A scaling matrix. + public static Matrix3x2 CreateScale(float scale, PointF centerPoint) => Matrix3x2.CreateScale(scale, centerPoint); - /// - /// Creates a skew matrix from the given angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// A skew matrix. - public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + /// + /// Creates a skew matrix from the given angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// A skew matrix. + public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - /// - /// Creates a skew matrix from the given angles in radians and a center point. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The center point. - /// A skew matrix. - public static Matrix3x2 CreateSkew(float radiansX, float radiansY, PointF centerPoint) => Matrix3x2.CreateSkew(radiansX, radiansY, centerPoint); + /// + /// Creates a skew matrix from the given angles in radians and a center point. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The center point. + /// A skew matrix. + public static Matrix3x2 CreateSkew(float radiansX, float radiansY, PointF centerPoint) => Matrix3x2.CreateSkew(radiansX, radiansY, centerPoint); - /// - /// Creates a skew matrix from the given angles in degrees and a center point. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The center point. - /// A skew matrix. - public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); + /// + /// Creates a skew matrix from the given angles in degrees and a center point. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The center point. + /// A skew matrix. + public static Matrix3x2 CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); - /// - /// Creates a rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// A rotation matrix. - public static Matrix3x2 CreateRotationDegrees(float degrees) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees)); + /// + /// Creates a rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// A rotation matrix. + public static Matrix3x2 CreateRotationDegrees(float degrees) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees)); - /// - /// Creates a rotation matrix using the given rotation in radians and a center point. - /// - /// The amount of rotation, in radians. - /// The center point. - /// A rotation matrix. - public static Matrix3x2 CreateRotation(float radians, PointF centerPoint) => Matrix3x2.CreateRotation(radians, centerPoint); + /// + /// Creates a rotation matrix using the given rotation in radians and a center point. + /// + /// The amount of rotation, in radians. + /// The center point. + /// A rotation matrix. + public static Matrix3x2 CreateRotation(float radians, PointF centerPoint) => Matrix3x2.CreateRotation(radians, centerPoint); - /// - /// Creates a rotation matrix using the given rotation in degrees and a center point. - /// - /// The amount of rotation, in degrees. - /// The center point. - /// A rotation matrix. - public static Matrix3x2 CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); - } + /// + /// Creates a rotation matrix using the given rotation in degrees and a center point. + /// + /// The amount of rotation, in degrees. + /// The center point. + /// A rotation matrix. + public static Matrix3x2 CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); } diff --git a/src/ImageSharp/Primitives/Number.cs b/src/ImageSharp/Primitives/Number.cs index 2a79864101..62ac1e4de7 100644 --- a/src/ImageSharp/Primitives/Number.cs +++ b/src/ImageSharp/Primitives/Number.cs @@ -1,187 +1,185 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents an integral number. +/// +[StructLayout(LayoutKind.Explicit)] +public struct Number : IEquatable, IComparable { + [FieldOffset(0)] + private readonly int signedValue; + + [FieldOffset(0)] + private readonly uint unsignedValue; + + [FieldOffset(4)] + private readonly bool isSigned; + /// - /// Represents an integral number. + /// Initializes a new instance of the struct. /// - [StructLayout(LayoutKind.Explicit)] - public struct Number : IEquatable, IComparable + /// The value of the number. + public Number(int value) + : this() { - [FieldOffset(0)] - private readonly int signedValue; + this.signedValue = value; + this.isSigned = true; + } - [FieldOffset(0)] - private readonly uint unsignedValue; + /// + /// Initializes a new instance of the struct. + /// + /// The value of the number. + public Number(uint value) + : this() + { + this.unsignedValue = value; + this.isSigned = false; + } - [FieldOffset(4)] - private readonly bool isSigned; + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(int value) => new Number(value); - /// - /// Initializes a new instance of the struct. - /// - /// The value of the number. - public Number(int value) - : this() - { - this.signedValue = value; - this.isSigned = true; - } + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(uint value) => new Number(value); - /// - /// Initializes a new instance of the struct. - /// - /// The value of the number. - public Number(uint value) - : this() - { - this.unsignedValue = value; - this.isSigned = false; - } + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(ushort value) => new Number((uint)value); - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(int value) => new Number(value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(uint value) => new Number(value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(ushort value) => new Number((uint)value); - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator int(Number number) - { - return number.isSigned - ? number.signedValue - : (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue); - } + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator int(Number number) + { + return number.isSigned + ? number.signedValue + : (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue); + } - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator uint(Number number) - { - return number.isSigned - ? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue) - : number.unsignedValue; - } + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator uint(Number number) + { + return number.isSigned + ? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue) + : number.unsignedValue; + } - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator ushort(Number number) - { - return number.isSigned - ? (ushort)Numerics.Clamp(number.signedValue, ushort.MinValue, ushort.MaxValue) - : (ushort)Numerics.Clamp(number.unsignedValue, ushort.MinValue, ushort.MaxValue); - } + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator ushort(Number number) + { + return number.isSigned + ? (ushort)Numerics.Clamp(number.signedValue, ushort.MinValue, ushort.MaxValue) + : (ushort)Numerics.Clamp(number.unsignedValue, ushort.MinValue, ushort.MaxValue); + } - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator ==(Number left, Number right) => Equals(left, right); - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - public static bool operator !=(Number left, Number right) => !Equals(left, right); - - /// - /// Determines whether the first is more than the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator >(Number left, Number right) => left.CompareTo(right) == 1; - - /// - /// Determines whether the first is less than the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator <(Number left, Number right) => left.CompareTo(right) == -1; - - /// - /// Determines whether the first is more than or equal to the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator >=(Number left, Number right) => left.CompareTo(right) >= 0; - - /// - /// Determines whether the first is less than or equal to the second . - /// - /// The first to compare. - /// The second to compare. - public static bool operator <=(Number left, Number right) => left.CompareTo(right) <= 0; - - /// - public int CompareTo(Number other) - { - return this.isSigned - ? this.signedValue.CompareTo(other.signedValue) - : this.unsignedValue.CompareTo(other.unsignedValue); - } + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator ==(Number left, Number right) => Equals(left, right); - /// - public override bool Equals(object obj) => obj is Number other && this.Equals(other); + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + public static bool operator !=(Number left, Number right) => !Equals(left, right); - /// - public bool Equals(Number other) - { - if (this.isSigned != other.isSigned) - { - return false; - } - - return this.isSigned - ? this.signedValue.Equals(other.signedValue) - : this.unsignedValue.Equals(other.unsignedValue); - } + /// + /// Determines whether the first is more than the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator >(Number left, Number right) => left.CompareTo(right) == 1; - /// - public override int GetHashCode() - { - return this.isSigned - ? this.signedValue.GetHashCode() - : this.unsignedValue.GetHashCode(); - } + /// + /// Determines whether the first is less than the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator <(Number left, Number right) => left.CompareTo(right) == -1; - /// - public override string ToString() => this.ToString(CultureInfo.InvariantCulture); + /// + /// Determines whether the first is more than or equal to the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator >=(Number left, Number right) => left.CompareTo(right) >= 0; - /// - /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. - /// - /// An object that supplies culture-specific formatting information. - /// The string representation of the value of this instance, which consists of a sequence of digits ranging from 0 to 9, without a sign or leading zeros. - public string ToString(IFormatProvider provider) + /// + /// Determines whether the first is less than or equal to the second . + /// + /// The first to compare. + /// The second to compare. + public static bool operator <=(Number left, Number right) => left.CompareTo(right) <= 0; + + /// + public int CompareTo(Number other) + { + return this.isSigned + ? this.signedValue.CompareTo(other.signedValue) + : this.unsignedValue.CompareTo(other.unsignedValue); + } + + /// + public override bool Equals(object obj) => obj is Number other && this.Equals(other); + + /// + public bool Equals(Number other) + { + if (this.isSigned != other.isSigned) { - return this.isSigned - ? this.signedValue.ToString(provider) - : this.unsignedValue.ToString(provider); + return false; } + + return this.isSigned + ? this.signedValue.Equals(other.signedValue) + : this.unsignedValue.Equals(other.unsignedValue); + } + + /// + public override int GetHashCode() + { + return this.isSigned + ? this.signedValue.GetHashCode() + : this.unsignedValue.GetHashCode(); + } + + /// + public override string ToString() => this.ToString(CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent string representation using the specified culture-specific format information. + /// + /// An object that supplies culture-specific formatting information. + /// The string representation of the value of this instance, which consists of a sequence of digits ranging from 0 to 9, without a sign or leading zeros. + public string ToString(IFormatProvider provider) + { + return this.isSigned + ? this.signedValue.ToString(provider) + : this.unsignedValue.ToString(provider); } } diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs index 356595cb4d..c7f13f771d 100644 --- a/src/ImageSharp/Primitives/Point.cs +++ b/src/ImageSharp/Primitives/Point.cs @@ -1,288 +1,286 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents an ordered pair of integer x- and y-coordinates that defines a point in +/// a two-dimensional plane. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct Point : IEquatable { /// - /// Represents an ordered pair of integer x- and y-coordinates that defines a point in - /// a two-dimensional plane. + /// Represents a that has X and Y values set to zero. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Point : IEquatable + public static readonly Point Empty; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal and vertical position of the point. + public Point(int value) + : this() { - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Point Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal and vertical position of the point. - public Point(int value) - : this() - { - this.X = LowInt16(value); - this.Y = HighInt16(value); - } + this.X = LowInt16(value); + this.Y = HighInt16(value); + } - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public Point(int x, int y) - : this() - { - this.X = x; - this.Y = y; - } + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + : this() + { + this.X = x; + this.Y = y; + } - /// - /// Initializes a new instance of the struct from the given . - /// - /// The size. - public Point(Size size) - { - this.X = size.Width; - this.Y = size.Height; - } + /// + /// Initializes a new instance of the struct from the given . + /// + /// The size. + public Point(Size size) + { + this.X = size.Width; + this.Y = size.Height; + } - /// - /// Gets or sets the x-coordinate of this . - /// - public int X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator PointF(Point point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(Point point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Size(Point point) => new(point.X, point.Y); - - /// - /// Negates the given point by multiplying all values by -1. - /// - /// The source point. - /// The negated point. - public static Point operator -(Point value) => new(-value.X, -value.Y); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator +(Point point, Size size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator -(Point point, Size size) => Subtract(point, size); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static Point operator *(int left, Point right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static Point operator *(Point left, int right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static Point operator /(Point left, int right) - => new(left.X / right, left.Y / right); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Point left, Point right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Point left, Point right) => !left.Equals(right); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Add(Point point, Size size) => new(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); - - /// - /// Translates a by the negative of a given value. - /// - /// The point on the left hand of the operand. - /// The value on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Multiply(Point point, int value) => new(unchecked(point.X * value), unchecked(point.Y * value)); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Subtract(Point point, Size size) => new(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); - - /// - /// Converts a to a by performing a ceiling operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Ceiling(PointF point) => new(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); - - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Round(PointF point) => new(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); - - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The vector. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Round(Vector2 vector) => new(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); - - /// - /// Converts a to a by performing a truncate operation on all the coordinates. - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Truncate(PointF point) => new(unchecked((int)point.X), unchecked((int)point.Y)); - - /// - /// Transforms a point by a specified 3x2 matrix. - /// - /// The point to transform. - /// The transformation matrix used. - /// The transformed . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); - - /// - /// Deconstructs this point into two integers. - /// - /// The out value for X. - /// The out value for Y. - public void Deconstruct(out int x, out int y) - { - x = this.X; - y = this.Y; - } + /// + /// Gets or sets the x-coordinate of this . + /// + public int X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PointF(Point point) => new(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(Point point) => new(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(Point point) => new(point.X, point.Y); + + /// + /// Negates the given point by multiplying all values by -1. + /// + /// The source point. + /// The negated point. + public static Point operator -(Point value) => new(-value.X, -value.Y); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator +(Point point, Size size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator -(Point point, Size size) => Subtract(point, size); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static Point operator *(int left, Point right) => Multiply(right, left); - /// - /// Translates this by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(int dx, int dy) + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static Point operator *(Point left, int right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static Point operator /(Point left, int right) + => new(left.X / right, left.Y / right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Point left, Point right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Point left, Point right) => !left.Equals(right); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Add(Point point, Size size) => new(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); + + /// + /// Translates a by the negative of a given value. + /// + /// The point on the left hand of the operand. + /// The value on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Multiply(Point point, int value) => new(unchecked(point.X * value), unchecked(point.Y * value)); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Subtract(Point point, Size size) => new(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Ceiling(PointF point) => new(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(PointF point) => new(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); + + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(Vector2 vector) => new(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Truncate(PointF point) => new(unchecked((int)point.X), unchecked((int)point.Y)); + + /// + /// Transforms a point by a specified 3x2 matrix. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); + + /// + /// Deconstructs this point into two integers. + /// + /// The out value for X. + /// The out value for Y. + public void Deconstruct(out int x, out int y) + { + x = this.X; + y = this.Y; + } + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(int dx, int dy) + { + unchecked { - unchecked - { - this.X += dx; - this.Y += dy; - } + this.X += dx; + this.Y += dy; } + } - /// - /// Translates this by the specified amount. - /// - /// The used offset this . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(Point point) => this.Offset(point.X, point.Y); + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); - /// - public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; + /// + public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; - /// - public override bool Equals(object obj) => obj is Point other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Point other && this.Equals(other); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); - private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); + private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); - private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); - } + private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); } diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs index d701f3d541..a07a11582d 100644 --- a/src/ImageSharp/Primitives/PointF.cs +++ b/src/ImageSharp/Primitives/PointF.cs @@ -1,293 +1,291 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in +/// a two-dimensional plane. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct PointF : IEquatable { /// - /// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in - /// a two-dimensional plane. + /// Represents a that has X and Y values set to zero. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct PointF : IEquatable + public static readonly PointF Empty; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public PointF(float x, float y) + : this() + { + this.X = x; + this.Y = y; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The size. + public PointF(SizeF size) { - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly PointF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public PointF(float x, float y) - : this() - { - this.X = x; - this.Y = y; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The size. - public PointF(SizeF size) - { - this.X = size.Width; - this.Y = size.Height; - } - - /// - /// Gets or sets the x-coordinate of this . - /// - public float X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public float Y { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The vector. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator PointF(Vector2 vector) => new(vector.X, vector.Y); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(PointF point) => new(point.X, point.Y); - - /// - /// Creates a with the coordinates of the specified by truncating each of the coordinates. - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Point(PointF point) => Point.Truncate(point); - - /// - /// Negates the given point by multiplying all values by -1. - /// - /// The source point. - /// The negated point. - public static PointF operator -(PointF value) => new(-value.X, -value.Y); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator +(PointF point, SizeF size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator -(PointF point, PointF size) => Subtract(point, size); - - /// - /// Translates a by a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator +(PointF point, PointF size) => Add(point, size); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static PointF operator *(float left, PointF right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static PointF operator *(PointF left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static PointF operator /(PointF left, float right) - => new(left.X / right, left.Y / right); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(PointF left, PointF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(PointF left, PointF right) => !left.Equals(right); - - /// - /// Translates a by the given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Add(PointF point, SizeF size) => new(point.X + size.Width, point.Y + size.Height); - - /// - /// Translates a by the given . - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Add(PointF point, PointF pointb) => new(point.X + pointb.X, point.Y + pointb.Y); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Subtract(PointF point, SizeF size) => new(point.X - size.Width, point.Y - size.Height); - - /// - /// Translates a by the negative of a given . - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Subtract(PointF point, PointF pointb) => new(point.X - pointb.X, point.Y - pointb.Y); - - /// - /// Translates a by the multiplying the X and Y by the given value. - /// - /// The point on the left hand of the operand. - /// The value on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Multiply(PointF point, float right) => new(point.X * right, point.Y * right); - - /// - /// Transforms a point by a specified 3x2 matrix. - /// - /// The point to transform. - /// The transformation matrix used. - /// The transformed . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); - - /// - /// Deconstructs this point into two floats. - /// - /// The out value for X. - /// The out value for Y. - public void Deconstruct(out float x, out float y) - { - x = this.X; - y = this.Y; - } - - /// - /// Translates this by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(float dx, float dy) - { - this.X += dx; - this.Y += dy; - } - - /// - /// Translates this by the specified amount. - /// - /// The used offset this . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(PointF point) => this.Offset(point.X, point.Y); - - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); - - /// - public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; - - /// - public override bool Equals(object obj) => obj is PointF pointF && this.Equals(pointF); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + this.X = size.Width; + this.Y = size.Height; } + + /// + /// Gets or sets the x-coordinate of this . + /// + public float X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public float Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The vector. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PointF(Vector2 vector) => new(vector.X, vector.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(PointF point) => new(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified by truncating each of the coordinates. + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(PointF point) => Point.Truncate(point); + + /// + /// Negates the given point by multiplying all values by -1. + /// + /// The source point. + /// The negated point. + public static PointF operator -(PointF value) => new(-value.X, -value.Y); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator +(PointF point, SizeF size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator -(PointF point, PointF size) => Subtract(point, size); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator +(PointF point, PointF size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static PointF operator *(float left, PointF right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static PointF operator *(PointF left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static PointF operator /(PointF left, float right) + => new(left.X / right, left.Y / right); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(PointF left, PointF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(PointF left, PointF right) => !left.Equals(right); + + /// + /// Translates a by the given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Add(PointF point, SizeF size) => new(point.X + size.Width, point.Y + size.Height); + + /// + /// Translates a by the given . + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Add(PointF point, PointF pointb) => new(point.X + pointb.X, point.Y + pointb.Y); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Subtract(PointF point, SizeF size) => new(point.X - size.Width, point.Y - size.Height); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Subtract(PointF point, PointF pointb) => new(point.X - pointb.X, point.Y - pointb.Y); + + /// + /// Translates a by the multiplying the X and Y by the given value. + /// + /// The point on the left hand of the operand. + /// The value on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Multiply(PointF point, float right) => new(point.X * right, point.Y * right); + + /// + /// Transforms a point by a specified 3x2 matrix. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); + + /// + /// Deconstructs this point into two floats. + /// + /// The out value for X. + /// The out value for Y. + public void Deconstruct(out float x, out float y) + { + x = this.X; + y = this.Y; + } + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) + { + this.X += dx; + this.Y += dy; + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + + /// + public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; + + /// + public override bool Equals(object obj) => obj is PointF pointF && this.Equals(pointF); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); } diff --git a/src/ImageSharp/Primitives/Rational.cs b/src/ImageSharp/Primitives/Rational.cs index a258eb55ee..515af45960 100644 --- a/src/ImageSharp/Primitives/Rational.cs +++ b/src/ImageSharp/Primitives/Rational.cs @@ -1,177 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a number that can be expressed as a fraction. +/// +/// +/// This is a very simplified implementation of a rational number designed for use with metadata only. +/// +public readonly struct Rational : IEquatable { /// - /// Represents a number that can be expressed as a fraction. + /// Initializes a new instance of the struct. /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - public readonly struct Rational : IEquatable + /// The to create the rational from. + public Rational(uint value) + : this(value, 1) { - /// - /// Initializes a new instance of the struct. - /// - /// The to create the rational from. - public Rational(uint value) - : this(value, 1) - { - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public Rational(uint numerator, uint denominator) - : this(numerator, denominator, true) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public Rational(uint numerator, uint denominator) + : this(numerator, denominator, true) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public Rational(uint numerator, uint denominator, bool simplify) + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public Rational(uint numerator, uint denominator, bool simplify) + { + if (simplify) { - if (simplify) - { - LongRational rational = new LongRational(numerator, denominator).Simplify(); - - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; - } - else - { - this.Numerator = numerator; - this.Denominator = denominator; - } - } + LongRational rational = new LongRational(numerator, denominator).Simplify(); - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public Rational(double value) - : this(value, false) + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } + else { + this.Numerator = numerator; + this.Denominator = denominator; } + } - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public Rational(double value, bool bestPrecision) - { - var rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public Rational(double value) + : this(value, false) + { + } - this.Numerator = (uint)rational.Numerator; - this.Denominator = (uint)rational.Denominator; - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public Rational(double value, bool bestPrecision) + { + var rational = LongRational.FromDouble(Math.Abs(value), bestPrecision); - /// - /// Gets the numerator of a number. - /// - public uint Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public uint Denominator { get; } - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(Rational left, Rational right) => left.Equals(right); - - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(Rational left, Rational right) => !left.Equals(right); - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static Rational FromDouble(double value) => new Rational(value, false); - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static Rational FromDouble(double value, bool bestPrecision) => new Rational(value, bestPrecision); - - /// - public override bool Equals(object obj) => obj is Rational other && this.Equals(other); - - /// - public bool Equals(Rational other) - { - var left = new LongRational(this.Numerator, this.Denominator); - var right = new LongRational(other.Numerator, other.Denominator); + this.Numerator = (uint)rational.Numerator; + this.Denominator = (uint)rational.Denominator; + } - return left.Equals(right); - } + /// + /// Gets the numerator of a number. + /// + public uint Numerator { get; } - /// - public override int GetHashCode() - { - var self = new LongRational(this.Numerator, this.Denominator); - return self.GetHashCode(); - } + /// + /// Gets the denominator of a number. + /// + public uint Denominator { get; } - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() => this.Numerator / (double)this.Denominator; - - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public float ToSingle() => this.Numerator / (float)this.Denominator; - - /// - public override string ToString() => this.ToString(CultureInfo.InvariantCulture); - - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - var rational = new LongRational(this.Numerator, this.Denominator); - return rational.ToString(provider); - } + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(Rational left, Rational right) => left.Equals(right); + + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(Rational left, Rational right) => !left.Equals(right); + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static Rational FromDouble(double value) => new Rational(value, false); + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static Rational FromDouble(double value, bool bestPrecision) => new Rational(value, bestPrecision); + + /// + public override bool Equals(object obj) => obj is Rational other && this.Equals(other); + + /// + public bool Equals(Rational other) + { + var left = new LongRational(this.Numerator, this.Denominator); + var right = new LongRational(other.Numerator, other.Denominator); + + return left.Equals(right); + } + + /// + public override int GetHashCode() + { + var self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } + + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() => this.Numerator / (double)this.Denominator; + + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public float ToSingle() => this.Numerator / (float)this.Denominator; + + /// + public override string ToString() => this.ToString(CultureInfo.InvariantCulture); + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + var rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); } } diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index b21aae1fe0..7f32acae79 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -1,457 +1,455 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Stores a set of four integers that represent the location and size of a rectangle. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct Rectangle : IEquatable { /// - /// Stores a set of four integers that represent the location and size of a rectangle. + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly Rectangle Empty; + + /// + /// Initializes a new instance of the struct. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Rectangle : IEquatable + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public Rectangle(int x, int y, int width, int height) { - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly Rectangle Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public Rectangle(int x, int y, int width, int height) - { - this.X = x; - this.Y = y; - this.Width = width; - this.Height = height; - } + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public Rectangle(Point point, Size size) - { - this.X = point.X; - this.Y = point.Y; - this.Width = size.Width; - this.Height = size.Height; - } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public Rectangle(Point point, Size size) + { + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; + } - /// - /// Gets or sets the x-coordinate of this . - /// - public int X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public int Y { get; set; } - - /// - /// Gets or sets the width of this . - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public int Height { get; set; } - - /// - /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Point Location - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.X, this.Y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.X = value.X; - this.Y = value.Y; - } - } + /// + /// Gets or sets the x-coordinate of this . + /// + public int X { get; set; } - /// - /// Gets or sets the size of this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Size Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Width = value.Width; - this.Height = value.Height; - } - } + /// + /// Gets or sets the y-coordinate of this . + /// + public int Y { get; set; } - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public int Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public int Right + /// + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public int Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Point Location + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.X, this.Y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => unchecked(this.X + this.Width); + this.X = value.X; + this.Y = value.Y; } + } + + /// + /// Gets or sets the size of this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Size Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.Width, this.Height); - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public int Bottom + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => unchecked(this.Y + this.Height); + this.Width = value.Width; + this.Height = value.Height; } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); - /// - /// Gets the x-coordinate of the left edge of this . - /// - public int Left => this.X; + /// + /// Gets the y-coordinate of the top edge of this . + /// + public int Top => this.Y; - /// - /// Creates a with the coordinates of the specified . - /// - /// The rectangle. + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator RectangleF(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); + get => unchecked(this.X + this.Width); + } - /// - /// Creates a with the coordinates of the specified . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector4(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); - - /// - /// Creates a new with the specified location and size. - /// The left coordinate of the rectangle. - /// The top coordinate of the rectangle. - /// The right coordinate of the rectangle. - /// The bottom coordinate of the rectangle. - /// The . + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public int Bottom + { [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => unchecked(this.Y + this.Height); + } - // ReSharper disable once InconsistentNaming - public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new(left, top, unchecked(right - left), unchecked(bottom - top)); + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left => this.X; - /// - /// Returns the center point of the given . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Center(Rectangle rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); - - /// - /// Creates a rectangle that represents the intersection between and - /// . If there is no intersection, an empty rectangle is returned. - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Intersect(Rectangle a, Rectangle b) - { - int x1 = Math.Max(a.X, b.X); - int x2 = Math.Min(a.Right, b.Right); - int y1 = Math.Max(a.Y, b.Y); - int y2 = Math.Min(a.Bottom, b.Bottom); + /// + /// Creates a with the coordinates of the specified . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator RectangleF(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); - if (x2 >= x1 && y2 >= y1) - { - return new Rectangle(x1, y1, x2 - x1, y2 - y1); - } + /// + /// Creates a with the coordinates of the specified . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector4(Rectangle rectangle) => new(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); - return Empty; - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); - /// - /// Creates a that is inflated by the specified amount. - /// - /// The rectangle. - /// The amount to inflate the width by. - /// The amount to inflate the height by. - /// A new . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Inflate(Rectangle rectangle, int x, int y) - { - Rectangle r = rectangle; - r.Inflate(x, y); - return r; - } + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); - /// - /// Converts a to a by performing a ceiling operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Ceiling(RectangleF rectangle) + /// + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle. + /// The top coordinate of the rectangle. + /// The right coordinate of the rectangle. + /// The bottom coordinate of the rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + // ReSharper disable once InconsistentNaming + public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new(left, top, unchecked(right - left), unchecked(bottom - top)); + + /// + /// Returns the center point of the given . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Center(Rectangle rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + + /// + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Intersect(Rectangle a, Rectangle b) + { + int x1 = Math.Max(a.X, b.X); + int x2 = Math.Min(a.Right, b.Right); + int y1 = Math.Max(a.Y, b.Y); + int y2 = Math.Min(a.Bottom, b.Bottom); + + if (x2 >= x1 && y2 >= y1) { - unchecked - { - return new Rectangle( - (int)MathF.Ceiling(rectangle.X), - (int)MathF.Ceiling(rectangle.Y), - (int)MathF.Ceiling(rectangle.Width), - (int)MathF.Ceiling(rectangle.Height)); - } + return new Rectangle(x1, y1, x2 - x1, y2 - y1); } - /// - /// Transforms a rectangle by the given matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// A transformed rectangle. - public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) + return Empty; + } + + /// + /// Creates a that is inflated by the specified amount. + /// + /// The rectangle. + /// The amount to inflate the width by. + /// The amount to inflate the height by. + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Inflate(Rectangle rectangle, int x, int y) + { + Rectangle r = rectangle; + r.Inflate(x, y); + return r; + } + + /// + /// Converts a to a by performing a ceiling operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Ceiling(RectangleF rectangle) + { + unchecked { - PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = Point.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + return new Rectangle( + (int)MathF.Ceiling(rectangle.X), + (int)MathF.Ceiling(rectangle.Y), + (int)MathF.Ceiling(rectangle.Width), + (int)MathF.Ceiling(rectangle.Height)); } + } - /// - /// Converts a to a by performing a truncate operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Truncate(RectangleF rectangle) + /// + /// Transforms a rectangle by the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed rectangle. + public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) + { + PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = Point.Transform(rectangle.Location, matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } + + /// + /// Converts a to a by performing a truncate operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Truncate(RectangleF rectangle) + { + unchecked { - unchecked - { - return new Rectangle( - (int)rectangle.X, - (int)rectangle.Y, - (int)rectangle.Width, - (int)rectangle.Height); - } + return new Rectangle( + (int)rectangle.X, + (int)rectangle.Y, + (int)rectangle.Width, + (int)rectangle.Height); } + } - /// - /// Converts a to a by performing a round operation on all the coordinates. - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Round(RectangleF rectangle) + /// + /// Converts a to a by performing a round operation on all the coordinates. + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Round(RectangleF rectangle) + { + unchecked { - unchecked - { - return new Rectangle( - (int)MathF.Round(rectangle.X), - (int)MathF.Round(rectangle.Y), - (int)MathF.Round(rectangle.Width), - (int)MathF.Round(rectangle.Height)); - } + return new Rectangle( + (int)MathF.Round(rectangle.X), + (int)MathF.Round(rectangle.Y), + (int)MathF.Round(rectangle.Width), + (int)MathF.Round(rectangle.Height)); } + } - /// - /// Creates a rectangle that represents the union between and . - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle Union(Rectangle a, Rectangle b) - { - int x1 = Math.Min(a.X, b.X); - int x2 = Math.Max(a.Right, b.Right); - int y1 = Math.Min(a.Y, b.Y); - int y2 = Math.Max(a.Bottom, b.Bottom); + /// + /// Creates a rectangle that represents the union between and . + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Union(Rectangle a, Rectangle b) + { + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.Right, b.Right); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Bottom, b.Bottom); - return new Rectangle(x1, y1, x2 - x1, y2 - y1); - } + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } - /// - /// Deconstructs this rectangle into four integers. - /// - /// The out value for X. - /// The out value for Y. - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out int x, out int y, out int width, out int height) - { - x = this.X; - y = this.Y; - width = this.Width; - height = this.Height; - } + /// + /// Deconstructs this rectangle into four integers. + /// + /// The out value for X. + /// The out value for Y. + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out int x, out int y, out int width, out int height) + { + x = this.X; + y = this.Y; + width = this.Width; + height = this.Height; + } - /// - /// Creates a Rectangle that represents the intersection between this Rectangle and the . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Intersect(Rectangle rectangle) - { - Rectangle result = Intersect(rectangle, this); + /// + /// Creates a Rectangle that represents the intersection between this Rectangle and the . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(Rectangle rectangle) + { + Rectangle result = Intersect(rectangle, this); - this.X = result.X; - this.Y = result.Y; - this.Width = result.Width; - this.Height = result.Height; - } + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; + } - /// - /// Inflates this by the specified amount. - /// - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(int width, int height) + /// + /// Inflates this by the specified amount. + /// + /// The width. + /// The height. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(int width, int height) + { + unchecked { - unchecked - { - this.X -= width; - this.Y -= height; - - this.Width += 2 * width; - this.Height += 2 * height; - } + this.X -= width; + this.Y -= height; + + this.Width += 2 * width; + this.Height += 2 * height; } + } - /// - /// Inflates this by the specified amount. - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(Size size) => this.Inflate(size.Width, size.Height); - - /// - /// Determines if the specified point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + /// + /// Inflates this by the specified amount. + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(Size size) => this.Inflate(size.Width, size.Height); - /// - /// Determines if the specified point is contained within the rectangular region defined by this . - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Point point) => this.Contains(point.X, point.Y); - - /// - /// Determines if the rectangular region represented by is entirely contained - /// within the rectangular region represented by this . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Rectangle rectangle) => - (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && - (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); - - /// - /// Determines if the specified intersects the rectangular region defined by - /// this . - /// - /// The other Rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IntersectsWith(Rectangle rectangle) => - (rectangle.X < this.Right) && (this.X < rectangle.Right) && - (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(Point point) => this.Offset(point.X, point.Y); + /// + /// Determines if the specified point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(int dx, int dy) + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Point point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Rectangle rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); + + /// + /// Determines if the specified intersects the rectangular region defined by + /// this . + /// + /// The other Rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(Rectangle rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(int dx, int dy) + { + unchecked { - unchecked - { - this.X += dx; - this.Y += dy; - } + this.X += dx; + this.Y += dy; } + } - /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Width, this.Height); + /// + public override int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Width, this.Height); - /// - public override string ToString() => $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + /// + public override string ToString() => $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; - /// - public override bool Equals(object obj) => obj is Rectangle other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is Rectangle other && this.Equals(other); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rectangle other) => - this.X.Equals(other.X) && - this.Y.Equals(other.Y) && - this.Width.Equals(other.Width) && - this.Height.Equals(other.Height); - } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rectangle other) => + this.X.Equals(other.X) && + this.Y.Equals(other.Y) && + this.Width.Equals(other.Width) && + this.Height.Equals(other.Height); } diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs index 9719d2b7e3..a8a584ba5f 100644 --- a/src/ImageSharp/Primitives/RectangleF.cs +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -1,392 +1,390 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Stores a set of four single precision floating points that represent the location and size of a rectangle. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct RectangleF : IEquatable { /// - /// Stores a set of four single precision floating points that represent the location and size of a rectangle. + /// Represents a that has X, Y, Width, and Height values set to zero. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct RectangleF : IEquatable + public static readonly RectangleF Empty; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public RectangleF(float x, float y, float width, float height) { - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly RectangleF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public RectangleF(float x, float y, float width, float height) - { - this.X = x; - this.Y = y; - this.Width = width; - this.Height = height; - } + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; + } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public RectangleF(PointF point, SizeF size) - { - this.X = point.X; - this.Y = point.Y; - this.Width = size.Width; - this.Height = size.Height; - } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public RectangleF(PointF point, SizeF size) + { + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; + } - /// - /// Gets or sets the x-coordinate of this . - /// - public float X { get; set; } - - /// - /// Gets or sets the y-coordinate of this . - /// - public float Y { get; set; } - - /// - /// Gets or sets the width of this . - /// - public float Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public float Height { get; set; } - - /// - /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public PointF Location - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.X, this.Y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.X = value.X; - this.Y = value.Y; - } - } + /// + /// Gets or sets the x-coordinate of this . + /// + public float X { get; set; } - /// - /// Gets or sets the size of this . - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public SizeF Size - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.Width, this.Height); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.Width = value.Width; - this.Height = value.Height; - } - } + /// + /// Gets or sets the y-coordinate of this . + /// + public float Y { get; set; } + + /// + /// Gets or sets the width of this . + /// + public float Width { get; set; } - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public float Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public float Right + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public PointF Location + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.X, this.Y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.X + this.Width; + this.X = value.X; + this.Y = value.Y; } + } + + /// + /// Gets or sets the size of this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public SizeF Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.Width, this.Height); - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public float Bottom + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.Y + this.Height; + this.Width = value.Width; + this.Height = value.Height; } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); - /// - /// Gets the x-coordinate of the left edge of this . - /// - public float Left => this.X; + /// + /// Gets the y-coordinate of the top edge of this . + /// + public float Top => this.Y; - /// - /// Creates a with the coordinates of the specified by truncating each coordinate. - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// + /// + /// Gets the x-coordinate of the right edge of this . + /// + public float Right + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); - - /// - /// Creates a new with the specified location and size. - /// The left coordinate of the rectangle. - /// The top coordinate of the rectangle. - /// The right coordinate of the rectangle. - /// The bottom coordinate of the rectangle. - /// The . + get => this.X + this.Width; + } + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public float Bottom + { [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.Y + this.Height; + } - // ReSharper disable once InconsistentNaming - public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new(left, top, right - left, bottom - top); + /// + /// Gets the x-coordinate of the left edge of this . + /// + public float Left => this.X; - /// - /// Returns the center point of the given . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PointF Center(RectangleF rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); - - /// - /// Creates a rectangle that represents the intersection between and - /// . If there is no intersection, an empty rectangle is returned. - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Intersect(RectangleF a, RectangleF b) - { - float x1 = MathF.Max(a.X, b.X); - float x2 = MathF.Min(a.Right, b.Right); - float y1 = MathF.Max(a.Y, b.Y); - float y2 = MathF.Min(a.Bottom, b.Bottom); + /// + /// Creates a with the coordinates of the specified by truncating each coordinate. + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); - if (x2 >= x1 && y2 >= y1) - { - return new RectangleF(x1, y1, x2 - x1, y2 - y1); - } + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); - return Empty; - } + /// + /// Compares two objects for inequality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); - /// - /// Creates a that is inflated by the specified amount. - /// - /// The rectangle. - /// The amount to inflate the width by. - /// The amount to inflate the height by. - /// A new . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Inflate(RectangleF rectangle, float x, float y) - { - RectangleF r = rectangle; - r.Inflate(x, y); - return r; - } + /// + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle. + /// The top coordinate of the rectangle. + /// The right coordinate of the rectangle. + /// The bottom coordinate of the rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] - /// - /// Transforms a rectangle by the given matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// A transformed . - public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) - { - PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = PointF.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } + // ReSharper disable once InconsistentNaming + public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new(left, top, right - left, bottom - top); - /// - /// Creates a rectangle that represents the union between and . - /// - /// The first rectangle. - /// The second rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RectangleF Union(RectangleF a, RectangleF b) - { - float x1 = MathF.Min(a.X, b.X); - float x2 = MathF.Max(a.Right, b.Right); - float y1 = MathF.Min(a.Y, b.Y); - float y2 = MathF.Max(a.Bottom, b.Bottom); + /// + /// Returns the center point of the given . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Center(RectangleF rectangle) => new(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); - return new RectangleF(x1, y1, x2 - x1, y2 - y1); - } + /// + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Intersect(RectangleF a, RectangleF b) + { + float x1 = MathF.Max(a.X, b.X); + float x2 = MathF.Min(a.Right, b.Right); + float y1 = MathF.Max(a.Y, b.Y); + float y2 = MathF.Min(a.Bottom, b.Bottom); - /// - /// Deconstructs this rectangle into four floats. - /// - /// The out value for X. - /// The out value for Y. - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out float x, out float y, out float width, out float height) + if (x2 >= x1 && y2 >= y1) { - x = this.X; - y = this.Y; - width = this.Width; - height = this.Height; + return new RectangleF(x1, y1, x2 - x1, y2 - y1); } - /// - /// Creates a RectangleF that represents the intersection between this RectangleF and the . - /// - /// The rectangle. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Intersect(RectangleF rectangle) - { - RectangleF result = Intersect(rectangle, this); + return Empty; + } - this.X = result.X; - this.Y = result.Y; - this.Width = result.Width; - this.Height = result.Height; - } + /// + /// Creates a that is inflated by the specified amount. + /// + /// The rectangle. + /// The amount to inflate the width by. + /// The amount to inflate the height by. + /// A new . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Inflate(RectangleF rectangle, float x, float y) + { + RectangleF r = rectangle; + r.Inflate(x, y); + return r; + } - /// - /// Inflates this by the specified amount. - /// - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(float width, float height) - { - this.X -= width; - this.Y -= height; + /// + /// Transforms a rectangle by the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed . + public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) + { + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + PointF topLeft = PointF.Transform(rectangle.Location, matrix); + return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + } - this.Width += 2 * width; - this.Height += 2 * height; - } + /// + /// Creates a rectangle that represents the union between and . + /// + /// The first rectangle. + /// The second rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Union(RectangleF a, RectangleF b) + { + float x1 = MathF.Min(a.X, b.X); + float x2 = MathF.Max(a.Right, b.Right); + float y1 = MathF.Min(a.Y, b.Y); + float y2 = MathF.Max(a.Bottom, b.Bottom); - /// - /// Inflates this by the specified amount. - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + return new RectangleF(x1, y1, x2 - x1, y2 - y1); + } - /// - /// Determines if the specified point is contained within the rectangular region defined by this . - /// - /// The point. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(PointF point) => this.Contains(point.X, point.Y); - - /// - /// Determines if the rectangular region represented by is entirely contained - /// within the rectangular region represented by this . - /// - /// The rectangle. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(RectangleF rectangle) => - (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && - (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); - - /// - /// Determines if the specfied intersects the rectangular region defined by - /// this . - /// - /// The other Rectange. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IntersectsWith(RectangleF rectangle) => - (rectangle.X < this.Right) && (this.X < rectangle.Right) && - (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); - - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(PointF point) => this.Offset(point.X, point.Y); + /// + /// Deconstructs this rectangle into four floats. + /// + /// The out value for X. + /// The out value for Y. + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out float x, out float y, out float width, out float height) + { + x = this.X; + y = this.Y; + width = this.Width; + height = this.Height; + } - /// - /// Adjusts the location of this rectangle by the specified amount. - /// - /// The amount to offset the x-coordinate. - /// The amount to offset the y-coordinate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Offset(float dx, float dy) - { - this.X += dx; - this.Y += dy; - } + /// + /// Creates a RectangleF that represents the intersection between this RectangleF and the . + /// + /// The rectangle. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(RectangleF rectangle) + { + RectangleF result = Intersect(rectangle, this); - /// - public override int GetHashCode() - => HashCode.Combine(this.X, this.Y, this.Width, this.Height); + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; + } - /// - public override string ToString() - => $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + /// + /// Inflates this by the specified amount. + /// + /// The width. + /// The height. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(float width, float height) + { + this.X -= width; + this.Y -= height; - /// - public override bool Equals(object obj) => obj is RectangleF other && this.Equals(other); + this.Width += 2 * width; + this.Height += 2 * height; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(RectangleF other) => - this.X.Equals(other.X) && - this.Y.Equals(other.Y) && - this.Width.Equals(other.Width) && - this.Height.Equals(other.Height); + /// + /// Inflates this by the specified amount. + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(PointF point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(RectangleF rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); + + /// + /// Determines if the specfied intersects the rectangular region defined by + /// this . + /// + /// The other Rectange. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(RectangleF rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) + { + this.X += dx; + this.Y += dy; } + + /// + public override int GetHashCode() + => HashCode.Combine(this.X, this.Y, this.Width, this.Height); + + /// + public override string ToString() + => $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + + /// + public override bool Equals(object obj) => obj is RectangleF other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(RectangleF other) => + this.X.Equals(other.X) && + this.Y.Equals(other.Y) && + this.Width.Equals(other.Width) && + this.Height.Equals(other.Height); } diff --git a/src/ImageSharp/Primitives/SignedRational.cs b/src/ImageSharp/Primitives/SignedRational.cs index 9fda322645..25dc70441f 100644 --- a/src/ImageSharp/Primitives/SignedRational.cs +++ b/src/ImageSharp/Primitives/SignedRational.cs @@ -1,190 +1,188 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Represents a number that can be expressed as a fraction. +/// +/// +/// This is a very simplified implementation of a rational number designed for use with metadata only. +/// +public readonly struct SignedRational : IEquatable { /// - /// Represents a number that can be expressed as a fraction. + /// Initializes a new instance of the struct. /// - /// - /// This is a very simplified implementation of a rational number designed for use with metadata only. - /// - public readonly struct SignedRational : IEquatable + /// The to create the rational from. + public SignedRational(int value) + : this(value, 1) { - /// - /// Initializes a new instance of the struct. - /// - /// The to create the rational from. - public SignedRational(int value) - : this(value, 1) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - public SignedRational(int numerator, int denominator) - : this(numerator, denominator, true) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. - /// The number below the line in a vulgar fraction; a divisor. - /// Specified if the rational should be simplified. - public SignedRational(int numerator, int denominator, bool simplify) - { - if (simplify) - { - var rational = new LongRational(numerator, denominator).Simplify(); - - this.Numerator = (int)rational.Numerator; - this.Denominator = (int)rational.Denominator; - } - else - { - this.Numerator = numerator; - this.Denominator = denominator; - } - } + } - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - public SignedRational(double value) - : this(value, false) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + public SignedRational(int numerator, int denominator) + : this(numerator, denominator, true) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The to create the instance from. - /// Whether to use the best possible precision when parsing the value. - public SignedRational(double value, bool bestPrecision) + /// + /// Initializes a new instance of the struct. + /// + /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken. + /// The number below the line in a vulgar fraction; a divisor. + /// Specified if the rational should be simplified. + public SignedRational(int numerator, int denominator, bool simplify) + { + if (simplify) { - var rational = LongRational.FromDouble(value, bestPrecision); + var rational = new LongRational(numerator, denominator).Simplify(); this.Numerator = (int)rational.Numerator; this.Denominator = (int)rational.Denominator; } - - /// - /// Gets the numerator of a number. - /// - public int Numerator { get; } - - /// - /// Gets the denominator of a number. - /// - public int Denominator { get; } - - /// - /// Determines whether the specified instances are considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator ==(SignedRational left, SignedRational right) + else { - return left.Equals(right); + this.Numerator = numerator; + this.Denominator = denominator; } + } - /// - /// Determines whether the specified instances are not considered equal. - /// - /// The first to compare. - /// The second to compare. - /// The - public static bool operator !=(SignedRational left, SignedRational right) - { - return !left.Equals(right); - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + public SignedRational(double value) + : this(value, false) + { + } - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// - /// The . - /// - public static SignedRational FromDouble(double value) - { - return new SignedRational(value, false); - } + /// + /// Initializes a new instance of the struct. + /// + /// The to create the instance from. + /// Whether to use the best possible precision when parsing the value. + public SignedRational(double value, bool bestPrecision) + { + var rational = LongRational.FromDouble(value, bestPrecision); - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert to an instance of this type. - /// Whether to use the best possible precision when parsing the value. - /// - /// The . - /// - public static SignedRational FromDouble(double value, bool bestPrecision) - { - return new SignedRational(value, bestPrecision); - } + this.Numerator = (int)rational.Numerator; + this.Denominator = (int)rational.Denominator; + } - /// - public override bool Equals(object obj) - { - return obj is SignedRational other && this.Equals(other); - } + /// + /// Gets the numerator of a number. + /// + public int Numerator { get; } - /// - public bool Equals(SignedRational other) - { - var left = new LongRational(this.Numerator, this.Denominator); - var right = new LongRational(other.Numerator, other.Denominator); + /// + /// Gets the denominator of a number. + /// + public int Denominator { get; } - return left.Equals(right); - } + /// + /// Determines whether the specified instances are considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator ==(SignedRational left, SignedRational right) + { + return left.Equals(right); + } - /// - public override int GetHashCode() - { - var self = new LongRational(this.Numerator, this.Denominator); - return self.GetHashCode(); - } + /// + /// Determines whether the specified instances are not considered equal. + /// + /// The first to compare. + /// The second to compare. + /// The + public static bool operator !=(SignedRational left, SignedRational right) + { + return !left.Equals(right); + } - /// - /// Converts a rational number to the nearest . - /// - /// - /// The . - /// - public double ToDouble() - { - return this.Numerator / (double)this.Denominator; - } + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// + /// The . + /// + public static SignedRational FromDouble(double value) + { + return new SignedRational(value, false); + } - /// - public override string ToString() - { - return this.ToString(CultureInfo.InvariantCulture); - } + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert to an instance of this type. + /// Whether to use the best possible precision when parsing the value. + /// + /// The . + /// + public static SignedRational FromDouble(double value, bool bestPrecision) + { + return new SignedRational(value, bestPrecision); + } - /// - /// Converts the numeric value of this instance to its equivalent string representation using - /// the specified culture-specific format information. - /// - /// - /// An object that supplies culture-specific formatting information. - /// - /// The - public string ToString(IFormatProvider provider) - { - var rational = new LongRational(this.Numerator, this.Denominator); - return rational.ToString(provider); - } + /// + public override bool Equals(object obj) + { + return obj is SignedRational other && this.Equals(other); + } + + /// + public bool Equals(SignedRational other) + { + var left = new LongRational(this.Numerator, this.Denominator); + var right = new LongRational(other.Numerator, other.Denominator); + + return left.Equals(right); + } + + /// + public override int GetHashCode() + { + var self = new LongRational(this.Numerator, this.Denominator); + return self.GetHashCode(); + } + + /// + /// Converts a rational number to the nearest . + /// + /// + /// The . + /// + public double ToDouble() + { + return this.Numerator / (double)this.Denominator; + } + + /// + public override string ToString() + { + return this.ToString(CultureInfo.InvariantCulture); + } + + /// + /// Converts the numeric value of this instance to its equivalent string representation using + /// the specified culture-specific format information. + /// + /// + /// An object that supplies culture-specific formatting information. + /// + /// The + public string ToString(IFormatProvider provider) + { + var rational = new LongRational(this.Numerator, this.Denominator); + return rational.ToString(provider); } } diff --git a/src/ImageSharp/Primitives/Size.cs b/src/ImageSharp/Primitives/Size.cs index 0e55b6845e..8287653fdc 100644 --- a/src/ImageSharp/Primitives/Size.cs +++ b/src/ImageSharp/Primitives/Size.cs @@ -1,296 +1,294 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Stores an ordered pair of integers, which specify a height and width. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct Size : IEquatable { /// - /// Stores an ordered pair of integers, which specify a height and width. + /// Represents a that has Width and Height values set to zero. + /// + public static readonly Size Empty; + + /// + /// Initializes a new instance of the struct. + /// + /// The width and height of the size. + public Size(int value) + : this() + { + this.Width = value; + this.Height = value; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public Size(int width, int height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size. + public Size(Size size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The point. + public Size(Point point) + { + this.Width = point.X; + this.Height = point.Y; + } + + /// + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public int Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the dimensions of the specified . + /// + /// The point. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height); + + /// + /// Converts the given into a . + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(Size size) => new Point(size.Width, size.Height); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator +(Size left, Size right) => Add(left, right); + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size operator -(Size left, Size right) => Subtract(left, right); + + /// + /// Multiplies a by an producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static Size operator *(int left, Size right) => Multiply(right, left); + + /// + /// Multiplies by an producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static Size operator *(Size left, int right) => Multiply(left, right); + + /// + /// Divides by an producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static Size operator /(Size left, int right) => new Size(unchecked(left.Width / right), unchecked(left.Height / right)); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static SizeF operator *(float left, Size right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static SizeF operator *(Size left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static SizeF operator /(Size left, float right) + => new SizeF(left.Width / right, left.Height / right); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Size left, Size right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Size left, Size right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); + + /// + /// Contracts a by another . + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the dimensions. /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Size : IEquatable + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); + + /// + /// Transforms a size by the given matrix. + /// + /// The source size. + /// The transformation matrix. + /// A transformed size. + public static SizeF Transform(Size size, Matrix3x2 matrix) { - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly Size Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The width and height of the size. - public Size(int value) - : this() - { - this.Width = value; - this.Height = value; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public Size(int width, int height) - { - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The size. - public Size(Size size) - : this() - { - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The point. - public Size(Point point) - { - this.Width = point.X; - this.Height = point.Y; - } - - /// - /// Gets or sets the width of this . - /// - public int Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public int Height { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the dimensions of the specified . - /// - /// The point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height); - - /// - /// Converts the given into a . - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Point(Size size) => new Point(size.Width, size.Height); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator +(Size left, Size right) => Add(left, right); - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator -(Size left, Size right) => Subtract(left, right); - - /// - /// Multiplies a by an producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static Size operator *(int left, Size right) => Multiply(right, left); - - /// - /// Multiplies by an producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static Size operator *(Size left, int right) => Multiply(left, right); - - /// - /// Divides by an producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static Size operator /(Size left, int right) => new Size(unchecked(left.Width / right), unchecked(left.Height / right)); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static SizeF operator *(float left, Size right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static SizeF operator *(Size left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static SizeF operator /(Size left, float right) - => new SizeF(left.Width / right, left.Height / right); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Size left, Size right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Size left, Size right) => !left.Equals(right); - - /// - /// Performs vector addition of two objects. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); - - /// - /// Contracts a by another . - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); - - /// - /// Converts a to a by performing a ceiling operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); - - /// - /// Converts a to a by performing a round operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); - - /// - /// Transforms a size by the given matrix. - /// - /// The source size. - /// The transformation matrix. - /// A transformed size. - public static SizeF Transform(Size size, Matrix3x2 matrix) - { - var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); - - return new SizeF(v.X, v.Y); - } - - /// - /// Converts a to a by performing a round operation on all the dimensions. - /// - /// The size. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); - - /// - /// Deconstructs this size into two integers. - /// - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out int width, out int height) - { - width = this.Width; - height = this.Height; - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); - - /// - public override string ToString() => $"Size [ Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object obj) => obj is Size other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Size other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); - - /// - /// Multiplies by an producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - private static Size Multiply(Size size, int multiplier) => - new Size(unchecked(size.Width * multiplier), unchecked(size.Height * multiplier)); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type SizeF. - private static SizeF Multiply(Size size, float multiplier) => - new SizeF(size.Width * multiplier, size.Height * multiplier); + var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); + + return new SizeF(v.X, v.Y); } + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); + + /// + /// Deconstructs this size into two integers. + /// + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out int width, out int height) + { + width = this.Width; + height = this.Height; + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); + + /// + public override string ToString() => $"Size [ Width={this.Width}, Height={this.Height} ]"; + + /// + public override bool Equals(object obj) => obj is Size other && this.Equals(other); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Size other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + + /// + /// Multiplies by an producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + private static Size Multiply(Size size, int multiplier) => + new Size(unchecked(size.Width * multiplier), unchecked(size.Height * multiplier)); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type SizeF. + private static SizeF Multiply(Size size, float multiplier) => + new SizeF(size.Width * multiplier, size.Height * multiplier); } diff --git a/src/ImageSharp/Primitives/SizeF.cs b/src/ImageSharp/Primitives/SizeF.cs index fed62e5120..818ddf180a 100644 --- a/src/ImageSharp/Primitives/SizeF.cs +++ b/src/ImageSharp/Primitives/SizeF.cs @@ -1,233 +1,231 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Stores an ordered pair of single precision floating points, which specify a height and width. +/// +/// +/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, +/// as it avoids the need to create new values for modification operations. +/// +public struct SizeF : IEquatable { /// - /// Stores an ordered pair of single precision floating points, which specify a height and width. + /// Represents a that has Width and Height values set to zero. + /// + public static readonly SizeF Empty; + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public SizeF(float width, float height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size. + public SizeF(SizeF size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct SizeF : IEquatable + /// The point. + public SizeF(PointF point) { - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly SizeF Empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public SizeF(float width, float height) - { - this.Width = width; - this.Height = height; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The size. - public SizeF(SizeF size) - : this() - { - this.Width = size.Width; - this.Height = size.Height; - } - - /// - /// Initializes a new instance of the struct from the given . - /// - /// The point. - public SizeF(PointF point) - { - this.Width = point.X; - this.Height = point.Y; - } - - /// - /// Gets or sets the width of this . - /// - public float Width { get; set; } - - /// - /// Gets or sets the height of this . - /// - public float Height { get; set; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Creates a with the coordinates of the specified . - /// - /// The point. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Vector2(SizeF point) => new(point.Width, point.Height); - - /// - /// Creates a with the dimensions of the specified by truncating each of the dimensions. - /// - /// The size. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Size(SizeF size) => new(unchecked((int)size.Width), unchecked((int)size.Height)); - - /// - /// Converts the given into a . - /// - /// The size. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator PointF(SizeF size) => new(size.Width, size.Height); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); - - /// - /// Multiplies by a producing . - /// - /// Multiplier of type . - /// Multiplicand of type . - /// Product of type . - public static SizeF operator *(float left, SizeF right) => Multiply(right, left); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type . - public static SizeF operator *(SizeF left, float right) => Multiply(left, right); - - /// - /// Divides by a producing . - /// - /// Dividend of type . - /// Divisor of type . - /// Result of type . - public static SizeF operator /(SizeF left, float right) - => new(left.Width / right, left.Height / right); - - /// - /// Compares two objects for equality. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); - - /// - /// Compares two objects for inequality. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); - - /// - /// Performs vector addition of two objects. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF Add(SizeF left, SizeF right) => new(left.Width + right.Width, left.Height + right.Height); - - /// - /// Contracts a by another . - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SizeF Subtract(SizeF left, SizeF right) => new(left.Width - right.Width, left.Height - right.Height); - - /// - /// Transforms a size by the given matrix. - /// - /// The source size. - /// The transformation matrix. - /// A transformed size. - public static SizeF Transform(SizeF size, Matrix3x2 matrix) - { - var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); - - return new SizeF(v.X, v.Y); - } - - /// - /// Deconstructs this size into two floats. - /// - /// The out value for the width. - /// The out value for the height. - public void Deconstruct(out float width, out float height) - { - width = this.Width; - height = this.Height; - } - - /// - public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); - - /// - public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; - - /// - public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); - - /// - /// Multiplies by a producing . - /// - /// Multiplicand of type . - /// Multiplier of type . - /// Product of type SizeF. - private static SizeF Multiply(SizeF size, float multiplier) => - new(size.Width * multiplier, size.Height * multiplier); + this.Width = point.X; + this.Height = point.Y; } + + /// + /// Gets or sets the width of this . + /// + public float Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(SizeF point) => new(point.Width, point.Height); + + /// + /// Creates a with the dimensions of the specified by truncating each of the dimensions. + /// + /// The size. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(SizeF size) => new(unchecked((int)size.Width), unchecked((int)size.Height)); + + /// + /// Converts the given into a . + /// + /// The size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator PointF(SizeF size) => new(size.Width, size.Height); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); + + /// + /// Multiplies by a producing . + /// + /// Multiplier of type . + /// Multiplicand of type . + /// Product of type . + public static SizeF operator *(float left, SizeF right) => Multiply(right, left); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type . + public static SizeF operator *(SizeF left, float right) => Multiply(left, right); + + /// + /// Divides by a producing . + /// + /// Dividend of type . + /// Divisor of type . + /// Result of type . + public static SizeF operator /(SizeF left, float right) + => new(left.Width / right, left.Height / right); + + /// + /// Compares two objects for equality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Add(SizeF left, SizeF right) => new(left.Width + right.Width, left.Height + right.Height); + + /// + /// Contracts a by another . + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Subtract(SizeF left, SizeF right) => new(left.Width - right.Width, left.Height - right.Height); + + /// + /// Transforms a size by the given matrix. + /// + /// The source size. + /// The transformation matrix. + /// A transformed size. + public static SizeF Transform(SizeF size, Matrix3x2 matrix) + { + var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix); + + return new SizeF(v.X, v.Y); + } + + /// + /// Deconstructs this size into two floats. + /// + /// The out value for the width. + /// The out value for the height. + public void Deconstruct(out float width, out float height) + { + width = this.Width; + height = this.Height; + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); + + /// + public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; + + /// + public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + + /// + /// Multiplies by a producing . + /// + /// Multiplicand of type . + /// Multiplier of type . + /// Product of type SizeF. + private static SizeF Multiply(SizeF size, float multiplier) => + new(size.Width * multiplier, size.Height * multiplier); } diff --git a/src/ImageSharp/Primitives/ValueSize.cs b/src/ImageSharp/Primitives/ValueSize.cs index e54619f659..e3034d8a6f 100644 --- a/src/ImageSharp/Primitives/ValueSize.cs +++ b/src/ImageSharp/Primitives/ValueSize.cs @@ -1,133 +1,130 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp; -namespace SixLabors.ImageSharp +/// +/// Represents a value in relation to a value on the image. +/// +internal readonly struct ValueSize : IEquatable { /// - /// Represents a value in relation to a value on the image. + /// Initializes a new instance of the struct. /// - internal readonly struct ValueSize : IEquatable + /// The value. + /// The type. + public ValueSize(float value, ValueSizeType type) { - /// - /// Initializes a new instance of the struct. - /// - /// The value. - /// The type. - public ValueSize(float value, ValueSizeType type) + if (type != ValueSizeType.Absolute) { - if (type != ValueSizeType.Absolute) - { - Guard.MustBeBetweenOrEqualTo(value, 0, 1, nameof(value)); - } - - this.Value = value; - this.Type = type; + Guard.MustBeBetweenOrEqualTo(value, 0, 1, nameof(value)); } - /// - /// Enumerates the different value types. - /// - public enum ValueSizeType - { - /// - /// The value is the final return value. - /// - Absolute, - - /// - /// The value is a percentage of the image width. - /// - PercentageOfWidth, - - /// - /// The value is a percentage of the images height. - /// - PercentageOfHeight - } + this.Value = value; + this.Type = type; + } + /// + /// Enumerates the different value types. + /// + public enum ValueSizeType + { /// - /// Gets the value. + /// The value is the final return value. /// - public float Value { get; } + Absolute, /// - /// Gets the type. + /// The value is a percentage of the image width. /// - public ValueSizeType Type { get; } + PercentageOfWidth, /// - /// Implicitly converts a float into an absolute value. + /// The value is a percentage of the images height. /// - /// the value to use as the absolute figure. - public static implicit operator ValueSize(float f) => Absolute(f); + PercentageOfHeight + } - /// - /// Create a new ValueSize with as a PercentageOfWidth type with value set to percentage. - /// - /// The percentage. - /// a Values size with type PercentageOfWidth - public static ValueSize PercentageOfWidth(float percentage) - { - return new ValueSize(percentage, ValueSizeType.PercentageOfWidth); - } + /// + /// Gets the value. + /// + public float Value { get; } - /// - /// Create a new ValueSize with as a PercentageOfHeight type with value set to percentage. - /// - /// The percentage. - /// a Values size with type PercentageOfHeight - public static ValueSize PercentageOfHeight(float percentage) - { - return new ValueSize(percentage, ValueSizeType.PercentageOfHeight); - } + /// + /// Gets the type. + /// + public ValueSizeType Type { get; } - /// - /// Create a new ValueSize with as a Absolute type with value set to value. - /// - /// The value. - /// a Values size with type Absolute. - public static ValueSize Absolute(float value) - { - return new ValueSize(value, ValueSizeType.Absolute); - } + /// + /// Implicitly converts a float into an absolute value. + /// + /// the value to use as the absolute figure. + public static implicit operator ValueSize(float f) => Absolute(f); - /// - /// Calculates the specified size. - /// - /// The size. - /// The calculated value. - public float Calculate(Size size) - { - switch (this.Type) - { - case ValueSizeType.PercentageOfWidth: - return this.Value * size.Width; - case ValueSizeType.PercentageOfHeight: - return this.Value * size.Height; - case ValueSizeType.Absolute: - default: - return this.Value; - } - } + /// + /// Create a new ValueSize with as a PercentageOfWidth type with value set to percentage. + /// + /// The percentage. + /// a Values size with type PercentageOfWidth + public static ValueSize PercentageOfWidth(float percentage) + { + return new ValueSize(percentage, ValueSizeType.PercentageOfWidth); + } - /// - public override string ToString() => $"{this.Value} - {this.Type}"; + /// + /// Create a new ValueSize with as a PercentageOfHeight type with value set to percentage. + /// + /// The percentage. + /// a Values size with type PercentageOfHeight + public static ValueSize PercentageOfHeight(float percentage) + { + return new ValueSize(percentage, ValueSizeType.PercentageOfHeight); + } - /// - public override bool Equals(object obj) - { - return obj is ValueSize size && this.Equals(size); - } + /// + /// Create a new ValueSize with as a Absolute type with value set to value. + /// + /// The value. + /// a Values size with type Absolute. + public static ValueSize Absolute(float value) + { + return new ValueSize(value, ValueSizeType.Absolute); + } - /// - public bool Equals(ValueSize other) + /// + /// Calculates the specified size. + /// + /// The size. + /// The calculated value. + public float Calculate(Size size) + { + switch (this.Type) { - return this.Type == other.Type && this.Value.Equals(other.Value); + case ValueSizeType.PercentageOfWidth: + return this.Value * size.Width; + case ValueSizeType.PercentageOfHeight: + return this.Value * size.Height; + case ValueSizeType.Absolute: + default: + return this.Value; } + } + + /// + public override string ToString() => $"{this.Value} - {this.Type}"; - /// - public override int GetHashCode() => HashCode.Combine(this.Value, this.Type); + /// + public override bool Equals(object obj) + { + return obj is ValueSize size && this.Equals(size); } + + /// + public bool Equals(ValueSize other) + { + return this.Type == other.Type && this.Value.Equals(other.Value); + } + + /// + public override int GetHashCode() => HashCode.Combine(this.Value, this.Type); } diff --git a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs index 29ff8e4104..fce2f574db 100644 --- a/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs +++ b/src/ImageSharp/Processing/AdaptiveThresholdExtensions.cs @@ -3,72 +3,71 @@ using SixLabors.ImageSharp.Processing.Processors.Binarization; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Extensions to perform AdaptiveThreshold through Mutator. +/// +public static class AdaptiveThresholdExtensions { /// - /// Extensions to perform AdaptiveThreshold through Mutator. + /// Applies Bradley Adaptive Threshold to the image. /// - public static class AdaptiveThresholdExtensions - { - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) - => source.ApplyProcessor(new AdaptiveThresholdProcessor()); + /// The image this method extends. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source) + => source.ApplyProcessor(new AdaptiveThresholdProcessor()); - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, float thresholdLimit) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(thresholdLimit)); - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower)); + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower)); - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit)); - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Rectangle region to apply the processor on. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Rectangle region to apply the processor on. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, Rectangle rectangle) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower), rectangle); - /// - /// Applies Bradley Adaptive Threshold to the image. - /// - /// The image this method extends. - /// Upper (white) color for thresholding. - /// Lower (black) color for thresholding. - /// Threshold limit (0.0-1.0) to consider for binarization. - /// Rectangle region to apply the processor on. - /// The . - public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle) - => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); - } + /// + /// Applies Bradley Adaptive Threshold to the image. + /// + /// The image this method extends. + /// Upper (white) color for thresholding. + /// Lower (black) color for thresholding. + /// Threshold limit (0.0-1.0) to consider for binarization. + /// Rectangle region to apply the processor on. + /// The . + public static IImageProcessingContext AdaptiveThreshold(this IImageProcessingContext source, Color upper, Color lower, float thresholdLimit, Rectangle rectangle) + => source.ApplyProcessor(new AdaptiveThresholdProcessor(upper, lower, thresholdLimit), rectangle); } diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 2114df0bd2..d16b2fe55a 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -1,335 +1,332 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// A helper class for constructing instances for use in affine transforms. +/// +public class AffineTransformBuilder { + private readonly List> matrixFactories = new List>(); + /// - /// A helper class for constructing instances for use in affine transforms. + /// Prepends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. /// - public class AffineTransformBuilder - { - private readonly List> matrixFactories = new List>(); - - /// - /// Prepends a rotation matrix using the given rotation angle in degrees - /// and the image center point as rotation center. - /// - /// The amount of rotation, in degrees. - /// The . - public AffineTransformBuilder PrependRotationDegrees(float degrees) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Prepends a rotation matrix using the given rotation angle in radians - /// and the image center point as rotation center. - /// - /// The amount of rotation, in radians. - /// The . - public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); - - /// - /// Prepends a rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in degrees. - /// The rotation origin point. - /// The . - public AffineTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Prepends a rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - public AffineTransformBuilder PrependRotationRadians(float radians, Vector2 origin) - => this.PrependMatrix(Matrix3x2.CreateRotation(radians, origin)); - - /// - /// Appends a rotation matrix using the given rotation angle in degrees - /// and the image center point as rotation center. - /// - /// The amount of rotation, in degrees. - /// The . - public AffineTransformBuilder AppendRotationDegrees(float degrees) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Appends a rotation matrix using the given rotation angle in radians - /// and the image center point as rotation center. - /// - /// The amount of rotation, in radians. - /// The . - public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); - - /// - /// Appends a rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in degrees. - /// The rotation origin point. - /// The . - public AffineTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Appends a rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - public AffineTransformBuilder AppendRotationRadians(float radians, Vector2 origin) - => this.AppendMatrix(Matrix3x2.CreateRotation(radians, origin)); - - /// - /// Prepends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public AffineTransformBuilder PrependScale(float scale) - => this.PrependMatrix(Matrix3x2.CreateScale(scale)); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder PrependScale(SizeF scale) - => this.PrependScale((Vector2)scale); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder PrependScale(Vector2 scales) - => this.PrependMatrix(Matrix3x2.CreateScale(scales)); - - /// - /// Appends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public AffineTransformBuilder AppendScale(float scale) - => this.AppendMatrix(Matrix3x2.CreateScale(scale)); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder AppendScale(SizeF scales) - => this.AppendScale((Vector2)scales); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public AffineTransformBuilder AppendScale(Vector2 scales) - => this.AppendMatrix(Matrix3x2.CreateScale(scales)); - - /// - /// Prepends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); - - /// - /// Prepends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); - - /// - /// Prepends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Prepends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.PrependMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); - - /// - /// Appends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); - - /// - /// Appends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); - - /// - /// Appends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Appends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.AppendMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder PrependTranslation(PointF position) - => this.PrependTranslation((Vector2)position); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder PrependTranslation(Vector2 position) - => this.PrependMatrix(Matrix3x2.CreateTranslation(position)); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder AppendTranslation(PointF position) - => this.AppendTranslation((Vector2)position); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public AffineTransformBuilder AppendTranslation(Vector2 position) - => this.AppendMatrix(Matrix3x2.CreateTranslation(position)); - - /// - /// Prepends a raw matrix. - /// - /// The matrix to prepend. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) - { - CheckDegenerate(matrix); - return this.Prepend(_ => matrix); - } + /// The amount of rotation, in degrees. + /// The . + public AffineTransformBuilder PrependRotationDegrees(float degrees) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - /// - /// Appends a raw matrix. - /// - /// The matrix to append. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) - { - CheckDegenerate(matrix); - return this.Append(_ => matrix); - } + /// + /// Prepends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. + /// The . + public AffineTransformBuilder PrependRotationRadians(float radians) + => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); - /// - /// Returns the combined matrix for a given source size. - /// - /// The source image size. - /// The . - public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); - - /// - /// Returns the combined matrix for a given source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Matrix3x2 BuildMatrix(Rectangle sourceRectangle) - { - Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); - Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + /// + /// Prepends a rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in degrees. + /// The rotation origin point. + /// The . + public AffineTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - // Translate the origin matrix to cater for source rectangle offsets. - var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); + /// + /// Prepends a rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + public AffineTransformBuilder PrependRotationRadians(float radians, Vector2 origin) + => this.PrependMatrix(Matrix3x2.CreateRotation(radians, origin)); - Size size = sourceRectangle.Size; + /// + /// Appends a rotation matrix using the given rotation angle in degrees + /// and the image center point as rotation center. + /// + /// The amount of rotation, in degrees. + /// The . + public AffineTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - foreach (Func factory in this.matrixFactories) - { - matrix *= factory(size); - } + /// + /// Appends a rotation matrix using the given rotation angle in radians + /// and the image center point as rotation center. + /// + /// The amount of rotation, in radians. + /// The . + public AffineTransformBuilder AppendRotationRadians(float radians) + => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); - CheckDegenerate(matrix); + /// + /// Appends a rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in degrees. + /// The rotation origin point. + /// The . + public AffineTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - return matrix; - } + /// + /// Appends a rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + public AffineTransformBuilder AppendRotationRadians(float radians, Vector2 origin) + => this.AppendMatrix(Matrix3x2.CreateRotation(radians, origin)); - private static void CheckDegenerate(Matrix3x2 matrix) - { - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - } + /// + /// Prepends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public AffineTransformBuilder PrependScale(float scale) + => this.PrependMatrix(Matrix3x2.CreateScale(scale)); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder PrependScale(SizeF scale) + => this.PrependScale((Vector2)scale); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder PrependScale(Vector2 scales) + => this.PrependMatrix(Matrix3x2.CreateScale(scales)); + + /// + /// Appends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public AffineTransformBuilder AppendScale(float scale) + => this.AppendMatrix(Matrix3x2.CreateScale(scale)); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder AppendScale(SizeF scales) + => this.AppendScale((Vector2)scales); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public AffineTransformBuilder AppendScale(Vector2 scales) + => this.AppendMatrix(Matrix3x2.CreateScale(scales)); + + /// + /// Prepends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) + => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + + /// + /// Prepends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) + => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + + /// + /// Prepends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Prepends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.PrependMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); - private AffineTransformBuilder Prepend(Func factory) + /// + /// Appends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) + => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + + /// + /// Appends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) + => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + + /// + /// Appends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Appends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.AppendMatrix(Matrix3x2.CreateSkew(radiansX, radiansY, origin)); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder PrependTranslation(PointF position) + => this.PrependTranslation((Vector2)position); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder PrependTranslation(Vector2 position) + => this.PrependMatrix(Matrix3x2.CreateTranslation(position)); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder AppendTranslation(PointF position) + => this.AppendTranslation((Vector2)position); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public AffineTransformBuilder AppendTranslation(Vector2 position) + => this.AppendMatrix(Matrix3x2.CreateTranslation(position)); + + /// + /// Prepends a raw matrix. + /// + /// The matrix to prepend. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) + { + CheckDegenerate(matrix); + return this.Prepend(_ => matrix); + } + + /// + /// Appends a raw matrix. + /// + /// The matrix to append. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) + { + CheckDegenerate(matrix); + return this.Append(_ => matrix); + } + + /// + /// Returns the combined matrix for a given source size. + /// + /// The source image size. + /// The . + public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + + /// + /// Returns the combined matrix for a given source rectangle. + /// + /// The rectangle in the source image. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public Matrix3x2 BuildMatrix(Rectangle sourceRectangle) + { + Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); + Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + + // Translate the origin matrix to cater for source rectangle offsets. + var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); + + Size size = sourceRectangle.Size; + + foreach (Func factory in this.matrixFactories) { - this.matrixFactories.Insert(0, factory); - return this; + matrix *= factory(size); } - private AffineTransformBuilder Append(Func factory) + CheckDegenerate(matrix); + + return matrix; + } + + private static void CheckDegenerate(Matrix3x2 matrix) + { + if (TransformUtils.IsDegenerate(matrix)) { - this.matrixFactories.Add(factory); - return this; + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } } + + private AffineTransformBuilder Prepend(Func factory) + { + this.matrixFactories.Insert(0, factory); + return this; + } + + private AffineTransformBuilder Append(Func factory) + { + this.matrixFactories.Add(factory); + return this; + } } diff --git a/src/ImageSharp/Processing/AnchorPositionMode.cs b/src/ImageSharp/Processing/AnchorPositionMode.cs index a53ac64e07..9f50eb6b64 100644 --- a/src/ImageSharp/Processing/AnchorPositionMode.cs +++ b/src/ImageSharp/Processing/AnchorPositionMode.cs @@ -1,56 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Enumerated anchor positions to apply to resized images. +/// +public enum AnchorPositionMode { /// - /// Enumerated anchor positions to apply to resized images. - /// - public enum AnchorPositionMode - { - /// - /// Anchors the position of the image to the center of it's bounding container. - /// - Center, - - /// - /// Anchors the position of the image to the top of it's bounding container. - /// - Top, - - /// - /// Anchors the position of the image to the bottom of it's bounding container. - /// - Bottom, - - /// - /// Anchors the position of the image to the left of it's bounding container. - /// - Left, - - /// - /// Anchors the position of the image to the right of it's bounding container. - /// - Right, - - /// - /// Anchors the position of the image to the top left side of it's bounding container. - /// - TopLeft, - - /// - /// Anchors the position of the image to the top right side of it's bounding container. - /// - TopRight, - - /// - /// Anchors the position of the image to the bottom right side of it's bounding container. - /// - BottomRight, - - /// - /// Anchors the position of the image to the bottom left side of it's bounding container. - /// - BottomLeft - } + /// Anchors the position of the image to the center of it's bounding container. + /// + Center, + + /// + /// Anchors the position of the image to the top of it's bounding container. + /// + Top, + + /// + /// Anchors the position of the image to the bottom of it's bounding container. + /// + Bottom, + + /// + /// Anchors the position of the image to the left of it's bounding container. + /// + Left, + + /// + /// Anchors the position of the image to the right of it's bounding container. + /// + Right, + + /// + /// Anchors the position of the image to the top left side of it's bounding container. + /// + TopLeft, + + /// + /// Anchors the position of the image to the top right side of it's bounding container. + /// + TopRight, + + /// + /// Anchors the position of the image to the bottom right side of it's bounding container. + /// + BottomRight, + + /// + /// Anchors the position of the image to the bottom left side of it's bounding container. + /// + BottomLeft } diff --git a/src/ImageSharp/Processing/BinaryThresholdMode.cs b/src/ImageSharp/Processing/BinaryThresholdMode.cs index 64776c7ef4..369926057e 100644 --- a/src/ImageSharp/Processing/BinaryThresholdMode.cs +++ b/src/ImageSharp/Processing/BinaryThresholdMode.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Selects the value to be compared to threshold. +/// +public enum BinaryThresholdMode { /// - /// Selects the value to be compared to threshold. + /// Compare the color luminance (according to ITU-R Recommendation BT.709). /// - public enum BinaryThresholdMode - { - /// - /// Compare the color luminance (according to ITU-R Recommendation BT.709). - /// - Luminance = 0, + Luminance = 0, - /// - /// Compare the HSL saturation of the color. - /// - Saturation = 1, + /// + /// Compare the HSL saturation of the color. + /// + Saturation = 1, - /// - /// Compare the maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value. - /// - MaxChroma = 2, - } + /// + /// Compare the maximum of YCbCr chroma value, i.e. Cb and Cr distance from achromatic value. + /// + MaxChroma = 2, } diff --git a/src/ImageSharp/Processing/ColorBlindnessMode.cs b/src/ImageSharp/Processing/ColorBlindnessMode.cs index 6fe9d38b26..c2b2a9e94c 100644 --- a/src/ImageSharp/Processing/ColorBlindnessMode.cs +++ b/src/ImageSharp/Processing/ColorBlindnessMode.cs @@ -1,51 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Enumerates the various types of defined color blindness filters. +/// +public enum ColorBlindnessMode { /// - /// Enumerates the various types of defined color blindness filters. - /// - public enum ColorBlindnessMode - { - /// - /// Partial color desensitivity. - /// - Achromatomaly, - - /// - /// Complete color desensitivity (Monochrome) - /// - Achromatopsia, - - /// - /// Green weak - /// - Deuteranomaly, - - /// - /// Green blind - /// - Deuteranopia, - - /// - /// Red weak - /// - Protanomaly, - - /// - /// Red blind - /// - Protanopia, - - /// - /// Blue weak - /// - Tritanomaly, - - /// - /// Blue blind - /// - Tritanopia - } + /// Partial color desensitivity. + /// + Achromatomaly, + + /// + /// Complete color desensitivity (Monochrome) + /// + Achromatopsia, + + /// + /// Green weak + /// + Deuteranomaly, + + /// + /// Green blind + /// + Deuteranopia, + + /// + /// Red weak + /// + Protanomaly, + + /// + /// Red blind + /// + Protanopia, + + /// + /// Blue weak + /// + Tritanomaly, + + /// + /// Blue blind + /// + Tritanopia } diff --git a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs index e43affca0e..35a2734577 100644 --- a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs +++ b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs @@ -2,99 +2,97 @@ // Licensed under the Six Labors Split License. using System.Collections.Concurrent; -using System.Collections.Generic; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Performs processor application operations on the source image +/// +/// The pixel format +internal class DefaultImageProcessorContext : IInternalImageProcessingContext + where TPixel : unmanaged, IPixel { + private readonly bool mutate; + private readonly Image source; + private Image destination; + /// - /// Performs processor application operations on the source image + /// Initializes a new instance of the class. /// - /// The pixel format - internal class DefaultImageProcessorContext : IInternalImageProcessingContext - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. + /// Whether to mutate the image. + public DefaultImageProcessorContext(Configuration configuration, Image source, bool mutate) { - private readonly bool mutate; - private readonly Image source; - private Image destination; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// Whether to mutate the image. - public DefaultImageProcessorContext(Configuration configuration, Image source, bool mutate) - { - this.Configuration = configuration; - this.mutate = mutate; - this.source = source; + this.Configuration = configuration; + this.mutate = mutate; + this.source = source; - // Mutate acts upon the source image only. - if (this.mutate) - { - this.destination = source; - } + // Mutate acts upon the source image only. + if (this.mutate) + { + this.destination = source; } + } - /// - public Configuration Configuration { get; } + /// + public Configuration Configuration { get; } - /// - public IDictionary Properties { get; } = new ConcurrentDictionary(); + /// + public IDictionary Properties { get; } = new ConcurrentDictionary(); - /// - public Image GetResultImage() + /// + public Image GetResultImage() + { + if (!this.mutate && this.destination is null) { - if (!this.mutate && this.destination is null) - { - // Ensure we have cloned the source if we are not mutating as we might have failed - // to register any processors. - this.destination = this.source.Clone(); - } - - return this.destination; + // Ensure we have cloned the source if we are not mutating as we might have failed + // to register any processors. + this.destination = this.source.Clone(); } - /// - public Size GetCurrentSize() => this.GetCurrentBounds().Size; + return this.destination; + } + + /// + public Size GetCurrentSize() => this.GetCurrentBounds().Size; - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) - { - return this.ApplyProcessor(processor, this.GetCurrentBounds()); - } + /// + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + { + return this.ApplyProcessor(processor, this.GetCurrentBounds()); + } - /// - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + /// + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + if (!this.mutate && this.destination is null) { - if (!this.mutate && this.destination is null) + // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary + // interim clone if the first processor in the pipeline is a cloning processor. + if (processor is ICloningImageProcessor cloningImageProcessor) { - // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary - // interim clone if the first processor in the pipeline is a cloning processor. - if (processor is ICloningImageProcessor cloningImageProcessor) + using (ICloningImageProcessor pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.Configuration, this.source, rectangle)) { - using (ICloningImageProcessor pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.Configuration, this.source, rectangle)) - { - this.destination = pixelProcessor.CloneAndExecute(); - return this; - } + this.destination = pixelProcessor.CloneAndExecute(); + return this; } - - // Not a cloning processor? We need to create a clone to operate on. - this.destination = this.source.Clone(); } - // Standard processing pipeline. - using (IImageProcessor specificProcessor = processor.CreatePixelSpecificProcessor(this.Configuration, this.destination, rectangle)) - { - specificProcessor.Execute(); - } + // Not a cloning processor? We need to create a clone to operate on. + this.destination = this.source.Clone(); + } - return this; + // Standard processing pipeline. + using (IImageProcessor specificProcessor = processor.CreatePixelSpecificProcessor(this.Configuration, this.destination, rectangle)) + { + specificProcessor.Execute(); } - private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds(); + return this; } + + private Rectangle GetCurrentBounds() => this.destination?.Bounds() ?? this.source.Bounds(); } diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs index 313b8e007b..887455b821 100644 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs @@ -3,71 +3,70 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions to apply binary dithering on an +/// using Mutate/Clone. +/// +public static class BinaryDitherExtensions { /// - /// Defines extensions to apply binary dithering on an - /// using Mutate/Clone. + /// Dithers the image reducing it to two colors using ordered dithering. /// - public static class BinaryDitherExtensions - { - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The to allow chaining of operations. - public static IImageProcessingContext - BinaryDither(this IImageProcessingContext source, IDither dither) => - BinaryDither(source, dither, Color.White, Color.Black); + /// The image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + public static IImageProcessingContext + BinaryDither(this IImageProcessingContext source, IDither dither) => + BinaryDither(source, dither, Color.White, Color.Black); - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// 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 - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor })); + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// 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 + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Color upperColor, + Color lowerColor) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor })); - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// The ordered ditherer. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Rectangle rectangle) => - BinaryDither(source, dither, Color.White, Color.Black, rectangle); + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Rectangle rectangle) => + BinaryDither(source, dither, Color.White, Color.Black, rectangle); - /// - /// Dithers the image reducing it to two colors using ordered dithering. - /// - /// The image this method extends. - /// 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 - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDither( - this IImageProcessingContext source, - IDither dither, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor }), rectangle); - } + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The image this method extends. + /// 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 + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryDither( + this IImageProcessingContext source, + IDither dither, + Color upperColor, + Color lowerColor, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor }), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs index 56ea03a38b..802ad06d75 100644 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryThresholdExtensions.cs @@ -3,142 +3,141 @@ using SixLabors.ImageSharp.Processing.Processors.Binarization; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extension methods to apply binary thresholding on an +/// using Mutate/Clone. +/// +public static class BinaryThresholdExtensions { /// - /// Defines extension methods to apply binary thresholding on an - /// using Mutate/Clone. + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as the color component to be compared to threshold. /// - public static class BinaryThresholdExtensions - { - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// 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 BinaryThreshold(this IImageProcessingContext source, float threshold) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance)); + /// 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 BinaryThreshold(this IImageProcessingContext source, float threshold) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance)); - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// Selects the value to be compared to threshold. - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - BinaryThresholdMode mode) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode)); + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// Selects the value to be compared to threshold. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + BinaryThresholdMode mode) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode)); - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The image this method extends. - /// 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 BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance), rectangle); + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as the color component to be compared to threshold. + /// + /// The image this method extends. + /// 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 BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Rectangle rectangle) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, BinaryThresholdMode.Luminance), rectangle); - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// Selects the value to be compared to threshold. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - BinaryThresholdMode mode, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode), rectangle); + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// Selects the value to be compared to threshold. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + BinaryThresholdMode mode, + Rectangle rectangle) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, mode), rectangle); - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The image this method extends. - /// 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 BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance)); + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as the color component to be compared to threshold. + /// + /// The image this method extends. + /// 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 BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance)); - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// 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 - /// Selects the value to be compared to threshold. - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - BinaryThresholdMode mode) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode)); + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// 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 + /// Selects the value to be compared to threshold. + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + BinaryThresholdMode mode) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode)); - /// - /// Applies binarization to the image splitting the pixels at the given threshold with - /// Luminance as the color component to be compared to threshold. - /// - /// The image this method extends. - /// 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 BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - Rectangle rectangle) - => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance), rectangle); + /// + /// Applies binarization to the image splitting the pixels at the given threshold with + /// Luminance as the color component to be compared to threshold. + /// + /// The image this method extends. + /// 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 BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + Rectangle rectangle) + => source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance), rectangle); - /// - /// Applies binarization to the image splitting the pixels at the given threshold. - /// - /// The image this method extends. - /// 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 - /// Selects the value to be compared to threshold. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryThreshold( - this IImageProcessingContext source, - float threshold, - Color upperColor, - Color lowerColor, - BinaryThresholdMode mode, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode), rectangle); - } + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The image this method extends. + /// 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 + /// Selects the value to be compared to threshold. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BinaryThreshold( + this IImageProcessingContext source, + float threshold, + Color upperColor, + Color lowerColor, + BinaryThresholdMode mode, + Rectangle rectangle) => + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor, mode), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs index b41f0cfeb8..1688e5cc76 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BokehBlurExtensions.cs @@ -3,55 +3,54 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Adds bokeh blurring extensions to the type. +/// +public static class BokehBlurExtensions { /// - /// Adds bokeh blurring extensions to the type. + /// Applies a bokeh blur to the image. /// - public static class BokehBlurExtensions - { - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BokehBlurProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BokehBlurProcessor()); - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma)); - /// - /// Applies a bokeh blur to the image. - /// - /// 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 BokehBlur(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); + /// + /// Applies a bokeh blur to the image. + /// + /// 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 BokehBlur(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(), rectangle); - /// - /// Applies a bokeh blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. - /// The gamma highlight factor to use to emphasize bright spots in the source image - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) - => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); - } + /// + /// Applies a bokeh blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The 'components' value representing the number of kernels to use to approximate the bokeh effect. + /// The gamma highlight factor to use to emphasize bright spots in the source image + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BokehBlur(this IImageProcessingContext source, int radius, int components, float gamma, Rectangle rectangle) + => source.ApplyProcessor(new BokehBlurProcessor(radius, components, gamma), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs index 09414b2463..85b4888f0d 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs @@ -3,62 +3,61 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions methods to apply box blurring to an +/// using Mutate/Clone. +/// +public static class BoxBlurExtensions { /// - /// Defines extensions methods to apply box blurring to an - /// using Mutate/Clone. + /// Applies a box blur to the image. /// - public static class BoxBlurExtensions - { - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new BoxBlurProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new BoxBlurProcessor()); - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) - => source.ApplyProcessor(new BoxBlurProcessor(radius)); + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius) + => source.ApplyProcessor(new BoxBlurProcessor(radius)); - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) - => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) + => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); - /// - /// Applies a box blur to the image. - /// - /// The image this method extends. - /// The 'radius' value representing the size of the area to sample. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + /// + /// Applies a box blur to the image. + /// + /// The image this method extends. + /// The 'radius' value representing the size of the area to sample. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// + /// + /// The to use when mapping the pixels outside of the border, in Y direction. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY); + return source.ApplyProcessor(processor, rectangle); } } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs index b7e468863d..7f57ae9837 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/DetectEdgesExtensions.cs @@ -3,238 +3,237 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines edge detection extensions applicable on an using Mutate/Clone. +/// +public static class DetectEdgesExtensions { /// - /// Defines edge detection extensions applicable on an using Mutate/Clone. + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. + /// + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => + DetectEdges(source, KnownEdgeDetectorKernels.Sobel); + + /// + /// Detects any edges within the image. + /// Uses the kernel operating in grayscale mode. + /// + /// 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 DetectEdges( + this IImageProcessingContext source, + Rectangle rectangle) => + DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle); + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + bool grayscale) + { + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetector2DKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetector2DProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + bool grayscale) + { + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorKernel kernel, + bool grayscale, + Rectangle rectangle) + { + var processor = new EdgeDetectorProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel) => + DetectEdges(source, kernel, true); + + /// + /// Detects any edges within the image using a . + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale) + { + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor); + return source; + } + + /// + /// Detects any edges within the image operating in grayscale mode. + /// + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + Rectangle rectangle) => + DetectEdges(source, kernel, true, rectangle); + + /// + /// Detects any edges within the image using a . /// - public static class DetectEdgesExtensions + /// The image this method extends. + /// Thecompass edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext DetectEdges( + this IImageProcessingContext source, + EdgeDetectorCompassKernel kernel, + bool grayscale, + Rectangle rectangle) { - /// - /// Detects any edges within the image. - /// Uses the kernel operating in grayscale mode. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges(this IImageProcessingContext source) => - DetectEdges(source, KnownEdgeDetectorKernels.Sobel); - - /// - /// Detects any edges within the image. - /// Uses the kernel operating in grayscale mode. - /// - /// 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 DetectEdges( - this IImageProcessingContext source, - Rectangle rectangle) => - DetectEdges(source, KnownEdgeDetectorKernels.Sobel, rectangle); - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// The 2D edge detector kernel. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel) => - DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel, - bool grayscale) - { - var processor = new EdgeDetector2DProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// The 2D edge detector kernel. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetector2DKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetector2DProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// The edge detector kernel. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel) => - DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel, - bool grayscale) - { - var processor = new EdgeDetectorProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// The edge detector kernel. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetectorProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// Thecompass edge detector kernel. - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel) => - DetectEdges(source, kernel, true); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// Thecompass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel, - bool grayscale) - { - var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); - source.ApplyProcessor(processor); - return source; - } - - /// - /// Detects any edges within the image operating in grayscale mode. - /// - /// The image this method extends. - /// Thecompass edge detector kernel. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel, - Rectangle rectangle) => - DetectEdges(source, kernel, true, rectangle); - - /// - /// Detects any edges within the image using a . - /// - /// The image this method extends. - /// Thecompass edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext DetectEdges( - this IImageProcessingContext source, - EdgeDetectorCompassKernel kernel, - bool grayscale, - Rectangle rectangle) - { - var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); - source.ApplyProcessor(processor, rectangle); - return source; - } + var processor = new EdgeDetectorCompassProcessor(kernel, grayscale); + source.ApplyProcessor(processor, rectangle); + return source; } } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs index b11f8da5e1..9ea4e24af4 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs @@ -3,62 +3,61 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines Gaussian blurring extensions to apply on an +/// using Mutate/Clone. +/// +public static class GaussianBlurExtensions { /// - /// Defines Gaussian blurring extensions to apply on an - /// using Mutate/Clone. + /// Applies a Gaussian blur to the image. /// - public static class GaussianBlurExtensions - { - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) - => source.ApplyProcessor(new GaussianBlurProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source) + => source.ApplyProcessor(new GaussianBlurProcessor()); - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma)); - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) - => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) + => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); - /// - /// Applies a Gaussian blur to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + /// + /// Applies a Gaussian blur to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// + /// + /// The to use when mapping the pixels outside of the border, in Y direction. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY); + return source.ApplyProcessor(processor, rectangle); } } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs index e0dc27a563..70b8e6f439 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs @@ -3,65 +3,64 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines Gaussian sharpening extensions to apply on an +/// using Mutate/Clone. +/// +public static class GaussianSharpenExtensions { /// - /// Defines Gaussian sharpening extensions to apply on an - /// using Mutate/Clone. + /// Applies a Gaussian sharpening filter to the image. /// - public static class GaussianSharpenExtensions - { - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => - source.ApplyProcessor(new GaussianSharpenProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) => + source.ApplyProcessor(new GaussianSharpenProcessor()); - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma)); - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen( - this IImageProcessingContext source, - float sigma, - Rectangle rectangle) => - source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen( + this IImageProcessingContext source, + float sigma, + Rectangle rectangle) => + source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); - /// - /// Applies a Gaussian sharpening filter to the image. - /// - /// The image this method extends. - /// The 'sigma' value representing the weight of the blur. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY); - return source.ApplyProcessor(processor, rectangle); - } + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// The image this method extends. + /// The 'sigma' value representing the weight of the blur. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// + /// + /// The to use when mapping the pixels outside of the border, in Y direction. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY); + return source.ApplyProcessor(processor, rectangle); } } diff --git a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs index a80a119004..60a80d35e5 100644 --- a/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Convolution/MedianBlurExtensions.cs @@ -3,39 +3,38 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the applying of the median blur on an +/// using Mutate/Clone. +/// +public static class MedianBlurExtensions { /// - /// Defines extensions that allow the applying of the median blur on an - /// using Mutate/Clone. + /// Applies a median blur on the image. /// - public static class MedianBlurExtensions - { - /// - /// Applies a median blur on the image. - /// - /// The image this method extends. - /// The radius of the area to find the median for. - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha) - => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha)); + /// The image this method extends. + /// The radius of the area to find the median for. + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha) + => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha)); - /// - /// Applies a median blur on the image. - /// - /// The image this method extends. - /// The radius of the area to find the median for. - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle) - => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle); - } + /// + /// Applies a median blur on the image. + /// + /// The image this method extends. + /// The radius of the area to find the median for. + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext MedianBlur(this IImageProcessingContext source, int radius, bool preserveAlpha, Rectangle rectangle) + => source.ApplyProcessor(new MedianBlurProcessor(radius, preserveAlpha), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs index 1895081896..3eaeb715d3 100644 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -1,154 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines dithering extensions to apply on an +/// using Mutate/Clone. +/// +public static class DitherExtensions { /// - /// Defines dithering extensions to apply on an - /// using Mutate/Clone. + /// Dithers the image reducing it to a web-safe palette using . /// - public static class DitherExtensions - { - /// - /// 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, KnownDitherings.Bayer8x8); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source) => + Dither(source, KnownDitherings.Bayer8x8); - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// The image this method extends. - /// The ordered ditherer. - /// The to allow chaining of operations. - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither) => - source.ApplyProcessor(new PaletteDitherProcessor(dither)); + /// + /// Dithers the image reducing it to a web-safe palette. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither) => + source.ApplyProcessor(new PaletteDitherProcessor(dither)); - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// 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 a web-safe palette. + /// + /// 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. - /// - /// 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. + /// + /// 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. - /// - /// 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, - IDither dither, - float ditherScale, - ReadOnlyMemory palette) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette)); + /// + /// Dithers the image reducing it to the given palette. + /// + /// 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, + IDither dither, + float ditherScale, + ReadOnlyMemory 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 . + /// + /// 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. - /// - /// The image this method extends. - /// The ordered ditherer. - /// - /// 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, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); + /// + /// Dithers the image reducing it to a web-safe palette. + /// + /// The image this method extends. + /// The ordered ditherer. + /// + /// 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, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); - /// - /// Dithers the image reducing it to a web-safe palette. - /// - /// 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 a web-safe palette. + /// + /// 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. - /// - /// 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 PaletteDitherProcessor(dither, palette), rectangle); + /// + /// Dithers the image reducing it to the given palette. + /// + /// 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 PaletteDitherProcessor(dither, palette), rectangle); - /// - /// Dithers the image reducing it to the given palette. - /// - /// 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. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Dither( - this IImageProcessingContext source, - IDither dither, - float ditherScale, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette), rectangle); - } + /// + /// Dithers the image reducing it to the given palette. + /// + /// 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. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale, + ReadOnlyMemory palette, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs index 304b59382c..2cb8dc3211 100644 --- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs @@ -4,177 +4,176 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Drawing; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Adds extensions that allow the drawing of images to the type. +/// +public static class DrawImageExtensions { /// - /// Adds extensions that allow the drawing of images to the type. + /// Draws the given image together with the current one by blending their pixels. /// - public static class DrawImageExtensions + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + float opacity) { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - float opacity) - { - var options = source.GetGraphicsOptions(); - return source.ApplyProcessor( - new DrawImageProcessor( + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + PixelColorBlendingMode colorBlending, + float opacity) => + source.ApplyProcessor( + new DrawImageProcessor( image, Point.Empty, - options.ColorBlendingMode, - options.AlphaCompositionMode, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, opacity)); - } - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The blending mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - PixelColorBlendingMode colorBlending, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - Point.Empty, - colorBlending, - source.GetGraphicsOptions().AlphaCompositionMode, - opacity)); + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The color blending mode. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity) => + source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The color blending mode. - /// The alpha composition mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) => - source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and blending amount. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + GraphicsOptions options) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + Point.Empty, + options.ColorBlendingMode, + options.AlphaCompositionMode, + options.BlendPercentage)); - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The options, including the blending type and blending amount. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - GraphicsOptions options) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - Point.Empty, - options.ColorBlendingMode, - options.AlphaCompositionMode, - options.BlendPercentage)); + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + float opacity) + { + var options = source.GetGraphicsOptions(); + return source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + opacity)); + } - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - float opacity) - { - var options = source.GetGraphicsOptions(); - return source.ApplyProcessor( - new DrawImageProcessor( + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + PixelColorBlendingMode colorBlending, + float opacity) => + source.ApplyProcessor( + new DrawImageProcessor( image, location, - options.ColorBlendingMode, - options.AlphaCompositionMode, + colorBlending, + source.GetGraphicsOptions().AlphaCompositionMode, opacity)); - } - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The color blending to apply. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - PixelColorBlendingMode colorBlending, - float opacity) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - location, - colorBlending, - source.GetGraphicsOptions().AlphaCompositionMode, - opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The color blending to apply. - /// The alpha composition mode. - /// The opacity of the image to blend. Must be between 0 and 1. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - PixelColorBlendingMode colorBlending, - PixelAlphaCompositionMode alphaComposition, - float opacity) => - source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The color blending to apply. + /// The alpha composition mode. + /// The opacity of the image to blend. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity) => + source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The location to draw the blended image. - /// The options containing the blend mode and opacity. - /// The . - public static IImageProcessingContext DrawImage( - this IImageProcessingContext source, - Image image, - Point location, - GraphicsOptions options) => - source.ApplyProcessor( - new DrawImageProcessor( - image, - location, - options.ColorBlendingMode, - options.AlphaCompositionMode, - options.BlendPercentage)); - } + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The location to draw the blended image. + /// The options containing the blend mode and opacity. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image image, + Point location, + GraphicsOptions options) => + source.ApplyProcessor( + new DrawImageProcessor( + image, + location, + options.ColorBlendingMode, + options.AlphaCompositionMode, + options.BlendPercentage)); } diff --git a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs index 4b95cf0704..e29b39478f 100644 --- a/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Effects/OilPaintExtensions.cs @@ -3,60 +3,59 @@ using SixLabors.ImageSharp.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines oil painting effect extensions applicable on an +/// using Mutate/Clone. +/// +public static class OilPaintExtensions { /// - /// Defines oil painting effect extensions applicable on an - /// using Mutate/Clone. + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. /// - public static class OilPaintExtensions - { - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext OilPaint(this IImageProcessingContext source) => OilPaint(source, 10, 15); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext OilPaint(this IImageProcessingContext source) => OilPaint(source, 10, 15); - /// - /// Alters the colors of the image recreating an oil painting effect with levels and brushSize - /// set to 10 and 15 respectively. - /// - /// 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 OilPaint(this IImageProcessingContext source, Rectangle rectangle) => - OilPaint(source, 10, 15, rectangle); + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// 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 OilPaint(this IImageProcessingContext source, Rectangle rectangle) => + OilPaint(source, 10, 15, rectangle); - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// The to allow chaining of operations. - public static IImageProcessingContext - OilPaint(this IImageProcessingContext source, int levels, int brushSize) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// The to allow chaining of operations. + public static IImageProcessingContext + OilPaint(this IImageProcessingContext source, int levels, int brushSize) => + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize)); - /// - /// Alters the colors of the image recreating an oil painting effect. - /// - /// The image this method extends. - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// The number of neighboring pixels used in calculating each individual pixel value. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext OilPaint( - this IImageProcessingContext source, - int levels, - int brushSize, - Rectangle rectangle) => - source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); - } + /// + /// Alters the colors of the image recreating an oil painting effect. + /// + /// The image this method extends. + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// The number of neighboring pixels used in calculating each individual pixel value. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext OilPaint( + this IImageProcessingContext source, + int levels, + int brushSize, + Rectangle rectangle) => + source.ApplyProcessor(new OilPaintingProcessor(levels, brushSize), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs index 5e937bf7cd..e7b5d7623e 100644 --- a/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelRowDelegateExtensions.cs @@ -4,99 +4,98 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extension methods that allow the application of user defined processing delegate to an . +/// +public static class PixelRowDelegateExtensions { /// - /// Defines extension methods that allow the application of user defined processing delegate to an . + /// Applies a user defined processing delegate to the image. /// - public static class PixelRowDelegateExtensions - { - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) - => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// The to apply during the pixel conversions. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers)); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers)); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) - => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) + => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to apply during the pixel conversions. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers), rectangle); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PixelRowDelegateProcessor(rowOperation, modifiers), rectangle); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) - => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation) + => ProcessPixelRowsAsVector4(source, rowOperation, PixelConversionModifiers.None); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// The to apply during the pixel conversions. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers)); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers)); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) - => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle) + => ProcessPixelRowsAsVector4(source, rowOperation, rectangle, PixelConversionModifiers.None); - /// - /// Applies a user defined processing delegate to the image. - /// - /// The image this method extends. - /// The user defined processing delegate to use to modify image rows. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to apply during the pixel conversions. - /// The to allow chaining of operations. - public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) - => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers), rectangle); - } + /// + /// Applies a user defined processing delegate to the image. + /// + /// The image this method extends. + /// The user defined processing delegate to use to modify image rows. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to apply during the pixel conversions. + /// The to allow chaining of operations. + public static IImageProcessingContext ProcessPixelRowsAsVector4(this IImageProcessingContext source, PixelRowOperation rowOperation, Rectangle rectangle, PixelConversionModifiers modifiers) + => source.ApplyProcessor(new PositionAwarePixelRowDelegateProcessor(rowOperation, modifiers), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs index 4d9e5311ca..17654a2061 100644 --- a/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Effects/PixelateExtensions.cs @@ -3,43 +3,42 @@ using SixLabors.ImageSharp.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines pixelation effect extensions applicable on an +/// using Mutate/Clone. +/// +public static class PixelateExtensions { /// - /// Defines pixelation effect extensions applicable on an - /// using Mutate/Clone. + /// Pixelates an image with the given pixel size. /// - public static class PixelateExtensions - { - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source) => Pixelate(source, 4); - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The size of the pixels. - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => - source.ApplyProcessor(new PixelateProcessor(size)); + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate(this IImageProcessingContext source, int size) => + source.ApplyProcessor(new PixelateProcessor(size)); - /// - /// Pixelates an image with the given pixel size. - /// - /// The image this method extends. - /// The size of the pixels. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Pixelate( - this IImageProcessingContext source, - int size, - Rectangle rectangle) => - source.ApplyProcessor(new PixelateProcessor(size), rectangle); - } + /// + /// Pixelates an image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Pixelate( + this IImageProcessingContext source, + int size, + Rectangle rectangle) => + source.ApplyProcessor(new PixelateProcessor(size), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs index efb8950081..54f2d2143e 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/BlackWhiteExtensions.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extension methods that allow the application of black and white toning to an +/// using Mutate/Clone. +/// +public static class BlackWhiteExtensions { /// - /// Defines extension methods that allow the application of black and white toning to an - /// using Mutate/Clone. + /// Applies black and white toning to the image. /// - public static class BlackWhiteExtensions - { - /// - /// Applies black and white toning to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) - => source.ApplyProcessor(new BlackWhiteProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext BlackWhite(this IImageProcessingContext source) + => source.ApplyProcessor(new BlackWhiteProcessor()); - /// - /// Applies black and white toning to the image. - /// - /// 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 BlackWhite(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); - } + /// + /// Applies black and white toning to the image. + /// + /// 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 BlackWhite(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new BlackWhiteProcessor(), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs index edd68dd0ff..e53f22040f 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/BrightnessExtensions.cs @@ -3,41 +3,40 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the brightness component of an +/// using Mutate/Clone. +/// +public static class BrightnessExtensions { /// - /// Defines extensions that allow the alteration of the brightness component of an - /// using Mutate/Clone. + /// Alters the brightness component of the image. /// - public static class BrightnessExtensions - { - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new BrightnessProcessor(amount)); + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new BrightnessProcessor(amount)); - /// - /// Alters the brightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); - } + /// + /// Alters the brightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Brightness(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new BrightnessProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs index aab8f380e8..73c3c29a91 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/ColorBlindnessExtensions.cs @@ -4,56 +4,55 @@ using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that simulate the effects of various color blindness disorders on an +/// using Mutate/Clone. +/// +public static class ColorBlindnessExtensions { /// - /// Defines extensions that simulate the effects of various color blindness disorders on an - /// using Mutate/Clone. + /// Applies the given colorblindness simulator to the image. /// - public static class ColorBlindnessExtensions - { - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The image this method extends. - /// The type of color blindness simulator to apply. - /// The to allow chaining of operations. - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) - => source.ApplyProcessor(GetProcessor(colorBlindness)); + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// The to allow chaining of operations. + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindness) + => source.ApplyProcessor(GetProcessor(colorBlindness)); - /// - /// Applies the given colorblindness simulator to the image. - /// - /// The image this method extends. - /// The type of color blindness simulator to apply. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) - => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); + /// + /// Applies the given colorblindness simulator to the image. + /// + /// The image this method extends. + /// The type of color blindness simulator to apply. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext ColorBlindness(this IImageProcessingContext source, ColorBlindnessMode colorBlindnessMode, Rectangle rectangle) + => source.ApplyProcessor(GetProcessor(colorBlindnessMode), rectangle); - private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) + private static IImageProcessor GetProcessor(ColorBlindnessMode colorBlindness) + { + switch (colorBlindness) { - switch (colorBlindness) - { - case ColorBlindnessMode.Achromatomaly: - return new AchromatomalyProcessor(); - case ColorBlindnessMode.Achromatopsia: - return new AchromatopsiaProcessor(); - case ColorBlindnessMode.Deuteranomaly: - return new DeuteranomalyProcessor(); - case ColorBlindnessMode.Deuteranopia: - return new DeuteranopiaProcessor(); - case ColorBlindnessMode.Protanomaly: - return new ProtanomalyProcessor(); - case ColorBlindnessMode.Protanopia: - return new ProtanopiaProcessor(); - case ColorBlindnessMode.Tritanomaly: - return new TritanomalyProcessor(); - default: - return new TritanopiaProcessor(); - } + case ColorBlindnessMode.Achromatomaly: + return new AchromatomalyProcessor(); + case ColorBlindnessMode.Achromatopsia: + return new AchromatopsiaProcessor(); + case ColorBlindnessMode.Deuteranomaly: + return new DeuteranomalyProcessor(); + case ColorBlindnessMode.Deuteranopia: + return new DeuteranopiaProcessor(); + case ColorBlindnessMode.Protanomaly: + return new ProtanomalyProcessor(); + case ColorBlindnessMode.Protanopia: + return new ProtanopiaProcessor(); + case ColorBlindnessMode.Tritanomaly: + return new TritanomalyProcessor(); + default: + return new TritanopiaProcessor(); } } } diff --git a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs index aa00d0e0a4..5ed5c9a21d 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/ContrastExtensions.cs @@ -3,41 +3,40 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the contrast component of an +/// using Mutate/Clone. +/// +public static class ContrastExtensions { /// - /// Defines extensions that allow the alteration of the contrast component of an - /// using Mutate/Clone. + /// Alters the contrast component of the image. /// - public static class ContrastExtensions - { - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new ContrastProcessor(amount)); + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new ContrastProcessor(amount)); - /// - /// Alters the contrast component of the image. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); - } + /// + /// Alters the contrast component of the image. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Contrast(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new ContrastProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs index 56dd6f741c..051afebc97 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of composable filters to an +/// using Mutate/Clone. +/// +public static class FilterExtensions { /// - /// Defines extensions that allow the application of composable filters to an - /// using Mutate/Clone. + /// Filters an image by the given color matrix /// - public static class FilterExtensions - { - /// - /// Filters an image by the given color matrix - /// - /// The image this method extends. - /// The filter color matrix - /// The to allow chaining of operations. - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) - => source.ApplyProcessor(new FilterProcessor(matrix)); + /// The image this method extends. + /// The filter color matrix + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix) + => source.ApplyProcessor(new FilterProcessor(matrix)); - /// - /// Filters an image by the given color matrix - /// - /// The image this method extends. - /// The filter color matrix - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) - => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); - } + /// + /// Filters an image by the given color matrix + /// + /// The image this method extends. + /// The filter color matrix + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) + => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs index 6a2980828e..88633719a1 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/GrayscaleExtensions.cs @@ -4,110 +4,109 @@ using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of grayscale toning to an +/// using Mutate/Clone. +/// +public static class GrayscaleExtensions { /// - /// Defines extensions that allow the application of grayscale toning to an - /// using Mutate/Clone. + /// Applies grayscale toning to the image. /// - public static class GrayscaleExtensions - { - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source) - => Grayscale(source, GrayscaleMode.Bt709); + /// The image this method extends. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source) + => Grayscale(source, GrayscaleMode.Bt709); - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) - => Grayscale(source, GrayscaleMode.Bt709, amount); + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount) + => Grayscale(source, GrayscaleMode.Bt709, amount); - /// - /// Applies grayscale toning to the image with the given . - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) - => Grayscale(source, mode, 1F); + /// + /// Applies grayscale toning to the image with the given . + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode) + => Grayscale(source, mode, 1F); - /// - /// Applies grayscale toning to the image with the given using the given amount. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); + /// + /// Applies grayscale toning to the image with the given using the given amount. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount) + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); - source.ApplyProcessor(processor); - return source; - } + source.ApplyProcessor(processor); + return source; + } - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) - => Grayscale(source, 1F, rectangle); + /// + /// Applies grayscale toning to the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, Rectangle rectangle) + => Grayscale(source, 1F, rectangle); - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) - => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, float amount, Rectangle rectangle) + => Grayscale(source, GrayscaleMode.Bt709, amount, rectangle); - /// - /// Applies grayscale toning to the image. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) - => Grayscale(source, mode, 1F, rectangle); + /// + /// Applies grayscale toning to the image. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, Rectangle rectangle) + => Grayscale(source, mode, 1F, rectangle); - /// - /// Applies grayscale toning to the image using the given amount. - /// - /// The image this method extends. - /// The formula to apply to perform the operation. - /// The proportion of the conversion. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) - { - IImageProcessor processor = mode == GrayscaleMode.Bt709 - ? (IImageProcessor)new GrayscaleBt709Processor(amount) - : new GrayscaleBt601Processor(amount); + /// + /// Applies grayscale toning to the image using the given amount. + /// + /// The image this method extends. + /// The formula to apply to perform the operation. + /// The proportion of the conversion. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Grayscale(this IImageProcessingContext source, GrayscaleMode mode, float amount, Rectangle rectangle) + { + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor(amount) + : new GrayscaleBt601Processor(amount); - source.ApplyProcessor(processor, rectangle); - return source; - } + source.ApplyProcessor(processor, rectangle); + return source; } } diff --git a/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs index 5dfa18fc92..3ec339e9c9 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/HueExtensions.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the hue component of an +/// using Mutate/Clone. +/// +public static class HueExtensions { /// - /// Defines extensions that allow the alteration of the hue component of an - /// using Mutate/Clone. + /// Alters the hue component of the image. /// - public static class HueExtensions - { - /// - /// Alters the hue component of the image. - /// - /// The image this method extends. - /// The rotation angle in degrees to adjust the hue. - /// The to allow chaining of operations. - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) - => source.ApplyProcessor(new HueProcessor(degrees)); + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// The to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees) + => source.ApplyProcessor(new HueProcessor(degrees)); - /// - /// Alters the hue component of the image. - /// - /// The image this method extends. - /// The rotation angle in degrees to adjust the hue. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) - => source.ApplyProcessor(new HueProcessor(degrees), rectangle); - } + /// + /// Alters the hue component of the image. + /// + /// The image this method extends. + /// The rotation angle in degrees to adjust the hue. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Hue(this IImageProcessingContext source, float degrees, Rectangle rectangle) + => source.ApplyProcessor(new HueProcessor(degrees), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs index 30a780e977..3c7845ec83 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/InvertExtensions.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the inversion of colors of an +/// using Mutate/Clone. +/// +public static class InvertExtensions { /// - /// Defines extensions that allow the inversion of colors of an - /// using Mutate/Clone. + /// Inverts the colors of the image. /// - public static class InvertExtensions - { - /// - /// Inverts the colors of the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Invert(this IImageProcessingContext source) - => source.ApplyProcessor(new InvertProcessor(1F)); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Invert(this IImageProcessingContext source) + => source.ApplyProcessor(new InvertProcessor(1F)); - /// - /// Inverts the colors of the image. - /// - /// 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 Invert(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new InvertProcessor(1F), rectangle); - } + /// + /// Inverts the colors of the image. + /// + /// 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 Invert(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new InvertProcessor(1F), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs index ffacb2f199..8aa8260676 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/KodachromeExtensions.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the recreation of an old Kodachrome camera effect on an +/// using Mutate/Clone. +/// +public static class KodachromeExtensions { /// - /// Defines extensions that allow the recreation of an old Kodachrome camera effect on an - /// using Mutate/Clone. + /// Alters the colors of the image recreating an old Kodachrome camera effect. /// - public static class KodachromeExtensions - { - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) - => source.ApplyProcessor(new KodachromeProcessor()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Kodachrome(this IImageProcessingContext source) + => source.ApplyProcessor(new KodachromeProcessor()); - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// 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 Kodachrome(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new KodachromeProcessor(), rectangle); - } + /// + /// Alters the colors of the image recreating an old Kodachrome camera effect. + /// + /// 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 Kodachrome(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new KodachromeProcessor(), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs index 9d30bd7d3b..fb937d9728 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/LightnessExtensions.cs @@ -3,41 +3,40 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the lightness component of an +/// using Mutate/Clone. +/// +public static class LightnessExtensions { /// - /// Defines extensions that allow the alteration of the lightness component of an - /// using Mutate/Clone. + /// Alters the lightness component of the image. /// - public static class LightnessExtensions - { - /// - /// Alters the lightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new LightnessProcessor(amount)); + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new LightnessProcessor(amount)); - /// - /// Alters the lightness component of the image. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new LightnessProcessor(amount), rectangle); - } + /// + /// Alters the lightness component of the image. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new LightnessProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs index 7b510834e0..816647bd42 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/LomographExtensions.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the recreation of an old Lomograph camera effect on an +/// using Mutate/Clone. +/// +public static class LomographExtensions { /// - /// Defines extensions that allow the recreation of an old Lomograph camera effect on an - /// using Mutate/Clone. + /// Alters the colors of the image recreating an old Lomograph camera effect. /// - public static class LomographExtensions - { - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Lomograph(this IImageProcessingContext source) - => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions())); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Lomograph(this IImageProcessingContext source) + => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions())); - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// 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 Lomograph(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle); - } + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// 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 Lomograph(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new LomographProcessor(source.GetGraphicsOptions()), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs index 2d4d27afd9..e704dd763c 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/OpacityExtensions.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the opacity component of an +/// using Mutate/Clone. +/// +public static class OpacityExtensions { /// - /// Defines extensions that allow the alteration of the opacity component of an - /// using Mutate/Clone. + /// Multiplies the alpha component of the image. /// - public static class OpacityExtensions - { - /// - /// Multiplies the alpha component of the image. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new OpacityProcessor(amount)); + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Opacity(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new OpacityProcessor(amount)); - /// - /// Multiplies the alpha component of the image. - /// - /// The image this method extends. - /// The proportion of the conversion. 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 Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); - } + /// + /// Multiplies the alpha component of the image. + /// + /// The image this method extends. + /// The proportion of the conversion. 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 Opacity(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new OpacityProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs index 7bf63290d1..bab05c0365 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/PolaroidExtensions.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the recreation of an old Polaroid camera effect on an +/// using Mutate/Clone. +/// +public static class PolaroidExtensions { /// - /// Defines extensions that allow the recreation of an old Polaroid camera effect on an - /// using Mutate/Clone. + /// Alters the colors of the image recreating an old Polaroid camera effect. /// - public static class PolaroidExtensions - { - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Polaroid(this IImageProcessingContext source) - => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions())); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Polaroid(this IImageProcessingContext source) + => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions())); - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// 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 Polaroid(this IImageProcessingContext source, Rectangle rectangle) - => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle); - } + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// 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 Polaroid(this IImageProcessingContext source, Rectangle rectangle) + => source.ApplyProcessor(new PolaroidProcessor(source.GetGraphicsOptions()), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs index c49ac1bb5e..03e6ddcba5 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/SaturateExtensions.cs @@ -3,41 +3,40 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the alteration of the saturation component of an +/// using Mutate/Clone. +/// +public static class SaturateExtensions { /// - /// Defines extensions that allow the alteration of the saturation component of an - /// using Mutate/Clone. + /// Alters the saturation component of the image. /// - public static class SaturateExtensions - { - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The to allow chaining of operations. - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SaturateProcessor(amount)); + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The to allow chaining of operations. + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new SaturateProcessor(amount)); - /// - /// Alters the saturation component of the image. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The image this method extends. - /// The proportion of the conversion. Must be greater than or equal to 0. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); - } + /// + /// Alters the saturation component of the image. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The image this method extends. + /// The proportion of the conversion. Must be greater than or equal to 0. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Saturate(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SaturateProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs index eb02658b4f..01ddccba0d 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/SepiaExtensions.cs @@ -3,52 +3,51 @@ using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of sepia toning on an +/// using Mutate/Clone. +/// +public static class SepiaExtensions { /// - /// Defines extensions that allow the application of sepia toning on an - /// using Mutate/Clone. + /// Applies sepia toning to the image. /// - public static class SepiaExtensions - { - /// - /// Applies sepia toning to the image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source) - => Sepia(source, 1F); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source) + => Sepia(source, 1F); - /// - /// Applies sepia toning to the image using the given amount. - /// - /// The image this method extends. - /// The proportion of the conversion. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) - => source.ApplyProcessor(new SepiaProcessor(amount)); + /// + /// Applies sepia toning to the image using the given amount. + /// + /// The image this method extends. + /// The proportion of the conversion. Must be between 0 and 1. + /// The to allow chaining of operations. + public static IImageProcessingContext Sepia(this IImageProcessingContext source, float amount) + => source.ApplyProcessor(new SepiaProcessor(amount)); - /// - /// Applies sepia toning to the image. - /// - /// 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 Sepia(this IImageProcessingContext source, Rectangle rectangle) - => Sepia(source, 1F, rectangle); + /// + /// Applies sepia toning to the image. + /// + /// 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 Sepia(this IImageProcessingContext source, Rectangle rectangle) + => Sepia(source, 1F, rectangle); - /// - /// Applies sepia toning to the image. - /// - /// The image this method extends. - /// The proportion of the conversion. 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 Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) - => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); - } + /// + /// Applies sepia toning to the image. + /// + /// The image this method extends. + /// The proportion of the conversion. 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 Sepia(this IImageProcessingContext source, float amount, Rectangle rectangle) + => source.ApplyProcessor(new SepiaProcessor(amount), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs index 8346be9516..ab5b8e3e1b 100644 --- a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs @@ -3,30 +3,29 @@ using SixLabors.ImageSharp.Processing.Processors.Normalization; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extension that allow the adjustment of the contrast of an image via its histogram. +/// +public static class HistogramEqualizationExtensions { /// - /// Defines extension that allow the adjustment of the contrast of an image via its histogram. + /// Equalizes the histogram of an image to increases the contrast. /// - public static class HistogramEqualizationExtensions - { - /// - /// Equalizes the histogram of an image to increases the contrast. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) => - HistogramEqualization(source, new HistogramEqualizationOptions()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) => + HistogramEqualization(source, new HistogramEqualizationOptions()); - /// - /// Equalizes the histogram of an image to increases the contrast. - /// - /// The image this method extends. - /// The histogram equalization options to use. - /// The to allow chaining of operations. - public static IImageProcessingContext HistogramEqualization( - this IImageProcessingContext source, - HistogramEqualizationOptions options) => - source.ApplyProcessor(HistogramEqualizationProcessor.FromOptions(options)); - } + /// + /// Equalizes the histogram of an image to increases the contrast. + /// + /// The image this method extends. + /// The histogram equalization options to use. + /// The to allow chaining of operations. + public static IImageProcessingContext HistogramEqualization( + this IImageProcessingContext source, + HistogramEqualizationOptions options) => + source.ApplyProcessor(HistogramEqualizationProcessor.FromOptions(options)); } diff --git a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs index 04363014c7..4369f45326 100644 --- a/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Overlays/BackgroundColorExtensions.cs @@ -3,66 +3,65 @@ using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extension methods to replace the background color of an +/// using Mutate/Clone. +/// +public static class BackgroundColorExtensions { /// - /// Defines extension methods to replace the background color of an - /// using Mutate/Clone. + /// Replaces the background color of image with the given one. /// - public static class BackgroundColorExtensions - { - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The color to set as the background. - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => - BackgroundColor(source, source.GetGraphicsOptions(), color); + /// The image this method extends. + /// The color to set as the background. + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) => + BackgroundColor(source, source.GetGraphicsOptions(), color); - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - Color color, - Rectangle rectangle) => - BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle); + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + Color color, + Rectangle rectangle) => + BackgroundColor(source, source.GetGraphicsOptions(), color, rectangle); - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the background. - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.ApplyProcessor(new BackgroundColorProcessor(options, color)); + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.ApplyProcessor(new BackgroundColorProcessor(options, color)); - /// - /// Replaces the background color of image with the given one. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the background. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BackgroundColor( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - Rectangle rectangle) => - source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle); - } + /// + /// Replaces the background color of image with the given one. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext BackgroundColor( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + Rectangle rectangle) => + source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs index 682a0f2634..c588ae9647 100644 --- a/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Overlays/GlowExtensions.cs @@ -3,171 +3,170 @@ using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of a radial glow on an +/// using Mutate/Clone. +/// +public static class GlowExtensions { /// - /// Defines extensions that allow the application of a radial glow on an - /// using Mutate/Clone. + /// Applies a radial glow effect to an image. /// - public static class GlowExtensions - { - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source) => - Glow(source, source.GetGraphicsOptions()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source) => + Glow(source, source.GetGraphicsOptions()); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The color to set as the glow. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) - { - return Glow(source, source.GetGraphicsOptions(), color); - } + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The color to set as the glow. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color) + { + return Glow(source, source.GetGraphicsOptions(), color); + } - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The the radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => - Glow(source, source.GetGraphicsOptions(), radius); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The the radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) => + Glow(source, source.GetGraphicsOptions(), radius); - /// - /// Applies a radial glow effect to an image. - /// - /// 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 Glow(this IImageProcessingContext source, Rectangle rectangle) => - source.Glow(source.GetGraphicsOptions(), rectangle); + /// + /// Applies a radial glow effect to an image. + /// + /// 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 Glow(this IImageProcessingContext source, Rectangle rectangle) => + source.Glow(source.GetGraphicsOptions(), rectangle); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + Color color, + float radius, + Rectangle rectangle) => + source.Glow(source.GetGraphicsOptions(), color, ValueSize.Absolute(radius), rectangle); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f)); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow(this IImageProcessingContext source, GraphicsOptions options) => + source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f)); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.Glow(options, color, ValueSize.PercentageOfWidth(0.5f)); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The the radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - float radius) => - source.Glow(options, Color.Black, ValueSize.Absolute(radius)); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The the radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + float radius) => + source.Glow(options, Color.Black, ValueSize.Absolute(radius)); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Rectangle rectangle) => + source.Glow(options, Color.Black, ValueSize.PercentageOfWidth(0.5f), rectangle); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radius, - Rectangle rectangle) => - source.Glow(options, color, ValueSize.Absolute(radius), rectangle); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float radius, + Rectangle rectangle) => + source.Glow(options, color, ValueSize.Absolute(radius), rectangle); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius, - Rectangle rectangle) => - source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle); + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + private static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radius, + Rectangle rectangle) => + source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle); - /// - /// Applies a radial glow effect to an image. - /// - /// The image this method extends. - /// The options effecting things like blending. - /// The color to set as the glow. - /// The the radius. - /// The to allow chaining of operations. - private static IImageProcessingContext Glow( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radius) => - source.ApplyProcessor(new GlowProcessor(options, color, radius)); - } + /// + /// Applies a radial glow effect to an image. + /// + /// The image this method extends. + /// The options effecting things like blending. + /// The color to set as the glow. + /// The the radius. + /// The to allow chaining of operations. + private static IImageProcessingContext Glow( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radius) => + source.ApplyProcessor(new GlowProcessor(options, color, radius)); } diff --git a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs index 7e00b2d38e..379a2f32ab 100644 --- a/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Overlays/VignetteExtensions.cs @@ -3,175 +3,174 @@ using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of a radial glow to an +/// using Mutate/Clone. +/// +public static class VignetteExtensions { /// - /// Defines extensions that allow the application of a radial glow to an - /// using Mutate/Clone. + /// Applies a radial vignette effect to an image. /// - public static class VignetteExtensions - { - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source) => - Vignette(source, source.GetGraphicsOptions()); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source) => + Vignette(source, source.GetGraphicsOptions()); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The color to set as the vignette. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => - Vignette(source, source.GetGraphicsOptions(), color); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The color to set as the vignette. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) => + Vignette(source, source.GetGraphicsOptions(), color); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The the x-radius. - /// The the y-radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - float radiusX, - float radiusY) => - Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + float radiusX, + float radiusY) => + Vignette(source, source.GetGraphicsOptions(), radiusX, radiusY); - /// - /// Applies a radial vignette effect to an image. - /// - /// 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 Vignette(this IImageProcessingContext source, Rectangle rectangle) => - Vignette(source, source.GetGraphicsOptions(), rectangle); + /// + /// Applies a radial vignette effect to an image. + /// + /// 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 Vignette(this IImageProcessingContext source, Rectangle rectangle) => + Vignette(source, source.GetGraphicsOptions(), rectangle); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + Color color, + float radiusX, + float radiusY, + Rectangle rectangle) => + source.Vignette(source.GetGraphicsOptions(), color, radiusX, radiusY, rectangle); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette(this IImageProcessingContext source, GraphicsOptions options) => + source.VignetteInternal( + options, + Color.Black, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f)); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color) => - source.VignetteInternal( - options, - color, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f)); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Color color) => + source.VignetteInternal( + options, + color, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f)); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The the x-radius. - /// The the y-radius. - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - float radiusX, - float radiusY) => - source.VignetteInternal(options, Color.Black, radiusX, radiusY); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The the x-radius. + /// The the y-radius. + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + float radiusX, + float radiusY) => + source.VignetteInternal(options, Color.Black, radiusX, radiusY); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Rectangle rectangle) => - source.VignetteInternal( - options, - Color.Black, - ValueSize.PercentageOfWidth(.5f), - ValueSize.PercentageOfHeight(.5f), - rectangle); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Rectangle rectangle) => + source.VignetteInternal( + options, + Color.Black, + ValueSize.PercentageOfWidth(.5f), + ValueSize.PercentageOfHeight(.5f), + rectangle); - /// - /// Applies a radial vignette effect to an image. - /// - /// The image this method extends. - /// The options effecting pixel blending. - /// The color to set as the vignette. - /// The the x-radius. - /// The the y-radius. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Vignette( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - float radiusX, - float radiusY, - Rectangle rectangle) => - source.VignetteInternal(options, color, radiusX, radiusY, rectangle); + /// + /// Applies a radial vignette effect to an image. + /// + /// The image this method extends. + /// The options effecting pixel blending. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Vignette( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + float radiusX, + float radiusY, + Rectangle rectangle) => + source.VignetteInternal(options, color, radiusX, radiusY, rectangle); - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY, - Rectangle rectangle) => - source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle); + private static IImageProcessingContext VignetteInternal( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radiusX, + ValueSize radiusY, + Rectangle rectangle) => + source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle); - private static IImageProcessingContext VignetteInternal( - this IImageProcessingContext source, - GraphicsOptions options, - Color color, - ValueSize radiusX, - ValueSize radiusY) => - source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY)); - } + private static IImageProcessingContext VignetteInternal( + this IImageProcessingContext source, + GraphicsOptions options, + Color color, + ValueSize radiusX, + ValueSize radiusY) => + source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY)); } diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs index deed044543..fdf0967c53 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -1,112 +1,110 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the computation of image integrals on an +/// +public static partial class ProcessingExtensions { /// - /// Defines extensions that allow the computation of image integrals on an + /// Apply an image integral. + /// + /// The image on which to apply the integral. + /// The type of the pixel. + /// The containing all the sums. + public static Buffer2D CalculateIntegralImage(this Image source) + where TPixel : unmanaged, IPixel + => CalculateIntegralImage(source.Frames.RootFrame); + + /// + /// Apply an image integral. + /// + /// The image on which to apply the integral. + /// The bounds within the image frame to calculate. + /// The type of the pixel. + /// The containing all the sums. + public static Buffer2D CalculateIntegralImage(this Image source, Rectangle bounds) + where TPixel : unmanaged, IPixel + => CalculateIntegralImage(source.Frames.RootFrame, bounds); + + /// + /// Apply an image integral. /// - public static partial class ProcessingExtensions + /// The image frame on which to apply the integral. + /// The type of the pixel. + /// The containing all the sums. + public static Buffer2D CalculateIntegralImage(this ImageFrame source) + where TPixel : unmanaged, IPixel + => source.CalculateIntegralImage(source.Bounds()); + + /// + /// Apply an image integral. + /// + /// The image frame on which to apply the integral. + /// The bounds within the image frame to calculate. + /// The type of the pixel. + /// The containing all the sums. + public static Buffer2D CalculateIntegralImage(this ImageFrame source, Rectangle bounds) + where TPixel : unmanaged, IPixel { - /// - /// Apply an image integral. - /// - /// The image on which to apply the integral. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this Image source) - where TPixel : unmanaged, IPixel - => CalculateIntegralImage(source.Frames.RootFrame); - - /// - /// Apply an image integral. - /// - /// The image on which to apply the integral. - /// The bounds within the image frame to calculate. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this Image source, Rectangle bounds) - where TPixel : unmanaged, IPixel - => CalculateIntegralImage(source.Frames.RootFrame, bounds); - - /// - /// Apply an image integral. - /// - /// The image frame on which to apply the integral. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this ImageFrame source) - where TPixel : unmanaged, IPixel - => source.CalculateIntegralImage(source.Bounds()); - - /// - /// Apply an image integral. - /// - /// The image frame on which to apply the integral. - /// The bounds within the image frame to calculate. - /// The type of the pixel. - /// The containing all the sums. - public static Buffer2D CalculateIntegralImage(this ImageFrame source, Rectangle bounds) - where TPixel : unmanaged, IPixel + Configuration configuration = source.GetConfiguration(); + + var interest = Rectangle.Intersect(bounds, source.Bounds()); + int startY = interest.Y; + int startX = interest.X; + int endY = interest.Height; + + Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(interest.Width, interest.Height); + ulong sumX0 = 0; + Buffer2D sourceBuffer = source.PixelBuffer; + + using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(interest.Width)) { - Configuration configuration = source.GetConfiguration(); + Span tempSpan = tempRow.GetSpan(); + Span sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length); + Span destRow = intImage.DangerousGetRowSpan(0); - var interest = Rectangle.Intersect(bounds, source.Bounds()); - int startY = interest.Y; - int startX = interest.X; - int endY = interest.Height; + PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); - Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(interest.Width, interest.Height); - ulong sumX0 = 0; - Buffer2D sourceBuffer = source.PixelBuffer; + // First row + for (int x = 0; x < tempSpan.Length; x++) + { + sumX0 += tempSpan[x].PackedValue; + destRow[x] = sumX0; + } - using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(interest.Width)) + Span previousDestRow = destRow; + + // All other rows + for (int y = 1; y < endY; y++) { - Span tempSpan = tempRow.GetSpan(); - Span sourceRow = sourceBuffer.DangerousGetRowSpan(startY).Slice(startX, tempSpan.Length); - Span destRow = intImage.DangerousGetRowSpan(0); + sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length); + destRow = intImage.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); - // First row - for (int x = 0; x < tempSpan.Length; x++) + // Process first column + sumX0 = tempSpan[0].PackedValue; + destRow[0] = sumX0 + previousDestRow[0]; + + // Process all other colmns + for (int x = 1; x < tempSpan.Length; x++) { sumX0 += tempSpan[x].PackedValue; - destRow[x] = sumX0; + destRow[x] = sumX0 + previousDestRow[x]; } - Span previousDestRow = destRow; - - // All other rows - for (int y = 1; y < endY; y++) - { - sourceRow = sourceBuffer.DangerousGetRowSpan(y + startY).Slice(startX, tempSpan.Length); - destRow = intImage.DangerousGetRowSpan(y); - - PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); - - // Process first column - sumX0 = tempSpan[0].PackedValue; - destRow[0] = sumX0 + previousDestRow[0]; - - // Process all other colmns - for (int x = 1; x < tempSpan.Length; x++) - { - sumX0 += tempSpan[x].PackedValue; - destRow[x] = sumX0 + previousDestRow[x]; - } - - previousDestRow = destRow; - } + previousDestRow = destRow; } - - return intImage; } + + return intImage; } } diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs index 6833a9ba40..575a6c1e4b 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -1,298 +1,296 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Adds extensions that allow the processing of images to the type. +/// +public static partial class ProcessingExtensions { /// - /// Adds extensions that allow the processing of images to the type. + /// Mutates the source image by applying the image operation to it. /// - public static partial class ProcessingExtensions + /// The image to mutate. + /// The operation to perform on the source. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Action operation) + => Mutate(source, source.GetConfiguration(), operation); + + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, Action operation) { - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The image to mutate. - /// The operation to perform on the source. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Action operation) - => Mutate(source, source.GetConfiguration(), operation); - - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, Action operation) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operation, nameof(operation)); + source.EnsureNotDisposed(); - source.AcceptVisitor(new ProcessingVisitor(configuration, operation, true)); - } + source.AcceptVisitor(new ProcessingVisitor(configuration, operation, true)); + } - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The pixel format. - /// The image to mutate. - /// The operation to perform on the source. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Action operation) - where TPixel : unmanaged, IPixel - => Mutate(source, source.GetConfiguration(), operation); - - /// - /// Mutates the source image by applying the image operation to it. - /// - /// The pixel format. - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, Action operation) + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The pixel format. + /// The image to mutate. + /// The operation to perform on the source. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Action operation) where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); + => Mutate(source, source.GetConfiguration(), operation); - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); + /// + /// Mutates the source image by applying the image operation to it. + /// + /// The pixel format. + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, Action operation) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operation, nameof(operation)); + source.EnsureNotDisposed(); - operation(operationsRunner); - } + IInternalImageProcessingContext operationsRunner + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); - /// - /// Mutates the source image by applying the operations to it. - /// - /// The pixel format. - /// The image to mutate. - /// The operations to perform on the source. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - => Mutate(source, source.GetConfiguration(), operations); - - /// - /// Mutates the source image by applying the operations to it. - /// - /// The pixel format. - /// The image to mutate. - /// The configuration which allows altering default behaviour or extending the library. - /// The operations to perform on the source. - /// The configuration is null. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operations, nameof(operations)); - source.EnsureNotDisposed(); + operation(operationsRunner); + } - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); + /// + /// Mutates the source image by applying the operations to it. + /// + /// The pixel format. + /// The image to mutate. + /// The operations to perform on the source. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, params IImageProcessor[] operations) + where TPixel : unmanaged, IPixel + => Mutate(source, source.GetConfiguration(), operations); - operationsRunner.ApplyProcessors(operations); - } + /// + /// Mutates the source image by applying the operations to it. + /// + /// The pixel format. + /// The image to mutate. + /// The configuration which allows altering default behaviour or extending the library. + /// The operations to perform on the source. + /// The configuration is null. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operations, nameof(operations)); + source.EnsureNotDisposed(); - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The image to clone. - /// The operation to perform on the clone. - /// The new . - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - public static Image Clone(this Image source, Action operation) - => Clone(source, source.GetConfiguration(), operation); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new . - public static Image Clone(this Image source, Configuration configuration, Action operation) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); - - var visitor = new ProcessingVisitor(configuration, operation, false); - source.AcceptVisitor(visitor); - return visitor.ResultImage; - } + IInternalImageProcessingContext operationsRunner + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, true); - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The pixel format. - /// The image to clone. - /// The operation to perform on the clone. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new . - public static Image Clone(this Image source, Action operation) - where TPixel : unmanaged, IPixel - => Clone(source, source.GetConfiguration(), operation); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operation. - /// - /// The pixel format. - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operation to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operation is null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, Configuration configuration, Action operation) + operationsRunner.ApplyProcessors(operations); + } + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The image to clone. + /// The operation to perform on the clone. + /// The new . + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + public static Image Clone(this Image source, Action operation) + => Clone(source, source.GetConfiguration(), operation); + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new . + public static Image Clone(this Image source, Configuration configuration, Action operation) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operation, nameof(operation)); + source.EnsureNotDisposed(); + + var visitor = new ProcessingVisitor(configuration, operation, false); + source.AcceptVisitor(visitor); + return visitor.ResultImage; + } + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The pixel format. + /// The image to clone. + /// The operation to perform on the clone. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new . + public static Image Clone(this Image source, Action operation) where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operation, nameof(operation)); - source.EnsureNotDisposed(); + => Clone(source, source.GetConfiguration(), operation); + + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operation. + /// + /// The pixel format. + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operation to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operation is null. + /// The source has been disposed. + /// The processing operation failed. + /// The new + public static Image Clone(this Image source, Configuration configuration, Action operation) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operation, nameof(operation)); + source.EnsureNotDisposed(); - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); + IInternalImageProcessingContext operationsRunner + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); - operation(operationsRunner); - return operationsRunner.GetResultImage(); - } + operation(operationsRunner); + return operationsRunner.GetResultImage(); + } - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operations. - /// - /// The pixel format. - /// The image to clone. - /// The operations to perform on the clone. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, params IImageProcessor[] operations) - where TPixel : unmanaged, IPixel - => Clone(source, source.GetConfiguration(), operations); - - /// - /// Creates a deep clone of the current image. The clone is then mutated by the given operations. - /// - /// The pixel format. - /// The image to clone. - /// The configuration which allows altering default behaviour or extending the library. - /// The operations to perform on the clone. - /// The configuration is null. - /// The source is null. - /// The operations are null. - /// The source has been disposed. - /// The processing operation failed. - /// The new - public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations) + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operations. + /// + /// The pixel format. + /// The image to clone. + /// The operations to perform on the clone. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + /// The new + public static Image Clone(this Image source, params IImageProcessor[] operations) where TPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(source, nameof(source)); - Guard.NotNull(operations, nameof(operations)); - source.EnsureNotDisposed(); + => Clone(source, source.GetConfiguration(), operations); - IInternalImageProcessingContext operationsRunner - = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); + /// + /// Creates a deep clone of the current image. The clone is then mutated by the given operations. + /// + /// The pixel format. + /// The image to clone. + /// The configuration which allows altering default behaviour or extending the library. + /// The operations to perform on the clone. + /// The configuration is null. + /// The source is null. + /// The operations are null. + /// The source has been disposed. + /// The processing operation failed. + /// The new + public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(operations, nameof(operations)); + source.EnsureNotDisposed(); - operationsRunner.ApplyProcessors(operations); - return operationsRunner.GetResultImage(); - } + IInternalImageProcessingContext operationsRunner + = configuration.ImageOperationsProvider.CreateImageProcessingContext(configuration, source, false); - /// - /// Applies the given collection against the context - /// - /// The image processing context. - /// The operations to perform on the source. - /// The processing operation failed. - /// The to allow chaining of operations. - public static IImageProcessingContext ApplyProcessors( - this IImageProcessingContext source, - params IImageProcessor[] operations) - { - foreach (IImageProcessor p in operations) - { - source = source.ApplyProcessor(p); - } + operationsRunner.ApplyProcessors(operations); + return operationsRunner.GetResultImage(); + } - return source; + /// + /// Applies the given collection against the context + /// + /// The image processing context. + /// The operations to perform on the source. + /// The processing operation failed. + /// The to allow chaining of operations. + public static IImageProcessingContext ApplyProcessors( + this IImageProcessingContext source, + params IImageProcessor[] operations) + { + foreach (IImageProcessor p in operations) + { + source = source.ApplyProcessor(p); } - private class ProcessingVisitor : IImageVisitor - { - private readonly Configuration configuration; + return source; + } + + private class ProcessingVisitor : IImageVisitor + { + private readonly Configuration configuration; - private readonly Action operation; + private readonly Action operation; - private readonly bool mutate; + private readonly bool mutate; - public ProcessingVisitor(Configuration configuration, Action operation, bool mutate) - { - this.configuration = configuration; - this.operation = operation; - this.mutate = mutate; - } + public ProcessingVisitor(Configuration configuration, Action operation, bool mutate) + { + this.configuration = configuration; + this.operation = operation; + this.mutate = mutate; + } - public Image ResultImage { get; private set; } + public Image ResultImage { get; private set; } - public void Visit(Image image) - where TPixel : unmanaged, IPixel - { - IInternalImageProcessingContext operationsRunner = - this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate); + public void Visit(Image image) + where TPixel : unmanaged, IPixel + { + IInternalImageProcessingContext operationsRunner = + this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate); - this.operation(operationsRunner); - this.ResultImage = operationsRunner.GetResultImage(); - } + this.operation(operationsRunner); + this.ResultImage = operationsRunner.GetResultImage(); } } } diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs index 98435c0a85..559477a05e 100644 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -3,52 +3,51 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of quantizing algorithms on an +/// using Mutate/Clone. +/// +public static class QuantizeExtensions { /// - /// Defines extensions that allow the application of quantizing algorithms on an - /// using Mutate/Clone. + /// Applies quantization to the image using the . /// - public static class QuantizeExtensions - { - /// - /// Applies quantization to the image using the . - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Quantize(this IImageProcessingContext source) => - Quantize(source, KnownQuantizers.Octree); + /// The image this method extends. + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source) => + Quantize(source, KnownQuantizers.Octree); - /// - /// Applies quantization to the image. - /// - /// The image this method extends. - /// The quantizer to apply to perform the operation. - /// 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. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// 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 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); - } + /// + /// 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); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs index c70cf8a836..a7b8dbf43f 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/AutoOrientExtensions.cs @@ -3,20 +3,19 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of auto-orientation operations to an +/// using Mutate/Clone. +/// +public static class AutoOrientExtensions { /// - /// Defines extensions that allow the application of auto-orientation operations to an - /// using Mutate/Clone. + /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. /// - public static class AutoOrientExtensions - { - /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. - /// - /// The image to auto rotate. - /// The to allow chaining of operations. - public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) - => source.ApplyProcessor(new AutoOrientProcessor()); - } + /// The image to auto rotate. + /// The to allow chaining of operations. + public static IImageProcessingContext AutoOrient(this IImageProcessingContext source) + => source.ApplyProcessor(new AutoOrientProcessor()); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs index fc360a4a55..434aeb6582 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/CropExtensions.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of cropping operations on an +/// using Mutate/Clone. +/// +public static class CropExtensions { /// - /// Defines extensions that allow the application of cropping operations on an - /// using Mutate/Clone. + /// Crops an image to the given width and height. /// - public static class CropExtensions - { - /// - /// Crops an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to allow chaining of operations. - public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => - Crop(source, new Rectangle(0, 0, width, height)); + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + public static IImageProcessingContext Crop(this IImageProcessingContext source, int width, int height) => + Crop(source, new Rectangle(0, 0, width, height)); - /// - /// Crops an image to the given rectangle. - /// - /// The image to crop. - /// - /// The structure that specifies the portion of the image object to retain. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => - source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); - } + /// + /// Crops an image to the given rectangle. + /// + /// The image to crop. + /// + /// The structure that specifies the portion of the image object to retain. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) => + source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs index 8802442d1b..14e7f9f134 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/EntropyCropExtensions.cs @@ -3,29 +3,28 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of entropy cropping operations on an +/// using Mutate/Clone. +/// +public static class EntropyCropExtensions { /// - /// Defines extensions that allow the application of entropy cropping operations on an - /// using Mutate/Clone. + /// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F. /// - public static class EntropyCropExtensions - { - /// - /// Crops an image to the area of greatest entropy using a threshold for entropic density of .5F. - /// - /// The image to crop. - /// The to allow chaining of operations. - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => - source.ApplyProcessor(new EntropyCropProcessor()); + /// The image to crop. + /// The to allow chaining of operations. + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source) => + source.ApplyProcessor(new EntropyCropProcessor()); - /// - /// Crops an image to the area of greatest entropy. - /// - /// The image to crop. - /// The threshold for entropic density. - /// The to allow chaining of operations. - public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => - source.ApplyProcessor(new EntropyCropProcessor(threshold)); - } + /// + /// Crops an image to the area of greatest entropy. + /// + /// The image to crop. + /// The threshold for entropic density. + /// The to allow chaining of operations. + public static IImageProcessingContext EntropyCrop(this IImageProcessingContext source, float threshold) => + source.ApplyProcessor(new EntropyCropProcessor(threshold)); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs index 03ba6aea18..243d818b84 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/FlipExtensions.cs @@ -3,21 +3,20 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of flipping operations on an +/// using Mutate/Clone. +/// +public static class FlipExtensions { /// - /// Defines extensions that allow the application of flipping operations on an - /// using Mutate/Clone. + /// Flips an image by the given instructions. /// - public static class FlipExtensions - { - /// - /// Flips an image by the given instructions. - /// - /// The image to rotate, flip, or both. - /// The to perform the flip. - /// The to allow chaining of operations. - public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) - => source.ApplyProcessor(new FlipProcessor(flipMode)); - } + /// The image to rotate, flip, or both. + /// The to perform the flip. + /// The to allow chaining of operations. + public static IImageProcessingContext Flip(this IImageProcessingContext source, FlipMode flipMode) + => source.ApplyProcessor(new FlipProcessor(flipMode)); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs index 8edb497822..dfade98475 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/PadExtensions.cs @@ -1,47 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Processing +/// +/// Defines extensions that allow the application of padding operations on an +/// using Mutate/Clone. +/// +public static class PadExtensions { /// - /// Defines extensions that allow the application of padding operations on an - /// using Mutate/Clone. + /// Evenly pads an image to fit the new dimensions. /// - public static class PadExtensions - { - /// - /// Evenly pads an image to fit the new dimensions. - /// - /// The source image to pad. - /// The new width. - /// The new height. - /// The to allow chaining of operations. - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) - => source.Pad(width, height, default); + /// The source image to pad. + /// The new width. + /// The new height. + /// The to allow chaining of operations. + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) + => source.Pad(width, height, default); - /// - /// Evenly pads an image to fit the new dimensions with the given background color. - /// - /// The source image to pad. - /// The new width. - /// The new height. - /// The background color with which to pad the image. - /// The to allow chaining of operations. - public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) + /// + /// Evenly pads an image to fit the new dimensions with the given background color. + /// + /// The source image to pad. + /// The new width. + /// The new height. + /// The background color with which to pad the image. + /// The to allow chaining of operations. + public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height, Color color) + { + Size size = source.GetCurrentSize(); + var options = new ResizeOptions { - Size size = source.GetCurrentSize(); - var options = new ResizeOptions - { - // Prevent downsizing. - Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)), - Mode = ResizeMode.BoxPad, - Sampler = KnownResamplers.NearestNeighbor, - PadColor = color - }; + // Prevent downsizing. + Size = new Size(Math.Max(width, size.Width), Math.Max(height, size.Height)), + Mode = ResizeMode.BoxPad, + Sampler = KnownResamplers.NearestNeighbor, + PadColor = color + }; - return source.Resize(options); - } + return source.Resize(options); } } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs index 89229646e2..7580f64690 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/ResizeExtensions.cs @@ -3,174 +3,173 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of resize operations on an +/// using Mutate/Clone. +/// +public static class ResizeExtensions { /// - /// Defines extensions that allow the application of resize operations on an - /// using Mutate/Clone. + /// Resizes an image to the given . /// - public static class ResizeExtensions - { - /// - /// Resizes an image to the given . - /// - /// The image to resize. - /// The target image size. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); + /// The image to resize. + /// The target image size. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); - /// - /// Resizes an image to the given . - /// - /// The image to resize. - /// The target image size. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) - => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); + /// + /// Resizes an image to the given . + /// + /// The image to resize. + /// The target image size. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) - => Resize(source, width, height, KnownResamplers.Bicubic, false); + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) + => Resize(source, width, height, KnownResamplers.Bicubic, false); - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) - => Resize(source, width, height, KnownResamplers.Bicubic, compand); + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) + => Resize(source, width, height, KnownResamplers.Bicubic, compand); - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) - => Resize(source, width, height, sampler, false); + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) + => Resize(source, width, height, sampler, false); - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image size. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) - => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image size. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) + => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) - => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) + => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); - /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle sourceRectangle, - Rectangle targetRectangle, - bool compand) + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle sourceRectangle, + Rectangle targetRectangle, + bool compand) + { + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; + Size = new Size(width, height), + Mode = ResizeMode.Manual, + Sampler = sampler, + TargetRectangle = targetRectangle, + Compand = compand + }; - return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle); - } + return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle); + } - /// - /// Resizes an image to the given width and height with the given sampler and source rectangle. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// The to allow chaining of operations. - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize( - this IImageProcessingContext source, - int width, - int height, - IResampler sampler, - Rectangle targetRectangle, - bool compand) + /// + /// Resizes an image to the given width and height with the given sampler and source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The to allow chaining of operations. + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle targetRectangle, + bool compand) + { + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.Manual, - Sampler = sampler, - TargetRectangle = targetRectangle, - Compand = compand - }; - - return Resize(source, options); - } + Size = new Size(width, height), + Mode = ResizeMode.Manual, + Sampler = sampler, + TargetRectangle = targetRectangle, + Compand = compand + }; - /// - /// Resizes an image in accordance with the given . - /// - /// The image to resize. - /// The resize options. - /// The to allow chaining of operations. - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. - public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) - => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); + return Resize(source, options); } + + /// + /// Resizes an image in accordance with the given . + /// + /// The image to resize. + /// The resize options. + /// The to allow chaining of operations. + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. + public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) + => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs index 477fdec80b..ac3c464468 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/RotateExtensions.cs @@ -3,43 +3,42 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of rotate operations on an +/// using Mutate/Clone. +/// +public static class RotateExtensions { /// - /// Defines extensions that allow the application of rotate operations on an - /// using Mutate/Clone. + /// Rotates and flips an image by the given instructions. /// - public static class RotateExtensions - { - /// - /// Rotates and flips an image by the given instructions. - /// - /// The image to rotate. - /// The to perform the rotation. - /// The to allow chaining of operations. - public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => - Rotate(source, (float)rotateMode); + /// The image to rotate. + /// The to perform the rotation. + /// The to allow chaining of operations. + public static IImageProcessingContext Rotate(this IImageProcessingContext source, RotateMode rotateMode) => + Rotate(source, (float)rotateMode); - /// - /// Rotates an image by the given angle in degrees. - /// - /// The image to rotate. - /// The angle in degrees to perform the rotation. - /// The to allow chaining of operations. - public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => - Rotate(source, degrees, KnownResamplers.Bicubic); + /// + /// Rotates an image by the given angle in degrees. + /// + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The to allow chaining of operations. + public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees) => + Rotate(source, degrees, KnownResamplers.Bicubic); - /// - /// Rotates an image by the given angle in degrees using the specified sampling algorithm. - /// - /// The image to rotate. - /// The angle in degrees to perform the rotation. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Rotate( - this IImageProcessingContext source, - float degrees, - IResampler sampler) => - source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); - } + /// + /// Rotates an image by the given angle in degrees using the specified sampling algorithm. + /// + /// The image to rotate. + /// The angle in degrees to perform the rotation. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Rotate( + this IImageProcessingContext source, + float degrees, + IResampler sampler) => + source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs index 692738f2ad..fe66af6aa6 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/RotateFlipExtensions.cs @@ -1,22 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of rotate-flip operations on an +/// using Mutate/Clone. +/// +public static class RotateFlipExtensions { /// - /// Defines extensions that allow the application of rotate-flip operations on an - /// using Mutate/Clone. + /// Rotates and flips an image by the given instructions. /// - public static class RotateFlipExtensions - { - /// - /// Rotates and flips an image by the given instructions. - /// - /// The image to rotate, flip, or both. - /// The to perform the rotation. - /// The to perform the flip. - /// The to allow chaining of operations. - public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) - => source.Rotate(rotateMode).Flip(flipMode); - } + /// The image to rotate, flip, or both. + /// The to perform the rotation. + /// The to perform the flip. + /// The to allow chaining of operations. + public static IImageProcessingContext RotateFlip(this IImageProcessingContext source, RotateMode rotateMode, FlipMode flipMode) + => source.Rotate(rotateMode).Flip(flipMode); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs index abdbd52b3a..6b2b8b15f9 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/SkewExtensions.cs @@ -3,38 +3,37 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of skew operations on an +/// using Mutate/Clone. +/// +public static class SkewExtensions { /// - /// Defines extensions that allow the application of skew operations on an - /// using Mutate/Clone. + /// Skews an image by the given angles in degrees. /// - public static class SkewExtensions - { - /// - /// Skews an image by the given angles in degrees. - /// - /// The image to skew. - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The to allow chaining of operations. - public static IImageProcessingContext - Skew(this IImageProcessingContext source, float degreesX, float degreesY) => - Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); + /// The image to skew. + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The to allow chaining of operations. + public static IImageProcessingContext + Skew(this IImageProcessingContext source, float degreesX, float degreesY) => + Skew(source, degreesX, degreesY, KnownResamplers.Bicubic); - /// - /// Skews an image by the given angles in degrees using the specified sampling algorithm. - /// - /// The image to skew. - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Skew( - this IImageProcessingContext source, - float degreesX, - float degreesY, - IResampler sampler) => - source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); - } + /// + /// Skews an image by the given angles in degrees using the specified sampling algorithm. + /// + /// The image to skew. + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Skew( + this IImageProcessingContext source, + float degreesX, + float degreesY, + IResampler sampler) => + source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs index b245c49dee..73ec111c95 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/SwizzleExtensions.cs @@ -3,22 +3,21 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing.Extensions.Transforms +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of swizzle operations on an +/// +public static class SwizzleExtensions { /// - /// Defines extensions that allow the application of swizzle operations on an + /// Swizzles an image. /// - public static class SwizzleExtensions - { - /// - /// Swizzles an image. - /// - /// The image to swizzle. - /// The swizzler function. - /// The swizzler function type. - /// The to allow chaining of operations. - public static IImageProcessingContext Swizzle(this IImageProcessingContext source, TSwizzler swizzler) - where TSwizzler : struct, ISwizzler - => source.ApplyProcessor(new SwizzleProcessor(swizzler)); - } + /// The image to swizzle. + /// The swizzler function. + /// The swizzler function type. + /// The to allow chaining of operations. + public static IImageProcessingContext Swizzle(this IImageProcessingContext source, TSwizzler swizzler) + where TSwizzler : struct, ISwizzler + => source.ApplyProcessor(new SwizzleProcessor(swizzler)); } diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs index d285f497a7..fbf3b3a6df 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -4,140 +4,139 @@ using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Defines extensions that allow the application of composable transform operations on an +/// using Mutate/Clone. +/// +public static class TransformExtensions { /// - /// Defines extensions that allow the application of composable transform operations on an - /// using Mutate/Clone. + /// Performs an affine transform of an image. /// - public static class TransformExtensions - { - /// - /// Performs an affine transform of an image. - /// - /// The image to transform. - /// The affine transform builder. - /// The - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - AffineTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); + /// The image to transform. + /// The affine transform builder. + /// The + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + AffineTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The affine transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - AffineTransformBuilder builder, - IResampler sampler) => - ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + AffineTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The affine transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - AffineTransformBuilder builder, - IResampler sampler) - { - Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); - return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); - } + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The affine transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + AffineTransformBuilder builder, + IResampler sampler) + { + Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } - /// - /// Performs an affine transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - Matrix3x2 transform, - Size targetDimensions, - IResampler sampler) - { - return ctx.ApplyProcessor( - new AffineTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); - } + /// + /// Performs an affine transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix3x2 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new AffineTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); + } - /// - /// Performs a projective transform of an image. - /// - /// The image to transform. - /// The affine transform builder. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext source, - ProjectiveTransformBuilder builder) => - Transform(source, builder, KnownResamplers.Bicubic); + /// + /// Performs a projective transform of an image. + /// + /// The image to transform. + /// The affine transform builder. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext source, + ProjectiveTransformBuilder builder) => + Transform(source, builder, KnownResamplers.Bicubic); - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The projective transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - ProjectiveTransformBuilder builder, - IResampler sampler) => - ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + ProjectiveTransformBuilder builder, + IResampler sampler) => + ctx.Transform(new Rectangle(Point.Empty, ctx.GetCurrentSize()), builder, sampler); - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The projective transform builder. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - ProjectiveTransformBuilder builder, - IResampler sampler) - { - Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); - return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); - } + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The projective transform builder. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + ProjectiveTransformBuilder builder, + IResampler sampler) + { + Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); + Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); + } - /// - /// Performs a projective transform of an image using the specified sampling algorithm. - /// - /// The . - /// The source rectangle - /// The transformation matrix. - /// The size of the result image. - /// The to perform the resampling. - /// The to allow chaining of operations. - public static IImageProcessingContext Transform( - this IImageProcessingContext ctx, - Rectangle sourceRectangle, - Matrix4x4 transform, - Size targetDimensions, - IResampler sampler) - { - return ctx.ApplyProcessor( - new ProjectiveTransformProcessor(transform, sampler, targetDimensions), - sourceRectangle); - } + /// + /// Performs a projective transform of an image using the specified sampling algorithm. + /// + /// The . + /// The source rectangle + /// The transformation matrix. + /// The size of the result image. + /// The to perform the resampling. + /// The to allow chaining of operations. + public static IImageProcessingContext Transform( + this IImageProcessingContext ctx, + Rectangle sourceRectangle, + Matrix4x4 transform, + Size targetDimensions, + IResampler sampler) + { + return ctx.ApplyProcessor( + new ProjectiveTransformProcessor(transform, sampler, targetDimensions), + sourceRectangle); } } diff --git a/src/ImageSharp/Processing/FlipMode.cs b/src/ImageSharp/Processing/FlipMode.cs index ad1d5062bf..3f1b19ce31 100644 --- a/src/ImageSharp/Processing/FlipMode.cs +++ b/src/ImageSharp/Processing/FlipMode.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Provides enumeration over how a image should be flipped. +/// +public enum FlipMode { /// - /// Provides enumeration over how a image should be flipped. + /// Don't flip the image. /// - public enum FlipMode - { - /// - /// Don't flip the image. - /// - None, + None, - /// - /// Flip the image horizontally. - /// - Horizontal, + /// + /// Flip the image horizontally. + /// + Horizontal, - /// - /// Flip the image vertically. - /// - Vertical, - } + /// + /// Flip the image vertically. + /// + Vertical, } diff --git a/src/ImageSharp/Processing/GrayscaleMode.cs b/src/ImageSharp/Processing/GrayscaleMode.cs index fbf92d2d7a..e5e73a005c 100644 --- a/src/ImageSharp/Processing/GrayscaleMode.cs +++ b/src/ImageSharp/Processing/GrayscaleMode.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Enumerates the various types of defined grayscale filters. +/// +public enum GrayscaleMode { /// - /// Enumerates the various types of defined grayscale filters. + /// ITU-R Recommendation BT.709 /// - public enum GrayscaleMode - { - /// - /// ITU-R Recommendation BT.709 - /// - Bt709, + Bt709, - /// - /// ITU-R Recommendation BT.601 - /// - Bt601 - } + /// + /// ITU-R Recommendation BT.601 + /// + Bt601 } diff --git a/src/ImageSharp/Processing/IImageProcessingContext.cs b/src/ImageSharp/Processing/IImageProcessingContext.cs index fe5688412e..01e0fab097 100644 --- a/src/ImageSharp/Processing/IImageProcessingContext.cs +++ b/src/ImageSharp/Processing/IImageProcessingContext.cs @@ -1,46 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Processing.Processors; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// A pixel-agnostic interface to queue up image operations to apply to an image. +/// +public interface IImageProcessingContext { /// - /// A pixel-agnostic interface to queue up image operations to apply to an image. + /// Gets the configuration which allows altering default behaviour or extending the library. /// - public interface IImageProcessingContext - { - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - Configuration Configuration { get; } + Configuration Configuration { get; } - /// - /// Gets a set of properties for the Image Processing Context. - /// - /// This can be used for storing global settings and defaults to be accessable to processors. - IDictionary Properties { get; } + /// + /// Gets a set of properties for the Image Processing Context. + /// + /// This can be used for storing global settings and defaults to be accessable to processors. + IDictionary Properties { get; } - /// - /// Gets the image dimensions at the current point in the processing pipeline. - /// - /// The . - Size GetCurrentSize(); + /// + /// Gets the image dimensions at the current point in the processing pipeline. + /// + /// The . + Size GetCurrentSize(); - /// - /// Adds the processor to the current set of image operations to be applied. - /// - /// The processor to apply. - /// The area to apply it to. - /// The current operations class to allow chaining of operations. - IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle); + /// + /// Adds the processor to the current set of image operations to be applied. + /// + /// The processor to apply. + /// The area to apply it to. + /// The current operations class to allow chaining of operations. + IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle); - /// - /// Adds the processor to the current set of image operations to be applied. - /// - /// The processor to apply. - /// The current operations class to allow chaining of operations. - IImageProcessingContext ApplyProcessor(IImageProcessor processor); - } + /// + /// Adds the processor to the current set of image operations to be applied. + /// + /// The processor to apply. + /// The current operations class to allow chaining of operations. + IImageProcessingContext ApplyProcessor(IImageProcessor processor); } diff --git a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs index 7ff77c9c62..970cae46c4 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -3,35 +3,34 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Represents an interface that will create IInternalImageProcessingContext instances +/// +internal interface IImageProcessingContextFactory { /// - /// Represents an interface that will create IInternalImageProcessingContext instances + /// Called during mutate operations to generate the image operations provider. /// - internal interface IImageProcessingContextFactory - { - /// - /// Called during mutate operations to generate the image operations provider. - /// - /// The pixel format - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// 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 : unmanaged, IPixel; - } + /// The pixel format + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. + /// 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 : unmanaged, IPixel; +} - /// - /// The default implementation of - /// - internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory +/// +/// The default implementation of +/// +internal class DefaultImageOperationsProviderFactory : IImageProcessingContextFactory +{ + /// + public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel { - /// - public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : unmanaged, IPixel - { - return new DefaultImageProcessorContext(configuration, source, mutate); - } + return new DefaultImageProcessorContext(configuration, source, mutate); } } diff --git a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs index 4fb9714155..95126c300d 100644 --- a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs +++ b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs @@ -3,20 +3,19 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// An interface for internal operations we don't want to expose on . +/// +/// The pixel type. +internal interface IInternalImageProcessingContext : IImageProcessingContext + where TPixel : unmanaged, IPixel { /// - /// An interface for internal operations we don't want to expose on . + /// Returns the result image to return by + /// (and other overloads). /// - /// The pixel type. - internal interface IInternalImageProcessingContext : IImageProcessingContext - where TPixel : unmanaged, IPixel - { - /// - /// Returns the result image to return by - /// (and other overloads). - /// - /// The current image or a new image depending on whether it is requested to mutate the source image. - Image GetResultImage(); - } + /// The current image or a new image depending on whether it is requested to mutate the source image. + Image GetResultImage(); } diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs index 6ed3c544a3..e386b6ff0b 100644 --- a/src/ImageSharp/Processing/KnownDitherings.cs +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -3,81 +3,80 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Contains reusable static instances of known dithering algorithms. +/// +public static class KnownDitherings { /// - /// Contains reusable static instances of known dithering algorithms. + /// 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 order ditherer using the 16x16 Bayer dithering matrix + /// + public static IDither Bayer16x16 { get; } = OrderedDither.Bayer16x16; + + /// + /// 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 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 order ditherer using the 16x16 Bayer dithering matrix - /// - public static IDither Bayer16x16 { get; } = OrderedDither.Bayer16x16; - - /// - /// 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; - } + public static IDither Stucki { get; } = ErrorDither.Stucki; } diff --git a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs index c994695a42..705dde1f18 100644 --- a/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs +++ b/src/ImageSharp/Processing/KnownEdgeDetectorKernels.cs @@ -3,61 +3,60 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Contains reusable static instances of known edge detection kernels. +/// +public static class KnownEdgeDetectorKernels { /// - /// Contains reusable static instances of known edge detection kernels. - /// - public static class KnownEdgeDetectorKernels - { - /// - /// Gets the Kayyali edge detector kernel. - /// - public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel; - - /// - /// Gets the Kirsch edge detector kernel. - /// - public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch; - - /// - /// Gets the Laplacian 3x3 edge detector kernel. - /// - public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3; - - /// - /// Gets the Laplacian 5x5 edge detector kernel. - /// - public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5; - - /// - /// Gets the Laplacian of Gaussian edge detector kernel. - /// - public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian; - - /// - /// Gets the Prewitt edge detector kernel. - /// - public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel; - - /// - /// Gets the Roberts-Cross edge detector kernel. - /// - public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel; - - /// - /// Gets the Robinson edge detector kernel. - /// - public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson; - - /// - /// Gets the Scharr edge detector kernel. - /// - public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel; - - /// - /// Gets the Sobel edge detector kernel. - /// - public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel; - } + /// Gets the Kayyali edge detector kernel. + /// + public static EdgeDetector2DKernel Kayyali { get; } = EdgeDetector2DKernel.KayyaliKernel; + + /// + /// Gets the Kirsch edge detector kernel. + /// + public static EdgeDetectorCompassKernel Kirsch { get; } = EdgeDetectorCompassKernel.Kirsch; + + /// + /// Gets the Laplacian 3x3 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian3x3 { get; } = EdgeDetectorKernel.Laplacian3x3; + + /// + /// Gets the Laplacian 5x5 edge detector kernel. + /// + public static EdgeDetectorKernel Laplacian5x5 { get; } = EdgeDetectorKernel.Laplacian5x5; + + /// + /// Gets the Laplacian of Gaussian edge detector kernel. + /// + public static EdgeDetectorKernel LaplacianOfGaussian { get; } = EdgeDetectorKernel.LaplacianOfGaussian; + + /// + /// Gets the Prewitt edge detector kernel. + /// + public static EdgeDetector2DKernel Prewitt { get; } = EdgeDetector2DKernel.PrewittKernel; + + /// + /// Gets the Roberts-Cross edge detector kernel. + /// + public static EdgeDetector2DKernel RobertsCross { get; } = EdgeDetector2DKernel.RobertsCrossKernel; + + /// + /// Gets the Robinson edge detector kernel. + /// + public static EdgeDetectorCompassKernel Robinson { get; } = EdgeDetectorCompassKernel.Robinson; + + /// + /// Gets the Scharr edge detector kernel. + /// + public static EdgeDetector2DKernel Scharr { get; } = EdgeDetector2DKernel.ScharrKernel; + + /// + /// Gets the Sobel edge detector kernel. + /// + public static EdgeDetector2DKernel Sobel { get; } = EdgeDetector2DKernel.SobelKernel; } diff --git a/src/ImageSharp/Processing/KnownFilterMatrices.cs b/src/ImageSharp/Processing/KnownFilterMatrices.cs index f2771d3aa1..b312287b52 100644 --- a/src/ImageSharp/Processing/KnownFilterMatrices.cs +++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - // Many of these matrices are translated from Chromium project where // SkScalar[] is memory-mapped to a row-major matrix. // The following translates to our column-major form: @@ -12,478 +10,477 @@ // |10|11|12|13|14| = |2|7|12|17| = |M31|M32|M33|M34| // |15|16|17|18|19| |3|8|13|18| |M41|M42|M43|M44| // |4|9|14|19| |M51|M52|M53|M54| -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// A collection of known values for composing filters +/// +public static class KnownFilterMatrices { /// - /// A collection of known values for composing filters + /// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness /// - public static class KnownFilterMatrices + public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix { - /// - /// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness - /// - public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix - { - M11 = .618F, - M12 = .163F, - M13 = .163F, - M21 = .320F, - M22 = .775F, - M23 = .320F, - M31 = .062F, - M32 = .062F, - M33 = .516F, - M44 = 1 - }; + M11 = .618F, + M12 = .163F, + M13 = .163F, + M21 = .320F, + M22 = .775F, + M23 = .320F, + M31 = .062F, + M32 = .062F, + M33 = .516F, + M44 = 1 + }; - /// - /// Gets a filter recreating Achromatopsia (Monochrome) color blindness. - /// - public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix - { - M11 = .299F, - M12 = .299F, - M13 = .299F, - M21 = .587F, - M22 = .587F, - M23 = .587F, - M31 = .114F, - M32 = .114F, - M33 = .114F, - M44 = 1F - }; + /// + /// Gets a filter recreating Achromatopsia (Monochrome) color blindness. + /// + public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix + { + M11 = .299F, + M12 = .299F, + M13 = .299F, + M21 = .587F, + M22 = .587F, + M23 = .587F, + M31 = .114F, + M32 = .114F, + M33 = .114F, + M44 = 1F + }; - /// - /// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness. - /// - public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix - { - M11 = .8F, - M12 = .258F, - M21 = .2F, - M22 = .742F, - M23 = .142F, - M33 = .858F, - M44 = 1F - }; + /// + /// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness. + /// + public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix + { + M11 = .8F, + M12 = .258F, + M21 = .2F, + M22 = .742F, + M23 = .142F, + M33 = .858F, + M44 = 1F + }; - /// - /// Gets a filter recreating Deuteranopia (Green-Blind) color blindness. - /// - public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix - { - M11 = .625F, - M12 = .7F, - M21 = .375F, - M22 = .3F, - M23 = .3F, - M33 = .7F, - M44 = 1F - }; + /// + /// Gets a filter recreating Deuteranopia (Green-Blind) color blindness. + /// + public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix + { + M11 = .625F, + M12 = .7F, + M21 = .375F, + M22 = .3F, + M23 = .3F, + M33 = .7F, + M44 = 1F + }; - /// - /// Gets a filter recreating Protanomaly (Red-Weak) color blindness. - /// - public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix - { - M11 = .817F, - M12 = .333F, - M21 = .183F, - M22 = .667F, - M23 = .125F, - M33 = .875F, - M44 = 1F - }; + /// + /// Gets a filter recreating Protanomaly (Red-Weak) color blindness. + /// + public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix + { + M11 = .817F, + M12 = .333F, + M21 = .183F, + M22 = .667F, + M23 = .125F, + M33 = .875F, + M44 = 1F + }; - /// - /// Gets a filter recreating Protanopia (Red-Blind) color blindness. - /// - public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix - { - M11 = .567F, - M12 = .558F, - M21 = .433F, - M22 = .442F, - M23 = .242F, - M33 = .758F, - M44 = 1F - }; + /// + /// Gets a filter recreating Protanopia (Red-Blind) color blindness. + /// + public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix + { + M11 = .567F, + M12 = .558F, + M21 = .433F, + M22 = .442F, + M23 = .242F, + M33 = .758F, + M44 = 1F + }; - /// - /// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness. - /// - public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix - { - M11 = .967F, - M21 = .33F, - M22 = .733F, - M23 = .183F, - M32 = .267F, - M33 = .817F, - M44 = 1F - }; + /// + /// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness. + /// + public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix + { + M11 = .967F, + M21 = .33F, + M22 = .733F, + M23 = .183F, + M32 = .267F, + M33 = .817F, + M44 = 1F + }; + + /// + /// Gets a filter recreating Tritanopia (Blue-Blind) color blindness. + /// + public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix + { + M11 = .95F, + M21 = .05F, + M22 = .433F, + M23 = .475F, + M32 = .567F, + M33 = .525F, + M44 = 1F + }; + + /// + /// Gets an approximated black and white filter + /// + public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix + { + M11 = 1.5F, + M12 = 1.5F, + M13 = 1.5F, + M21 = 1.5F, + M22 = 1.5F, + M23 = 1.5F, + M31 = 1.5F, + M32 = 1.5F, + M33 = 1.5F, + M44 = 1F, + M51 = -1F, + M52 = -1F, + M53 = -1F, + }; + + /// + /// Gets a filter recreating an old Kodachrome camera effect. + /// + public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix + { + M11 = .7297023F, + M22 = .6109577F, + M33 = .597218F, + M44 = 1F, + M51 = .105F, + M52 = .145F, + M53 = .155F, + } + + * CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F); - /// - /// Gets a filter recreating Tritanopia (Blue-Blind) color blindness. - /// - public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix + /// + /// Gets a filter recreating an old Lomograph camera effect. + /// + public static ColorMatrix LomographFilter { get; } = new ColorMatrix + { + M11 = 1.5F, + M22 = 1.45F, + M33 = 1.16F, + M44 = 1F, + M51 = -.1F, + M52 = -.02F, + M53 = -.07F, + } + + * CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F); + + /// + /// Gets a filter recreating an old Polaroid camera effect. + /// + public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix + { + M11 = 1.538F, + M12 = -.062F, + M13 = -.262F, + M21 = -.022F, + M22 = 1.578F, + M23 = -.022F, + M31 = .216F, + M32 = -.16F, + M33 = 1.5831F, + M44 = 1F, + M51 = .02F, + M52 = -.05F, + M53 = -.05F + }; + + /// + /// Create a brightness filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateBrightnessFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix { - M11 = .95F, - M21 = .05F, - M22 = .433F, - M23 = .475F, - M32 = .567F, - M33 = .525F, + M11 = amount, + M22 = amount, + M33 = amount, M44 = 1F }; + } + + /// + /// Create a contrast filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateContrastFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + float contrast = (-.5F * amount) + .5F; - /// - /// Gets an approximated black and white filter - /// - public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix + return new ColorMatrix { - M11 = 1.5F, - M12 = 1.5F, - M13 = 1.5F, - M21 = 1.5F, - M22 = 1.5F, - M23 = 1.5F, - M31 = 1.5F, - M32 = 1.5F, - M33 = 1.5F, + M11 = amount, + M22 = amount, + M33 = amount, M44 = 1F, - M51 = -1F, - M52 = -1F, - M53 = -1F, + M51 = contrast, + M52 = contrast, + M53 = contrast }; + } - /// - /// Gets a filter recreating an old Kodachrome camera effect. - /// - public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix - { - M11 = .7297023F, - M22 = .6109577F, - M33 = .597218F, - M44 = 1F, - M51 = .105F, - M52 = .145F, - M53 = .155F, - } + /// + /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.601. + /// + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateGrayscaleBt601Filter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); + amount = 1F - amount; - * CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F); + ColorMatrix m = default; + m.M11 = .299F + (.701F * amount); + m.M21 = .587F - (.587F * amount); + m.M31 = 1F - (m.M11 + m.M21); - /// - /// Gets a filter recreating an old Lomograph camera effect. - /// - public static ColorMatrix LomographFilter { get; } = new ColorMatrix - { - M11 = 1.5F, - M22 = 1.45F, - M33 = 1.16F, - M44 = 1F, - M51 = -.1F, - M52 = -.02F, - M53 = -.07F, - } + m.M12 = .299F - (.299F * amount); + m.M22 = .587F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); - * CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F); + m.M13 = .299F - (.299F * amount); + m.M23 = .587F - (.587F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - /// - /// Gets a filter recreating an old Polaroid camera effect. - /// - public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix - { - M11 = 1.538F, - M12 = -.062F, - M13 = -.262F, - M21 = -.022F, - M22 = 1.578F, - M23 = -.022F, - M31 = .216F, - M32 = -.16F, - M33 = 1.5831F, - M44 = 1F, - M51 = .02F, - M52 = -.05F, - M53 = -.05F - }; + return m; + } - /// - /// Create a brightness filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateBrightnessFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix - { - M11 = amount, - M22 = amount, - M33 = amount, - M44 = 1F - }; - } + /// + /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.709. + /// + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateGrayscaleBt709Filter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); + amount = 1F - amount; - /// - /// Create a contrast filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateContrastFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - float contrast = (-.5F * amount) + .5F; - - return new ColorMatrix - { - M11 = amount, - M22 = amount, - M33 = amount, - M44 = 1F, - M51 = contrast, - M52 = contrast, - M53 = contrast - }; - } + // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + ColorMatrix m = default; + m.M11 = .2126F + (.7874F * amount); + m.M21 = .7152F - (.7152F * amount); + m.M31 = 1F - (m.M11 + m.M21); - /// - /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.601. - /// - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateGrayscaleBt601Filter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); - amount = 1F - amount; + m.M12 = .2126F - (.2126F * amount); + m.M22 = .7152F + (.2848F * amount); + m.M32 = 1F - (m.M12 + m.M22); - ColorMatrix m = default; - m.M11 = .299F + (.701F * amount); - m.M21 = .587F - (.587F * amount); - m.M31 = 1F - (m.M11 + m.M21); + m.M13 = .2126F - (.2126F * amount); + m.M23 = .7152F - (.7152F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - m.M12 = .299F - (.299F * amount); - m.M22 = .587F + (.2848F * amount); - m.M32 = 1F - (m.M12 + m.M22); + return m; + } - m.M13 = .299F - (.299F * amount); - m.M23 = .587F - (.587F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + /// + /// Create a hue filter matrix using the given angle in degrees. + /// + /// The angle of rotation in degrees. + /// The + public static ColorMatrix CreateHueFilter(float degrees) + { + // Wrap the angle round at 360. + degrees %= 360; - return m; + // Make sure it's not negative. + while (degrees < 0) + { + degrees += 360; } - /// - /// Create a grayscale filter matrix using the given amount using the formula as specified by ITU-R Recommendation BT.709. - /// - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateGrayscaleBt709Filter(float amount) + float radian = GeometryUtilities.DegreeToRadian(degrees); + float cosRadian = MathF.Cos(radian); + float sinRadian = MathF.Sin(radian); + + // The matrix is set up to preserve the luminance of the image. + // See http://graficaobscura.com/matrix/index.html + // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx + return new ColorMatrix { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount)); - amount = 1F - amount; + M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F), + M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F), + M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F), - // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - ColorMatrix m = default; - m.M11 = .2126F + (.7874F * amount); - m.M21 = .7152F - (.7152F * amount); - m.M31 = 1F - (m.M11 + m.M21); + M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F), + M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F), + M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F), - m.M12 = .2126F - (.2126F * amount); - m.M22 = .7152F + (.2848F * amount); - m.M32 = 1F - (m.M12 + m.M22); + M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), + M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), + M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F), + M44 = 1F + }; + } - m.M13 = .2126F - (.2126F * amount); - m.M23 = .7152F - (.7152F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + /// + /// Create an invert filter matrix using the given amount. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateInvertFilter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - return m; - } + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + float invert = 1F - (2F * amount); - /// - /// Create a hue filter matrix using the given angle in degrees. - /// - /// The angle of rotation in degrees. - /// The - public static ColorMatrix CreateHueFilter(float degrees) + return new ColorMatrix { - // Wrap the angle round at 360. - degrees %= 360; - - // Make sure it's not negative. - while (degrees < 0) - { - degrees += 360; - } - - float radian = GeometryUtilities.DegreeToRadian(degrees); - float cosRadian = MathF.Cos(radian); - float sinRadian = MathF.Sin(radian); - - // The matrix is set up to preserve the luminance of the image. - // See http://graficaobscura.com/matrix/index.html - // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx - return new ColorMatrix - { - M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F), - M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F), - M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F), - - M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F), - M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F), - M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F), - - M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F), - M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F), - M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F), - M44 = 1F - }; - } + M11 = invert, + M22 = invert, + M33 = invert, + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount, + }; + } - /// - /// Create an invert filter matrix using the given amount. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateInvertFilter(float amount) - { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - float invert = 1F - (2F * amount); - - return new ColorMatrix - { - M11 = invert, - M22 = invert, - M33 = invert, - M44 = 1F, - M51 = amount, - M52 = amount, - M53 = amount, - }; - } + /// + /// Create an opacity filter matrix using the given amount. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateOpacityFilter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - /// - /// Create an opacity filter matrix using the given amount. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateOpacityFilter(float amount) + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix - { - M11 = 1F, - M22 = 1F, - M33 = 1F, - M44 = amount - }; - } + M11 = 1F, + M22 = 1F, + M33 = 1F, + M44 = amount + }; + } - /// - /// Create a saturation filter matrix using the given amount. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateSaturateFilter(float amount) - { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + /// + /// Create a saturation filter matrix using the given amount. + /// + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateSaturateFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - ColorMatrix m = default; - m.M11 = .213F + (.787F * amount); - m.M21 = .715F - (.715F * amount); - m.M31 = 1F - (m.M11 + m.M21); + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + ColorMatrix m = default; + m.M11 = .213F + (.787F * amount); + m.M21 = .715F - (.715F * amount); + m.M31 = 1F - (m.M11 + m.M21); - m.M12 = .213F - (.213F * amount); - m.M22 = .715F + (.285F * amount); - m.M32 = 1F - (m.M12 + m.M22); + m.M12 = .213F - (.213F * amount); + m.M22 = .715F + (.285F * amount); + m.M32 = 1F - (m.M12 + m.M22); - m.M13 = .213F - (.213F * amount); - m.M23 = .715F - (.715F * amount); - m.M33 = 1F - (m.M13 + m.M23); - m.M44 = 1F; + m.M13 = .213F - (.213F * amount); + m.M23 = .715F - (.715F * amount); + m.M33 = 1F - (m.M13 + m.M23); + m.M44 = 1F; - return m; - } + return m; + } + + /// + /// Create a lightness filter matrix using the given amount. + /// + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + /// The + public static ColorMatrix CreateLightnessFilter(float amount) + { + Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); + amount--; - /// - /// Create a lightness filter matrix using the given amount. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - /// The - public static ColorMatrix CreateLightnessFilter(float amount) + return new ColorMatrix { - Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount)); - amount--; - - return new ColorMatrix - { - M11 = 1F, - M22 = 1F, - M33 = 1F, - M44 = 1F, - M51 = amount, - M52 = amount, - M53 = amount - }; - } + M11 = 1F, + M22 = 1F, + M33 = 1F, + M44 = 1F, + M51 = amount, + M52 = amount, + M53 = amount + }; + } - /// - /// Create a sepia filter matrix using the given amount. - /// The formula used matches the svg specification. - /// - /// The proportion of the conversion. Must be between 0 and 1. - /// The - public static ColorMatrix CreateSepiaFilter(float amount) + /// + /// Create a sepia filter matrix using the given amount. + /// The formula used matches the svg specification. + /// + /// The proportion of the conversion. Must be between 0 and 1. + /// The + public static ColorMatrix CreateSepiaFilter(float amount) + { + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + amount = 1F - amount; + + // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc + return new ColorMatrix { - Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); - amount = 1F - amount; - - // See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc - return new ColorMatrix - { - M11 = .393F + (.607F * amount), - M21 = .769F - (.769F * amount), - M31 = .189F - (.189F * amount), - - M12 = .349F - (.349F * amount), - M22 = .686F + (.314F * amount), - M32 = .168F - (.168F * amount), - - M13 = .272F - (.272F * amount), - M23 = .534F - (.534F * amount), - M33 = .131F + (.869F * amount), - M44 = 1F - }; - } + M11 = .393F + (.607F * amount), + M21 = .769F - (.769F * amount), + M31 = .189F - (.189F * amount), + + M12 = .349F - (.349F * amount), + M22 = .686F + (.314F * amount), + M32 = .168F - (.168F * amount), + + M13 = .272F - (.272F * amount), + M23 = .534F - (.534F * amount), + M33 = .131F + (.869F * amount), + M44 = 1F + }; } } diff --git a/src/ImageSharp/Processing/KnownQuantizers.cs b/src/ImageSharp/Processing/KnownQuantizers.cs index c5a2cef26a..6fb3c72e81 100644 --- a/src/ImageSharp/Processing/KnownQuantizers.cs +++ b/src/ImageSharp/Processing/KnownQuantizers.cs @@ -3,32 +3,31 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Contains reusable static instances of known quantizing algorithms +/// +public static class KnownQuantizers { /// - /// Contains reusable static instances of known quantizing algorithms + /// Gets the adaptive Octree quantizer. Fast with good quality. /// - public static class KnownQuantizers - { - /// - /// Gets the adaptive Octree quantizer. Fast with good quality. - /// - public static IQuantizer Octree { get; } = new OctreeQuantizer(); + public static IQuantizer Octree { get; } = new OctreeQuantizer(); - /// - /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. - /// - public static IQuantizer Wu { get; } = new WuQuantizer(); + /// + /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. + /// + public static IQuantizer Wu { get; } = new WuQuantizer(); - /// - /// Gets the palette based quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. - /// - public static IQuantizer WebSafe { get; } = new WebSafePaletteQuantizer(); + /// + /// Gets the palette based quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. + /// + public static IQuantizer WebSafe { get; } = new WebSafePaletteQuantizer(); - /// - /// Gets the palette based quantizer consisting 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 IQuantizer Werner { get; } = new WernerPaletteQuantizer(); - } + /// + /// Gets the palette based quantizer consisting 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 IQuantizer Werner { get; } = new WernerPaletteQuantizer(); } diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 3f44196c69..8be8e712d2 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -3,96 +3,95 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Contains reusable static instances of known resampling algorithms +/// +public static class KnownResamplers { /// - /// Contains reusable static instances of known resampling algorithms + /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) + /// + 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; } = default(BoxResampler); + + /// + /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function + /// + 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = CubicResampler.Robidoux; + + /// + /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler + /// + public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; + + /// + /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. + /// + 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; } = default(TriangleResampler); + + /// + /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. /// - public static class KnownResamplers - { - /// - /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) - /// - 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; } = default(BoxResampler); - - /// - /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function - /// - 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = 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; } = CubicResampler.Robidoux; - - /// - /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler - /// - public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; - - /// - /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. - /// - 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; } = default(TriangleResampler); - - /// - /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. - /// - public static IResampler Welch { get; } = default(WelchResampler); - } + public static IResampler Welch { get; } = default(WelchResampler); } diff --git a/src/ImageSharp/Processing/PixelRowOperation.cs b/src/ImageSharp/Processing/PixelRowOperation.cs index aaaadf652b..4f9b507443 100644 --- a/src/ImageSharp/Processing/PixelRowOperation.cs +++ b/src/ImageSharp/Processing/PixelRowOperation.cs @@ -1,27 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Processing -{ - /// - /// A representing a user defined processing delegate to use to modify image rows. - /// - /// The target row of pixels to process. - /// The , , , and fields map the RGBA channels respectively. - public delegate void PixelRowOperation(Span span); +namespace SixLabors.ImageSharp.Processing; - /// - /// A representing a user defined processing delegate to use to modify image rows. - /// - /// - /// The type of the parameter of the method that this delegate encapsulates. - /// This type parameter is contravariant.That is, you can use either the type you specified or any type that is less derived. - /// - /// The target row of pixels to process. - /// The parameter of the method that this delegate encapsulates. - /// The , , , and fields map the RGBA channels respectively. - public delegate void PixelRowOperation(Span span, T value); -} +/// +/// A representing a user defined processing delegate to use to modify image rows. +/// +/// The target row of pixels to process. +/// The , , , and fields map the RGBA channels respectively. +public delegate void PixelRowOperation(Span span); + +/// +/// A representing a user defined processing delegate to use to modify image rows. +/// +/// +/// The type of the parameter of the method that this delegate encapsulates. +/// This type parameter is contravariant.That is, you can use either the type you specified or any type that is less derived. +/// +/// The target row of pixels to process. +/// The parameter of the method that this delegate encapsulates. +/// The , , , and fields map the RGBA channels respectively. +public delegate void PixelRowOperation(Span span, T value); diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs index 470d709026..aebfc3b464 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor.cs @@ -3,75 +3,74 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Processing.Processors.Binarization; + +/// +/// Performs Bradley Adaptive Threshold filter against an image. +/// +/// +/// Implements "Adaptive Thresholding Using the Integral Image", +/// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf +/// +public class AdaptiveThresholdProcessor : IImageProcessor { /// - /// Performs Bradley Adaptive Threshold filter against an image. + /// Initializes a new instance of the class. /// - /// - /// Implements "Adaptive Thresholding Using the Integral Image", - /// see paper: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.420.7883&rep=rep1&type=pdf - /// - public class AdaptiveThresholdProcessor : IImageProcessor + public AdaptiveThresholdProcessor() + : this(Color.White, Color.Black, 0.85f) { - /// - /// Initializes a new instance of the class. - /// - public AdaptiveThresholdProcessor() - : this(Color.White, Color.Black, 0.85f) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Threshold limit. - public AdaptiveThresholdProcessor(float thresholdLimit) - : this(Color.White, Color.Black, thresholdLimit) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Threshold limit. + public AdaptiveThresholdProcessor(float thresholdLimit) + : this(Color.White, Color.Black, thresholdLimit) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Color for upper threshold. - /// Color for lower threshold. - public AdaptiveThresholdProcessor(Color upper, Color lower) - : this(upper, lower, 0.85f) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Color for upper threshold. + /// Color for lower threshold. + public AdaptiveThresholdProcessor(Color upper, Color lower) + : this(upper, lower, 0.85f) + { + } - /// - /// Initializes a new instance of the class. - /// - /// Color for upper threshold. - /// Color for lower threshold. - /// Threshold limit. - public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) - { - this.Upper = upper; - this.Lower = lower; - this.ThresholdLimit = thresholdLimit; - } + /// + /// Initializes a new instance of the class. + /// + /// Color for upper threshold. + /// Color for lower threshold. + /// Threshold limit. + public AdaptiveThresholdProcessor(Color upper, Color lower, float thresholdLimit) + { + this.Upper = upper; + this.Lower = lower; + this.ThresholdLimit = thresholdLimit; + } - /// - /// Gets or sets upper color limit for thresholding. - /// - public Color Upper { get; set; } + /// + /// Gets or sets upper color limit for thresholding. + /// + public Color Upper { get; set; } - /// - /// Gets or sets lower color limit for threshold. - /// - public Color Lower { get; set; } + /// + /// Gets or sets lower color limit for threshold. + /// + public Color Lower { get; set; } - /// - /// Gets or sets the value for threshold limit. - /// - public float ThresholdLimit { get; set; } + /// + /// Gets or sets the value for threshold limit. + /// + public float ThresholdLimit { get; set; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new AdaptiveThresholdProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new AdaptiveThresholdProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index e7c5ad4719..e3b2025aa5 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -1,118 +1,116 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Processing.Processors.Binarization; + +/// +/// Performs Bradley Adaptive Threshold filter against an image. +/// +internal class AdaptiveThresholdProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly AdaptiveThresholdProcessor definition; + /// - /// Performs Bradley Adaptive Threshold filter against an image. + /// Initializes a new instance of the class. /// - internal class AdaptiveThresholdProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; + + /// + protected override void OnFrameApply(ImageFrame source) { - private readonly AdaptiveThresholdProcessor definition; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - /// - /// 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 AdaptiveThresholdProcessor(Configuration configuration, AdaptiveThresholdProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; + Configuration configuration = this.Configuration; + TPixel upper = this.definition.Upper.ToPixel(); + TPixel lower = this.definition.Lower.ToPixel(); + float thresholdLimit = this.definition.ThresholdLimit; - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + // ClusterSize defines the size of cluster to used to check for average. + // Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' + byte clusterSize = (byte)Math.Clamp(interest.Width / 16F, 0, 255); - Configuration configuration = this.Configuration; - TPixel upper = this.definition.Upper.ToPixel(); - TPixel lower = this.definition.Lower.ToPixel(); - float thresholdLimit = this.definition.ThresholdLimit; + using Buffer2D intImage = source.CalculateIntegralImage(interest); + RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - // ClusterSize defines the size of cluster to used to check for average. - // Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' - byte clusterSize = (byte)Math.Clamp(interest.Width / 16F, 0, 255); + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly Buffer2D source; + private readonly Buffer2D intImage; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly float thresholdLimit; + private readonly int startX; + private readonly int startY; + private readonly byte clusterSize; - using Buffer2D intImage = source.CalculateIntegralImage(interest); - RowOperation operation = new(configuration, interest, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + Buffer2D source, + Buffer2D intImage, + TPixel upper, + TPixel lower, + float thresholdLimit, + byte clusterSize) + { + this.configuration = configuration; + this.bounds = bounds; + this.startX = bounds.X; + this.startY = bounds.Y; + this.source = source; + this.intImage = intImage; + this.upper = upper; + this.lower = lower; + this.thresholdLimit = thresholdLimit; + this.clusterSize = clusterSize; } - private readonly struct RowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D source; - private readonly Buffer2D intImage; - private readonly TPixel upper; - private readonly TPixel lower; - private readonly float thresholdLimit; - private readonly int startX; - private readonly int startY; - private readonly byte clusterSize; + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToL8(this.configuration, rowSpan, span); - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - Buffer2D source, - Buffer2D intImage, - TPixel upper, - TPixel lower, - float thresholdLimit, - byte clusterSize) + int maxX = this.bounds.Width - 1; + int maxY = this.bounds.Height - 1; + for (int x = 0; x < rowSpan.Length; x++) { - this.configuration = configuration; - this.bounds = bounds; - this.startX = bounds.X; - this.startY = bounds.Y; - this.source = source; - this.intImage = intImage; - this.upper = upper; - this.lower = lower; - this.thresholdLimit = thresholdLimit; - this.clusterSize = clusterSize; - } + int x1 = Math.Clamp(x - this.clusterSize + 1, 0, maxX); + int x2 = Math.Min(x + this.clusterSize + 1, maxX); + int y1 = Math.Clamp(y - this.startY - this.clusterSize + 1, 0, maxY); + int y2 = Math.Min(y - this.startY + this.clusterSize + 1, maxY); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToL8(this.configuration, rowSpan, span); + uint count = (uint)((x2 - x1) * (y2 - y1)); + ulong sum = Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], ulong.MaxValue); - int maxX = this.bounds.Width - 1; - int maxY = this.bounds.Height - 1; - for (int x = 0; x < rowSpan.Length; x++) + if (span[x].PackedValue * count <= sum * this.thresholdLimit) { - int x1 = Math.Clamp(x - this.clusterSize + 1, 0, maxX); - int x2 = Math.Min(x + this.clusterSize + 1, maxX); - int y1 = Math.Clamp(y - this.startY - this.clusterSize + 1, 0, maxY); - int y2 = Math.Min(y - this.startY + this.clusterSize + 1, maxY); - - uint count = (uint)((x2 - x1) * (y2 - y1)); - ulong sum = Math.Min(this.intImage[x2, y2] - this.intImage[x1, y2] - this.intImage[x2, y1] + this.intImage[x1, y1], ulong.MaxValue); - - if (span[x].PackedValue * count <= sum * this.thresholdLimit) - { - rowSpan[x] = this.lower; - } - else - { - rowSpan[x] = this.upper; - } + rowSpan[x] = this.lower; + } + else + { + rowSpan[x] = this.upper; } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index a3fda27093..d6c3881174 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -3,84 +3,83 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Processing.Processors.Binarization; + +/// +/// Performs simple binary threshold filtering against an image. +/// +public class BinaryThresholdProcessor : IImageProcessor { /// - /// Performs simple binary threshold filtering against an image. + /// Initializes a new instance of the class. /// - public class BinaryThresholdProcessor : IImageProcessor + /// The threshold to split the image. Must be between 0 and 1. + /// The color component to be compared to threshold. + public BinaryThresholdProcessor(float threshold, BinaryThresholdMode mode) + : this(threshold, Color.White, Color.Black, mode) { - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// The color component to be compared to threshold. - public BinaryThresholdProcessor(float threshold, BinaryThresholdMode mode) - : this(threshold, Color.White, Color.Black, mode) - { - } + } - /// - /// Initializes a new instance of the class with - /// Luminance as color component to be compared to threshold. - /// - /// The threshold to split the image. Must be between 0 and 1. - public BinaryThresholdProcessor(float threshold) - : this(threshold, Color.White, Color.Black, BinaryThresholdMode.Luminance) - { - } + /// + /// Initializes a new instance of the class with + /// Luminance as color component to be compared to threshold. + /// + /// The threshold to split the image. Must be between 0 and 1. + public BinaryThresholdProcessor(float threshold) + : this(threshold, Color.White, Color.Black, BinaryThresholdMode.Luminance) + { + } - /// - /// Initializes a new instance of the class. - /// - /// 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. - /// The color component to be compared to threshold. - public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor, BinaryThresholdMode mode) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Threshold = threshold; - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - this.Mode = mode; - } + /// + /// Initializes a new instance of the class. + /// + /// 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. + /// The color component to be compared to threshold. + public BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor, BinaryThresholdMode mode) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Threshold = threshold; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + this.Mode = mode; + } - /// - /// Initializes a new instance of the class with - /// Luminance as color component to be compared to threshold. - /// - /// 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 BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) - : this(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance) - { - } + /// + /// Initializes a new instance of the class with + /// Luminance as color component to be compared to threshold. + /// + /// 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 BinaryThresholdProcessor(float threshold, Color upperColor, Color lowerColor) + : this(threshold, upperColor, lowerColor, BinaryThresholdMode.Luminance) + { + } - /// - /// Gets the threshold value. - /// - public float Threshold { 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 are above the threshold. + /// + public Color UpperColor { get; } - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public Color LowerColor { get; } - /// - /// Gets the defining the value to be compared to threshold. - /// - public BinaryThresholdMode Mode { get; } + /// + /// Gets the defining the value to be compared to threshold. + /// + public BinaryThresholdMode Mode { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 bccb2208af..1a35973fbd 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -1,189 +1,187 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Processing.Processors.Binarization; + +/// +/// Performs simple binary threshold filtering against an image. +/// +/// The pixel format. +internal class BinaryThresholdProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly BinaryThresholdProcessor definition; + /// - /// Performs simple binary threshold filtering against an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class BinaryThresholdProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; + + /// + protected override void OnFrameApply(ImageFrame source) { - private readonly BinaryThresholdProcessor 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 BinaryThresholdProcessor(Configuration configuration, BinaryThresholdProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; + byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); + TPixel upper = this.definition.UpperColor.ToPixel(); + TPixel lower = this.definition.LowerColor.ToPixel(); + + Rectangle sourceRectangle = this.SourceRectangle; + Configuration configuration = this.Configuration; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + var operation = new RowOperation( + interest.X, + source.PixelBuffer, + upper, + lower, + threshold, + this.definition.Mode, + configuration); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - /// - protected override void OnFrameApply(ImageFrame source) + /// + /// A implementing the clone logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Buffer2D source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly BinaryThresholdMode mode; + private readonly int startX; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + Buffer2D source, + TPixel upper, + TPixel lower, + byte threshold, + BinaryThresholdMode mode, + Configuration configuration) { - byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); - TPixel upper = this.definition.UpperColor.ToPixel(); - TPixel lower = this.definition.LowerColor.ToPixel(); - - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - var operation = new RowOperation( - interest.X, - source.PixelBuffer, - upper, - lower, - threshold, - this.definition.Mode, - configuration); - - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); + this.startX = startX; + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.mode = mode; + this.configuration = configuration; } - /// - /// A implementing the clone logic for . - /// - private readonly struct RowOperation : IRowOperation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) { - private readonly Buffer2D source; - private readonly TPixel upper; - private readonly TPixel lower; - private readonly byte threshold; - private readonly BinaryThresholdMode mode; - private readonly int startX; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D source, - TPixel upper, - TPixel lower, - byte threshold, - BinaryThresholdMode mode, - Configuration configuration) - { - this.startX = startX; - this.source = source; - this.upper = upper; - this.lower = lower; - this.threshold = threshold; - this.mode = mode; - this.configuration = configuration; - } + TPixel upper = this.upper; + TPixel lower = this.lower; - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) - { - TPixel upper = this.upper; - TPixel lower = this.lower; - - Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span); - switch (this.mode) + switch (this.mode) + { + case BinaryThresholdMode.Luminance: { - case BinaryThresholdMode.Luminance: + byte threshold = this.threshold; + for (int x = 0; x < rowSpan.Length; x++) { - byte threshold = this.threshold; - for (int x = 0; x < rowSpan.Length; x++) - { - Rgb24 rgb = span[x]; - byte luminance = ColorNumerics.Get8BitBT709Luminance(rgb.R, rgb.G, rgb.B); - ref TPixel color = ref rowSpan[x]; - color = luminance >= threshold ? upper : lower; - } - - break; + Rgb24 rgb = span[x]; + byte luminance = ColorNumerics.Get8BitBT709Luminance(rgb.R, rgb.G, rgb.B); + ref TPixel color = ref rowSpan[x]; + color = luminance >= threshold ? upper : lower; } - case BinaryThresholdMode.Saturation: + break; + } + + case BinaryThresholdMode.Saturation: + { + float threshold = this.threshold / 255F; + for (int x = 0; x < rowSpan.Length; x++) { - float threshold = this.threshold / 255F; - for (int x = 0; x < rowSpan.Length; x++) - { - float saturation = GetSaturation(span[x]); - ref TPixel color = ref rowSpan[x]; - color = saturation >= threshold ? upper : lower; - } - - break; + float saturation = GetSaturation(span[x]); + ref TPixel color = ref rowSpan[x]; + color = saturation >= threshold ? upper : lower; } - case BinaryThresholdMode.MaxChroma: + break; + } + + case BinaryThresholdMode.MaxChroma: + { + float threshold = this.threshold / 2F; + for (int x = 0; x < rowSpan.Length; x++) { - float threshold = this.threshold / 2F; - for (int x = 0; x < rowSpan.Length; x++) - { - float chroma = GetMaxChroma(span[x]); - ref TPixel color = ref rowSpan[x]; - color = chroma >= threshold ? upper : lower; - } - - break; + float chroma = GetMaxChroma(span[x]); + ref TPixel color = ref rowSpan[x]; + color = chroma >= threshold ? upper : lower; } + + break; } } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetSaturation(Rgb24 rgb) - { - // Slimmed down RGB => HSL formula. See HslAndRgbConverter. - float r = rgb.R / 255F; - float g = rgb.G / 255F; - float b = rgb.B / 255F; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetSaturation(Rgb24 rgb) + { + // Slimmed down RGB => HSL formula. See HslAndRgbConverter. + float r = rgb.R / 255F; + float g = rgb.G / 255F; + float b = rgb.B / 255F; - float max = MathF.Max(r, MathF.Max(g, b)); - float min = MathF.Min(r, MathF.Min(g, b)); - float chroma = max - min; + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); + float chroma = max - min; - if (MathF.Abs(chroma) < Constants.Epsilon) - { - return 0F; - } + if (MathF.Abs(chroma) < Constants.Epsilon) + { + return 0F; + } - float l = (max + min) / 2F; + float l = (max + min) / 2F; - if (l <= .5F) - { - return chroma / (max + min); - } - else - { - return chroma / (2F - max - min); - } + if (l <= .5F) + { + return chroma / (max + min); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetMaxChroma(Rgb24 rgb) + else { - // Slimmed down RGB => YCbCr formula. See YCbCrAndRgbConverter. - float r = rgb.R; - float g = rgb.G; - float b = rgb.B; - const float achromatic = 127.5F; + return chroma / (2F - max - min); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetMaxChroma(Rgb24 rgb) + { + // Slimmed down RGB => YCbCr formula. See YCbCrAndRgbConverter. + float r = rgb.R; + float g = rgb.G; + float b = rgb.B; + const float achromatic = 127.5F; - float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); - return MathF.Max(MathF.Abs(cb - achromatic), MathF.Abs(cr - achromatic)); - } + return MathF.Max(MathF.Abs(cb - achromatic), MathF.Abs(cr - achromatic)); } } } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 87166ecfb8..a8e6b77dca 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -3,19 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// The base class for all cloning image processors. +/// +public abstract class CloningImageProcessor : ICloningImageProcessor { - /// - /// The base class for all cloning image processors. - /// - public abstract class CloningImageProcessor : ICloningImageProcessor - { - /// - public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; + /// + public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; - /// - IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => this.CreatePixelSpecificCloningProcessor(configuration, source, sourceRectangle); - } + /// + IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => this.CreatePixelSpecificCloningProcessor(configuration, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index 455cf09603..bafe33c484 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -1,183 +1,181 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// The base class for all pixel specific cloning image processors. +/// Allows the application of processing algorithms to the image. +/// The image is cloned before operating upon and the buffers swapped upon completion. +/// +/// The pixel format. +public abstract class CloningImageProcessor : ICloningImageProcessor + where TPixel : unmanaged, IPixel { /// - /// The base class for all pixel specific cloning image processors. - /// Allows the application of processing algorithms to the image. - /// The image is cloned before operating upon and the buffers swapped upon completion. + /// Initializes a new instance of the class. /// - /// The pixel format. - public abstract class CloningImageProcessor : ICloningImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + protected CloningImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected CloningImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - this.Configuration = configuration; - this.Source = source; - this.SourceRectangle = sourceRectangle; - } + this.Configuration = configuration; + this.Source = source; + this.SourceRectangle = sourceRectangle; + } - /// - /// Gets The source for the current processor instance. - /// - protected Image Source { get; } + /// + /// Gets The source for the current processor instance. + /// + protected Image Source { get; } - /// - /// Gets The source area to process for the current processor instance. - /// - protected Rectangle SourceRectangle { get; } + /// + /// Gets The source area to process for the current processor instance. + /// + protected Rectangle SourceRectangle { get; } - /// - /// Gets the instance to use when performing operations. - /// - protected Configuration Configuration { get; } + /// + /// Gets the instance to use when performing operations. + /// + protected Configuration Configuration { get; } - /// - Image ICloningImageProcessor.CloneAndExecute() - { - Image clone = this.CreateTarget(); - this.CheckFrameCount(this.Source, clone); + /// + Image ICloningImageProcessor.CloneAndExecute() + { + Image clone = this.CreateTarget(); + this.CheckFrameCount(this.Source, clone); - Configuration configuration = this.Configuration; - this.BeforeImageApply(clone); + Configuration configuration = this.Configuration; + this.BeforeImageApply(clone); - for (int i = 0; i < this.Source.Frames.Count; i++) - { - ImageFrame sourceFrame = this.Source.Frames[i]; - ImageFrame clonedFrame = clone.Frames[i]; + for (int i = 0; i < this.Source.Frames.Count; i++) + { + ImageFrame sourceFrame = this.Source.Frames[i]; + ImageFrame clonedFrame = clone.Frames[i]; - this.BeforeFrameApply(sourceFrame, clonedFrame); - this.OnFrameApply(sourceFrame, clonedFrame); - this.AfterFrameApply(sourceFrame, clonedFrame); - } + this.BeforeFrameApply(sourceFrame, clonedFrame); + this.OnFrameApply(sourceFrame, clonedFrame); + this.AfterFrameApply(sourceFrame, clonedFrame); + } - this.AfterImageApply(clone); + this.AfterImageApply(clone); - return clone; - } + return clone; + } - /// - void IImageProcessor.Execute() + /// + void IImageProcessor.Execute() + { + // Create an interim clone of the source image to operate on. + // Doing this allows for the application of transforms that will alter + // the dimensions of the image. + Image clone = default; + try { - // Create an interim clone of the source image to operate on. - // Doing this allows for the application of transforms that will alter - // the dimensions of the image. - Image clone = default; - try - { - clone = ((ICloningImageProcessor)this).CloneAndExecute(); - - // We now need to move the pixel data/size data from the clone to the source. - this.CheckFrameCount(this.Source, clone); - this.Source.SwapOrCopyPixelsBuffersFrom(clone); - } - finally - { - // Dispose of the clone now that we have swapped the pixel/size data. - clone?.Dispose(); - } - } + clone = ((ICloningImageProcessor)this).CloneAndExecute(); - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); + // We now need to move the pixel data/size data from the clone to the source. + this.CheckFrameCount(this.Source, clone); + this.Source.SwapOrCopyPixelsBuffersFrom(clone); } - - /// - /// Gets the size of the destination image. - /// - /// The . - protected abstract Size GetDestinationSize(); - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The cloned/destination image. Cannot be null. - protected virtual void BeforeImageApply(Image destination) + finally { + // Dispose of the clone now that we have swapped the pixel/size data. + clone?.Dispose(); } + } - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected virtual void BeforeFrameApply(ImageFrame source, ImageFrame destination) - { - } + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected abstract void OnFrameApply(ImageFrame source, ImageFrame destination); - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - /// The cloned/destination image. Cannot be null. - protected virtual void AfterFrameApply(ImageFrame source, ImageFrame destination) - { - } + /// + /// Gets the size of the destination image. + /// + /// The . + protected abstract Size GetDestinationSize(); - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The cloned/destination image. Cannot be null. - protected virtual void AfterImageApply(Image destination) - { - } + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The cloned/destination image. Cannot be null. + protected virtual void BeforeImageApply(Image destination) + { + } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - } + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + protected virtual void BeforeFrameApply(ImageFrame source, ImageFrame destination) + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + protected abstract void OnFrameApply(ImageFrame source, ImageFrame destination); - private Image CreateTarget() + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + /// The cloned/destination image. Cannot be null. + protected virtual void AfterFrameApply(ImageFrame source, ImageFrame destination) + { + } + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The cloned/destination image. Cannot be null. + protected virtual void AfterImageApply(Image destination) + { + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose managed and unmanaged objects. + protected virtual void Dispose(bool disposing) + { + } + + private Image CreateTarget() + { + Image source = this.Source; + Size destinationSize = this.GetDestinationSize(); + + // We will always be creating the clone even for mutate because we may need to resize the canvas. + var destinationFrames = new ImageFrame[source.Frames.Count]; + for (int i = 0; i < destinationFrames.Length; i++) { - Image source = this.Source; - Size destinationSize = this.GetDestinationSize(); - - // We will always be creating the clone even for mutate because we may need to resize the canvas. - var destinationFrames = new ImageFrame[source.Frames.Count]; - for (int i = 0; i < destinationFrames.Length; i++) - { - destinationFrames[i] = new ImageFrame( - this.Configuration, - 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(), destinationFrames); + destinationFrames[i] = new ImageFrame( + this.Configuration, + destinationSize.Width, + destinationSize.Height, + source.Frames[i].Metadata.DeepClone()); } - private void CheckFrameCount(Image a, Image b) + // Use the overload to prevent an extra frame being added. + return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames); + } + + private void CheckFrameCount(Image a, Image b) + { + if (a.Frames.Count != b.Frames.Count) { - if (a.Frames.Count != b.Frames.Count) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); - } + throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 79ec090175..0ceb5bc130 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,155 +8,154 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies bokeh blur processing to the image. +/// +public sealed class BokehBlurProcessor : IImageProcessor { /// - /// Applies bokeh blur processing to the image. + /// The default radius used by the parameterless constructor. + /// + public const int DefaultRadius = 32; + + /// + /// The default component count used by the parameterless constructor. + /// + public const int DefaultComponents = 2; + + /// + /// The default gamma used by the parameterless constructor. + /// + public const float DefaultGamma = 3F; + + /// + /// Initializes a new instance of the class. /// - public sealed class BokehBlurProcessor : IImageProcessor + public BokehBlurProcessor() { - /// - /// The default radius used by the parameterless constructor. - /// - public const int DefaultRadius = 32; - - /// - /// The default component count used by the parameterless constructor. - /// - public const int DefaultComponents = 2; - - /// - /// The default gamma used by the parameterless constructor. - /// - public const float DefaultGamma = 3F; - - /// - /// Initializes a new instance of the class. - /// - public BokehBlurProcessor() - { - this.Radius = DefaultRadius; - this.Components = DefaultComponents; - this.Gamma = DefaultGamma; - } + this.Radius = DefaultRadius; + this.Components = DefaultComponents; + this.Gamma = DefaultGamma; + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// - /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. - /// - /// - /// The gamma highlight factor to use to further process the image. - /// - public BokehBlurProcessor(int radius, int components, float gamma) - { - Guard.MustBeGreaterThan(radius, 0, nameof(radius)); - Guard.MustBeBetweenOrEqualTo(components, 1, 6, nameof(components)); - Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma)); + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + /// + /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. + /// + /// + /// The gamma highlight factor to use to further process the image. + /// + public BokehBlurProcessor(int radius, int components, float gamma) + { + Guard.MustBeGreaterThan(radius, 0, nameof(radius)); + Guard.MustBeBetweenOrEqualTo(components, 1, 6, nameof(components)); + Guard.MustBeGreaterThanOrEqualTo(gamma, 1, nameof(gamma)); + + this.Radius = radius; + this.Components = components; + this.Gamma = gamma; + } + + /// + /// Gets the radius. + /// + public int Radius { get; } + + /// + /// Gets the number of components. + /// + public int Components { get; } - this.Radius = radius; - this.Components = components; - this.Gamma = gamma; + /// + /// Gets the gamma highlight factor to use when applying the effect. + /// + public float Gamma { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BokehBlurProcessor(configuration, this, source, sourceRectangle); + + /// + /// A implementing the horizontal convolution logic for . + /// + /// + /// This type is located in the non-generic class and not in , where + /// it is actually used, because it does not use any generic parameters internally. Defining in a non-generic class means that there will only + /// ever be a single instantiation of this type for the JIT/AOT compilers to process, instead of having duplicate versions for each pixel type. + /// + internal readonly struct SecondPassConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly KernelSamplingMap map; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + + [MethodImpl(InliningOptions.ShortMethod)] + public SecondPassConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + KernelSamplingMap map, + Complex64[] kernel, + float z, + float w) + { + this.bounds = bounds; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.map = map; + this.kernel = kernel; + this.z = z; + this.w = w; } - /// - /// Gets the radius. - /// - public int Radius { get; } - - /// - /// Gets the number of components. - /// - public int Components { get; } - - /// - /// Gets the gamma highlight factor to use when applying the effect. - /// - public float Gamma { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BokehBlurProcessor(configuration, this, source, sourceRectangle); - - /// - /// A implementing the horizontal convolution logic for . - /// - /// - /// This type is located in the non-generic class and not in , where - /// it is actually used, because it does not use any generic parameters internally. Defining in a non-generic class means that there will only - /// ever be a single instantiation of this type for the JIT/AOT compilers to process, instead of having duplicate versions for each pixel type. - /// - internal readonly struct SecondPassConvolutionRowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Rectangle bounds; - private readonly Buffer2D targetValues; - private readonly Buffer2D sourceValues; - private readonly KernelSamplingMap map; - private readonly Complex64[] kernel; - private readonly float z; - private readonly float w; - - [MethodImpl(InliningOptions.ShortMethod)] - public SecondPassConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetValues, - Buffer2D sourceValues, - KernelSamplingMap map, - Complex64[] kernel, - float z, - float w) - { - this.bounds = bounds; - this.targetValues = targetValues; - this.sourceValues = sourceValues; - this.map = map; - this.kernel = kernel; - this.z = z; - this.w = w; - } + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; + ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); + // The target buffer is zeroed initially and then it accumulates the results + // of each partial convolution, so we don't have to clear it here as well + ref Vector4 targetBase = ref this.targetValues.GetElementUnsafe(boundsX, y); + ref Complex64 kernelStart = ref this.kernel[0]; + ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); - // The target buffer is zeroed initially and then it accumulates the results - // of each partial convolution, so we don't have to clear it here as well - ref Vector4 targetBase = ref this.targetValues.GetElementUnsafe(boundsX, y); - ref Complex64 kernelStart = ref this.kernel[0]; - ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer + ref ComplexVector4 sourceBase = ref this.sourceValues.GetElementUnsafe(0, sampleRowBase); + ref ComplexVector4 sourceEnd = ref Unsafe.Add(ref sourceBase, boundsWidth); + ref Vector4 targetStart = ref targetBase; + Complex64 factor = kernelStart; - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) { - // Get the precalculated source sample row for this kernel row and copy to our buffer - ref ComplexVector4 sourceBase = ref this.sourceValues.GetElementUnsafe(0, sampleRowBase); - ref ComplexVector4 sourceEnd = ref Unsafe.Add(ref sourceBase, boundsWidth); - ref Vector4 targetStart = ref targetBase; - Complex64 factor = kernelStart; - - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - ComplexVector4 partial = factor * sourceBase; + ComplexVector4 partial = factor * sourceBase; - targetStart += partial.WeightedSum(this.z, this.w); + targetStart += partial.WeightedSum(this.z, this.w); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + targetStart = ref Unsafe.Add(ref targetStart, 1); } + + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index e0221f8152..7e7cf81385 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,427 +9,426 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies bokeh blur processing to the image. +/// +/// The pixel format. +/// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. +internal class BokehBlurProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies bokeh blur processing to the image. + /// The gamma highlight factor to use when applying the effect + /// + private readonly float gamma; + + /// + /// The size of each complex convolution kernel. + /// + private readonly int kernelSize; + + /// + /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) /// - /// The pixel format. - /// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. - internal class BokehBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + private readonly Vector4[] kernelParameters; + + /// + /// The kernel components for the current instance + /// + private readonly Complex64[][] kernels; + + /// + /// 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 BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// The gamma highlight factor to use when applying the effect - /// - private readonly float gamma; + this.gamma = definition.Gamma; + this.kernelSize = (definition.Radius * 2) + 1; - /// - /// The size of each complex convolution kernel. - /// - private readonly int kernelSize; - - /// - /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) - /// - private readonly Vector4[] kernelParameters; - - /// - /// The kernel components for the current instance - /// - private readonly Complex64[][] kernels; - - /// - /// 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 BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.gamma = definition.Gamma; - this.kernelSize = (definition.Radius * 2) + 1; + // Get the bokeh blur data + BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( + definition.Radius, + this.kernelSize, + definition.Components); - // Get the bokeh blur data - BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( - definition.Radius, - this.kernelSize, - definition.Components); + this.kernelParameters = data.Parameters; + this.kernels = data.Kernels; + } - this.kernelParameters = data.Parameters; - this.kernels = data.Kernels; - } + /// + /// Gets the complex kernels to use to apply the blur for the current instance + /// + public IReadOnlyList Kernels => this.kernels; - /// - /// Gets the complex kernels to use to apply the blur for the current instance - /// - public IReadOnlyList Kernels => this.kernels; + /// + /// Gets the kernel parameters used to compute the pixel values from each complex pixel + /// + public IReadOnlyList KernelParameters => this.kernelParameters; - /// - /// Gets the kernel parameters used to compute the pixel values from each complex pixel - /// - public IReadOnlyList KernelParameters => this.kernelParameters; + /// + protected override void OnFrameApply(ImageFrame source) + { + var sourceRectangle = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - /// - protected override void OnFrameApply(ImageFrame source) + // Preliminary gamma highlight pass + if (this.gamma == 3F) + { + var gammaOperation = new ApplyGamma3ExposureRowOperation(sourceRectangle, source.PixelBuffer, this.Configuration); + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in gammaOperation); + } + else { - var sourceRectangle = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var gammaOperation = new ApplyGammaExposureRowOperation(sourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in gammaOperation); + } - // Preliminary gamma highlight pass - if (this.gamma == 3F) - { - var gammaOperation = new ApplyGamma3ExposureRowOperation(sourceRectangle, source.PixelBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); - } - else - { - var gammaOperation = new ApplyGammaExposureRowOperation(sourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in gammaOperation); - } + // 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); - // 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); + // Perform the 1D convolutions on all the kernel components and accumulate the results + this.OnFrameApplyCore(source, sourceRectangle, this.Configuration, processingBuffer); - // Perform the 1D convolutions on all the kernel components and accumulate the results - this.OnFrameApplyCore(source, sourceRectangle, this.Configuration, processingBuffer); + // Apply the inverse gamma exposure pass, and write the final pixel data + if (this.gamma == 3F) + { + var operation = new ApplyInverseGamma3ExposureRowOperation(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration); + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in operation); + } + else + { + var operation = new ApplyInverseGammaExposureRowOperation(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, 1 / this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + sourceRectangle, + in operation); + } + } - // Apply the inverse gamma exposure pass, and write the final pixel data - if (this.gamma == 3F) - { - var operation = new ApplyInverseGamma3ExposureRowOperation(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); - } - else - { - var operation = new ApplyInverseGammaExposureRowOperation(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, 1 / this.gamma); - ParallelRowIterator.IterateRows( - this.Configuration, - sourceRectangle, - in operation); - } + /// + /// 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) + { + // Allocate the buffer with the intermediate convolution results + using Buffer2D firstPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size()); + + // Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width + // to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation. + // The second half of the buffer will just target the temporary buffer of complex pixel values. + // This is needed because the bokeh blur operates as TPixel -> complex -> TPixel, so we cannot + // convert back to standard pixels after each separate 1D convolution pass. Like in the gaussian + // blur though, we preallocate and compute the kernel sampling maps before processing each complex + // component, to avoid recomputing the same sampling map once per convolution pass. Since we are + // doing two 1D convolutions with the same kernel, we can use a single kernel sampling map as if + // we were using a 2D kernel with each dimension being the same as the length of our kernel, and + // use the two sampling offset spans resulting from this same map. This saves some extra work. + using var mapXY = new KernelSamplingMap(configuration.MemoryAllocator); + + mapXY.BuildSamplingOffsetMap(this.kernelSize, this.kernelSize, sourceRectangle); + + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + + // Perform two 1D convolutions for each component in the current instance + for (int i = 0; i < this.kernels.Length; i++) + { + // Compute the resulting complex buffer for the current component + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Horizontal convolution + var horizontalOperation = new FirstPassConvolutionRowOperation( + sourceRectangle, + firstPassBuffer, + source.PixelBuffer, + mapXY, + kernel, + configuration); + + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); + + // Vertical 1D convolutions to accumulate the partial results on the target buffer + var verticalOperation = new BokehBlurProcessor.SecondPassConvolutionRowOperation( + sourceRectangle, + processingBuffer, + firstPassBuffer, + mapXY, + kernel, + parameters.Z, + parameters.W); + + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in verticalOperation); } + } - /// - /// 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) + /// + /// A implementing the vertical convolution logic for . + /// + private readonly struct FirstPassConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly Complex64[] kernel; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public FirstPassConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + KernelSamplingMap map, + Complex64[] kernel, + Configuration configuration) { - // Allocate the buffer with the intermediate convolution results - using Buffer2D firstPassBuffer = configuration.MemoryAllocator.Allocate2D(source.Size()); - - // Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width - // to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation. - // The second half of the buffer will just target the temporary buffer of complex pixel values. - // This is needed because the bokeh blur operates as TPixel -> complex -> TPixel, so we cannot - // convert back to standard pixels after each separate 1D convolution pass. Like in the gaussian - // blur though, we preallocate and compute the kernel sampling maps before processing each complex - // component, to avoid recomputing the same sampling map once per convolution pass. Since we are - // doing two 1D convolutions with the same kernel, we can use a single kernel sampling map as if - // we were using a 2D kernel with each dimension being the same as the length of our kernel, and - // use the two sampling offset spans resulting from this same map. This saves some extra work. - using var mapXY = new KernelSamplingMap(configuration.MemoryAllocator); - - mapXY.BuildSamplingOffsetMap(this.kernelSize, this.kernelSize, sourceRectangle); - - ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); - ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - - // Perform two 1D convolutions for each component in the current instance - for (int i = 0; i < this.kernels.Length; i++) - { - // Compute the resulting complex buffer for the current component - Complex64[] kernel = Unsafe.Add(ref baseRef, i); - Vector4 parameters = Unsafe.Add(ref paramsRef, i); - - // Horizontal convolution - var horizontalOperation = new FirstPassConvolutionRowOperation( - sourceRectangle, - firstPassBuffer, - source.PixelBuffer, - mapXY, - kernel, - configuration); - - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in horizontalOperation); - - // Vertical 1D convolutions to accumulate the partial results on the target buffer - var verticalOperation = new BokehBlurProcessor.SecondPassConvolutionRowOperation( - sourceRectangle, - processingBuffer, - firstPassBuffer, - mapXY, - kernel, - parameters.Z, - parameters.W); - - ParallelRowIterator.IterateRows( - configuration, - sourceRectangle, - in verticalOperation); - } + this.bounds = bounds; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernel = kernel; + this.configuration = configuration; } - /// - /// A implementing the vertical convolution logic for . - /// - private readonly struct FirstPassConvolutionRowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - private readonly Rectangle bounds; - private readonly Buffer2D targetValues; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly Complex64[] kernel; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public FirstPassConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetValues, - Buffer2D sourcePixels, - KernelSamplingMap map, - Complex64[] kernel, - Configuration configuration) + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; + + // Clear the target buffer for each row run + Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); + targetBuffer.Clear(); + + // Execute the bulk pixel format conversion for the current row + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); + ref ComplexVector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); + ref ComplexVector4 targetEnd = ref Unsafe.Add(ref targetStart, span.Length); + ref Complex64 kernelBase = ref this.kernel[0]; + ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); + ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); + + while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) { - this.bounds = bounds; - this.targetValues = targetValues; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - } + ref Complex64 kernelStart = ref kernelBase; + ref int sampleColumnStart = ref sampleColumnBase; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - // Clear the target buffer for each row run - Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); - targetBuffer.Clear(); - - // Execute the bulk pixel format conversion for the current row - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); - ref ComplexVector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref ComplexVector4 targetEnd = ref Unsafe.Add(ref targetStart, span.Length); - ref Complex64 kernelBase = ref this.kernel[0]; - ref Complex64 kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) { - ref Complex64 kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); + targetStart.Sum(kernelStart * sample); - targetStart.Sum(kernelStart * sample); - - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } - - // Shift the base column sampling reference by one row at the end of each outer - // iteration so that the inner tight loop indexing can skip the multiplication - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); - targetStart = ref Unsafe.Add(ref targetStart, 1); + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); } + + // Shift the base column sampling reference by one row at the end of each outer + // iteration so that the inner tight loop indexing can skip the multiplication + sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); + targetStart = ref Unsafe.Add(ref targetStart, 1); } } + } - /// - /// A implementing the gamma exposure logic for . - /// - private readonly struct ApplyGammaExposureRowOperation : IRowOperation - { - 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) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.configuration = configuration; - this.gamma = gamma; - } + /// + /// A implementing the gamma exposure logic for . + /// + private readonly struct ApplyGammaExposureRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration, + float gamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + this.gamma = gamma; + } - for (int x = 0; x < this.bounds.Width; x++) - { - 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); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + for (int x = 0; x < this.bounds.Width; x++) + { + 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); } + } - /// - /// A implementing the 3F gamma exposure logic for . - /// - private readonly struct ApplyGamma3ExposureRowOperation : IRowOperation + /// + /// A implementing the 3F gamma exposure logic for . + /// + private readonly struct ApplyGamma3ExposureRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGamma3ExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyGamma3ExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Configuration configuration) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.configuration = configuration; - } + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - Numerics.CubePowOnXYZ(span); + Numerics.CubePowOnXYZ(span); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } + } - /// - /// A implementing the inverse gamma exposure logic for . - /// - private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation + /// + /// A implementing the inverse gamma exposure logic for . + /// + private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation + { + 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) { - 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; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Vector4 low = Vector4.Zero; - var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + this.inverseGamma = inverseGamma; + } - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y)[this.bounds.X..]; - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - Vector4 clamp = Numerics.Clamp(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); - } + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y)[this.bounds.X..]; + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + Vector4 clamp = Numerics.Clamp(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[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); } + } - /// - /// A implementing the inverse 3F gamma exposure logic for . - /// - private readonly struct ApplyInverseGamma3ExposureRowOperation : IRowOperation + /// + /// A implementing the inverse 3F gamma exposure logic for . + /// + private readonly struct ApplyInverseGamma3ExposureRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGamma3ExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourceValues, + Configuration configuration) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourceValues; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public ApplyInverseGamma3ExposureRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourceValues, - Configuration configuration) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourceValues = sourceValues; - this.configuration = configuration; - } + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void Invoke(int y) - { - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe void Invoke(int y) + { + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); - Numerics.CubeRootOnXYZ(sourceRowSpan); + Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); + Numerics.CubeRootOnXYZ(sourceRowSpan); - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs index db5895f3e9..cf647db6d6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs @@ -1,25 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Wrapping mode for the border pixels in convolution processing. +/// +public enum BorderWrappingMode : byte { - /// - /// Wrapping mode for the border pixels in convolution processing. - /// - public enum BorderWrappingMode : byte - { - /// Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh - Repeat = 0, + /// Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh + Repeat = 0, - /// Take values from the opposite edge: cdefgh|abcdefgh|abcdefg - Wrap = 1, + /// Take values from the opposite edge: cdefgh|abcdefgh|abcdefg + Wrap = 1, - /// Mirror the last few border values: fedcba|abcdefgh|hgfedcb - /// This Mode is similar to , but here the very border pixel is repeated. - Mirror = 2, + /// Mirror the last few border values: fedcba|abcdefgh|hgfedcb + /// This Mode is similar to , but here the very border pixel is repeated. + Mirror = 2, - /// Bounce off the border: fedcb|abcdefgh|gfedcb - /// This Mode is similar to , but here the very border pixel is not repeated. - Bounce = 3 - } + /// Bounce off the border: fedcb|abcdefgh|gfedcb + /// This Mode is similar to , but here the very border pixel is not repeated. + Bounce = 3 } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 776c031b43..7e50c53cc7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -3,70 +3,69 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a box blur processor of a given radius. +/// +public sealed class BoxBlurProcessor : IImageProcessor { /// - /// Defines a box blur processor of a given radius. + /// The default radius used by the parameterless constructor. /// - public sealed class BoxBlurProcessor : IImageProcessor - { - /// - /// The default radius used by the parameterless constructor. - /// - public const int DefaultRadius = 7; + public const int DefaultRadius = 7; - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + this.Radius = radius; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public BoxBlurProcessor(int radius) - : this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public BoxBlurProcessor(int radius) + : this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) + { + } - /// - /// Initializes a new instance of the class. - /// - public BoxBlurProcessor() - : this(DefaultRadius) - { - } + /// + /// Initializes a new instance of the class. + /// + public BoxBlurProcessor() + : this(DefaultRadius) + { + } - /// - /// Gets the Radius. - /// - public int Radius { get; } + /// + /// Gets the Radius. + /// + public int Radius { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BoxBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new BoxBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs index b264dcb6bc..ff3b30e9c1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -1,91 +1,89 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies box blur processing to the image. +/// +/// The pixel format. +internal class BoxBlurProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies box blur processing to the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class BoxBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 BoxBlurProcessor(Configuration configuration, BoxBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// 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 BoxBlurProcessor(Configuration configuration, BoxBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = CreateBoxKernel(kernelSize); - } + int kernelSize = (definition.Radius * 2) + 1; + this.Kernel = CreateBoxKernel(kernelSize); + } - /// - /// 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. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public BoxBlurProcessor( - Configuration configuration, - BoxBlurProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = CreateBoxKernel(kernelSize); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// 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. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public BoxBlurProcessor( + Configuration configuration, + BoxBlurProcessor definition, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + : base(configuration, source, sourceRectangle) + { + int kernelSize = (definition.Radius * 2) + 1; + this.Kernel = CreateBoxKernel(kernelSize); + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } + /// + /// Gets the 1D convolution kernel. + /// + public float[] Kernel { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - protected override void OnFrameApply(ImageFrame source) - { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - processor.Apply(source); - } + processor.Apply(source); + } - /// - /// Create a 1 dimensional Box kernel. - /// - /// The maximum size of the kernel in either direction. - /// The . - private static float[] CreateBoxKernel(int kernelSize) - { - var kernel = new float[kernelSize]; + /// + /// Create a 1 dimensional Box kernel. + /// + /// The maximum size of the kernel in either direction. + /// The . + private static float[] CreateBoxKernel(int kernelSize) + { + var kernel = new float[kernelSize]; - kernel.AsSpan().Fill(1F / kernelSize); + kernel.AsSpan().Fill(1F / kernelSize); - return kernel; - } + return kernel; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 632cac00de..b8a61e5a32 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -6,90 +6,89 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that uses two one-dimensional matrices to perform convolution against an image. +/// +/// The pixel format. +internal class Convolution2DProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Defines a processor that uses two one-dimensional matrices to perform convolution against an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class Convolution2DProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The horizontal gradient operator. + /// The vertical gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public Convolution2DProcessor( + Configuration configuration, + in DenseMatrix kernelX, + in DenseMatrix kernelY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The horizontal gradient operator. - /// The vertical gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public Convolution2DProcessor( - Configuration configuration, - in DenseMatrix kernelX, - in DenseMatrix kernelY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); - this.KernelX = kernelX; - this.KernelY = kernelY; - this.PreserveAlpha = preserveAlpha; - } - - /// - /// Gets the horizontal convolution kernel. - /// - public DenseMatrix KernelX { get; } + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); + this.KernelX = kernelX; + this.KernelY = kernelY; + this.PreserveAlpha = preserveAlpha; + } - /// - /// Gets the vertical convolution kernel. - /// - public DenseMatrix KernelY { get; } + /// + /// Gets the horizontal convolution kernel. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } + /// + /// Gets the vertical convolution kernel. + /// + public DenseMatrix KernelY { get; } - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } - source.CopyTo(targetPixels); + /// + protected override void OnFrameApply(ImageFrame source) + { + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + source.CopyTo(targetPixels); - // We use a rectangle 3x the interest width to allocate a buffer big enough - // for source and target bulk pixel conversion. - var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 3, interest.Height); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var map = new KernelSamplingMap(allocator)) - { - // Since the kernel sizes are identical we can use a single map. - map.BuildSamplingOffsetMap(this.KernelY, interest); + // We use a rectangle 3x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 3, interest.Height); - var operation = new Convolution2DRowOperation( - interest, - targetPixels, - source.PixelBuffer, - map, - this.KernelY, - this.KernelX, - this.Configuration, - this.PreserveAlpha); + using (var map = new KernelSamplingMap(allocator)) + { + // Since the kernel sizes are identical we can use a single map. + map.BuildSamplingOffsetMap(this.KernelY, interest); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - operationBounds, - in operation); - } + var operation = new Convolution2DRowOperation( + interest, + targetPixels, + source.PixelBuffer, + map, + this.KernelY, + this.KernelX, + this.Configuration, + this.PreserveAlpha); - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + ParallelRowIterator.IterateRows, Vector4>( + this.Configuration, + operationBounds, + in operation); } + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index 8b4eaab801..caeb225731 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,185 +8,184 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// A implementing the logic for 2D convolution. +/// +internal readonly struct Convolution2DRowOperation : IRowOperation + where TPixel : unmanaged, IPixel { - /// - /// A implementing the logic for 2D convolution. - /// - internal readonly struct Convolution2DRowOperation : IRowOperation - where TPixel : unmanaged, IPixel + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly DenseMatrix kernelMatrixY; + private readonly DenseMatrix kernelMatrixX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Convolution2DRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + DenseMatrix kernelMatrixY, + DenseMatrix kernelMatrixX, + Configuration configuration, + bool preserveAlpha) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernelMatrixY; - private readonly DenseMatrix kernelMatrixX; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Convolution2DRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernelMatrixY, - DenseMatrix kernelMatrixX, - Configuration configuration, - bool preserveAlpha) + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernelMatrixY = kernelMatrixY; + this.kernelMatrixX = kernelMatrixX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) + { + if (this.preserveAlpha) { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernelMatrixY = kernelMatrixY; - this.kernelMatrixX = kernelMatrixX; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; + this.Convolve3(y, span); } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) + else { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } + this.Convolve4(y, span); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve3(int y, Span span) + { + // Span is 3x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span[..boundsWidth]; + Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); + Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); + + var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); + + // Clear the target buffers for each row run. + targetYBuffer.Clear(); + targetXBuffer.Clear(); + ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); + ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; + Span sourceRow; + for (int kY = 0; kY < kernelY.Rows; kY++) { - // Span is 3x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..boundsWidth]; - Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); - Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); - - var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); - - // Clear the target buffers for each row run. - targetYBuffer.Clear(); - targetXBuffer.Clear(); - ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); - ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); - - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - Span sourceRow; - for (int kY = 0; kY < kernelY.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int sampleY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - for (int x = 0; x < sourceBuffer.Length; x++) + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); + ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); + + for (int kX = 0; kX < kernelY.Columns; kX++) { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); - ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); - - for (int kX = 0; kX < kernelY.Columns; kX++) - { - int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); - targetY += kernelX[kY, kX] * sample; - targetX += kernelY[kY, kX] * sample; - } + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); + targetY += kernelX[kY, kX] * sample; + targetX += kernelY[kY, kX] * sample; } } + } - // Now we need to combine the values and copy the original alpha values - // from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - for (int x = 0; x < sourceRow.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); - Vector4 vectorY = target; - Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); + // Now we need to combine the values and copy the original alpha values + // from the source row. + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; - } + for (int x = 0; x < sourceRow.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); + Vector4 vectorY = target; + Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); + target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve4(int y, Span span) + { + // Span is 3x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span[..boundsWidth]; + Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); + Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); + + var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); + + // Clear the target buffers for each row run. + targetYBuffer.Clear(); + targetXBuffer.Clear(); + ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); + ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; + for (int kY = 0; kY < kernelY.Rows; kY++) { - // Span is 3x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..boundsWidth]; - Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); - Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); - - var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); - - // Clear the target buffers for each row run. - targetYBuffer.Clear(); - targetXBuffer.Clear(); - ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); - ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); - - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - for (int kY = 0; kY < kernelY.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int sampleY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - Numerics.Premultiply(sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - for (int x = 0; x < sourceBuffer.Length; x++) + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); + ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); + + for (int kX = 0; kX < kernelY.Columns; kX++) { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); - ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); - - for (int kX = 0; kX < kernelY.Columns; kX++) - { - int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); - targetY += kernelX[kY, kX] * sample; - targetX += kernelY[kY, kX] * sample; - } + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); + targetY += kernelX[kY, kX] * sample; + targetX += kernelY[kY, kX] * sample; } } + } - // Now we need to combine the values - for (int x = 0; x < targetYBuffer.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); - Vector4 vectorY = target; - Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); + // Now we need to combine the values + for (int x = 0; x < targetYBuffer.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); + Vector4 vectorY = target; + Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); - target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - } + target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + } - Numerics.UnPremultiply(targetYBuffer); + Numerics.UnPremultiply(targetYBuffer); - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); - } + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs index ba991bcde0..6052934d89 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs @@ -1,54 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// A stack only struct used for reducing reference indirection during 2D convolution operations. +/// +internal readonly ref struct Convolution2DState { - /// - /// A stack only struct used for reducing reference indirection during 2D convolution operations. - /// - internal readonly ref struct Convolution2DState + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public Convolution2DState( + in DenseMatrix kernelY, + in DenseMatrix kernelX, + KernelSamplingMap map) { - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly int kernelHeight; - private readonly int kernelWidth; - - public Convolution2DState( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - KernelSamplingMap map) - { - // We check the kernels are the same size upstream. - this.KernelY = new ReadOnlyKernel(kernelY); - this.KernelX = new ReadOnlyKernel(kernelX); - this.kernelHeight = kernelY.Rows; - this.kernelWidth = kernelY.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } - - public readonly ReadOnlyKernel KernelY - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } - - public readonly ReadOnlyKernel KernelX - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + // We check the kernels are the same size upstream. + this.KernelY = new ReadOnlyKernel(kernelY); + this.KernelX = new ReadOnlyKernel(kernelX); + this.kernelHeight = kernelY.Rows; + this.kernelWidth = kernelY.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + public readonly ReadOnlyKernel KernelY + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(int row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + get; + } + public readonly ReadOnlyKernel KernelX + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(int column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); + get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 697351abc4..7aa83b0dd0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,433 +8,432 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that uses two one-dimensional matrices to perform two-pass convolution against an image. +/// +/// The pixel format. +internal class Convolution2PassProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Defines a processor that uses two one-dimensional matrices to perform two-pass convolution against an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class Convolution2PassProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The 1D convolution kernel. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public Convolution2PassProcessor( + Configuration configuration, + float[] kernel, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + : base(configuration, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 1D convolution kernel. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public Convolution2PassProcessor( - Configuration configuration, - float[] kernel, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - this.Kernel = kernel; - this.PreserveAlpha = preserveAlpha; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + this.Kernel = kernel; + this.PreserveAlpha = preserveAlpha; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the convolution kernel. - /// - public float[] Kernel { get; } + /// + /// Gets the convolution kernel. + /// + public float[] Kernel { get; } - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - protected override void OnFrameApply(ImageFrame source) + /// + protected override void OnFrameApply(ImageFrame source) + { + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We use a rectangle 2x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); + + // We can create a single sampling map with the size as if we were using the non separated 2D kernel + // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. + using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); + + mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); + + // Horizontal convolution + var horizontalOperation = new HorizontalConvolutionRowOperation( + interest, + firstPassPixels, + source.PixelBuffer, + mapXY, + this.Kernel, + this.Configuration, + this.PreserveAlpha); + + ParallelRowIterator.IterateRows( + this.Configuration, + operationBounds, + in horizontalOperation); + + // Vertical convolution + var verticalOperation = new VerticalConvolutionRowOperation( + interest, + source.PixelBuffer, + firstPassPixels, + mapXY, + this.Kernel, + this.Configuration, + this.PreserveAlpha); + + ParallelRowIterator.IterateRows( + this.Configuration, + operationBounds, + in verticalOperation); + } + + /// + /// A implementing the logic for the horizontal 1D convolution. + /// + internal readonly struct HorizontalConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly float[] kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HorizontalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + float[] kernel, + Configuration configuration, + bool preserveAlpha) { - using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - - // We use a rectangle 2x the interest width to allocate a buffer big enough - // for source and target bulk pixel conversion. - var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); - - // We can create a single sampling map with the size as if we were using the non separated 2D kernel - // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. - using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); - - mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY); - - // Horizontal convolution - var horizontalOperation = new HorizontalConvolutionRowOperation( - interest, - firstPassPixels, - source.PixelBuffer, - mapXY, - this.Kernel, - this.Configuration, - this.PreserveAlpha); - - ParallelRowIterator.IterateRows( - this.Configuration, - operationBounds, - in horizontalOperation); - - // Vertical convolution - var verticalOperation = new VerticalConvolutionRowOperation( - interest, - source.PixelBuffer, - firstPassPixels, - mapXY, - this.Kernel, - this.Configuration, - this.PreserveAlpha); - - ParallelRowIterator.IterateRows( - this.Configuration, - operationBounds, - in verticalOperation); + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; } - /// - /// A implementing the logic for the horizontal 1D convolution. - /// - internal readonly struct HorizontalConvolutionRowOperation : IRowOperation + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly float[] kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public HorizontalConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - float[] kernel, - Configuration configuration, - bool preserveAlpha) + if (this.preserveAlpha) { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; + this.Convolve3(y, span); } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) + else { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } + this.Convolve4(y, span); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; - - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve3(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; - // Clear the target buffer for each row run. - targetBuffer.Clear(); + Span sourceBuffer = span[..this.bounds.Width]; + Span targetBuffer = span[this.bounds.Width..]; - // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + // Clear the target buffer for each row run. + targetBuffer.Clear(); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, sourceBuffer.Length); - ref float kernelBase = ref this.kernel[0]; - ref float kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - ref float kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); + ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, sourceBuffer.Length); + ref float kernelBase = ref this.kernel[0]; + ref float kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); + ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); + while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) + { + ref float kernelStart = ref kernelBase; + ref int sampleColumnStart = ref sampleColumnBase; - targetStart += kernelStart * sample; + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + { + Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } + targetStart += kernelStart * sample; - targetStart = ref Unsafe.Add(ref targetStart, 1); - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); } - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + targetStart = ref Unsafe.Add(ref targetStart, 1); + sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); + } - targetStart = ref MemoryMarshal.GetReference(targetBuffer); + // Now we need to copy the original alpha values from the source row. + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - targetStart.W = sourceBase.W; + targetStart = ref MemoryMarshal.GetReference(targetBuffer); - targetStart = ref Unsafe.Add(ref targetStart, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } + while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) + { + targetStart.W = sourceBase.W; - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + targetStart = ref Unsafe.Add(ref targetStart, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + } - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve4(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; - // Clear the target buffer for each row run. - targetBuffer.Clear(); + Span sourceBuffer = span[..this.bounds.Width]; + Span targetBuffer = span[this.bounds.Width..]; - // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + // Clear the target buffer for each row run. + targetBuffer.Clear(); - Numerics.Premultiply(sourceBuffer); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); - ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, sourceBuffer.Length); - ref float kernelBase = ref this.kernel[0]; - ref float kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); - ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); + Numerics.Premultiply(sourceBuffer); - while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) - { - ref float kernelStart = ref kernelBase; - ref int sampleColumnStart = ref sampleColumnBase; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); + ref Vector4 targetEnd = ref Unsafe.Add(ref targetStart, sourceBuffer.Length); + ref float kernelBase = ref this.kernel[0]; + ref float kernelEnd = ref Unsafe.Add(ref kernelBase, kernelSize); + ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); + while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) + { + ref float kernelStart = ref kernelBase; + ref int sampleColumnStart = ref sampleColumnBase; - targetStart += kernelStart * sample; + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + { + Vector4 sample = Unsafe.Add(ref sourceBase, sampleColumnStart - boundsX); - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); - } + targetStart += kernelStart * sample; - targetStart = ref Unsafe.Add(ref targetStart, 1); - sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleColumnStart = ref Unsafe.Add(ref sampleColumnStart, 1); } - Numerics.UnPremultiply(targetBuffer); - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + targetStart = ref Unsafe.Add(ref targetStart, 1); + sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, kernelSize); } + + Numerics.UnPremultiply(targetBuffer); + + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } + } - /// - /// A implementing the logic for the vertical 1D convolution. - /// - internal readonly struct VerticalConvolutionRowOperation : IRowOperation + /// + /// A implementing the logic for the vertical 1D convolution. + /// + internal readonly struct VerticalConvolutionRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly float[] kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public VerticalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + float[] kernel, + Configuration configuration, + bool preserveAlpha) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly float[] kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public VerticalConvolutionRowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - float[] kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invoke(int y, Span span) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) + { + if (this.preserveAlpha) { - if (this.preserveAlpha) - { - this.Convolve3(y, span); - } - else - { - this.Convolve4(y, span); - } + this.Convolve3(y, span); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve3(int y, Span span) + else { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; + this.Convolve4(y, span); + } + } - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve3(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); + Span sourceBuffer = span[..this.bounds.Width]; + Span targetBuffer = span[this.bounds.Width..]; - // Clear the target buffer for each row run. - targetBuffer.Clear(); + ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - ref float kernelStart = ref this.kernel[0]; - ref float kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); + // Clear the target buffer for each row run. + targetBuffer.Clear(); - Span sourceRow; - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + ref float kernelStart = ref this.kernel[0]; + ref float kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + Span sourceRow; + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); - ref Vector4 targetStart = ref targetBase; - float factor = kernelStart; + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetStart += factor * sourceBase; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); + ref Vector4 targetStart = ref targetBase; + float factor = kernelStart; - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + targetStart += factor * sourceBase; - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + targetStart = ref Unsafe.Add(ref targetStart, 1); } - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - { - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); + } - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetBase.W = sourceBase.W; + // Now we need to copy the original alpha values from the source row. + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); - targetBase = ref Unsafe.Add(ref targetBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - } + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + targetBase.W = sourceBase.W; - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + targetBase = ref Unsafe.Add(ref targetBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Convolve4(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelSize = this.kernel.Length; + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + } - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve4(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelSize = this.kernel.Length; - ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); + Span sourceBuffer = span[..this.bounds.Width]; + Span targetBuffer = span[this.bounds.Width..]; - // Clear the target buffer for each row run. - targetBuffer.Clear(); + ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - ref float kernelStart = ref this.kernel[0]; - ref float kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); + // Clear the target buffer for each row run. + targetBuffer.Clear(); - Span sourceRow; - while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + ref float kernelStart = ref this.kernel[0]; + ref float kernelEnd = ref Unsafe.Add(ref kernelStart, kernelSize); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + Span sourceRow; + while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - Numerics.Premultiply(sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); - ref Vector4 targetStart = ref targetBase; - float factor = kernelStart; + Numerics.Premultiply(sourceBuffer); - while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) - { - targetStart += factor * sourceBase; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); + ref Vector4 targetStart = ref targetBase; + float factor = kernelStart; - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - targetStart = ref Unsafe.Add(ref targetStart, 1); - } + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + targetStart += factor * sourceBase; - kernelStart = ref Unsafe.Add(ref kernelStart, 1); - sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + targetStart = ref Unsafe.Add(ref targetStart, 1); } - Numerics.UnPremultiply(targetBuffer); - - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + kernelStart = ref Unsafe.Add(ref kernelStart, 1); + sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); } + + Numerics.UnPremultiply(targetBuffer); + + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index ef25de6396..d2833fea9b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -1,89 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +internal static class ConvolutionProcessorHelpers { - internal static class ConvolutionProcessorHelpers + /// + /// Kernel radius is calculated using the minimum viable value. + /// See . + /// + internal static int GetDefaultGaussianRadius(float sigma) + => (int)MathF.Ceiling(sigma * 3); + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function. + /// + /// The convolution kernel. + internal static float[] CreateGaussianBlurKernel(int size, float weight) { - /// - /// Kernel radius is calculated using the minimum viable value. - /// See . - /// - internal static int GetDefaultGaussianRadius(float sigma) - => (int)MathF.Ceiling(sigma * 3); + var kernel = new float[size]; + + float sum = 0F; + float midpoint = (size - 1) / 2F; - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function. - /// - /// The convolution kernel. - internal static float[] CreateGaussianBlurKernel(int size, float weight) + for (int i = 0; i < size; i++) { - var kernel = new float[size]; + float x = i - midpoint; + float gx = Numerics.Gaussian(x, weight); + sum += gx; + kernel[i] = gx; + } - float sum = 0F; - float midpoint = (size - 1) / 2F; + // Normalize kernel so that the sum of all weights equals 1 + for (int i = 0; i < size; i++) + { + kernel[i] /= sum; + } - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = Numerics.Gaussian(x, weight); - sum += gx; - kernel[i] = gx; - } + return kernel; + } - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// The convolution kernel. + internal static float[] CreateGaussianSharpenKernel(int size, float weight) + { + var kernel = new float[size]; - return kernel; - } + float sum = 0; - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// The convolution kernel. - internal static float[] CreateGaussianSharpenKernel(int size, float weight) + float midpoint = (size - 1) / 2F; + for (int i = 0; i < size; i++) { - var kernel = new float[size]; - - float sum = 0; - - float midpoint = (size - 1) / 2F; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = Numerics.Gaussian(x, weight); - sum += gx; - kernel[i] = gx; - } + float x = i - midpoint; + float gx = Numerics.Gaussian(x, weight); + sum += gx; + kernel[i] = gx; + } - // Invert the kernel for sharpening. - int midpointRounded = (int)midpoint; - for (int i = 0; i < size; i++) + // Invert the kernel for sharpening. + int midpointRounded = (int)midpoint; + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) { - if (i == midpointRounded) - { - // Calculate central value - kernel[i] = (2F * sum) - kernel[i]; - } - else - { - // invert value - kernel[i] = -kernel[i]; - } + // Calculate central value + kernel[i] = (2F * sum) - kernel[i]; } - - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) + else { - kernel[i] /= sum; + // invert value + kernel[i] = -kernel[i]; } + } - return kernel; + // Normalize kernel so that the sum of all weights equals 1 + for (int i = 0; i < size; i++) + { + kernel[i] /= sum; } + + return kernel; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index ed8b63ab2e..d7a8f743c7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,196 +8,195 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. +/// +/// The pixel format. +internal class ConvolutionProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Defines a processor that uses a 2 dimensional matrix to perform convolution against an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class ConvolutionProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The 2d gradient operator. + /// Whether the convolution filter is applied to alpha as well as the color channels. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public ConvolutionProcessor( + Configuration configuration, + in DenseMatrix kernelXY, + bool preserveAlpha, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The 2d gradient operator. - /// Whether the convolution filter is applied to alpha as well as the color channels. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ConvolutionProcessor( - Configuration configuration, - in DenseMatrix kernelXY, - bool preserveAlpha, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.KernelXY = kernelXY; - this.PreserveAlpha = preserveAlpha; - } + this.KernelXY = kernelXY; + this.PreserveAlpha = preserveAlpha; + } - /// - /// Gets the 2d convolution kernel. - /// - public DenseMatrix KernelXY { get; } + /// + /// Gets the 2d convolution kernel. + /// + public DenseMatrix KernelXY { get; } - /// - /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } + /// + /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Size()); + /// + protected override void OnFrameApply(ImageFrame source) + { + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Size()); - source.CopyTo(targetPixels); + source.CopyTo(targetPixels); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // We use a rectangle 2x the interest width to allocate a buffer big enough - // for source and target bulk pixel conversion. - var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); - using (var map = new KernelSamplingMap(allocator)) - { - map.BuildSamplingOffsetMap(this.KernelXY, interest); + // We use a rectangle 2x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); + using (var map = new KernelSamplingMap(allocator)) + { + map.BuildSamplingOffsetMap(this.KernelXY, interest); - var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - operationBounds, - in operation); - } + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + operationBounds, + in operation); + } + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; } - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span[..this.bounds.Width]; + Span targetBuffer = span[this.bounds.Width..]; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - // Span is 2x bounds. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - Span sourceBuffer = span[..this.bounds.Width]; - Span targetBuffer = span[this.bounds.Width..]; + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + var state = new ConvolutionState(in this.kernel, this.map); + int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); - var state = new ConvolutionState(in this.kernel, this.map); - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow(row); + if (this.preserveAlpha) + { + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - if (this.preserveAlpha) + Span sourceRow; + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - // Clear the target buffer for each row run. - targetBuffer.Clear(); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - Span sourceRow; - for (int kY = 0; kY < state.Kernel.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - for (int x = 0; x < sourceBuffer.Length; x++) + for (int kX = 0; kX < state.Kernel.Columns; kX++) { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); - target += state.Kernel[kY, kX] * sample; - } + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += state.Kernel[kY, kX] * sample; } } + } - // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + // Now we need to copy the original alpha values from the source row. + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - for (int x = 0; x < sourceRow.Length; x++) - { - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; - } + for (int x = 0; x < sourceRow.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } - else + } + else + { + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - // Clear the target buffer for each row run. - targetBuffer.Clear(); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - for (int kY = 0; kY < state.Kernel.Rows; kY++) - { - // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); - Numerics.Premultiply(sourceBuffer); - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - for (int x = 0; x < sourceBuffer.Length; x++) + for (int kX = 0; kX < state.Kernel.Columns; kX++) { - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); - target += state.Kernel[kY, kX] * sample; - } + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += state.Kernel[kY, kX] * sample; } } - - Numerics.UnPremultiply(targetBuffer); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + Numerics.UnPremultiply(targetBuffer); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs index bbe975ef6c..3d0e551b68 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs @@ -1,45 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// A stack only struct used for reducing reference indirection during convolution operations. - /// - internal readonly ref struct ConvolutionState - { - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly int kernelHeight; - private readonly int kernelWidth; - - public ConvolutionState( - in DenseMatrix kernel, - KernelSamplingMap map) - { - this.Kernel = new ReadOnlyKernel(kernel); - this.kernelHeight = kernel.Rows; - this.kernelWidth = kernel.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - public readonly ReadOnlyKernel Kernel - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } +/// +/// A stack only struct used for reducing reference indirection during convolution operations. +/// +internal readonly ref struct ConvolutionState +{ + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(int row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + public ConvolutionState( + in DenseMatrix kernel, + KernelSamplingMap map) + { + this.Kernel = new ReadOnlyKernel(kernel); + this.kernelHeight = kernel.Rows; + this.kernelWidth = kernel.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + public readonly ReadOnlyKernel Kernel + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(int column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); + get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index 52c3ae2143..56a5155209 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -3,40 +3,39 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines edge detection using the two 1D gradient operators. +/// +public sealed class EdgeDetector2DProcessor : IImageProcessor { /// - /// Defines edge detection using the two 1D gradient operators. + /// Initializes a new instance of the class. /// - public sealed class EdgeDetector2DProcessor : IImageProcessor + /// The 2D edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale) { - /// - /// Initializes a new instance of the class. - /// - /// The 2D edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetector2DProcessor(EdgeDetector2DKernel kernel, bool grayscale) - { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + this.Kernel = kernel; + this.Grayscale = grayscale; + } - /// - /// Gets the 2D edge detector kernel. - /// - public EdgeDetector2DKernel Kernel { get; } + /// + /// Gets the 2D edge detector kernel. + /// + public EdgeDetector2DKernel Kernel { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetector2DProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 4b03989010..5af4360442 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -4,66 +4,65 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that detects edges within an image using two one-dimensional matrices. +/// +/// The pixel format. +internal class EdgeDetector2DProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly DenseMatrix kernelX; + private readonly DenseMatrix kernelY; + private readonly bool grayscale; + /// - /// Defines a processor that detects edges within an image using two one-dimensional matrices. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class EdgeDetector2DProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 EdgeDetector2DProcessor( + Configuration configuration, + EdgeDetector2DProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly DenseMatrix kernelX; - private readonly DenseMatrix kernelY; - private readonly bool grayscale; + this.kernelX = definition.Kernel.KernelX; + this.kernelY = definition.Kernel.KernelY; + this.grayscale = definition.Grayscale; + } - /// - /// 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 EdgeDetector2DProcessor( - Configuration configuration, - EdgeDetector2DProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) + /// + protected override void BeforeImageApply() + { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) { - this.kernelX = definition.Kernel.KernelX; - this.kernelY = definition.Kernel.KernelY; - this.grayscale = definition.Grayscale; + opaque.Execute(); } - /// - protected override void BeforeImageApply() + if (this.grayscale) { - 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); - } - - base.BeforeImageApply(); + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } - /// - protected override void OnFrameApply(ImageFrame source) - { - using var processor = new Convolution2DProcessor( - this.Configuration, - in this.kernelX, - in this.kernelY, - true, - this.Source, - this.SourceRectangle); + base.BeforeImageApply(); + } - processor.Apply(source); - } + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2DProcessor( + this.Configuration, + in this.kernelX, + in this.kernelY, + true, + this.Source, + this.SourceRectangle); + + processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs index 3902f27d24..69f9932c1a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -3,40 +3,39 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines edge detection using eight gradient operators. +/// +public sealed class EdgeDetectorCompassProcessor : IImageProcessor { /// - /// Defines edge detection using eight gradient operators. + /// Initializes a new instance of the class. /// - public sealed class EdgeDetectorCompassProcessor : IImageProcessor + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) { - /// - /// Initializes a new instance of the class. - /// - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetectorCompassProcessor(EdgeDetectorCompassKernel kernel, bool grayscale) - { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + this.Kernel = kernel; + this.Grayscale = grayscale; + } - /// - /// Gets the edge detector kernel. - /// - public EdgeDetectorCompassKernel Kernel { get; } + /// + /// Gets the edge detector kernel. + /// + public EdgeDetectorCompassKernel Kernel { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorCompassProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 990b55be05..75e8e98e91 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -9,127 +9,126 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that detects edges within an image using a eight two dimensional matrices. +/// +/// The pixel format. +internal class EdgeDetectorCompassProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly DenseMatrix[] kernels; + private readonly bool grayscale; + /// - /// Defines a processor that detects edges within an image using a eight two dimensional matrices. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class EdgeDetectorCompassProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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. + internal EdgeDetectorCompassProcessor( + Configuration configuration, + EdgeDetectorCompassProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.grayscale = definition.Grayscale; + this.kernels = definition.Kernel.Flatten(); + } + + /// + protected override void BeforeImageApply() { - private readonly DenseMatrix[] kernels; - private readonly bool grayscale; - - /// - /// 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. - internal EdgeDetectorCompassProcessor( - Configuration configuration, - EdgeDetectorCompassProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) { - this.grayscale = definition.Grayscale; - this.kernels = definition.Kernel.Flatten(); + opaque.Execute(); } - /// - protected override void BeforeImageApply() + if (this.grayscale) { - using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) - { - opaque.Execute(); - } + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); + } - if (this.grayscale) - { - new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); - } + base.BeforeImageApply(); + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We need a clean copy for each pass to start from + using ImageFrame cleanCopy = source.Clone(); - base.BeforeImageApply(); + using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[0], true, this.Source, interest)) + { + processor.Apply(source); } - /// - protected override void OnFrameApply(ImageFrame source) + if (this.kernels.Length == 1) { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + return; + } - // We need a clean copy for each pass to start from - using ImageFrame cleanCopy = source.Clone(); + // Additional runs + for (int i = 1; i < this.kernels.Length; i++) + { + using ImageFrame pass = cleanCopy.Clone(); - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[0], true, this.Source, interest)) + using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) { - processor.Apply(source); + processor.Apply(pass); } - if (this.kernels.Length == 1) - { - return; - } + var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + } - // Additional runs - for (int i = 1; i < this.kernels.Length; i++) - { - using ImageFrame pass = cleanCopy.Clone(); - - using (var processor = new ConvolutionProcessor(this.Configuration, in this.kernels[i], true, this.Source, interest)) - { - processor.Apply(pass); - } - - 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; } - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - 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; - } + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.DangerousGetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.DangerousGetRowSpan(y)); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = this.minX; x < this.maxX; x++) { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.DangerousGetRowSpan(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.DangerousGetRowSpan(y)); - - for (int x = this.minX; x < this.maxX; x++) - { - // 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); + // 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()); + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); - currentTargetPixel.FromVector4(pixelValue); - } + currentTargetPixel.FromVector4(pixelValue); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index 963e7c9829..b8eed0fcaa 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -3,40 +3,39 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines edge detection using a single 2D gradient operator. +/// +public sealed class EdgeDetectorProcessor : IImageProcessor { /// - /// Defines edge detection using a single 2D gradient operator. + /// Initializes a new instance of the class. /// - public sealed class EdgeDetectorProcessor : IImageProcessor + /// The edge detector kernel. + /// + /// Whether to convert the image to grayscale before performing edge detection. + /// + public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) { - /// - /// Initializes a new instance of the class. - /// - /// The edge detector kernel. - /// - /// Whether to convert the image to grayscale before performing edge detection. - /// - public EdgeDetectorProcessor(EdgeDetectorKernel kernel, bool grayscale) - { - this.Kernel = kernel; - this.Grayscale = grayscale; - } + this.Kernel = kernel; + this.Grayscale = grayscale; + } - /// - /// Gets the edge detector kernel. - /// - public EdgeDetectorKernel Kernel { get; } + /// + /// Gets the edge detector kernel. + /// + public EdgeDetectorKernel Kernel { get; } - /// - /// Gets a value indicating whether to convert the image to grayscale before performing - /// edge detection. - /// - public bool Grayscale { get; } + /// + /// Gets a value indicating whether to convert the image to grayscale before performing + /// edge detection. + /// + public bool Grayscale { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new EdgeDetectorProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index 9baa9db1f7..c353f46b5f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -4,57 +4,56 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines a processor that detects edges within an image using a single two dimensional matrix. +/// +/// The pixel format. +internal class EdgeDetectorProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly bool grayscale; + private readonly DenseMatrix kernelXY; + /// - /// Defines a processor that detects edges within an image using a single two dimensional matrix. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class EdgeDetectorProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The target area to process for the current processor instance. + public EdgeDetectorProcessor( + Configuration configuration, + EdgeDetectorProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly bool grayscale; - private readonly DenseMatrix kernelXY; - - /// - /// 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 target area to process for the current processor instance. - public EdgeDetectorProcessor( - Configuration configuration, - EdgeDetectorProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.kernelXY = definition.Kernel.KernelXY; - this.grayscale = definition.Grayscale; - } + this.kernelXY = definition.Kernel.KernelXY; + this.grayscale = definition.Grayscale; + } - /// - protected override void BeforeImageApply() + /// + protected override void BeforeImageApply() + { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) { - 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); - } - - base.BeforeImageApply(); + opaque.Execute(); } - /// - protected override void OnFrameApply(ImageFrame source) + if (this.grayscale) { - using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); - processor.Apply(source); + new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); } + + base.BeforeImageApply(); + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new ConvolutionProcessor(this.Configuration, in this.kernelXY, true, this.Source, this.SourceRectangle); + processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index bb44385d0b..e7dad69b59 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -3,119 +3,118 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines Gaussian blur by a (Sigma, Radius) pair. +/// +public sealed class GaussianBlurProcessor : IImageProcessor { /// - /// Defines Gaussian blur by a (Sigma, Radius) pair. + /// The default value for . /// - public sealed class GaussianBlurProcessor : IImageProcessor - { - /// - /// The default value for . - /// - public const float DefaultSigma = 3f; + public const float DefaultSigma = 3f; - /// - /// Initializes a new instance of the class. - /// - public GaussianBlurProcessor() - : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + public GaussianBlurProcessor() + : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GaussianBlurProcessor(float sigma) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + public GaussianBlurProcessor(float sigma) + : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GaussianBlurProcessor(int radius) - : this(radius / 3F, radius) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GaussianBlurProcessor(int radius) + : this(radius / 3F, radius) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GaussianBlurProcessor(float sigma, int radius) - : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GaussianBlurProcessor(float sigma, int radius) + : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - this.Sigma = sigma; - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// + /// + /// The to use when mapping the pixels outside of the border, in Y direction. + /// + public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + this.Sigma = sigma; + this.Radius = radius; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the sigma value representing the weight of the blur - /// - public float Sigma { get; } + /// + /// Gets the sigma value representing the weight of the blur + /// + public float Sigma { get; } - /// - /// Gets the radius defining the size of the area to sample. - /// - public int Radius { get; } + /// + /// Gets the radius defining the size of the area to sample. + /// + public int Radius { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GaussianBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index fd8aa21616..6518375b9e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -3,78 +3,77 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies Gaussian blur processing to an image. +/// +/// The pixel format. +internal class GaussianBlurProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies Gaussian blur processing to an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class GaussianBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 GaussianBlurProcessor( + Configuration configuration, + GaussianBlurProcessor definition, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// 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 GaussianBlurProcessor( - Configuration configuration, - GaussianBlurProcessor definition, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); - } + int kernelSize = (definition.Radius * 2) + 1; + this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); + } - /// - /// 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. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianBlurProcessor( - Configuration configuration, - GaussianBlurProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// 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. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public GaussianBlurProcessor( + Configuration configuration, + GaussianBlurProcessor definition, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + : base(configuration, source, sourceRectangle) + { + int kernelSize = (definition.Radius * 2) + 1; + this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } + /// + /// Gets the 1D convolution kernel. + /// + public float[] Kernel { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - protected override void OnFrameApply(ImageFrame source) - { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - processor.Apply(source); - } + processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 3292398f8f..ab5687e80c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -3,119 +3,118 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Defines Gaussian sharpening by a (Sigma, Radius) pair. +/// +public sealed class GaussianSharpenProcessor : IImageProcessor { /// - /// Defines Gaussian sharpening by a (Sigma, Radius) pair. + /// The default value for . /// - public sealed class GaussianSharpenProcessor : IImageProcessor - { - /// - /// The default value for . - /// - public const float DefaultSigma = 3f; + public const float DefaultSigma = 3f; - /// - /// Initializes a new instance of the class. - /// - public GaussianSharpenProcessor() - : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + public GaussianSharpenProcessor() + : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GaussianSharpenProcessor(float sigma) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + public GaussianSharpenProcessor(float sigma) + : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma)) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GaussianSharpenProcessor(int radius) - : this(radius / 3F, radius) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GaussianSharpenProcessor(int radius) + : this(radius / 3F, radius) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GaussianSharpenProcessor(float sigma, int radius) - : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GaussianSharpenProcessor(float sigma, int radius) + : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) + { + } - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - /// - /// The to use when mapping the pixels outside of the border, in X direction. - /// - /// - /// The to use when mapping the pixels outside of the border, in Y direction. - /// - public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) - { - this.Sigma = sigma; - this.Radius = radius; - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + /// + /// The to use when mapping the pixels outside of the border, in X direction. + /// + /// + /// The to use when mapping the pixels outside of the border, in Y direction. + /// + public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY) + { + this.Sigma = sigma; + this.Radius = radius; + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the sigma value representing the weight of the blur - /// - public float Sigma { get; } + /// + /// Gets the sigma value representing the weight of the blur + /// + public float Sigma { get; } - /// - /// Gets the radius defining the size of the area to sample. - /// - public int Radius { get; } + /// + /// Gets the radius defining the size of the area to sample. + /// + public int Radius { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index 220330385b..a286201dff 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -3,76 +3,75 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies Gaussian sharpening processing to the image. +/// +/// The pixel format. +internal class GaussianSharpenProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies Gaussian sharpening processing to the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class GaussianSharpenProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 GaussianSharpenProcessor( + Configuration configuration, + GaussianSharpenProcessor definition, + Image source, + Rectangle sourceRectangle) + : this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) { - /// - /// 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 GaussianSharpenProcessor( - Configuration configuration, - GaussianSharpenProcessor definition, - Image source, - Rectangle sourceRectangle) - : this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat) - { - } + } - /// - /// 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. - /// The to use when mapping the pixels outside of the border, in X direction. - /// The to use when mapping the pixels outside of the border, in Y direction. - public GaussianSharpenProcessor( - Configuration configuration, - GaussianSharpenProcessor definition, - Image source, - Rectangle sourceRectangle, - BorderWrappingMode borderWrapModeX, - BorderWrappingMode borderWrapModeY) - : base(configuration, source, sourceRectangle) - { - int kernelSize = (definition.Radius * 2) + 1; - this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); - this.BorderWrapModeX = borderWrapModeX; - this.BorderWrapModeY = borderWrapModeY; - } + /// + /// 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. + /// The to use when mapping the pixels outside of the border, in X direction. + /// The to use when mapping the pixels outside of the border, in Y direction. + public GaussianSharpenProcessor( + Configuration configuration, + GaussianSharpenProcessor definition, + Image source, + Rectangle sourceRectangle, + BorderWrappingMode borderWrapModeX, + BorderWrappingMode borderWrapModeY) + : base(configuration, source, sourceRectangle) + { + int kernelSize = (definition.Radius * 2) + 1; + this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); + this.BorderWrapModeX = borderWrapModeX; + this.BorderWrapModeY = borderWrapModeY; + } - /// - /// Gets the 1D convolution kernel. - /// - public float[] Kernel { get; } + /// + /// Gets the 1D convolution kernel. + /// + public float[] Kernel { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } - /// - protected override void OnFrameApply(ImageFrame source) - { - using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); + /// + protected override void OnFrameApply(ImageFrame source) + { + using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY); - processor.Apply(source); - } + processor.Apply(source); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs index 032a8ce445..35aa933cf7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernel.cs @@ -1,99 +1,97 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// A stack only, readonly, kernel matrix that can be indexed without +/// bounds checks when compiled in release mode. +/// +/// The type of each element in the kernel. +internal readonly ref struct Kernel + where T : struct, IEquatable { - /// - /// A stack only, readonly, kernel matrix that can be indexed without - /// bounds checks when compiled in release mode. - /// - /// The type of each element in the kernel. - internal readonly ref struct Kernel - where T : struct, IEquatable - { - private readonly Span values; + private readonly Span values; - public Kernel(DenseMatrix matrix) - { - this.Columns = matrix.Columns; - this.Rows = matrix.Rows; - this.values = matrix.Span; - } + public Kernel(DenseMatrix matrix) + { + this.Columns = matrix.Columns; + this.Rows = matrix.Rows; + this.values = matrix.Span; + } - public int Columns - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + public int Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - public int Rows - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + public int Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - public ReadOnlySpan Span - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.values; - } + public ReadOnlySpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.values; + } - public T this[int row, int column] + public T this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.CheckCoordinates(row, column); - ref T vBase = ref MemoryMarshal.GetReference(this.values); - return Unsafe.Add(ref vBase, (row * this.Columns) + column); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - this.CheckCoordinates(row, column); - ref T vBase = ref MemoryMarshal.GetReference(this.values); - Unsafe.Add(ref vBase, (row * this.Columns) + column) = value; - } + this.CheckCoordinates(row, column); + ref T vBase = ref MemoryMarshal.GetReference(this.values); + return Unsafe.Add(ref vBase, (row * this.Columns) + column); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetValue(int index, T value) + set { - this.CheckIndex(index); + this.CheckCoordinates(row, column); ref T vBase = ref MemoryMarshal.GetReference(this.values); - Unsafe.Add(ref vBase, index) = value; + Unsafe.Add(ref vBase, (row * this.Columns) + column) = value; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() => this.values.Clear(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetValue(int index, T value) + { + this.CheckIndex(index); + ref T vBase = ref MemoryMarshal.GetReference(this.values); + Unsafe.Add(ref vBase, index) = value; + } - [Conditional("DEBUG")] - private void CheckCoordinates(int row, int column) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() => this.values.Clear(); + + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) { - if (row < 0 || row >= this.Rows) - { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds."); - } + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds."); + } - if (column < 0 || column >= this.Columns) - { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds."); - } + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds."); } + } - [Conditional("DEBUG")] - private void CheckIndex(int index) + [Conditional("DEBUG")] + private void CheckIndex(int index) + { + if (index < 0 || index >= this.values.Length) { - if (index < 0 || index >= this.values.Length) - { - throw new ArgumentOutOfRangeException(nameof(index), index, $"{index} is outside the matrix bounds."); - } + throw new ArgumentOutOfRangeException(nameof(index), index, $"{index} is outside the matrix bounds."); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index a4ae9cb4c6..9680dd0ac4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -1,184 +1,182 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Provides a map of the convolution kernel sampling offsets. +/// +internal sealed class KernelSamplingMap : IDisposable { + private readonly MemoryAllocator allocator; + private bool isDisposed; + private IMemoryOwner yOffsets; + private IMemoryOwner xOffsets; + /// - /// Provides a map of the convolution kernel sampling offsets. + /// Initializes a new instance of the class. /// - internal sealed class KernelSamplingMap : IDisposable + /// The memory allocator. + public KernelSamplingMap(MemoryAllocator allocator) => this.allocator = allocator; + + /// + /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. + /// + /// The convolution kernel. + /// The source bounds. + public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) + => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. + /// + /// The height (number of rows) of the convolution kernel to use. + /// The width (number of columns) of the convolution kernel to use. + /// The source bounds. + public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds) + => this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); + + /// + /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. + /// + /// The height (number of rows) of the convolution kernel to use. + /// The width (number of columns) of the convolution kernel to use. + /// The source bounds. + /// The wrapping mode on the horizontal borders. + /// The wrapping mode on the vertical borders. + public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode) { - private readonly MemoryAllocator allocator; - private bool isDisposed; - private IMemoryOwner yOffsets; - private IMemoryOwner xOffsets; - - /// - /// Initializes a new instance of the class. - /// - /// The memory allocator. - public KernelSamplingMap(MemoryAllocator allocator) => this.allocator = allocator; - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The convolution kernel. - /// The source bounds. - public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) - => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The height (number of rows) of the convolution kernel to use. - /// The width (number of columns) of the convolution kernel to use. - /// The source bounds. - public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds) - => this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat); - - /// - /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. - /// - /// The height (number of rows) of the convolution kernel to use. - /// The width (number of columns) of the convolution kernel to use. - /// The source bounds. - /// The wrapping mode on the horizontal borders. - /// The wrapping mode on the vertical borders. - public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode) - { - this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight); - this.xOffsets = this.allocator.Allocate(bounds.Width * kernelWidth); + this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight); + this.xOffsets = this.allocator.Allocate(bounds.Width * kernelWidth); - int minY = bounds.Y; - int maxY = bounds.Bottom - 1; - int minX = bounds.X; - int maxX = bounds.Right - 1; + int minY = bounds.Y; + int maxY = bounds.Bottom - 1; + int minX = bounds.X; + int maxX = bounds.Right - 1; - BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode); - BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode); - } + BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode); + BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetRowOffsetSpan() => this.yOffsets.GetSpan(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowOffsetSpan() => this.yOffsets.GetSpan(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetColumnOffsetSpan() => this.xOffsets.GetSpan(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetColumnOffsetSpan() => this.xOffsets.GetSpan(); - /// - public void Dispose() + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.yOffsets?.Dispose(); - this.xOffsets?.Dispose(); + this.yOffsets?.Dispose(); + this.xOffsets?.Dispose(); - this.isDisposed = true; - } + this.isDisposed = true; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BuildOffsets(IMemoryOwner offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BuildOffsets(IMemoryOwner offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode) + { + int radius = kernelSize >> 1; + Span span = offsets.GetSpan(); + ref int spanBase = ref MemoryMarshal.GetReference(span); + for (int chunk = 0; chunk < boundsSize; chunk++) { - int radius = kernelSize >> 1; - Span span = offsets.GetSpan(); - ref int spanBase = ref MemoryMarshal.GetReference(span); - for (int chunk = 0; chunk < boundsSize; chunk++) + int chunkBase = chunk * kernelSize; + for (int i = 0; i < kernelSize; i++) { - int chunkBase = chunk * kernelSize; - for (int i = 0; i < kernelSize; i++) - { - Unsafe.Add(ref spanBase, chunkBase + i) = chunk + i + min - radius; - } + Unsafe.Add(ref spanBase, chunkBase + i) = chunk + i + min - radius; } - - CorrectBorder(span, kernelSize, min, max, borderMode); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CorrectBorder(Span span, int kernelSize, int min, int max, BorderWrappingMode borderMode) + CorrectBorder(span, kernelSize, min, max, borderMode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CorrectBorder(Span span, int kernelSize, int min, int max, BorderWrappingMode borderMode) + { + int affectedSize = (kernelSize >> 1) * kernelSize; + ref int spanBase = ref MemoryMarshal.GetReference(span); + if (affectedSize > 0) { - int affectedSize = (kernelSize >> 1) * kernelSize; - ref int spanBase = ref MemoryMarshal.GetReference(span); - if (affectedSize > 0) + switch (borderMode) { - switch (borderMode) - { - case BorderWrappingMode.Repeat: - Numerics.Clamp(span[..affectedSize], min, max); - Numerics.Clamp(span[^affectedSize..], min, max); - break; - case BorderWrappingMode.Mirror: - int min2dec = min + min - 1; - for (int i = 0; i < affectedSize; i++) + case BorderWrappingMode.Repeat: + Numerics.Clamp(span[..affectedSize], min, max); + Numerics.Clamp(span[^affectedSize..], min, max); + break; + case BorderWrappingMode.Mirror: + int min2dec = min + min - 1; + for (int i = 0; i < affectedSize; i++) + { + int value = span[i]; + if (value < min) { - int value = span[i]; - if (value < min) - { - span[i] = min2dec - value; - } + span[i] = min2dec - value; } + } - int max2inc = max + max + 1; - for (int i = span.Length - affectedSize; i < span.Length; i++) + int max2inc = max + max + 1; + for (int i = span.Length - affectedSize; i < span.Length; i++) + { + int value = span[i]; + if (value > max) { - int value = span[i]; - if (value > max) - { - span[i] = max2inc - value; - } + span[i] = max2inc - value; } - - break; - case BorderWrappingMode.Bounce: - int min2 = min + min; - for (int i = 0; i < affectedSize; i++) + } + + break; + case BorderWrappingMode.Bounce: + int min2 = min + min; + for (int i = 0; i < affectedSize; i++) + { + int value = span[i]; + if (value < min) { - int value = span[i]; - if (value < min) - { - span[i] = min2 - value; - } + span[i] = min2 - value; } + } - int max2 = max + max; - for (int i = span.Length - affectedSize; i < span.Length; i++) + int max2 = max + max; + for (int i = span.Length - affectedSize; i < span.Length; i++) + { + int value = span[i]; + if (value > max) { - int value = span[i]; - if (value > max) - { - span[i] = max2 - value; - } + span[i] = max2 - value; } - - break; - case BorderWrappingMode.Wrap: - int diff = max - min + 1; - for (int i = 0; i < affectedSize; i++) + } + + break; + case BorderWrappingMode.Wrap: + int diff = max - min + 1; + for (int i = 0; i < affectedSize; i++) + { + int value = span[i]; + if (value < min) { - int value = span[i]; - if (value < min) - { - span[i] = diff + value; - } + span[i] = diff + value; } + } - for (int i = span.Length - affectedSize; i < span.Length; i++) + for (int i = span.Length - affectedSize; i < span.Length; i++) + { + int value = span[i]; + if (value > max) { - int value = span[i]; - if (value > max) - { - span[i] = value - diff; - } + span[i] = value - diff; } + } - break; - } + break; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs index ff1bba22d2..310384d854 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetector2DKernel.cs @@ -1,103 +1,100 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +/// +/// Represents an edge detection convolution kernel consisting of two 1D gradient operators. +/// +public readonly struct EdgeDetector2DKernel : IEquatable { /// - /// Represents an edge detection convolution kernel consisting of two 1D gradient operators. + /// An edge detection kernel containing two Kayyali operators. /// - public readonly struct EdgeDetector2DKernel : IEquatable - { - /// - /// An edge detection kernel containing two Kayyali operators. - /// - public static readonly EdgeDetector2DKernel KayyaliKernel = new(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); + public static readonly EdgeDetector2DKernel KayyaliKernel = new(KayyaliKernels.KayyaliX, KayyaliKernels.KayyaliY); - /// - /// An edge detection kernel containing two Prewitt operators. - /// . - /// - public static readonly EdgeDetector2DKernel PrewittKernel = new(PrewittKernels.PrewittX, PrewittKernels.PrewittY); + /// + /// An edge detection kernel containing two Prewitt operators. + /// . + /// + public static readonly EdgeDetector2DKernel PrewittKernel = new(PrewittKernels.PrewittX, PrewittKernels.PrewittY); - /// - /// An edge detection kernel containing two Roberts-Cross operators. - /// . - /// - public static readonly EdgeDetector2DKernel RobertsCrossKernel = new(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); + /// + /// An edge detection kernel containing two Roberts-Cross operators. + /// . + /// + public static readonly EdgeDetector2DKernel RobertsCrossKernel = new(RobertsCrossKernels.RobertsCrossX, RobertsCrossKernels.RobertsCrossY); - /// - /// An edge detection kernel containing two Scharr operators. - /// - public static readonly EdgeDetector2DKernel ScharrKernel = new(ScharrKernels.ScharrX, ScharrKernels.ScharrY); + /// + /// An edge detection kernel containing two Scharr operators. + /// + public static readonly EdgeDetector2DKernel ScharrKernel = new(ScharrKernels.ScharrX, ScharrKernels.ScharrY); - /// - /// An edge detection kernel containing two Sobel operators. - /// . - /// - public static readonly EdgeDetector2DKernel SobelKernel = new(SobelKernels.SobelX, SobelKernels.SobelY); + /// + /// An edge detection kernel containing two Sobel operators. + /// . + /// + public static readonly EdgeDetector2DKernel SobelKernel = new(SobelKernels.SobelX, SobelKernels.SobelY); - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal gradient operator. - /// The vertical gradient operator. - public EdgeDetector2DKernel(DenseMatrix kernelX, DenseMatrix kernelY) - { - Guard.IsTrue( - kernelX.Size.Equals(kernelY.Size), - $"{nameof(kernelX)} {nameof(kernelY)}", - "Kernel sizes must be the same."); + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal gradient operator. + /// The vertical gradient operator. + public EdgeDetector2DKernel(DenseMatrix kernelX, DenseMatrix kernelY) + { + Guard.IsTrue( + kernelX.Size.Equals(kernelY.Size), + $"{nameof(kernelX)} {nameof(kernelY)}", + "Kernel sizes must be the same."); - this.KernelX = kernelX; - this.KernelY = kernelY; - } + this.KernelX = kernelX; + this.KernelY = kernelY; + } - /// - /// Gets the horizontal gradient operator. - /// - public DenseMatrix KernelX { get; } + /// + /// Gets the horizontal gradient operator. + /// + public DenseMatrix KernelX { get; } - /// - /// Gets the vertical gradient operator. - /// - public DenseMatrix KernelY { get; } + /// + /// Gets the vertical gradient operator. + /// + public DenseMatrix KernelY { get; } - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right) - => left.Equals(right); + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => left.Equals(right); - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right) - => !(left == right); + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetector2DKernel left, EdgeDetector2DKernel right) + => !(left == right); - /// - public override bool Equals(object obj) - => obj is EdgeDetector2DKernel kernel && this.Equals(kernel); + /// + public override bool Equals(object obj) + => obj is EdgeDetector2DKernel kernel && this.Equals(kernel); - /// - public bool Equals(EdgeDetector2DKernel other) - => this.KernelX.Equals(other.KernelX) - && this.KernelY.Equals(other.KernelY); + /// + public bool Equals(EdgeDetector2DKernel other) + => this.KernelX.Equals(other.KernelX) + && this.KernelY.Equals(other.KernelY); - /// - public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY); - } + /// + public override int GetHashCode() => HashCode.Combine(this.KernelX, this.KernelY); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs index 41d3c8ad24..9f26e68e99 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorCompassKernel.cs @@ -1,163 +1,160 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +/// +/// Represents an edge detection convolution kernel consisting of eight gradient operators. +/// +public readonly struct EdgeDetectorCompassKernel : IEquatable { /// - /// Represents an edge detection convolution kernel consisting of eight gradient operators. + /// An edge detection kenel comprised of Kirsch gradient operators. + /// . /// - public readonly struct EdgeDetectorCompassKernel : IEquatable + public static readonly EdgeDetectorCompassKernel Kirsch = + new( + KirschKernels.North, + KirschKernels.NorthWest, + KirschKernels.West, + KirschKernels.SouthWest, + KirschKernels.South, + KirschKernels.SouthEast, + KirschKernels.East, + KirschKernels.NorthEast); + + /// + /// An edge detection kenel comprised of Robinson gradient operators. + /// + /// + public static readonly EdgeDetectorCompassKernel Robinson = + new( + RobinsonKernels.North, + RobinsonKernels.NorthWest, + RobinsonKernels.West, + RobinsonKernels.SouthWest, + RobinsonKernels.South, + RobinsonKernels.SouthEast, + RobinsonKernels.East, + RobinsonKernels.NorthEast); + + /// + /// Initializes a new instance of the struct. + /// + /// The north gradient operator. + /// The north-west gradient operator. + /// The west gradient operator. + /// The south-west gradient operator. + /// The south gradient operator. + /// The south-east gradient operator. + /// The east gradient operator. + /// The north-east gradient operator. + public EdgeDetectorCompassKernel( + DenseMatrix north, + DenseMatrix northWest, + DenseMatrix west, + DenseMatrix southWest, + DenseMatrix south, + DenseMatrix southEast, + DenseMatrix east, + DenseMatrix northEast) { - /// - /// An edge detection kenel comprised of Kirsch gradient operators. - /// . - /// - public static readonly EdgeDetectorCompassKernel Kirsch = - new( - KirschKernels.North, - KirschKernels.NorthWest, - KirschKernels.West, - KirschKernels.SouthWest, - KirschKernels.South, - KirschKernels.SouthEast, - KirschKernels.East, - KirschKernels.NorthEast); - - /// - /// An edge detection kenel comprised of Robinson gradient operators. - /// - /// - public static readonly EdgeDetectorCompassKernel Robinson = - new( - RobinsonKernels.North, - RobinsonKernels.NorthWest, - RobinsonKernels.West, - RobinsonKernels.SouthWest, - RobinsonKernels.South, - RobinsonKernels.SouthEast, - RobinsonKernels.East, - RobinsonKernels.NorthEast); - - /// - /// Initializes a new instance of the struct. - /// - /// The north gradient operator. - /// The north-west gradient operator. - /// The west gradient operator. - /// The south-west gradient operator. - /// The south gradient operator. - /// The south-east gradient operator. - /// The east gradient operator. - /// The north-east gradient operator. - public EdgeDetectorCompassKernel( - DenseMatrix north, - DenseMatrix northWest, - DenseMatrix west, - DenseMatrix southWest, - DenseMatrix south, - DenseMatrix southEast, - DenseMatrix east, - DenseMatrix northEast) - { - this.North = north; - this.NorthWest = northWest; - this.West = west; - this.SouthWest = southWest; - this.South = south; - this.SouthEast = southEast; - this.East = east; - this.NorthEast = northEast; - } - - /// - /// Gets the North gradient operator. - /// - public DenseMatrix North { get; } - - /// - /// Gets the NorthWest gradient operator. - /// - public DenseMatrix NorthWest { get; } - - /// - /// Gets the West gradient operator. - /// - public DenseMatrix West { get; } - - /// - /// Gets the SouthWest gradient operator. - /// - public DenseMatrix SouthWest { get; } - - /// - /// Gets the South gradient operator. - /// - public DenseMatrix South { get; } - - /// - /// Gets the SouthEast gradient operator. - /// - public DenseMatrix SouthEast { get; } - - /// - /// Gets the East gradient operator. - /// - public DenseMatrix East { get; } - - /// - /// Gets the NorthEast gradient operator. - /// - public DenseMatrix NorthEast { get; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) - => left.Equals(right); - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) - => !(left == right); - - /// - public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); - - /// - public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast); - - /// - public override int GetHashCode() - => HashCode.Combine( - this.North, - this.NorthWest, - this.West, - this.SouthWest, - this.South, - this.SouthEast, - this.East, - this.NorthEast); - - internal DenseMatrix[] Flatten() => - new[] - { - this.North, this.NorthWest, this.West, this.SouthWest, - this.South, this.SouthEast, this.East, this.NorthEast - }; + this.North = north; + this.NorthWest = northWest; + this.West = west; + this.SouthWest = southWest; + this.South = south; + this.SouthEast = southEast; + this.East = east; + this.NorthEast = northEast; } + + /// + /// Gets the North gradient operator. + /// + public DenseMatrix North { get; } + + /// + /// Gets the NorthWest gradient operator. + /// + public DenseMatrix NorthWest { get; } + + /// + /// Gets the West gradient operator. + /// + public DenseMatrix West { get; } + + /// + /// Gets the SouthWest gradient operator. + /// + public DenseMatrix SouthWest { get; } + + /// + /// Gets the South gradient operator. + /// + public DenseMatrix South { get; } + + /// + /// Gets the SouthEast gradient operator. + /// + public DenseMatrix SouthEast { get; } + + /// + /// Gets the East gradient operator. + /// + public DenseMatrix East { get; } + + /// + /// Gets the NorthEast gradient operator. + /// + public DenseMatrix NorthEast { get; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => left.Equals(right); + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorCompassKernel left, EdgeDetectorCompassKernel right) + => !(left == right); + + /// + public override bool Equals(object obj) => obj is EdgeDetectorCompassKernel kernel && this.Equals(kernel); + + /// + public bool Equals(EdgeDetectorCompassKernel other) => this.North.Equals(other.North) && this.NorthWest.Equals(other.NorthWest) && this.West.Equals(other.West) && this.SouthWest.Equals(other.SouthWest) && this.South.Equals(other.South) && this.SouthEast.Equals(other.SouthEast) && this.East.Equals(other.East) && this.NorthEast.Equals(other.NorthEast); + + /// + public override int GetHashCode() + => HashCode.Combine( + this.North, + this.NorthWest, + this.West, + this.SouthWest, + this.South, + this.SouthEast, + this.East, + this.NorthEast); + + internal DenseMatrix[] Flatten() => + new[] + { + this.North, this.NorthWest, this.West, this.SouthWest, + this.South, this.SouthEast, this.East, this.NorthEast + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs index 25007846c9..30b6001d2a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/EdgeDetectorKernel.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +/// +/// Represents an edge detection convolution kernel consisting of a single 2D gradient operator. +/// +public readonly struct EdgeDetectorKernel : IEquatable { /// - /// Represents an edge detection convolution kernel consisting of a single 2D gradient operator. + /// An edge detection kernel containing a 3x3 Laplacian operator. + /// /// - public readonly struct EdgeDetectorKernel : IEquatable - { - /// - /// An edge detection kernel containing a 3x3 Laplacian operator. - /// - /// - public static readonly EdgeDetectorKernel Laplacian3x3 = new(LaplacianKernels.Laplacian3x3); + public static readonly EdgeDetectorKernel Laplacian3x3 = new(LaplacianKernels.Laplacian3x3); - /// - /// An edge detection kernel containing a 5x5 Laplacian operator. - /// - /// - public static readonly EdgeDetectorKernel Laplacian5x5 = new(LaplacianKernels.Laplacian5x5); + /// + /// An edge detection kernel containing a 5x5 Laplacian operator. + /// + /// + public static readonly EdgeDetectorKernel Laplacian5x5 = new(LaplacianKernels.Laplacian5x5); - /// - /// An edge detection kernel containing a Laplacian of Gaussian operator. - /// . - /// - public static readonly EdgeDetectorKernel LaplacianOfGaussian = new(LaplacianKernels.LaplacianOfGaussianXY); + /// + /// An edge detection kernel containing a Laplacian of Gaussian operator. + /// . + /// + public static readonly EdgeDetectorKernel LaplacianOfGaussian = new(LaplacianKernels.LaplacianOfGaussianXY); - /// - /// Initializes a new instance of the struct. - /// - /// The 2D gradient operator. - public EdgeDetectorKernel(DenseMatrix kernelXY) - => this.KernelXY = kernelXY; + /// + /// Initializes a new instance of the struct. + /// + /// The 2D gradient operator. + public EdgeDetectorKernel(DenseMatrix kernelXY) + => this.KernelXY = kernelXY; - /// - /// Gets the 2D gradient operator. - /// - public DenseMatrix KernelXY { get; } + /// + /// Gets the 2D gradient operator. + /// + public DenseMatrix KernelXY { get; } - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right) - => left.Equals(right); + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(EdgeDetectorKernel left, EdgeDetectorKernel right) + => left.Equals(right); - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right) - => !(left == right); + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(EdgeDetectorKernel left, EdgeDetectorKernel right) + => !(left == right); - /// - public override bool Equals(object obj) - => obj is EdgeDetectorKernel kernel && this.Equals(kernel); + /// + public override bool Equals(object obj) + => obj is EdgeDetectorKernel kernel && this.Equals(kernel); - /// - public bool Equals(EdgeDetectorKernel other) - => this.KernelXY.Equals(other.KernelXY); + /// + public bool Equals(EdgeDetectorKernel other) + => this.KernelXY.Equals(other.KernelXY); - /// - public override int GetHashCode() => this.KernelXY.GetHashCode(); - } + /// + public override int GetHashCode() => this.KernelXY.GetHashCode(); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs index 4617a75117..50fbdc6b31 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KayyaliKernels.cs @@ -1,33 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for Kayyali edge detection +/// +internal static class KayyaliKernels { /// - /// Contains the kernels used for Kayyali edge detection + /// Gets the horizontal gradient operator. /// - internal static class KayyaliKernels - { - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix KayyaliX => - new float[,] - { - { 6, 0, -6 }, - { 0, 0, 0 }, - { -6, 0, 6 } - }; + public static DenseMatrix KayyaliX => + new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }; - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix KayyaliY => - new float[,] - { - { -6, 0, 6 }, - { 0, 0, 0 }, - { 6, 0, -6 } - }; - } + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix KayyaliY => + new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs index c394f9f8e6..6a4a6d7700 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/KirschKernels.cs @@ -1,100 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the eight matrices used for Kirsch edge detection. +/// . +/// +internal static class KirschKernels { /// - /// Contains the eight matrices used for Kirsch edge detection. - /// . + /// Gets the North gradient operator /// - internal static class KirschKernels - { - /// - /// Gets the North gradient operator - /// - public static DenseMatrix North => - new float[,] - { - { 5, 5, 5 }, - { -3, 0, -3 }, - { -3, -3, -3 } - }; + public static DenseMatrix North => + new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }; - /// - /// Gets the NorthWest gradient operator - /// - public static DenseMatrix NorthWest => - new float[,] - { - { 5, 5, -3 }, - { 5, 0, -3 }, - { -3, -3, -3 } - }; + /// + /// Gets the NorthWest gradient operator + /// + public static DenseMatrix NorthWest => + new float[,] + { + { 5, 5, -3 }, + { 5, 0, -3 }, + { -3, -3, -3 } + }; - /// - /// Gets the West gradient operator - /// - public static DenseMatrix West => - new float[,] - { - { 5, -3, -3 }, - { 5, 0, -3 }, - { 5, -3, -3 } - }; + /// + /// Gets the West gradient operator + /// + public static DenseMatrix West => + new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }; - /// - /// Gets the SouthWest gradient operator - /// - public static DenseMatrix SouthWest => - new float[,] - { - { -3, -3, -3 }, - { 5, 0, -3 }, - { 5, 5, -3 } - }; + /// + /// Gets the SouthWest gradient operator + /// + public static DenseMatrix SouthWest => + new float[,] + { + { -3, -3, -3 }, + { 5, 0, -3 }, + { 5, 5, -3 } + }; - /// - /// Gets the South gradient operator - /// - public static DenseMatrix South => - new float[,] - { - { -3, -3, -3 }, - { -3, 0, -3 }, - { 5, 5, 5 } - }; + /// + /// Gets the South gradient operator + /// + public static DenseMatrix South => + new float[,] + { + { -3, -3, -3 }, + { -3, 0, -3 }, + { 5, 5, 5 } + }; - /// - /// Gets the SouthEast gradient operator - /// - public static DenseMatrix SouthEast => - new float[,] - { - { -3, -3, -3 }, - { -3, 0, 5 }, - { -3, 5, 5 } - }; + /// + /// Gets the SouthEast gradient operator + /// + public static DenseMatrix SouthEast => + new float[,] + { + { -3, -3, -3 }, + { -3, 0, 5 }, + { -3, 5, 5 } + }; - /// - /// Gets the East gradient operator - /// - public static DenseMatrix East => - new float[,] - { - { -3, -3, 5 }, - { -3, 0, 5 }, - { -3, -3, 5 } - }; + /// + /// Gets the East gradient operator + /// + public static DenseMatrix East => + new float[,] + { + { -3, -3, 5 }, + { -3, 0, 5 }, + { -3, -3, 5 } + }; - /// - /// Gets the NorthEast gradient operator - /// - public static DenseMatrix NorthEast => - new float[,] - { - { -3, 5, 5 }, - { -3, 0, 5 }, - { -3, -3, -3 } - }; - } + /// + /// Gets the NorthEast gradient operator + /// + public static DenseMatrix NorthEast => + new float[,] + { + { -3, 5, 5 }, + { -3, 0, 5 }, + { -3, -3, -3 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs index a4dadc12e8..7efcbf3a6c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernelFactory.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// A factory for creating Laplacian kernel matrices. +/// +internal static class LaplacianKernelFactory { /// - /// A factory for creating Laplacian kernel matrices. + /// Creates a Laplacian matrix, 2nd derivative, of an arbitrary length. + /// /// - internal static class LaplacianKernelFactory + /// The length of the matrix sides + /// The + public static DenseMatrix CreateKernel(uint length) { - /// - /// Creates a Laplacian matrix, 2nd derivative, of an arbitrary length. - /// - /// - /// The length of the matrix sides - /// The - public static DenseMatrix CreateKernel(uint length) - { - Guard.MustBeGreaterThanOrEqualTo(length, 3u, nameof(length)); - Guard.IsFalse(length % 2 == 0, nameof(length), "The kernel length must be an odd number."); + Guard.MustBeGreaterThanOrEqualTo(length, 3u, nameof(length)); + Guard.IsFalse(length % 2 == 0, nameof(length), "The kernel length must be an odd number."); - var kernel = new DenseMatrix((int)length); - kernel.Fill(-1); + var kernel = new DenseMatrix((int)length); + kernel.Fill(-1); - int mid = (int)(length / 2); - kernel[mid, mid] = (length * length) - 1; + int mid = (int)(length / 2); + kernel[mid, mid] = (length * length) - 1; - return kernel; - } + return kernel; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs index c2e60003fe..2137c8cc2e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/LaplacianKernels.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains Laplacian kernels of different sizes. +/// +/// . +/// +internal static class LaplacianKernels { /// - /// Contains Laplacian kernels of different sizes. - /// - /// . + /// Gets the 3x3 Laplacian kernel /// - internal static class LaplacianKernels - { - /// - /// Gets the 3x3 Laplacian kernel - /// - public static DenseMatrix Laplacian3x3 => LaplacianKernelFactory.CreateKernel(3); + public static DenseMatrix Laplacian3x3 => LaplacianKernelFactory.CreateKernel(3); - /// - /// Gets the 5x5 Laplacian kernel - /// - public static DenseMatrix Laplacian5x5 => LaplacianKernelFactory.CreateKernel(5); + /// + /// Gets the 5x5 Laplacian kernel + /// + public static DenseMatrix Laplacian5x5 => LaplacianKernelFactory.CreateKernel(5); - /// - /// Gets the Laplacian of Gaussian kernel. - /// - public static DenseMatrix LaplacianOfGaussianXY => - new float[,] - { - { 0, 0, -1, 0, 0 }, - { 0, -1, -2, -1, 0 }, - { -1, -2, 16, -2, -1 }, - { 0, -1, -2, -1, 0 }, - { 0, 0, -1, 0, 0 } - }; - } + /// + /// Gets the Laplacian of Gaussian kernel. + /// + public static DenseMatrix LaplacianOfGaussianXY => + new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs index c18512326e..85e7f7b89f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/PrewittKernels.cs @@ -1,33 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for Prewitt edge detection +/// +internal static class PrewittKernels { /// - /// Contains the kernels used for Prewitt edge detection + /// Gets the horizontal gradient operator. /// - internal static class PrewittKernels - { - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix PrewittX => - new float[,] - { - { -1, 0, 1 }, - { -1, 0, 1 }, - { -1, 0, 1 } - }; + public static DenseMatrix PrewittX => + new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }; - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix PrewittY => - new float[,] - { - { 1, 1, 1 }, - { 0, 0, 0 }, - { -1, -1, -1 } - }; - } + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix PrewittY => + new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs index c6518fe88e..562f94c63d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobertsCrossKernels.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for RobertsCross edge detection +/// +internal static class RobertsCrossKernels { /// - /// Contains the kernels used for RobertsCross edge detection + /// Gets the horizontal gradient operator. /// - internal static class RobertsCrossKernels - { - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix RobertsCrossX => - new float[,] - { - { 1, 0 }, - { 0, -1 } - }; + public static DenseMatrix RobertsCrossX => + new float[,] + { + { 1, 0 }, + { 0, -1 } + }; - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix RobertsCrossY => - new float[,] - { - { 0, 1 }, - { -1, 0 } - }; - } + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix RobertsCrossY => + new float[,] + { + { 0, 1 }, + { -1, 0 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs index d447aa574e..290b4c8737 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/RobinsonKernels.cs @@ -1,100 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for Robinson edge detection. +/// +/// +internal static class RobinsonKernels { /// - /// Contains the kernels used for Robinson edge detection. - /// + /// Gets the North gradient operator /// - internal static class RobinsonKernels - { - /// - /// Gets the North gradient operator - /// - public static DenseMatrix North => - new float[,] - { - { 1, 2, 1 }, - { 0, 0, 0 }, - { -1, -2, -1 } - }; + public static DenseMatrix North => + new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }; - /// - /// Gets the NorthWest gradient operator - /// - public static DenseMatrix NorthWest => - new float[,] - { - { 2, 1, 0 }, - { 1, 0, -1 }, - { 0, -1, -2 } - }; + /// + /// Gets the NorthWest gradient operator + /// + public static DenseMatrix NorthWest => + new float[,] + { + { 2, 1, 0 }, + { 1, 0, -1 }, + { 0, -1, -2 } + }; - /// - /// Gets the West gradient operator - /// - public static DenseMatrix West => - new float[,] - { - { 1, 0, -1 }, - { 2, 0, -2 }, - { 1, 0, -1 } - }; + /// + /// Gets the West gradient operator + /// + public static DenseMatrix West => + new float[,] + { + { 1, 0, -1 }, + { 2, 0, -2 }, + { 1, 0, -1 } + }; - /// - /// Gets the SouthWest gradient operator - /// - public static DenseMatrix SouthWest => - new float[,] - { - { 0, -1, -2 }, - { 1, 0, -1 }, - { 2, 1, 0 } - }; + /// + /// Gets the SouthWest gradient operator + /// + public static DenseMatrix SouthWest => + new float[,] + { + { 0, -1, -2 }, + { 1, 0, -1 }, + { 2, 1, 0 } + }; - /// - /// Gets the South gradient operator - /// - public static DenseMatrix South => - new float[,] - { - { -1, -2, -1 }, - { 0, 0, 0 }, - { 1, 2, 1 } - }; + /// + /// Gets the South gradient operator + /// + public static DenseMatrix South => + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; - /// - /// Gets the SouthEast gradient operator - /// - public static DenseMatrix SouthEast => - new float[,] - { - { -2, -1, 0 }, - { -1, 0, 1 }, - { 0, 1, 2 } - }; + /// + /// Gets the SouthEast gradient operator + /// + public static DenseMatrix SouthEast => + new float[,] + { + { -2, -1, 0 }, + { -1, 0, 1 }, + { 0, 1, 2 } + }; - /// - /// Gets the East gradient operator - /// - public static DenseMatrix East => - new float[,] - { - { -1, 0, 1 }, - { -2, 0, 2 }, - { -1, 0, 1 } - }; + /// + /// Gets the East gradient operator + /// + public static DenseMatrix East => + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; - /// - /// Gets the NorthEast gradient operator - /// - public static DenseMatrix NorthEast => - new float[,] - { - { 0, 1, 2 }, - { -1, 0, 1 }, - { -2, -1, 0 } - }; - } + /// + /// Gets the NorthEast gradient operator + /// + public static DenseMatrix NorthEast => + new float[,] + { + { 0, 1, 2 }, + { -1, 0, 1 }, + { -2, -1, 0 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs index c10a24c23f..132f6f048b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/ScharrKernels.cs @@ -1,33 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for Scharr edge detection +/// +internal static class ScharrKernels { /// - /// Contains the kernels used for Scharr edge detection + /// Gets the horizontal gradient operator. /// - internal static class ScharrKernels - { - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix ScharrX => - new float[,] - { - { -3, 0, 3 }, - { -10, 0, 10 }, - { -3, 0, 3 } - }; - - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix ScharrY => - new float[,] + public static DenseMatrix ScharrX => + new float[,] { - { 3, 10, 3 }, - { 0, 0, 0 }, - { -3, -10, -3 } + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } }; - } + + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix ScharrY => + new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs index dbc16b2a12..e75826c28c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/Implementation/SobelKernels.cs @@ -1,33 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Contains the kernels used for Sobel edge detection +/// +internal static class SobelKernels { /// - /// Contains the kernels used for Sobel edge detection + /// Gets the horizontal gradient operator. /// - internal static class SobelKernels - { - /// - /// Gets the horizontal gradient operator. - /// - public static DenseMatrix SobelX => - new float[,] - { - { -1, 0, 1 }, - { -2, 0, 2 }, - { -1, 0, 1 } - }; + public static DenseMatrix SobelX => + new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; - /// - /// Gets the vertical gradient operator. - /// - public static DenseMatrix SobelY => - new float[,] - { - { -1, -2, -1 }, - { 0, 0, 0 }, - { 1, 2, 1 } - }; - } + /// + /// Gets the vertical gradient operator. + /// + public static DenseMatrix SobelY => + new float[,] + { + { -1, -2, -1 }, + { 0, 0, 0 }, + { 1, 2, 1 } + }; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs index a3819f5e95..a31193e672 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor.cs @@ -3,51 +3,50 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies an median filter. +/// +public sealed class MedianBlurProcessor : IImageProcessor { /// - /// Applies an median filter. + /// Initializes a new instance of the class. /// - public sealed class MedianBlurProcessor : IImageProcessor + /// + /// The 'radius' value representing the size of the area to filter over. + /// + /// + /// Whether the filter is applied to alpha as well as the color channels. + /// + public MedianBlurProcessor(int radius, bool preserveAlpha) { - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to filter over. - /// - /// - /// Whether the filter is applied to alpha as well as the color channels. - /// - public MedianBlurProcessor(int radius, bool preserveAlpha) - { - this.Radius = radius; - this.PreserveAlpha = preserveAlpha; - } - - /// - /// Gets the size of the area to find the median of. - /// - public int Radius { get; } - - /// - /// Gets a value indicating whether the filter is applied to alpha as well as the color channels. - /// - public bool PreserveAlpha { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in X direction. - /// - public BorderWrappingMode BorderWrapModeX { get; } - - /// - /// Gets the to use when mapping the pixels outside of the border, in Y direction. - /// - public BorderWrappingMode BorderWrapModeY { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new MedianBlurProcessor(configuration, this, source, sourceRectangle); + this.Radius = radius; + this.PreserveAlpha = preserveAlpha; } + + /// + /// Gets the size of the area to find the median of. + /// + public int Radius { get; } + + /// + /// Gets a value indicating whether the filter is applied to alpha as well as the color channels. + /// + public bool PreserveAlpha { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in X direction. + /// + public BorderWrappingMode BorderWrapModeX { get; } + + /// + /// Gets the to use when mapping the pixels outside of the border, in Y direction. + /// + public BorderWrappingMode BorderWrapModeY { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new MedianBlurProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs index 8e2540faf8..4f0c2a36c9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs @@ -6,54 +6,53 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies an median filter. +/// +/// The type of pixel format. +internal sealed class MedianBlurProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { - /// - /// Applies an median filter. - /// - /// The type of pixel format. - internal sealed class MedianBlurProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - { - private readonly MedianBlurProcessor definition; + private readonly MedianBlurProcessor definition; - public MedianBlurProcessor(Configuration configuration, MedianBlurProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) => this.definition = definition; + public MedianBlurProcessor(Configuration configuration, MedianBlurProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) => this.definition = definition; - protected override void OnFrameApply(ImageFrame source) - { - int kernelSize = (2 * this.definition.Radius) + 1; + protected override void OnFrameApply(ImageFrame source) + { + int kernelSize = (2 * this.definition.Radius) + 1; - MemoryAllocator allocator = this.Configuration.MemoryAllocator; - using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); - source.CopyTo(targetPixels); + source.CopyTo(targetPixels); - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // We use a rectangle with width set wider, to allocate a buffer big enough - // for kernel source, channel buffers, source rows and target bulk pixel conversion. - int operationWidth = (2 * kernelSize * kernelSize) + interest.Width + (kernelSize * interest.Width); - Rectangle operationBounds = new(interest.X, interest.Y, operationWidth, interest.Height); + // We use a rectangle with width set wider, to allocate a buffer big enough + // for kernel source, channel buffers, source rows and target bulk pixel conversion. + int operationWidth = (2 * kernelSize * kernelSize) + interest.Width + (kernelSize * interest.Width); + Rectangle operationBounds = new(interest.X, interest.Y, operationWidth, interest.Height); - using KernelSamplingMap map = new(this.Configuration.MemoryAllocator); - map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); + using KernelSamplingMap map = new(this.Configuration.MemoryAllocator); + map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); - MedianRowOperation operation = new( - interest, - targetPixels, - source.PixelBuffer, - map, - kernelSize, - this.Configuration, - this.definition.PreserveAlpha); + MedianRowOperation operation = new( + interest, + targetPixels, + source.PixelBuffer, + map, + kernelSize, + this.Configuration, + this.definition.PreserveAlpha); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - operationBounds, - in operation); + ParallelRowIterator.IterateRows, Vector4>( + this.Configuration, + operationBounds, + in operation); - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs index f4aa68e22f..d557896e3c 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs @@ -1,46 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution -{ - /// - /// A stack only struct used for reducing reference indirection during convolution operations. - /// - internal readonly ref struct MedianConvolutionState - { - private readonly Span rowOffsetMap; - private readonly Span columnOffsetMap; - private readonly int kernelHeight; - private readonly int kernelWidth; - - public MedianConvolutionState( - in DenseMatrix kernel, - KernelSamplingMap map) - { - this.Kernel = new Kernel(kernel); - this.kernelHeight = kernel.Rows; - this.kernelWidth = kernel.Columns; - this.rowOffsetMap = map.GetRowOffsetSpan(); - this.columnOffsetMap = map.GetColumnOffsetSpan(); - } +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; - public readonly Kernel Kernel - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } +/// +/// A stack only struct used for reducing reference indirection during convolution operations. +/// +internal readonly ref struct MedianConvolutionState +{ + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleRow(int row) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + public MedianConvolutionState( + in DenseMatrix kernel, + KernelSamplingMap map) + { + this.Kernel = new Kernel(kernel); + this.kernelHeight = kernel.Rows; + this.kernelWidth = kernel.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + public readonly Kernel Kernel + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleColumn(int column) - => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); + get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly ref int GetSampleColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index 90dce4dad9..aff26865fb 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,170 +8,169 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// Applies an median filter. +/// +/// The type of pixel format. +internal readonly struct MedianRowOperation : IRowOperation + where TPixel : unmanaged, IPixel { - /// - /// Applies an median filter. - /// - /// The type of pixel format. - internal readonly struct MedianRowOperation : IRowOperation - where TPixel : unmanaged, IPixel + private readonly int yChannelStart; + private readonly int zChannelStart; + private readonly int wChannelStart; + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly int kernelSize; + private readonly bool preserveAlpha; + + public MedianRowOperation(Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, KernelSamplingMap map, int kernelSize, Configuration configuration, bool preserveAlpha) + { + this.bounds = bounds; + this.configuration = configuration; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernelSize = kernelSize; + this.preserveAlpha = preserveAlpha; + int kernelCount = this.kernelSize * this.kernelSize; + this.yChannelStart = kernelCount; + this.zChannelStart = this.yChannelStart + kernelCount; + this.wChannelStart = this.zChannelStart + kernelCount; + } + + public void Invoke(int y, Span span) { - private readonly int yChannelStart; - private readonly int zChannelStart; - private readonly int wChannelStart; - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly int kernelSize; - private readonly bool preserveAlpha; - - public MedianRowOperation(Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, KernelSamplingMap map, int kernelSize, Configuration configuration, bool preserveAlpha) + // Span has kernelSize^2 followed by bound width. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + int kernelCount = this.kernelSize * this.kernelSize; + Span kernelBuffer = span[..kernelCount]; + Span channelVectorBuffer = span.Slice(kernelCount, kernelCount); + Span sourceVectorBuffer = span.Slice(kernelCount << 1, this.kernelSize * boundsWidth); + Span targetBuffer = span.Slice((kernelCount << 1) + sourceVectorBuffer.Length, boundsWidth); + + // Stack 4 channels of floats in the space of Vector4's. + Span channelBuffer = MemoryMarshal.Cast(channelVectorBuffer); + Span xChannel = channelBuffer[..kernelCount]; + Span yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount); + Span zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount); + + DenseMatrix kernel = new(this.kernelSize, this.kernelSize, kernelBuffer); + + int row = y - this.bounds.Y; + MedianConvolutionState state = new(in kernel, this.map); + ref int sampleRowBase = ref state.GetSampleRow(row); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + // First convert the required source rows to Vector4. + for (int i = 0; i < this.kernelSize; i++) { - this.bounds = bounds; - this.configuration = configuration; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernelSize = kernelSize; - this.preserveAlpha = preserveAlpha; - int kernelCount = this.kernelSize * this.kernelSize; - this.yChannelStart = kernelCount; - this.zChannelStart = this.yChannelStart + kernelCount; - this.wChannelStart = this.zChannelStart + kernelCount; + int currentYIndex = Unsafe.Add(ref sampleRowBase, i); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); + Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); } - public void Invoke(int y, Span span) + if (this.preserveAlpha) { - // Span has kernelSize^2 followed by bound width. - int boundsX = this.bounds.X; - int boundsWidth = this.bounds.Width; - int kernelCount = this.kernelSize * this.kernelSize; - Span kernelBuffer = span[..kernelCount]; - Span channelVectorBuffer = span.Slice(kernelCount, kernelCount); - Span sourceVectorBuffer = span.Slice(kernelCount << 1, this.kernelSize * boundsWidth); - Span targetBuffer = span.Slice((kernelCount << 1) + sourceVectorBuffer.Length, boundsWidth); - - // Stack 4 channels of floats in the space of Vector4's. - Span channelBuffer = MemoryMarshal.Cast(channelVectorBuffer); - Span xChannel = channelBuffer[..kernelCount]; - Span yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount); - Span zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount); - - DenseMatrix kernel = new(this.kernelSize, this.kernelSize, kernelBuffer); - - int row = y - this.bounds.Y; - MedianConvolutionState state = new(in kernel, this.map); - ref int sampleRowBase = ref state.GetSampleRow(row); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); - - // First convert the required source rows to Vector4. - for (int i = 0; i < this.kernelSize; i++) + for (int x = 0; x < boundsWidth; x++) { - int currentYIndex = Unsafe.Add(ref sampleRowBase, i); - Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); - Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); - } - - if (this.preserveAlpha) - { - for (int x = 0; x < boundsWidth; x++) + int index = 0; + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - int index = 0; - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - for (int kY = 0; kY < state.Kernel.Rows; kY++) + Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; + ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); + for (int kX = 0; kX < state.Kernel.Columns; kX++) { - Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; - ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); - state.Kernel.SetValue(index, pixel); - index++; - } + int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); + state.Kernel.SetValue(index, pixel); + index++; } - - target = FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel); } + + target = FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel); } - else + } + else + { + Span wChannel = channelBuffer.Slice(this.wChannelStart, kernelCount); + for (int x = 0; x < boundsWidth; x++) { - Span wChannel = channelBuffer.Slice(this.wChannelStart, kernelCount); - for (int x = 0; x < boundsWidth; x++) + int index = 0; + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - int index = 0; - ref int sampleColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - for (int kY = 0; kY < state.Kernel.Rows; kY++) + Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; + ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); + for (int kX = 0; kX < state.Kernel.Columns; kX++) { - Span sourceRow = sourceVectorBuffer[(kY * boundsWidth)..]; - ref Vector4 sourceRowBase = ref MemoryMarshal.GetReference(sourceRow); - for (int kX = 0; kX < state.Kernel.Columns; kX++) - { - int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); - state.Kernel.SetValue(index, pixel); - index++; - } + int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 pixel = Unsafe.Add(ref sourceRowBase, currentXIndex); + state.Kernel.SetValue(index, pixel); + index++; } - - target = FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel); } - } - Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + target = FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel); + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 FindMedian3(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel) - { - int halfLength = (kernelSpan.Length + 1) >> 1; - - // Split color channels - for (int i = 0; i < xChannel.Length; i++) - { - xChannel[i] = kernelSpan[i].X; - yChannel[i] = kernelSpan[i].Y; - zChannel[i] = kernelSpan[i].Z; - } + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + } - // Sort each channel serarately. - xChannel.Sort(); - yChannel.Sort(); - zChannel.Sort(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 FindMedian3(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel) + { + int halfLength = (kernelSpan.Length + 1) >> 1; - // Taking the W value from the source pixels, where the middle index in the kernelSpan is by definition the resulting pixel. - // This will preserve the alpha value. - return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], kernelSpan[halfLength].W); + // Split color channels + for (int i = 0; i < xChannel.Length; i++) + { + xChannel[i] = kernelSpan[i].X; + yChannel[i] = kernelSpan[i].Y; + zChannel[i] = kernelSpan[i].Z; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 FindMedian4(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel) - { - int halfLength = (kernelSpan.Length + 1) >> 1; + // Sort each channel serarately. + xChannel.Sort(); + yChannel.Sort(); + zChannel.Sort(); - // Split color channels - for (int i = 0; i < xChannel.Length; i++) - { - xChannel[i] = kernelSpan[i].X; - yChannel[i] = kernelSpan[i].Y; - zChannel[i] = kernelSpan[i].Z; - wChannel[i] = kernelSpan[i].W; - } + // Taking the W value from the source pixels, where the middle index in the kernelSpan is by definition the resulting pixel. + // This will preserve the alpha value. + return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], kernelSpan[halfLength].W); + } - // Sort each channel serarately. - xChannel.Sort(); - yChannel.Sort(); - zChannel.Sort(); - wChannel.Sort(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 FindMedian4(ReadOnlySpan kernelSpan, Span xChannel, Span yChannel, Span zChannel, Span wChannel) + { + int halfLength = (kernelSpan.Length + 1) >> 1; - return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], wChannel[halfLength]); + // Split color channels + for (int i = 0; i < xChannel.Length; i++) + { + xChannel[i] = kernelSpan[i].X; + yChannel[i] = kernelSpan[i].Y; + zChannel[i] = kernelSpan[i].Z; + wChannel[i] = kernelSpan[i].W; } + + // Sort each channel serarately. + xChannel.Sort(); + yChannel.Sort(); + zChannel.Sort(); + wChannel.Sort(); + + return new Vector4(xChannel[halfLength], yChannel[halfLength], zChannel[halfLength], wChannel[halfLength]); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs index dd378382c4..10e436da7f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs @@ -3,32 +3,31 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; + +/// +/// A that contains data about a set of bokeh blur kernels +/// +internal readonly struct BokehBlurKernelData { /// - /// A that contains data about a set of bokeh blur kernels + /// The kernel parameters to use for the current set of complex kernels /// - internal readonly struct BokehBlurKernelData - { - /// - /// The kernel parameters to use for the current set of complex kernels - /// - public readonly Vector4[] Parameters; + public readonly Vector4[] Parameters; - /// - /// The kernel components to apply the bokeh blur effect - /// - public readonly Complex64[][] Kernels; + /// + /// The kernel components to apply the bokeh blur effect + /// + public readonly Complex64[][] Kernels; - /// - /// Initializes a new instance of the struct. - /// - /// The kernel parameters - /// The complex kernel components - public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels) - { - this.Parameters = parameters; - this.Kernels = kernels; - } + /// + /// Initializes a new instance of the struct. + /// + /// The kernel parameters + /// The complex kernel components + public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels) + { + this.Parameters = parameters; + this.Kernels = kernels; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index 39a943c6ef..08dee86a4e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -1,227 +1,224 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; + +/// +/// Provides parameters to be used in the . +/// +internal static class BokehBlurKernelDataProvider { /// - /// Provides parameters to be used in the . + /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances /// - 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) }, + private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); - // 2 components - new[] - { - new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), - new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) - }, + /// + /// 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 }; - // 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) - }, + /// + /// 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) }, - // 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) + // 2 components + new[] { - // 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; - } + new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), + new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) + }, - /// - /// 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) + // 3 components + new[] { - // Prepare the kernel components - int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Count)); + 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) + }, - return (KernelComponents[index], KernelScales[index]); + // 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) } + }; - /// - /// 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) + /// + /// 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)) { - 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; + // 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); } - /// - /// 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) + 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++) { - var kernel = new Complex64[kernelSize]; - ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); - int r = radius, n = -r; + ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); + kernels[i] = CreateComplex1DKernel(radius, kernelSize, kernelsScale, paramsRef.X, paramsRef.Y); + } - 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 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; - return kernel; + 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)); } - /// - /// 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) + 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++) { - // 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()); + 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 i = 0; i < kernelParameters.Length; i++) + for (int j = 0; j < length; j++) { - 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++) { - 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))); - } + 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]; + // 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; - } + for (int j = 0; j < length; j++) + { + Unsafe.Add(ref valueRef, j) *= scalar; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs index aca81b90ef..bfc61a5549 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurParameters.cs @@ -1,52 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +/// +/// A that contains parameters to apply a bokeh blur filter +/// +internal readonly struct BokehBlurParameters : IEquatable { /// - /// A that contains parameters to apply a bokeh blur filter + /// The size of the convolution kernel to use when applying the bokeh blur /// - internal readonly struct BokehBlurParameters : IEquatable - { - /// - /// The size of the convolution kernel to use when applying the bokeh blur - /// - public readonly int Radius; + public readonly int Radius; - /// - /// The number of complex components to use to approximate the bokeh kernel - /// - public readonly int Components; + /// + /// The number of complex components to use to approximate the bokeh kernel + /// + public readonly int Components; - /// - /// Initializes a new instance of the struct. - /// - /// The size of the kernel - /// The number of kernel components - public BokehBlurParameters(int radius, int components) - { - this.Radius = radius; - this.Components = components; - } + /// + /// Initializes a new instance of the struct. + /// + /// The size of the kernel + /// The number of kernel components + public BokehBlurParameters(int radius, int components) + { + this.Radius = radius; + this.Components = components; + } - /// - public bool Equals(BokehBlurParameters other) - { - return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components); - } + /// + public bool Equals(BokehBlurParameters other) + { + return this.Radius.Equals(other.Radius) && this.Components.Equals(other.Components); + } - /// - public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other); + /// + public override bool Equals(object obj) => obj is BokehBlurParameters other && this.Equals(other); - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked { - unchecked - { - return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode(); - } + return (this.Radius.GetHashCode() * 397) ^ this.Components.GetHashCode(); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs index f6a4bf56c8..641a8ae8b0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs @@ -1,63 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Processing.Processors.Convolution; + +/// +/// A stack only, readonly, kernel matrix that can be indexed without +/// bounds checks when compiled in release mode. +/// +internal readonly ref struct ReadOnlyKernel { - /// - /// A stack only, readonly, kernel matrix that can be indexed without - /// bounds checks when compiled in release mode. - /// - internal readonly ref struct ReadOnlyKernel + private readonly ReadOnlySpan values; + + public ReadOnlyKernel(DenseMatrix matrix) { - private readonly ReadOnlySpan values; + this.Columns = matrix.Columns; + this.Rows = matrix.Rows; + this.values = matrix.Span; + } - public ReadOnlyKernel(DenseMatrix matrix) - { - this.Columns = matrix.Columns; - this.Rows = matrix.Rows; - this.values = matrix.Span; - } + public int Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - public int Columns - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; - } + public int Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } - public int Rows + public float this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get; + this.CheckCoordinates(row, column); + ref float vBase = ref MemoryMarshal.GetReference(this.values); + return Unsafe.Add(ref vBase, (row * this.Columns) + column); } + } - public float this[int row, int column] + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - this.CheckCoordinates(row, column); - ref float vBase = ref MemoryMarshal.GetReference(this.values); - return Unsafe.Add(ref vBase, (row * this.Columns) + column); - } + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); } - [Conditional("DEBUG")] - private void CheckCoordinates(int row, int column) + if (column < 0 || column >= this.Columns) { - if (row < 0 || row >= this.Rows) - { - throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); - } - - if (column < 0 || column >= this.Columns) - { - throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); - } + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs index 2c9ce24194..57d8ef59a6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs @@ -1,188 +1,187 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// An error diffusion dithering implementation. +/// +public readonly partial struct ErrorDither { /// - /// An error diffusion dithering implementation. + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + public static readonly ErrorDither Atkinson = CreateAtkinson(); + + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + public static readonly ErrorDither Burkes = CreateBurks(); + + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + public static readonly ErrorDither FloydSteinberg = CreateFloydSteinberg(); + + /// + /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. + /// + public static readonly ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); + + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + public static readonly ErrorDither Sierra2 = CreateSierra2(); + + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + public static readonly ErrorDither Sierra3 = CreateSierra3(); + + /// + /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. + /// + public static readonly ErrorDither SierraLite = CreateSierraLite(); + + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. /// - public readonly partial struct ErrorDither + public static readonly ErrorDither StevensonArce = CreateStevensonArce(); + + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + public static readonly ErrorDither Stucki = CreateStucki(); + + private static ErrorDither CreateAtkinson() { - /// - /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. - /// - public static readonly ErrorDither Atkinson = CreateAtkinson(); - - /// - /// Applies error diffusion based dithering using the Burks image dithering algorithm. - /// - public static readonly ErrorDither Burkes = CreateBurks(); - - /// - /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. - /// - public static readonly ErrorDither FloydSteinberg = CreateFloydSteinberg(); - - /// - /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. - /// - public static readonly ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); - - /// - /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. - /// - public static readonly ErrorDither Sierra2 = CreateSierra2(); - - /// - /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. - /// - public static readonly ErrorDither Sierra3 = CreateSierra3(); - - /// - /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. - /// - public static readonly ErrorDither SierraLite = CreateSierraLite(); - - /// - /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. - /// - public static readonly ErrorDither StevensonArce = CreateStevensonArce(); - - /// - /// Applies error diffusion based dithering using the Stucki image dithering algorithm. - /// - public static readonly ErrorDither Stucki = CreateStucki(); - - private static ErrorDither CreateAtkinson() + const float divisor = 8F; + const int offset = 1; + + float[,] matrix = { - const float divisor = 8F; - const int offset = 1; + { 0, 0, 1 / divisor, 1 / divisor }, + { 1 / divisor, 1 / divisor, 1 / divisor, 0 }, + { 0, 1 / divisor, 0, 0 } + }; - float[,] matrix = - { - { 0, 0, 1 / divisor, 1 / divisor }, - { 1 / divisor, 1 / divisor, 1 / divisor, 0 }, - { 0, 1 / divisor, 0, 0 } - }; + return new ErrorDither(matrix, offset); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateBurks() + { + const float divisor = 32F; + const int offset = 2; - private static ErrorDither CreateBurks() + float[,] matrix = { - const float divisor = 32F; - const int offset = 2; + { 0, 0, 0, 8 / divisor, 4 / divisor }, + { 2 / divisor, 4 / divisor, 8 / divisor, 4 / divisor, 2 / divisor } + }; - float[,] matrix = - { - { 0, 0, 0, 8 / divisor, 4 / divisor }, - { 2 / divisor, 4 / divisor, 8 / divisor, 4 / divisor, 2 / divisor } - }; + return new ErrorDither(matrix, offset); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateFloydSteinberg() + { + const float divisor = 16F; + const int offset = 1; - private static ErrorDither CreateFloydSteinberg() + float[,] matrix = { - const float divisor = 16F; - const int offset = 1; + { 0, 0, 7 / divisor }, + { 3 / divisor, 5 / divisor, 1 / divisor } + }; - float[,] matrix = - { - { 0, 0, 7 / divisor }, - { 3 / divisor, 5 / divisor, 1 / divisor } - }; + return new ErrorDither(matrix, offset); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateJarvisJudiceNinke() + { + const float divisor = 48F; + const int offset = 2; - private static ErrorDither CreateJarvisJudiceNinke() + float[,] matrix = { - const float divisor = 48F; - const int offset = 2; + { 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 } + }; - float[,] matrix = - { - { 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); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateSierra2() + { + const float divisor = 16F; + const int offset = 2; - private static ErrorDither CreateSierra2() + float[,] matrix = { - const float divisor = 16F; - const int offset = 2; + { 0, 0, 0, 4 / divisor, 3 / divisor }, + { 1 / divisor, 2 / divisor, 3 / divisor, 2 / divisor, 1 / divisor } + }; - float[,] matrix = - { - { 0, 0, 0, 4 / divisor, 3 / divisor }, - { 1 / divisor, 2 / divisor, 3 / divisor, 2 / divisor, 1 / divisor } - }; + return new ErrorDither(matrix, offset); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateSierra3() + { + const float divisor = 32F; + const int offset = 2; - private static ErrorDither CreateSierra3() + float[,] matrix = { - const float divisor = 32F; - const int offset = 2; + { 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 } + }; - float[,] matrix = - { - { 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); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateSierraLite() + { + const float divisor = 4F; + const int offset = 1; - private static ErrorDither CreateSierraLite() + float[,] matrix = { - const float divisor = 4F; - const int offset = 1; + { 0, 0, 2 / divisor }, + { 1 / divisor, 1 / divisor, 0 } + }; - float[,] matrix = - { - { 0, 0, 2 / divisor }, - { 1 / divisor, 1 / divisor, 0 } - }; + return new ErrorDither(matrix, offset); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateStevensonArce() + { + const float divisor = 200F; + const int offset = 3; - private static ErrorDither CreateStevensonArce() + float[,] matrix = { - const float divisor = 200F; - const int offset = 3; + { 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 } + }; - float[,] matrix = - { - { 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); + } - return new ErrorDither(matrix, offset); - } + private static ErrorDither CreateStucki() + { + const float divisor = 42F; + const int offset = 2; - private static ErrorDither CreateStucki() + float[,] matrix = { - const float divisor = 42F; - const int offset = 2; - - float[,] matrix = - { - { 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); - } + { 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/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index aa6603373c..b4bd7b7972 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,226 +8,225 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +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; + /// - /// An error diffusion dithering implementation. - /// + /// Initializes a new instance of the struct. /// - public readonly partial struct ErrorDither : IDither, IEquatable, IEquatable + /// The diffusion matrix. + /// The starting offset within the matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public ErrorDither(in DenseMatrix matrix, int offset) { - 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) - { - Guard.MustBeGreaterThan(offset, 0, nameof(offset)); + Guard.MustBeGreaterThan(offset, 0, nameof(offset)); - this.matrix = matrix; - this.offset = 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, IQuantizer - where TPixel : unmanaged, IPixel - { - if (this == default) - { - ThrowDefaultInstance(); - } + /// + /// 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; - int offsetY = bounds.Top; - int offsetX = bounds.Left; - float scale = quantizer.Options.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; + /// + /// 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); - for (int y = bounds.Top; y < bounds.Bottom; y++) - { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); - ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + /// + /// 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); - 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); - } - } + /// + /// 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, IQuantizer + where TPixel : unmanaged, IPixel + { + if (this == default) + { + ThrowDefaultInstance(); } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - in TPaletteDitherImageProcessor processor, - ImageFrame source, - Rectangle bounds) - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel + int offsetY = bounds.Top; + int offsetX = bounds.Left; + float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; + + for (int y = bounds.Top; y < bounds.Bottom; y++) { - if (this == default) + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = bounds.Left; x < bounds.Right; x++) { - ThrowDefaultInstance(); + 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 + { + if (this == default) + { + ThrowDefaultInstance(); + } - Buffer2D sourceBuffer = source.PixelBuffer; - float scale = processor.DitherScale; - for (int y = bounds.Top; y < bounds.Bottom; y++) + Buffer2D sourceBuffer = source.PixelBuffer; + float scale = processor.DitherScale; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); + for (int x = bounds.Left; x < bounds.Right; x++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(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; - } + 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; + Buffer2D imageBuffer = image.PixelBuffer; - // 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 + // Loop through and distribute the error amongst neighboring pixels. + for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) { - // Equal? Break out as there's no error to pass. - if (source.Equals(transformed)) + if (targetY >= bounds.Bottom) { - return transformed; + continue; } - // Calculate the error - Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; + Span rowSpan = imageBuffer.DangerousGetRowSpan(targetY); - int offset = this.offset; - DenseMatrix matrix = this.matrix; - Buffer2D imageBuffer = image.PixelBuffer; - - // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) + for (int col = 0; col < matrix.Columns; col++) { - if (targetY >= bounds.Bottom) + int targetX = x + (col - offset); + if (targetX < bounds.Left || targetX >= bounds.Right) { continue; } - Span rowSpan = imageBuffer.DangerousGetRowSpan(targetY); - - for (int col = 0; col < matrix.Columns; col++) + float coefficient = matrix[row, col]; + if (coefficient == 0) { - 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); + continue; } - } - return transformed; + ref TPixel pixel = ref rowSpan[targetX]; + var result = pixel.ToVector4(); + + result += error * coefficient; + pixel.FromVector4(result); + } } - /// - public override bool Equals(object obj) - => obj is ErrorDither dither && this.Equals(dither); + return transformed; + } - /// - public bool Equals(ErrorDither other) - => this.offset == other.offset && this.matrix.Equals(other.matrix); + /// + public override bool Equals(object obj) + => obj is ErrorDither dither && this.Equals(dither); - /// - public bool Equals(IDither other) - => this.Equals((object)other); + /// + public bool Equals(ErrorDither other) + => this.offset == other.offset && this.matrix.Equals(other.matrix); - /// - public override int GetHashCode() - => HashCode.Combine(this.offset, this.matrix); + /// + public bool Equals(IDither other) + => this.Equals((object)other); - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowDefaultInstance() - => throw new ImageProcessingException("Cannot use the default value type instance to dither."); - } + /// + public override int GetHashCode() + => HashCode.Combine(this.offset, this.matrix); + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowDefaultInstance() + => throw new ImageProcessingException("Cannot use the default value type instance to dither."); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs index 547e64e2f6..ac2921b98d 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -4,45 +4,44 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// Defines the contract for types that apply dithering to images. +/// +public interface IDither { /// - /// Defines the contract for types that apply dithering to images. + /// Transforms the quantized image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. /// - 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, IQuantizer - where TPixel : unmanaged, IPixel; + /// 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, IQuantizer + 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; - } + /// + /// 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/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs index 13923cd123..e406d82c69 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -1,38 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +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 { /// - /// Implements an algorithm to alter the pixels of an image via palette dithering. + /// Gets the configuration instance to use when performing operations. /// - /// The pixel format. - public interface IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel - { - /// - /// Gets the configuration instance to use when performing operations. - /// - Configuration Configuration { get; } + Configuration Configuration { get; } - /// - /// Gets the dithering palette. - /// - ReadOnlyMemory Palette { 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; } + /// + /// 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); - } + /// + /// 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/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs index f036273c2e..cd35c1aa4b 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// An ordered dithering matrix with equal sides of arbitrary length +/// +public readonly partial struct OrderedDither { - /// - /// 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 readonly OrderedDither Bayer2x2 = new(2); + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public static readonly OrderedDither Bayer2x2 = new(2); - /// - /// Applies order dithering using the 4x4 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer4x4 = new(4); + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public static readonly OrderedDither Bayer4x4 = new(4); - /// - /// Applies order dithering using the 8x8 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer8x8 = new(8); + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public static readonly OrderedDither Bayer8x8 = new(8); - /// - /// Applies order dithering using the 16x16 Bayer dithering matrix. - /// - public static readonly OrderedDither Bayer16x16 = new(16); + /// + /// Applies order dithering using the 16x16 Bayer dithering matrix. + /// + public static readonly OrderedDither Bayer16x16 = new(16); - /// - /// Applies order dithering using the 3x3 ordered dithering matrix. - /// - public static readonly OrderedDither Ordered3x3 = new(3); - } + /// + /// Applies order dithering using the 3x3 ordered dithering matrix. + /// + public static readonly OrderedDither Ordered3x3 = new(3); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 4c6e8e043b..4d012de02c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,224 +1,222 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// An ordered dithering matrix with equal sides of arbitrary length +/// +public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable { + private readonly DenseMatrix thresholdMatrix; + private readonly int modulusX; + private readonly int modulusY; + /// - /// An ordered dithering matrix with equal sides of arbitrary length + /// Initializes a new instance of the struct. /// - public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable + /// The length of the matrix sides + [MethodImpl(InliningOptions.ShortMethod)] + public OrderedDither(uint length) { - private readonly DenseMatrix thresholdMatrix; - private readonly int modulusX; - private readonly int modulusY; - - /// - /// Initializes a new instance of the struct. - /// - /// The length of the matrix sides - [MethodImpl(InliningOptions.ShortMethod)] - public OrderedDither(uint length) - { - Guard.MustBeGreaterThan(length, 0, nameof(length)); + Guard.MustBeGreaterThan(length, 0, nameof(length)); - DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(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++) + // 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++) { - for (int x = 0; x < length; x++) - { - thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; - } + 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, IQuantizer - where TPixel : unmanaged, IPixel + 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, IQuantizer + where TPixel : unmanaged, IPixel + { + if (this == default) { - if (this == default) - { - ThrowDefaultInstance(); - } + ThrowDefaultInstance(); + } - int spread = CalculatePaletteSpread(destination.Palette.Length); - float scale = quantizer.Options.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; + int spread = CalculatePaletteSpread(destination.Palette.Length); + float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; - for (int y = bounds.Top; y < bounds.Bottom; y++) + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); + Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y)[..sourceRow.Length]; + + for (int x = 0; x < sourceRow.Length; x++) { - ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); - Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y)[..sourceRow.Length]; - - for (int x = 0; x < sourceRow.Length; x++) - { - TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale); - destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); - } + TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale); + destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _); } } + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyPaletteDither( - in TPaletteDitherImageProcessor processor, - ImageFrame source, - Rectangle bounds) - where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor - where TPixel : unmanaged, IPixel + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + if (this == default) { - if (this == default) - { - ThrowDefaultInstance(); - } + ThrowDefaultInstance(); + } + + int spread = CalculatePaletteSpread(processor.Palette.Length); + float scale = processor.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; - int spread = CalculatePaletteSpread(processor.Palette.Length); - float scale = processor.DitherScale; - Buffer2D sourceBuffer = source.PixelBuffer; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); - for (int y = bounds.Top; y < bounds.Bottom; y++) + for (int x = 0; x < row.Length; x++) { - Span row = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); - - for (int x = 0; x < row.Length; x++) - { - ref TPixel sourcePixel = ref row[x]; - TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale); - sourcePixel = processor.GetPaletteColor(dithered); - } + ref TPixel sourcePixel = ref row[x]; + TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale); + sourcePixel = processor.GetPaletteColor(dithered); } } + } - // Spread assumes an even colorspace distribution and precision. - // TODO: Cubed root is currently used to represent 3 color channels - // but we should introduce something to PixelTypeInfo. - // https://bisqwit.iki.fi/story/howto/dither/jy/ - // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm - internal static int CalculatePaletteSpread(int colors) - => (int)(255 / Math.Max(1, Math.Pow(colors, 1.0 / 3) - 1)); - - [MethodImpl(InliningOptions.ShortMethod)] - internal TPixel Dither( - TPixel source, - int x, - int y, - int spread, - float scale) - where TPixel : unmanaged, IPixel - { - Unsafe.SkipInit(out Rgba32 rgba); - source.ToRgba32(ref rgba); - Unsafe.SkipInit(out Rgba32 attempt); + // Spread assumes an even colorspace distribution and precision. + // TODO: Cubed root is currently used to represent 3 color channels + // but we should introduce something to PixelTypeInfo. + // https://bisqwit.iki.fi/story/howto/dither/jy/ + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + internal static int CalculatePaletteSpread(int colors) + => (int)(255 / Math.Max(1, Math.Pow(colors, 1.0 / 3) - 1)); + + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + TPixel source, + int x, + int y, + int spread, + float scale) + where TPixel : unmanaged, IPixel + { + Unsafe.SkipInit(out Rgba32 rgba); + source.ToRgba32(ref rgba); + Unsafe.SkipInit(out Rgba32 attempt); - float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; + float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; - attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue); - attempt.G = (byte)Numerics.Clamp(rgba.G + factor, byte.MinValue, byte.MaxValue); - attempt.B = (byte)Numerics.Clamp(rgba.B + factor, byte.MinValue, byte.MaxValue); - attempt.A = (byte)Numerics.Clamp(rgba.A + factor, byte.MinValue, byte.MaxValue); + attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue); + attempt.G = (byte)Numerics.Clamp(rgba.G + factor, byte.MinValue, byte.MaxValue); + attempt.B = (byte)Numerics.Clamp(rgba.B + factor, byte.MinValue, byte.MaxValue); + attempt.A = (byte)Numerics.Clamp(rgba.A + factor, byte.MinValue, byte.MaxValue); - TPixel result = default; - result.FromRgba32(attempt); + TPixel result = default; + result.FromRgba32(attempt); - return result; - } + return result; + } - /// - public override bool Equals(object obj) - => obj is OrderedDither dither && this.Equals(dither); + /// + 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; + /// + [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); + /// + public bool Equals(IDither other) + => this.Equals((object)other); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() - => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowDefaultInstance() - => throw new ImageProcessingException("Cannot use the default value type instance to dither."); - } + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowDefaultInstance() + => throw new ImageProcessingException("Cannot use the default value type instance to dither."); } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs index 6000b9b64b..7f8b347243 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs @@ -3,91 +3,90 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// A factory for creating ordered dither matrices. +/// +internal static class OrderedDitherFactory { /// - /// A factory for creating ordered dither matrices. + /// Creates an ordered dithering matrix with equal sides of arbitrary length. + /// /// - internal static class OrderedDitherFactory + /// The length of the matrix sides + /// The + public static DenseMatrix CreateDitherMatrix(uint length) { - /// - /// Creates an ordered dithering matrix with equal sides of arbitrary length. - /// - /// - /// The length of the matrix sides - /// The - public static DenseMatrix CreateDitherMatrix(uint length) + // Calculate the the logarithm of length to the base 2 + uint exponent = 0; + uint bayerLength; + do { - // Calculate the the logarithm of length to the base 2 - uint exponent = 0; - uint bayerLength; - do - { - exponent++; - bayerLength = (uint)(1 << (int)exponent); - } - while (length > bayerLength); + exponent++; + bayerLength = (uint)(1 << (int)exponent); + } + while (length > bayerLength); - // Create our Bayer matrix that matches the given exponent and dimensions - var matrix = new DenseMatrix((int)length); - uint i = 0; - for (int y = 0; y < length; y++) + // Create our Bayer matrix that matches the given exponent and dimensions + var matrix = new DenseMatrix((int)length); + uint i = 0; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) { - for (int x = 0; x < length; x++) - { - matrix[y, x] = Bayer(i / length, i % length, exponent); - i++; - } + matrix[y, x] = Bayer(i / length, i % length, exponent); + i++; } + } - // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, - // we need to convert the numbers so that the resulting range is un-gapped. - // We generated: We saved: We compress the number range: - // 0 8 2 10 0 8 2 0 5 2 - // 12 4 14 6 12 4 14 7 4 8 - // 3 11 1 9 3 11 1 3 6 1 - // 15 7 13 5 - uint maxValue = bayerLength * bayerLength; - uint missing = 0; - for (uint v = 0; v < maxValue; ++v) + // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, + // we need to convert the numbers so that the resulting range is un-gapped. + // We generated: We saved: We compress the number range: + // 0 8 2 10 0 8 2 0 5 2 + // 12 4 14 6 12 4 14 7 4 8 + // 3 11 1 9 3 11 1 3 6 1 + // 15 7 13 5 + uint maxValue = bayerLength * bayerLength; + uint missing = 0; + for (uint v = 0; v < maxValue; ++v) + { + bool found = false; + for (int y = 0; y < length; ++y) { - bool found = false; - for (int y = 0; y < length; ++y) + for (int x = 0; x < length; x++) { - for (int x = 0; x < length; x++) + if (matrix[y, x] == v) { - if (matrix[y, x] == v) - { - matrix[y, x] -= missing; - found = true; - break; - } + matrix[y, x] -= missing; + found = true; + break; } } - - if (!found) - { - ++missing; - } } - return matrix; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint Bayer(uint x, uint y, uint order) - { - uint result = 0; - for (uint i = 0; i < order; ++i) + if (!found) { - uint xOddXorYOdd = (x & 1) ^ (y & 1); - uint xOdd = x & 1; - result = ((result << 1 | xOddXorYOdd) << 1) | xOdd; - x >>= 1; - y >>= 1; + ++missing; } + } - return result; + return matrix; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Bayer(uint x, uint y, uint order) + { + uint result = 0; + for (uint i = 0; i < order; ++i) + { + uint xOddXorYOdd = (x & 1) ^ (y & 1); + uint xOdd = x & 1; + result = ((result << 1 | xOddXorYOdd) << 1) | xOdd; + x >>= 1; + y >>= 1; } + + return result; } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs index d973109f45..973299453c 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -1,79 +1,77 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// Allows the consumption a palette to dither an image. +/// +public sealed class PaletteDitherProcessor : IImageProcessor { /// - /// Allows the consumption a palette to dither an image. + /// Initializes a new instance of the class. /// - public sealed class PaletteDitherProcessor : IImageProcessor + /// The ordered ditherer. + public PaletteDitherProcessor(IDither dither) + : this(dither, QuantizerConstants.MaxDitherScale) { - /// - /// 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 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. - public PaletteDitherProcessor(IDither dither, ReadOnlyMemory palette) - : this(dither, QuantizerConstants.MaxDitherScale, palette) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The dithering algorithm. + /// The palette to select substitute colors from. + 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 = Numerics.Clamp(ditherScale, QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); - this.Palette = 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 = Numerics.Clamp(ditherScale, QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); + this.Palette = palette; + } - /// - /// Gets the dithering algorithm to apply to the output image. - /// - public IDither Dither { get; } + /// + /// 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 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; } + /// + /// Gets the palette to select substitute colors from. + /// + public ReadOnlyMemory Palette { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new PaletteDitherProcessor(configuration, this, source, sourceRectangle); - } + /// + 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 5b336d78fe..f436343183 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -1,114 +1,112 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Processing.Processors.Dithering +namespace SixLabors.ImageSharp.Processing.Processors.Dithering; + +/// +/// Allows the consumption a palette to dither an image. +/// +/// The pixel format. +internal sealed class PaletteDitherProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly DitherProcessor ditherProcessor; + private readonly IDither dither; + private IMemoryOwner paletteOwner; + private bool isDisposed; + /// - /// Allows the consumption a palette to dither an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal sealed class PaletteDitherProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly DitherProcessor ditherProcessor; - private readonly IDither dither; - 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 defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.dither = definition.Dither; + 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); + + this.ditherProcessor = new DitherProcessor( + this.Configuration, + this.paletteOwner.Memory, + definition.DitherScale); + } - ReadOnlySpan sourcePalette = definition.Palette.Span; - this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); - Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); + /// + protected override void OnFrameApply(ImageFrame source) + { + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); + } - this.ditherProcessor = new DitherProcessor( - this.Configuration, - this.paletteOwner.Memory, - definition.DitherScale); + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; } - /// - protected override void OnFrameApply(ImageFrame source) + this.isDisposed = true; + if (disposing) { - Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); + this.paletteOwner.Dispose(); + this.ditherProcessor.Dispose(); } - /// - protected override void Dispose(bool disposing) + this.paletteOwner = null; + base.Dispose(disposing); + } + + /// + /// Used to allow inlining of calls to + /// . + /// + /// Internal for AOT + [SuppressMessage( + "Design", + "CA1001:Types that own disposable fields should be disposable", + Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] + internal readonly struct DitherProcessor : IPaletteDitherImageProcessor, IDisposable + { + private readonly EuclideanPixelMap pixelMap; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherProcessor( + Configuration configuration, + ReadOnlyMemory palette, + float ditherScale) { - if (this.isDisposed) - { - return; - } - - this.isDisposed = true; - if (disposing) - { - this.paletteOwner.Dispose(); - this.ditherProcessor.Dispose(); - } - - this.paletteOwner = null; - base.Dispose(disposing); + this.Configuration = configuration; + this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.Palette = palette; + this.DitherScale = ditherScale; } - /// - /// Used to allow inlining of calls to - /// . - /// - /// Internal for AOT - [SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] - internal readonly struct DitherProcessor : IPaletteDitherImageProcessor, IDisposable + public Configuration Configuration { get; } + + public ReadOnlyMemory Palette { get; } + + public float DitherScale { get; } + + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel GetPaletteColor(TPixel color) { - private readonly EuclideanPixelMap pixelMap; - - [MethodImpl(InliningOptions.ShortMethod)] - public DitherProcessor( - Configuration configuration, - ReadOnlyMemory palette, - float ditherScale) - { - this.Configuration = configuration; - this.pixelMap = new EuclideanPixelMap(configuration, palette); - this.Palette = palette; - this.DitherScale = ditherScale; - } - - public Configuration Configuration { get; } - - public ReadOnlyMemory Palette { get; } - - public float DitherScale { get; } - - [MethodImpl(InliningOptions.ShortMethod)] - public TPixel GetPaletteColor(TPixel color) - { - this.pixelMap.GetClosestColor(color, out TPixel match); - return match; - } - - public void Dispose() => this.pixelMap.Dispose(); + this.pixelMap.GetClosestColor(color, out TPixel match); + return match; } + + public void Dispose() => this.pixelMap.Dispose(); } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs index c051ea9986..3206f7a3cb 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -4,100 +4,99 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Drawing +namespace SixLabors.ImageSharp.Processing.Processors.Drawing; + +/// +/// Combines two images together by blending the pixels. +/// +public class DrawImageProcessor : IImageProcessor { /// - /// Combines two images together by blending the pixels. + /// Initializes a new instance of the class. /// - public class DrawImageProcessor : IImageProcessor + /// The image to blend. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. + public DrawImageProcessor( + Image image, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) { - /// - /// Initializes a new instance of the class. - /// - /// The image to blend. - /// The location to draw the blended image. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. - public DrawImageProcessor( - Image image, - Point location, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - { - this.Image = image; - this.Location = location; - this.ColorBlendingMode = colorBlendingMode; - this.AlphaCompositionMode = alphaCompositionMode; - this.Opacity = opacity; - } + this.Image = image; + this.Location = location; + this.ColorBlendingMode = colorBlendingMode; + this.AlphaCompositionMode = alphaCompositionMode; + this.Opacity = opacity; + } - /// - /// Gets the image to blend. - /// - public Image Image { get; } + /// + /// Gets the image to blend. + /// + public Image Image { get; } - /// - /// Gets the location to draw the blended image. - /// - public Point Location { get; } + /// + /// Gets the location to draw the blended image. + /// + public Point Location { get; } - /// - /// Gets the blending mode to use when drawing the image. - /// - public PixelColorBlendingMode ColorBlendingMode { get; } + /// + /// Gets the blending mode to use when drawing the image. + /// + public PixelColorBlendingMode ColorBlendingMode { get; } - /// - /// Gets the Alpha blending mode to use when drawing the image. - /// - public PixelAlphaCompositionMode AlphaCompositionMode { get; } + /// + /// Gets the Alpha blending mode to use when drawing the image. + /// + public PixelAlphaCompositionMode AlphaCompositionMode { get; } - /// - /// Gets the opacity of the image to blend. - /// - public float Opacity { get; } + /// + /// Gets the opacity of the image to blend. + /// + public float Opacity { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixelBg : unmanaged, IPixel - { - var visitor = new ProcessorFactoryVisitor(configuration, this, source, sourceRectangle); - this.Image.AcceptVisitor(visitor); - return visitor.Result; - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixelBg : unmanaged, IPixel + { + var visitor = new ProcessorFactoryVisitor(configuration, this, source, sourceRectangle); + this.Image.AcceptVisitor(visitor); + return visitor.Result; + } - private class ProcessorFactoryVisitor : IImageVisitor - where TPixelBg : unmanaged, IPixel - { - private readonly Configuration configuration; - private readonly DrawImageProcessor definition; - private readonly Image source; - private readonly Rectangle sourceRectangle; + private class ProcessorFactoryVisitor : IImageVisitor + where TPixelBg : unmanaged, IPixel + { + private readonly Configuration configuration; + private readonly DrawImageProcessor definition; + private readonly Image source; + private readonly Rectangle sourceRectangle; - public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle) - { - this.configuration = configuration; - this.definition = definition; - this.source = source; - this.sourceRectangle = sourceRectangle; - } + public ProcessorFactoryVisitor(Configuration configuration, DrawImageProcessor definition, Image source, Rectangle sourceRectangle) + { + this.configuration = configuration; + this.definition = definition; + this.source = source; + this.sourceRectangle = sourceRectangle; + } - public IImageProcessor Result { get; private set; } + public IImageProcessor Result { get; private set; } - public void Visit(Image image) - where TPixelFg : unmanaged, IPixel - { - this.Result = new DrawImageProcessor( - this.configuration, - image, - this.source, - this.sourceRectangle, - this.definition.Location, - this.definition.ColorBlendingMode, - this.definition.AlphaCompositionMode, - this.definition.Opacity); - } + public void Visit(Image image) + where TPixelFg : unmanaged, IPixel + { + this.Result = new DrawImageProcessor( + this.configuration, + image, + this.source, + this.sourceRectangle, + this.definition.Location, + this.definition.ColorBlendingMode, + this.definition.AlphaCompositionMode, + this.definition.Opacity); } } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 7ed8034e1f..ac0d367515 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -1,157 +1,155 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Drawing +namespace SixLabors.ImageSharp.Processing.Processors.Drawing; + +/// +/// Combines two images together by blending the pixels. +/// +/// The pixel format of destination image. +/// The pixel format of source image. +internal class DrawImageProcessor : ImageProcessor + where TPixelBg : unmanaged, IPixel + where TPixelFg : unmanaged, IPixel { /// - /// Combines two images together by blending the pixels. + /// Initializes a new instance of the class. /// - /// The pixel format of destination image. - /// The pixel format of source image. - internal class DrawImageProcessor : ImageProcessor - where TPixelBg : unmanaged, IPixel - where TPixelFg : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The foreground to blend with the currently processing image. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + /// The location to draw the blended image. + /// The blending mode to use when drawing the image. + /// The Alpha blending mode to use when drawing the image. + /// The opacity of the image to blend. Must be between 0 and 1. + public DrawImageProcessor( + Configuration configuration, + Image image, + Image source, + Rectangle sourceRectangle, + Point location, + PixelColorBlendingMode colorBlendingMode, + PixelAlphaCompositionMode alphaCompositionMode, + float opacity) + : base(configuration, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The foreground to blend with the currently processing image. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - /// The location to draw the blended image. - /// The blending mode to use when drawing the image. - /// The Alpha blending mode to use when drawing the image. - /// The opacity of the image to blend. Must be between 0 and 1. - public DrawImageProcessor( - Configuration configuration, - Image image, - Image source, - Rectangle sourceRectangle, - Point location, - PixelColorBlendingMode colorBlendingMode, - PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - : base(configuration, source, sourceRectangle) - { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - this.Image = image; - this.Opacity = opacity; - this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); - this.Location = location; - } + this.Image = image; + this.Opacity = opacity; + this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); + this.Location = location; + } + + /// + /// Gets the image to blend + /// + public Image Image { get; } - /// - /// Gets the image to blend - /// - public Image Image { get; } + /// + /// Gets the opacity of the image to blend + /// + public float Opacity { get; } - /// - /// Gets the opacity of the image to blend - /// - public float Opacity { get; } + /// + /// Gets the pixel blender + /// + public PixelBlender Blender { get; } - /// - /// Gets the pixel blender - /// - public PixelBlender Blender { get; } + /// + /// Gets the location to draw the blended image + /// + public Point Location { get; } - /// - /// Gets the location to draw the blended image - /// - public Point Location { get; } + /// + protected override void OnFrameApply(ImageFrame source) + { + Rectangle sourceRectangle = this.SourceRectangle; + Configuration configuration = this.Configuration; - /// - protected override void OnFrameApply(ImageFrame source) - { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; + Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; - Image targetImage = this.Image; - PixelBlender blender = this.Blender; - int locationY = this.Location.Y; + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); - // Align start/end positions. - Rectangle bounds = targetImage.Bounds(); + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); + int targetX = minX - this.Location.X; - int minX = Math.Max(this.Location.X, sourceRectangle.X); - int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Right); - int targetX = minX - this.Location.X; + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - int minY = Math.Max(this.Location.Y, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + int width = maxX - minX; - int width = maxX - minX; + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + // Not a valid operation because rectangle does not overlap with this image. + if (workingRect.Width <= 0 || workingRect.Height <= 0) + { + throw new ImageProcessingException( + "Cannot draw image because the source image does not overlap the target image."); + } - // Not a valid operation because rectangle does not overlap with this image. - if (workingRect.Width <= 0 || workingRect.Height <= 0) - { - throw new ImageProcessingException( - "Cannot draw image because the source image does not overlap the target image."); - } + var operation = new RowOperation(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( + configuration, + workingRect, + in operation); + } - var operation = new RowOperation(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity); - ParallelRowIterator.IterateRows( - configuration, - workingRect, - in operation); + /// + /// A implementing the draw logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Buffer2D source; + private readonly Buffer2D target; + 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( + Buffer2D source, + Buffer2D target, + PixelBlender blender, + Configuration configuration, + int minX, + int width, + int locationY, + int targetX, + float opacity) + { + this.source = source; + this.target = target; + this.blender = blender; + this.configuration = configuration; + this.minX = minX; + this.width = width; + this.locationY = locationY; + this.targetX = targetX; + this.opacity = opacity; } - /// - /// A implementing the draw logic for . - /// - private readonly struct RowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Buffer2D source; - private readonly Buffer2D target; - 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( - Buffer2D source, - Buffer2D target, - PixelBlender blender, - Configuration configuration, - int minX, - int width, - int locationY, - int targetX, - float opacity) - { - this.source = source; - this.target = target; - 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.source.DangerousGetRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); - } + Span background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.target.DangerousGetRowSpan(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 index bc0c8f8735..5d7b626ddf 100644 --- a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs +++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs @@ -1,21 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// An used by the row delegates for a given instance +/// +public interface IPixelRowDelegate { /// - /// An used by the row delegates for a given instance + /// Applies the current pixel row delegate to a target row of preprocessed pixels. /// - 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); - } + /// 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 c03b3526b0..e24fbc348a 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -3,44 +3,43 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Defines an oil painting effect. +/// +public sealed class OilPaintingProcessor : IImageProcessor { /// - /// Defines an oil painting effect. + /// Initializes a new instance of the class. /// - public sealed class OilPaintingProcessor : IImageProcessor + /// + /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. + /// + /// + /// The number of neighboring pixels used in calculating each individual pixel value. + /// + public OilPaintingProcessor(int levels, int brushSize) { - /// - /// Initializes a new instance of the class. - /// - /// - /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. - /// - /// - /// The number of neighboring pixels used in calculating each individual pixel value. - /// - public OilPaintingProcessor(int levels, int brushSize) - { - Guard.MustBeGreaterThan(levels, 0, nameof(levels)); - Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); + Guard.MustBeGreaterThan(levels, 0, nameof(levels)); + Guard.MustBeGreaterThan(brushSize, 0, nameof(brushSize)); - this.Levels = levels; - this.BrushSize = brushSize; - } + this.Levels = levels; + this.BrushSize = brushSize; + } - /// - /// Gets the number of intensity levels. - /// - public int Levels { get; } + /// + /// Gets the number of intensity levels. + /// + public int Levels { get; } - /// - /// Gets the brush size. - /// - public int BrushSize { get; } + /// + /// Gets the brush size. + /// + public int BrushSize { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new OilPaintingProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 4d33b848b1..67964f0ebd 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,171 +8,170 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Applies oil painting effect processing to the image. +/// +/// Adapted from by Dewald Esterhuizen. +/// The pixel format. +internal class OilPaintingProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly OilPaintingProcessor definition; + /// - /// Applies oil painting effect processing to the image. + /// Initializes a new instance of the class. /// - /// Adapted from by Dewald Esterhuizen. - /// The pixel format. - internal class OilPaintingProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 OilPaintingProcessor(Configuration configuration, OilPaintingProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; + + /// + protected override void OnFrameApply(ImageFrame source) { - private readonly OilPaintingProcessor 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 OilPaintingProcessor(Configuration configuration, OilPaintingProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; + int brushSize = Math.Clamp(this.definition.BrushSize, 1, Math.Min(source.Width, source.Height)); - /// - protected override void OnFrameApply(ImageFrame source) - { - int brushSize = Math.Clamp(this.definition.BrushSize, 1, Math.Min(source.Width, source.Height)); + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + source.CopyTo(targetPixels); - source.CopyTo(targetPixels); + RowIntervalOperation operation = new(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, this.definition.Levels); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + this.SourceRectangle, + in operation); - RowIntervalOperation operation = new(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, this.definition.Levels); - ParallelRowIterator.IterateRowIntervals( - this.Configuration, - this.SourceRectangle, - in operation); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D source; + private readonly Configuration configuration; + private readonly int radius; + private readonly int levels; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D 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; } - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowIntervalOperation : IRowIntervalOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D source; - private readonly Configuration configuration; - private readonly int radius; - private readonly int levels; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D source, - Configuration configuration, - int radius, - int levels) + 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++) { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.source = source; - this.configuration = configuration; - this.radius = radius; - this.levels = levels; - } + Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - /// - [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++) + PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + + for (int x = this.bounds.X; x < this.bounds.Right; x++) { - Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); - Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); + int maxIntensity = 0; + int maxIndex = 0; - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + // Clear the current shared buffer before processing each target pixel + bins.Memory.Span.Clear(); - for (int x = this.bounds.X; x < this.bounds.Right; x++) + for (int fy = 0; fy <= this.radius; fy++) { - int maxIntensity = 0; - int maxIndex = 0; + int fyr = fy - this.radius; + int offsetY = y + fyr; + offsetY = Numerics.Clamp(offsetY, 0, maxY); - // Clear the current shared buffer before processing each target pixel - bins.Memory.Span.Clear(); + Span sourceOffsetRow = this.source.DangerousGetRowSpan(offsetY); - for (int fy = 0; fy <= this.radius; fy++) + for (int fx = 0; fx <= this.radius; fx++) { - int fyr = fy - this.radius; - int offsetY = y + fyr; - offsetY = Numerics.Clamp(offsetY, 0, maxY); - - Span sourceOffsetRow = this.source.DangerousGetRowSpan(offsetY); - - for (int fx = 0; fx <= this.radius; fx++) - { - int fxr = fx - this.radius; - int offsetX = x + fxr; - offsetX = Numerics.Clamp(offsetX, 0, maxX); + int fxr = fx - this.radius; + int offsetX = x + fxr; + offsetX = Numerics.Clamp(offsetX, 0, maxX); - Vector4 vector = sourceOffsetRow[offsetX].ToVector4(); + Vector4 vector = sourceOffsetRow[offsetX].ToVector4(); - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); - Unsafe.Add(ref intensityBinRef, currentIntensity)++; - Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; - Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; - Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; - if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) - { - maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); - maxIndex = currentIntensity; - } + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) + { + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; } + } - 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; + 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; - targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); - } + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); } + } - Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); - } + 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 43816da029..847a211a5a 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Applies a user defined row processing delegate to the image. +/// +internal sealed class PixelRowDelegateProcessor : IImageProcessor { /// - /// Applies a user defined row processing delegate to the image. + /// Initializes a new instance of the class. /// - internal sealed class PixelRowDelegateProcessor : IImageProcessor + /// The user defined, row processing delegate. + /// The to apply during the pixel conversions. + public PixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) { - /// - /// Initializes a new instance of the class. - /// - /// The user defined, row processing delegate. - /// The to apply during the pixel conversions. - public PixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) - { - this.PixelRowOperation = pixelRowOperation; - this.Modifiers = modifiers; - } - - /// - /// Gets the user defined row processing delegate to the image. - /// - public PixelRowOperation PixelRowOperation { get; } + this.PixelRowOperation = pixelRowOperation; + this.Modifiers = modifiers; + } - /// - /// Gets the to apply during the pixel conversions. - /// - public PixelConversionModifiers Modifiers { get; } + /// + /// Gets the user defined row processing delegate to the image. + /// + public PixelRowOperation PixelRowOperation { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - { - return new PixelRowDelegateProcessor( - new PixelRowDelegate(this.PixelRowOperation), - configuration, - this.Modifiers, - source, - sourceRectangle); - } + /// + /// Gets the to apply during the pixel conversions. + /// + public PixelConversionModifiers Modifiers { get; } - /// - /// A implementing the row processing logic for . - /// - public readonly struct PixelRowDelegate : IPixelRowDelegate - { - private readonly PixelRowOperation pixelRowOperation; + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } - [MethodImpl(InliningOptions.ShortMethod)] - public PixelRowDelegate(PixelRowOperation pixelRowOperation) - { - this.pixelRowOperation = pixelRowOperation; - } + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(Span span, Point offset) => this.pixelRowOperation(span); + [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/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 2444852a63..e8c7468911 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -1,101 +1,99 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 +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; + /// - /// The base class for all processors that accept a user defined row processing delegate. + /// Initializes a new instance of the class. /// - /// The pixel format. - /// The row processor type. - internal sealed class PixelRowDelegateProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - where TDelegate : struct, IPixelRowDelegate + /// 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) { - private readonly TDelegate rowDelegate; + 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.PixelBuffer, this.Configuration, this.modifiers, this.rowDelegate); + + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } - /// - /// The to apply during the pixel conversions. - /// + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly Buffer2D source; + private readonly Configuration configuration; private readonly PixelConversionModifiers modifiers; + private readonly TDelegate rowProcessor; - /// - /// 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, + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + Buffer2D source, Configuration configuration, PixelConversionModifiers modifiers, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) + in TDelegate rowProcessor) { - this.rowDelegate = rowDelegate; + this.startX = startX; + this.source = source; + this.configuration = configuration; this.modifiers = modifiers; + this.rowProcessor = rowProcessor; } /// - protected override void OnFrameApply(ImageFrame source) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source.PixelBuffer, 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 Buffer2D source; - private readonly Configuration configuration; - private readonly PixelConversionModifiers modifiers; - private readonly TDelegate rowProcessor; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D 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.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); + Span rowSpan = this.source.DangerousGetRowSpan(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)); + // 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); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 62c1e2dc74..a724cb7622 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -3,34 +3,33 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Defines a pixelation effect of a given size. +/// +public sealed class PixelateProcessor : IImageProcessor { /// - /// Defines a pixelation effect of a given size. + /// Initializes a new instance of the class. /// - public sealed class PixelateProcessor : IImageProcessor + /// The size of the pixels. Must be greater than 0. + /// + /// is less than 0 or equal to 0. + /// + public PixelateProcessor(int size) { - /// - /// Initializes a new instance of the class. - /// - /// The size of the pixels. Must be greater than 0. - /// - /// is less than 0 or equal to 0. - /// - public PixelateProcessor(int size) - { - Guard.MustBeGreaterThan(size, 0, nameof(size)); - this.Size = size; - } + Guard.MustBeGreaterThan(size, 0, nameof(size)); + this.Size = size; + } - /// - /// Gets or the pixel size. - /// - public int Size { get; } + /// + /// Gets or the pixel size. + /// + public int Size { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new PixelateProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 26e291592a..68000ba3e8 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -1,101 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Applies a pixelation effect processing to the image. +/// +/// The pixel format. +internal class PixelateProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly PixelateProcessor definition; + /// - /// Applies a pixelation effect processing to the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class PixelateProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - { - private readonly PixelateProcessor definition; + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// 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; - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// 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; + private int Size => this.definition.Size; - private int Size => this.definition.Size; + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + int size = this.Size; - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int size = this.Size; + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); - Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); - Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); + // 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.PixelBuffer).Invoke); + } - // 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.PixelBuffer).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 Buffer2D source; - private readonly struct RowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + int size, + Buffer2D source) { - 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 Buffer2D source; + 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; + } - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - int size, - Buffer2D source) - { - 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; - } + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span rowSpan = this.source.DangerousGetRowSpan(Math.Min(y + this.radius, this.maxYIndex)); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = this.minX; x < this.maxX; x += this.size) { - Span rowSpan = this.source.DangerousGetRowSpan(Math.Min(y + this.radius, this.maxYIndex)); + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; - for (int x = this.minX; x < this.maxX; x += this.size) + // 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++) { - // Get the pixel color in the centre of the soon to be pixelated area. - TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; - - // 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++) + for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) { - for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) - { - this.source[oX, oY] = pixel; - } + this.source[oX, oY] = pixel; } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs index bee8deb03e..6786eb5f50 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Effects +namespace SixLabors.ImageSharp.Processing.Processors.Effects; + +/// +/// Applies a user defined, position aware, row processing delegate to the image. +/// +internal sealed class PositionAwarePixelRowDelegateProcessor : IImageProcessor { /// - /// Applies a user defined, position aware, row processing delegate to the image. + /// Initializes a new instance of the class. /// - internal sealed class PositionAwarePixelRowDelegateProcessor : IImageProcessor + /// The user defined, position aware, row processing delegate. + /// The to apply during the pixel conversions. + public PositionAwarePixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) { - /// - /// Initializes a new instance of the class. - /// - /// The user defined, position aware, row processing delegate. - /// The to apply during the pixel conversions. - public PositionAwarePixelRowDelegateProcessor(PixelRowOperation pixelRowOperation, PixelConversionModifiers modifiers) - { - this.PixelRowOperation = pixelRowOperation; - this.Modifiers = modifiers; - } - - /// - /// Gets the user defined, position aware, row processing delegate. - /// - public PixelRowOperation PixelRowOperation { get; } + this.PixelRowOperation = pixelRowOperation; + this.Modifiers = modifiers; + } - /// - /// Gets the to apply during the pixel conversions. - /// - public PixelConversionModifiers Modifiers { get; } + /// + /// Gets the user defined, position aware, row processing delegate. + /// + public PixelRowOperation PixelRowOperation { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - { - return new PixelRowDelegateProcessor( - new PixelRowDelegate(this.PixelRowOperation), - configuration, - this.Modifiers, - source, - sourceRectangle); - } + /// + /// Gets the to apply during the pixel conversions. + /// + public PixelConversionModifiers Modifiers { get; } - /// - /// A implementing the row processing logic for . - /// - public readonly struct PixelRowDelegate : IPixelRowDelegate - { - private readonly PixelRowOperation pixelRowOperation; + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } - [MethodImpl(InliningOptions.ShortMethod)] - public PixelRowDelegate(PixelRowOperation pixelRowOperation) - { - this.pixelRowOperation = pixelRowOperation; - } + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(Span span, Point offset) => this.pixelRowOperation(span, offset); + [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/Filters/AchromatomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs index 6a9f30d33b..6decd39a74 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatomalyProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. +/// +public sealed class AchromatomalyProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. + /// Initializes a new instance of the class. /// - public sealed class AchromatomalyProcessor : FilterProcessor + public AchromatomalyProcessor() + : base(KnownFilterMatrices.AchromatomalyFilter) { - /// - /// Initializes a new instance of the class. - /// - public AchromatomalyProcessor() - : base(KnownFilterMatrices.AchromatomalyFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs index 7146b7c1db..8f25776a47 100644 --- a/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/AchromatopsiaProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. +/// +public sealed class AchromatopsiaProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. + /// Initializes a new instance of the class. /// - public sealed class AchromatopsiaProcessor : FilterProcessor + public AchromatopsiaProcessor() + : base(KnownFilterMatrices.AchromatopsiaFilter) { - /// - /// Initializes a new instance of the class. - /// - public AchromatopsiaProcessor() - : base(KnownFilterMatrices.AchromatopsiaFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs index bbe4120f47..b7d8cb525d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BlackWhiteProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a black and white filter matrix to the image. +/// +public sealed class BlackWhiteProcessor : FilterProcessor { /// - /// Applies a black and white filter matrix to the image. + /// Initializes a new instance of the class. /// - public sealed class BlackWhiteProcessor : FilterProcessor + public BlackWhiteProcessor() + : base(KnownFilterMatrices.BlackWhiteFilter) { - /// - /// Initializes a new instance of the class. - /// - public BlackWhiteProcessor() - : base(KnownFilterMatrices.BlackWhiteFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs index f933426e17..191553217b 100644 --- a/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/BrightnessProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a brightness filter matrix using the given amount. +/// +public sealed class BrightnessProcessor : FilterProcessor { /// - /// Applies a brightness filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class BrightnessProcessor : FilterProcessor + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public BrightnessProcessor(float amount) + : base(KnownFilterMatrices.CreateBrightnessFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public BrightnessProcessor(float amount) - : base(KnownFilterMatrices.CreateBrightnessFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs index 58ea967bd5..b41c2ac72a 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ContrastProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a contrast filter matrix using the given amount. +/// +public sealed class ContrastProcessor : FilterProcessor { /// - /// Applies a contrast filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class ContrastProcessor : FilterProcessor + /// + /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public ContrastProcessor(float amount) + : base(KnownFilterMatrices.CreateContrastFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// - /// A value of 0 will create an image that is completely gray. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public ContrastProcessor(float amount) - : base(KnownFilterMatrices.CreateContrastFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion. + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs index b9370e21ff..153c70a986 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranomalyProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. +/// +public sealed class DeuteranomalyProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. + /// Initializes a new instance of the class. /// - public sealed class DeuteranomalyProcessor : FilterProcessor + public DeuteranomalyProcessor() + : base(KnownFilterMatrices.DeuteranomalyFilter) { - /// - /// Initializes a new instance of the class. - /// - public DeuteranomalyProcessor() - : base(KnownFilterMatrices.DeuteranomalyFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs index 2b6e3d94c4..3b5d458c1d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/DeuteranopiaProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. +/// +public sealed class DeuteranopiaProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. + /// Initializes a new instance of the class. /// - public sealed class DeuteranopiaProcessor : FilterProcessor + public DeuteranopiaProcessor() + : base(KnownFilterMatrices.DeuteranopiaFilter) { - /// - /// Initializes a new instance of the class. - /// - public DeuteranopiaProcessor() - : base(KnownFilterMatrices.DeuteranopiaFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index 19ca6526d7..bb11918159 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -3,27 +3,26 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Defines a free-form color filter by a . +/// +public class FilterProcessor : IImageProcessor { /// - /// Defines a free-form color filter by a . + /// Initializes a new instance of the class. /// - public class FilterProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The matrix used to apply the image filter - public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; + /// The matrix used to apply the image filter + public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix; - /// - /// Gets the used to apply the image filter. - /// - public ColorMatrix Matrix { get; } + /// + /// Gets the used to apply the image filter. + /// + public ColorMatrix Matrix { get; } - /// - public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new FilterProcessor(configuration, this, source, sourceRectangle); - } + /// + public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 607821b544..e61b528efb 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Provides methods that accept a matrix to apply free-form filters to images. +/// +/// The pixel format. +internal class FilterProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly FilterProcessor definition; + /// - /// Provides methods that accept a matrix to apply free-form filters to images. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class FilterProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public FilterProcessor(Configuration configuration, FilterProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly FilterProcessor definition; + this.definition = definition; + } - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public FilterProcessor(Configuration configuration, FilterProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest.X, source.PixelBuffer, this.definition.Matrix, this.Configuration); - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source.PixelBuffer, this.definition.Matrix, this.Configuration); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); - } + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly Buffer2D source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + Buffer2D source, + ColorMatrix matrix, + Configuration configuration) { - private readonly int startX; - private readonly Buffer2D source; - private readonly ColorMatrix matrix; - private readonly Configuration configuration; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - int startX, - Buffer2D source, - ColorMatrix matrix, - Configuration configuration) - { - this.startX = startX; - this.source = source; - this.matrix = matrix; - this.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.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); - ColorNumerics.Transform(span, ref Unsafe.AsRef(this.matrix)); + ColorNumerics.Transform(span, ref Unsafe.AsRef(this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs index 9cebc88778..957ce37655 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt601Processor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.601 +/// +public sealed class GrayscaleBt601Processor : FilterProcessor { /// - /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.601 + /// Initializes a new instance of the class. /// - public sealed class GrayscaleBt601Processor : FilterProcessor + /// The proportion of the conversion. Must be between 0 and 1. + public GrayscaleBt601Processor(float amount) + : base(KnownFilterMatrices.CreateGrayscaleBt601Filter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// The proportion of the conversion. Must be between 0 and 1. - public GrayscaleBt601Processor(float amount) - : base(KnownFilterMatrices.CreateGrayscaleBt601Filter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index e2284c604e..adf1f4ec41 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 +/// +public sealed class GrayscaleBt709Processor : FilterProcessor { /// - /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 + /// Initializes a new instance of the class. /// - public sealed class GrayscaleBt709Processor : FilterProcessor + /// The proportion of the conversion. Must be between 0 and 1. + public GrayscaleBt709Processor(float amount) + : base(KnownFilterMatrices.CreateGrayscaleBt709Filter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// The proportion of the conversion. Must be between 0 and 1. - public GrayscaleBt709Processor(float amount) - : base(KnownFilterMatrices.CreateGrayscaleBt709Filter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion. + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs index 4fabbc4802..e6f2519878 100644 --- a/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/HueProcessor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a hue filter matrix using the given angle of rotation in degrees +/// +public sealed class HueProcessor : FilterProcessor { /// - /// Applies a hue filter matrix using the given angle of rotation in degrees + /// Initializes a new instance of the class. /// - public sealed class HueProcessor : FilterProcessor + /// The angle of rotation in degrees + public HueProcessor(float degrees) + : base(KnownFilterMatrices.CreateHueFilter(degrees)) { - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees - public HueProcessor(float degrees) - : base(KnownFilterMatrices.CreateHueFilter(degrees)) - { - this.Degrees = degrees; - } - - /// - /// Gets the angle of rotation in degrees - /// - public float Degrees { get; } + this.Degrees = degrees; } + + /// + /// Gets the angle of rotation in degrees + /// + public float Degrees { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs index fa476fc4c2..238df5ea06 100644 --- a/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/InvertProcessor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a filter matrix that inverts the colors of an image +/// +public sealed class InvertProcessor : FilterProcessor { /// - /// Applies a filter matrix that inverts the colors of an image + /// Initializes a new instance of the class. /// - public sealed class InvertProcessor : FilterProcessor + /// The proportion of the conversion. Must be between 0 and 1. + public InvertProcessor(float amount) + : base(KnownFilterMatrices.CreateInvertFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// The proportion of the conversion. Must be between 0 and 1. - public InvertProcessor(float amount) - : base(KnownFilterMatrices.CreateInvertFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs index 6aa1af89bf..9dcc94d536 100644 --- a/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/KodachromeProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a filter matrix recreating an old Kodachrome camera effect matrix to the image +/// +public sealed class KodachromeProcessor : FilterProcessor { /// - /// Applies a filter matrix recreating an old Kodachrome camera effect matrix to the image + /// Initializes a new instance of the class. /// - public sealed class KodachromeProcessor : FilterProcessor + public KodachromeProcessor() + : base(KnownFilterMatrices.KodachromeFilter) { - /// - /// Initializes a new instance of the class. - /// - public KodachromeProcessor() - : base(KnownFilterMatrices.KodachromeFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs index f1f243d0a6..a977d5102e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LightnessProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a lightness filter matrix using the given amount. +/// +public sealed class LightnessProcessor : FilterProcessor { /// - /// Applies a lightness filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class LightnessProcessor : FilterProcessor + /// + /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public LightnessProcessor(float amount) + : base(KnownFilterMatrices.CreateLightnessFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// - /// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results. - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public LightnessProcessor(float amount) - : base(KnownFilterMatrices.CreateLightnessFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index a0e721fadb..f15b93e6ca 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating an old Lomograph effect. +/// +public sealed class LomographProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating an old Lomograph effect. + /// Initializes a new instance of the class. /// - public sealed class LomographProcessor : FilterProcessor + /// Graphics options to use within the processor. + public LomographProcessor(GraphicsOptions graphicsOptions) + : base(KnownFilterMatrices.LomographFilter) { - /// - /// Initializes a new instance of the class. - /// - /// Graphics options to use within the processor. - public LomographProcessor(GraphicsOptions graphicsOptions) - : base(KnownFilterMatrices.LomographFilter) - { - this.GraphicsOptions = graphicsOptions; - } + this.GraphicsOptions = graphicsOptions; + } - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => - new LomographProcessor(configuration, this, source, sourceRectangle); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new LomographProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs index c3574354ce..4a02642fd6 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs @@ -4,35 +4,34 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating an old Lomograph effect. +/// +internal class LomographProcessor : FilterProcessor + where TPixel : unmanaged, IPixel { + private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); + private readonly LomographProcessor definition; + /// - /// Converts the colors of the image recreating an old Lomograph effect. + /// Initializes a new instance of the class. /// - internal class LomographProcessor : FilterProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public LomographProcessor(Configuration configuration, LomographProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, definition, source, sourceRectangle) { - private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); - private readonly LomographProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public LomographProcessor(Configuration configuration, LomographProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - this.definition = definition; - } + this.definition = definition; + } - /// - protected override void AfterImageApply() - { - new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle); - base.AfterImageApply(); - } + /// + protected override void AfterImageApply() + { + new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkGreen).Execute(this.Configuration, this.Source, this.SourceRectangle); + base.AfterImageApply(); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs index 64730c678c..5654f998db 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpacityProcessor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies an opacity filter matrix using the given amount. +/// +public sealed class OpacityProcessor : FilterProcessor { /// - /// Applies an opacity filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class OpacityProcessor : FilterProcessor + /// The proportion of the conversion. Must be between 0 and 1. + public OpacityProcessor(float amount) + : base(KnownFilterMatrices.CreateOpacityFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// The proportion of the conversion. Must be between 0 and 1. - public OpacityProcessor(float amount) - : base(KnownFilterMatrices.CreateOpacityFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion. - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion. + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 7cc115137d..30ffa7899d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,60 +8,59 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +internal sealed class OpaqueProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { - internal sealed class OpaqueProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + public OpaqueProcessor( + Configuration configuration, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - 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()); + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); + ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); + } - var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); - ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); + private readonly struct OpaqueRowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Buffer2D target; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public OpaqueRowOperation( + Configuration configuration, + Buffer2D target, + Rectangle bounds) + { + this.configuration = configuration; + this.target = target; + this.bounds = bounds; } - private readonly struct OpaqueRowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - private readonly Configuration configuration; - private readonly Buffer2D target; - private readonly Rectangle bounds; + Span targetRowSpan = this.target.DangerousGetRowSpan(y)[this.bounds.X..]; + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); - [MethodImpl(InliningOptions.ShortMethod)] - public OpaqueRowOperation( - Configuration configuration, - Buffer2D target, - Rectangle bounds) + for (int x = 0; x < this.bounds.Width; x++) { - this.configuration = configuration; - this.target = target; - this.bounds = bounds; + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.W = 1F; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - Span targetRowSpan = this.target.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..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); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index 10d6b4ba6d..a42a693635 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating an old Polaroid effect. +/// +public sealed class PolaroidProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating an old Polaroid effect. + /// Initializes a new instance of the class. /// - public sealed class PolaroidProcessor : FilterProcessor + /// Graphics options to use within the processor. + public PolaroidProcessor(GraphicsOptions graphicsOptions) + : base(KnownFilterMatrices.PolaroidFilter) { - /// - /// Initializes a new instance of the class. - /// - /// Graphics options to use within the processor. - public PolaroidProcessor(GraphicsOptions graphicsOptions) - : base(KnownFilterMatrices.PolaroidFilter) - { - this.GraphicsOptions = graphicsOptions; - } + this.GraphicsOptions = graphicsOptions; + } - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => - new PolaroidProcessor(configuration, this, source, sourceRectangle); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => + new PolaroidProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs index cb0a73165c..c1b79d10a1 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs @@ -4,37 +4,36 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating an old Polaroid effect. +/// +internal class PolaroidProcessor : FilterProcessor + 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); + private readonly PolaroidProcessor definition; + /// - /// Converts the colors of the image recreating an old Polaroid effect. + /// Initializes a new instance of the class. /// - internal class PolaroidProcessor : FilterProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, definition, source, sourceRectangle) { - private static readonly Color LightOrange = Color.FromRgba(255, 153, 102, 128); - private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0); - private readonly PolaroidProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PolaroidProcessor(Configuration configuration, PolaroidProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - this.definition = definition; - } + this.definition = definition; + } - /// - protected override void AfterImageApply() - { - new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle); - new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle); - base.AfterImageApply(); - } + /// + protected override void AfterImageApply() + { + new VignetteProcessor(this.definition.GraphicsOptions, VeryDarkOrange).Execute(this.Configuration, this.Source, this.SourceRectangle); + new GlowProcessor(this.definition.GraphicsOptions, LightOrange, this.Source.Width / 4F).Execute(this.Configuration, this.Source, this.SourceRectangle); + base.AfterImageApply(); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs index a946715b52..81821b8bd6 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanomalyProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Protanomaly (Red-Weak) color blindness. +/// +public sealed class ProtanomalyProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Protanomaly (Red-Weak) color blindness. + /// Initializes a new instance of the class. /// - public sealed class ProtanomalyProcessor : FilterProcessor + public ProtanomalyProcessor() + : base(KnownFilterMatrices.ProtanomalyFilter) { - /// - /// Initializes a new instance of the class. - /// - public ProtanomalyProcessor() - : base(KnownFilterMatrices.ProtanomalyFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs index 53085a12c2..7f402cc58e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/ProtanopiaProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. +/// +public sealed class ProtanopiaProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. + /// Initializes a new instance of the class. /// - public sealed class ProtanopiaProcessor : FilterProcessor + public ProtanopiaProcessor() + : base(KnownFilterMatrices.ProtanopiaFilter) { - /// - /// Initializes a new instance of the class. - /// - public ProtanopiaProcessor() - : base(KnownFilterMatrices.ProtanopiaFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs index bdaa9c4d10..095cb76750 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SaturateProcessor.cs @@ -1,30 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a saturation filter matrix using the given amount. +/// +public sealed class SaturateProcessor : FilterProcessor { /// - /// Applies a saturation filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class SaturateProcessor : FilterProcessor + /// + /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results + /// + /// The proportion of the conversion. Must be greater than or equal to 0. + public SaturateProcessor(float amount) + : base(KnownFilterMatrices.CreateSaturateFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// - /// A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. - /// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results - /// - /// The proportion of the conversion. Must be greater than or equal to 0. - public SaturateProcessor(float amount) - : base(KnownFilterMatrices.CreateSaturateFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs index b6d7532601..12b9fb8a5d 100644 --- a/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/SepiaProcessor.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Applies a sepia filter matrix using the given amount. +/// +public sealed class SepiaProcessor : FilterProcessor { /// - /// Applies a sepia filter matrix using the given amount. + /// Initializes a new instance of the class. /// - public sealed class SepiaProcessor : FilterProcessor + /// The proportion of the conversion. Must be between 0 and 1. + public SepiaProcessor(float amount) + : base(KnownFilterMatrices.CreateSepiaFilter(amount)) { - /// - /// Initializes a new instance of the class. - /// - /// The proportion of the conversion. Must be between 0 and 1. - public SepiaProcessor(float amount) - : base(KnownFilterMatrices.CreateSepiaFilter(amount)) - { - this.Amount = amount; - } - - /// - /// Gets the proportion of the conversion - /// - public float Amount { get; } + this.Amount = amount; } + + /// + /// Gets the proportion of the conversion + /// + public float Amount { get; } } diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs index ef6c30bbee..700e1cdfff 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanomalyProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. +/// +public sealed class TritanomalyProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. + /// Initializes a new instance of the class. /// - public sealed class TritanomalyProcessor : FilterProcessor + public TritanomalyProcessor() + : base(KnownFilterMatrices.TritanomalyFilter) { - /// - /// Initializes a new instance of the class. - /// - public TritanomalyProcessor() - : base(KnownFilterMatrices.TritanomalyFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs index 39fd79c3ff..4f1a7885e9 100644 --- a/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/TritanopiaProcessor.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Processing.Processors.Filters; + +/// +/// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. +/// +public sealed class TritanopiaProcessor : FilterProcessor { /// - /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. + /// Initializes a new instance of the class. /// - public sealed class TritanopiaProcessor : FilterProcessor + public TritanopiaProcessor() + : base(KnownFilterMatrices.TritanopiaFilter) { - /// - /// Initializes a new instance of the class. - /// - public TritanopiaProcessor() - : base(KnownFilterMatrices.TritanopiaFilter) - { - } } } diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs index 2cd6b090d5..a4ca48f793 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs @@ -3,25 +3,24 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// Defines an algorithm to alter the pixels of a cloned image. +/// +public interface ICloningImageProcessor : IImageProcessor { /// - /// Defines an algorithm to alter the pixels of a cloned image. + /// Creates a pixel specific that is capable of executing + /// the processing algorithm on an . /// - public interface ICloningImageProcessor : IImageProcessor - { - /// - /// Creates a pixel specific that is capable of executing - /// the processing algorithm on an . - /// - /// The pixel type. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; - } + /// The pixel type. + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs index eefcf32c28..26b8402676 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs @@ -3,19 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// Implements an algorithm to alter the pixels of a cloned image. +/// +/// The pixel format. +public interface ICloningImageProcessor : IImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Implements an algorithm to alter the pixels of a cloned image. + /// Clones the specified and executes the process against the clone. /// - /// The pixel format. - public interface ICloningImageProcessor : IImageProcessor - where TPixel : unmanaged, IPixel - { - /// - /// Clones the specified and executes the process against the clone. - /// - /// The . - Image CloneAndExecute(); - } + /// The . + Image CloneAndExecute(); } diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor.cs b/src/ImageSharp/Processing/Processors/IImageProcessor.cs index 33078f63b2..8b9ded23e7 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -3,28 +3,27 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// Defines an algorithm to alter the pixels of an image. +/// Non-generic implementations are responsible for: +/// 1. Encapsulating the parameters of the algorithm. +/// 2. Creating the generic instance to execute the algorithm. +/// +public interface IImageProcessor { /// - /// Defines an algorithm to alter the pixels of an image. - /// Non-generic implementations are responsible for: - /// 1. Encapsulating the parameters of the algorithm. - /// 2. Creating the generic instance to execute the algorithm. + /// Creates a pixel specific that is capable of executing + /// the processing algorithm on an . /// - public interface IImageProcessor - { - /// - /// Creates a pixel specific that is capable of executing - /// the processing algorithm on an . - /// - /// The pixel type. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; - } + /// The pixel type. + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs index f19263f32b..8f2df26d74 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs @@ -1,21 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// Implements an algorithm to alter the pixels of an image. +/// +/// The pixel format. +public interface IImageProcessor : IDisposable + where TPixel : unmanaged, IPixel { /// - /// Implements an algorithm to alter the pixels of an image. + /// Executes the process against the specified . /// - /// The pixel format. - public interface IImageProcessor : IDisposable - where TPixel : unmanaged, IPixel - { - /// - /// Executes the process against the specified . - /// - void Execute(); - } + void Execute(); } diff --git a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs index f41f7280ee..2c814e2a10 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs @@ -4,40 +4,39 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +internal static class ImageProcessorExtensions { - internal static class ImageProcessorExtensions + /// + /// Executes the processor against the given source image and rectangle bounds. + /// + /// The processor. + /// The configuration which allows altering default behaviour or extending the library. + /// The source image. + /// The source bounds. + public static void Execute(this IImageProcessor processor, Configuration configuration, Image source, Rectangle sourceRectangle) + => source.AcceptVisitor(new ExecuteVisitor(configuration, processor, sourceRectangle)); + + private class ExecuteVisitor : IImageVisitor { - /// - /// Executes the processor against the given source image and rectangle bounds. - /// - /// The processor. - /// The configuration which allows altering default behaviour or extending the library. - /// The source image. - /// The source bounds. - public static void Execute(this IImageProcessor processor, Configuration configuration, Image source, Rectangle sourceRectangle) - => source.AcceptVisitor(new ExecuteVisitor(configuration, processor, sourceRectangle)); + private readonly Configuration configuration; + private readonly IImageProcessor processor; + private readonly Rectangle sourceRectangle; - private class ExecuteVisitor : IImageVisitor + public ExecuteVisitor(Configuration configuration, IImageProcessor processor, Rectangle sourceRectangle) { - private readonly Configuration configuration; - private readonly IImageProcessor processor; - private readonly Rectangle sourceRectangle; - - public ExecuteVisitor(Configuration configuration, IImageProcessor processor, Rectangle sourceRectangle) - { - this.configuration = configuration; - this.processor = processor; - this.sourceRectangle = sourceRectangle; - } + this.configuration = configuration; + this.processor = processor; + this.sourceRectangle = sourceRectangle; + } - public void Visit(Image image) - where TPixel : unmanaged, IPixel + public void Visit(Image image) + where TPixel : unmanaged, IPixel + { + using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(this.configuration, image, this.sourceRectangle)) { - using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(this.configuration, image, this.sourceRectangle)) - { - processorImpl.Execute(); - } + processorImpl.Execute(); } } } diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index 86a1f2a64b..2fa79220e5 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -1,121 +1,119 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors +namespace SixLabors.ImageSharp.Processing.Processors; + +/// +/// The base class for all pixel specific image processors. +/// Allows the application of processing algorithms to the image. +/// +/// The pixel format. +public abstract class ImageProcessor : IImageProcessor + where TPixel : unmanaged, IPixel { /// - /// The base class for all pixel specific image processors. - /// Allows the application of processing algorithms to the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - public abstract class ImageProcessor : IImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + protected ImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected ImageProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - this.Configuration = configuration; - this.Source = source; - this.SourceRectangle = sourceRectangle; - } + this.Configuration = configuration; + this.Source = source; + this.SourceRectangle = sourceRectangle; + } + + /// + /// Gets The source for the current processor instance. + /// + protected Image Source { get; } - /// - /// Gets The source for the current processor instance. - /// - protected Image Source { get; } + /// + /// Gets The source area to process for the current processor instance. + /// + protected Rectangle SourceRectangle { get; } - /// - /// Gets The source area to process for the current processor instance. - /// - protected Rectangle SourceRectangle { get; } + /// + /// Gets the instance to use when performing operations. + /// + protected Configuration Configuration { get; } - /// - /// Gets the instance to use when performing operations. - /// - protected Configuration Configuration { get; } + /// + void IImageProcessor.Execute() + { + this.BeforeImageApply(); - /// - void IImageProcessor.Execute() + foreach (ImageFrame sourceFrame in this.Source.Frames) { - this.BeforeImageApply(); + this.Apply(sourceFrame); + } - foreach (ImageFrame sourceFrame in this.Source.Frames) - { - this.Apply(sourceFrame); - } + this.AfterImageApply(); + } - this.AfterImageApply(); - } + /// + /// Applies the processor to a single image frame. + /// + /// the source image. + public void Apply(ImageFrame source) + { + this.BeforeFrameApply(source); + this.OnFrameApply(source); + this.AfterFrameApply(source); + } - /// - /// Applies the processor to a single image frame. - /// - /// the source image. - public void Apply(ImageFrame source) - { - this.BeforeFrameApply(source); - this.OnFrameApply(source); - this.AfterFrameApply(source); - } + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } + /// + /// This method is called before the process is applied to prepare the processor. + /// + protected virtual void BeforeImageApply() + { + } - /// - /// This method is called before the process is applied to prepare the processor. - /// - protected virtual void BeforeImageApply() - { - } + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + protected virtual void BeforeFrameApply(ImageFrame source) + { + } - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - protected virtual void BeforeFrameApply(ImageFrame source) - { - } + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The source image. Cannot be null. + protected abstract void OnFrameApply(ImageFrame source); - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The source image. Cannot be null. - protected abstract void OnFrameApply(ImageFrame source); - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The source image. Cannot be null. - protected virtual void AfterFrameApply(ImageFrame source) - { - } + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The source image. Cannot be null. + protected virtual void AfterFrameApply(ImageFrame source) + { + } - /// - /// This method is called after the process is applied to prepare the processor. - /// - protected virtual void AfterImageApply() - { - } + /// + /// This method is called after the process is applied to prepare the processor. + /// + protected virtual void AfterImageApply() + { + } - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - } + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose managed and unmanaged objects. + protected virtual void Dispose(bool disposing) + { } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs index 0730a335d2..3f80d47f6c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor.cs @@ -1,43 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. +/// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. +/// +public class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor { /// - /// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. - /// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. + /// Initializes a new instance of the class. /// - public class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - public AdaptiveHistogramEqualizationProcessor( - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimit) => this.NumberOfTiles = numberOfTiles; + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationProcessor( + int luminanceLevels, + bool clipHistogram, + int clipLimit, + int numberOfTiles) + : base(luminanceLevels, clipHistogram, clipLimit) => this.NumberOfTiles = numberOfTiles; - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - public int NumberOfTiles { get; } + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + public int NumberOfTiles { get; } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AdaptiveHistogramEqualizationProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - this.NumberOfTiles, - source, - sourceRectangle); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new AdaptiveHistogramEqualizationProcessor( + configuration, + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimit, + this.NumberOfTiles, + source, + sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 518b19c4ca..e2272db039 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,367 +9,554 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. +/// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. +/// +/// The pixel format. +internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies an adaptive histogram equalization to the image. The image is split up in tiles. For each tile a cumulative distribution function (cdf) is calculated. - /// To calculate the final equalized pixel value, the cdf value of four adjacent tiles will be interpolated. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AdaptiveHistogramEqualizationProcessor( + Configuration configuration, + int luminanceLevels, + bool clipHistogram, + int clipLimit, + int tiles, + Image source, + Rectangle sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AdaptiveHistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int tiles, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); - Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); - this.Tiles = tiles; - } + this.Tiles = tiles; + } - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - private int Tiles { get; } + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } - /// - protected override void OnFrameApply(ImageFrame source) + /// + protected override void OnFrameApply(ImageFrame source) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); + int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); + int tileCount = this.Tiles; + int halfTileWidth = tileWidth / 2; + int halfTileHeight = tileHeight / 2; + int luminanceLevels = this.LuminanceLevels; + + // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. + using (var cdfData = new CdfTileData(this.Configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); - int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); - int tileCount = this.Tiles; - int halfTileWidth = tileWidth / 2; - int halfTileHeight = tileHeight / 2; - int luminanceLevels = this.LuminanceLevels; - - // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. - using (var cdfData = new CdfTileData(this.Configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) + cdfData.CalculateLookupTables(source, this); + + var tileYStartPositions = new List<(int Y, int CdfY)>(); + int cdfY = 0; + int yStart = halfTileHeight; + for (int tile = 0; tile < tileCount - 1; tile++) { - cdfData.CalculateLookupTables(source, this); + tileYStartPositions.Add((yStart, cdfY)); + cdfY++; + yStart += tileHeight; + } - var tileYStartPositions = new List<(int Y, int CdfY)>(); - int cdfY = 0; - int yStart = halfTileHeight; - for (int tile = 0; tile < tileCount - 1; tile++) - { - tileYStartPositions.Add((yStart, cdfY)); - cdfY++; - yStart += tileHeight; - } + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source.PixelBuffer); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); - var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source.PixelBuffer); - ParallelRowIterator.IterateRowIntervals( - this.Configuration, - new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), - in operation); + // Fix left column + ProcessBorderColumn(source.PixelBuffer, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); - // Fix left column - ProcessBorderColumn(source.PixelBuffer, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); + // Fix right column + int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; + ProcessBorderColumn(source.PixelBuffer, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); - // Fix right column - int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; - ProcessBorderColumn(source.PixelBuffer, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + // Fix top row + ProcessBorderRow(source.PixelBuffer, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - // Fix top row - ProcessBorderRow(source.PixelBuffer, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + // Fix bottom row + int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; + ProcessBorderRow(source.PixelBuffer, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); - // Fix bottom row - int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; - ProcessBorderRow(source.PixelBuffer, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + // Left top corner + ProcessCornerTile(source.PixelBuffer, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - // Left top corner - ProcessCornerTile(source.PixelBuffer, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + // Left bottom corner + ProcessCornerTile(source.PixelBuffer, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); - // Left bottom corner - ProcessCornerTile(source.PixelBuffer, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + // Right top corner + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); - // Right top corner - ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + // Right bottom corner + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + } + } - // Right bottom corner - ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + /// + /// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation. + /// + /// The source image. + /// The lookup table to remap the grey values. + /// The x-position in the CDF lookup map. + /// The y-position in the CDF lookup map. + /// X start position. + /// X end position. + /// Y start position. + /// Y end position. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessCornerTile( + Buffer2D source, + CdfTileData cdfData, + int cdfX, + int cdfY, + int xStart, + int xEnd, + int yStart, + int yEnd, + int luminanceLevels) + { + for (int dy = yStart; dy < yEnd; dy++) + { + Span rowSpan = source.DangerousGetRowSpan(dy); + for (int dx = xStart; dx < xEnd; dx++) + { + ref TPixel pixel = ref rowSpan[dx]; + float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); } } + } - /// - /// Processes the part of a corner tile which was previously left out. It consists of 1 / 4 of a tile and does not need interpolation. - /// - /// The source image. - /// The lookup table to remap the grey values. - /// The x-position in the CDF lookup map. - /// The y-position in the CDF lookup map. - /// X start position. - /// X end position. - /// Y start position. - /// Y end position. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessCornerTile( - Buffer2D source, - CdfTileData cdfData, - int cdfX, - int cdfY, - int xStart, - int xEnd, - int yStart, - int yEnd, - int luminanceLevels) + /// + /// Processes a border column of the image which is half the size of the tile width. + /// + /// The source image. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The X index of the lookup table to use. + /// The source image height. + /// The number of vertical tiles. + /// The height of a tile. + /// X start position in the image. + /// X end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderColumn( + Buffer2D source, + CdfTileData cdfData, + int cdfX, + int sourceHeight, + int tileCount, + int tileHeight, + int xStart, + int xEnd, + int luminanceLevels) + { + int halfTileHeight = tileHeight / 2; + + int cdfY = 0; + int y = halfTileHeight; + for (int tile = 0; tile < tileCount - 1; tile++) { - for (int dy = yStart; dy < yEnd; dy++) + int yLimit = Math.Min(y + tileHeight, sourceHeight - 1); + int tileY = 0; + for (int dy = y; dy < yLimit; dy++) { Span rowSpan = source.DangerousGetRowSpan(dy); for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); } + + tileY++; } + + cdfY++; + y += tileHeight; } + } - /// - /// Processes a border column of the image which is half the size of the tile width. - /// - /// The source image. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The X index of the lookup table to use. - /// The source image height. - /// The number of vertical tiles. - /// The height of a tile. - /// X start position in the image. - /// X end position of the image. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessBorderColumn( - Buffer2D source, - CdfTileData cdfData, - int cdfX, - int sourceHeight, - int tileCount, - int tileHeight, - int xStart, - int xEnd, - int luminanceLevels) - { - int halfTileHeight = tileHeight / 2; + /// + /// Processes a border row of the image which is half of the size of the tile height. + /// + /// The source image. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The Y index of the lookup table to use. + /// The source image width. + /// The number of horizontal tiles. + /// The width of a tile. + /// Y start position in the image. + /// Y end position of the image. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + private static void ProcessBorderRow( + Buffer2D source, + CdfTileData cdfData, + int cdfY, + int sourceWidth, + int tileCount, + int tileWidth, + int yStart, + int yEnd, + int luminanceLevels) + { + int halfTileWidth = tileWidth / 2; - int cdfY = 0; - int y = halfTileHeight; - for (int tile = 0; tile < tileCount - 1; tile++) + int cdfX = 0; + int x = halfTileWidth; + for (int tile = 0; tile < tileCount - 1; tile++) + { + for (int dy = yStart; dy < yEnd; dy++) { - int yLimit = Math.Min(y + tileHeight, sourceHeight - 1); - int tileY = 0; - for (int dy = y; dy < yLimit; dy++) + Span rowSpan = source.DangerousGetRowSpan(dy); + int tileX = 0; + int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); + for (int dx = x; dx < xLimit; dx++) { - Span rowSpan = source.DangerousGetRowSpan(dy); - for (int dx = xStart; dx < xEnd; dx++) - { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - - tileY++; + ref TPixel pixel = ref rowSpan[dx]; + float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; } - - cdfY++; - y += tileHeight; } + + cdfX++; + x += tileWidth; } + } - /// - /// Processes a border row of the image which is half of the size of the tile height. - /// - /// The source image. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The Y index of the lookup table to use. - /// The source image width. - /// The number of horizontal tiles. - /// The width of a tile. - /// Y start position in the image. - /// Y end position of the image. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - private static void ProcessBorderRow( - Buffer2D source, + /// + /// Bilinear interpolation between four adjacent tiles. + /// + /// The pixel to remap the grey value from. + /// The pre-computed lookup tables to remap the grey values for each tiles. + /// The number of tiles in the x-direction. + /// The number of tiles in the y-direction. + /// X position inside the tile. + /// Y position inside the tile. + /// X index of the top left lookup table to use. + /// Y index of the top left lookup table to use. + /// Width of one tile in pixels. + /// Height of one tile in pixels. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenFourTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileCountX, + int tileCountY, + int tileX, + int tileY, + int cdfX, + int cdfY, + int tileWidth, + int tileHeight, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tileX / (float)(tileWidth - 1); + float ty = tileY / (float)(tileHeight - 1); + + int yTop = cdfY; + int yBottom = Math.Min(tileCountY - 1, yTop + 1); + int xLeft = cdfX; + int xRight = Math.Min(tileCountX - 1, xLeft + 1); + + float cdfLeftTopLuminance = cdfData.RemapGreyValue(xLeft, yTop, luminance); + float cdfRightTopLuminance = cdfData.RemapGreyValue(xRight, yTop, luminance); + float cdfLeftBottomLuminance = cdfData.RemapGreyValue(xLeft, yBottom, luminance); + float cdfRightBottomLuminance = cdfData.RemapGreyValue(xRight, yBottom, luminance); + return BilinearInterpolation(tx, ty, cdfLeftTopLuminance, cdfRightTopLuminance, cdfLeftBottomLuminance, cdfRightBottomLuminance); + } + + /// + /// Linear interpolation between two tiles. + /// + /// The pixel to remap the grey value from. + /// The CDF lookup map. + /// X position inside the first tile. + /// Y position inside the first tile. + /// X position inside the second tile. + /// Y position inside the second tile. + /// Position inside the tile. + /// Width of the tile. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// A re-mapped grey value. + [MethodImpl(InliningOptions.ShortMethod)] + private static float InterpolateBetweenTwoTiles( + TPixel sourcePixel, + CdfTileData cdfData, + int tileX1, + int tileY1, + int tileX2, + int tileY2, + int tilePos, + int tileWidth, + int luminanceLevels) + { + int luminance = GetLuminance(sourcePixel, luminanceLevels); + float tx = tilePos / (float)(tileWidth - 1); + + float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); + float cdfLuminance2 = cdfData.RemapGreyValue(tileX2, tileY2, luminance); + return LinearInterpolation(cdfLuminance1, cdfLuminance2, tx); + } + + /// + /// Bilinear interpolation between four tiles. + /// + /// The interpolation value in x direction in the range of [0, 1]. + /// The interpolation value in y direction in the range of [0, 1]. + /// Luminance from top left tile. + /// Luminance from right top tile. + /// Luminance from left bottom tile. + /// Luminance from right bottom tile. + /// Interpolated Luminance. + [MethodImpl(InliningOptions.ShortMethod)] + private static float BilinearInterpolation(float tx, float ty, float lt, float rt, float lb, float rb) + => LinearInterpolation(LinearInterpolation(lt, rt, tx), LinearInterpolation(lb, rb, tx), ty); + + /// + /// Linear interpolation between two grey values. + /// + /// The left value. + /// The right value. + /// The interpolation value between the two values in the range of [0, 1]. + /// The interpolated value. + [MethodImpl(InliningOptions.ShortMethod)] + 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 Buffer2D source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( CdfTileData cdfData, - int cdfY, - int sourceWidth, - int tileCount, + List<(int Y, int CdfY)> tileYStartPositions, int tileWidth, - int yStart, - int yEnd, - int luminanceLevels) + int tileHeight, + int tileCount, + int halfTileWidth, + int luminanceLevels, + Buffer2D source) { - int halfTileWidth = tileWidth / 2; + 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; + } - int cdfX = 0; - int x = halfTileWidth; - for (int tile = 0; tile < tileCount - 1; tile++) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int index = rows.Min; index < rows.Max; index++) { - for (int dy = yStart; dy < yEnd; dy++) + (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++) { - Span rowSpan = source.DangerousGetRowSpan(dy); - int tileX = 0; - int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); - for (int dx = x; dx < xLimit; dx++) + 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++) { - ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; + Span rowSpan = this.source.DangerousGetRowSpan(dy); + int tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref rowSpan[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 += tileWidth; + cdfX++; + x += this.tileWidth; + } } } + } + + /// + /// Contains the results of the cumulative distribution function for all tiles. + /// + private sealed class CdfTileData : IDisposable + { + private readonly Configuration configuration; + private readonly MemoryAllocator memoryAllocator; /// - /// Bilinear interpolation between four adjacent tiles. + /// Used for storing the minimum value for each CDF entry. /// - /// The pixel to remap the grey value from. - /// The pre-computed lookup tables to remap the grey values for each tiles. - /// The number of tiles in the x-direction. - /// The number of tiles in the y-direction. - /// X position inside the tile. - /// Y position inside the tile. - /// X index of the top left lookup table to use. - /// Y index of the top left lookup table to use. - /// Width of one tile in pixels. - /// Height of one tile in pixels. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// A re-mapped grey value. - [MethodImpl(InliningOptions.ShortMethod)] - private static float InterpolateBetweenFourTiles( - TPixel sourcePixel, - CdfTileData cdfData, + private readonly Buffer2D cdfMinBuffer2D; + + /// + /// Used for storing the LUT for each CDF entry. + /// + private readonly Buffer2D cdfLutBuffer2D; + private readonly int pixelsInTile; + private readonly int sourceWidth; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly List<(int Y, int CdfY)> tileYStartPositions; + + public CdfTileData( + Configuration configuration, + int sourceWidth, + int sourceHeight, int tileCountX, int tileCountY, - int tileX, - int tileY, - int cdfX, - int cdfY, int tileWidth, int tileHeight, int luminanceLevels) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); - float tx = tileX / (float)(tileWidth - 1); - float ty = tileY / (float)(tileHeight - 1); - - int yTop = cdfY; - int yBottom = Math.Min(tileCountY - 1, yTop + 1); - int xLeft = cdfX; - int xRight = Math.Min(tileCountX - 1, xLeft + 1); - - float cdfLeftTopLuminance = cdfData.RemapGreyValue(xLeft, yTop, luminance); - float cdfRightTopLuminance = cdfData.RemapGreyValue(xRight, yTop, luminance); - float cdfLeftBottomLuminance = cdfData.RemapGreyValue(xLeft, yBottom, luminance); - float cdfRightBottomLuminance = cdfData.RemapGreyValue(xRight, yBottom, luminance); - return BilinearInterpolation(tx, ty, cdfLeftTopLuminance, cdfRightTopLuminance, cdfLeftBottomLuminance, cdfRightBottomLuminance); + this.configuration = configuration; + this.memoryAllocator = configuration.MemoryAllocator; + this.luminanceLevels = luminanceLevels; + this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); + this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); + this.sourceWidth = sourceWidth; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.pixelsInTile = tileWidth * tileHeight; + + // Calculate the start positions and rent buffers. + this.tileYStartPositions = new List<(int Y, int CdfY)>(); + int cdfY = 0; + for (int y = 0; y < sourceHeight; y += tileHeight) + { + this.tileYStartPositions.Add((y, cdfY)); + cdfY++; + } } - /// - /// Linear interpolation between two tiles. - /// - /// The pixel to remap the grey value from. - /// The CDF lookup map. - /// X position inside the first tile. - /// Y position inside the first tile. - /// X position inside the second tile. - /// Y position inside the second tile. - /// Position inside the tile. - /// Width of the tile. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// A re-mapped grey value. - [MethodImpl(InliningOptions.ShortMethod)] - private static float InterpolateBetweenTwoTiles( - TPixel sourcePixel, - CdfTileData cdfData, - int tileX1, - int tileY1, - int tileX2, - int tileY2, - int tilePos, - int tileWidth, - int luminanceLevels) + public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); - float tx = tilePos / (float)(tileWidth - 1); - - float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); - float cdfLuminance2 = cdfData.RemapGreyValue(tileX2, tileY2, luminance); - return LinearInterpolation(cdfLuminance1, cdfLuminance2, tx); + var operation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source.PixelBuffer); + + ParallelRowIterator.IterateRowIntervals( + this.configuration, + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } - /// - /// Bilinear interpolation between four tiles. - /// - /// The interpolation value in x direction in the range of [0, 1]. - /// The interpolation value in y direction in the range of [0, 1]. - /// Luminance from top left tile. - /// Luminance from right top tile. - /// Luminance from left bottom tile. - /// Luminance from right bottom tile. - /// Interpolated Luminance. [MethodImpl(InliningOptions.ShortMethod)] - private static float BilinearInterpolation(float tx, float ty, float lt, float rt, float lb, float rb) - => LinearInterpolation(LinearInterpolation(lt, rt, tx), LinearInterpolation(lb, rb, tx), ty); + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.DangerousGetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); /// - /// Linear interpolation between two grey values. + /// Remaps the grey value with the cdf. /// - /// The left value. - /// The right value. - /// The interpolation value between the two values in the range of [0, 1]. - /// The interpolated value. + /// The tiles x-position. + /// The tiles y-position. + /// The original luminance. + /// The remapped luminance. [MethodImpl(InliningOptions.ShortMethod)] - private static float LinearInterpolation(float left, float right, float t) - => left + ((right - left) * t); + public float RemapGreyValue(int tilesX, int tilesY, int luminance) + { + int cdfMin = this.cdfMinBuffer2D[tilesX, tilesY]; + Span cdfSpan = this.GetCdfLutSpan(tilesX, tilesY); + return (this.pixelsInTile - cdfMin) == 0 + ? cdfSpan[luminance] / this.pixelsInTile + : cdfSpan[luminance] / (float)(this.pixelsInTile - cdfMin); + } + + public void Dispose() + { + this.cdfMinBuffer2D.Dispose(); + this.cdfLutBuffer2D.Dispose(); + } private readonly struct RowIntervalOperation : IRowIntervalOperation { - private readonly CdfTileData cdfData; + 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 tileCount; - private readonly int halfTileWidth; private readonly int luminanceLevels; private readonly Buffer2D source; private readonly int sourceWidth; @@ -379,21 +564,23 @@ private static float LinearInterpolation(float left, float right, float t) [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( - CdfTileData cdfData, + HistogramEqualizationProcessor processor, + MemoryAllocator allocator, + Buffer2D cdfMinBuffer2D, + Buffer2D cdfLutBuffer2D, List<(int Y, int CdfY)> tileYStartPositions, int tileWidth, int tileHeight, - int tileCount, - int halfTileWidth, int luminanceLevels, Buffer2D source) { - this.cdfData = cdfData; + this.processor = processor; + this.allocator = allocator; + this.cdfMinBuffer2D = cdfMinBuffer2D; + this.cdfLutBuffer2D = cdfLutBuffer2D; 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; @@ -406,232 +593,42 @@ public void Invoke(in RowInterval rows) { 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 cdfY = this.tileYStartPositions[index].CdfY; + int y = this.tileYStartPositions[index].Y; + int endY = Math.Min(y + this.tileHeight, this.sourceHeight); + Span cdfMinSpan = this.cdfMinBuffer2D.DangerousGetRowSpan(cdfY); + cdfMinSpan.Clear(); + + 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) { - 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++) + histogram.Clear(); + Span cdfLutSpan = this.cdfLutBuffer2D.DangerousGetRowSpan(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++) { Span rowSpan = this.source.DangerousGetRowSpan(dy); - int tileX = 0; - for (int dx = x; dx < xEnd; dx++) + for (int dx = x; dx < xlimit; dx++) { - ref TPixel pixel = ref rowSpan[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++; + int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels); + histogram[luminance]++; } - - tileY++; } - cdfX++; - x += this.tileWidth; - } - } - } - } - - /// - /// Contains the results of the cumulative distribution function for all tiles. - /// - private sealed class CdfTileData : IDisposable - { - private readonly Configuration configuration; - private readonly MemoryAllocator memoryAllocator; - - /// - /// Used for storing the minimum value for each CDF entry. - /// - private readonly Buffer2D cdfMinBuffer2D; - - /// - /// Used for storing the LUT for each CDF entry. - /// - private readonly Buffer2D cdfLutBuffer2D; - private readonly int pixelsInTile; - private readonly int sourceWidth; - private readonly int tileWidth; - private readonly int tileHeight; - private readonly int luminanceLevels; - private readonly List<(int Y, int CdfY)> tileYStartPositions; - - public CdfTileData( - Configuration configuration, - int sourceWidth, - int sourceHeight, - int tileCountX, - int tileCountY, - int tileWidth, - int tileHeight, - int luminanceLevels) - { - this.configuration = configuration; - this.memoryAllocator = configuration.MemoryAllocator; - this.luminanceLevels = luminanceLevels; - this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); - this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); - this.sourceWidth = sourceWidth; - this.tileWidth = tileWidth; - this.tileHeight = tileHeight; - this.pixelsInTile = tileWidth * tileHeight; - - // Calculate the start positions and rent buffers. - this.tileYStartPositions = new List<(int Y, int CdfY)>(); - int cdfY = 0; - for (int y = 0; y < sourceHeight; y += tileHeight) - { - this.tileYStartPositions.Add((y, cdfY)); - cdfY++; - } - } - - public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) - { - var operation = new RowIntervalOperation( - processor, - this.memoryAllocator, - this.cdfMinBuffer2D, - this.cdfLutBuffer2D, - this.tileYStartPositions, - this.tileWidth, - this.tileHeight, - this.luminanceLevels, - source.PixelBuffer); - - ParallelRowIterator.IterateRowIntervals( - this.configuration, - new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), - in operation); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.DangerousGetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); - - /// - /// Remaps the grey value with the cdf. - /// - /// The tiles x-position. - /// The tiles y-position. - /// The original luminance. - /// The remapped luminance. - [MethodImpl(InliningOptions.ShortMethod)] - public float RemapGreyValue(int tilesX, int tilesY, int luminance) - { - int cdfMin = this.cdfMinBuffer2D[tilesX, tilesY]; - Span cdfSpan = this.GetCdfLutSpan(tilesX, tilesY); - return (this.pixelsInTile - cdfMin) == 0 - ? cdfSpan[luminance] / this.pixelsInTile - : cdfSpan[luminance] / (float)(this.pixelsInTile - cdfMin); - } - - public void Dispose() - { - 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 Buffer2D 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, - Buffer2D 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) - { - 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); - Span cdfMinSpan = this.cdfMinBuffer2D.DangerousGetRowSpan(cdfY); - cdfMinSpan.Clear(); - - 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) + if (this.processor.ClipHistogramEnabled) { - histogram.Clear(); - Span cdfLutSpan = this.cdfLutBuffer2D.DangerousGetRowSpan(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++) - { - Span rowSpan = this.source.DangerousGetRowSpan(dy); - for (int dx = x; dx < xlimit; dx++) - { - int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels); - histogram[luminance]++; - } - } - - if (this.processor.ClipHistogramEnabled) - { - this.processor.ClipHistogram(histogram, this.processor.ClipLimit); - } + this.processor.ClipHistogram(histogram, this.processor.ClipLimit); + } - cdfMinSpan[cdfX] += CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + cdfMinSpan[cdfX] += CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - cdfX++; - } + cdfX++; } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs index 9b80f2ba95..e775285944 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor.cs @@ -1,45 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Applies an adaptive histogram equalization to the image using an sliding window approach. +/// +public class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor { /// - /// Applies an adaptive histogram equalization to the image using an sliding window approach. + /// Initializes a new instance of the class. /// - public class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + public AdaptiveHistogramEqualizationSlidingWindowProcessor( + int luminanceLevels, + bool clipHistogram, + int clipLimit, + int numberOfTiles) + : base(luminanceLevels, clipHistogram, clipLimit) { - /// - /// Initializes a new instance of the class. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - public AdaptiveHistogramEqualizationSlidingWindowProcessor( - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int numberOfTiles) - : base(luminanceLevels, clipHistogram, clipLimit) - { - this.NumberOfTiles = numberOfTiles; - } + this.NumberOfTiles = numberOfTiles; + } - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - public int NumberOfTiles { get; } + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + public int NumberOfTiles { get; } - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AdaptiveHistogramEqualizationSlidingWindowProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - this.NumberOfTiles, - source, - sourceRectangle); - } + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new AdaptiveHistogramEqualizationSlidingWindowProcessor( + configuration, + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimit, + this.NumberOfTiles, + source, + sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index b7e5819e0e..3aaf2631ae 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -1,440 +1,437 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Applies an adaptive histogram equalization to the image using an sliding window approach. +/// +/// The pixel format. +internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies an adaptive histogram equalization to the image using an sliding window approach. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AdaptiveHistogramEqualizationSlidingWindowProcessor( + Configuration configuration, + int luminanceLevels, + bool clipHistogram, + int clipLimit, + int tiles, + Image source, + Rectangle sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The number of tiles the image is split into (horizontal and vertically). Minimum value is 2. Maximum value is 100. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AdaptiveHistogramEqualizationSlidingWindowProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - int tiles, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); - Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); + Guard.MustBeGreaterThanOrEqualTo(tiles, 2, nameof(tiles)); + Guard.MustBeLessThanOrEqualTo(tiles, 100, nameof(tiles)); - this.Tiles = tiles; - } + this.Tiles = tiles; + } - /// - /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// - private int Tiles { get; } + /// + /// Gets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// + private int Tiles { get; } + + /// + protected override void OnFrameApply(ImageFrame source) + { + MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; + + ParallelOptions parallelOptions = new() + { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; + int tileWidth = source.Width / this.Tiles; + int tileHeight = tileWidth; + int pixelInTile = tileWidth * tileHeight; + int halfTileHeight = tileHeight / 2; + int halfTileWidth = halfTileHeight; + SlidingWindowInfos slidingWindowInfos = new(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); + + // 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. + SlidingWindowOperation innerOperation = new( + 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. + SlidingWindowOperation leftBorderOperation = new( + 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. + SlidingWindowOperation rightBorderOperation = new( + 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. + SlidingWindowOperation topBorderOperation = new( + 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. + SlidingWindowOperation bottomBorderOperation = new( + 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); + } - /// - protected override void OnFrameApply(ImageFrame source) + /// + /// Get the a pixel row at a given position with a length of the tile width. Mirrors pixels which exceeds the edges. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + private static void CopyPixelRow( + ImageFrame source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + { + if (y < 0) { - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - - ParallelOptions parallelOptions = new() - { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }; - int tileWidth = source.Width / this.Tiles; - int tileHeight = tileWidth; - int pixelInTile = tileWidth * tileHeight; - int halfTileHeight = tileHeight / 2; - int halfTileWidth = halfTileHeight; - SlidingWindowInfos slidingWindowInfos = new(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); - - // 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. - SlidingWindowOperation innerOperation = new( - 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. - SlidingWindowOperation leftBorderOperation = new( - 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. - SlidingWindowOperation rightBorderOperation = new( - 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. - SlidingWindowOperation topBorderOperation = new( - 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. - SlidingWindowOperation bottomBorderOperation = new( - 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); + y = Numerics.Abs(y); + } + else if (y >= source.Height) + { + int diff = y - source.Height; + y = source.Height - diff - 1; } - /// - /// Get the a pixel row at a given position with a length of the tile width. Mirrors pixels which exceeds the edges. - /// - /// The source image. - /// Pre-allocated pixel row span of the size of a the tile width. - /// The x position. - /// The y position. - /// The width in pixels of a tile. - /// The configuration. - private static void CopyPixelRow( - ImageFrame source, - Span rowPixels, - int x, - int y, - int tileWidth, - Configuration configuration) + // Special cases for the left and the right border where DangerousGetRowSpan can not be used. + if (x < 0) { - if (y < 0) + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) { - y = Numerics.Abs(y); - } - else if (y >= source.Height) - { - int diff = y - source.Height; - y = source.Height - diff - 1; + rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4(); + idx++; } - // Special cases for the left and the right border where DangerousGetRowSpan can not be used. - if (x < 0) + return; + } + else if (x + tileWidth > source.Width) + { + rowPixels.Clear(); + int idx = 0; + for (int dx = x; dx < x + tileWidth; dx++) { - rowPixels.Clear(); - int idx = 0; - for (int dx = x; dx < x + tileWidth; dx++) + if (dx >= source.Width) { - rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4(); - idx++; + int diff = dx - source.Width; + rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); } - - return; - } - else if (x + tileWidth > source.Width) - { - rowPixels.Clear(); - int idx = 0; - for (int dx = x; dx < x + tileWidth; dx++) + else { - if (dx >= source.Width) - { - int diff = dx - source.Width; - rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); - } - else - { - rowPixels[idx] = source[dx, y].ToVector4(); - } - - idx++; + rowPixels[idx] = source[dx, y].ToVector4(); } - return; + idx++; } - CopyPixelRowFast(source.PixelBuffer, rowPixels, x, y, tileWidth, configuration); + return; } - /// - /// Get the a pixel row at a given position with a length of the tile width. - /// - /// The source image. - /// Pre-allocated pixel row span of the size of a the tile width. - /// The x position. - /// The y position. - /// The width in pixels of a tile. - /// The configuration. - [MethodImpl(InliningOptions.ShortMethod)] - private static void CopyPixelRowFast( - Buffer2D source, - Span rowPixels, - int x, - int y, - int tileWidth, - Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + CopyPixelRowFast(source.PixelBuffer, rowPixels, x, y, tileWidth, configuration); + } - /// - /// Adds a column of grey values to the histogram. - /// - /// The reference to the span of grey values to add. - /// The reference to the histogram span. - /// The number of different luminance levels. - /// The grey values span length. - [MethodImpl(InliningOptions.ShortMethod)] - private static void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + /// + /// Get the a pixel row at a given position with a length of the tile width. + /// + /// The source image. + /// Pre-allocated pixel row span of the size of a the tile width. + /// The x position. + /// The y position. + /// The width in pixels of a tile. + /// The configuration. + [MethodImpl(InliningOptions.ShortMethod)] + private static void CopyPixelRowFast( + Buffer2D source, + Span rowPixels, + int x, + int y, + int tileWidth, + Configuration configuration) + => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + + /// + /// Adds a column of grey values to the histogram. + /// + /// The reference to the span of grey values to add. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (nint idx = 0; idx < length; idx++) { - for (nint idx = 0; idx < length; idx++) - { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } + int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; } + } + + /// + /// Removes a column of grey values from the histogram. + /// + /// The reference to the span of grey values to remove. + /// The reference to the histogram span. + /// The number of different luminance levels. + /// The grey values span length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + { + for (int idx = 0; idx < length; idx++) + { + int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)--; + } + } + + /// + /// 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; /// - /// Removes a column of grey values from the histogram. + /// Initializes a new instance of the struct. /// - /// The reference to the span of grey values to remove. - /// The reference to the histogram span. - /// The number of different luminance levels. - /// The grey values span length. + /// 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)] - private static void RemovePixelsFromHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) + public SlidingWindowOperation( + Configuration configuration, + AdaptiveHistogramEqualizationSlidingWindowProcessor processor, + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath) { - for (int idx = 0; idx < length; idx++) - { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); - Unsafe.Add(ref histogramBase, luminance)--; - } + 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; } - /// - /// 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 + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int x) { - 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) + 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)) { - 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; - } + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - [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 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++) { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + if (this.useFastPath) + { + CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + else + { + CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } - Span histogramCopy = histogramBufferCopy.GetSpan(); - ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + 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); + } - Span pixelRow = pixelRowBuffer.GetSpan(); - ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.processor.ClipHistogramEnabled + ? CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - // Build the initial histogram of grayscale values. - for (int dy = this.yStart - this.swInfos.HalfTileHeight; dy < this.yStart + this.swInfos.HalfTileHeight; dy++) + 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) + { + CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else { - if (this.useFastPath) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); - } - - AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } - for (int y = this.yStart; y < this.yEnd; y++) + 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) { - 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 - ? CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) - : 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) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - - 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) - { - CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - else - { - CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); - } - - AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } + else + { + CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); } } } + } - private class SlidingWindowInfos + private class SlidingWindowInfos + { + public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) { - public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) - { - this.TileWidth = tileWidth; - this.TileHeight = tileHeight; - this.HalfTileWidth = halfTileWidth; - this.HalfTileHeight = halfTileHeight; - this.PixelInTile = pixelInTile; - } + this.TileWidth = tileWidth; + this.TileHeight = tileHeight; + this.HalfTileWidth = halfTileWidth; + this.HalfTileHeight = halfTileHeight; + this.PixelInTile = pixelInTile; + } - public int TileWidth { get; } + public int TileWidth { get; } - public int TileHeight { get; } + public int TileHeight { get; } - public int PixelInTile { get; } + public int PixelInTile { get; } - public int HalfTileWidth { get; } + public int HalfTileWidth { get; } - public int HalfTileHeight { get; } - } + public int HalfTileHeight { get; } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs index be26f88faf..f2aeada841 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor.cs @@ -1,32 +1,31 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Defines a global histogram equalization applicable to an . +/// +public class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor { /// - /// Defines a global histogram equalization applicable to an . + /// Initializes a new instance of the class. /// - public class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor + /// The number of luminance levels. + /// A value indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) + : base(luminanceLevels, clipHistogram, clipLimit) { - /// - /// Initializes a new instance of the class. - /// - /// The number of luminance levels. - /// A value indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - public GlobalHistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) - : base(luminanceLevels, clipHistogram, clipLimit) - { - } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new GlobalHistogramEqualizationProcessor( - configuration, - this.LuminanceLevels, - this.ClipHistogram, - this.ClipLimit, - source, - sourceRectangle); } + + /// + public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new GlobalHistogramEqualizationProcessor( + configuration, + this.LuminanceLevels, + this.ClipHistogram, + this.ClipLimit, + source, + sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index c4b925efed..59c37373ea 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -1,173 +1,170 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Applies a global histogram equalization to the image. +/// +/// The pixel format. +internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor + where TPixel : unmanaged, IPixel { /// - /// Applies a global histogram equalization to the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// + /// Indicating whether to clip the histogram bins at a specific value. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public GlobalHistogramEqualizationProcessor( + Configuration configuration, + int luminanceLevels, + bool clipHistogram, + int clipLimit, + Image source, + Rectangle sourceRectangle) + : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// - /// Indicating whether to clip the histogram bins at a specific value. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public GlobalHistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - Image source, - Rectangle sourceRectangle) - : base(configuration, luminanceLevels, clipHistogram, clipLimit, source, sourceRectangle) - { - } + } - /// - protected override void OnFrameApply(ImageFrame source) - { - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - int numberOfPixels = source.Width * source.Height; - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + /// + protected override void OnFrameApply(ImageFrame source) + { + MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; + int numberOfPixels = source.Width * source.Height; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); - // Build the histogram of the grayscale levels. - var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in grayscaleOperation); + // Build the histogram of the grayscale levels. + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in grayscaleOperation); - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) - { - this.ClipHistogram(histogram, this.ClipLimit); - } + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimit); + } - using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + 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 = CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in cdfOperation); + // Apply the cdf to each pixel of the image + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, 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 Buffer2D source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowOperation( + Rectangle bounds, + IMemoryOwner histogramBuffer, + Buffer2D source, + int luminanceLevels) + { + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; } - /// - /// A implementing the grayscale levels logic for . - /// - private readonly struct GrayscaleLevelsRowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Rectangle bounds; - private readonly IMemoryOwner histogramBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - - [MethodImpl(InliningOptions.ShortMethod)] - public GrayscaleLevelsRowOperation( - Rectangle bounds, - IMemoryOwner histogramBuffer, - Buffer2D source, - int luminanceLevels) - { - this.bounds = bounds; - this.histogramBuffer = histogramBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - } + ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); + Span pixelRow = this.source.DangerousGetRowSpan(y); + int levels = this.luminanceLevels; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = 0; x < this.bounds.Width; x++) { - ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - Span pixelRow = this.source.DangerousGetRowSpan(y); - int levels = this.luminanceLevels; - - for (int x = 0; x < this.bounds.Width; x++) - { - // TODO: We should bulk convert here. - var vector = pixelRow[x].ToVector4(); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); - Interlocked.Increment(ref Unsafe.Add(ref histogramBase, luminance)); - } + // TODO: We should bulk convert here. + var vector = pixelRow[x].ToVector4(); + int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + Interlocked.Increment(ref 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 Buffer2D source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowOperation( + Rectangle bounds, + IMemoryOwner cdfBuffer, + Buffer2D source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } - /// - /// A implementing the cdf application levels logic for . - /// - private readonly struct CdfApplicationRowOperation : IRowOperation + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Rectangle bounds; - private readonly IMemoryOwner cdfBuffer; - private readonly Buffer2D source; - private readonly int luminanceLevels; - private readonly float numberOfPixelsMinusCdfMin; - - [MethodImpl(InliningOptions.ShortMethod)] - public CdfApplicationRowOperation( - Rectangle bounds, - IMemoryOwner cdfBuffer, - Buffer2D source, - int luminanceLevels, - float numberOfPixelsMinusCdfMin) - { - this.bounds = bounds; - this.cdfBuffer = cdfBuffer; - this.source = source; - this.luminanceLevels = luminanceLevels; - this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; - } + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + Span pixelRow = this.source.DangerousGetRowSpan(y); + int levels = this.luminanceLevels; + float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = 0; x < this.bounds.Width; x++) { - ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - Span pixelRow = this.source.DangerousGetRowSpan(y); - int levels = this.luminanceLevels; - float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - - for (int x = 0; x < this.bounds.Width; x++) - { - // TODO: We should bulk convert here. - ref TPixel pixel = ref pixelRow[x]; - var vector = pixel.ToVector4(); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W)); - } + // TODO: We should bulk convert here. + ref TPixel pixel = ref pixelRow[x]; + var vector = pixel.ToVector4(); + int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W)); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs index b64ea018da..c8fb361398 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationMethod.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Enumerates the different types of defined histogram equalization methods. +/// +public enum HistogramEqualizationMethod : int { /// - /// Enumerates the different types of defined histogram equalization methods. + /// A global histogram equalization. /// - public enum HistogramEqualizationMethod : int - { - /// - /// A global histogram equalization. - /// - Global, + Global, - /// - /// Adaptive histogram equalization using a tile interpolation approach. - /// - AdaptiveTileInterpolation, + /// + /// Adaptive histogram equalization using a tile interpolation approach. + /// + AdaptiveTileInterpolation, - /// - /// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results. - /// - AdaptiveSlidingWindow, - } + /// + /// Adaptive histogram equalization using sliding window. Slower then the tile interpolation mode, but can yield to better results. + /// + AdaptiveSlidingWindow, } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 0e9bfa7e48..6343788425 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -1,46 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Data container providing the different options for the histogram equalization. +/// +public class HistogramEqualizationOptions { /// - /// Data container providing the different options for the histogram equalization. + /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. /// - public class HistogramEqualizationOptions - { - /// - /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. - /// - public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global; + public HistogramEqualizationMethod Method { get; set; } = HistogramEqualizationMethod.Global; - /// - /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Defaults to 256. - /// - public int LuminanceLevels { get; set; } = 256; + /// + /// Gets or sets the number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Defaults to 256. + /// + public int LuminanceLevels { get; set; } = 256; - /// - /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. - /// It is recommended to use clipping when the AdaptiveTileInterpolation method is used, to suppress artifacts which can occur on the borders of the tiles. - /// Defaults to false. - /// - public bool ClipHistogram { get; set; } + /// + /// Gets or sets a value indicating whether to clip the histogram bins at a specific value. + /// It is recommended to use clipping when the AdaptiveTileInterpolation method is used, to suppress artifacts which can occur on the borders of the tiles. + /// Defaults to false. + /// + public bool ClipHistogram { get; set; } - /// - /// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant - /// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values - /// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into - /// and therefore the image size itself. - /// Defaults to 350. - /// - /// For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE - public int ClipLimit { get; set; } = 350; + /// + /// Gets or sets the histogram clip limit. Adaptive histogram equalization may cause noise to be amplified in near constant + /// regions. To reduce this problem, histogram bins which exceed a given limit will be capped at this value. The exceeding values + /// will be redistributed equally to all other bins. The clipLimit depends on the size of the tiles the image is split into + /// and therefore the image size itself. + /// Defaults to 350. + /// + /// For more information, see also: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE + public int ClipLimit { get; set; } = 350; - /// - /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. - /// Defaults to 8. - /// - public int NumberOfTiles { get; set; } = 8; - } + /// + /// Gets or sets the number of tiles the image is split into (horizontal and vertically) for the adaptive histogram equalization. + /// Defaults to 8. + /// + public int NumberOfTiles { get; set; } = 8; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 26071e27a7..f90a810790 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -3,64 +3,63 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Defines a processor that normalizes the histogram of an image. +/// +public abstract class HistogramEqualizationProcessor : IImageProcessor { /// - /// Defines a processor that normalizes the histogram of an image. + /// Initializes a new instance of the class. /// - public abstract class HistogramEqualizationProcessor : IImageProcessor + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicates, if histogram bins should be clipped. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) { - /// - /// Initializes a new instance of the class. - /// - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicates, if histogram bins should be clipped. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - protected HistogramEqualizationProcessor(int luminanceLevels, bool clipHistogram, int clipLimit) - { - this.LuminanceLevels = luminanceLevels; - this.ClipHistogram = clipHistogram; - this.ClipLimit = clipLimit; - } + this.LuminanceLevels = luminanceLevels; + this.ClipHistogram = clipHistogram; + this.ClipLimit = clipLimit; + } - /// - /// Gets the number of luminance levels. - /// - public int LuminanceLevels { get; } + /// + /// Gets the number of luminance levels. + /// + public int LuminanceLevels { get; } - /// - /// Gets a value indicating whether to clip the histogram bins at a specific value. - /// - public bool ClipHistogram { get; } + /// + /// Gets a value indicating whether to clip the histogram bins at a specific value. + /// + public bool ClipHistogram { get; } - /// - /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// - public int ClipLimit { get; } + /// + /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// + public int ClipLimit { get; } - /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel; + /// + public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel; - /// - /// Creates the that implements the algorithm - /// defined by the given . - /// - /// The . - /// The . - public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) => options.Method switch - { - HistogramEqualizationMethod.Global - => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), + /// + /// Creates the that implements the algorithm + /// defined by the given . + /// + /// The . + /// The . + public static HistogramEqualizationProcessor FromOptions(HistogramEqualizationOptions options) => options.Method switch + { + HistogramEqualizationMethod.Global + => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), - HistogramEqualizationMethod.AdaptiveTileInterpolation - => new AdaptiveHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), + HistogramEqualizationMethod.AdaptiveTileInterpolation + => new AdaptiveHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), - HistogramEqualizationMethod.AdaptiveSlidingWindow - => new AdaptiveHistogramEqualizationSlidingWindowProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), + HistogramEqualizationMethod.AdaptiveSlidingWindow + => new AdaptiveHistogramEqualizationSlidingWindowProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit, options.NumberOfTiles), - _ => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), - }; - } + _ => new GlobalHistogramEqualizationProcessor(options.LuminanceLevels, options.ClipHistogram, options.ClipLimit), + }; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index 0213799071..49168f4b24 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -1,150 +1,148 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Normalization +namespace SixLabors.ImageSharp.Processing.Processors.Normalization; + +/// +/// Defines a processor that normalizes the histogram of an image. +/// +/// The pixel format. +internal abstract class HistogramEqualizationProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly float luminanceLevelsFloat; + /// - /// Defines a processor that normalizes the histogram of an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal abstract class HistogramEqualizationProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images + /// or 65536 for 16-bit grayscale images. + /// Indicates, if histogram bins should be clipped. + /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + protected HistogramEqualizationProcessor( + Configuration configuration, + int luminanceLevels, + bool clipHistogram, + int clipLimit, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly float luminanceLevelsFloat; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The number of different luminance levels. Typical values are 256 for 8-bit grayscale images - /// or 65536 for 16-bit grayscale images. - /// Indicates, if histogram bins should be clipped. - /// The histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected HistogramEqualizationProcessor( - Configuration configuration, - int luminanceLevels, - bool clipHistogram, - int clipLimit, - Image source, - Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); - Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit)); + Guard.MustBeGreaterThan(luminanceLevels, 0, nameof(luminanceLevels)); + Guard.MustBeGreaterThan(clipLimit, 1, nameof(clipLimit)); - this.LuminanceLevels = luminanceLevels; - this.luminanceLevelsFloat = luminanceLevels; - this.ClipHistogramEnabled = clipHistogram; - this.ClipLimit = clipLimit; - } + this.LuminanceLevels = luminanceLevels; + this.luminanceLevelsFloat = luminanceLevels; + this.ClipHistogramEnabled = clipHistogram; + this.ClipLimit = clipLimit; + } - /// - /// Gets the number of luminance levels. - /// - public int LuminanceLevels { get; } - - /// - /// Gets a value indicating whether to clip the histogram bins at a specific value. - /// - public bool ClipHistogramEnabled { get; } - - /// - /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - /// - public int ClipLimit { get; } - - /// - /// Calculates the cumulative distribution function. - /// - /// The reference to the array holding the cdf. - /// The reference to the histogram of the input image. - /// Index of the maximum of the histogram. - /// The first none zero value of the cdf. - public static int CalculateCdf(ref int cdfBase, ref int histogramBase, int maxIdx) - { - int histSum = 0; - int cdfMin = 0; - bool cdfMinFound = false; + /// + /// Gets the number of luminance levels. + /// + public int LuminanceLevels { get; } + + /// + /// Gets a value indicating whether to clip the histogram bins at a specific value. + /// + public bool ClipHistogramEnabled { get; } + + /// + /// Gets the histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + /// + public int ClipLimit { get; } + + /// + /// Calculates the cumulative distribution function. + /// + /// The reference to the array holding the cdf. + /// The reference to the histogram of the input image. + /// Index of the maximum of the histogram. + /// The first none zero value of the cdf. + public static int CalculateCdf(ref int cdfBase, ref int histogramBase, int maxIdx) + { + int histSum = 0; + int cdfMin = 0; + bool cdfMinFound = false; - for (int i = 0; i <= maxIdx; i++) + for (int i = 0; i <= maxIdx; i++) + { + histSum += Unsafe.Add(ref histogramBase, i); + if (!cdfMinFound && histSum != 0) { - histSum += Unsafe.Add(ref histogramBase, i); - if (!cdfMinFound && histSum != 0) - { - cdfMin = histSum; - cdfMinFound = true; - } - - // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop. - Unsafe.Add(ref cdfBase, i) = Math.Max(0, histSum - cdfMin); + cdfMin = histSum; + cdfMinFound = true; } - return cdfMin; + // Creating the lookup table: subtracting cdf min, so we do not need to do that inside the for loop. + Unsafe.Add(ref cdfBase, i) = Math.Max(0, histSum - cdfMin); } - /// - /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated. - /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute - /// the values over the clip limit to all other bins equally. - /// - /// The histogram to apply the clipping. - /// Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. - public void ClipHistogram(Span histogram, int clipLimit) - { - int sumOverClip = 0; - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + return cdfMin; + } - for (int i = 0; i < histogram.Length; i++) - { - ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); - if (histogramLevel > clipLimit) - { - sumOverClip += histogramLevel - clipLimit; - histogramLevel = clipLimit; - } - } + /// + /// AHE tends to over amplify the contrast in near-constant regions of the image, since the histogram in such regions is highly concentrated. + /// Clipping the histogram is meant to reduce this effect, by cutting of histogram bin's which exceed a certain amount and redistribute + /// the values over the clip limit to all other bins equally. + /// + /// The histogram to apply the clipping. + /// Histogram clip limit. Histogram bins which exceed this limit, will be capped at this value. + public void ClipHistogram(Span histogram, int clipLimit) + { + int sumOverClip = 0; + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - // Redistribute the clipped pixels over all bins of the histogram. - int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; - if (addToEachBin > 0) + for (int i = 0; i < histogram.Length; i++) + { + ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); + if (histogramLevel > clipLimit) { - for (int i = 0; i < histogram.Length; i++) - { - Unsafe.Add(ref histogramBase, i) += addToEachBin; - } + sumOverClip += histogramLevel - clipLimit; + histogramLevel = clipLimit; } + } - int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); - if (residual != 0) + // Redistribute the clipped pixels over all bins of the histogram. + int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; + if (addToEachBin > 0) + { + for (int i = 0; i < histogram.Length; i++) { - int residualStep = Math.Max(this.LuminanceLevels / residual, 1); - for (int i = 0; i < this.LuminanceLevels && residual > 0; i += residualStep, residual--) - { - ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); - histogramLevel++; - } + Unsafe.Add(ref histogramBase, i) += addToEachBin; } } - /// - /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. - /// - /// The pixel to get the luminance from - /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) + int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); + if (residual != 0) { - // TODO: We need a bulk per span equivalent. - Vector4 vector = sourcePixel.ToVector4(); - return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + int residualStep = Math.Max(this.LuminanceLevels / residual, 1); + for (int i = 0; i < this.LuminanceLevels && residual > 0; i += residualStep, residual--) + { + ref int histogramLevel = ref Unsafe.Add(ref histogramBase, i); + histogramLevel++; + } } } + + /// + /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709. + /// + /// The pixel to get the luminance from + /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images) + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) + { + // TODO: We need a bulk per span equivalent. + Vector4 vector = sourcePixel.ToVector4(); + return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index b1f8a485a3..cbe556d4b8 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -3,37 +3,36 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// Defines a processing operation to replace the background color of an . +/// +public sealed class BackgroundColorProcessor : IImageProcessor { /// - /// Defines a processing operation to replace the background color of an . + /// Initializes a new instance of the class. /// - public sealed class BackgroundColorProcessor : IImageProcessor + /// The options defining blending algorithm and amount. + /// The to set the background color to. + public BackgroundColorProcessor(GraphicsOptions options, Color color) { - /// - /// Initializes a new instance of the class. - /// - /// The options defining blending algorithm and amount. - /// The to set the background color to. - public BackgroundColorProcessor(GraphicsOptions options, Color color) - { - this.Color = color; - this.GraphicsOptions = options; - } + this.Color = color; + this.GraphicsOptions = options; + } - /// - /// Gets the Graphics options to alter how processor is applied. - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Gets the Graphics options to alter how processor is applied. + /// + public GraphicsOptions GraphicsOptions { get; } - /// - /// Gets the background color value. - /// - public Color Color { get; } + /// + /// Gets the background color value. + /// + public Color Color { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new BackgroundColorProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 1880f0bf10..1e43458253 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -1,103 +1,101 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// Sets the background color of the image. +/// +/// The pixel format. +internal class BackgroundColorProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly BackgroundColorProcessor definition; + /// - /// Sets the background color of the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class BackgroundColorProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.definition = definition; + + /// + protected override void OnFrameApply(ImageFrame source) { - private readonly BackgroundColorProcessor definition; + TPixel color = this.definition.Color.ToPixel(); + GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; - /// - /// 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 BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.definition = definition; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel color = this.definition.Color.ToPixel(); - GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; + Configuration configuration = this.Configuration; + MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); + using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; + colors.GetSpan().Fill(color); + amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); - using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - colors.GetSpan().Fill(color); - amount.GetSpan().Fill(graphicsOptions.BlendPercentage); + var operation = new RowOperation(configuration, interest, blender, amount, colors, source.PixelBuffer); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + 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 Buffer2D source; - var operation = new RowOperation(configuration, interest, blender, amount, colors, source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + PixelBlender blender, + IMemoryOwner amount, + IMemoryOwner colors, + Buffer2D source) + { + this.configuration = configuration; + this.bounds = bounds; + this.blender = blender; + this.amount = amount; + this.colors = colors; + this.source = source; } - private readonly struct RowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly PixelBlender blender; - private readonly IMemoryOwner amount; - private readonly IMemoryOwner colors; - private readonly Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - PixelBlender blender, - IMemoryOwner amount, - IMemoryOwner colors, - Buffer2D 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.DangerousGetRowSpan(y) - .Slice(this.bounds.X, this.bounds.Width); + Span destination = + this.source.DangerousGetRowSpan(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()); - } + // 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 0f7c7f5273..da8bf45a8e 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -3,54 +3,53 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// Defines a radial glow effect applicable to an . +/// +public sealed class GlowProcessor : IImageProcessor { /// - /// Defines a radial glow effect applicable to an . + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + /// The color or the glow. + public GlowProcessor(GraphicsOptions options, Color color) + : this(options, color, 0) + { + } + + /// + /// Initializes a new instance of the class. /// - public sealed class GlowProcessor : IImageProcessor + /// The options effecting blending and composition. + /// The color or the glow. + /// The radius of the glow. + internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius) { - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color or the glow. - public GlowProcessor(GraphicsOptions options, Color color) - : this(options, color, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color or the glow. - /// The radius of the glow. - internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius) - { - this.GlowColor = color; - this.Radius = radius; - this.GraphicsOptions = options; - } - - /// - /// Gets the options effecting blending and composition. - /// - public GraphicsOptions GraphicsOptions { get; } - - /// - /// Gets the glow color to apply. - /// - public Color GlowColor { get; } - - /// - /// Gets the the radius. - /// - internal ValueSize Radius { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new GlowProcessor(configuration, this, source, sourceRectangle); + this.GlowColor = color; + this.Radius = radius; + this.GraphicsOptions = options; } + + /// + /// Gets the options effecting blending and composition. + /// + public GraphicsOptions GraphicsOptions { get; } + + /// + /// Gets the glow color to apply. + /// + public Color GlowColor { get; } + + /// + /// Gets the the radius. + /// + internal ValueSize Radius { get; } + + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 9623a3edaf..c431650b33 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,111 +8,110 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// An that applies a radial glow effect an . +/// +/// The pixel format. +internal class GlowProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly PixelBlender blender; + private readonly GlowProcessor definition; + /// - /// An that applies a radial glow effect an . + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class GlowProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 GlowProcessor(Configuration configuration, GlowProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly PixelBlender blender; - private readonly GlowProcessor 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 GlowProcessor(Configuration configuration, GlowProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); - } + this.definition = definition; + this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); + } - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel glowColor = this.definition.GlowColor.ToPixel(); - float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + /// + protected override void OnFrameApply(ImageFrame source) + { + TPixel glowColor = this.definition.GlowColor.ToPixel(); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - 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, interest.Width * .5F) + : interest.Width * .5F; - Vector2 center = Rectangle.Center(interest); - float finalRadius = this.definition.Radius.Calculate(interest.Size); - float maxDistance = finalRadius > 0 - ? MathF.Min(finalRadius, interest.Width * .5F) - : interest.Width * .5F; + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - Configuration configuration = this.Configuration; - MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(glowColor); - using IMemoryOwner rowColors = allocator.Allocate(interest.Width); - rowColors.GetSpan().Fill(glowColor); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); - 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 Buffer2D source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + Buffer2D source) + { + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - private readonly struct RowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - 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 Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner colors, - PixelBlender blender, - Vector2 center, - float maxDistance, - float blendPercent, - Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.colors = colors; - this.blender = blender; - this.center = center; - this.maxDistance = maxDistance; - this.blendPercent = blendPercent; - this.source = source; - } + Span colorSpan = this.colors.GetSpan(); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + for (int i = 0; i < this.bounds.Width; i++) { - 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] = Numerics.Clamp(this.blendPercent * (1 - (.95F * (distance / this.maxDistance))), 0, 1F); - } - - Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = Numerics.Clamp(this.blendPercent * (1 - (.95F * (distance / this.maxDistance))), 0, 1F); } + + Span destination = this.source.DangerousGetRowSpan(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 2e31f93c23..c244ed6ab4 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -3,62 +3,61 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// Defines a radial vignette effect applicable to an . +/// +public sealed class VignetteProcessor : IImageProcessor { /// - /// Defines a radial vignette effect applicable to an . + /// Initializes a new instance of the class. /// - public sealed class VignetteProcessor : IImageProcessor + /// The options effecting blending and composition. + /// The color of the vignette. + public VignetteProcessor(GraphicsOptions options, Color color) { - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color of the vignette. - public VignetteProcessor(GraphicsOptions options, Color color) - { - this.VignetteColor = color; - this.GraphicsOptions = options; - } + this.VignetteColor = color; + this.GraphicsOptions = options; + } - /// - /// Initializes a new instance of the class. - /// - /// The options effecting blending and composition. - /// The color of the vignette. - /// The x-radius. - /// The y-radius. - internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY) - { - this.VignetteColor = color; - this.RadiusX = radiusX; - this.RadiusY = radiusY; - this.GraphicsOptions = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + /// The color of the vignette. + /// The x-radius. + /// The y-radius. + internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY) + { + this.VignetteColor = color; + this.RadiusX = radiusX; + this.RadiusY = radiusY; + this.GraphicsOptions = options; + } - /// - /// Gets the options effecting blending and composition - /// - public GraphicsOptions GraphicsOptions { get; } + /// + /// Gets the options effecting blending and composition + /// + public GraphicsOptions GraphicsOptions { get; } - /// - /// Gets the vignette color to apply. - /// - public Color VignetteColor { get; } + /// + /// Gets the vignette color to apply. + /// + public Color VignetteColor { get; } - /// - /// Gets the the x-radius. - /// - internal ValueSize RadiusX { get; } + /// + /// Gets the the x-radius. + /// + internal ValueSize RadiusX { get; } - /// - /// Gets the the y-radius. - /// - internal ValueSize RadiusY { get; } + /// + /// Gets the the y-radius. + /// + internal ValueSize RadiusY { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new VignetteProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 dc17590ad2..c69b6360d5 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,119 +8,118 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Processing.Processors.Overlays; + +/// +/// An that applies a radial vignette effect to an . +/// +/// The pixel format. +internal class VignetteProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly PixelBlender blender; + private readonly VignetteProcessor definition; + /// - /// An that applies a radial vignette effect to an . + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class VignetteProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// 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 VignetteProcessor(Configuration configuration, VignetteProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly PixelBlender blender; - private readonly VignetteProcessor 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 VignetteProcessor(Configuration configuration, VignetteProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); - } + this.definition = definition; + this.blender = PixelOperations.Instance.GetPixelBlender(definition.GraphicsOptions); + } - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); - float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + /// + protected override void OnFrameApply(ImageFrame source) + { + TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + 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); + Vector2 center = Rectangle.Center(interest); + float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); + float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); - float rX = finalRadiusX > 0 - ? MathF.Min(finalRadiusX, interest.Width * .5F) - : interest.Width * .5F; + float rX = finalRadiusX > 0 + ? MathF.Min(finalRadiusX, interest.Width * .5F) + : interest.Width * .5F; - float rY = finalRadiusY > 0 - ? MathF.Min(finalRadiusY, interest.Height * .5F) - : interest.Height * .5F; + float rY = finalRadiusY > 0 + ? MathF.Min(finalRadiusY, interest.Height * .5F) + : interest.Height * .5F; - float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); + float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - Configuration configuration = this.Configuration; - MemoryAllocator allocator = configuration.MemoryAllocator; + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - using IMemoryOwner rowColors = allocator.Allocate(interest.Width); - rowColors.GetSpan().Fill(vignetteColor); + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(vignetteColor); - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); + 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 Buffer2D source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + Buffer2D source) + { + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - private readonly struct RowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - 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 Buffer2D source; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Configuration configuration, - Rectangle bounds, - IMemoryOwner colors, - PixelBlender blender, - Vector2 center, - float maxDistance, - float blendPercent, - Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.colors = colors; - this.blender = blender; - this.center = center; - this.maxDistance = maxDistance; - this.blendPercent = blendPercent; - this.source = source; - } + Span colorSpan = this.colors.GetSpan(); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) + for (int i = 0; i < this.bounds.Width; i++) { - 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] = Numerics.Clamp(this.blendPercent * (.9F * (distance / this.maxDistance)), 0, 1F); - } - - Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - - this.blender.Blend( - this.configuration, - destination, - destination, - colorSpan, - span); + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = Numerics.Clamp(this.blendPercent * (.9F * (distance / this.maxDistance)), 0, 1F); } + + Span destination = this.source.DangerousGetRowSpan(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/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs index 370fa3bf2c..9da1d98e3b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs @@ -1,106 +1,103 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// A pixel sampling strategy that enumerates a limited amount of rows from different frames, +/// if the total number of pixels is over a threshold. +/// +public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy { + // TODO: This value shall be determined by benchmarking. + // A smaller value should likely work well, providing better perf. + private const int DefaultMaximumPixels = 4096 * 4096; + /// - /// A pixel sampling strategy that enumerates a limited amount of rows from different frames, - /// if the total number of pixels is over a threshold. + /// Initializes a new instance of the class. /// - public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy + public DefaultPixelSamplingStrategy() + : this(DefaultMaximumPixels, 0.1) { - // TODO: This value shall be determined by benchmarking. - // A smaller value should likely work well, providing better perf. - private const int DefaultMaximumPixels = 4096 * 4096; + } - /// - /// Initializes a new instance of the class. - /// - public DefaultPixelSamplingStrategy() - : this(DefaultMaximumPixels, 0.1) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of pixels to process. + /// always scan at least this portion of total pixels within the image. + public DefaultPixelSamplingStrategy(int maximumPixels, double minimumScanRatio) + { + Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels)); + this.MaximumPixels = maximumPixels; + this.MinimumScanRatio = minimumScanRatio; + } - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of pixels to process. - /// always scan at least this portion of total pixels within the image. - public DefaultPixelSamplingStrategy(int maximumPixels, double minimumScanRatio) - { - Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels)); - this.MaximumPixels = maximumPixels; - this.MinimumScanRatio = minimumScanRatio; - } + /// + /// Gets the maximum number of pixels to process. (The threshold.) + /// + public long MaximumPixels { get; } - /// - /// Gets the maximum number of pixels to process. (The threshold.) - /// - public long MaximumPixels { get; } + /// + /// Gets a value indicating: always scan at least this portion of total pixels within the image. + /// The default is 0.1 (10%). + /// + public double MinimumScanRatio { get; } - /// - /// Gets a value indicating: always scan at least this portion of total pixels within the image. - /// The default is 0.1 (10%). - /// - public double MinimumScanRatio { get; } + /// + public IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel + { + long maximumPixels = Math.Min(this.MaximumPixels, (long)image.Width * image.Height * image.Frames.Count); + long maxNumberOfRows = maximumPixels / image.Width; + long totalNumberOfRows = (long)image.Height * image.Frames.Count; - /// - public IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel + if (totalNumberOfRows <= maxNumberOfRows) { - long maximumPixels = Math.Min(this.MaximumPixels, (long)image.Width * image.Height * image.Frames.Count); - long maxNumberOfRows = maximumPixels / image.Width; - long totalNumberOfRows = (long)image.Height * image.Frames.Count; + // Enumerate all pixels + foreach (ImageFrame frame in image.Frames) + { + yield return frame.PixelBuffer.GetRegion(); + } + } + else + { + double r = maxNumberOfRows / (double)totalNumberOfRows; - if (totalNumberOfRows <= maxNumberOfRows) + // Use a rough approximation to make sure we don't leave out large contiguous regions: + if (maxNumberOfRows > 200) { - // Enumerate all pixels - foreach (ImageFrame frame in image.Frames) - { - yield return frame.PixelBuffer.GetRegion(); - } + r = Math.Round(r, 2); } else { - double r = maxNumberOfRows / (double)totalNumberOfRows; - - // Use a rough approximation to make sure we don't leave out large contiguous regions: - if (maxNumberOfRows > 200) - { - r = Math.Round(r, 2); - } - else - { - r = Math.Round(r, 1); - } + r = Math.Round(r, 1); + } - r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image. + r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image. - var ratio = new Rational(r); + var ratio = new Rational(r); - int denom = (int)ratio.Denominator; - int num = (int)ratio.Numerator; + int denom = (int)ratio.Denominator; + int num = (int)ratio.Numerator; - for (int pos = 0; pos < totalNumberOfRows; pos++) + for (int pos = 0; pos < totalNumberOfRows; pos++) + { + int subPos = pos % denom; + if (subPos < num) { - int subPos = pos % denom; - if (subPos < num) - { - yield return GetRow(pos); - } + yield return GetRow(pos); } + } - Buffer2DRegion GetRow(int pos) - { - int frameIdx = pos / image.Height; - int y = pos % image.Height; - return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1); - } + Buffer2DRegion GetRow(int pos) + { + int frameIdx = pos / image.Height; + int y = pos % image.Height; + return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index d06661d07e..a8cf4d3e91 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -1,226 +1,224 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Gets the closest color to the supplied color based upon the Euclidean distance. +/// +/// The pixel format. +/// +/// This class is not threadsafe and should not be accessed in parallel. +/// Doing so will result in non-idempotent results. +/// +internal sealed class EuclideanPixelMap : IDisposable + where TPixel : unmanaged, IPixel { + private Rgba32[] rgbaPalette; + /// - /// Gets the closest color to the supplied color based upon the Euclidean distance. + /// Do not make this readonly! Struct value would be always copied on non-readonly method calls. /// - /// The pixel format. - /// - /// This class is not threadsafe and should not be accessed in parallel. - /// Doing so will result in non-idempotent results. - /// - internal sealed class EuclideanPixelMap : IDisposable - where TPixel : unmanaged, IPixel + private ColorDistanceCache cache; + private readonly Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The color palette to map from. + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) { - private Rgba32[] rgbaPalette; + this.configuration = configuration; + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + this.cache = new ColorDistanceCache(configuration.MemoryAllocator); + PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); + } - /// - /// Do not make this readonly! Struct value would be always copied on non-readonly method calls. - /// - private ColorDistanceCache cache; - private readonly Configuration configuration; + /// + /// 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; - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The color palette to map from. - public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + [MethodImpl(InliningOptions.ShortMethod)] + private set; + } + + /// + /// 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); + Unsafe.SkipInit(out Rgba32 rgba); + color.ToRgba32(ref rgba); + + // Check if the color is in the lookup table + if (!this.cache.TryGetValue(rgba, out short index)) { - this.configuration = configuration; - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - this.cache = new ColorDistanceCache(configuration.MemoryAllocator); - PixelOperations.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette); + return this.GetClosestColorSlow(rgba, ref paletteRef, out match); } - /// - /// 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; + match = Unsafe.Add(ref paletteRef, index); + return index; + } - [MethodImpl(InliningOptions.ShortMethod)] - private set; - } + /// + /// Clears the map, resetting it to use the given palette. + /// + /// The color palette to map from. + public void Clear(ReadOnlyMemory palette) + { + this.Palette = palette; + this.rgbaPalette = new Rgba32[palette.Length]; + PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); + this.cache.Clear(); + } - /// - /// 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) + [MethodImpl(InliningOptions.ShortMethod)] + private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) + { + // Loop through the palette and find the nearest match. + int index = 0; + float leastDistance = float.MaxValue; + for (int i = 0; i < this.rgbaPalette.Length; i++) { - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); - Unsafe.SkipInit(out Rgba32 rgba); - color.ToRgba32(ref rgba); + Rgba32 candidate = this.rgbaPalette[i]; + int distance = DistanceSquared(rgba, candidate); - // Check if the color is in the lookup table - if (!this.cache.TryGetValue(rgba, out short index)) + // If it's an exact match, exit the loop + if (distance == 0) { - return this.GetClosestColorSlow(rgba, ref paletteRef, out match); + index = i; + break; } - match = Unsafe.Add(ref paletteRef, index); - return index; + if (distance < leastDistance) + { + // Less than... assign. + index = i; + leastDistance = distance; + } } - /// - /// Clears the map, resetting it to use the given palette. - /// - /// The color palette to map from. - public void Clear(ReadOnlyMemory palette) + // Now I have the index, pop it into the cache for next time + this.cache.Add(rgba, (byte)index); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + /// Returns the Euclidean distance squared between two specified points. + /// + /// The first point. + /// The second point. + /// The distance squared. + [MethodImpl(InliningOptions.ShortMethod)] + private static int DistanceSquared(Rgba32 a, Rgba32 b) + { + int deltaR = a.R - b.R; + int deltaG = a.G - b.G; + int deltaB = a.B - b.B; + int deltaA = a.A - b.A; + return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); + } + + public void Dispose() => this.cache.Dispose(); + + /// + /// A cache for storing color distance matching results. + /// + /// + /// + /// The granularity of the cache has been determined based upon the current + /// suite of test images and provides the lowest possible memory usage while + /// providing enough match accuracy. + /// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB). + /// + /// + private unsafe struct ColorDistanceCache : IDisposable + { + private const int IndexBits = 5; + private const int IndexAlphaBits = 5; + private const int IndexCount = (1 << IndexBits) + 1; + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + private const int RgbShift = 8 - IndexBits; + private const int AlphaShift = 8 - IndexAlphaBits; + private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + private MemoryHandle tableHandle; + private readonly IMemoryOwner table; + private readonly short* tablePointer; + + public ColorDistanceCache(MemoryAllocator allocator) { - this.Palette = palette; - this.rgbaPalette = new Rgba32[palette.Length]; - PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette); - this.cache.Clear(); + this.table = allocator.Allocate(Entries); + this.table.GetSpan().Fill(-1); + this.tableHandle = this.table.Memory.Pin(); + this.tablePointer = (short*)this.tableHandle.Pointer; } [MethodImpl(InliningOptions.ShortMethod)] - private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) + public void Add(Rgba32 rgba, byte index) { - // Loop through the palette and find the nearest match. - int index = 0; - float leastDistance = float.MaxValue; - for (int i = 0; i < this.rgbaPalette.Length; i++) - { - Rgba32 candidate = this.rgbaPalette[i]; - int distance = DistanceSquared(rgba, 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.cache.Add(rgba, (byte)index); - match = Unsafe.Add(ref paletteRef, index); - return index; + int r = rgba.R >> RgbShift; + int g = rgba.G >> RgbShift; + int b = rgba.B >> RgbShift; + int a = rgba.A >> AlphaShift; + int idx = GetPaletteIndex(r, g, b, a); + this.tablePointer[idx] = index; } - /// - /// Returns the Euclidean distance squared between two specified points. - /// - /// The first point. - /// The second point. - /// The distance squared. [MethodImpl(InliningOptions.ShortMethod)] - private static int DistanceSquared(Rgba32 a, Rgba32 b) + public bool TryGetValue(Rgba32 rgba, out short match) { - int deltaR = a.R - b.R; - int deltaG = a.G - b.G; - int deltaB = a.B - b.B; - int deltaA = a.A - b.A; - return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); + int r = rgba.R >> RgbShift; + int g = rgba.G >> RgbShift; + int b = rgba.B >> RgbShift; + int a = rgba.A >> AlphaShift; + int idx = GetPaletteIndex(r, g, b, a); + match = this.tablePointer[idx]; + return match > -1; } - public void Dispose() => this.cache.Dispose(); - /// - /// A cache for storing color distance matching results. + /// Clears the cache resetting each entry to empty. /// - /// - /// - /// The granularity of the cache has been determined based upon the current - /// suite of test images and provides the lowest possible memory usage while - /// providing enough match accuracy. - /// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB). - /// - /// - private unsafe struct ColorDistanceCache : IDisposable - { - private const int IndexBits = 5; - private const int IndexAlphaBits = 5; - private const int IndexCount = (1 << IndexBits) + 1; - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - private const int RgbShift = 8 - IndexBits; - private const int AlphaShift = 8 - IndexAlphaBits; - private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - private MemoryHandle tableHandle; - private readonly IMemoryOwner table; - private readonly short* tablePointer; - - public ColorDistanceCache(MemoryAllocator allocator) - { - this.table = allocator.Allocate(Entries); - this.table.GetSpan().Fill(-1); - this.tableHandle = this.table.Memory.Pin(); - this.tablePointer = (short*)this.tableHandle.Pointer; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Add(Rgba32 rgba, byte index) - { - int r = rgba.R >> RgbShift; - int g = rgba.G >> RgbShift; - int b = rgba.B >> RgbShift; - int a = rgba.A >> AlphaShift; - int idx = GetPaletteIndex(r, g, b, a); - this.tablePointer[idx] = index; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(Rgba32 rgba, out short match) - { - int r = rgba.R >> RgbShift; - int g = rgba.G >> RgbShift; - int b = rgba.B >> RgbShift; - int a = rgba.A >> AlphaShift; - int idx = GetPaletteIndex(r, g, b, a); - match = this.tablePointer[idx]; - return match > -1; - } + [MethodImpl(InliningOptions.ShortMethod)] + public void Clear() => this.table.GetSpan().Fill(-1); - /// - /// Clears the cache resetting each entry to empty. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Clear() => this.table.GetSpan().Fill(-1); - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetPaletteIndex(int r, int g, int b, int a) - => (r << ((IndexBits << 1) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits << 1)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - - public void Dispose() + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetPaletteIndex(int r, int g, int b, int a) + => (r << ((IndexBits << 1) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits << 1)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; + + public void Dispose() + { + if (this.table != null) { - if (this.table != null) - { - this.tableHandle.Dispose(); - this.table.Dispose(); - } + this.tableHandle.Dispose(); + this.table.Dispose(); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs index 8abc431ee3..580227c2d7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs @@ -1,25 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// A pixel sampling strategy that enumerates all pixels. +/// +public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy { - /// - /// A pixel sampling strategy that enumerates all pixels. - /// - public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy + /// + public IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel { - /// - public IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel + foreach (ImageFrame frame in image.Frames) { - foreach (ImageFrame frame in image.Frames) - { - yield return frame.PixelBuffer.GetRegion(); - } + yield return frame.PixelBuffer.GetRegion(); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs index cfd0bd557e..ab118b55d4 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs @@ -1,24 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Provides an abstraction to enumerate pixel regions within a multi-framed . +/// +public interface IPixelSamplingStrategy { /// - /// Provides an abstraction to enumerate pixel regions within a multi-framed . + /// Enumerates pixel regions within the image as . /// - public interface IPixelSamplingStrategy - { - /// - /// Enumerates pixel regions within the image as . - /// - /// The image. - /// The pixel type. - /// An enumeration of pixel regions. - IEnumerable> EnumeratePixelRegions(Image image) - where TPixel : unmanaged, IPixel; - } + /// The image. + /// The pixel type. + /// An enumeration of pixel regions. + IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index a4251f5aa7..9d5b606040 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -3,35 +3,34 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Provides methods for allowing quantization of images pixels with configurable dithering. +/// +public interface IQuantizer { /// - /// Provides methods for allowing quantization of images pixels with configurable dithering. + /// Gets the quantizer options defining quantization rules. /// - public interface IQuantizer - { - /// - /// Gets the quantizer options defining quantization rules. - /// - QuantizerOptions Options { get; } + QuantizerOptions Options { get; } - /// - /// Creates the generic frame quantizer. - /// - /// The to configure internal operations. - /// The pixel format. - /// The . - IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel; + /// + /// Creates the generic frame quantizer. + /// + /// The to configure internal operations. + /// The pixel format. + /// The . + IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel; - /// - /// Creates the generic frame quantizer. - /// - /// The pixel format. - /// The to configure internal operations. - /// The options to create the quantizer with. - /// The . - IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel; - } + /// + /// Creates the generic frame quantizer. + /// + /// The pixel format. + /// The to configure internal operations. + /// The options to create the quantizer with. + /// The . + IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs index fd8b62e067..35bbb1289e 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs @@ -1,66 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Provides methods to allow the execution of the quantization process on an image frame. +/// +/// The pixel format. +public interface IQuantizer : IDisposable + where TPixel : unmanaged, IPixel { /// - /// Provides methods to allow the execution of the quantization process on an image frame. + /// Gets the configuration. /// - /// The pixel format. - public interface IQuantizer : IDisposable - where TPixel : unmanaged, IPixel - { - /// - /// Gets the configuration. - /// - Configuration Configuration { get; } + Configuration Configuration { get; } - /// - /// Gets the quantizer options defining quantization rules. - /// - QuantizerOptions Options { get; } + /// + /// Gets the quantizer options defining quantization rules. + /// + QuantizerOptions Options { get; } - /// - /// Gets the quantized color palette. - /// - /// - /// The palette has not been built via . - /// - ReadOnlyMemory Palette { get; } + /// + /// Gets the quantized color palette. + /// + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { get; } - /// - /// Adds colors to the quantized palette from the given pixel source. - /// - /// The of source pixels to register. - void AddPaletteColors(Buffer2DRegion pixelRegion); + /// + /// Adds colors to the quantized palette from the given pixel source. + /// + /// The of source pixels to register. + void AddPaletteColors(Buffer2DRegion pixelRegion); - /// - /// 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 source frame pixels. - /// - /// - /// Only executes the second (quantization) step. The palette has to be built by calling . - /// To run both steps, use . - /// - IndexedImageFrame QuantizeFrame(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 source frame pixels. + /// + /// + /// Only executes the second (quantization) step. The palette has to be built by calling . + /// To run both steps, use . + /// + 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); + /// + /// 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); - } + // TODO: Enable bulk operations. + // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index c388ff528a..0a1032bf0d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -3,44 +3,43 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Allows the quantization of images pixels using Octrees. +/// +/// +public class OctreeQuantizer : IQuantizer { /// - /// Allows the quantization of images pixels using Octrees. - /// + /// Initializes a new instance of the class + /// using the default . /// - public class OctreeQuantizer : IQuantizer + public OctreeQuantizer() + : this(new QuantizerOptions()) { - /// - /// Initializes a new instance of the class - /// using the default . - /// - public OctreeQuantizer() - : this(new QuantizerOptions()) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public OctreeQuantizer(QuantizerOptions options) - { - Guard.NotNull(options, nameof(options)); - this.Options = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The quantizer options defining quantization rules. + public OctreeQuantizer(QuantizerOptions options) + { + Guard.NotNull(options, nameof(options)); + this.Options = options; + } - /// - public QuantizerOptions Options { get; } + /// + public QuantizerOptions Options { get; } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel + => this.CreatePixelSpecificQuantizer(configuration, this.Options); - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - => new OctreeQuantizer(configuration, options); - } + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new OctreeQuantizer(configuration, options); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index b3aa58acf3..bbe8e4113f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Numerics; @@ -10,569 +9,568 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Encapsulates methods to calculate the color palette if an image using an Octree pattern. +/// +/// +/// The pixel format. +[SuppressMessage( + "Design", + "CA1001:Types that own disposable fields should be disposable", + Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] +public struct OctreeQuantizer : IQuantizer + where TPixel : unmanaged, IPixel { + private readonly int maxColors; + private readonly int bitDepth; + private readonly Octree octree; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; + /// - /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. - /// + /// Initializes a new instance of the struct. /// - /// The pixel format. - [SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] - public struct OctreeQuantizer : IQuantizer - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public OctreeQuantizer(Configuration configuration, QuantizerOptions options) { - private readonly int maxColors; - private readonly int bitDepth; - private readonly Octree octree; - private IMemoryOwner paletteOwner; - private ReadOnlyMemory palette; - private EuclideanPixelMap pixelMap; - private readonly bool isDithering; - private bool isDisposed; - - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer options defining quantization rules. - [MethodImpl(InliningOptions.ShortMethod)] - public OctreeQuantizer(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.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(this.bitDepth); - this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); - this.pixelMap = default; - this.palette = default; - this.isDithering = this.Options.Dither is not null; - this.isDisposed = false; - } + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + + this.maxColors = this.Options.MaxColors; + this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); + this.octree = new Octree(this.bitDepth); + this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.pixelMap = default; + this.palette = default; + this.isDithering = this.Options.Dither is not null; + this.isDisposed = false; + } - /// - public Configuration Configuration { get; } + /// + public Configuration Configuration { get; } - /// - public QuantizerOptions Options { get; } + /// + public QuantizerOptions Options { get; } - /// - public ReadOnlyMemory Palette + /// + public ReadOnlyMemory Palette + { + get { - get - { - QuantizerUtilities.CheckPaletteState(in this.palette); - return this.palette; - } + QuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; } + } - /// - public void AddPaletteColors(Buffer2DRegion pixelRegion) + /// + public void AddPaletteColors(Buffer2DRegion pixelRegion) + { + Rectangle bounds = pixelRegion.Rectangle; + Buffer2D source = pixelRegion.Buffer; + using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width)) { - Rectangle bounds = pixelRegion.Rectangle; - Buffer2D source = pixelRegion.Buffer; - 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 bufferSpan = buffer.GetSpan(); + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // Loop through each row - for (int y = bounds.Top; y < bounds.Bottom; y++) + for (int x = 0; x < bufferSpan.Length; x++) { - Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + Rgba32 rgba = bufferSpan[x]; - for (int x = 0; x < bufferSpan.Length; x++) - { - Rgba32 rgba = bufferSpan[x]; - - // Add the color to the Octree - this.octree.AddColor(rgba); - } + // Add the color to the Octree + this.octree.AddColor(rgba); } } + } - int paletteIndex = 0; - Span paletteSpan = this.paletteOwner.GetSpan(); + int paletteIndex = 0; + Span paletteSpan = this.paletteOwner.GetSpan(); - // On very rare occasions, (blur.png), the quantizer does not preserve a - // transparent entry when palletizing the captured colors. - // To workaround this we ensure the palette ends with the default color - // for higher bit depths. Lower bit depths will correctly reduce the palette. - // TODO: Investigate more evenly reduced palette reduction. - int max = this.maxColors; - if (this.bitDepth == 8) - { - max--; - } + // On very rare occasions, (blur.png), the quantizer does not preserve a + // transparent entry when palletizing the captured colors. + // To workaround this we ensure the palette ends with the default color + // for higher bit depths. Lower bit depths will correctly reduce the palette. + // TODO: Investigate more evenly reduced palette reduction. + int max = this.maxColors; + if (this.bitDepth == 8) + { + max--; + } + + this.octree.Palletize(paletteSpan, max, ref paletteIndex); + ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; + + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) + { + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + } + else + { + this.pixelMap.Clear(result); + } - this.octree.Palletize(paletteSpan, max, ref paletteIndex); - ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; + this.palette = result; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + { + // 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)) + { + return (byte)this.pixelMap.GetClosestColor(color, out match); + } + + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); + byte index = (byte)this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.paletteOwner?.Dispose(); + this.paletteOwner = null; + this.pixelMap?.Dispose(); + this.pixelMap = null; + } + } + + /// + /// Class which does the actual quantization. + /// + private sealed class Octree + { + /// + /// The root of the Octree + /// + private readonly OctreeNode root; + + /// + /// Maximum number of significant bits in the image + /// + private readonly int maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode previousNode; + + /// + /// Cache the previous color quantized + /// + private Rgba32 previousColor; - // When called multiple times by QuantizerUtilities.BuildPalette - // this prevents memory churn caused by reallocation. - if (this.pixelMap is null) + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of significant bits in the image + /// + public Octree(int maxColorBits) + { + this.maxColorBits = maxColorBits; + this.Leaves = 0; + this.ReducibleNodes = new OctreeNode[9]; + this.root = new OctreeNode(0, this.maxColorBits, this); + this.previousColor = default; + this.previousNode = null; + } + + /// + /// Gets or sets the number of leaves in the tree + /// + public int Leaves + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + + [MethodImpl(InliningOptions.ShortMethod)] + set; + } + + /// + /// Gets the array of reducible nodes + /// + private OctreeNode[] ReducibleNodes + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } + + /// + /// Add a given color value to the Octree + /// + /// 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(color)) { - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + // 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 = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); + } + else + { + // Just update the previous node + this.previousNode.Increment(ref color); + } } else { - this.pixelMap.Clear(result); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } - - this.palette = result; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - - /// + /// + /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// + /// The palette to fill. + /// The maximum number of colors + /// The palette index, used to calculate the final size of the palette. [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + public void Palletize(Span palette, int colorCount, ref int paletteIndex) { - // 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)) + while (this.Leaves > colorCount) { - return (byte)this.pixelMap.GetClosestColor(color, out match); + this.Reduce(); } - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - byte index = (byte)this.octree.GetPaletteIndex(color); - match = Unsafe.Add(ref paletteRef, index); - return index; + this.root.ConstructPalette(palette, ref paletteIndex); } - /// - public void Dispose() + /// + /// Get the palette index for the passed color + /// + /// The color to match. + /// + /// The index. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetPaletteIndex(TPixel color) { - if (!this.isDisposed) + Unsafe.SkipInit(out Rgba32 rgba); + color.ToRgba32(ref rgba); + return this.root.GetPaletteIndex(ref rgba, 0); + } + + /// + /// Keep track of the previous node that was quantized + /// + /// + /// The node last quantized + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void TrackPrevious(OctreeNode node) => this.previousNode = node; + + /// + /// Reduce the depth of the tree + /// + private void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this.maxColorBits - 1; + while ((index > 0) && (this.ReducibleNodes[index] is null)) { - this.isDisposed = true; - this.paletteOwner?.Dispose(); - this.paletteOwner = null; - this.pixelMap?.Dispose(); - this.pixelMap = null; + index--; } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = this.ReducibleNodes[index]; + this.ReducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + this.Leaves -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this.previousNode = null; } /// - /// Class which does the actual quantization. + /// Class which encapsulates each node in the tree /// - private sealed class Octree + public sealed class OctreeNode { /// - /// The root of the Octree + /// Pointers to any child nodes /// - private readonly OctreeNode root; + private readonly OctreeNode[] children; /// - /// Maximum number of significant bits in the image + /// Flag indicating that this is a leaf node /// - private readonly int maxColorBits; + private bool leaf; /// - /// Store the last node quantized + /// Number of pixels in this node /// - private OctreeNode previousNode; + private int pixelCount; /// - /// Cache the previous color quantized + /// Red component /// - private Rgba32 previousColor; + private int red; /// - /// Initializes a new instance of the class. + /// Green Component /// - /// - /// The maximum number of significant bits in the image - /// - public Octree(int maxColorBits) - { - this.maxColorBits = maxColorBits; - this.Leaves = 0; - this.ReducibleNodes = new OctreeNode[9]; - this.root = new OctreeNode(0, this.maxColorBits, this); - this.previousColor = default; - this.previousNode = null; - } + private int green; /// - /// Gets or sets the number of leaves in the tree + /// Blue component /// - public int Leaves - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - - [MethodImpl(InliningOptions.ShortMethod)] - set; - } + private int blue; /// - /// Gets the array of reducible nodes + /// The index of this node in the palette /// - private OctreeNode[] ReducibleNodes - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } + private int paletteIndex; /// - /// Add a given color value to the Octree + /// Initializes a new instance of the class. /// - /// The color to add. - public void AddColor(Rgba32 color) + /// 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) { - // Check if this request is for the same color as the last - if (this.previousColor.Equals(color)) + // Construct the new node + this.leaf = level == colorBits; + + this.red = this.green = this.blue = 0; + this.pixelCount = 0; + + // If a leaf, increment the leaf count + if (this.leaf) { - // 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 = color; - this.root.AddColor(ref color, this.maxColorBits, 0, this); - } - else - { - // Just update the previous node - this.previousNode.Increment(ref color); - } + octree.Leaves++; + this.NextReducible = null; + this.children = null; } else { - 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 - /// 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) - { - this.Reduce(); + // Otherwise add this to the reducible nodes + this.NextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + this.children = new OctreeNode[8]; } - - this.root.ConstructPalette(palette, ref paletteIndex); } /// - /// Get the palette index for the passed color + /// Gets the next reducible node /// - /// The color to match. - /// - /// The index. - /// - [MethodImpl(InliningOptions.ShortMethod)] - public int GetPaletteIndex(TPixel color) + public OctreeNode NextReducible { - Unsafe.SkipInit(out Rgba32 rgba); - color.ToRgba32(ref rgba); - return this.root.GetPaletteIndex(ref rgba, 0); + [MethodImpl(InliningOptions.ShortMethod)] + get; } /// - /// Keep track of the previous node that was quantized + /// Add a color into the tree /// - /// - /// The node last quantized - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void TrackPrevious(OctreeNode node) => this.previousNode = node; - - /// - /// Reduce the depth of the tree - /// - private void Reduce() + /// 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) { - // Find the deepest level containing at least one reducible node - int index = this.maxColorBits - 1; - while ((index > 0) && (this.ReducibleNodes[index] is null)) + // Update the color information if this is a leaf + if (this.leaf) { - index--; - } + this.Increment(ref color); - // Reduce the node most recently added to the list at level 'index' - OctreeNode node = this.ReducibleNodes[index]; - this.ReducibleNodes[index] = node.NextReducible; + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int index = GetColorIndex(ref color, level); - // Decrement the leaf count after reducing the node - this.Leaves -= node.Reduce(); + OctreeNode child = this.children[index]; + if (child is null) + { + // Create a new child node and store it in the array + child = new OctreeNode(level + 1, colorBits, octree); + this.children[index] = child; + } - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... - this.previousNode = null; + // Add the color to the child node + child.AddColor(ref color, colorBits, level + 1, octree); + } } /// - /// Class which encapsulates each node in the tree + /// Reduce this node by removing all of its children /// - public sealed class OctreeNode + /// The number of leaves removed + public int Reduce() { - /// - /// Pointers to any child nodes - /// - private readonly OctreeNode[] children; - - /// - /// Flag indicating that this is a leaf node - /// - private bool leaf; - - /// - /// Number of pixels in this node - /// - private int pixelCount; - - /// - /// Red component - /// - private int red; - - /// - /// Green Component - /// - private int green; - - /// - /// Blue component - /// - private int blue; - - /// - /// The index of this node in the palette - /// - private int paletteIndex; - - /// - /// 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. - public OctreeNode(int level, int colorBits, Octree octree) - { - // Construct the new node - this.leaf = level == colorBits; + this.red = this.green = this.blue = 0; + int childNodes = 0; - this.red = this.green = this.blue = 0; - this.pixelCount = 0; - - // If a leaf, increment the leaf count - if (this.leaf) - { - octree.Leaves++; - this.NextReducible = null; - this.children = null; - } - else + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + OctreeNode child = this.children[index]; + if (child != null) { - // Otherwise add this to the reducible nodes - this.NextReducible = octree.ReducibleNodes[level]; - octree.ReducibleNodes[level] = this; - this.children = new OctreeNode[8]; + this.red += child.red; + this.green += child.green; + this.blue += child.blue; + this.pixelCount += child.pixelCount; + ++childNodes; + this.children[index] = null; } } - /// - /// Gets the next reducible node - /// - public OctreeNode NextReducible + // Now change this to a leaf node + this.leaf = true; + + // Return the number of nodes to decrement the leaf count by + return childNodes - 1; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + [MethodImpl(InliningOptions.ColdPath)] + public void ConstructPalette(Span palette, ref int index) + { + if (this.leaf) { - [MethodImpl(InliningOptions.ShortMethod)] - get; + // Set the color of the palette entry + Vector3 vector = Vector3.Clamp( + new Vector3(this.red, this.green, this.blue) / this.pixelCount, + Vector3.Zero, + new Vector3(255)); + + Unsafe.SkipInit(out TPixel pixel); + pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); + palette[index] = pixel; + + // Consume the next palette index + this.paletteIndex = index++; } - - /// - /// Add a color into the tree - /// - /// 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) + else { - // Update the color information if this is a leaf - if (this.leaf) + // Loop through children looking for leaves + for (int i = 0; i < 8; i++) { - this.Increment(ref color); - - // Setup the previous node - octree.TrackPrevious(this); - } - else - { - // Go to the next level down in the tree - int index = GetColorIndex(ref color, level); - - OctreeNode child = this.children[index]; - if (child is null) - { - // Create a new child node and store it in the array - child = new OctreeNode(level + 1, colorBits, octree); - this.children[index] = child; - } - - // Add the color to the child node - child.AddColor(ref color, colorBits, level + 1, octree); + this.children[i]?.ConstructPalette(palette, ref index); } } + } - /// - /// Reduce this node by removing all of its children - /// - /// The number of leaves removed - public int Reduce() + /// + /// Return the palette index for the passed color + /// + /// The pixel data. + /// The level. + /// + /// The representing the index of the pixel in the palette. + /// + [MethodImpl(InliningOptions.ColdPath)] + public int GetPaletteIndex(ref Rgba32 pixel, int level) + { + if (this.leaf) { - this.red = this.green = this.blue = 0; - int childNodes = 0; - - // Loop through all children and add their information to this node - for (int index = 0; index < 8; index++) - { - OctreeNode child = this.children[index]; - if (child != null) - { - this.red += child.red; - this.green += child.green; - this.blue += child.blue; - this.pixelCount += child.pixelCount; - ++childNodes; - this.children[index] = null; - } - } - - // Now change this to a leaf node - this.leaf = true; - - // Return the number of nodes to decrement the leaf count by - return childNodes - 1; + return this.paletteIndex; } - /// - /// Traverse the tree, building up the color palette - /// - /// The palette - /// The current palette index - [MethodImpl(InliningOptions.ColdPath)] - public void ConstructPalette(Span palette, ref int index) + int colorIndex = GetColorIndex(ref pixel, level); + OctreeNode child = this.children[colorIndex]; + + int index = 0; + if (child != null) { - if (this.leaf) - { - // Set the color of the palette entry - Vector3 vector = Vector3.Clamp( - new Vector3(this.red, this.green, this.blue) / this.pixelCount, - Vector3.Zero, - new Vector3(255)); - - Unsafe.SkipInit(out TPixel pixel); - pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); - palette[index] = pixel; - - // Consume the next palette index - this.paletteIndex = index++; - } - else - { - // Loop through children looking for leaves - for (int i = 0; i < 8; i++) - { - this.children[i]?.ConstructPalette(palette, ref index); - } - } + index = child.GetPaletteIndex(ref pixel, level + 1); } - - /// - /// Return the palette index for the passed color - /// - /// The pixel data. - /// The level. - /// - /// The representing the index of the pixel in the palette. - /// - [MethodImpl(InliningOptions.ColdPath)] - public int GetPaletteIndex(ref Rgba32 pixel, int level) + else { - if (this.leaf) - { - return this.paletteIndex; - } - - int colorIndex = GetColorIndex(ref pixel, level); - OctreeNode child = this.children[colorIndex]; - - 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++) { - // Check other children. - for (int i = 0; i < this.children.Length; i++) + child = this.children[i]; + if (child != null) { - child = this.children[i]; - if (child != null) + int childIndex = child.GetPaletteIndex(ref pixel, level + 1); + if (childIndex != 0) { - int childIndex = child.GetPaletteIndex(ref pixel, level + 1); - if (childIndex != 0) - { - return childIndex; - } + return childIndex; } } } - - return index; } - /// - /// 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) - { - int shift = 7 - level; - byte mask = (byte)(1 << shift); + return index; + } - return ((color.R & mask) >> shift) - | (((color.G & mask) >> shift) << 1) - | (((color.B & mask) >> shift) << 2); - } + /// + /// 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) + { + int shift = 7 - level; + byte mask = (byte)(1 << shift); - /// - /// Increment the color count and add to the color information - /// - /// The pixel to add. - [MethodImpl(InliningOptions.ShortMethod)] - public void Increment(ref Rgba32 color) - { - this.pixelCount++; - this.red += color.R; - this.green += color.G; - this.blue += color.B; - } + 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(InliningOptions.ShortMethod)] + public void Increment(ref Rgba32 color) + { + this.pixelCount++; + this.red += color.R; + this.green += color.G; + this.blue += color.B; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 30043abfba..f40cde487f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -1,63 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Allows the quantization of images pixels using color palettes. +/// +public class PaletteQuantizer : IQuantizer { + private readonly ReadOnlyMemory colorPalette; + + /// + /// Initializes a new instance of the class. + /// + /// The color palette. + public PaletteQuantizer(ReadOnlyMemory palette) + : this(palette, new QuantizerOptions()) + { + } + /// - /// Allows the quantization of images pixels using color palettes. + /// Initializes a new instance of the class. /// - public class PaletteQuantizer : IQuantizer + /// The color palette. + /// The quantizer options defining quantization rules. + public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) { - private readonly ReadOnlyMemory colorPalette; - - /// - /// Initializes a new instance of the class. - /// - /// The color palette. - public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, new QuantizerOptions()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// 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)); - - this.colorPalette = palette; - this.Options = options; - } - - /// - public QuantizerOptions Options { get; } - - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); - - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(options, nameof(options)); - - // 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); - TPixel[] palette = new TPixel[length]; - - Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); - return new PaletteQuantizer(configuration, options, palette); - } + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(options, nameof(options)); + + this.colorPalette = palette; + this.Options = options; + } + + /// + public QuantizerOptions Options { get; } + + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel + => this.CreatePixelSpecificQuantizer(configuration, this.Options); + + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(options, nameof(options)); + + // 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); + TPixel[] palette = new TPixel[length]; + + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); + return new PaletteQuantizer(configuration, options, palette); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs index 1fcdbc5b1f..bb565affef 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs @@ -1,75 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Encapsulates methods to create a quantized image based upon the given palette. +/// +/// +/// The pixel format. +[SuppressMessage( + "Design", + "CA1001:Types that own disposable fields should be disposable", + Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] +internal struct PaletteQuantizer : IQuantizer + where TPixel : unmanaged, IPixel { + private EuclideanPixelMap pixelMap; + /// - /// Encapsulates methods to create a quantized image based upon the given palette. - /// + /// Initializes a new instance of the struct. /// - /// The pixel format. - [SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] - internal struct PaletteQuantizer : IQuantizer - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + /// The palette to use. + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) { - private EuclideanPixelMap pixelMap; + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer options defining quantization rules. - /// The palette to use. - [MethodImpl(InliningOptions.ShortMethod)] - public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette) - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - this.Configuration = configuration; - this.Options = options; - this.pixelMap = new EuclideanPixelMap(configuration, palette); - } + this.Configuration = configuration; + this.Options = options; + this.pixelMap = new EuclideanPixelMap(configuration, palette); + } - /// - public Configuration Configuration { get; } + /// + public Configuration Configuration { get; } - /// - public QuantizerOptions Options { get; } + /// + public QuantizerOptions Options { get; } - /// - public ReadOnlyMemory Palette => this.pixelMap.Palette; + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void AddPaletteColors(Buffer2DRegion pixelRegion) - { - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void AddPaletteColors(Buffer2DRegion pixelRegion) + { + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) - => (byte)this.pixelMap.GetClosestColor(color, out match); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + => (byte)this.pixelMap.GetClosestColor(color, out match); - /// - public void Dispose() - { - this.pixelMap?.Dispose(); - this.pixelMap = null; - } + /// + public void Dispose() + { + this.pixelMap?.Dispose(); + this.pixelMap = null; } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs index 09a37dafd7..87f990ccaa 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -3,28 +3,27 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Defines quantization processing for images to reduce the number of colors used in the image palette. +/// +public class QuantizeProcessor : IImageProcessor { /// - /// Defines quantization processing for images to reduce the number of colors used in the image palette. + /// Initializes a new instance of the class. /// - public class QuantizeProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The quantizer used to reduce the color palette. - public QuantizeProcessor(IQuantizer quantizer) - => this.Quantizer = quantizer; + /// The quantizer used to reduce the color palette. + public QuantizeProcessor(IQuantizer quantizer) + => this.Quantizer = quantizer; - /// - /// Gets the quantizer. - /// - public IQuantizer Quantizer { get; } + /// + /// Gets the quantizer. + /// + public IQuantizer Quantizer { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new QuantizeProcessor(configuration, this.Quantizer, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 7846f09688..f8be91bc2b 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -1,60 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Enables the quantization of images to reduce the number of colors used in the image palette. +/// +/// The pixel format. +internal class QuantizeProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly IQuantizer quantizer; + /// - /// Enables the quantization of images to reduce the number of colors used in the image palette. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class QuantizeProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer used to reduce the color palette. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public QuantizeProcessor(Configuration configuration, IQuantizer quantizer, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly IQuantizer quantizer; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer used to reduce the color palette. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public QuantizeProcessor(Configuration configuration, IQuantizer quantizer, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - Guard.NotNull(quantizer, nameof(quantizer)); - this.quantizer = quantizer; - } + Guard.NotNull(quantizer, nameof(quantizer)); + this.quantizer = quantizer; + } - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); - Configuration configuration = this.Configuration; - using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration); - using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); + Configuration configuration = this.Configuration; + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(configuration); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); - ReadOnlySpan paletteSpan = quantized.Palette.Span; - int offsetY = interest.Top; - int offsetX = interest.Left; - Buffer2D sourceBuffer = source.PixelBuffer; + ReadOnlySpan paletteSpan = quantized.Palette.Span; + int offsetY = interest.Top; + int offsetX = interest.Left; + Buffer2D sourceBuffer = source.PixelBuffer; - for (int y = interest.Y; y < interest.Height; y++) - { - Span row = sourceBuffer.DangerousGetRowSpan(y); - ReadOnlySpan quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); + for (int y = interest.Y; y < interest.Height; y++) + { + Span row = sourceBuffer.DangerousGetRowSpan(y); + ReadOnlySpan quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); - for (int x = interest.Left; x < interest.Right; x++) - { - row[x] = paletteSpan[quantizedRow[x - offsetX]]; - } + for (int x = interest.Left; x < interest.Right; x++) + { + row[x] = paletteSpan[quantizedRow[x - offsetX]]; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index 7d799da1fc..2bf4c6d56d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -3,36 +3,35 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Contains color quantization specific constants. +/// +public static class QuantizerConstants { /// - /// Contains color quantization specific constants. + /// The minimum number of colors to use when quantizing an image. /// - public static class QuantizerConstants - { - /// - /// The minimum number of colors to use when quantizing an image. - /// - public const int MinColors = 1; + public const int MinColors = 1; - /// - /// The maximum number of colors to use when quantizing an image. - /// - public const int MaxColors = 256; + /// + /// 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 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; + /// + /// 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; - } + /// + /// Gets the default dithering algorithm to use. + /// + public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs index 2880134ba3..f52cfd6ea7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -3,40 +3,39 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Defines options for quantization. +/// +public class QuantizerOptions { + private float ditherScale = QuantizerConstants.MaxDitherScale; + private int maxColors = QuantizerConstants.MaxColors; + /// - /// Defines options for quantization. + /// Gets or sets the algorithm to apply to the output image. + /// Defaults to ; set to for no dithering. /// - public class QuantizerOptions - { - private float ditherScale = QuantizerConstants.MaxDitherScale; - private int maxColors = QuantizerConstants.MaxColors; + public IDither Dither { get; set; } = QuantizerConstants.DefaultDither; - /// - /// 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 = Numerics.Clamp(value, QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); } - } + /// + /// 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 = Numerics.Clamp(value, 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 = Numerics.Clamp(value, QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } - } + /// + /// 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 = Numerics.Clamp(value, QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index a869cc42ae..04e8124037 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -1,149 +1,147 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Contains utility methods for instances. +/// +public static class QuantizerUtilities { /// - /// Contains utility methods for instances. + /// Helper method for throwing an exception when a frame quantizer palette has + /// been requested but not built yet. /// - public static class QuantizerUtilities + /// 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 { - /// - /// 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)) { - if (palette.Equals(default)) - { - throw new InvalidOperationException("Frame Quantizer palette has not been built."); - } + throw new InvalidOperationException("Frame Quantizer palette has not been built."); } + } - /// - /// Execute both steps of the quantization. - /// - /// The pixel specific quantizer. - /// The source image frame to quantize. - /// The bounds within the frame to quantize. - /// The pixel type. - /// - /// A representing a quantized version of the source frame pixels. - /// - public static IndexedImageFrame BuildPaletteAndQuantizeFrame( - this IQuantizer quantizer, - ImageFrame source, - Rectangle bounds) - where TPixel : unmanaged, IPixel - { - Guard.NotNull(quantizer, nameof(quantizer)); - Guard.NotNull(source, nameof(source)); + /// + /// Execute both steps of the quantization. + /// + /// The pixel specific quantizer. + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// The pixel type. + /// + /// A representing a quantized version of the source frame pixels. + /// + public static IndexedImageFrame BuildPaletteAndQuantizeFrame( + this IQuantizer quantizer, + ImageFrame source, + Rectangle bounds) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(quantizer, nameof(quantizer)); + Guard.NotNull(source, nameof(source)); - var interest = Rectangle.Intersect(source.Bounds(), bounds); - Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); + var interest = Rectangle.Intersect(source.Bounds(), bounds); + Buffer2DRegion region = source.PixelBuffer.GetRegion(interest); - // Collect the palette. Required before the second pass runs. - quantizer.AddPaletteColors(region); - return quantizer.QuantizeFrame(source, bounds); - } + // Collect the palette. Required before the second pass runs. + quantizer.AddPaletteColors(region); + return quantizer.QuantizeFrame(source, bounds); + } - /// - /// Quantizes an image frame and return the resulting output pixels. - /// - /// The type of frame quantizer. - /// The pixel format. - /// The pixel specific 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, IQuantizer - where TPixel : unmanaged, IPixel - { - Guard.NotNull(source, nameof(source)); - var interest = Rectangle.Intersect(source.Bounds(), bounds); + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The pixel specific 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, IQuantizer + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + var interest = Rectangle.Intersect(source.Bounds(), bounds); - var destination = new IndexedImageFrame( - quantizer.Configuration, - interest.Width, - interest.Height, - quantizer.Palette); + 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; + 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); } - internal static void BuildPalette( - this IQuantizer quantizer, - IPixelSamplingStrategy pixelSamplingStrategy, - Image image) - where TPixel : unmanaged, IPixel + return destination; + } + + internal static void BuildPalette( + this IQuantizer quantizer, + IPixelSamplingStrategy pixelSamplingStrategy, + Image image) + where TPixel : unmanaged, IPixel + { + foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(image)) { - foreach (Buffer2DRegion region in pixelSamplingStrategy.EnumeratePixelRegions(image)) - { - quantizer.AddPaletteColors(region); - } + quantizer.AddPaletteColors(region); } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void SecondPass( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IQuantizer + where TPixel : unmanaged, IPixel + { + IDither dither = quantizer.Options.Dither; + Buffer2D sourceBuffer = source.PixelBuffer; - [MethodImpl(InliningOptions.ShortMethod)] - private static void SecondPass( - ref TFrameQuantizer quantizer, - ImageFrame source, - IndexedImageFrame destination, - Rectangle bounds) - where TFrameQuantizer : struct, IQuantizer - where TPixel : unmanaged, IPixel + if (dither is null) { - IDither dither = quantizer.Options.Dither; - Buffer2D sourceBuffer = source.PixelBuffer; + int offsetY = bounds.Top; + int offsetX = bounds.Left; - if (dither is null) + for (int y = bounds.Y; y < bounds.Height; y++) { - int offsetY = bounds.Top; - int offsetX = bounds.Left; + Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); + Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); - for (int y = bounds.Y; y < bounds.Height; y++) + for (int x = bounds.Left; x < bounds.Right; x++) { - Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); - Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); - - for (int x = bounds.Left; x < bounds.Right; x++) - { - destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); - } + destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); } - - return; } - dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); + return; } + + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index 96da79de8c..604cae6681 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,28 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// A palette quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. +/// +public class WebSafePaletteQuantizer : PaletteQuantizer { /// - /// A palette quantizer consisting of web safe colors as defined in the CSS Color Module Level 4. + /// Initializes a new instance of the class. /// - public class WebSafePaletteQuantizer : PaletteQuantizer + public WebSafePaletteQuantizer() + : this(new QuantizerOptions()) { - /// - /// Initializes a new instance of the class. - /// - public WebSafePaletteQuantizer() - : this(new QuantizerOptions()) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WebSafePaletteQuantizer(QuantizerOptions options) - : base(Color.WebSafePalette, options) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The quantizer options defining quantization rules. + public WebSafePaletteQuantizer(QuantizerOptions options) + : base(Color.WebSafePalette, options) + { } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index 9769c5aa11..023ee7f2e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,29 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// A palette quantizer consisting 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 class WernerPaletteQuantizer : PaletteQuantizer { /// - /// A palette quantizer consisting 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 + /// Initializes a new instance of the class. /// - public class WernerPaletteQuantizer : PaletteQuantizer + public WernerPaletteQuantizer() + : this(new QuantizerOptions()) { - /// - /// Initializes a new instance of the class. - /// - public WernerPaletteQuantizer() - : this(new QuantizerOptions()) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WernerPaletteQuantizer(QuantizerOptions options) - : base(Color.WernerPalette, options) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The quantizer options defining quantization rules. + public WernerPaletteQuantizer(QuantizerOptions options) + : base(Color.WernerPalette, options) + { } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 945b5c47df..86d798d965 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -3,43 +3,42 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer +/// +public class WuQuantizer : IQuantizer { /// - /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer + /// Initializes a new instance of the class + /// using the default . /// - public class WuQuantizer : IQuantizer + public WuQuantizer() + : this(new QuantizerOptions()) { - /// - /// Initializes a new instance of the class - /// using the default . - /// - public WuQuantizer() - : this(new QuantizerOptions()) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The quantizer options defining quantization rules. - public WuQuantizer(QuantizerOptions options) - { - Guard.NotNull(options, nameof(options)); - this.Options = options; - } + /// + /// Initializes a new instance of the class. + /// + /// The quantizer options defining quantization rules. + public WuQuantizer(QuantizerOptions options) + { + Guard.NotNull(options, nameof(options)); + this.Options = options; + } - /// - public QuantizerOptions Options { get; } + /// + public QuantizerOptions Options { get; } - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) - where TPixel : unmanaged, IPixel - => this.CreatePixelSpecificQuantizer(configuration, this.Options); + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration) + where TPixel : unmanaged, IPixel + => this.CreatePixelSpecificQuantizer(configuration, this.Options); - /// - public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) - where TPixel : unmanaged, IPixel - => new WuQuantizer(configuration, options); - } + /// + public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new WuQuantizer(configuration, options); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 5f04669b74..1b39c85af6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Numerics; @@ -10,896 +9,895 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Processing.Processors.Quantization; + +/// +/// An implementation of Wu's color quantizer with alpha channel. +/// +/// +/// +/// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) +/// (see Graphics Gems volume II, pages 126-133) +/// (). +/// +/// +/// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel +/// +/// +/// +/// Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks. +/// For speed no nearest neighbor search is done. Slightly better performance can be expected by more sophisticated +/// but more expensive versions. +/// +/// +/// The pixel format. +[SuppressMessage( + "Design", + "CA1001:Types that own disposable fields should be disposable", + Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] +internal struct WuQuantizer : IQuantizer + where TPixel : unmanaged, IPixel { + private readonly MemoryAllocator memoryAllocator; + + // The following two variables determine the amount of bits to preserve when calculating the histogram. + // Reducing the value of these numbers the granularity of the color maps produced, making it much faster + // and using much less memory but potentially less accurate. Current results are very good though! + /// - /// An implementation of Wu's color quantizer with alpha channel. + /// The index bits. 6 in original code. /// - /// - /// - /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) - /// (see Graphics Gems volume II, pages 126-133) - /// (). - /// - /// - /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel - /// - /// - /// - /// Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks. - /// For speed no nearest neighbor search is done. Slightly better performance can be expected by more sophisticated - /// but more expensive versions. - /// - /// - /// The pixel format. - [SuppressMessage( - "Design", - "CA1001:Types that own disposable fields should be disposable", - Justification = "https://github.com/dotnet/roslyn-analyzers/issues/6151")] - internal struct WuQuantizer : IQuantizer - where TPixel : unmanaged, IPixel - { - private readonly MemoryAllocator memoryAllocator; + private const int IndexBits = 5; - // The following two variables determine the amount of bits to preserve when calculating the histogram. - // Reducing the value of these numbers the granularity of the color maps produced, making it much faster - // and using much less memory but potentially less accurate. Current results are very good though! + /// + /// The index alpha bits. 3 in original code. + /// + private const int IndexAlphaBits = 5; - /// - /// The index bits. 6 in original code. - /// - private const int IndexBits = 5; + /// + /// The index count. + /// + private const int IndexCount = (1 << IndexBits) + 1; - /// - /// The index alpha bits. 3 in original code. - /// - private const int IndexAlphaBits = 5; + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - /// - /// The index count. - /// - private const int IndexCount = (1 << IndexBits) + 1; + /// + /// The table length. Now 1185921. originally 2471625. + /// + private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + + 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; - /// - /// The index alpha count. - /// - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + /// + /// Initializes a new instance of the struct. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public WuQuantizer(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.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.colorCube = new Box[this.maxColors]; + this.isDisposed = false; + this.pixelMap = default; + this.palette = default; + this.isDithering = this.isDithering = this.Options.Dither is not null; + } - /// - /// The table length. Now 1185921. originally 2471625. - /// - private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - - 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; + /// + public Configuration Configuration { get; } - /// - /// Initializes a new instance of the struct. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer options defining quantization rules. - [MethodImpl(InliningOptions.ShortMethod)] - public WuQuantizer(Configuration configuration, QuantizerOptions options) + /// + public QuantizerOptions Options { get; } + + /// + public ReadOnlyMemory Palette + { + get { - Guard.NotNull(configuration, nameof(configuration)); - Guard.NotNull(options, nameof(options)); - - 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.colorCube = new Box[this.maxColors]; - this.isDisposed = false; - this.pixelMap = default; - this.palette = default; - this.isDithering = this.isDithering = this.Options.Dither is not null; + QuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; } + } - /// - public Configuration Configuration { get; } + /// + public void AddPaletteColors(Buffer2DRegion pixelRegion) + { + Rectangle bounds = pixelRegion.Rectangle; + Buffer2D source = pixelRegion.Buffer; - /// - public QuantizerOptions Options { get; } + this.Build3DHistogram(source, bounds); + this.Get3DMoments(this.memoryAllocator); + this.BuildCube(); - /// - public ReadOnlyMemory Palette + // Slice again since maxColors has been updated since the buffer was created. + Span paletteSpan = this.paletteOwner.GetSpan()[..this.maxColors]; + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + for (int k = 0; k < paletteSpan.Length; k++) { - get + this.Mark(ref this.colorCube[k], (byte)k); + + Moment moment = Volume(ref this.colorCube[k], momentsSpan); + + if (moment.Weight > 0) { - QuantizerUtilities.CheckPaletteState(in this.palette); - return this.palette; + ref TPixel color = ref paletteSpan[k]; + color.FromScaledVector4(moment.Normalize()); } } - /// - public void AddPaletteColors(Buffer2DRegion pixelRegion) + ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; + if (this.isDithering) { - Rectangle bounds = pixelRegion.Rectangle; - Buffer2D source = pixelRegion.Buffer; - - this.Build3DHistogram(source, bounds); - this.Get3DMoments(this.memoryAllocator); - this.BuildCube(); - - // Slice again since maxColors has been updated since the buffer was created. - Span paletteSpan = this.paletteOwner.GetSpan()[..this.maxColors]; - ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); - for (int k = 0; k < paletteSpan.Length; k++) + // When called multiple times by QuantizerUtilities.BuildPalette + // this prevents memory churn caused by reallocation. + if (this.pixelMap is null) { - this.Mark(ref this.colorCube[k], (byte)k); - - Moment moment = Volume(ref this.colorCube[k], momentsSpan); - - if (moment.Weight > 0) - { - ref TPixel color = ref paletteSpan[k]; - color.FromScaledVector4(moment.Normalize()); - } + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); } - - ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; - if (this.isDithering) + else { - // When called multiple times by QuantizerUtilities.BuildPalette - // this prevents memory churn caused by reallocation. - if (this.pixelMap is null) - { - this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - } - else - { - this.pixelMap.Clear(result); - } + this.pixelMap.Clear(result); } - - this.palette = result; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) - => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + this.palette = result; + } - /// - public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); + + /// + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + { + if (this.isDithering) { - if (this.isDithering) - { - return (byte)this.pixelMap.GetClosestColor(color, out match); - } + return (byte)this.pixelMap.GetClosestColor(color, out match); + } - Rgba32 rgba = default; - color.ToRgba32(ref rgba); + Rgba32 rgba = default; + color.ToRgba32(ref rgba); - const int shift = 8 - IndexBits; - int r = rgba.R >> shift; - int g = rgba.G >> shift; - int b = rgba.B >> shift; - int a = rgba.A >> (8 - IndexAlphaBits); + const int shift = 8 - IndexBits; + int r = rgba.R >> shift; + int g = rgba.G >> shift; + int b = rgba.B >> shift; + int a = rgba.A >> (8 - IndexAlphaBits); - ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); - byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; - ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - match = Unsafe.Add(ref paletteRef, index); - return index; - } + ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); + match = Unsafe.Add(ref paletteRef, index); + return index; + } - /// - public void Dispose() + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.isDisposed = true; - this.momentsOwner?.Dispose(); - this.tagsOwner?.Dispose(); - this.paletteOwner?.Dispose(); - this.momentsOwner = null; - this.tagsOwner = null; - this.paletteOwner = null; - this.pixelMap?.Dispose(); - this.pixelMap = null; - } + this.isDisposed = true; + this.momentsOwner?.Dispose(); + this.tagsOwner?.Dispose(); + this.paletteOwner?.Dispose(); + this.momentsOwner = null; + this.tagsOwner = null; + this.paletteOwner = null; + this.pixelMap?.Dispose(); + this.pixelMap = null; } + } - /// - /// Gets the index of the given color in the palette. - /// - /// The red value. - /// The green value. - /// The blue value. - /// The alpha value. - /// The index. - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetPaletteIndex(int r, int g, int b, int a) - => (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - - /// - /// Computes sum over a box of any given statistic. - /// - /// The cube. - /// The moment. - /// The result. - private static Moment Volume(ref Box cube, ReadOnlySpan moments) - => 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)]; + /// + /// Gets the index of the given color in the palette. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetPaletteIndex(int r, int g, int b, int a) + => (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; - /// - /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). - /// - /// The cube. - /// The direction. - /// The moment. - /// The result. - /// Invalid direction. - private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) - => direction switch - { - // Red - 3 => -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 - 2 => -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 - 1 => -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 - 0 => -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)], - _ => throw new ArgumentOutOfRangeException(nameof(direction)), - }; + /// + /// Computes sum over a box of any given statistic. + /// + /// The cube. + /// The moment. + /// The result. + private static Moment Volume(ref Box cube, ReadOnlySpan moments) + => 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)]; - /// - /// Computes remainder of Volume(cube, moment), substituting position for RMax, GMax, BMax, or AMax (depending on direction). - /// - /// The cube. - /// The direction. - /// The position. - /// The moment. - /// The result. - /// Invalid direction. - private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) - => direction switch - { - // Red - 3 => 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 - 2 => 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 - 1 => 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 - 0 => 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)], - _ => throw new ArgumentOutOfRangeException(nameof(direction)), - }; + /// + /// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). + /// + /// The cube. + /// The direction. + /// The moment. + /// The result. + /// Invalid direction. + private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) + => direction switch + { + // Red + 3 => -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 + 2 => -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 + 1 => -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 + 0 => -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)], + _ => throw new ArgumentOutOfRangeException(nameof(direction)), + }; - /// - /// Builds a 3-D color histogram of counts, r/g/b, c^2. - /// - /// The source data. - /// The bounds within the source image to quantize. - private void Build3DHistogram(Buffer2D source, Rectangle bounds) + /// + /// Computes remainder of Volume(cube, moment), substituting position for RMax, GMax, BMax, or AMax (depending on direction). + /// + /// The cube. + /// The direction. + /// The position. + /// The moment. + /// The result. + /// Invalid direction. + private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) + => direction switch { - Span momentSpan = this.momentsOwner.GetSpan(); + // Red + 3 => 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 + 2 => 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 + 1 => 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 + 0 => 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)], + _ => throw new ArgumentOutOfRangeException(nameof(direction)), + }; - // Build up the 3-D color histogram - using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); - Span bufferSpan = buffer.GetSpan(); + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The source data. + /// The bounds within the source image to quantize. + private void Build3DHistogram(Buffer2D source, Rectangle bounds) + { + Span momentSpan = this.momentsOwner.GetSpan(); - for (int y = bounds.Top; y < bounds.Bottom; y++) - { - Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); - PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + // Build up the 3-D color histogram + using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); - for (int x = 0; x < bufferSpan.Length; x++) - { - Rgba32 rgba = bufferSpan[x]; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); + + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[x]; - 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; + 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; - momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; - } + momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; } } + } - /// - /// 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 allocator) + /// + /// 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 allocator) + { + 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(); + const int indexBits2 = IndexBits * 2; + const int indexAndAlphaBits = IndexBits + IndexAlphaBits; + const int indexBitsAndAlphaBits1 = IndexBits + IndexAlphaBits + 1; + int baseIndex = GetPaletteIndex(1, 0, 0, 0); + + for (int r = 1; r < IndexCount; r++) { - 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(); - const int indexBits2 = IndexBits * 2; - const int indexAndAlphaBits = IndexBits + IndexAlphaBits; - const int indexBitsAndAlphaBits1 = IndexBits + IndexAlphaBits + 1; - int baseIndex = GetPaletteIndex(1, 0, 0, 0); - - for (int r = 1; r < IndexCount; r++) + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << (indexBits2 + IndexAlphaBits)) + + (r << indexBitsAndAlphaBits1) + + (r << indexBits2) + + (r << (IndexBits + 1)) + + r; + + volumeSpan.Clear(); + + for (int g = 1; g < IndexCount; g++) { - // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the - // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 - // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations - // in the form of ind1* manually. - int ind1R = (r << (indexBits2 + IndexAlphaBits)) + - (r << indexBitsAndAlphaBits1) + - (r << indexBits2) + - (r << (IndexBits + 1)) + - r; - - volumeSpan.Clear(); - - for (int g = 1; g < IndexCount; g++) - { - int ind1G = ind1R + - (g << indexAndAlphaBits) + - (g << IndexBits) + - g; - int r_g = r + g; + int ind1G = ind1R + + (g << indexAndAlphaBits) + + (g << IndexBits) + + g; + int r_g = r + g; - areaSpan.Clear(); + areaSpan.Clear(); - for (int b = 1; b < IndexCount; b++) - { - int ind1B = ind1G + - ((r_g + b) << IndexAlphaBits) + - b; + for (int b = 1; b < IndexCount; b++) + { + int ind1B = ind1G + + ((r_g + b) << IndexAlphaBits) + + b; - Moment line = default; - int bIndexAlphaOffset = b * IndexAlphaCount; - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = ind1B + a; + Moment line = default; + int bIndexAlphaOffset = b * IndexAlphaCount; + for (int a = 1; a < IndexAlphaCount; a++) + { + int ind1 = ind1B + a; - line += momentSpan[ind1]; + line += momentSpan[ind1]; - areaSpan[a] += line; + areaSpan[a] += line; - int inv = bIndexAlphaOffset + a; - volumeSpan[inv] += areaSpan[a]; + int inv = bIndexAlphaOffset + a; + volumeSpan[inv] += areaSpan[a]; - int ind2 = ind1 - baseIndex; - momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv]; - } + int ind2 = ind1 - baseIndex; + momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv]; } } } } + } - /// - /// Computes the weighted variance of a box cube. - /// - /// The cube. - /// The . - private double Variance(ref Box cube) - { - 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)]; - - Vector4 vector = new(volume.R, volume.G, volume.B, volume.A); - return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight); - } + /// + /// Computes the weighted variance of a box cube. + /// + /// The cube. + /// The . + private double Variance(ref Box cube) + { + 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)]; + + Vector4 vector = new(volume.R, volume.G, volume.B, volume.A); + return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight); + } - /// - /// We want to minimize the sum of the variances of two sub-boxes. - /// The sum(c^2) terms can be ignored since their sum over both sub-boxes - /// is the same (the sum for the whole box) no matter where we split. - /// The remaining terms have a minus sign in the variance formula, - /// so we drop the minus sign and maximize the sum of the two terms. - /// - /// The cube. - /// The direction. - /// The first position. - /// The last position. - /// The cutting point. - /// The whole moment. - /// The . - private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) + /// + /// We want to minimize the sum of the variances of two sub-boxes. + /// The sum(c^2) terms can be ignored since their sum over both sub-boxes + /// is the same (the sum for the whole box) no matter where we split. + /// The remaining terms have a minus sign in the variance formula, + /// so we drop the minus sign and maximize the sum of the two terms. + /// + /// The cube. + /// The direction. + /// The first position. + /// The last position. + /// The cutting point. + /// The whole moment. + /// The . + private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) + { + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment bottom = Bottom(ref cube, direction, momentSpan); + + float max = 0F; + cut = -1; + + for (int i = first; i < last; i++) { - ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); - Moment bottom = Bottom(ref cube, direction, momentSpan); + Moment half = bottom + Top(ref cube, direction, i, momentSpan); + + if (half.Weight == 0) + { + continue; + } - float max = 0F; - cut = -1; + Vector4 vector = new(half.R, half.G, half.B, half.A); + float temp = Vector4.Dot(vector, vector) / half.Weight; - for (int i = first; i < last; i++) + half = whole - half; + + if (half.Weight == 0) { - Moment half = bottom + Top(ref cube, direction, i, momentSpan); + continue; + } - if (half.Weight == 0) - { - continue; - } + vector = new Vector4(half.R, half.G, half.B, half.A); + temp += Vector4.Dot(vector, vector) / half.Weight; + + if (temp > max) + { + max = temp; + cut = i; + } + } - Vector4 vector = new(half.R, half.G, half.B, half.A); - float temp = Vector4.Dot(vector, vector) / half.Weight; + return max; + } - half = whole - half; + /// + /// Cuts a box. + /// + /// The first set. + /// The second set. + /// Returns a value indicating whether the box has been split. + private bool Cut(ref Box set1, ref Box set2) + { + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment whole = Volume(ref set1, momentSpan); - if (half.Weight == 0) - { - continue; - } + 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); - vector = new Vector4(half.R, half.G, half.B, half.A); - temp += Vector4.Dot(vector, vector) / half.Weight; + int dir; - if (temp > max) - { - max = temp; - cut = i; - } - } + if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA)) + { + dir = 3; - return max; + if (cutR < 0) + { + return false; + } + } + else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA)) + { + dir = 2; + } + else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA)) + { + dir = 1; + } + else + { + dir = 0; } - /// - /// Cuts a box. - /// - /// The first set. - /// The second set. - /// Returns a value indicating whether the box has been split. - private bool Cut(ref Box set1, ref Box set2) + set2.RMax = set1.RMax; + set2.GMax = set1.GMax; + set2.BMax = set1.BMax; + set2.AMax = set1.AMax; + + switch (dir) { - ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); - Moment whole = Volume(ref set1, momentSpan); + // Red + case 3: + set2.RMin = set1.RMax = cutR; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; + break; + + // Green + case 2: + set2.GMin = set1.GMax = cutG; + set2.RMin = set1.RMin; + set2.BMin = set1.BMin; + set2.AMin = set1.AMin; + break; + + // Blue + case 1: + set2.BMin = set1.BMax = cutB; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.AMin = set1.AMin; + break; + + // Alpha + case 0: + set2.AMin = set1.AMax = cutA; + set2.RMin = set1.RMin; + set2.GMin = set1.GMin; + set2.BMin = set1.BMin; + break; + } - 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); + set1.Volume = (set1.RMax - set1.RMin) * (set1.GMax - set1.GMin) * (set1.BMax - set1.BMin) * (set1.AMax - set1.AMin); + set2.Volume = (set2.RMax - set2.RMin) * (set2.GMax - set2.GMin) * (set2.BMax - set2.BMin) * (set2.AMax - set2.AMin); - int dir; + return true; + } - if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA)) + /// + /// Marks a color space tag. + /// + /// The cube. + /// A label. + private void Mark(ref Box cube, byte label) + { + Span tagSpan = this.tagsOwner.GetSpan(); + + for (int r = cube.RMin + 1; r <= cube.RMax; r++) + { + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + r; + + for (int g = cube.GMin + 1; g <= cube.GMax; g++) { - dir = 3; + int ind1G = ind1R + + (g << (IndexBits + IndexAlphaBits)) + + (g << IndexBits) + + g; + int r_g = r + g; - if (cutR < 0) + for (int b = cube.BMin + 1; b <= cube.BMax; b++) { - return false; + int ind1B = ind1G + + ((r_g + b) << IndexAlphaBits) + + b; + + for (int a = cube.AMin + 1; a <= cube.AMax; a++) + { + int index = ind1B + a; + + tagSpan[index] = label; + } } } - else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA)) - { - dir = 2; - } - else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA)) + } + } + + /// + /// Builds the cube. + /// + private void BuildCube() + { + // 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; + cube.RMax = cube.GMax = cube.BMax = IndexCount - 1; + cube.AMax = IndexAlphaCount - 1; + + int next = 0; + + 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)) { - dir = 1; + vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0D; + vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D; } else { - dir = 0; + vv[next] = 0D; + i--; } - set2.RMax = set1.RMax; - set2.GMax = set1.GMax; - set2.BMax = set1.BMax; - set2.AMax = set1.AMax; + next = 0; - switch (dir) + double temp = vv[0]; + for (int k = 1; k <= i; k++) { - // Red - case 3: - set2.RMin = set1.RMax = cutR; - set2.GMin = set1.GMin; - set2.BMin = set1.BMin; - set2.AMin = set1.AMin; - break; - - // Green - case 2: - set2.GMin = set1.GMax = cutG; - set2.RMin = set1.RMin; - set2.BMin = set1.BMin; - set2.AMin = set1.AMin; - break; - - // Blue - case 1: - set2.BMin = set1.BMax = cutB; - set2.RMin = set1.RMin; - set2.GMin = set1.GMin; - set2.AMin = set1.AMin; - break; - - // Alpha - case 0: - set2.AMin = set1.AMax = cutA; - set2.RMin = set1.RMin; - set2.GMin = set1.GMin; - set2.BMin = set1.BMin; - break; + if (vv[k] > temp) + { + temp = vv[k]; + next = k; + } } - set1.Volume = (set1.RMax - set1.RMin) * (set1.GMax - set1.GMin) * (set1.BMax - set1.BMin) * (set1.AMax - set1.AMin); - set2.Volume = (set2.RMax - set2.RMin) * (set2.GMax - set2.GMin) * (set2.BMax - set2.BMin) * (set2.AMax - set2.AMin); - - return true; + if (temp <= 0D) + { + this.maxColors = i + 1; + break; + } } + } + private struct Moment + { /// - /// Marks a color space tag. + /// Moment of r*P(c). /// - /// The cube. - /// A label. - private void Mark(ref Box cube, byte label) - { - Span tagSpan = this.tagsOwner.GetSpan(); + public long R; - for (int r = cube.RMin + 1; r <= cube.RMax; r++) - { - // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the - // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 - // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations - // in the form of ind1* manually. - int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + - (r << (IndexBits + IndexAlphaBits + 1)) + - (r << (IndexBits * 2)) + - (r << (IndexBits + 1)) + - r; - - for (int g = cube.GMin + 1; g <= cube.GMax; g++) - { - int ind1G = ind1R + - (g << (IndexBits + IndexAlphaBits)) + - (g << IndexBits) + - g; - int r_g = r + g; + /// + /// Moment of g*P(c). + /// + public long G; - for (int b = cube.BMin + 1; b <= cube.BMax; b++) - { - int ind1B = ind1G + - ((r_g + b) << IndexAlphaBits) + - b; + /// + /// Moment of b*P(c). + /// + public long B; - for (int a = cube.AMin + 1; a <= cube.AMax; a++) - { - int index = ind1B + a; + /// + /// Moment of a*P(c). + /// + public long A; - tagSpan[index] = label; - } - } - } - } - } + /// + /// Moment of P(c). + /// + public long Weight; /// - /// Builds the cube. + /// Moment of c^2*P(c). /// - private void BuildCube() - { - // Store the volume variance. - using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); - Span vv = vvOwner.GetSpan(); + public double Moment2; - ref Box cube = ref this.colorCube[0]; - cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; - cube.RMax = cube.GMax = cube.BMax = IndexCount - 1; - cube.AMax = IndexAlphaCount - 1; + [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 next = 0; + [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; + } - 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) : 0D; - vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D; - } - else - { - vv[next] = 0D; - i--; - } + [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; + } - next = 0; + [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++; - double temp = vv[0]; - for (int k = 1; k <= i; k++) - { - if (vv[k] > temp) - { - temp = vv[k]; - next = k; - } - } + Vector4 vector = new(y.R, y.G, y.B, y.A); + x.Moment2 += Vector4.Dot(vector, vector); - if (temp <= 0D) - { - this.maxColors = i + 1; - break; - } - } + return x; } - private struct Moment - { - /// - /// 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) - { - 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; - } + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 Normalize() + => new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; + } - [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; - } + /// + /// Represents a box color cube. + /// + private struct Box : IEquatable + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int RMin; - [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; - } + /// + /// Gets or sets the max red value, inclusive. + /// + public int RMax; - [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++; + /// + /// Gets or sets the min green value, exclusive. + /// + public int GMin; - Vector4 vector = new(y.R, y.G, y.B, y.A); - x.Moment2 += Vector4.Dot(vector, vector); + /// + /// Gets or sets the max green value, inclusive. + /// + public int GMax; - return x; - } + /// + /// Gets or sets the min blue value, exclusive. + /// + public int BMin; - [MethodImpl(InliningOptions.ShortMethod)] - public readonly Vector4 Normalize() - => new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; - } + /// + /// Gets or sets the max blue value, inclusive. + /// + public int BMax; + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int AMin; + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int AMax; /// - /// Represents a box color cube. + /// Gets or sets the volume. /// - private struct Box : IEquatable + public int Volume; + + /// + public override readonly bool Equals(object obj) + => obj is Box box + && this.Equals(box); + + /// + public readonly bool Equals(Box other) => + this.RMin == other.RMin + && this.RMax == other.RMax + && this.GMin == other.GMin + && this.GMax == other.GMax + && this.BMin == other.BMin + && this.BMax == other.BMax + && this.AMin == other.AMin + && this.AMax == other.AMax + && this.Volume == other.Volume; + + /// + public override readonly int GetHashCode() { - /// - /// Gets or sets the min red value, exclusive. - /// - public int RMin; - - /// - /// Gets or sets the max red value, inclusive. - /// - public int RMax; - - /// - /// Gets or sets the min green value, exclusive. - /// - public int GMin; - - /// - /// Gets or sets the max green value, inclusive. - /// - public int GMax; - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int BMin; - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int BMax; - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int AMin; - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int AMax; - - /// - /// Gets or sets the volume. - /// - public int Volume; - - /// - public override readonly bool Equals(object obj) - => obj is Box box - && this.Equals(box); - - /// - public readonly bool Equals(Box other) => - this.RMin == other.RMin - && this.RMax == other.RMax - && this.GMin == other.GMin - && this.GMax == other.GMax - && this.BMin == other.BMin - && this.BMax == other.BMax - && this.AMin == other.AMin - && this.AMax == other.AMax - && this.Volume == other.Volume; - - /// - public override readonly int GetHashCode() - { - HashCode hash = default; - hash.Add(this.RMin); - hash.Add(this.RMax); - hash.Add(this.GMin); - hash.Add(this.GMax); - hash.Add(this.BMin); - hash.Add(this.BMax); - hash.Add(this.AMin); - hash.Add(this.AMax); - hash.Add(this.Volume); - return hash.ToHashCode(); - } + HashCode hash = default; + hash.Add(this.RMin); + hash.Add(this.RMax); + hash.Add(this.GMin); + hash.Add(this.GMax); + hash.Add(this.BMin); + hash.Add(this.BMax); + hash.Add(this.AMin); + hash.Add(this.AMax); + hash.Add(this.Volume); + return hash.ToHashCode(); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index eeaf0a15bc..69e8d67e86 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -1,36 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a crop operation on an image. +/// +public sealed class CropProcessor : CloningImageProcessor { /// - /// Defines a crop operation on an image. + /// Initializes a new instance of the class. /// - public sealed class CropProcessor : CloningImageProcessor + /// The target cropped rectangle. + /// The source image size. + public CropProcessor(Rectangle cropRectangle, Size sourceSize) { - /// - /// Initializes a new instance of the class. - /// - /// The target cropped rectangle. - /// The source image size. - public CropProcessor(Rectangle cropRectangle, Size sourceSize) - { - // Check bounds here and throw if we are passed a rectangle exceeding our source bounds. - Guard.IsTrue( - new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), - nameof(cropRectangle), - "Crop rectangle should be smaller than the source bounds."); + // Check bounds here and throw if we are passed a rectangle exceeding our source bounds. + Guard.IsTrue( + new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), + nameof(cropRectangle), + "Crop rectangle should be smaller than the source bounds."); - this.CropRectangle = cropRectangle; - } + this.CropRectangle = cropRectangle; + } - /// - /// Gets the width. - /// - public Rectangle CropRectangle { get; } + /// + /// Gets the width. + /// + public Rectangle CropRectangle { get; } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new CropProcessor(configuration, this, source, sourceRectangle); - } + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new CropProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 32c93c2812..1d82dd12ad 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -1,95 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides methods to allow the cropping of an image. +/// +/// The pixel format. +internal class CropProcessor : TransformProcessor + where TPixel : unmanaged, IPixel { + private readonly Rectangle cropRectangle; + /// - /// Provides methods to allow the cropping of an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class CropProcessor : TransformProcessor - where TPixel : unmanaged, IPixel - { - private readonly Rectangle cropRectangle; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public CropProcessor(Configuration configuration, CropProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.cropRectangle = definition.CropRectangle; + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public CropProcessor(Configuration configuration, CropProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + => this.cropRectangle = definition.CropRectangle; - /// - protected override Size GetDestinationSize() => 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) + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + // Handle crop dimensions identical to the original + if (source.Width == destination.Width + && source.Height == destination.Height + && this.SourceRectangle == this.cropRectangle) { - // Handle crop dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && this.SourceRectangle == this.cropRectangle) - { - // the cloned will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - return; - } + // the cloned will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } - Rectangle bounds = this.cropRectangle; + Rectangle bounds = this.cropRectangle; - // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = - ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); + // Copying is cheap, we should process more pixels per task: + ParallelExecutionSettings parallelSettings = + ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var operation = new RowOperation(bounds, source.PixelBuffer, destination.PixelBuffer); + var operation = new RowOperation(bounds, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - bounds, - in parallelSettings, - in operation); - } + ParallelRowIterator.IterateRows( + bounds, + in parallelSettings, + in operation); + } + + /// + /// A implementing the processor logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D source; + private readonly Buffer2D destination; /// - /// A implementing the processor logic for . + /// Initializes a new instance of the struct. /// - private readonly struct RowOperation : IRowOperation + /// 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, Buffer2D source, Buffer2D destination) { - private readonly Rectangle bounds; - private readonly Buffer2D source; - private readonly Buffer2D 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, Buffer2D source, Buffer2D destination) - { - this.bounds = bounds; - this.source = source; - this.destination = destination; - } + this.bounds = bounds; + this.source = source; + this.destination = destination; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) - { - Span sourceRow = this.source.DangerousGetRowSpan(y)[this.bounds.Left..]; - Span targetRow = this.destination.DangerousGetRowSpan(y - this.bounds.Top); - sourceRow[..this.bounds.Width].CopyTo(targetRow); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.DangerousGetRowSpan(y)[this.bounds.Left..]; + Span targetRow = this.destination.DangerousGetRowSpan(y - this.bounds.Top); + sourceRow[..this.bounds.Width].CopyTo(targetRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs index bd9514dc8f..e5de459b12 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/DegenerateTransformException.cs @@ -1,42 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +/// +/// Represents an error that occurs during a transform operation. +/// +public sealed class DegenerateTransformException : Exception { /// - /// Represents an error that occurs during a transform operation. + /// Initializes a new instance of the class. /// - public sealed class DegenerateTransformException : Exception + public DegenerateTransformException() { - /// - /// Initializes a new instance of the class. - /// - public DegenerateTransformException() - { - } + } - /// - /// Initializes a new instance of the class - /// with a specified error message. - /// - /// The message that describes the error. - public DegenerateTransformException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public DegenerateTransformException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class - /// with a specified error message and a reference to the inner exception that is - /// the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public DegenerateTransformException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is + /// the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. + public DegenerateTransformException(string message, Exception innerException) + : base(message, innerException) + { } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index b1f4ee26d0..1d72669282 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -3,42 +3,41 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines cropping operation that preserves areas of highest entropy. +/// +public sealed class EntropyCropProcessor : IImageProcessor { /// - /// Defines cropping operation that preserves areas of highest entropy. + /// Initializes a new instance of the class. /// - public sealed class EntropyCropProcessor : IImageProcessor + public EntropyCropProcessor() + : this(.5F) { - /// - /// Initializes a new instance of the class. - /// - public EntropyCropProcessor() - : this(.5F) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// - public EntropyCropProcessor(float threshold) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1F, nameof(threshold)); - this.Threshold = threshold; - } + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public EntropyCropProcessor(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1F, nameof(threshold)); + this.Threshold = threshold; + } - /// - /// Gets the entropy threshold value. - /// - public float Threshold { get; } + /// + /// Gets the entropy threshold value. + /// + public float Threshold { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new EntropyCropProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + 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 a8148e18a8..1ba62aa9eb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -1,198 +1,196 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.ImageSharp.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides methods to allow the cropping of an image to preserve areas of highest entropy. +/// +/// The pixel format. +internal class EntropyCropProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly EntropyCropProcessor definition; + /// - /// Provides methods to allow the cropping of an image to preserve areas of highest entropy. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class EntropyCropProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly EntropyCropProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public EntropyCropProcessor(Configuration configuration, EntropyCropProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + this.definition = definition; + } + + /// + protected override void BeforeImageApply() + { + Rectangle rectangle; - /// - protected override void BeforeImageApply() + // TODO: This is clunky. We should add behavior enum to ExtractFrame. + // All frames have be the same size so we only need to calculate the correct dimensions for the first frame + using (var temp = new Image(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() })) { - Rectangle rectangle; + Configuration configuration = this.Source.GetConfiguration(); - // TODO: This is clunky. We should add behavior enum to ExtractFrame. - // All frames have be the same size so we only need to calculate the correct dimensions for the first frame - using (var temp = new Image(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() })) - { - Configuration configuration = this.Source.GetConfiguration(); + // Detect the edges. + new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle); - // Detect the edges. - new EdgeDetector2DProcessor(KnownEdgeDetectorKernels.Sobel, false).Execute(this.Configuration, temp, this.SourceRectangle); + // Apply threshold binarization filter. + new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); - // Apply threshold binarization filter. - new BinaryThresholdProcessor(this.definition.Threshold).Execute(this.Configuration, temp, this.SourceRectangle); + // Search for the first white pixels + rectangle = GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); + } - // Search for the first white pixels - rectangle = GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); - } + new CropProcessor(rectangle, this.Source.Size()).Execute(this.Configuration, this.Source, this.SourceRectangle); - new CropProcessor(rectangle, this.Source.Size()).Execute(this.Configuration, this.Source, this.SourceRectangle); + base.BeforeImageApply(); + } - base.BeforeImageApply(); - } + /// + protected override void OnFrameApply(ImageFrame source) + { + // All processing happens at the image level within BeforeImageApply(); + } - /// - protected override void OnFrameApply(ImageFrame source) - { - // All processing happens at the image level within BeforeImageApply(); - } + /// + /// Gets the bounding from the given points. + /// + /// + /// The designating the top left position. + /// + /// + /// The designating the bottom right position. + /// + /// + /// The bounding . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) + => new Rectangle( + topLeft.X, + topLeft.Y, + bottomRight.X - topLeft.X, + bottomRight.Y - topLeft.Y); - /// - /// Gets the bounding from the given points. - /// - /// - /// The designating the top left position. - /// - /// - /// The designating the bottom right position. - /// - /// - /// The bounding . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) - => new Rectangle( - topLeft.X, - topLeft.Y, - bottomRight.X - topLeft.X, - bottomRight.Y - topLeft.Y); - - /// - /// Finds the bounding rectangle based on the first instance of any color component other - /// than the given one. - /// - /// The to search within. - /// The color component value to remove. - /// The channel to test against. - /// - /// The . - /// - private static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - { - int width = bitmap.Width; - int height = bitmap.Height; - Point topLeft = default; - Point bottomRight = default; + /// + /// Finds the bounding rectangle based on the first instance of any color component other + /// than the given one. + /// + /// The to search within. + /// The color component value to remove. + /// The channel to test against. + /// + /// The . + /// + private static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + { + int width = bitmap.Width; + int height = bitmap.Height; + Point topLeft = default; + Point bottomRight = default; - Func, int, int, float, bool> delegateFunc; + Func, int, int, float, bool> delegateFunc; - // Determine which channel to check against - switch (channel) - { - case RgbaComponent.R: - delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; - break; + // Determine which channel to check against + switch (channel) + { + case RgbaComponent.R: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; + break; - case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon; - break; + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon; + break; - case RgbaComponent.B: - delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon; - break; + case RgbaComponent.B: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon; + break; - default: - delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon; - break; - } + default: + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon; + break; + } - int GetMinY(ImageFrame pixels) + int GetMinY(ImageFrame pixels) + { + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + if (delegateFunc(pixels, x, y, componentValue)) { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } + return y; } } - - return 0; } - int GetMaxY(ImageFrame pixels) + return 0; + } + + int GetMaxY(ImageFrame pixels) + { + for (int y = height - 1; y > -1; y--) { - for (int y = height - 1; y > -1; y--) + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + if (delegateFunc(pixels, x, y, componentValue)) { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } + return y; } } - - return height; } - int GetMinX(ImageFrame pixels) + return height; + } + + int GetMinX(ImageFrame pixels) + { + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + if (delegateFunc(pixels, x, y, componentValue)) { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } + return x; } } - - return 0; } - int GetMaxX(ImageFrame pixels) + return 0; + } + + int GetMaxX(ImageFrame pixels) + { + for (int x = width - 1; x > -1; x--) { - for (int x = width - 1; x > -1; x--) + for (int y = 0; y < height; y++) { - for (int y = 0; y < height; y++) + if (delegateFunc(pixels, x, y, componentValue)) { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } + return x; } } - - return width; } - topLeft.Y = GetMinY(bitmap); - topLeft.X = GetMinX(bitmap); - bottomRight.Y = Numerics.Clamp(GetMaxY(bitmap) + 1, 0, height); - bottomRight.X = Numerics.Clamp(GetMaxX(bitmap) + 1, 0, width); - - return GetBoundingRectangle(topLeft, bottomRight); + return width; } + + topLeft.Y = GetMinY(bitmap); + topLeft.X = GetMinX(bitmap); + bottomRight.Y = Numerics.Clamp(GetMaxY(bitmap) + 1, 0, height); + bottomRight.X = Numerics.Clamp(GetMaxX(bitmap) + 1, 0, width); + + return GetBoundingRectangle(topLeft, bottomRight); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index e2ce7e9ae3..520cbce423 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Encapsulates an interpolation algorithm for resampling images. +/// +public interface IResampler { /// - /// Encapsulates an interpolation algorithm for resampling images. + /// Gets the radius in which to sample pixels. /// - public interface IResampler - { - /// - /// Gets the radius in which to sample pixels. - /// - float Radius { get; } + float Radius { get; } - /// - /// Gets the result of the interpolation algorithm. - /// - /// The value to process. - /// - /// The - /// - float GetValue(float x); + /// + /// Gets the result of the interpolation algorithm. + /// + /// The value to process. + /// + /// 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; - } + /// + /// Applies a transformation upon an image. + /// + /// The pixel format. + /// The transforming image processor. + void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs index 83d2d5ca31..bde428f7d9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs @@ -3,21 +3,20 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +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 { /// - /// Implements an algorithm to alter the pixels of an image via resampling transforms. + /// Applies a resampling transform with the given sampler. /// - /// 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; - } + /// 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/ISwizzler.cs b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs index b9f045bd1d..321ecf9684 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs @@ -1,23 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Encapsulate an algorithm to swizzle pixels in an image. +/// +public interface ISwizzler { /// - /// Encapsulate an algorithm to swizzle pixels in an image. + /// Gets the size of the image after transformation. /// - public interface ISwizzler - { - /// - /// Gets the size of the image after transformation. - /// - Size DestinationSize { get; } + Size DestinationSize { get; } - /// - /// Applies the swizzle transformation to a given point. - /// - /// Point to transform. - /// The transformed point. - Point Transform(Point point); - } + /// + /// Applies the swizzle transformation to a given point. + /// + /// Point to transform. + /// The transformed point. + Point Transform(Point point); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs index 0d22837002..abb8beccdf 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -3,51 +3,50 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines an affine transformation applicable on an . +/// +public class AffineTransformProcessor : CloningImageProcessor { /// - /// Defines an affine transformation applicable on an . + /// Initializes a new instance of the class. /// - public class AffineTransformProcessor : CloningImageProcessor + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) { - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeValueType(sampler, nameof(sampler)); + Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.DestinationSize = targetDimensions; + if (TransformUtils.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.DestinationSize = targetDimensions; + } - /// - /// Gets the matrix used to supply the affine transform. - /// - public Matrix3x2 TransformMatrix { get; } + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } - /// - /// Gets the destination size to constrain the transformed image to. - /// - public Size DestinationSize { get; } + /// + /// Gets the matrix used to supply the affine transform. + /// + public Matrix3x2 TransformMatrix { get; } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new AffineTransformProcessor(configuration, this, source, sourceRectangle); - } + /// + /// Gets the destination size to constrain the transformed image to. + /// + public Size DestinationSize { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new AffineTransformProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 680ada1d36..8add73d33c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -1,244 +1,242 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Transforms +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; + /// - /// Provides the base methods to perform affine transforms on an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel + /// 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) { - 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; - } + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } - protected override Size GetDestinationSize() => this.destinationSize; + protected override Size GetDestinationSize() => this.destinationSize; - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } + /// + 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 + /// + 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. + // Degenerate matrices are already handled in the upstream definition. + if (matrix.Equals(Matrix3x2.Identity)) { - 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. - // Degenerate matrices are already handled in the upstream definition. - if (matrix.Equals(Matrix3x2.Identity)) + // The clone will be blank here copy all the pixel data over + var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); + Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + for (int y = 0; y < sourceBuffer.Height; y++) { - // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); - Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); - for (int y = 0; y < sourceBuffer.Height; y++) - { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); - } - - return; + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); } - // Convert from screen to world space. - Matrix3x2.Invert(matrix, out matrix); + return; + } - if (sampler is NearestNeighborResampler) - { - var nnOperation = new NNAffineOperation( - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds()), - destination.PixelBuffer, - matrix); - - ParallelRowIterator.IterateRows( - configuration, - destination.Bounds(), - in nnOperation); - - return; - } + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); - var operation = new AffineOperation( - configuration, + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNAffineOperation( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, - in sampler, matrix); - ParallelRowIterator.IterateRowIntervals, Vector4>( + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), - in operation); + in nnOperation); + + return; } - private readonly struct NNAffineOperation : IRowOperation + var operation = new AffineOperation( + configuration, + source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), + destination.PixelBuffer, + in sampler, + matrix); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNAffineOperation : IRowOperation + { + private readonly Buffer2D source; + private readonly Buffer2D destination; + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNAffineOperation( + Buffer2D source, + Rectangle bounds, + Buffer2D destination, + Matrix3x2 matrix) { - private readonly Buffer2D source; - private readonly Buffer2D destination; - private readonly Rectangle bounds; - private readonly Matrix3x2 matrix; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNAffineOperation( - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - Matrix3x2 matrix) - { - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.matrix = matrix; - } + this.source = source; + this.bounds = bounds; + this.destination = destination; + this.matrix = matrix; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.DangerousGetRowSpan(y); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = 0; x < destRow.Length; x++) { - Span destRow = this.destination.DangerousGetRowSpan(y); + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); - for (int x = 0; x < destRow.Length; x++) + if (this.bounds.Contains(px, py)) { - 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.GetElementUnsafe(px, py); - } + destRow[x] = this.source.GetElementUnsafe(px, py); } } } + } - private readonly struct AffineOperation : IRowIntervalOperation - where TResampler : struct, IResampler + private readonly struct AffineOperation : IRowIntervalOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly Buffer2D source; + private readonly Rectangle bounds; + private readonly Buffer2D destination; + private readonly TResampler sampler; + private readonly Matrix3x2 matrix; + private readonly float yRadius; + private readonly float xRadius; + + [MethodImpl(InliningOptions.ShortMethod)] + public AffineOperation( + Configuration configuration, + Buffer2D source, + Rectangle bounds, + Buffer2D destination, + in TResampler sampler, + Matrix3x2 matrix) { - private readonly Configuration configuration; - private readonly Buffer2D source; - private readonly Rectangle bounds; - private readonly Buffer2D destination; - private readonly TResampler sampler; - private readonly Matrix3x2 matrix; - private readonly float yRadius; - private readonly float xRadius; - - [MethodImpl(InliningOptions.ShortMethod)] - public AffineOperation( - Configuration configuration, - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - in TResampler sampler, - Matrix3x2 matrix) - { - this.configuration = configuration; - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.sampler = sampler; - this.matrix = matrix; - - this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height); - this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); - } + this.configuration = configuration; + this.source = source; + this.bounds = bounds; + this.destination = destination; + this.sampler = sampler; + this.matrix = matrix; - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height); + this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + Matrix3x2 matrix = this.matrix; + TResampler sampler = this.sampler; + float yRadius = this.yRadius; + float xRadius = this.xRadius; + int minY = this.bounds.Y; + int maxY = this.bounds.Bottom - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Right - 1; + + for (int y = rows.Min; y < rows.Max; y++) { - Matrix3x2 matrix = this.matrix; - TResampler sampler = this.sampler; - float yRadius = this.yRadius; - float xRadius = this.xRadius; - int minY = this.bounds.Y; - int maxY = this.bounds.Bottom - 1; - int minX = this.bounds.X; - int maxX = this.bounds.Right - 1; - - for (int y = rows.Min; y < rows.Max; y++) + Span rowSpan = this.destination.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4( + this.configuration, + rowSpan, + span, + PixelConversionModifiers.Scale); + + for (int x = 0; x < span.Length; x++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - rowSpan, - span, - PixelConversionModifiers.Scale); - - for (int x = 0; x < span.Length; x++) - { - var point = Vector2.Transform(new Vector2(x, y), matrix); - float pY = point.Y; - float pX = point.X; + var point = Vector2.Transform(new Vector2(x, y), matrix); + float pY = point.Y; + float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); - if (bottom == top || right == left) - { - continue; - } + if (bottom == top || right == left) + { + continue; + } - Vector4 sum = Vector4.Zero; - for (int yK = top; yK <= bottom; yK++) - { - float yWeight = sampler.GetValue(yK - pY); + Vector4 sum = Vector4.Zero; + for (int yK = top; yK <= bottom; yK++) + { + float yWeight = sampler.GetValue(yK - pY); - for (int xK = left; xK <= right; xK++) - { - float xWeight = sampler.GetValue(xK - pX); + for (int xK = left; xK <= right; xK++) + { + float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); - Numerics.Premultiply(ref current); - sum += current * xWeight * yWeight; - } + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Numerics.Premultiply(ref current); + sum += current * xWeight * yWeight; } - - span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - rowSpan, - PixelConversionModifiers.Scale); + span[x] = sum; } + + Numerics.UnPremultiply(span); + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + rowSpan, + PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs index 91b0b5dbbc..1378e16e04 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs @@ -3,16 +3,15 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. +/// +public sealed class AutoOrientProcessor : IImageProcessor { - /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. - /// - public sealed class AutoOrientProcessor : IImageProcessor - { - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new AutoOrientProcessor(configuration, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new AutoOrientProcessor(configuration, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs index 34bca94339..6952e561fb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs @@ -1,114 +1,112 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. +/// +/// The pixel format. +internal class AutoOrientProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { /// - /// Adjusts an image so that its orientation is suitable for viewing. Adjustments are based on EXIF metadata embedded in the image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class AutoOrientProcessor : ImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AutoOrientProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + } + + /// + protected override void BeforeImageApply() { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AutoOrientProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) + ushort orientation = GetExifOrientation(this.Source); + Size size = this.SourceRectangle.Size; + switch (orientation) { + case ExifOrientationMode.TopRight: + new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.BottomRight: + new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.BottomLeft: + new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.LeftTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.RightTop: + new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.RightBottom: + new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); + new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.LeftBottom: + new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); + break; + + case ExifOrientationMode.Unknown: + case ExifOrientationMode.TopLeft: + default: + break; } - /// - protected override void BeforeImageApply() + base.BeforeImageApply(); + } + + /// + protected override void OnFrameApply(ImageFrame sourceBase) + { + // All processing happens at the image level within BeforeImageApply(); + } + + /// + /// Returns the current EXIF orientation + /// + /// The image to auto rotate. + /// The + private static ushort GetExifOrientation(Image source) + { + if (source.Metadata.ExifProfile is null) { - ushort orientation = GetExifOrientation(this.Source); - Size size = this.SourceRectangle.Size; - switch (orientation) - { - case ExifOrientationMode.TopRight: - new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.BottomRight: - new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.BottomLeft: - new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.LeftTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.RightTop: - new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.RightBottom: - new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.LeftBottom: - new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); - break; - - case ExifOrientationMode.Unknown: - case ExifOrientationMode.TopLeft: - default: - break; - } - - base.BeforeImageApply(); + return ExifOrientationMode.Unknown; } - /// - protected override void OnFrameApply(ImageFrame sourceBase) + IExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); + if (value is null) { - // All processing happens at the image level within BeforeImageApply(); + return ExifOrientationMode.Unknown; } - /// - /// Returns the current EXIF orientation - /// - /// The image to auto rotate. - /// The - private static ushort GetExifOrientation(Image source) + ushort orientation; + if (value.DataType == ExifDataType.Short) { - if (source.Metadata.ExifProfile is null) - { - return ExifOrientationMode.Unknown; - } - - IExifValue value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); - if (value is null) - { - return ExifOrientationMode.Unknown; - } - - ushort orientation; - if (value.DataType == ExifDataType.Short) - { - orientation = value.Value; - } - else - { - orientation = Convert.ToUInt16(value.Value); - source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); - } - - source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, ExifOrientationMode.TopLeft); - - return orientation; + orientation = value.Value; } + else + { + orientation = Convert.ToUInt16(value.Value); + source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); + } + + source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, ExifOrientationMode.TopLeft); + + return orientation; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs index a1c6219885..8fdfdc6d13 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs @@ -3,27 +3,26 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a flipping around the center point of the image. +/// +public sealed class FlipProcessor : IImageProcessor { /// - /// Defines a flipping around the center point of the image. + /// Initializes a new instance of the class. /// - public sealed class FlipProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The used to perform flipping. - public FlipProcessor(FlipMode flipMode) => this.FlipMode = flipMode; + /// The used to perform flipping. + public FlipProcessor(FlipMode flipMode) => this.FlipMode = flipMode; - /// - /// Gets the used to perform flipping. - /// - public FlipMode FlipMode { get; } + /// + /// Gets the used to perform flipping. + /// + public FlipMode FlipMode { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new FlipProcessor(configuration, this, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new FlipProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index 3f4e18b8c7..11befd5dac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -1,94 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides methods that allow the flipping of an image around its center point. +/// +/// The pixel format. +internal class FlipProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { + private readonly FlipProcessor definition; + /// - /// Provides methods that allow the flipping of an image around its center point. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class FlipProcessor : ImageProcessor - where TPixel : unmanaged, IPixel - { - private readonly FlipProcessor definition; + /// The configuration which allows altering default behaviour or extending the library. + /// The . + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public FlipProcessor(Configuration configuration, FlipProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) => this.definition = definition; - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The . - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public FlipProcessor(Configuration configuration, FlipProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) => this.definition = definition; - - /// - protected override void OnFrameApply(ImageFrame source) + /// + protected override void OnFrameApply(ImageFrame source) + { + switch (this.definition.FlipMode) { - switch (this.definition.FlipMode) - { - // No default needed as we have already set the pixels. - case FlipMode.Vertical: - FlipX(source.PixelBuffer, this.Configuration); - break; - case FlipMode.Horizontal: - FlipY(source, this.Configuration); - break; - } + // No default needed as we have already set the pixels. + case FlipMode.Vertical: + FlipX(source.PixelBuffer, this.Configuration); + break; + case FlipMode.Horizontal: + FlipY(source, this.Configuration); + break; } + } - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private static void FlipX(Buffer2D source, Configuration configuration) - { - int height = source.Height; - using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < height / 2; yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.DangerousGetRowSpan(yBottom); - Span bottomRow = source.DangerousGetRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } - } + /// + /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private static void FlipX(Buffer2D source, Configuration configuration) + { + int height = source.Height; + using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); + Span temp = tempBuffer.Memory.Span; - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. - /// - /// The source image to apply the process to. - /// The configuration. - private static void FlipY(ImageFrame source, Configuration configuration) + for (int yTop = 0; yTop < height / 2; yTop++) { - var operation = new RowOperation(source.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds(), - in operation); + int yBottom = height - yTop - 1; + Span topRow = source.DangerousGetRowSpan(yBottom); + Span bottomRow = source.DangerousGetRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); } + } - private readonly struct RowOperation : IRowOperation - { - private readonly Buffer2D source; + /// + /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. + /// + /// The source image to apply the process to. + /// The configuration. + private static void FlipY(ImageFrame source, Configuration configuration) + { + var operation = new RowOperation(source.PixelBuffer); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(Buffer2D source) => this.source = source; + private readonly struct RowOperation : IRowOperation + { + private readonly Buffer2D source; - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) => this.source.DangerousGetRowSpan(y).Reverse(); - } + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(Buffer2D source) => this.source = source; + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) => this.source.DangerousGetRowSpan(y).Reverse(); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs index d799bb02ae..c92f424290 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs @@ -1,62 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Utility methods for linear transforms. +/// +internal static class LinearTransformUtility { /// - /// Utility methods for linear transforms. + /// Returns the sampling radius for the given sampler and dimensions. /// - internal static class LinearTransformUtility + /// The type of resampler. + /// The resampler sampler. + /// The source size. + /// The destination size. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static float GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) + where TResampler : struct, IResampler { - /// - /// Returns the sampling radius for the given sampler and dimensions. - /// - /// The type of resampler. - /// The resampler sampler. - /// The source size. - /// The destination size. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static float GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) - where TResampler : struct, IResampler - { - float scale = (float)sourceSize / destinationSize; - - if (scale < 1F) - { - scale = 1F; - } + float scale = (float)sourceSize / destinationSize; - return MathF.Ceiling(sampler.Radius * scale); + if (scale < 1F) + { + scale = 1F; } - /// - /// Gets the start position (inclusive) for a sampling range given - /// the radius, center position and max constraint. - /// - /// The radius. - /// The center position. - /// The min allowed amouunt. - /// The max allowed amouunt. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeStart(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Ceiling(center - radius), min, max); - - /// - /// Gets the end position (inclusive) for a sampling range given - /// the radius, center position and max constraint. - /// - /// The radius. - /// The center position. - /// The min allowed amouunt. - /// The max allowed amouunt. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeEnd(float radius, float center, int min, int max) - => Numerics.Clamp((int)MathF.Floor(center + radius), min, max); + return MathF.Ceiling(sampler.Radius * scale); } + + /// + /// Gets the start position (inclusive) for a sampling range given + /// the radius, center position and max constraint. + /// + /// The radius. + /// The center position. + /// The min allowed amouunt. + /// The max allowed amouunt. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetRangeStart(float radius, float center, int min, int max) + => Numerics.Clamp((int)MathF.Ceiling(center - radius), min, max); + + /// + /// Gets the end position (inclusive) for a sampling range given + /// the radius, center position and max constraint. + /// + /// The radius. + /// The center position. + /// The min allowed amouunt. + /// The max allowed amouunt. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static int GetRangeEnd(float radius, float center, int min, int max) + => Numerics.Clamp((int)MathF.Floor(center + radius), min, max); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs index 3ebecb7741..9b0b979e69 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -3,51 +3,50 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a projective transformation applicable to an . +/// +public sealed class ProjectiveTransformProcessor : CloningImageProcessor { /// - /// Defines a projective transformation applicable to an . + /// Initializes a new instance of the class. /// - public sealed class ProjectiveTransformProcessor : CloningImageProcessor + /// The transform matrix. + /// The sampler to perform the transform operation. + /// The target dimensions. + public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) { - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix. - /// The sampler to perform the transform operation. - /// The target dimensions. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeValueType(sampler, nameof(sampler)); + Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - - this.Sampler = sampler; - this.TransformMatrix = matrix; - this.DestinationSize = targetDimensions; + if (TransformUtils.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } + this.Sampler = sampler; + this.TransformMatrix = matrix; + this.DestinationSize = targetDimensions; + } - /// - /// Gets the matrix used to supply the projective transform. - /// - public Matrix4x4 TransformMatrix { get; } + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } - /// - /// Gets the destination size to constrain the transformed image to. - /// - public Size DestinationSize { get; } + /// + /// Gets the matrix used to supply the projective transform. + /// + public Matrix4x4 TransformMatrix { get; } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new ProjectiveTransformProcessor(configuration, this, source, sourceRectangle); - } + /// + /// Gets the destination size to constrain the transformed image to. + /// + public Size DestinationSize { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new ProjectiveTransformProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 9dbf4708fe..440becf833 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -1,244 +1,242 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Transforms +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; + /// - /// Provides the base methods to perform non-affine transforms on an image. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel + /// 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) { - 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; - } + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } - protected override Size GetDestinationSize() => this.destinationSize; + protected override Size GetDestinationSize() => this.destinationSize; - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } + /// + 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 + /// + 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. + // Degenerate matrices are already handled in the upstream definition. + if (matrix.Equals(Matrix4x4.Identity)) { - 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. - // Degenerate matrices are already handled in the upstream definition. - if (matrix.Equals(Matrix4x4.Identity)) + // The clone will be blank here copy all the pixel data over + var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); + Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + for (int y = 0; y < sourceBuffer.Height; y++) { - // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); - Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); - for (int y = 0; y < sourceBuffer.Height; y++) - { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); - } - - return; + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); } - // Convert from screen to world space. - Matrix4x4.Invert(matrix, out matrix); + return; + } - if (sampler is NearestNeighborResampler) - { - var nnOperation = new NNProjectiveOperation( - source.PixelBuffer, - Rectangle.Intersect(this.SourceRectangle, source.Bounds()), - destination.PixelBuffer, - matrix); - - ParallelRowIterator.IterateRows( - configuration, - destination.Bounds(), - in nnOperation); - - return; - } + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); - var operation = new ProjectiveOperation( - configuration, + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNProjectiveOperation( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, - in sampler, matrix); - ParallelRowIterator.IterateRowIntervals, Vector4>( + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), - in operation); + in nnOperation); + + return; } - private readonly struct NNProjectiveOperation : IRowOperation + var operation = new ProjectiveOperation( + configuration, + source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), + destination.PixelBuffer, + in sampler, + matrix); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNProjectiveOperation : IRowOperation + { + private readonly Buffer2D source; + private readonly Buffer2D destination; + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + Buffer2D source, + Rectangle bounds, + Buffer2D destination, + Matrix4x4 matrix) { - private readonly Buffer2D source; - private readonly Buffer2D destination; - private readonly Rectangle bounds; - private readonly Matrix4x4 matrix; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNProjectiveOperation( - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - Matrix4x4 matrix) - { - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.matrix = matrix; - } + this.source = source; + this.bounds = bounds; + this.destination = destination; + this.matrix = matrix; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.DangerousGetRowSpan(y); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = 0; x < destRow.Length; x++) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); - for (int x = 0; x < destRow.Length; x++) + if (this.bounds.Contains(px, py)) { - Vector2 point = TransformUtils.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.GetElementUnsafe(px, py); - } + destRow[x] = this.source.GetElementUnsafe(px, py); } } } + } - private readonly struct ProjectiveOperation : IRowIntervalOperation - where TResampler : struct, IResampler + private readonly struct ProjectiveOperation : IRowIntervalOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly Buffer2D source; + private readonly Rectangle bounds; + private readonly Buffer2D destination; + private readonly TResampler sampler; + private readonly Matrix4x4 matrix; + private readonly float yRadius; + private readonly float xRadius; + + [MethodImpl(InliningOptions.ShortMethod)] + public ProjectiveOperation( + Configuration configuration, + Buffer2D source, + Rectangle bounds, + Buffer2D destination, + in TResampler sampler, + Matrix4x4 matrix) { - private readonly Configuration configuration; - private readonly Buffer2D source; - private readonly Rectangle bounds; - private readonly Buffer2D destination; - private readonly TResampler sampler; - private readonly Matrix4x4 matrix; - private readonly float yRadius; - private readonly float xRadius; - - [MethodImpl(InliningOptions.ShortMethod)] - public ProjectiveOperation( - Configuration configuration, - Buffer2D source, - Rectangle bounds, - Buffer2D destination, - in TResampler sampler, - Matrix4x4 matrix) - { - this.configuration = configuration; - this.source = source; - this.bounds = bounds; - this.destination = destination; - this.sampler = sampler; - this.matrix = matrix; - - this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Height, destination.Height); - this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); - } + this.configuration = configuration; + this.source = source; + this.bounds = bounds; + this.destination = destination; + this.sampler = sampler; + this.matrix = matrix; - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows, Span span) + this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Height, destination.Height); + this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span span) + { + Matrix4x4 matrix = this.matrix; + TResampler sampler = this.sampler; + float yRadius = this.yRadius; + float xRadius = this.xRadius; + int minY = this.bounds.Y; + int maxY = this.bounds.Bottom - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Right - 1; + + for (int y = rows.Min; y < rows.Max; y++) { - Matrix4x4 matrix = this.matrix; - TResampler sampler = this.sampler; - float yRadius = this.yRadius; - float xRadius = this.xRadius; - int minY = this.bounds.Y; - int maxY = this.bounds.Bottom - 1; - int minX = this.bounds.X; - int maxX = this.bounds.Right - 1; - - for (int y = rows.Min; y < rows.Max; y++) + Span rowSpan = this.destination.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4( + this.configuration, + rowSpan, + span, + PixelConversionModifiers.Scale); + + for (int x = 0; x < span.Length; x++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - rowSpan, - span, - PixelConversionModifiers.Scale); - - for (int x = 0; x < span.Length; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - float pY = point.Y; - float pX = point.X; + Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + float pY = point.Y; + float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); - if (bottom <= top || right <= left) - { - continue; - } + if (bottom <= top || right <= left) + { + continue; + } - Vector4 sum = Vector4.Zero; - for (int yK = top; yK <= bottom; yK++) - { - float yWeight = sampler.GetValue(yK - pY); + Vector4 sum = Vector4.Zero; + for (int yK = top; yK <= bottom; yK++) + { + float yWeight = sampler.GetValue(yK - pY); - for (int xK = left; xK <= right; xK++) - { - float xWeight = sampler.GetValue(xK - pX); + for (int xK = left; xK <= right; xK++) + { + float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); - Numerics.Premultiply(ref current); - sum += current * xWeight * yWeight; - } + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Numerics.Premultiply(ref current); + sum += current * xWeight * yWeight; } - - span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - rowSpan, - PixelConversionModifiers.Scale); + span[x] = sum; } + + Numerics.UnPremultiply(span); + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + rowSpan, + PixelConversionModifiers.Scale); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs index 5c172b1da8..b8a8f424ba 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -3,49 +3,48 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a rotation applicable to an . +/// +public sealed class RotateProcessor : AffineTransformProcessor { /// - /// Defines a rotation applicable to an . + /// Initializes a new instance of the class. /// - public sealed class RotateProcessor : AffineTransformProcessor + /// The angle of rotation in degrees. + /// The source image size + public RotateProcessor(float degrees, Size sourceSize) + : this(degrees, KnownResamplers.Bicubic, sourceSize) { - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The source image size - public RotateProcessor(float degrees, Size sourceSize) - : this(degrees, KnownResamplers.Bicubic, sourceSize) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The angle of rotation in degrees. - /// The sampler to perform the rotating operation. - /// The source image size - public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), - sampler, - sourceSize) - => this.Degrees = degrees; + /// + /// Initializes a new instance of the class. + /// + /// The angle of rotation in degrees. + /// The sampler to perform the rotating operation. + /// The source image size + public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) + : this( + TransformUtils.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)) - { - } + // Helper constructor + private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) + : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix)) + { + } - /// - /// Gets the angle of rotation in degrees. - /// - public float Degrees { get; } + /// + /// Gets the angle of rotation in degrees. + /// + public float Degrees { get; } - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new RotateProcessor(configuration, this, source, sourceRectangle); - } + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new RotateProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index 8f6ba28963..ac50337f6e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -1,284 +1,282 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides methods that allow the rotating of images. +/// +/// The pixel format. +internal class RotateProcessor : AffineTransformProcessor + where TPixel : unmanaged, IPixel { + private readonly float degrees; + /// - /// Provides methods that allow the rotating of images. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal class RotateProcessor : AffineTransformProcessor - where TPixel : unmanaged, IPixel + /// 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 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) { - private readonly float degrees; - - /// - /// 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 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) + if (this.OptimizedApply(source, destination, this.Configuration)) { - if (this.OptimizedApply(source, destination, this.Configuration)) - { - return; - } - - base.OnFrameApply(source, destination); + return; } - /// - protected override void AfterImageApply(Image destination) - { - ExifProfile profile = destination.Metadata.ExifProfile; - if (profile is null) - { - return; - } - - if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) - { - // No need to do anything so return. - return; - } - - profile.RemoveValue(ExifTag.Orientation); + base.OnFrameApply(source, destination); + } - base.AfterImageApply(destination); + /// + protected override void AfterImageApply(Image destination) + { + ExifProfile profile = destination.Metadata.ExifProfile; + if (profile is null) + { + return; } - /// - /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range - /// - /// The angle of rotation in degrees. - /// The . - private static float WrapDegrees(float degrees) + if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) { - degrees %= 360; + // No need to do anything so return. + return; + } - while (degrees < 0) - { - degrees += 360; - } + profile.RemoveValue(ExifTag.Orientation); - return degrees; - } + base.AfterImageApply(destination); + } - /// - /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. - /// - /// The source image. - /// The destination image. - /// The configuration. - /// - /// The - /// - private bool OptimizedApply( - ImageFrame source, - ImageFrame destination, - Configuration configuration) - { - // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.degrees); + /// + /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range + /// + /// The angle of rotation in degrees. + /// The . + private static float WrapDegrees(float degrees) + { + degrees %= 360; - if (MathF.Abs(degrees) < Constants.Epsilon) - { - // The destination will be blank here so copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - return true; - } + while (degrees < 0) + { + degrees += 360; + } - if (MathF.Abs(degrees - 90) < Constants.Epsilon) - { - Rotate90(source, destination, configuration); - return true; - } + return degrees; + } - if (MathF.Abs(degrees - 180) < Constants.Epsilon) - { - Rotate180(source, destination, configuration); - return true; - } + /// + /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. + /// + /// The source image. + /// The destination image. + /// The configuration. + /// + /// The + /// + private bool OptimizedApply( + ImageFrame source, + ImageFrame destination, + Configuration configuration) + { + // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. + float degrees = WrapDegrees(this.degrees); - if (MathF.Abs(degrees - 270) < Constants.Epsilon) - { - Rotate270(source, destination, configuration); - return true; - } + if (MathF.Abs(degrees) < Constants.Epsilon) + { + // The destination will be blank here so copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return true; + } - return false; + if (MathF.Abs(degrees - 90) < Constants.Epsilon) + { + Rotate90(source, destination, configuration); + return true; } - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + if (MathF.Abs(degrees - 180) < Constants.Epsilon) { - var operation = new Rotate180RowOperation(source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds(), - in operation); + Rotate180(source, destination, configuration); + return true; } - /// - /// Rotates the image 270 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) + if (MathF.Abs(degrees - 270) < Constants.Epsilon) { - var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRowIntervals( - configuration, - source.Bounds(), - in operation); + Rotate270(source, destination, configuration); + return true; } - /// - /// Rotates the image 90 degrees clockwise at the centre point. - /// - /// The source image. - /// The destination image. - /// The configuration. - private static void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) + return false; + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private static void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate180RowOperation(source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private static void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); + ParallelRowIterator.IterateRowIntervals( + configuration, + source.Bounds(), + in operation); + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The source image. + /// The destination image. + /// The configuration. + private static void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) + { + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); + ParallelRowIterator.IterateRows( + configuration, + source.Bounds(), + in operation); + } + + private readonly struct Rotate180RowOperation : IRowOperation + { + private readonly int width; + private readonly int height; + private readonly Buffer2D source; + private readonly Buffer2D destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowOperation( + int width, + int height, + Buffer2D source, + Buffer2D destination) { - var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - configuration, - source.Bounds(), - in operation); + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; } - private readonly struct Rotate180RowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate180RowOperation( - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.width = width; - this.height = height; - this.source = source; - this.destination = destination; - } + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.destination.DangerousGetRowSpan(this.height - y - 1); - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.DangerousGetRowSpan(y); - Span targetRow = this.destination.DangerousGetRowSpan(this.height - y - 1); - - for (int x = 0; x < this.width; x++) - { - targetRow[this.width - x - 1] = sourceRow[x]; - } + targetRow[this.width - x - 1] = sourceRow[x]; } } + } - private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation + private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly Buffer2D source; + private readonly Buffer2D destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalOperation( + Rectangle bounds, + int width, + int height, + Buffer2D source, + Buffer2D destination) { - private readonly Rectangle bounds; - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate270RowIntervalOperation( - Rectangle bounds, - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.bounds = bounds; - this.width = width; - this.height = height; - this.source = source; - this.destination = 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) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) + Span sourceRow = this.source.DangerousGetRowSpan(y); + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.DangerousGetRowSpan(y); - for (int x = 0; x < this.width; x++) + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) { - int newX = this.height - y - 1; - newX = this.height - newX - 1; - int newY = this.width - x - 1; - - if (this.bounds.Contains(newX, newY)) - { - this.destination[newX, newY] = sourceRow[x]; - } + this.destination[newX, newY] = sourceRow[x]; } } } } + } - private readonly struct Rotate90RowOperation : IRowOperation + private readonly struct Rotate90RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly Buffer2D source; + private readonly Buffer2D destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowOperation( + Rectangle bounds, + int width, + int height, + Buffer2D source, + Buffer2D destination) { - private readonly Rectangle bounds; - private readonly int width; - private readonly int height; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public Rotate90RowOperation( - Rectangle bounds, - int width, - int height, - Buffer2D source, - Buffer2D destination) - { - this.bounds = bounds; - this.width = width; - this.height = height; - this.source = source; - this.destination = destination; - } + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.DangerousGetRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) { - Span sourceRow = this.source.DangerousGetRowSpan(y); - int newX = this.height - y - 1; - for (int x = 0; x < this.width; x++) + if (this.bounds.Contains(newX, x)) { - if (this.bounds.Contains(newX, x)) - { - this.destination[newX, x] = sourceRow[x]; - } + this.destination[newX, x] = sourceRow[x]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs index f8fdd3a2f8..5f16ec530b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -3,55 +3,54 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a skew transformation applicable to an . +/// +public sealed class SkewProcessor : AffineTransformProcessor { /// - /// Defines a skew transformation applicable to an . + /// Initializes a new instance of the class. /// - public sealed class SkewProcessor : AffineTransformProcessor + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, Size sourceSize) + : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) { - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, Size sourceSize) - : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// The angle in degrees to perform the skew along the x-axis. - /// The angle in degrees to perform the skew along the y-axis. - /// The sampler to perform the skew operation. - /// The source image size - public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) - : this( - TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), - sampler, - sourceSize) - { - this.DegreesX = degreesX; - this.DegreesY = degreesY; - } + /// + /// Initializes a new instance of the class. + /// + /// The angle in degrees to perform the skew along the x-axis. + /// The angle in degrees to perform the skew along the y-axis. + /// The sampler to perform the skew operation. + /// The source image size + public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) + : this( + TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), + sampler, + sourceSize) + { + this.DegreesX = degreesX; + this.DegreesY = degreesY; + } - // Helper constructor: - private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) - { - } + // Helper constructor: + private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) + : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) + { + } - /// - /// Gets the angle of rotation along the x-axis in degrees. - /// - public float DegreesX { get; } + /// + /// Gets the angle of rotation along the x-axis in degrees. + /// + public float DegreesX { get; } - /// - /// Gets the angle of rotation along the y-axis in degrees. - /// - public float DegreesY { get; } - } + /// + /// Gets the angle of rotation along the y-axis in degrees. + /// + public float DegreesY { get; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index 63bdbf3f89..cf9aa70672 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -4,46 +4,45 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// The function implements the bicubic kernel algorithm W(x) as described on +/// Wikipedia +/// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. +/// +public readonly struct BicubicResampler : IResampler { - /// - /// The function implements the bicubic kernel algorithm W(x) as described on - /// Wikipedia - /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. - /// - public readonly struct BicubicResampler : IResampler - { - /// - public float Radius => 2; + /// + public float Radius => 2; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) { - if (x < 0F) - { - x = -x; - } - - // Given the coefficient "a" as -0.5F. - if (x <= 1F) - { - // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (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); - return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; - } + x = -x; + } - return 0; + // Given the coefficient "a" as -0.5F. + if (x <= 1F) + { + // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (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); + return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + return 0; } + + /// + [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/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 65b60c3bdc..224df0e2d5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -4,33 +4,32 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +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 readonly struct BoxResampler : IResampler { - /// - /// The function implements the box algorithm. Similar to nearest neighbor when upscaling. - /// When downscaling the pixels will average, merging together. - /// - public readonly struct BoxResampler : IResampler - { - /// - public float Radius => 0.5F; + /// + public float Radius => 0.5F; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x > -0.5F && x <= 0.5F) { - if (x > -0.5F && x <= 0.5F) - { - return 1; - } - - return 0; + return 1; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + return 0; } + + /// + [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/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs index 53dfb466f8..41228e4111 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -4,109 +4,108 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +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 readonly CubicResampler CatmullRom = new(2, 0, .5F); + /// - /// 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. + /// 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 readonly CubicResampler Hermite = new(2, 0, 0); + + /// + /// The function implements the Mitchell-Netravali algorithm as described on + /// Wikipedia + /// + public static readonly CubicResampler MitchellNetravali = new(2, .3333333F, .3333333F); + + /// + /// The function implements the Robidoux algorithm. + /// + /// + public static readonly CubicResampler Robidoux = new(2, .37821575509399867F, .31089212245300067F); + + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static readonly CubicResampler RobidouxSharp = new(2, .2620145123990142F, .3689927438004929F); + + /// + /// The function implements the spline algorithm. /// - /// /// - public readonly struct CubicResampler : IResampler + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static readonly CubicResampler Spline = new(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) { - 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 readonly CubicResampler CatmullRom = new(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 readonly CubicResampler Hermite = new(2, 0, 0); - - /// - /// The function implements the Mitchell-Netravali algorithm as described on - /// Wikipedia - /// - public static readonly CubicResampler MitchellNetravali = new(2, .3333333F, .3333333F); - - /// - /// The function implements the Robidoux algorithm. - /// - /// - public static readonly CubicResampler Robidoux = new(2, .37821575509399867F, .31089212245300067F); - - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public static readonly CubicResampler RobidouxSharp = new(2, .2620145123990142F, .3689927438004929F); - - /// - /// The function implements the spline algorithm. - /// - /// - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public static readonly CubicResampler Spline = new(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) + float b = this.bspline; + float c = this.cardinal; + + if (x < 0F) { - this.Radius = radius; - this.bspline = bspline; - this.cardinal = cardinal; + x = -x; } - /// - public float Radius { get; } + 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; + } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + if (x < 2F) { - 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; + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + 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/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs index 30bc5b5def..b4b544d25d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -4,65 +4,64 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// The function implements the Lanczos kernel algorithm as described on +/// Wikipedia. +/// +public readonly struct LanczosResampler : IResampler { /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia. + /// Implements the Lanczos kernel algorithm with a radius of 2. /// - public readonly struct LanczosResampler : IResampler - { - /// - /// Implements the Lanczos kernel algorithm with a radius of 2. - /// - public static readonly LanczosResampler Lanczos2 = new(2); + public static readonly LanczosResampler Lanczos2 = new(2); - /// - /// Implements the Lanczos kernel algorithm with a radius of 3. - /// - public static readonly LanczosResampler Lanczos3 = new(3); + /// + /// Implements the Lanczos kernel algorithm with a radius of 3. + /// + public static readonly LanczosResampler Lanczos3 = new(3); - /// - /// Implements the Lanczos kernel algorithm with a radius of 5. - /// - public static readonly LanczosResampler Lanczos5 = new(5); + /// + /// Implements the Lanczos kernel algorithm with a radius of 5. + /// + public static readonly LanczosResampler Lanczos5 = new(5); - /// - /// Implements the Lanczos kernel algorithm with a radius of 8. - /// - public static readonly LanczosResampler Lanczos8 = new(8); + /// + /// Implements the Lanczos kernel algorithm with a radius of 8. + /// + public static readonly LanczosResampler Lanczos8 = new(8); - /// - /// Initializes a new instance of the struct. - /// - /// The sampling radius. - public LanczosResampler(float radius) => this.Radius = radius; + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + public LanczosResampler(float radius) => this.Radius = radius; - /// - public float Radius { get; } + /// + public float Radius { get; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) { - if (x < 0F) - { - x = -x; - } - - float radius = this.Radius; - if (x < radius) - { - return Numerics.SinC(x) * Numerics.SinC(x / radius); - } + x = -x; + } - return 0F; + float radius = this.Radius; + if (x < radius) + { + return Numerics.SinC(x) * Numerics.SinC(x / radius); } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + 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/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index bd5fe86f07..ae0f86ae35 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -4,25 +4,24 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +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 readonly struct NearestNeighborResampler : IResampler { - /// - /// The function implements the nearest neighbor algorithm. This uses an unscaled filter - /// which will select the closest pixel to the new pixels position. - /// - public readonly struct NearestNeighborResampler : IResampler - { - /// - public float Radius => 1; + /// + public float Radius => 1; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) => 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); - } + /// + [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/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index 90d51e123a..2eede994d6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -4,39 +4,38 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// The function implements the triangle (bilinear) algorithm. +/// 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 readonly struct TriangleResampler : IResampler { - /// - /// The function implements the triangle (bilinear) algorithm. - /// 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 readonly struct TriangleResampler : IResampler - { - /// - public float Radius => 1; + /// + public float Radius => 1; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) { - if (x < 0F) - { - x = -x; - } - - if (x < 1F) - { - return 1F - x; - } + x = -x; + } - return 0F; + if (x < 1F) + { + return 1F - x; } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + 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/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index b1f016c167..de0fe3f802 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -4,38 +4,37 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// The function implements the welch algorithm. +/// +/// +public readonly struct WelchResampler : IResampler { - /// - /// The function implements the welch algorithm. - /// - /// - public readonly struct WelchResampler : IResampler - { - /// - public float Radius => 3; + /// + public float Radius => 3; - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return Numerics.SinC(x) * (1F - (x * x / 9F)); - } + x = -x; + } - return 0F; + if (x < 3F) + { + return Numerics.SinC(x) * (1F - (x * x / 9F)); } - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingTransformImageProcessor processor) - where TPixel : unmanaged, IPixel - => processor.ApplyTransform(in this); + 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/Resize/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs index 27e3508199..8cff50f55b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -1,429 +1,427 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides methods to help calculate the target rectangle when resizing using the +/// enumeration. +/// +internal static class ResizeHelper { + public static unsafe int CalculateResizeWorkerHeightInWindowBands( + int windowBandHeight, + int width, + int sizeLimitHintInBytes) + { + int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4); + int sizeOfOneWindow = windowBandHeight * width; + return Math.Max(2, sizeLimitHint / sizeOfOneWindow); + } + /// - /// Provides methods to help calculate the target rectangle when resizing using the - /// enumeration. + /// Calculates the target location and bounds to perform the resize operation against. /// - internal static class ResizeHelper + /// The source image size. + /// The resize options. + /// + /// The tuple representing the location and the bounds + /// + public static (Size Size, Rectangle Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options) { - public static unsafe int CalculateResizeWorkerHeightInWindowBands( - int windowBandHeight, - int width, - int sizeLimitHintInBytes) + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 && height <= 0) { - int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4); - int sizeOfOneWindow = windowBandHeight * width; - return Math.Max(2, sizeLimitHint / sizeOfOneWindow); + ThrowInvalid($"Target width {width} and height {height} must be greater than zero."); } - /// - /// Calculates the target location and bounds to perform the resize operation against. - /// - /// The source image size. - /// The resize options. - /// - /// The tuple representing the location and the bounds - /// - public static (Size Size, Rectangle Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options) + // Ensure target size is populated across both dimensions. + // These dimensions are used to calculate the final dimensions determined by the mode algorithm. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int Min = 1; + if (width == 0 && height > 0) { - int width = options.Size.Width; - int height = options.Size.Height; + width = (int)MathF.Max(Min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); + } - if (width <= 0 && height <= 0) - { - ThrowInvalid($"Target width {width} and height {height} must be greater than zero."); - } + if (height == 0 && width > 0) + { + height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); + } - // Ensure target size is populated across both dimensions. - // These dimensions are used to calculate the final dimensions determined by the mode algorithm. - // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. - // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. - const int Min = 1; - if (width == 0 && height > 0) - { - width = (int)MathF.Max(Min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); - } + switch (options.Mode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(sourceSize, options, width, height); + case ResizeMode.Pad: + return CalculatePadRectangle(sourceSize, options, width, height); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(sourceSize, options, width, height); + case ResizeMode.Max: + return CalculateMaxRectangle(sourceSize, width, height); + case ResizeMode.Min: + return CalculateMinRectangle(sourceSize, width, height); + case ResizeMode.Manual: + return CalculateManualRectangle(options, width, height); + + // case ResizeMode.Stretch: + default: + return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(0, 0, Sanitize(width), Sanitize(height))); + } + } - if (height == 0 && width > 0) - { - height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); - } + private static (Size Size, Rectangle Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; - switch (options.Mode) + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + + int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int targetX; + int targetY; + int targetWidth = sourceWidth; + int targetHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.Position) { - case ResizeMode.Crop: - return CalculateCropRectangle(sourceSize, options, width, height); - case ResizeMode.Pad: - return CalculatePadRectangle(sourceSize, options, width, height); - case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(sourceSize, options, width, height); - case ResizeMode.Max: - return CalculateMaxRectangle(sourceSize, width, height); - case ResizeMode.Min: - return CalculateMinRectangle(sourceSize, width, height); - case ResizeMode.Manual: - return CalculateManualRectangle(options, width, height); - - // case ResizeMode.Stretch: + case AnchorPositionMode.Left: + targetY = (height - sourceHeight) / 2; + targetX = 0; + break; + case AnchorPositionMode.Right: + targetY = (height - sourceHeight) / 2; + targetX = width - sourceWidth; + break; + case AnchorPositionMode.TopRight: + targetY = 0; + targetX = width - sourceWidth; + break; + case AnchorPositionMode.Top: + targetY = 0; + targetX = (width - sourceWidth) / 2; + break; + case AnchorPositionMode.TopLeft: + targetY = 0; + targetX = 0; + break; + case AnchorPositionMode.BottomRight: + targetY = height - sourceHeight; + targetX = width - sourceWidth; + break; + case AnchorPositionMode.Bottom: + targetY = height - sourceHeight; + targetX = (width - sourceWidth) / 2; + break; + case AnchorPositionMode.BottomLeft: + targetY = height - sourceHeight; + targetX = 0; + break; default: - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(0, 0, Sanitize(width), Sanitize(height))); + targetY = (height - sourceHeight) / 2; + targetX = (width - sourceWidth) / 2; + break; } + + // Target image width and height can be different to the rectangle width and height. + return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); } - private static (Size Size, Rectangle Rectangle) CalculateBoxPadRectangle( - Size source, - ResizeOptions options, - int width, - int height) - { - int sourceWidth = source.Width; - int sourceHeight = source.Height; + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options, width, height); + } - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); + private static (Size Size, Rectangle Rectangle) CalculateCropRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; - int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.CenterCoordinates.HasValue) { - int targetX; - int targetY; - int targetWidth = sourceWidth; - int targetHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; + float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y; + targetY = (int)MathF.Round(center + (height / 2F)); + if (targetY > 0) + { + targetY = 0; + } + + if (targetY < (int)MathF.Round(height - (sourceHeight * ratio))) + { + targetY = (int)MathF.Round(height - (sourceHeight * ratio)); + } + } + else + { switch (options.Position) { - case AnchorPositionMode.Left: - targetY = (height - sourceHeight) / 2; - targetX = 0; - break; - case AnchorPositionMode.Right: - targetY = (height - sourceHeight) / 2; - targetX = width - sourceWidth; - break; - case AnchorPositionMode.TopRight: - targetY = 0; - targetX = width - sourceWidth; - break; case AnchorPositionMode.Top: - targetY = 0; - targetX = (width - sourceWidth) / 2; - break; case AnchorPositionMode.TopLeft: + case AnchorPositionMode.TopRight: targetY = 0; - targetX = 0; - break; - case AnchorPositionMode.BottomRight: - targetY = height - sourceHeight; - targetX = width - sourceWidth; break; case AnchorPositionMode.Bottom: - targetY = height - sourceHeight; - targetX = (width - sourceWidth) / 2; - break; case AnchorPositionMode.BottomLeft: - targetY = height - sourceHeight; - targetX = 0; + case AnchorPositionMode.BottomRight: + targetY = (int)MathF.Round(height - (sourceHeight * ratio)); break; default: - targetY = (height - sourceHeight) / 2; - targetX = (width - sourceWidth) / 2; + targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); break; } - - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); } - // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options, width, height); + targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); } - - private static (Size Size, Rectangle Rectangle) CalculateCropRectangle( - Size source, - ResizeOptions options, - int width, - int height) + else { - float ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int targetX = 0; - int targetY = 0; - int targetWidth = width; - int targetHeight = height; + ratio = percentHeight; - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); - - if (percentHeight < percentWidth) + if (options.CenterCoordinates.HasValue) { - ratio = percentWidth; + float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X; + targetX = (int)MathF.Round(center + (width / 2F)); - if (options.CenterCoordinates.HasValue) + if (targetX > 0) { - float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y; - targetY = (int)MathF.Round(center + (height / 2F)); - - if (targetY > 0) - { - targetY = 0; - } - - if (targetY < (int)MathF.Round(height - (sourceHeight * ratio))) - { - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); - } + targetX = 0; } - else + + if (targetX < (int)MathF.Round(width - (sourceWidth * ratio))) { - switch (options.Position) - { - case AnchorPositionMode.Top: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.TopRight: - targetY = 0; - break; - case AnchorPositionMode.Bottom: - case AnchorPositionMode.BottomLeft: - case AnchorPositionMode.BottomRight: - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); - break; - default: - targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); - break; - } + targetX = (int)MathF.Round(width - (sourceWidth * ratio)); } - - targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); } else { - ratio = percentHeight; - - if (options.CenterCoordinates.HasValue) + switch (options.Position) { - float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X; - targetX = (int)MathF.Round(center + (width / 2F)); - - if (targetX > 0) - { + case AnchorPositionMode.Left: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.BottomLeft: targetX = 0; - } - - if (targetX < (int)MathF.Round(width - (sourceWidth * ratio))) - { + break; + case AnchorPositionMode.Right: + case AnchorPositionMode.TopRight: + case AnchorPositionMode.BottomRight: targetX = (int)MathF.Round(width - (sourceWidth * ratio)); - } - } - else - { - switch (options.Position) - { - case AnchorPositionMode.Left: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.BottomLeft: - targetX = 0; - break; - case AnchorPositionMode.Right: - case AnchorPositionMode.TopRight: - case AnchorPositionMode.BottomRight: - targetX = (int)MathF.Round(width - (sourceWidth * ratio)); - break; - default: - targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); - break; - } + break; + default: + targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); + break; } - - targetWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); + targetWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - private static (Size Size, Rectangle Rectangle) CalculateMaxRectangle( - Size source, - int width, - int height) - { - int targetWidth = width; - int targetHeight = height; + // Target image width and height can be different to the rectangle width and height. + return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); + } - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)source.Height); - float percentWidth = MathF.Abs(width / (float)source.Width); + private static (Size Size, Rectangle Rectangle) CalculateMaxRectangle( + Size source, + int width, + int height) + { + int targetWidth = width; + int targetHeight = height; - // Integers must be cast to floats to get needed precision - float ratio = height / (float)width; - float sourceRatio = source.Height / (float)source.Width; + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)source.Height); + float percentWidth = MathF.Abs(width / (float)source.Width); - if (sourceRatio < ratio) - { - targetHeight = (int)MathF.Round(source.Height * percentWidth); - } - else - { - targetWidth = (int)MathF.Round(source.Width * percentHeight); - } + // Integers must be cast to floats to get needed precision + float ratio = height / (float)width; + float sourceRatio = source.Height / (float)source.Width; - // Replace the size to match the rectangle. - return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); + if (sourceRatio < ratio) + { + targetHeight = (int)MathF.Round(source.Height * percentWidth); } - - private static (Size Size, Rectangle Rectangle) CalculateMinRectangle( - Size source, - int width, - int height) + else { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - int targetWidth = width; - int targetHeight = height; + targetWidth = (int)MathF.Round(source.Width * percentHeight); + } - // Don't upscale - if (width > sourceWidth || height > sourceHeight) - { - return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); - } + // Replace the size to match the rectangle. + return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); + } - // Find the shortest distance to go. - int widthDiff = sourceWidth - width; - int heightDiff = sourceHeight - height; + private static (Size Size, Rectangle Rectangle) CalculateMinRectangle( + Size source, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int targetWidth = width; + int targetHeight = height; - if (widthDiff < heightDiff) - { - float sourceRatio = (float)sourceHeight / sourceWidth; - targetHeight = (int)MathF.Round(width * sourceRatio); - } - else if (widthDiff > heightDiff) + // Don't upscale + if (width > sourceWidth || height > sourceHeight) + { + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); + } + + // Find the shortest distance to go. + int widthDiff = sourceWidth - width; + int heightDiff = sourceHeight - height; + + if (widthDiff < heightDiff) + { + float sourceRatio = (float)sourceHeight / sourceWidth; + targetHeight = (int)MathF.Round(width * sourceRatio); + } + else if (widthDiff > heightDiff) + { + float sourceRatioInverse = (float)sourceWidth / sourceHeight; + targetWidth = (int)MathF.Round(height * sourceRatioInverse); + } + else + { + if (height > width) { - float sourceRatioInverse = (float)sourceWidth / sourceHeight; - targetWidth = (int)MathF.Round(height * sourceRatioInverse); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + targetHeight = (int)MathF.Round(sourceHeight * percentWidth); } else { - if (height > width) - { - float percentWidth = MathF.Abs(width / (float)sourceWidth); - targetHeight = (int)MathF.Round(sourceHeight * percentWidth); - } - else - { - float percentHeight = MathF.Abs(height / (float)sourceHeight); - targetWidth = (int)MathF.Round(sourceWidth * percentHeight); - } + float percentHeight = MathF.Abs(height / (float)sourceHeight); + targetWidth = (int)MathF.Round(sourceWidth * percentHeight); } - - // Replace the size to match the rectangle. - return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); } - private static (Size Size, Rectangle Rectangle) CalculatePadRectangle( - Size sourceSize, - ResizeOptions options, - int width, - int height) - { - float ratio; - int sourceWidth = sourceSize.Width; - int sourceHeight = sourceSize.Height; + // Replace the size to match the rectangle. + return (new Size(Sanitize(targetWidth), Sanitize(targetHeight)), new Rectangle(0, 0, Sanitize(targetWidth), Sanitize(targetHeight))); + } - int targetX = 0; - int targetY = 0; - int targetWidth = width; - int targetHeight = height; + private static (Size Size, Rectangle Rectangle) CalculatePadRectangle( + Size sourceSize, + ResizeOptions options, + int width, + int height) + { + float ratio; + int sourceWidth = sourceSize.Width; + int sourceHeight = sourceSize.Height; - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; - if (percentHeight < percentWidth) - { - ratio = percentHeight; - targetWidth = (int)MathF.Round(sourceWidth * percentHeight); + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); - switch (options.Position) - { - case AnchorPositionMode.Left: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.BottomLeft: - targetX = 0; - break; - case AnchorPositionMode.Right: - case AnchorPositionMode.TopRight: - case AnchorPositionMode.BottomRight: - targetX = (int)MathF.Round(width - (sourceWidth * ratio)); - break; - default: - targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); - break; - } - } - else - { - ratio = percentWidth; - targetHeight = (int)MathF.Round(sourceHeight * percentWidth); + if (percentHeight < percentWidth) + { + ratio = percentHeight; + targetWidth = (int)MathF.Round(sourceWidth * percentHeight); - switch (options.Position) - { - case AnchorPositionMode.Top: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.TopRight: - targetY = 0; - break; - case AnchorPositionMode.Bottom: - case AnchorPositionMode.BottomLeft: - case AnchorPositionMode.BottomRight: - targetY = (int)MathF.Round(height - (sourceHeight * ratio)); - break; - default: - targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); - break; - } + switch (options.Position) + { + case AnchorPositionMode.Left: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.BottomLeft: + targetX = 0; + break; + case AnchorPositionMode.Right: + case AnchorPositionMode.TopRight: + case AnchorPositionMode.BottomRight: + targetX = (int)MathF.Round(width - (sourceWidth * ratio)); + break; + default: + targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); + break; } - - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); } - - private static (Size Size, Rectangle Rectangle) CalculateManualRectangle( - ResizeOptions options, - int width, - int height) + else { - if (!options.TargetRectangle.HasValue) + ratio = percentWidth; + targetHeight = (int)MathF.Round(sourceHeight * percentWidth); + + switch (options.Position) { - ThrowInvalid("Manual resizing requires a target location and size."); + case AnchorPositionMode.Top: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.TopRight: + targetY = 0; + break; + case AnchorPositionMode.Bottom: + case AnchorPositionMode.BottomLeft: + case AnchorPositionMode.BottomRight: + targetY = (int)MathF.Round(height - (sourceHeight * ratio)); + break; + default: + targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); + break; } + } - Rectangle targetRectangle = options.TargetRectangle.Value; - - int targetX = targetRectangle.X; - int targetY = targetRectangle.Y; - int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width; - int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : height; + // Target image width and height can be different to the rectangle width and height. + return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); + } - // Target image width and height can be different to the rectangle width and height. - return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); + private static (Size Size, Rectangle Rectangle) CalculateManualRectangle( + ResizeOptions options, + int width, + int height) + { + if (!options.TargetRectangle.HasValue) + { + ThrowInvalid("Manual resizing requires a target location and size."); } - private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + Rectangle targetRectangle = options.TargetRectangle.Value; + + int targetX = targetRectangle.X; + int targetY = targetRectangle.Y; + int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width; + int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : height; - private static int Sanitize(int input) => Math.Max(1, input); + // Target image width and height can be different to the rectangle width and height. + return (new Size(Sanitize(width), Sanitize(height)), new Rectangle(targetX, targetY, Sanitize(targetWidth), Sanitize(targetHeight))); } + + private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + + private static int Sanitize(int input) => Math.Max(1, input); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 73a75bf66f..51a739d35e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,178 +1,176 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Points to a collection of weights allocated in . +/// +internal readonly unsafe struct ResizeKernel { + private readonly float* bufferPtr; + /// - /// Points to a collection of weights allocated in . + /// Initializes a new instance of the struct. /// - internal readonly unsafe struct ResizeKernel + [MethodImpl(InliningOptions.ShortMethod)] + internal ResizeKernel(int startIndex, float* bufferPtr, int length) { - private readonly float* bufferPtr; + this.StartIndex = startIndex; + this.bufferPtr = bufferPtr; + this.Length = length; + } - /// - /// Initializes a new instance of the struct. - /// + /// + /// Gets the start index for the destination row. + /// + public int StartIndex + { [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel(int startIndex, float* bufferPtr, int length) - { - this.StartIndex = startIndex; - this.bufferPtr = bufferPtr; - this.Length = length; - } - - /// - /// Gets the start index for the destination row. - /// - public int StartIndex - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } - - /// - /// Gets the length of the kernel. - /// - public int Length - { - [MethodImpl(InliningOptions.ShortMethod)] - get; - } - - /// - /// Gets the span representing the portion of the that this window covers. - /// - /// The . - /// - public Span Values - { - [MethodImpl(InliningOptions.ShortMethod)] - get => new(this.bufferPtr, this.Length); - } + get; + } - /// - /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. - /// - /// The input span of vectors - /// The weighted sum + /// + /// Gets the length of the kernel. + /// + public int Length + { [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 Convolve(Span rowSpan) - => this.ConvolveCore(ref rowSpan[this.StartIndex]); + get; + } + /// + /// Gets the span representing the portion of the that this window covers. + /// + /// The . + /// + public Span Values + { [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ConvolveCore(ref Vector4 rowStartRef) + get => new(this.bufferPtr, this.Length); + } + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// + /// The input span of vectors + /// The weighted sum + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 Convolve(Span rowSpan) + => this.ConvolveCore(ref rowSpan[this.StartIndex]); + + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 ConvolveCore(ref Vector4 rowStartRef) + { + if (Avx2.IsSupported && Fma.IsSupported) { - if (Avx2.IsSupported && Fma.IsSupported) + float* bufferStart = this.bufferPtr; + float* bufferEnd = bufferStart + (this.Length & ~3); + Vector256 result256_0 = Vector256.Zero; + Vector256 result256_1 = Vector256.Zero; + ReadOnlySpan maskBytes = new byte[] { - float* bufferStart = this.bufferPtr; - float* bufferEnd = bufferStart + (this.Length & ~3); - Vector256 result256_0 = Vector256.Zero; - Vector256 result256_1 = Vector256.Zero; - ReadOnlySpan maskBytes = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, - }; - Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); - - while (bufferStart < bufferEnd) - { - // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps - // for the FMA operation, and execute it directly on the target register and reading directly from - // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. - // The code below should compile in the following assembly on .NET 5 x64: - // - // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] - // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] - // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 - // - // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. - // Additionally, we're also unrolling two computations per each loop iterations to leverage the - // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); - - result256_1 = Fma.MultiplyAdd( - Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), - result256_1); - - bufferStart += 4; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); - } - - result256_0 = Avx.Add(result256_0, result256_1); - - if ((this.Length & 3) >= 2) - { - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); - - bufferStart += 2; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); - } - - Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); - - if ((this.Length & 1) != 0) - { - result128 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Vector128.Create(*bufferStart), - result128); - } - - return *(Vector4*)&result128; - } - else + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + }; + Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); + + while (bufferStart < bufferEnd) { - // Destination color components - Vector4 result = Vector4.Zero; - float* bufferStart = this.bufferPtr; - float* bufferEnd = this.bufferPtr + this.Length; + // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps + // for the FMA operation, and execute it directly on the target register and reading directly from + // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. + // The code below should compile in the following assembly on .NET 5 x64: + // + // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] + // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] + // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 + // + // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. + // Additionally, we're also unrolling two computations per each loop iterations to leverage the + // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. + result256_0 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256_0); + + result256_1 = Fma.MultiplyAdd( + Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), + result256_1); + + bufferStart += 4; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); + } - while (bufferStart < bufferEnd) - { - // Vector4 v = offsetedRowSpan[i]; - result += rowStartRef * *bufferStart; + result256_0 = Avx.Add(result256_0, result256_1); - bufferStart++; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); - } + if ((this.Length & 3) >= 2) + { + result256_0 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256_0); - return result; + bufferStart += 2; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); } - } - /// - /// Copy the contents of altering - /// to the value . - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal ResizeKernel AlterLeftValue(int left) - => new(left, this.bufferPtr, this.Length); + Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); + + if ((this.Length & 1) != 0) + { + result128 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Vector128.Create(*bufferStart), + result128); + } - internal void Fill(Span values) + return *(Vector4*)&result128; + } + else { - DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!"); + // Destination color components + Vector4 result = Vector4.Zero; + float* bufferStart = this.bufferPtr; + float* bufferEnd = this.bufferPtr + this.Length; - for (int i = 0; i < this.Length; i++) + while (bufferStart < bufferEnd) { - this.Values[i] = (float)values[i]; + // Vector4 v = offsetedRowSpan[i]; + result += rowStartRef * *bufferStart; + + bufferStart++; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); } + + return result; + } + } + + /// + /// Copy the contents of altering + /// to the value . + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal ResizeKernel AlterLeftValue(int left) + => new(left, this.bufferPtr, this.Length); + + internal void Fill(Span values) + { + DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!"); + + for (int i = 0; i < this.Length; i++) + { + this.Values[i] = (float)values[i]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index 25d9cb9710..ee1ada43ad 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -3,69 +3,68 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +internal partial class ResizeKernelMap { - internal partial class ResizeKernelMap + /// + /// Memory-optimized where repeating rows are stored only once. + /// + private sealed class PeriodicKernelMap : ResizeKernelMap { - /// - /// Memory-optimized where repeating rows are stored only once. - /// - private sealed class PeriodicKernelMap : ResizeKernelMap + private readonly int period; + + private readonly int cornerInterval; + + public PeriodicKernelMap( + MemoryAllocator memoryAllocator, + int sourceLength, + int destinationLength, + double ratio, + double scale, + int radius, + int period, + int cornerInterval) + : base( + memoryAllocator, + sourceLength, + destinationLength, + (cornerInterval * 2) + period, + ratio, + scale, + radius) { - private readonly int period; + this.cornerInterval = cornerInterval; + this.period = period; + } + + internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - private readonly int cornerInterval; + protected internal override void Initialize(in TResampler sampler) + { + // Build top corner data + one period of the mosaic data: + int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; - public PeriodicKernelMap( - MemoryAllocator memoryAllocator, - int sourceLength, - int destinationLength, - double ratio, - double scale, - int radius, - int period, - int cornerInterval) - : base( - memoryAllocator, - sourceLength, - destinationLength, - (cornerInterval * 2) + period, - ratio, - scale, - radius) + for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { - this.cornerInterval = cornerInterval; - this.period = period; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } - internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - - protected internal override void Initialize(in TResampler sampler) + // Copy the mosaics: + int bottomStartDest = this.DestinationLength - this.cornerInterval; + for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) { - // Build top corner data + one period of the mosaic data: - int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; - - for (int i = 0; i < startOfFirstRepeatedMosaic; i++) - { - this.kernels[i] = this.BuildKernel(in sampler, i, i); - } - - // Copy the mosaics: - int bottomStartDest = this.DestinationLength - this.cornerInterval; - for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) - { - double center = ((i + .5) * this.ratio) - .5; - int left = (int)TolerantMath.Ceiling(center - this.radius); - ResizeKernel kernel = this.kernels[i - this.period]; - this.kernels[i] = kernel.AlterLeftValue(left); - } + double center = ((i + .5) * this.ratio) - .5; + int left = (int)TolerantMath.Ceiling(center - this.radius); + ResizeKernel kernel = this.kernels[i - this.period]; + this.kernels[i] = kernel.AlterLeftValue(left); + } - // Build bottom corner data: - int bottomStartData = this.cornerInterval + this.period; - for (int i = 0; i < this.cornerInterval; i++) - { - this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); - } + // Build bottom corner data: + int bottomStartData = this.cornerInterval + this.period; + for (int i = 0; i < this.cornerInterval; i++) + { + this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 78c64ff979..e492011788 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,272 +1,270 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Provides resize kernel values from an optimized contiguous memory region. +/// +internal partial class ResizeKernelMap : IDisposable { - /// - /// Provides resize kernel values from an optimized contiguous memory region. - /// - internal partial class ResizeKernelMap : IDisposable - { - private static readonly TolerantMath TolerantMath = TolerantMath.Default; + private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly int sourceLength; + private readonly int sourceLength; - private readonly double ratio; + private readonly double ratio; - private readonly double scale; + private readonly double scale; - private readonly int radius; + private readonly int radius; - private readonly MemoryHandle pinHandle; + private readonly MemoryHandle pinHandle; - private readonly Buffer2D data; + private readonly Buffer2D data; - private readonly ResizeKernel[] kernels; + private readonly ResizeKernel[] kernels; - private bool isDisposed; + private bool isDisposed; - // To avoid both GC allocations, and MemoryAllocator ceremony: - private readonly double[] tempValues; + // To avoid both GC allocations, and MemoryAllocator ceremony: + private readonly double[] tempValues; - private ResizeKernelMap( - MemoryAllocator memoryAllocator, - int sourceLength, - int destinationLength, - int bufferHeight, - double ratio, - double scale, - int radius) - { - this.ratio = ratio; - this.scale = scale; - this.radius = radius; - this.sourceLength = sourceLength; - this.DestinationLength = destinationLength; - this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); - this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); - this.kernels = new ResizeKernel[destinationLength]; - this.tempValues = new double[this.MaxDiameter]; - } + private ResizeKernelMap( + MemoryAllocator memoryAllocator, + int sourceLength, + int destinationLength, + int bufferHeight, + double ratio, + double scale, + int radius) + { + this.ratio = ratio; + this.scale = scale; + this.radius = radius; + this.sourceLength = sourceLength; + this.DestinationLength = destinationLength; + this.MaxDiameter = (radius * 2) + 1; + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); + this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); + this.kernels = new ResizeKernel[destinationLength]; + this.tempValues = new double[this.MaxDiameter]; + } - /// - /// Gets the length of the destination row/column - /// - public int DestinationLength { get; } - - /// - /// Gets the maximum diameter of the kernels. - /// - public int MaxDiameter { get; } - - /// - /// Gets a string of information to help debugging - /// - internal virtual string Info => - $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; - - /// - /// 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) - { - if (!this.isDisposed) - { - this.isDisposed = true; + /// + /// Gets the length of the destination row/column + /// + public int DestinationLength { get; } - if (disposing) - { - this.pinHandle.Dispose(); - this.data.Dispose(); - } - } - } + /// + /// Gets the maximum diameter of the kernels. + /// + public int MaxDiameter { get; } + + /// + /// Gets a string of information to help debugging + /// + internal virtual string Info => + $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; - /// - /// Returns a for an index value between 0 and DestinationSize - 1. - /// - [MethodImpl(InliningOptions.ShortMethod)] - internal ref ResizeKernel GetKernel(nint 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( - in TResampler sampler, - int destinationSize, - int sourceSize, - MemoryAllocator memoryAllocator) - where TResampler : struct, IResampler + /// + /// 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) + { + if (!this.isDisposed) { - double ratio = (double)sourceSize / destinationSize; - double scale = ratio; + this.isDisposed = true; - if (scale < 1) + if (disposing) { - scale = 1; + this.pinHandle.Dispose(); + this.data.Dispose(); } + } + } - int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); + /// + /// Returns a for an index value between 0 and DestinationSize - 1. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal ref ResizeKernel GetKernel(nint destIdx) => ref this.kernels[destIdx]; - // 'ratio' is a rational number. - // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again". - // This value is determining the length of the periods in repeating kernel map rows. - int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize); + /// + /// 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( + in TResampler sampler, + int destinationSize, + int sourceSize, + MemoryAllocator memoryAllocator) + where TResampler : struct, IResampler + { + double ratio = (double)sourceSize / destinationSize; + double scale = ratio; - // the center position at i == 0: - double center0 = (ratio - 1) * 0.5; - double firstNonNegativeLeftVal = (radius - center0 - 1) / ratio; + if (scale < 1) + { + scale = 1; + } - // The number of rows building a "stairway" at the top and the bottom of the kernel map - // corresponding to the corners of the image. - // If we do not normalize the kernel values, these rows also fit the periodic logic, - // however, it's just simpler to calculate them separately. - int cornerInterval = (int)TolerantMath.Ceiling(firstNonNegativeLeftVal); + int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); - // If firstNonNegativeLeftVal was an integral value, we need firstNonNegativeLeftVal+1 - // instead of Ceiling: - if (TolerantMath.AreEqual(firstNonNegativeLeftVal, cornerInterval)) - { - cornerInterval++; - } + // 'ratio' is a rational number. + // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again". + // This value is determining the length of the periods in repeating kernel map rows. + int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize); - // If 'cornerInterval' is too big compared to 'period', we can't apply the periodic optimization. - // If we don't have at least 2 periods, we go with the basic implementation: - bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; - - ResizeKernelMap result = hasAtLeast2Periods - ? new PeriodicKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - ratio, - scale, - radius, - period, - cornerInterval) - : new ResizeKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - destinationSize, - ratio, - scale, - radius); - - result.Initialize(in sampler); - - return result; - } + // the center position at i == 0: + double center0 = (ratio - 1) * 0.5; + double firstNonNegativeLeftVal = (radius - center0 - 1) / ratio; + + // The number of rows building a "stairway" at the top and the bottom of the kernel map + // corresponding to the corners of the image. + // If we do not normalize the kernel values, these rows also fit the periodic logic, + // however, it's just simpler to calculate them separately. + int cornerInterval = (int)TolerantMath.Ceiling(firstNonNegativeLeftVal); - /// - /// Initializes the kernel map. - /// - protected internal virtual void Initialize(in TResampler sampler) - where TResampler : struct, IResampler + // If firstNonNegativeLeftVal was an integral value, we need firstNonNegativeLeftVal+1 + // instead of Ceiling: + if (TolerantMath.AreEqual(firstNonNegativeLeftVal, cornerInterval)) { - for (int i = 0; i < this.DestinationLength; i++) - { - this.kernels[i] = this.BuildKernel(in sampler, i, i); - } + cornerInterval++; } - /// - /// Builds a for the row (in ) - /// referencing the data at row within , - /// so the data reusable by other data rows. - /// - private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) - where TResampler : struct, IResampler + // If 'cornerInterval' is too big compared to 'period', we can't apply the periodic optimization. + // If we don't have at least 2 periods, we go with the basic implementation: + bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; + + ResizeKernelMap result = hasAtLeast2Periods + ? new PeriodicKernelMap( + memoryAllocator, + sourceSize, + destinationSize, + ratio, + scale, + radius, + period, + cornerInterval) + : new ResizeKernelMap( + memoryAllocator, + sourceSize, + destinationSize, + destinationSize, + ratio, + scale, + radius); + + result.Initialize(in sampler); + + return result; + } + + /// + /// Initializes the kernel map. + /// + protected internal virtual void Initialize(in TResampler sampler) + where TResampler : struct, IResampler + { + for (int i = 0; i < this.DestinationLength; i++) { - double center = ((destRowIndex + .5) * this.ratio) - .5; + this.kernels[i] = this.BuildKernel(in sampler, i, i); + } + } - // Keep inside bounds. - int left = (int)TolerantMath.Ceiling(center - this.radius); - if (left < 0) - { - left = 0; - } + /// + /// Builds a for the row (in ) + /// referencing the data at row within , + /// so the data reusable by other data rows. + /// + private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) + where TResampler : struct, IResampler + { + double center = ((destRowIndex + .5) * this.ratio) - .5; - int right = (int)TolerantMath.Floor(center + this.radius); - if (right > this.sourceLength - 1) - { - right = this.sourceLength - 1; - } + // Keep inside bounds. + int left = (int)TolerantMath.Ceiling(center - this.radius); + if (left < 0) + { + left = 0; + } + + int right = (int)TolerantMath.Floor(center + this.radius); + if (right > this.sourceLength - 1) + { + right = this.sourceLength - 1; + } - ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); + ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); - Span kernelValues = this.tempValues.AsSpan(0, kernel.Length); - double sum = 0; + Span kernelValues = this.tempValues.AsSpan(0, kernel.Length); + double sum = 0; - for (int j = left; j <= right; j++) - { - double value = sampler.GetValue((float)((j - center) / this.scale)); - sum += value; + for (int j = left; j <= right; j++) + { + double value = sampler.GetValue((float)((j - center) / this.scale)); + sum += value; - kernelValues[j - left] = value; - } + kernelValues[j - left] = value; + } - // Normalize, best to do it here rather than in the pixel loop later on. - if (sum > 0) + // Normalize, best to do it here rather than in the pixel loop later on. + if (sum > 0) + { + for (int j = 0; j < kernel.Length; j++) { - for (int j = 0; j < kernel.Length; j++) - { - // weights[w] = weights[w] / sum: - ref double kRef = ref kernelValues[j]; - kRef /= sum; - } + // weights[w] = weights[w] / sum: + ref double kRef = ref kernelValues[j]; + kRef /= sum; } + } - kernel.Fill(kernelValues); + kernel.Fill(kernelValues); - return kernel; - } + return kernel; + } - /// - /// Returns a referencing values of - /// at row . - /// - private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) - { - int length = right - left + 1; - this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); + /// + /// Returns a referencing values of + /// at row . + /// + private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) + { + int length = right - left + 1; + this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); - Span rowSpan = this.data.DangerousGetRowSpan(dataRowIndex); + Span rowSpan = this.data.DangerousGetRowSpan(dataRowIndex); - ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); - float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); - return new ResizeKernel(left, rowPtr, length); - } + ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); + 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) + [Conditional("DEBUG")] + private void ValidateSizesForCreateKernel(int length, int dataRowIndex, int left, int right) + { + if (length > this.data.Width) { - if (length > this.data.Width) - { - throw new InvalidOperationException( - $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); - } + throw new InvalidOperationException( + $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index fe904126f0..b7651c03f3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -1,54 +1,53 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines an image resizing operation with the given and dimensional parameters. +/// +public class ResizeProcessor : CloningImageProcessor { /// - /// Defines an image resizing operation with the given and dimensional parameters. + /// Initializes a new instance of the class. /// - public class ResizeProcessor : CloningImageProcessor + /// The resize options. + /// The source image size. + public ResizeProcessor(ResizeOptions options, Size sourceSize) { - /// - /// Initializes a new instance of the class. - /// - /// The resize options. - /// The source image size. - public ResizeProcessor(ResizeOptions options, Size sourceSize) - { - 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.Options = options; - this.DestinationWidth = size.Width; - this.DestinationHeight = size.Height; - this.DestinationRectangle = rectangle; - } - - /// - /// Gets the destination width. - /// - public int DestinationWidth { get; } - - /// - /// Gets the destination height. - /// - public int DestinationHeight { get; } - - /// - /// Gets the resize rectangle. - /// - public Rectangle DestinationRectangle { get; } - - /// - /// Gets the resize options. - /// - public ResizeOptions Options { get; } - - /// - public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new ResizeProcessor(configuration, this, source, sourceRectangle); + 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.Options = options; + this.DestinationWidth = size.Width; + this.DestinationHeight = size.Height; + this.DestinationRectangle = rectangle; } + + /// + /// Gets the destination width. + /// + public int DestinationWidth { get; } + + /// + /// Gets the destination height. + /// + public int DestinationHeight { get; } + + /// + /// Gets the resize rectangle. + /// + public Rectangle DestinationRectangle { get; } + + /// + /// Gets the resize options. + /// + public ResizeOptions Options { get; } + + /// + public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + => new ResizeProcessor(configuration, this, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 2f953fa224..9011ec3944 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -1,128 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Implements resizing of images using various resamplers. +/// +/// The pixel format. +internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel { - /// - /// Implements resizing of images using various resamplers. - /// - /// The pixel format. - internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor - where TPixel : unmanaged, IPixel + private readonly ResizeOptions options; + private readonly int destinationWidth; + private readonly int destinationHeight; + private readonly IResampler resampler; + private readonly Rectangle destinationRectangle; + private Image destination; + + public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - private readonly ResizeOptions options; - private readonly int destinationWidth; - private readonly int destinationHeight; - private readonly IResampler resampler; - private readonly Rectangle destinationRectangle; - private Image destination; - - public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationWidth = definition.DestinationWidth; - this.destinationHeight = definition.DestinationHeight; - this.destinationRectangle = definition.DestinationRectangle; - this.options = definition.Options; - this.resampler = definition.Options.Sampler; - } + this.destinationWidth = definition.DestinationWidth; + this.destinationHeight = definition.DestinationHeight; + this.destinationRectangle = definition.DestinationRectangle; + this.options = definition.Options; + this.resampler = definition.Options.Sampler; + } - /// - protected override Size GetDestinationSize() => new(this.destinationWidth, this.destinationHeight); + /// + protected override Size GetDestinationSize() => new(this.destinationWidth, this.destinationHeight); - /// - protected override void BeforeImageApply(Image destination) - { - this.destination = destination; - this.resampler.ApplyTransform(this); + /// + protected override void BeforeImageApply(Image destination) + { + this.destination = destination; + this.resampler.ApplyTransform(this); - base.BeforeImageApply(destination); - } + base.BeforeImageApply(destination); + } - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Everything happens in BeforeImageApply. - } + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + // Everything happens in BeforeImageApply. + } - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler + 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.options.Compand; + bool premultiplyAlpha = this.options.PremultiplyAlpha; + TPixel fillColor = this.options.PadColor.ToPixel(); + bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) + && this.options.PadColor != default; + + // Handle resize dimensions identical to the original + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == destinationRectangle) { - Configuration configuration = this.Configuration; - Image source = this.Source; - Image destination = this.destination; - Rectangle sourceRectangle = this.SourceRectangle; - Rectangle destinationRectangle = this.destinationRectangle; - bool compand = this.options.Compand; - bool premultiplyAlpha = this.options.PremultiplyAlpha; - TPixel fillColor = this.options.PadColor.ToPixel(); - bool shouldFill = (this.options.Mode == ResizeMode.BoxPad || this.options.Mode == ResizeMode.Pad) - && this.options.PadColor != default; - - // Handle resize dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && sourceRectangle == destinationRectangle) + for (int i = 0; i < source.Frames.Count; i++) { - 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()); - } + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; - return; + // The cloned will be blank here copy all the pixel data over + sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); } - 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]; - - if (shouldFill) - { - destinationFrame.Clear(fillColor); - } - - ApplyNNResizeFrameTransform( - configuration, - sourceFrame, - destinationFrame, - sourceRectangle, - destinationRectangle, - interest); - } - - return; - } + return; + } - // 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); + 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]; @@ -133,144 +95,180 @@ public void ApplyTransform(in TResampler sampler) destinationFrame.Clear(fillColor); } - ApplyResizeFrameTransform( + ApplyNNResizeFrameTransform( configuration, sourceFrame, destinationFrame, - horizontalKernelMap, - verticalKernelMap, sourceRectangle, destinationRectangle, - interest, - compand, - premultiplyAlpha); + interest); } + + return; } - private static void ApplyNNResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest) + // 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)destinationRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; - var operation = new NNRowOperation( - sourceRectangle, - destinationRectangle, - interest, - widthFactor, - heightFactor, - source.PixelBuffer, - destination.PixelBuffer); + if (shouldFill) + { + destinationFrame.Clear(fillColor); + } - ParallelRowIterator.IterateRows( + ApplyResizeFrameTransform( configuration, + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, interest, - in operation); + compand, + premultiplyAlpha); } + } - private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) + 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, + interest, + widthFactor, + heightFactor, + source.PixelBuffer, + destination.PixelBuffer); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) + { + if (premultiplyAlpha) { - if (premultiplyAlpha) - { - return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); - } - else - { - return PixelConversionModifiers.None.ApplyCompanding(compand); - } + return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); } - - private static void ApplyResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest, - bool compand, - bool premultiplyAlpha) + else { - PixelAlphaRepresentation? alphaRepresentation = PixelOperations.Instance.GetPixelTypeInfo()?.AlphaRepresentation; - - // Premultiply only if alpha representation is unknown or Unassociated: - bool needsPremultiplication = alphaRepresentation == null || alphaRepresentation.Value == PixelAlphaRepresentation.Unassociated; - premultiplyAlpha &= needsPremultiplication; - PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); - - Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); + return PixelConversionModifiers.None.ApplyCompanding(compand); + } + } - // To reintroduce parallel processing, we would launch multiple workers - // for different row intervals of the image. - using var worker = new ResizeWorker( - configuration, - sourceRegion, - conversionModifiers, - horizontalKernelMap, - verticalKernelMap, - interest, - destinationRectangle.Location); - worker.Initialize(); + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand, + bool premultiplyAlpha) + { + PixelAlphaRepresentation? alphaRepresentation = PixelOperations.Instance.GetPixelTypeInfo()?.AlphaRepresentation; + + // Premultiply only if alpha representation is unknown or Unassociated: + bool needsPremultiplication = alphaRepresentation == null || alphaRepresentation.Value == PixelAlphaRepresentation.Unassociated; + premultiplyAlpha &= needsPremultiplication; + PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); + + Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); + + // To reintroduce parallel processing, we would launch multiple workers + // for different row intervals of the image. + using var worker = new ResizeWorker( + configuration, + sourceRegion, + conversionModifiers, + horizontalKernelMap, + verticalKernelMap, + interest, + destinationRectangle.Location); + worker.Initialize(); + + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } - var workingInterval = new RowInterval(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + private readonly struct NNRowOperation : IRowOperation + { + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly Rectangle interest; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly Buffer2D source; + private readonly Buffer2D destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + Rectangle interest, + float widthFactor, + float heightFactor, + Buffer2D source, + Buffer2D destination) + { + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.interest = interest; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; } - private readonly struct NNRowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - private readonly Rectangle sourceBounds; - private readonly Rectangle destinationBounds; - private readonly Rectangle interest; - private readonly float widthFactor; - private readonly float heightFactor; - private readonly Buffer2D source; - private readonly Buffer2D destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNRowOperation( - Rectangle sourceBounds, - Rectangle destinationBounds, - Rectangle interest, - float widthFactor, - float heightFactor, - Buffer2D source, - Buffer2D destination) - { - this.sourceBounds = sourceBounds; - this.destinationBounds = destinationBounds; - this.interest = interest; - this.widthFactor = widthFactor; - this.heightFactor = heightFactor; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destOriginX = this.destinationBounds.X; + int destOriginY = this.destinationBounds.Y; + int destLeft = this.interest.Left; + int destRight = this.interest.Right; + + // Y coordinates of source points + Span sourceRow = this.source.DangerousGetRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.DangerousGetRowSpan(y); + + for (int x = destLeft; x < destRight; x++) { - int sourceX = this.sourceBounds.X; - int sourceY = this.sourceBounds.Y; - int destOriginX = this.destinationBounds.X; - int destOriginY = this.destinationBounds.Y; - int destLeft = this.interest.Left; - int destRight = this.interest.Right; - - // Y coordinates of source points - Span sourceRow = this.source.DangerousGetRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.DangerousGetRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destOriginX) * this.widthFactor) + sourceX)]; - } + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destOriginX) * this.widthFactor) + sourceX)]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index cb8b68b572..5cd9976a58 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -9,192 +8,191 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Implements the resize algorithm using a sliding window of size +/// maximized by . +/// The height of the window is a multiple of the vertical kernel's maximum diameter. +/// 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 sealed class ResizeWorker : IDisposable + where TPixel : unmanaged, IPixel { - /// - /// Implements the resize algorithm using a sliding window of size - /// maximized by . - /// The height of the window is a multiple of the vertical kernel's maximum diameter. - /// 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 sealed class ResizeWorker : IDisposable - where TPixel : unmanaged, IPixel - { - private readonly Buffer2D transposedFirstPassBuffer; + private readonly Buffer2D transposedFirstPassBuffer; - private readonly Configuration configuration; + private readonly Configuration configuration; - private readonly PixelConversionModifiers conversionModifiers; + private readonly PixelConversionModifiers conversionModifiers; - private readonly ResizeKernelMap horizontalKernelMap; + private readonly ResizeKernelMap horizontalKernelMap; - private readonly Buffer2DRegion source; + private readonly Buffer2DRegion source; - private readonly Rectangle sourceRectangle; + private readonly Rectangle sourceRectangle; - private readonly IMemoryOwner tempRowBuffer; + private readonly IMemoryOwner tempRowBuffer; - private readonly IMemoryOwner tempColumnBuffer; + private readonly IMemoryOwner tempColumnBuffer; - private readonly ResizeKernelMap verticalKernelMap; + private readonly ResizeKernelMap verticalKernelMap; - private readonly Rectangle targetWorkingRect; + private readonly Rectangle targetWorkingRect; - private readonly Point targetOrigin; + private readonly Point targetOrigin; - private readonly int windowBandHeight; + private readonly int windowBandHeight; - private readonly int workerHeight; + private readonly int workerHeight; - private RowInterval currentWindow; + private RowInterval currentWindow; - public ResizeWorker( - Configuration configuration, - Buffer2DRegion source, - PixelConversionModifiers conversionModifiers, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, - Rectangle targetWorkingRect, - Point targetOrigin) - { - this.configuration = configuration; - this.source = source; - this.sourceRectangle = source.Rectangle; - this.conversionModifiers = conversionModifiers; - this.horizontalKernelMap = horizontalKernelMap; - this.verticalKernelMap = verticalKernelMap; - this.targetWorkingRect = targetWorkingRect; - this.targetOrigin = targetOrigin; - - 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, - targetWorkingRect.Width, - workingBufferLimitHintInBytes); - - this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); - - this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( - this.workerHeight, - targetWorkingRect.Width, - preferContiguosImageBuffers: true, - options: AllocationOptions.Clean); - - this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); - this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(targetWorkingRect.Width); - - this.currentWindow = new RowInterval(0, this.workerHeight); - } + public ResizeWorker( + Configuration configuration, + Buffer2DRegion source, + PixelConversionModifiers conversionModifiers, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle targetWorkingRect, + Point targetOrigin) + { + this.configuration = configuration; + this.source = source; + this.sourceRectangle = source.Rectangle; + this.conversionModifiers = conversionModifiers; + this.horizontalKernelMap = horizontalKernelMap; + this.verticalKernelMap = verticalKernelMap; + this.targetWorkingRect = targetWorkingRect; + this.targetOrigin = targetOrigin; + + 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, + targetWorkingRect.Width, + workingBufferLimitHintInBytes); + + this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); + + this.transposedFirstPassBuffer = configuration.MemoryAllocator.Allocate2D( + this.workerHeight, + targetWorkingRect.Width, + preferContiguosImageBuffers: true, + options: AllocationOptions.Clean); + + this.tempRowBuffer = configuration.MemoryAllocator.Allocate(this.sourceRectangle.Width); + this.tempColumnBuffer = configuration.MemoryAllocator.Allocate(targetWorkingRect.Width); + + this.currentWindow = new RowInterval(0, this.workerHeight); + } - public void Dispose() - { - this.transposedFirstPassBuffer.Dispose(); - this.tempRowBuffer.Dispose(); - this.tempColumnBuffer.Dispose(); - } + public void Dispose() + { + this.transposedFirstPassBuffer.Dispose(); + this.tempRowBuffer.Dispose(); + this.tempColumnBuffer.Dispose(); + } - [MethodImpl(InliningOptions.ShortMethod)] - public Span GetColumnSpan(int x, int startY) - => this.transposedFirstPassBuffer.DangerousGetRowSpan(x)[(startY - this.currentWindow.Min)..]; + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetColumnSpan(int x, int startY) + => this.transposedFirstPassBuffer.DangerousGetRowSpan(x)[(startY - this.currentWindow.Min)..]; - public void Initialize() - => this.CalculateFirstPassValues(this.currentWindow); + public void Initialize() + => this.CalculateFirstPassValues(this.currentWindow); - public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) - { - Span tempColSpan = this.tempColumnBuffer.GetSpan(); + public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) + { + Span tempColSpan = this.tempColumnBuffer.GetSpan(); - // When creating transposedFirstPassBuffer, we made sure it's contiguous: - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + // When creating transposedFirstPassBuffer, we made sure it's contiguous: + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); - int left = this.targetWorkingRect.Left; - int right = this.targetWorkingRect.Right; - int width = this.targetWorkingRect.Width; - for (int y = rowInterval.Min; y < rowInterval.Max; y++) - { - // Ensure offsets are normalized for cropping and padding. - ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; + int width = this.targetWorkingRect.Width; + for (int y = rowInterval.Min; y < rowInterval.Max; y++) + { + // Ensure offsets are normalized for cropping and padding. + ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - this.targetOrigin.Y); - while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) - { - this.Slide(); - } + while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + { + this.Slide(); + } - ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); - int top = kernel.StartIndex - this.currentWindow.Min; + int top = kernel.StartIndex - this.currentWindow.Min; - ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; + ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; - for (nint x = 0; x < (right - left); x++) - { - ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); + for (nint x = 0; x < (right - left); x++) + { + ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * this.workerHeight); - // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); - } + // Destination color components + Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); + } - Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); + Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); - } + PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } + } - private void Slide() - { - int minY = this.currentWindow.Max - this.windowBandHeight; - int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); + private void Slide() + { + int minY = this.currentWindow.Max - this.windowBandHeight; + int maxY = Math.Min(minY + this.workerHeight, this.sourceRectangle.Height); - // Copy previous bottom band to the new top: - // (rows <--> columns, because the buffer is transposed) - this.transposedFirstPassBuffer.DangerousCopyColumns( - this.workerHeight - this.windowBandHeight, - 0, - this.windowBandHeight); + // Copy previous bottom band to the new top: + // (rows <--> columns, because the buffer is transposed) + this.transposedFirstPassBuffer.DangerousCopyColumns( + this.workerHeight - this.windowBandHeight, + 0, + this.windowBandHeight); - this.currentWindow = new RowInterval(minY, maxY); + this.currentWindow = new RowInterval(minY, maxY); - // Calculate the remainder: - this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); - } + // Calculate the remainder: + this.CalculateFirstPassValues(this.currentWindow.Slice(this.windowBandHeight)); + } - private void CalculateFirstPassValues(RowInterval calculationInterval) + private void CalculateFirstPassValues(RowInterval calculationInterval) + { + Span tempRowSpan = this.tempRowBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + + int left = this.targetWorkingRect.Left; + int right = this.targetWorkingRect.Right; + int targetOriginX = this.targetOrigin.X; + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { - Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); + Span sourceRow = this.source.DangerousGetRowSpan(y); - int left = this.targetWorkingRect.Left; - int right = this.targetWorkingRect.Right; - int targetOriginX = this.targetOrigin.X; - for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) - { - Span sourceRow = this.source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4( + this.configuration, + sourceRow, + tempRowSpan, + this.conversionModifiers); - PixelOperations.Instance.ToVector4( - this.configuration, - sourceRow, - tempRowSpan, - this.conversionModifiers); + // optimization for: + // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); + ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - // optimization for: - // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); - ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; - - for (nint x = left, z = 0; x < right; x++, z++) - { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); + for (nint x = left, z = 0; x < right; x++, z++) + { + ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); - // optimization for: - // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan); - } + // optimization for: + // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, z * this.workerHeight) = kernel.Convolve(tempRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs index 6e62e90670..1ea2156c8f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs @@ -1,42 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +internal class SwizzleProcessor : TransformProcessor + where TSwizzler : struct, ISwizzler + where TPixel : unmanaged, IPixel { - internal class SwizzleProcessor : TransformProcessor - where TSwizzler : struct, ISwizzler - where TPixel : unmanaged, IPixel - { - private readonly TSwizzler swizzler; - private readonly Size destinationSize; + private readonly TSwizzler swizzler; + private readonly Size destinationSize; - public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.swizzler = swizzler; - this.destinationSize = swizzler.DestinationSize; - } + public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.swizzler = swizzler; + this.destinationSize = swizzler.DestinationSize; + } - protected override Size GetDestinationSize() - => this.destinationSize; + protected override Size GetDestinationSize() + => this.destinationSize; - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + Point p = default; + Point newPoint; + Buffer2D sourceBuffer = source.PixelBuffer; + for (p.Y = 0; p.Y < source.Height; p.Y++) { - Point p = default; - Point newPoint; - Buffer2D sourceBuffer = source.PixelBuffer; - for (p.Y = 0; p.Y < source.Height; p.Y++) + Span rowSpan = sourceBuffer.DangerousGetRowSpan(p.Y); + for (p.X = 0; p.X < source.Width; p.X++) { - Span rowSpan = sourceBuffer.DangerousGetRowSpan(p.Y); - for (p.X = 0; p.X < source.Width; p.X++) - { - newPoint = this.swizzler.Transform(p); - destination[newPoint.X, newPoint.Y] = rowSpan[p.X]; - } + newPoint = this.swizzler.Transform(p); + destination[newPoint.X, newPoint.Y] = rowSpan[p.X]; } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs index 249fef45b6..0b8274aff7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs @@ -3,32 +3,31 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Defines a swizzle operation on an image. +/// +/// The swizzle function type. +public sealed class SwizzleProcessor : IImageProcessor + where TSwizzler : struct, ISwizzler { /// - /// Defines a swizzle operation on an image. + /// Initializes a new instance of the class. /// - /// The swizzle function type. - public sealed class SwizzleProcessor : IImageProcessor - where TSwizzler : struct, ISwizzler + /// The swizzler operation. + public SwizzleProcessor(TSwizzler swizzler) { - /// - /// Initializes a new instance of the class. - /// - /// The swizzler operation. - public SwizzleProcessor(TSwizzler swizzler) - { - this.Swizzler = swizzler; - } + this.Swizzler = swizzler; + } - /// - /// Gets the swizzler operation. - /// - public TSwizzler Swizzler { get; } + /// + /// Gets the swizzler operation. + /// + public TSwizzler Swizzler { get; } - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new SwizzleProcessor(configuration, this.Swizzler, source, sourceRectangle); - } + /// + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new SwizzleProcessor(configuration, this.Swizzler, source, sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index 3bbd39f4ee..0c2c29391b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -3,31 +3,30 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// The base class for all transform processors. Any processor that changes the dimensions of the image should inherit from this. +/// +/// The pixel format. +internal abstract class TransformProcessor : CloningImageProcessor + where TPixel : unmanaged, IPixel { /// - /// The base class for all transform processors. Any processor that changes the dimensions of the image should inherit from this. + /// Initializes a new instance of the class. /// - /// The pixel format. - internal abstract class TransformProcessor : CloningImageProcessor - where TPixel : unmanaged, IPixel + /// The configuration which allows altering default behaviour or extending the library. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + protected TransformProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected TransformProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - } + } - /// - protected override void AfterImageApply(Image destination) - { - TransformProcessorHelpers.UpdateDimensionalMetadata(destination); - base.AfterImageApply(destination); - } + /// + protected override void AfterImageApply(Image destination) + { + TransformProcessorHelpers.UpdateDimensionalMetadata(destination); + base.AfterImageApply(destination); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs index e9d3c02859..8e7452679b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -4,37 +4,36 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Contains helper methods for working with transforms. +/// +internal static class TransformProcessorHelpers { /// - /// Contains helper methods for working with transforms. + /// Updates the dimensional metadata of a transformed image /// - internal static class TransformProcessorHelpers + /// The pixel format. + /// The image to update + public static void UpdateDimensionalMetadata(Image image) + where TPixel : unmanaged, IPixel { - /// - /// Updates the dimensional metadata of a transformed image - /// - /// The pixel format. - /// The image to update - public static void UpdateDimensionalMetadata(Image image) - where TPixel : unmanaged, IPixel + ExifProfile profile = image.Metadata.ExifProfile; + if (profile is null) { - ExifProfile profile = image.Metadata.ExifProfile; - if (profile is null) - { - return; - } + return; + } - // Only set the value if it already exists. - if (profile.GetValue(ExifTag.PixelXDimension) != null) - { - profile.SetValue(ExifTag.PixelXDimension, image.Width); - } + // Only set the value if it already exists. + if (profile.GetValue(ExifTag.PixelXDimension) != null) + { + profile.SetValue(ExifTag.PixelXDimension, image.Width); + } - if (profile.GetValue(ExifTag.PixelYDimension) != null) - { - profile.SetValue(ExifTag.PixelYDimension, image.Height); - } + if (profile.GetValue(ExifTag.PixelYDimension) != null) + { + profile.SetValue(ExifTag.PixelYDimension, image.Height); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs index f6572b2627..6ae0bbbdb0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs @@ -1,418 +1,416 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Processing.Processors.Transforms; + +/// +/// Contains utility methods for working with transforms. +/// +internal static class TransformUtils { /// - /// Contains utility methods for working with transforms. + /// Returns a value that indicates whether the specified matrix is degenerate + /// containing one or more values equivalent to or a + /// zero determinant and therefore cannot be used for linear transforms. + /// + /// The transform matrix. + public static bool IsDegenerate(Matrix3x2 matrix) + => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + + /// + /// Returns a value that indicates whether the specified matrix is degenerate + /// containing one or more values equivalent to or a + /// zero determinant and therefore cannot be used for linear transforms. /// - internal static class TransformUtils + /// The transform matrix. + public static bool IsDegenerate(Matrix4x4 matrix) + => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsZero(float a) + => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; + + /// + /// Returns a value that indicates whether the specified matrix contains any values + /// that are not a number . + /// + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix3x2 matrix) { - /// - /// Returns a value that indicates whether the specified matrix is degenerate - /// containing one or more values equivalent to or a - /// zero determinant and therefore cannot be used for linear transforms. - /// - /// The transform matrix. - public static bool IsDegenerate(Matrix3x2 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - /// - /// Returns a value that indicates whether the specified matrix is degenerate - /// containing one or more values equivalent to or a - /// zero determinant and therefore cannot be used for linear transforms. - /// - /// The transform matrix. - public static bool IsDegenerate(Matrix4x4 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - [MethodImpl(InliningOptions.ShortMethod)] - private static bool IsZero(float a) - => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; - - /// - /// Returns a value that indicates whether the specified matrix contains any values - /// that are not a number . - /// - /// The transform matrix. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static bool IsNaN(Matrix3x2 matrix) - { - return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) - || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) - || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32); - } + return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) + || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) + || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32); + } - /// - /// Returns a value that indicates whether the specified matrix contains any values - /// that are not a number . - /// - /// The transform matrix. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static bool IsNaN(Matrix4x4 matrix) - { - return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14) - || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24) - || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34) - || float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44); - } + /// + /// Returns a value that indicates whether the specified matrix contains any values + /// that are not a number . + /// + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static bool IsNaN(Matrix4x4 matrix) + { + return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14) + || float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24) + || float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34) + || float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44); + } - /// - /// Applies the projective transform against the given coordinates flattened into the 2D space. - /// - /// The "x" vector coordinate. - /// The "y" vector coordinate. - /// The transform matrix. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) - { - const float Epsilon = 0.0000001F; - var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); - return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon); - } + /// + /// Applies the projective transform against the given coordinates flattened into the 2D space. + /// + /// The "x" vector coordinate. + /// The "y" vector coordinate. + /// The transform matrix. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) + { + const float Epsilon = 0.0000001F; + var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); + return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon); + } - /// - /// Creates a centered rotation matrix using the given rotation in degrees and the source size. - /// - /// 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), - Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); - - /// - /// Creates a centered rotation matrix using the given rotation in radians and the source size. - /// - /// 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), - Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); - - /// - /// Creates a centered skew matrix from the give angles in degrees and the source size. - /// - /// The X angle, in degrees. - /// 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), - Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); - - /// - /// Creates a centered skew matrix from the give angles in radians and the source size. - /// - /// The X angle, in radians. - /// 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), - Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); - - /// - /// Gets the centered transform matrix based upon the source and destination rectangles. - /// - /// The source image bounds. - /// The transformation matrix. - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) - { - Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); + /// + /// Creates a centered rotation matrix using the given rotation in degrees and the source size. + /// + /// 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), + Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); + + /// + /// Creates a centered rotation matrix using the given rotation in radians and the source size. + /// + /// 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), + Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in degrees and the source size. + /// + /// The X angle, in degrees. + /// 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), + Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); + + /// + /// Creates a centered skew matrix from the give angles in radians and the source size. + /// + /// The X angle, in radians. + /// 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), + Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); + + /// + /// Gets the centered transform matrix based upon the source and destination rectangles. + /// + /// The source image bounds. + /// The transformation matrix. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) + { + Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); - // We invert the matrix to handle the transformation from screen to world space. - // This ensures scaling matrices are correct. - Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix3x2.Invert(matrix, out Matrix3x2 inverted); - var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); + var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + // Translate back to world space. + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); - return centered; - } + return centered; + } - /// - /// Creates a matrix that performs a tapering projective transform. - /// - /// - /// The rectangular size of the image being transformed. - /// An enumeration that indicates the side of the rectangle that tapers. - /// 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) + /// + /// Creates a matrix that performs a tapering projective transform. + /// + /// + /// The rectangular size of the image being transformed. + /// An enumeration that indicates the side of the rectangle that tapers. + /// 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; + + /* + * SkMatrix is laid out in the following manner: + * + * [ ScaleX SkewY Persp0 ] + * [ SkewX ScaleY Persp1 ] + * [ TransX TransY Persp2 ] + * + * When converting from Matrix4x4 to SkMatrix, the third row and + * column is dropped. When converting from SkMatrix to Matrix4x4 + * the third row and column remain as identity: + * + * [ a b c ] [ a b 0 c ] + * [ d e f ] -> [ d e 0 f ] + * [ g h i ] [ 0 0 1 0 ] + * [ g h 0 i ] + */ + switch (side) { - Matrix4x4 matrix = Matrix4x4.Identity; - - /* - * SkMatrix is laid out in the following manner: - * - * [ ScaleX SkewY Persp0 ] - * [ SkewX ScaleY Persp1 ] - * [ TransX TransY Persp2 ] - * - * When converting from Matrix4x4 to SkMatrix, the third row and - * column is dropped. When converting from SkMatrix to Matrix4x4 - * the third row and column remain as identity: - * - * [ a b c ] [ a b 0 c ] - * [ d e f ] -> [ d e 0 f ] - * [ g h i ] [ 0 0 1 0 ] - * [ g h 0 i ] - */ - switch (side) - { - case TaperSide.Left: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M14 = (fraction - 1) / size.Width; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - matrix.M42 = size.Height * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - matrix.M42 = size.Height * (1 - fraction) / 2; - break; - } - - break; - - case TaperSide.Top: - matrix.M11 = fraction; - matrix.M22 = fraction; - matrix.M24 = (fraction - 1) / size.Height; - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - matrix.M41 = size.Width * (1 - fraction); - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - matrix.M41 = size.Width * (1 - fraction) * .5F; - break; - } - - break; - - case TaperSide.Right: - matrix.M11 = 1 / fraction; - matrix.M14 = (1 - fraction) / (size.Width * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M12 = size.Height * matrix.M14; - break; - - case TaperCorner.Both: - matrix.M12 = size.Height * .5F * matrix.M14; - break; - } - - break; - - case TaperSide.Bottom: - matrix.M22 = 1 / fraction; - matrix.M24 = (1 - fraction) / (size.Height * fraction); - - switch (corner) - { - case TaperCorner.RightOrBottom: - break; - - case TaperCorner.LeftOrTop: - matrix.M21 = size.Width * matrix.M24; - break; - - case TaperCorner.Both: - matrix.M21 = size.Width * .5F * matrix.M24; - break; - } - - break; - } - - return matrix; + case TaperSide.Left: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M14 = (fraction - 1) / size.Width; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + matrix.M42 = size.Height * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + matrix.M42 = size.Height * (1 - fraction) / 2; + break; + } + + break; + + case TaperSide.Top: + matrix.M11 = fraction; + matrix.M22 = fraction; + matrix.M24 = (fraction - 1) / size.Height; + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + matrix.M41 = size.Width * (1 - fraction); + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + matrix.M41 = size.Width * (1 - fraction) * .5F; + break; + } + + break; + + case TaperSide.Right: + matrix.M11 = 1 / fraction; + matrix.M14 = (1 - fraction) / (size.Width * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M12 = size.Height * matrix.M14; + break; + + case TaperCorner.Both: + matrix.M12 = size.Height * .5F * matrix.M14; + break; + } + + break; + + case TaperSide.Bottom: + matrix.M22 = 1 / fraction; + matrix.M24 = (1 - fraction) / (size.Height * fraction); + + switch (corner) + { + case TaperCorner.RightOrBottom: + break; + + case TaperCorner.LeftOrTop: + matrix.M21 = size.Width * matrix.M24; + break; + + case TaperCorner.Both: + matrix.M21 = size.Width * .5F * matrix.M24; + break; + } + + break; } - /// - /// Returns the rectangle bounds relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + return matrix; + } + + /// + /// Returns the rectangle bounds relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Rectangle transformed = GetTransformedRectangle(rectangle, matrix); + return new Rectangle(0, 0, transformed.Width, transformed.Height); + } + + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) { - Rectangle transformed = GetTransformedRectangle(rectangle, matrix); - return new Rectangle(0, 0, transformed.Width, transformed.Height); + return rectangle; } - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) - { - if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) - { - return rectangle; - } + var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); - var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); - var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); - var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); - var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + return GetBoundingRectangle(tl, tr, bl, br); + } - return GetBoundingRectangle(tl, tr, bl, br); - } + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// The . + /// + public static Size GetTransformedSize(Size size, Matrix3x2 matrix) + { + Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The source size. - /// The transformation matrix. - /// - /// The . - /// - public static Size GetTransformedSize(Size size, Matrix3x2 matrix) + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + return size; + } - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) - { - return size; - } + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + return ConstrainSize(rectangle); + } - return ConstrainSize(rectangle); + /// + /// Returns the rectangle relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) + { + if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + { + return rectangle; } - /// - /// Returns the rectangle relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) - { - if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) - { - return rectangle; - } + Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); + Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); + Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); + Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); - Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); - Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); - Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); - Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); + return GetBoundingRectangle(tl, tr, bl, br); + } - return GetBoundingRectangle(tl, tr, bl, br); - } + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The source size. + /// The transformation matrix. + /// + /// 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!"); - /// - /// Returns the size relative to the source for the given transformation matrix. - /// - /// The source size. - /// The transformation matrix. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static Size GetTransformedSize(Size size, Matrix4x4 matrix) + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) { - Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); + return size; + } - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) - { - return size; - } + Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); - Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); + return ConstrainSize(rectangle); + } - return ConstrainSize(rectangle); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static Size ConstrainSize(Rectangle rectangle) + { + // We want to resize the canvas here taking into account any translations. + int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); + int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); - [MethodImpl(InliningOptions.ShortMethod)] - private static Size ConstrainSize(Rectangle rectangle) + // If location in either direction is translated to a negative value equal to or exceeding the + // dimensions in either direction we need to reassign the dimension. + if (height <= 0) { - // We want to resize the canvas here taking into account any translations. - int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); - int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); - - // If location in either direction is translated to a negative value equal to or exceeding the - // dimensions in either direction we need to reassign the dimension. - if (height <= 0) - { - height = rectangle.Height; - } - - if (width <= 0) - { - width = rectangle.Width; - } - - return new Size(width, height); + height = rectangle.Height; } - [MethodImpl(InliningOptions.ShortMethod)] - private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) + if (width <= 0) { - // Find the minimum and maximum "corners" based on the given vectors - float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); - float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); - float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); - float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - - return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); + width = rectangle.Width; } + + 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 + float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); + float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); + float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); + float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); + + return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 0c94055b57..44d6d8e720 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -1,353 +1,349 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// A helper class for constructing instances for use in projective transforms. +/// +public class ProjectiveTransformBuilder { + private readonly List> matrixFactories = new List>(); + /// - /// A helper class for constructing instances for use in projective transforms. + /// Prepends a matrix that performs a tapering projective transform. /// - public class ProjectiveTransformBuilder - { - private readonly List> matrixFactories = new List>(); - - /// - /// Prepends a matrix that performs a tapering projective transform. - /// - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The . - public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - - /// - /// Appends a matrix that performs a tapering projective transform. - /// - /// An enumeration that indicates the side of the rectangle that tapers. - /// An enumeration that indicates on which corners to taper the rectangle. - /// The amount to taper. - /// The . - public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - - /// - /// Prepends a centered rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// The . - public ProjectiveTransformBuilder PrependRotationDegrees(float degrees) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Prepends a centered rotation matrix using the given rotation in radians. - /// - /// The amount of rotation, in radians. - /// The . - public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); - - /// - /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) - => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Prepends a centered rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 origin) - => this.PrependMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); - - /// - /// Appends a centered rotation matrix using the given rotation in degrees. - /// - /// The amount of rotation, in degrees. - /// The . - public ProjectiveTransformBuilder AppendRotationDegrees(float degrees) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - - /// - /// Appends a centered rotation matrix using the given rotation in radians. - /// - /// The amount of rotation, in radians. - /// The . - public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); - - /// - /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) - => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - - /// - /// Appends a centered rotation matrix using the given rotation in radians at the given origin. - /// - /// The amount of rotation, in radians. - /// The rotation origin point. - /// The . - internal ProjectiveTransformBuilder AppendRotationRadians(float radians, Vector2 origin) - => this.AppendMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); - - /// - /// Prepends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public ProjectiveTransformBuilder PrependScale(float scale) - => this.PrependMatrix(Matrix4x4.CreateScale(scale)); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder PrependScale(SizeF scale) - => this.PrependScale((Vector2)scale); - - /// - /// Prepends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder PrependScale(Vector2 scales) - => this.PrependMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); - - /// - /// Appends a scale matrix from the given uniform scale. - /// - /// The uniform scale. - /// The . - public ProjectiveTransformBuilder AppendScale(float scale) - => this.AppendMatrix(Matrix4x4.CreateScale(scale)); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder AppendScale(SizeF scales) - => this.AppendScale((Vector2)scales); - - /// - /// Appends a scale matrix from the given vector scale. - /// - /// The horizontal and vertical scale. - /// The . - public ProjectiveTransformBuilder AppendScale(Vector2 scales) - => this.AppendMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); - - /// - /// Prepends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - internal ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Prepends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); - - /// - /// Prepends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Prepends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.PrependMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); - - /// - /// Appends a centered skew matrix from the give angles in degrees. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The . - internal ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); - - /// - /// Appends a centered skew matrix from the give angles in radians. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The . - public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); - - /// - /// Appends a skew matrix using the given angles in degrees at the given origin. - /// - /// The X angle, in degrees. - /// The Y angle, in degrees. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) - => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - - /// - /// Appends a skew matrix using the given angles in radians at the given origin. - /// - /// The X angle, in radians. - /// The Y angle, in radians. - /// The skew origin point. - /// The . - public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) - => this.AppendMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder PrependTranslation(PointF position) - => this.PrependTranslation((Vector2)position); - - /// - /// Prepends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder PrependTranslation(Vector2 position) - => this.PrependMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder AppendTranslation(PointF position) - => this.AppendTranslation((Vector2)position); - - /// - /// Appends a translation matrix from the given vector. - /// - /// The translation position. - /// The . - public ProjectiveTransformBuilder AppendTranslation(Vector2 position) - => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); - - /// - /// Prepends a raw matrix. - /// - /// The matrix to prepend. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) - { - CheckDegenerate(matrix); - return this.Prepend(_ => matrix); - } + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The . + public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) + => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - /// - /// Appends a raw matrix. - /// - /// The matrix to append. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) - { - CheckDegenerate(matrix); - return this.Append(_ => matrix); - } + /// + /// Appends a matrix that performs a tapering projective transform. + /// + /// An enumeration that indicates the side of the rectangle that tapers. + /// An enumeration that indicates on which corners to taper the rectangle. + /// The amount to taper. + /// The . + public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) + => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); - /// - /// Returns the combined matrix for a given source size. - /// - /// The source image size. - /// The . - public Matrix4x4 BuildMatrix(Size sourceSize) - => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); - - /// - /// Returns the combined matrix for a given source rectangle. - /// - /// The rectangle in the source image. - /// - /// The resultant matrix is degenerate containing one or more values equivalent - /// to or a zero determinant and therefore cannot be used - /// for linear transforms. - /// - /// The . - public Matrix4x4 BuildMatrix(Rectangle sourceRectangle) - { - Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); - Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + /// + /// Prepends a centered rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// The . + public ProjectiveTransformBuilder PrependRotationDegrees(float degrees) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - // Translate the origin matrix to cater for source rectangle offsets. - var matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); + /// + /// Prepends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The . + public ProjectiveTransformBuilder PrependRotationRadians(float radians) + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); - Size size = sourceRectangle.Size; + /// + /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder PrependRotationDegrees(float degrees, Vector2 origin) + => this.PrependRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); - foreach (Func factory in this.matrixFactories) - { - matrix *= factory(size); - } + /// + /// Prepends a centered rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 origin) + => this.PrependMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); - CheckDegenerate(matrix); + /// + /// Appends a centered rotation matrix using the given rotation in degrees. + /// + /// The amount of rotation, in degrees. + /// The . + public ProjectiveTransformBuilder AppendRotationDegrees(float degrees) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees)); - return matrix; - } + /// + /// Appends a centered rotation matrix using the given rotation in radians. + /// + /// The amount of rotation, in radians. + /// The . + public ProjectiveTransformBuilder AppendRotationRadians(float radians) + => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); - private static void CheckDegenerate(Matrix4x4 matrix) - { - if (TransformUtils.IsDegenerate(matrix)) - { - throw new DegenerateTransformException("Matrix is degenerate. Check input values."); - } - } + /// + /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder AppendRotationDegrees(float degrees, Vector2 origin) + => this.AppendRotationRadians(GeometryUtilities.DegreeToRadian(degrees), origin); + + /// + /// Appends a centered rotation matrix using the given rotation in radians at the given origin. + /// + /// The amount of rotation, in radians. + /// The rotation origin point. + /// The . + internal ProjectiveTransformBuilder AppendRotationRadians(float radians, Vector2 origin) + => this.AppendMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); + + /// + /// Prepends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public ProjectiveTransformBuilder PrependScale(float scale) + => this.PrependMatrix(Matrix4x4.CreateScale(scale)); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder PrependScale(SizeF scale) + => this.PrependScale((Vector2)scale); + + /// + /// Prepends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder PrependScale(Vector2 scales) + => this.PrependMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); + + /// + /// Appends a scale matrix from the given uniform scale. + /// + /// The uniform scale. + /// The . + public ProjectiveTransformBuilder AppendScale(float scale) + => this.AppendMatrix(Matrix4x4.CreateScale(scale)); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder AppendScale(SizeF scales) + => this.AppendScale((Vector2)scales); + + /// + /// Appends a scale matrix from the given vector scale. + /// + /// The horizontal and vertical scale. + /// The . + public ProjectiveTransformBuilder AppendScale(Vector2 scales) + => this.AppendMatrix(Matrix4x4.CreateScale(new Vector3(scales, 1F))); + + /// + /// Prepends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + internal ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + + /// + /// Prepends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) + => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + + /// + /// Prepends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder PrependSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.PrependSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); - private ProjectiveTransformBuilder Prepend(Func factory) + /// + /// Prepends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.PrependMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); + + /// + /// Appends a centered skew matrix from the give angles in degrees. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The . + internal ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY)); + + /// + /// Appends a centered skew matrix from the give angles in radians. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The . + public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) + => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + + /// + /// Appends a skew matrix using the given angles in degrees at the given origin. + /// + /// The X angle, in degrees. + /// The Y angle, in degrees. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder AppendSkewDegrees(float degreesX, float degreesY, Vector2 origin) + => this.AppendSkewRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), origin); + + /// + /// Appends a skew matrix using the given angles in radians at the given origin. + /// + /// The X angle, in radians. + /// The Y angle, in radians. + /// The skew origin point. + /// The . + public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY, Vector2 origin) + => this.AppendMatrix(new Matrix4x4(Matrix3x2.CreateSkew(radiansX, radiansY, origin))); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder PrependTranslation(PointF position) + => this.PrependTranslation((Vector2)position); + + /// + /// Prepends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder PrependTranslation(Vector2 position) + => this.PrependMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder AppendTranslation(PointF position) + => this.AppendTranslation((Vector2)position); + + /// + /// Appends a translation matrix from the given vector. + /// + /// The translation position. + /// The . + public ProjectiveTransformBuilder AppendTranslation(Vector2 position) + => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + + /// + /// Prepends a raw matrix. + /// + /// The matrix to prepend. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) + { + CheckDegenerate(matrix); + return this.Prepend(_ => matrix); + } + + /// + /// Appends a raw matrix. + /// + /// The matrix to append. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) + { + CheckDegenerate(matrix); + return this.Append(_ => matrix); + } + + /// + /// Returns the combined matrix for a given source size. + /// + /// The source image size. + /// The . + public Matrix4x4 BuildMatrix(Size sourceSize) + => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + + /// + /// Returns the combined matrix for a given source rectangle. + /// + /// The rectangle in the source image. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + public Matrix4x4 BuildMatrix(Rectangle sourceRectangle) + { + Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle)); + Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle)); + + // Translate the origin matrix to cater for source rectangle offsets. + var matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); + + Size size = sourceRectangle.Size; + + foreach (Func factory in this.matrixFactories) { - this.matrixFactories.Insert(0, factory); - return this; + matrix *= factory(size); } - private ProjectiveTransformBuilder Append(Func factory) + CheckDegenerate(matrix); + + return matrix; + } + + private static void CheckDegenerate(Matrix4x4 matrix) + { + if (TransformUtils.IsDegenerate(matrix)) { - this.matrixFactories.Add(factory); - return this; + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } } + + private ProjectiveTransformBuilder Prepend(Func factory) + { + this.matrixFactories.Insert(0, factory); + return this; + } + + private ProjectiveTransformBuilder Append(Func factory) + { + this.matrixFactories.Add(factory); + return this; + } } diff --git a/src/ImageSharp/Processing/ResizeMode.cs b/src/ImageSharp/Processing/ResizeMode.cs index 7d0ee93835..2c6bddd47b 100644 --- a/src/ImageSharp/Processing/ResizeMode.cs +++ b/src/ImageSharp/Processing/ResizeMode.cs @@ -1,52 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Provides enumeration over how the image should be resized. +/// +public enum ResizeMode { /// - /// Provides enumeration over how the image should be resized. + /// Crops the resized image to fit the bounds of its container. + /// + Crop, + + /// + /// Pads the resized image to fit the bounds of its container. + /// If only one dimension is passed, will maintain the original aspect ratio. + /// + Pad, + + /// + /// Pads the image to fit the bound of the container without resizing the + /// original source. + /// When downscaling, performs the same functionality as + /// + BoxPad, + + /// + /// Constrains the resized image to fit the bounds of its container maintaining + /// the original aspect ratio. + /// + Max, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. + /// Upscaling is disabled in this mode and the original image will be returned + /// if attempted. + /// + Min, + + /// + /// Stretches the resized image to fit the bounds of its container. + /// + Stretch, + + /// + /// The target location and size of the resized image has been manually set. /// - public enum ResizeMode - { - /// - /// Crops the resized image to fit the bounds of its container. - /// - Crop, - - /// - /// Pads the resized image to fit the bounds of its container. - /// If only one dimension is passed, will maintain the original aspect ratio. - /// - Pad, - - /// - /// Pads the image to fit the bound of the container without resizing the - /// original source. - /// When downscaling, performs the same functionality as - /// - BoxPad, - - /// - /// Constrains the resized image to fit the bounds of its container maintaining - /// the original aspect ratio. - /// - Max, - - /// - /// Resizes the image until the shortest side reaches the set given dimension. - /// Upscaling is disabled in this mode and the original image will be returned - /// if attempted. - /// - Min, - - /// - /// Stretches the resized image to fit the bounds of its container. - /// - Stretch, - - /// - /// The target location and size of the resized image has been manually set. - /// - Manual - } + Manual } diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index c7b862c1a5..1893f00ea5 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -3,58 +3,57 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// The resize options for resizing images against certain modes. +/// +public class ResizeOptions { /// - /// The resize options for resizing images against certain modes. - /// - public class ResizeOptions - { - /// - /// Gets or sets the resize mode. - /// - public ResizeMode Mode { get; set; } = ResizeMode.Crop; - - /// - /// Gets or sets the anchor position. - /// - public AnchorPositionMode Position { get; set; } = AnchorPositionMode.Center; - - /// - /// Gets or sets the center coordinates. - /// - public PointF? CenterCoordinates { get; set; } - - /// - /// Gets or sets the target size. - /// - public Size Size { get; set; } - - /// - /// Gets or sets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; set; } = KnownResamplers.Bicubic; - - /// - /// Gets or sets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - public bool Compand { get; set; } - - /// - /// Gets or sets the target rectangle to resize into. - /// - public Rectangle? TargetRectangle { get; set; } - - /// - /// Gets or sets a value indicating whether to premultiply - /// the alpha (if it exists) during the resize operation. - /// - public bool PremultiplyAlpha { get; set; } = true; - - /// - /// Gets or sets the color to use as a background when padding an image. - /// - public Color PadColor { get; set; } - } + /// Gets or sets the resize mode. + /// + public ResizeMode Mode { get; set; } = ResizeMode.Crop; + + /// + /// Gets or sets the anchor position. + /// + public AnchorPositionMode Position { get; set; } = AnchorPositionMode.Center; + + /// + /// Gets or sets the center coordinates. + /// + public PointF? CenterCoordinates { get; set; } + + /// + /// Gets or sets the target size. + /// + public Size Size { get; set; } + + /// + /// Gets or sets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; set; } = KnownResamplers.Bicubic; + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + public bool Compand { get; set; } + + /// + /// Gets or sets the target rectangle to resize into. + /// + public Rectangle? TargetRectangle { get; set; } + + /// + /// Gets or sets a value indicating whether to premultiply + /// the alpha (if it exists) during the resize operation. + /// + public bool PremultiplyAlpha { get; set; } = true; + + /// + /// Gets or sets the color to use as a background when padding an image. + /// + public Color PadColor { get; set; } } diff --git a/src/ImageSharp/Processing/RotateMode.cs b/src/ImageSharp/Processing/RotateMode.cs index 02d16f4327..3bbc7b2707 100644 --- a/src/ImageSharp/Processing/RotateMode.cs +++ b/src/ImageSharp/Processing/RotateMode.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Provides enumeration over how the image should be rotated. +/// +public enum RotateMode { /// - /// Provides enumeration over how the image should be rotated. + /// Do not rotate the image. /// - public enum RotateMode - { - /// - /// Do not rotate the image. - /// - None, + None, - /// - /// Rotate the image by 90 degrees clockwise. - /// - Rotate90 = 90, + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90 = 90, - /// - /// Rotate the image by 180 degrees clockwise. - /// - Rotate180 = 180, + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180 = 180, - /// - /// Rotate the image by 270 degrees clockwise. - /// - Rotate270 = 270 - } + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 = 270 } diff --git a/src/ImageSharp/Processing/TaperCorner.cs b/src/ImageSharp/Processing/TaperCorner.cs index 2fa39c676a..8c47f458d4 100644 --- a/src/ImageSharp/Processing/TaperCorner.cs +++ b/src/ImageSharp/Processing/TaperCorner.cs @@ -1,26 +1,25 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Enumerates the various options which determine how to taper corners +/// +public enum TaperCorner { /// - /// Enumerates the various options which determine how to taper corners + /// Taper the left or top corner /// - public enum TaperCorner - { - /// - /// Taper the left or top corner - /// - LeftOrTop, + LeftOrTop, - /// - /// Taper the right or bottom corner - /// - RightOrBottom, + /// + /// Taper the right or bottom corner + /// + RightOrBottom, - /// - /// Taper the both sets of corners - /// - Both - } + /// + /// Taper the both sets of corners + /// + Both } diff --git a/src/ImageSharp/Processing/TaperSide.cs b/src/ImageSharp/Processing/TaperSide.cs index 5c313199ce..f613181fc2 100644 --- a/src/ImageSharp/Processing/TaperSide.cs +++ b/src/ImageSharp/Processing/TaperSide.cs @@ -1,31 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing; + +/// +/// Enumerates the various options which determine which side to taper +/// +public enum TaperSide { /// - /// Enumerates the various options which determine which side to taper + /// Taper the left side /// - public enum TaperSide - { - /// - /// Taper the left side - /// - Left, + Left, - /// - /// Taper the top side - /// - Top, + /// + /// Taper the top side + /// + Top, - /// - /// Taper the right side - /// - Right, + /// + /// Taper the right side + /// + Right, - /// - /// Taper the bottom side - /// - Bottom - } + /// + /// Taper the bottom side + /// + Bottom } diff --git a/src/ImageSharp/ReadOrigin.cs b/src/ImageSharp/ReadOrigin.cs index a5f5bb2c9b..5c3bdfcafb 100644 --- a/src/ImageSharp/ReadOrigin.cs +++ b/src/ImageSharp/ReadOrigin.cs @@ -1,21 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp +namespace SixLabors.ImageSharp; + +/// +/// Specifies the position in a stream to use for reading. +/// +public enum ReadOrigin { /// - /// Specifies the position in a stream to use for reading. + /// Specifies the beginning of a stream. /// - public enum ReadOrigin - { - /// - /// Specifies the beginning of a stream. - /// - Begin, + Begin, - /// - /// Specifies the current position within a stream. - /// - Current - } + /// + /// Specifies the current position within a stream. + /// + Current } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs index e11825122d..5bd806c487 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeBmp { - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeBmp - { - private byte[] bmpBytes; + private byte[] bmpBytes; - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.bmpBytes == null) { - if (this.bmpBytes == null) - { - this.bmpBytes = File.ReadAllBytes(this.TestImageFullPath); - } + this.bmpBytes = File.ReadAllBytes(this.TestImageFullPath); } + } - [Params(TestImages.Bmp.Car)] - public string TestImage { get; set; } + [Params(TestImages.Bmp.Car)] + public string TestImage { get; set; } - [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public SDSize BmpSystemDrawing() - { - using var memoryStream = new MemoryStream(this.bmpBytes); - using var image = SDImage.FromStream(memoryStream); - return image.Size; - } + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] + public SDSize BmpSystemDrawing() + { + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; + } - [Benchmark(Description = "ImageSharp Bmp")] - public Size BmpImageSharp() - { - using var memoryStream = new MemoryStream(this.bmpBytes); - using var image = Image.Load(memoryStream); - return new Size(image.Width, image.Height); - } + [Benchmark(Description = "ImageSharp Bmp")] + public Size BmpImageSharp() + { + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs index 3f64b2cff9..ab6cdf28e0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs @@ -2,54 +2,52 @@ // Licensed under the Six Labors Split License. using System.Drawing.Imaging; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeBmp { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeBmp - { - private Stream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; + private Stream bmpStream; + private SDImage bmpDrawing; + private Image bmpCore; - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) { - if (this.bmpStream == null) - { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - } + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = SDImage.FromStream(this.bmpStream); } + } - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpStream = null; + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } - [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] - public void BmpSystemDrawing() - { - using var memoryStream = new MemoryStream(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); - } + [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] + public void BmpSystemDrawing() + { + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); + } - [Benchmark(Description = "ImageSharp Bmp")] - public void BmpImageSharp() - { - using var memoryStream = new MemoryStream(); - this.bmpCore.SaveAsBmp(memoryStream); - } + [Benchmark(Description = "ImageSharp Bmp")] + public void BmpImageSharp() + { + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsBmp(memoryStream); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs index a23cf878c7..50afea5a6d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs @@ -1,32 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using System.Drawing.Imaging; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Bmp; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded - { - protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; + protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; - [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] - public void EncodeBmpImageSharp() - => this.ForEachImageSharpImage((img, ms) => - { - img.Save(ms, new BmpEncoder()); - return null; - }); + [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] + public void EncodeBmpImageSharp() + => 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; - }); - } + [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] + public void EncodeBmpSystemDrawing() + => this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Bmp); + return null; + }); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs index 53718a4212..21b193ddaf 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeGif { - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeGif - { - private byte[] gifBytes; + private byte[] gifBytes; - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.gifBytes == null) { - if (this.gifBytes == null) - { - this.gifBytes = File.ReadAllBytes(this.TestImageFullPath); - } + this.gifBytes = File.ReadAllBytes(this.TestImageFullPath); } + } - [Params(TestImages.Gif.Rings)] - public string TestImage { get; set; } + [Params(TestImages.Gif.Rings)] + public string TestImage { get; set; } - [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public SDSize GifSystemDrawing() - { - using var memoryStream = new MemoryStream(this.gifBytes); - using var image = SDImage.FromStream(memoryStream); - return image.Size; - } + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] + public SDSize GifSystemDrawing() + { + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; + } - [Benchmark(Description = "ImageSharp Gif")] - public Size GifImageSharp() - { - using var memoryStream = new MemoryStream(this.gifBytes); - using var image = Image.Load(memoryStream); - return new Size(image.Width, image.Height); - } + [Benchmark(Description = "ImageSharp Gif")] + public Size GifImageSharp() + { + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs index 79d491b0ae..048c2aadda 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Drawing.Imaging; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; @@ -11,58 +10,57 @@ using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeGif { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeGif - { - // System.Drawing needs this. - private Stream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; + // System.Drawing needs this. + private Stream bmpStream; + 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 }) - }; + // 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; } + [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) { - if (this.bmpStream == null) - { - 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); - } + 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); } + } - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpStream = null; + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } - [Benchmark(Baseline = true, Description = "System.Drawing Gif")] - public void GifSystemDrawing() - { - using var memoryStream = new MemoryStream(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); - } + [Benchmark(Baseline = true, Description = "System.Drawing Gif")] + public void GifSystemDrawing() + { + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); + } - [Benchmark(Description = "ImageSharp Gif")] - public void GifImageSharp() - { - using var memoryStream = new MemoryStream(); - this.bmpCore.SaveAsGif(memoryStream, this.encoder); - } + [Benchmark(Description = "ImageSharp Gif")] + public void GifImageSharp() + { + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs index 301bf9c8f0..c523b5c204 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs @@ -1,43 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded - { - [Params(InputImageCategory.AllImages)] - public override InputImageCategory InputCategory { get; set; } + [Params(InputImageCategory.AllImages)] + public override InputImageCategory InputCategory { get; set; } - protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Gif/" }; + protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Gif/" }; - [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] - public void EncodeGifImageSharp() - => this.ForEachImageSharpImage((img, ms) => + [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] + public void EncodeGifImageSharp() + => this.ForEachImageSharpImage((img, ms) => + { + // Try to get as close to System.Drawing's output as possible + var options = new GifEncoder { - // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder - { - Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) - }; + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; - img.Save(ms, options); - return null; - }); + 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; - }); - } + [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] + public void EncodeGifSystemDrawing() + => this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Gif); + return null; + }); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs index af60ee8bc6..24e033cab1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_AddInPlace.cs @@ -4,18 +4,17 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Block8x8F_AddInPlace { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_AddInPlace + [Benchmark] + public float AddInplace() { - [Benchmark] - public float AddInplace() - { - float f = 42F; - Block8x8F b = default; - b.AddInPlace(f); - return f; - } + float f = 42F; + Block8x8F b = default; + b.AddInPlace(f); + return f; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs index ee196cbea2..f36f92e907 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,375 +10,374 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +public unsafe class Block8x8F_CopyTo1x1 { - public unsafe class Block8x8F_CopyTo1x1 - { - private Block8x8F block; - private readonly Block8x8F[] blockArray = new Block8x8F[1]; + private Block8x8F block; + private readonly Block8x8F[] blockArray = new Block8x8F[1]; - private static readonly int Width = 100; + private static readonly int Width = 100; - 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; + 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() + [GlobalSetup] + public void Setup() + { + if (!SimdUtils.HasVector8) { - if (!SimdUtils.HasVector8) - { - throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); - } + throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); + } - this.bufferHandle = GCHandle.Alloc(this.buffer, GCHandleType.Pinned); - this.bufferPtr = (float*)this.bufferHandle.AddrOfPinnedObject(); + 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); - } + // 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; - } + [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.AsRef(this.bufferPtr); - int destStride = Width * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } + [Benchmark(Baseline = true)] + public void Original() + { + ref byte selfBase = ref Unsafe.As(ref this.block); + 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); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, row * destStride); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } - // [Benchmark] - public void UseVector8() - { - ref Block8x8F s = ref this.block; - 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)); - ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); - ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); - ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); - ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); - ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); - ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); - - Vector row0 = Unsafe.As>(ref s.V0L); - Vector row1 = Unsafe.As>(ref s.V1L); - Vector row2 = Unsafe.As>(ref s.V2L); - Vector row3 = Unsafe.As>(ref s.V3L); - Vector row4 = Unsafe.As>(ref s.V4L); - Vector row5 = Unsafe.As>(ref s.V5L); - Vector row6 = Unsafe.As>(ref s.V6L); - Vector row7 = Unsafe.As>(ref s.V7L); - - d0 = row0; - d1 = row1; - d2 = row2; - d3 = row3; - d4 = row4; - d5 = row5; - d6 = row6; - d7 = row7; - } + // [Benchmark] + public void UseVector8() + { + ref Block8x8F s = ref this.block; + 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)); + ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); + ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); + ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); + ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); + ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); + ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); + + Vector row0 = Unsafe.As>(ref s.V0L); + Vector row1 = Unsafe.As>(ref s.V1L); + Vector row2 = Unsafe.As>(ref s.V2L); + Vector row3 = Unsafe.As>(ref s.V3L); + Vector row4 = Unsafe.As>(ref s.V4L); + Vector row5 = Unsafe.As>(ref s.V5L); + Vector row6 = Unsafe.As>(ref s.V6L); + Vector row7 = Unsafe.As>(ref s.V7L); + + d0 = row0; + d1 = row1; + d2 = row2; + d3 = row3; + d4 = row4; + d5 = row5; + d6 = row6; + d7 = row7; + } - // [Benchmark] - public void UseVector8_V2() - { - ref Block8x8F s = ref this.block; - 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)); - ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); - ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); - ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); - ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); - ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); - ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); - - d0 = Unsafe.As>(ref s.V0L); - d1 = Unsafe.As>(ref s.V1L); - d2 = Unsafe.As>(ref s.V2L); - d3 = Unsafe.As>(ref s.V3L); - d4 = Unsafe.As>(ref s.V4L); - d5 = Unsafe.As>(ref s.V5L); - d6 = Unsafe.As>(ref s.V6L); - d7 = Unsafe.As>(ref s.V7L); - } + // [Benchmark] + public void UseVector8_V2() + { + ref Block8x8F s = ref this.block; + 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)); + ref Vector d2 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 2)); + ref Vector d3 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 3)); + ref Vector d4 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 4)); + ref Vector d5 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 5)); + ref Vector d6 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 6)); + ref Vector d7 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride * 7)); + + d0 = Unsafe.As>(ref s.V0L); + d1 = Unsafe.As>(ref s.V1L); + d2 = Unsafe.As>(ref s.V2L); + d3 = Unsafe.As>(ref s.V3L); + d4 = Unsafe.As>(ref s.V4L); + d5 = Unsafe.As>(ref s.V5L); + d6 = Unsafe.As>(ref s.V6L); + d7 = Unsafe.As>(ref s.V7L); + } - [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; - } + [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; + } - [Benchmark] - public void UseVector256_Avx2_Variant1() - { - int stride = Width; - float* d = this.bufferPtr; - float* s = this.blockPtr; - Vector256 v; + [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); + Avx.Store(d, v); - v = Avx.LoadVector256(s + 8); - Avx.Store(d + stride, 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 * 2)); + Avx.Store(d + (stride * 2), v); - v = Avx.LoadVector256(s + (8 * 3)); - Avx.Store(d + (stride * 3), 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 * 4)); + Avx.Store(d + (stride * 4), v); - v = Avx.LoadVector256(s + (8 * 5)); - Avx.Store(d + (stride * 5), 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 * 6)); + Avx.Store(d + (stride * 6), v); - v = Avx.LoadVector256(s + (8 * 7)); - Avx.Store(d + (stride * 7), 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_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() + { + 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() + { + 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_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() + // [Benchmark] + public void UseVector256_Avx2_Variant3_WithLocalPinning() + { + int stride = Width; + fixed (float* d = this.unpinnedBuffer) { - int stride = Width; - fixed (float* d = this.unpinnedBuffer) + fixed (Block8x8F* ss = &this.block) { - fixed (Block8x8F* ss = &this.block) - { - float* 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); - } + float* 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; - sbyte* d = (sbyte*)this.bufferPtr; - sbyte* 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); - } - - // *** 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 | 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 | + // [Benchmark] + public void UseVector256_Avx2_Variant3_sbyte() + { + int stride = Width * 4; + sbyte* d = (sbyte*)this.bufferPtr; + sbyte* 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); } + + // *** 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 | 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 | } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 473cea8d01..88c0098230 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -9,402 +9,401 @@ using SixLabors.ImageSharp.Memory; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +public class Block8x8F_CopyTo2x2 { - public class Block8x8F_CopyTo2x2 + private Block8x8F block; + + private Buffer2D buffer; + + private Buffer2DRegion destRegion; + + [GlobalSetup] + public void Setup() { - private Block8x8F block; + this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); + this.destRegion = this.buffer.GetRegion(200, 100, 128, 128); + } - private Buffer2D buffer; + [Benchmark(Baseline = true)] + public void Original() + { + ref float destBase = ref this.destRegion.GetReferenceToOrigin(); + int destStride = this.destRegion.Stride; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2(ref src, ref destBase, 7, destStride); + } - private Buffer2DRegion destRegion; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2(ref Block8x8F src, ref float destBase, int row, int destStride) + { + ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; + Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W; + } - [GlobalSetup] - public void Setup() - { - this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destRegion = this.buffer.GetRegion(200, 100, 128, 128); - } + [Benchmark] + public void Original_V2() + { + ref float destBase = ref this.destRegion.GetReferenceToOrigin(); + int destStride = this.destRegion.Stride; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_V2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_V2(ref src, ref destBase, 7, destStride); + } - [Benchmark(Baseline = true)] - public void Original() - { - ref float destBase = ref this.destRegion.GetReferenceToOrigin(); - int destStride = this.destRegion.Stride; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2(ref Block8x8F src, ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); - - Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; - Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; - Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; - Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; - Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; - Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; - Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; - Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W; - - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W; - Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W; - } - - [Benchmark] - public void Original_V2() - { - ref float destBase = ref this.destRegion.GetReferenceToOrigin(); - int destStride = this.destRegion.Stride; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_V2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_V2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_V2(ref Block8x8F src, ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float dest0 = ref Unsafe.Add(ref destBase, row * 2 * destStride); - - Unsafe.Add(ref dest0, 0) = selfLeft.X; - Unsafe.Add(ref dest0, 1) = selfLeft.X; - Unsafe.Add(ref dest0, 2) = selfLeft.Y; - Unsafe.Add(ref dest0, 3) = selfLeft.Y; - Unsafe.Add(ref dest0, 4) = selfLeft.Z; - Unsafe.Add(ref dest0, 5) = selfLeft.Z; - Unsafe.Add(ref dest0, 6) = selfLeft.W; - Unsafe.Add(ref dest0, 7) = selfLeft.W; - - ref float dest1 = ref Unsafe.Add(ref dest0, 8); - - Unsafe.Add(ref dest1, 0) = selfRight.X; - Unsafe.Add(ref dest1, 1) = selfRight.X; - Unsafe.Add(ref dest1, 2) = selfRight.Y; - Unsafe.Add(ref dest1, 3) = selfRight.Y; - Unsafe.Add(ref dest1, 4) = selfRight.Z; - Unsafe.Add(ref dest1, 5) = selfRight.Z; - Unsafe.Add(ref dest1, 6) = selfRight.W; - Unsafe.Add(ref dest1, 7) = selfRight.W; - - ref float dest2 = ref Unsafe.Add(ref dest0, destStride); - - Unsafe.Add(ref dest2, 0) = selfLeft.X; - Unsafe.Add(ref dest2, 1) = selfLeft.X; - Unsafe.Add(ref dest2, 2) = selfLeft.Y; - Unsafe.Add(ref dest2, 3) = selfLeft.Y; - Unsafe.Add(ref dest2, 4) = selfLeft.Z; - Unsafe.Add(ref dest2, 5) = selfLeft.Z; - Unsafe.Add(ref dest2, 6) = selfLeft.W; - Unsafe.Add(ref dest2, 7) = selfLeft.W; - - ref float dest3 = ref Unsafe.Add(ref dest2, 8); - - Unsafe.Add(ref dest3, 0) = selfRight.X; - Unsafe.Add(ref dest3, 1) = selfRight.X; - Unsafe.Add(ref dest3, 2) = selfRight.Y; - Unsafe.Add(ref dest3, 3) = selfRight.Y; - Unsafe.Add(ref dest3, 4) = selfRight.Z; - Unsafe.Add(ref dest3, 5) = selfRight.Z; - Unsafe.Add(ref dest3, 6) = selfRight.W; - Unsafe.Add(ref dest3, 7) = selfRight.W; - } - - [Benchmark] - public void UseVector2() - { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); - ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); - ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); - - var xLeft = new Vector2(sLeft.X); - var yLeft = new Vector2(sLeft.Y); - var zLeft = new Vector2(sLeft.Z); - var wLeft = new Vector2(sLeft.W); - - var xRight = new Vector2(sRight.X); - var yRight = new Vector2(sRight.Y); - var zRight = new Vector2(sRight.Z); - var wRight = new Vector2(sRight.W); - - dTopLeft = xLeft; - Unsafe.Add(ref dTopLeft, 1) = yLeft; - Unsafe.Add(ref dTopLeft, 2) = zLeft; - Unsafe.Add(ref dTopLeft, 3) = wLeft; - - dTopRight = xRight; - Unsafe.Add(ref dTopRight, 1) = yRight; - Unsafe.Add(ref dTopRight, 2) = zRight; - Unsafe.Add(ref dTopRight, 3) = wRight; - - dBottomLeft = xLeft; - Unsafe.Add(ref dBottomLeft, 1) = yLeft; - Unsafe.Add(ref dBottomLeft, 2) = zLeft; - Unsafe.Add(ref dBottomLeft, 3) = wLeft; - - dBottomRight = xRight; - Unsafe.Add(ref dBottomRight, 1) = yRight; - Unsafe.Add(ref dBottomRight, 2) = zRight; - Unsafe.Add(ref dBottomRight, 3) = wRight; - } - - [Benchmark] - public void UseVector4() - { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) - { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); - ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); - ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); - - var xLeft = new Vector4(sLeft.X); - var yLeft = new Vector4(sLeft.Y); - var zLeft = new Vector4(sLeft.Z); - var wLeft = new Vector4(sLeft.W); - - var xRight = new Vector4(sRight.X); - var yRight = new Vector4(sRight.Y); - var zRight = new Vector4(sRight.Z); - var wRight = new Vector4(sRight.W); - - Unsafe.As(ref dTopLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; - - Unsafe.As(ref dTopRight) = xRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 1)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 2)) = zRight; - Unsafe.As(ref Unsafe.Add(ref dTopRight, 3)) = wRight; - - Unsafe.As(ref dBottomLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; - - Unsafe.As(ref dBottomRight) = xRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 1)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 2)) = zRight; - Unsafe.As(ref Unsafe.Add(ref dBottomRight, 3)) = wRight; - } - - [Benchmark] - public void UseVector4_SafeRightCorner() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_V2(ref Block8x8F src, ref float destBase, int row, int destStride) + { + ref Vector4 selfLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float dest0 = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref dest0, 0) = selfLeft.X; + Unsafe.Add(ref dest0, 1) = selfLeft.X; + Unsafe.Add(ref dest0, 2) = selfLeft.Y; + Unsafe.Add(ref dest0, 3) = selfLeft.Y; + Unsafe.Add(ref dest0, 4) = selfLeft.Z; + Unsafe.Add(ref dest0, 5) = selfLeft.Z; + Unsafe.Add(ref dest0, 6) = selfLeft.W; + Unsafe.Add(ref dest0, 7) = selfLeft.W; + + ref float dest1 = ref Unsafe.Add(ref dest0, 8); + + Unsafe.Add(ref dest1, 0) = selfRight.X; + Unsafe.Add(ref dest1, 1) = selfRight.X; + Unsafe.Add(ref dest1, 2) = selfRight.Y; + Unsafe.Add(ref dest1, 3) = selfRight.Y; + Unsafe.Add(ref dest1, 4) = selfRight.Z; + Unsafe.Add(ref dest1, 5) = selfRight.Z; + Unsafe.Add(ref dest1, 6) = selfRight.W; + Unsafe.Add(ref dest1, 7) = selfRight.W; + + ref float dest2 = ref Unsafe.Add(ref dest0, destStride); + + Unsafe.Add(ref dest2, 0) = selfLeft.X; + Unsafe.Add(ref dest2, 1) = selfLeft.X; + Unsafe.Add(ref dest2, 2) = selfLeft.Y; + Unsafe.Add(ref dest2, 3) = selfLeft.Y; + Unsafe.Add(ref dest2, 4) = selfLeft.Z; + Unsafe.Add(ref dest2, 5) = selfLeft.Z; + Unsafe.Add(ref dest2, 6) = selfLeft.W; + Unsafe.Add(ref dest2, 7) = selfLeft.W; + + ref float dest3 = ref Unsafe.Add(ref dest2, 8); + + Unsafe.Add(ref dest3, 0) = selfRight.X; + Unsafe.Add(ref dest3, 1) = selfRight.X; + Unsafe.Add(ref dest3, 2) = selfRight.Y; + Unsafe.Add(ref dest3, 3) = selfRight.Y; + Unsafe.Add(ref dest3, 4) = selfRight.Z; + Unsafe.Add(ref dest3, 5) = selfRight.Z; + Unsafe.Add(ref dest3, 6) = selfRight.W; + Unsafe.Add(ref dest3, 7) = selfRight.W; + } + + [Benchmark] + public void UseVector2() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); + + var xLeft = new Vector2(sLeft.X); + var yLeft = new Vector2(sLeft.Y); + var zLeft = new Vector2(sLeft.Z); + var wLeft = new Vector2(sLeft.W); + + var xRight = new Vector2(sRight.X); + var yRight = new Vector2(sRight.Y); + var zRight = new Vector2(sRight.Z); + var wRight = new Vector2(sRight.W); + + dTopLeft = xLeft; + Unsafe.Add(ref dTopLeft, 1) = yLeft; + Unsafe.Add(ref dTopLeft, 2) = zLeft; + Unsafe.Add(ref dTopLeft, 3) = wLeft; + + dTopRight = xRight; + Unsafe.Add(ref dTopRight, 1) = yRight; + Unsafe.Add(ref dTopRight, 2) = zRight; + Unsafe.Add(ref dTopRight, 3) = wRight; + + dBottomLeft = xLeft; + Unsafe.Add(ref dBottomLeft, 1) = yLeft; + Unsafe.Add(ref dBottomLeft, 2) = zLeft; + Unsafe.Add(ref dBottomLeft, 3) = wLeft; + + dBottomRight = xRight; + Unsafe.Add(ref dBottomRight, 1) = yRight; + Unsafe.Add(ref dBottomRight, 2) = zRight; + Unsafe.Add(ref dBottomRight, 3) = wRight; + } + + [Benchmark] + public void UseVector4() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dTopRight = ref Unsafe.Add(ref dTopLeft, 4); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + ref Vector2 dBottomRight = ref Unsafe.Add(ref dBottomLeft, 4); + + var xLeft = new Vector4(sLeft.X); + var yLeft = new Vector4(sLeft.Y); + var zLeft = new Vector4(sLeft.Z); + var wLeft = new Vector4(sLeft.W); + + var xRight = new Vector4(sRight.X); + var yRight = new Vector4(sRight.Y); + var zRight = new Vector4(sRight.Z); + var wRight = new Vector4(sRight.W); + + Unsafe.As(ref dTopLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; + + Unsafe.As(ref dTopRight) = xRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 1)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 2)) = zRight; + Unsafe.As(ref Unsafe.Add(ref dTopRight, 3)) = wRight; + + Unsafe.As(ref dBottomLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; + + Unsafe.As(ref dBottomRight) = xRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 1)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 2)) = zRight; + Unsafe.As(ref Unsafe.Add(ref dBottomRight, 3)) = wRight; + } + + [Benchmark] + public void UseVector4_SafeRightCorner() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4_SafeRightCorner(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); + ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); + + var xLeft = new Vector4(sLeft.X); + var yLeft = new Vector4(sLeft.Y); + var zLeft = new Vector4(sLeft.Z); + var wLeft = new Vector4(sLeft.W); + + var xRight = new Vector4(sRight.X); + var yRight = new Vector4(sRight.Y); + var zRight = new Vector4(sRight.Z); + var wRight = new Vector2(sRight.W); + + Unsafe.As(ref dTopLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; + + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 4)) = xRight; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 5)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dTopLeft, 6)) = zRight; + Unsafe.Add(ref dTopLeft, 7) = wRight; + + Unsafe.As(ref dBottomLeft) = xLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; + + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 4)) = xRight; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 5)) = yRight; + Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 6)) = zRight; + Unsafe.Add(ref dBottomLeft, 7) = wRight; + } + + [Benchmark] + public void UseVector4_V2() + { + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; + + ref Block8x8F src = ref this.block; + + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 0, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 1, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 2, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 3, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 4, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 5, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 6, destStride); + WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl2x2_Vector4_V2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + { + ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); + ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); + + int offset = 2 * row * destStride; + ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); + ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); + + var xyLeft = new Vector4(sLeft.X) { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4_SafeRightCorner(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4_SafeRightCorner(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + Z = sLeft.Y, + W = sLeft.Y + }; + + var zwLeft = new Vector4(sLeft.Z) { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - ref Vector2 dTopLeft = ref Unsafe.Add(ref destBase, 2 * row * destStride); - ref Vector2 dBottomLeft = ref Unsafe.Add(ref dTopLeft, destStride); - - var xLeft = new Vector4(sLeft.X); - var yLeft = new Vector4(sLeft.Y); - var zLeft = new Vector4(sLeft.Z); - var wLeft = new Vector4(sLeft.W); - - var xRight = new Vector4(sRight.X); - var yRight = new Vector4(sRight.Y); - var zRight = new Vector4(sRight.Z); - var wRight = new Vector2(sRight.W); - - Unsafe.As(ref dTopLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 3)) = wLeft; - - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 4)) = xRight; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 5)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dTopLeft, 6)) = zRight; - Unsafe.Add(ref dTopLeft, 7) = wRight; - - Unsafe.As(ref dBottomLeft) = xLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 1)) = yLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 2)) = zLeft; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 3)) = wLeft; - - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 4)) = xRight; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 5)) = yRight; - Unsafe.As(ref Unsafe.Add(ref dBottomLeft, 6)) = zRight; - Unsafe.Add(ref dBottomLeft, 7) = wRight; - } - - [Benchmark] - public void UseVector4_V2() + Z = sLeft.W, + W = sLeft.W + }; + + var xyRight = new Vector4(sRight.X) { - ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); - int destStride = this.destRegion.Stride / 2; - - ref Block8x8F src = ref this.block; - - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 0, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 1, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 2, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 3, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 4, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 5, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 6, destStride); - WidenCopyImpl2x2_Vector4_V2(ref src, ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WidenCopyImpl2x2_Vector4_V2(ref Block8x8F src, ref Vector2 destBase, int row, int destStride) + Z = sRight.Y, + W = sRight.Y + }; + + var zwRight = new Vector4(sRight.Z) { - ref Vector4 sLeft = ref Unsafe.Add(ref src.V0L, 2 * row); - ref Vector4 sRight = ref Unsafe.Add(ref sLeft, 1); - - int offset = 2 * row * destStride; - ref Vector4 dTopLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset)); - ref Vector4 dBottomLeft = ref Unsafe.As(ref Unsafe.Add(ref destBase, offset + destStride)); - - var xyLeft = new Vector4(sLeft.X) - { - Z = sLeft.Y, - W = sLeft.Y - }; - - var zwLeft = new Vector4(sLeft.Z) - { - Z = sLeft.W, - W = sLeft.W - }; - - var xyRight = new Vector4(sRight.X) - { - Z = sRight.Y, - W = sRight.Y - }; - - var zwRight = new Vector4(sRight.Z) - { - Z = sRight.W, - W = sRight.W - }; - - dTopLeft = xyLeft; - Unsafe.Add(ref dTopLeft, 1) = zwLeft; - Unsafe.Add(ref dTopLeft, 2) = xyRight; - Unsafe.Add(ref dTopLeft, 3) = zwRight; - - dBottomLeft = xyLeft; - Unsafe.Add(ref dBottomLeft, 1) = zwLeft; - Unsafe.Add(ref dBottomLeft, 2) = xyRight; - Unsafe.Add(ref dBottomLeft, 3) = zwRight; - } - - // RESULTS: - // Method | Mean | Error | StdDev | Scaled | ScaledSD | - // --------------------------- |---------:|----------:|----------:|-------:|---------:| - // Original | 92.69 ns | 2.4722 ns | 2.7479 ns | 1.00 | 0.00 | - // Original_V2 | 91.72 ns | 1.2089 ns | 1.0095 ns | 0.99 | 0.03 | - // UseVector2 | 86.70 ns | 0.5873 ns | 0.5206 ns | 0.94 | 0.03 | - // UseVector4 | 55.42 ns | 0.2482 ns | 0.2322 ns | 0.60 | 0.02 | - // 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 | + Z = sRight.W, + W = sRight.W + }; + + dTopLeft = xyLeft; + Unsafe.Add(ref dTopLeft, 1) = zwLeft; + Unsafe.Add(ref dTopLeft, 2) = xyRight; + Unsafe.Add(ref dTopLeft, 3) = zwRight; + + dBottomLeft = xyLeft; + Unsafe.Add(ref dBottomLeft, 1) = zwLeft; + Unsafe.Add(ref dBottomLeft, 2) = xyRight; + Unsafe.Add(ref dBottomLeft, 3) = zwRight; } + + // RESULTS: + // Method | Mean | Error | StdDev | Scaled | ScaledSD | + // --------------------------- |---------:|----------:|----------:|-------:|---------:| + // Original | 92.69 ns | 2.4722 ns | 2.7479 ns | 1.00 | 0.00 | + // Original_V2 | 91.72 ns | 1.2089 ns | 1.0095 ns | 0.99 | 0.03 | + // UseVector2 | 86.70 ns | 0.5873 ns | 0.5206 ns | 0.94 | 0.03 | + // UseVector4 | 55.42 ns | 0.2482 ns | 0.2322 ns | 0.60 | 0.02 | + // 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 | } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs index 531eca1f7d..efc347586c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs @@ -8,153 +8,152 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +/// +/// The goal of this benchmark is to measure the following Jpeg-related scenario: +/// - Take 2 blocks of float-s +/// - Divide each float pair, round the result +/// - Iterate through all rounded values as int-s +/// +public unsafe class Block8x8F_DivideRound { - /// - /// The goal of this benchmark is to measure the following Jpeg-related scenario: - /// - Take 2 blocks of float-s - /// - Divide each float pair, round the result - /// - Iterate through all rounded values as int-s - /// - public unsafe class Block8x8F_DivideRound - { - private const int ExecutionCount = 5; // Added this to reduce the effect of copying the blocks - private static readonly Vector4 MinusOne = new Vector4(-1); - private static readonly Vector4 Half = new Vector4(0.5f); + private const int ExecutionCount = 5; // Added this to reduce the effect of copying the blocks + private static readonly Vector4 MinusOne = new Vector4(-1); + private static readonly Vector4 Half = new Vector4(0.5f); - private Block8x8F inputDividend; - private Block8x8F inputDivisor; + private Block8x8F inputDividend; + private Block8x8F inputDivisor; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + for (int i = 0; i < Block8x8F.Size; i++) { - for (int i = 0; i < Block8x8F.Size; i++) - { - this.inputDividend[i] = i * 44.8f; - this.inputDivisor[i] = 100 - i; - } + this.inputDividend[i] = i * 44.8f; + this.inputDivisor[i] = 100 - i; } + } - [Benchmark(Baseline = true)] - public int ByRationalIntegers() - { - int sum = 0; + [Benchmark(Baseline = true)] + public int ByRationalIntegers() + { + int sum = 0; - Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisor; - float* pDividend = (float*)&b1; - float* pDivisor = (float*)&b2; + Block8x8F b1 = this.inputDividend; + Block8x8F b2 = this.inputDivisor; + float* pDividend = (float*)&b1; + float* pDivisor = (float*)&b2; - int* result = stackalloc int[Block8x8F.Size]; + int* result = stackalloc int[Block8x8F.Size]; - for (int cnt = 0; cnt < ExecutionCount; cnt++) + for (int cnt = 0; cnt < ExecutionCount; cnt++) + { + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) { - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - 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]; - } + int a = (int)pDividend[i]; + int b = (int)pDivisor; + result[i] = RationalRound(a, b); } - return sum; + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += result[i]; + } } - [Benchmark] - public int BySystemMathRound() - { - int sum = 0; + return sum; + } + + [Benchmark] + public int BySystemMathRound() + { + int sum = 0; - Block8x8F b1 = this.inputDividend; - Block8x8F b2 = this.inputDivisor; - float* pDividend = (float*)&b1; - float* pDivisor = (float*)&b2; + Block8x8F b1 = this.inputDividend; + Block8x8F b2 = this.inputDivisor; + float* pDividend = (float*)&b1; + float* pDivisor = (float*)&b2; - for (int cnt = 0; cnt < ExecutionCount; cnt++) + for (int cnt = 0; cnt < ExecutionCount; cnt++) + { + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) { - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - double value = pDividend[i] / pDivisor[i]; - pDividend[i] = (float)System.Math.Round(value); - } - - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += (int)pDividend[i]; - } + double value = pDividend[i] / pDivisor[i]; + pDividend[i] = (float)System.Math.Round(value); } - return sum; + for (int i = 0; i < Block8x8F.Size; i++) + { + sum += (int)pDividend[i]; + } } - [Benchmark] - public int BySimdMagic() - { - int sum = 0; + return sum; + } + + [Benchmark] + public int BySimdMagic() + { + int sum = 0; - Block8x8F bDividend = this.inputDividend; - Block8x8F bDivisor = this.inputDivisor; - float* pDividend = (float*)&bDividend; + Block8x8F bDividend = this.inputDividend; + Block8x8F bDivisor = this.inputDivisor; + float* pDividend = (float*)&bDividend; - for (int cnt = 0; cnt < ExecutionCount; cnt++) + for (int cnt = 0; cnt < ExecutionCount; cnt++) + { + sum = 0; + DivideRoundAll(ref bDividend, ref bDivisor); + for (int i = 0; i < Block8x8F.Size; i++) { - sum = 0; - DivideRoundAll(ref bDividend, ref bDivisor); - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += (int)pDividend[i]; - } + sum += (int)pDividend[i]; } - - return sum; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) - { - a.V0L = DivideRound(a.V0L, b.V0L); - a.V0R = DivideRound(a.V0R, b.V0R); - a.V1L = DivideRound(a.V1L, b.V1L); - a.V1R = DivideRound(a.V1R, b.V1R); - a.V2L = DivideRound(a.V2L, b.V2L); - a.V2R = DivideRound(a.V2R, b.V2R); - a.V3L = DivideRound(a.V3L, b.V3L); - a.V3R = DivideRound(a.V3R, b.V3R); - a.V4L = DivideRound(a.V4L, b.V4L); - a.V4R = DivideRound(a.V4R, b.V4R); - a.V5L = DivideRound(a.V5L, b.V5L); - a.V5R = DivideRound(a.V5R, b.V5R); - a.V6L = DivideRound(a.V6L, b.V6L); - a.V6R = DivideRound(a.V6R, b.V6R); - a.V7L = DivideRound(a.V7L, b.V7L); - a.V7R = DivideRound(a.V7R, b.V7R); - } + return sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) - { - var sign = Vector4.Min(dividend, Vector4.One); - sign = Vector4.Max(sign, MinusOne); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) + { + a.V0L = DivideRound(a.V0L, b.V0L); + a.V0R = DivideRound(a.V0R, b.V0R); + a.V1L = DivideRound(a.V1L, b.V1L); + a.V1R = DivideRound(a.V1R, b.V1R); + a.V2L = DivideRound(a.V2L, b.V2L); + a.V2R = DivideRound(a.V2R, b.V2R); + a.V3L = DivideRound(a.V3L, b.V3L); + a.V3R = DivideRound(a.V3R, b.V3R); + a.V4L = DivideRound(a.V4L, b.V4L); + a.V4R = DivideRound(a.V4R, b.V4R); + a.V5L = DivideRound(a.V5L, b.V5L); + a.V5R = DivideRound(a.V5R, b.V5R); + a.V6L = DivideRound(a.V6L, b.V6L); + a.V6R = DivideRound(a.V6R, b.V6R); + a.V7L = DivideRound(a.V7L, b.V7L); + a.V7R = DivideRound(a.V7R, b.V7R); + } - return (dividend / divisor) + (sign * Half); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) + { + var sign = Vector4.Min(dividend, Vector4.One); + sign = Vector4.Max(sign, MinusOne); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int RationalRound(int dividend, int divisor) - { - if (dividend >= 0) - { - return (dividend + (divisor >> 1)) / divisor; - } + return (dividend / divisor) + (sign * Half); + } - return -((-dividend + (divisor >> 1)) / divisor); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int RationalRound(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; } + + return -((-dividend + (divisor >> 1)) / divisor); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs index 18a76c652d..91255c9466 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs @@ -1,51 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +public class Block8x8F_LoadFromInt16 { - public class Block8x8F_LoadFromInt16 - { - private Block8x8 source; + private Block8x8 source; - private Block8x8F dest = default; + private Block8x8F dest = default; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + if (Vector.Count != 8) { - if (Vector.Count != 8) - { - throw new NotSupportedException("Vector.Count != 8"); - } - - for (short i = 0; i < Block8x8F.Size; i++) - { - this.source[i] = i; - } + throw new NotSupportedException("Vector.Count != 8"); } - [Benchmark(Baseline = true)] - public void Scalar() + for (short i = 0; i < Block8x8F.Size; i++) { - this.dest.LoadFromInt16Scalar(ref this.source); + this.source[i] = i; } + } - [Benchmark] - public void ExtendedAvx2() - { - this.dest.LoadFromInt16ExtendedAvx2(ref this.source); - } + [Benchmark(Baseline = true)] + public void Scalar() + { + this.dest.LoadFromInt16Scalar(ref this.source); + } - // RESULT: - // Method | Mean | Error | StdDev | Scaled | - // ------------- |---------:|----------:|----------:|-------:| - // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | - // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | + [Benchmark] + public void ExtendedAvx2() + { + this.dest.LoadFromInt16ExtendedAvx2(ref this.source); } + + // RESULT: + // Method | Mean | Error | StdDev | Scaled | + // ------------- |---------:|----------:|----------:|-------:| + // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | + // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs index 998656147f..a5abeb3b66 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceBlock.cs @@ -4,34 +4,33 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Block8x8F_MultiplyInPlaceBlock { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_MultiplyInPlaceBlock - { - private static readonly Block8x8F Source = Create8x8FloatData(); + private static readonly Block8x8F Source = Create8x8FloatData(); - [Benchmark] - public void MultiplyInPlaceBlock() - { - Block8x8F dest = default; - Source.MultiplyInPlace(ref dest); - } + [Benchmark] + public void MultiplyInPlaceBlock() + { + Block8x8F dest = default; + Source.MultiplyInPlace(ref dest); + } - private static Block8x8F Create8x8FloatData() + private static Block8x8F Create8x8FloatData() + { + var result = new float[64]; + for (int i = 0; i < 8; i++) { - var result = new float[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) - { - result[(i * 8) + j] = (i * 10) + j; - } + result[(i * 8) + j] = (i * 10) + j; } - - var source = default(Block8x8F); - source.LoadFrom(result); - return source; } + + var source = default(Block8x8F); + source.LoadFrom(result); + return source; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs index 92226e5452..8ffb06e38a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_MultiplyInPlaceScalar.cs @@ -4,18 +4,17 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Block8x8F_MultiplyInPlaceScalar { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_MultiplyInPlaceScalar + [Benchmark] + public float MultiplyInPlaceScalar() { - [Benchmark] - public float MultiplyInPlaceScalar() - { - float f = 42F; - Block8x8F b = default; - b.MultiplyInPlace(f); - return f; - } + float f = 42F; + Block8x8F b = default; + b.MultiplyInPlace(f); + return f; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs index 3c8c19f7a4..b1718759ea 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs @@ -4,32 +4,31 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Block8x8F_Quantize { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_Quantize + private Block8x8F block = CreateFromScalar(1); + private Block8x8F quant = CreateFromScalar(1); + private Block8x8 result = default; + + [Benchmark] + public short Quantize() { - private Block8x8F block = CreateFromScalar(1); - private Block8x8F quant = CreateFromScalar(1); - private Block8x8 result = default; + Block8x8F.Quantize(ref this.block, ref this.result, ref this.quant); + return this.result[0]; + } - [Benchmark] - public short Quantize() + private static Block8x8F CreateFromScalar(float scalar) + { + Block8x8F block = default; + for (int i = 0; i < 64; i++) { - Block8x8F.Quantize(ref this.block, ref this.result, ref this.quant); - return this.result[0]; + block[i] = scalar; } - private static Block8x8F CreateFromScalar(float scalar) - { - Block8x8F block = default; - for (int i = 0; i < 64; i++) - { - block[i] = scalar; - } - - return block; - } + return block; } } @@ -37,14 +36,14 @@ private static Block8x8F CreateFromScalar(float scalar) BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1165 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT | Method | Job | Mean | Error | StdDev | Ratio | |--------- |-----------------|---------:|---------:|---------:|------:| | Quantize | No HwIntrinsics | 73.34 ns | 1.081 ns | 1.011 ns | 1.00 | | Quantize | SSE | 24.11 ns | 0.298 ns | 0.279 ns | 0.33 | | Quantize | AVX | 15.90 ns | 0.074 ns | 0.065 ns | 0.22 | - */ +*/ diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs index 1ff81d677c..afe9d94ae8 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -12,481 +11,480 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +public unsafe class Block8x8F_Round { - public unsafe class Block8x8F_Round - { - private Block8x8F block; + private Block8x8F block; - private readonly byte[] blockBuffer = new byte[512]; - private GCHandle blockHandle; - private float* alignedPtr; + private readonly byte[] blockBuffer = new byte[512]; + private GCHandle blockHandle; + private float* alignedPtr; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + if (Vector.Count != 8) { - if (Vector.Count != 8) - { - throw new NotSupportedException("Vector.Count != 8"); - } - - this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); - ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); - ptr += 16; - ptr -= ptr % 16; - - if (ptr % 16 != 0) - { - throw new Exception("ptr is unaligned"); - } - - this.alignedPtr = (float*)ptr; + throw new NotSupportedException("Vector.Count != 8"); } - [GlobalCleanup] - public void Cleanup() + this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); + ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); + ptr += 16; + ptr -= ptr % 16; + + if (ptr % 16 != 0) { - this.blockHandle.Free(); - this.alignedPtr = null; + throw new Exception("ptr is unaligned"); } - [Benchmark] - public void ScalarRound() - { - ref float b = ref Unsafe.As(ref this.block); + this.alignedPtr = (float*)ptr; + } - for (int i = 0; i < Block8x8F.Size; i++) - { - ref float v = ref Unsafe.Add(ref b, i); - v = (float)Math.Round(v); - } - } + [GlobalCleanup] + public void Cleanup() + { + this.blockHandle.Free(); + this.alignedPtr = null; + } - [Benchmark(Baseline = true)] - public void SimdUtils_FastRound_Vector8() - { - ref Block8x8F b = ref this.block; - - 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 ScalarRound() + { + ref float b = ref Unsafe.As(ref this.block); - [Benchmark] - public void SimdUtils_FastRound_Vector8_ForceAligned() + for (int i = 0; i < Block8x8F.Size; i++) { - 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); + ref float v = ref Unsafe.Add(ref b, i); + v = (float)Math.Round(v); } + } - [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); - } + [Benchmark(Baseline = true)] + public void SimdUtils_FastRound_Vector8() + { + ref Block8x8F b = ref this.block; + + 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 Sse41_V1() - { - ref Vector128 b0 = ref Unsafe.As>(ref this.block); + [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); + } - ref Vector128 p = ref b0; - p = Sse41.RoundToNearestInteger(p); + [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); + } - 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 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); + [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); + } - 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); - [Benchmark] - public unsafe void Sse41_V3() + for (int i = 0; i < 15; i++) { - ref Vector128 p = ref Unsafe.As>(ref this.block); + p = ref Unsafe.AddByteOffset(ref p, offset); 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_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_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 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; + [Benchmark] + public void Sse41_V6_Aligned() + { + float* p = this.alignedPtr; - Round8SseVectors(p); - Round8SseVectors(p + 32); - } + 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); - } + 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); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs index 60a793c5bb..07907f21d7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Transpose.cs @@ -4,33 +4,32 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Block8x8F_Transpose { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Block8x8F_Transpose - { - private Block8x8F source = Create8x8FloatData(); + private Block8x8F source = Create8x8FloatData(); - [Benchmark] - public float TransposeInplace() - { - this.source.TransposeInplace(); - return this.source[0]; - } + [Benchmark] + public float TransposeInplace() + { + this.source.TransposeInplace(); + return this.source[0]; + } - private static Block8x8F Create8x8FloatData() + private static Block8x8F Create8x8FloatData() + { + Block8x8F block = default; + for (int i = 0; i < 8; i++) { - Block8x8F block = default; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) - { - block[(i * 8) + j] = (i * 10) + j; - } + block[(i * 8) + j] = (i * 10) + j; } - - return block; } + + return block; } } @@ -38,10 +37,10 @@ private static Block8x8F Create8x8FloatData() BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1237 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - 3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +1. No HwIntrinsics : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +2. SSE : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +3. AVX : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT Runtime=.NET Core 3.1 diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 59fe2ed431..6ad20ce679 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -4,38 +4,37 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class CmykColorConversion : ColorConversionBenchmark { - [Config(typeof(Config.ShortMultiFramework))] - public class CmykColorConversion : ColorConversionBenchmark + public CmykColorConversion() + : base(4) + { + } + + [Benchmark(Baseline = true)] + public void Scalar() { - public CmykColorConversion() - : base(4) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVector8() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVectorAvx() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values); - } + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVector8() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.CmykVector(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVectorAvx() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs index 130d884bd4..8964667b74 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs @@ -1,61 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +public abstract class ColorConversionBenchmark { - public abstract class ColorConversionBenchmark - { - private readonly int componentCount; + private readonly int componentCount; - public const int Count = 128; + public const int Count = 128; - protected ColorConversionBenchmark(int componentCount) - => this.componentCount = componentCount; + protected ColorConversionBenchmark(int componentCount) + => this.componentCount = componentCount; - protected Buffer2D[] Input { get; private set; } + protected Buffer2D[] Input { get; private set; } - [GlobalSetup] - public void Setup() - { - this.Input = CreateRandomValues(this.componentCount, Count); - } + [GlobalSetup] + public void Setup() + { + this.Input = CreateRandomValues(this.componentCount, Count); + } - [GlobalCleanup] - public void Cleanup() + [GlobalCleanup] + public void Cleanup() + { + foreach (Buffer2D buffer in this.Input) { - foreach (Buffer2D buffer in this.Input) - { - buffer.Dispose(); - } + buffer.Dispose(); } + } - private static Buffer2D[] CreateRandomValues( - int componentCount, - int inputBufferLength, - float minVal = 0f, - float maxVal = 255f) + private static Buffer2D[] CreateRandomValues( + int componentCount, + int inputBufferLength, + float minVal = 0f, + float maxVal = 255f) + { + var rnd = new Random(42); + var buffers = new Buffer2D[componentCount]; + for (int i = 0; i < componentCount; i++) { - var rnd = new Random(42); - var buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) - { - var values = new float[inputBufferLength]; + var values = new float[inputBufferLength]; - for (int j = 0; j < inputBufferLength; j++) - { - values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; - } - - // no need to dispose when buffer is not array owner - buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); + for (int j = 0; j < inputBufferLength; j++) + { + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } - return buffers; + // no need to dispose when buffer is not array owner + buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); } + + return buffers; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 5f70787943..47aac3464e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -4,30 +4,29 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class GrayscaleColorConversion : ColorConversionBenchmark { - [Config(typeof(Config.ShortMultiFramework))] - public class GrayscaleColorConversion : ColorConversionBenchmark + public GrayscaleColorConversion() + : base(1) { - public GrayscaleColorConversion() - : base(1) - { - } + } - [Benchmark(Baseline = true)] - public void Scalar() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + [Benchmark(Baseline = true)] + public void Scalar() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values); - } + new JpegColorConverterBase.GrayscaleScalar(8).ConvertToRgbInplace(values); + } - [Benchmark] - public void SimdVectorAvx() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + [Benchmark] + public void SimdVectorAvx() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values); - } + new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 99b1f88107..18b3eac611 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -4,38 +4,37 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class RgbColorConversion : ColorConversionBenchmark { - [Config(typeof(Config.ShortMultiFramework))] - public class RgbColorConversion : ColorConversionBenchmark + public RgbColorConversion() + : base(3) + { + } + + [Benchmark(Baseline = true)] + public void Scalar() { - public RgbColorConversion() - : base(3) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVector8() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVectorAvx() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values); - } + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVector8() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.RgbVector(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVectorAvx() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.RgbAvx(8).ConvertToRgbInplace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index d528ea5ff4..800190d2d7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -4,38 +4,37 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class YCbCrColorConversion : ColorConversionBenchmark { - [Config(typeof(Config.ShortMultiFramework))] - public class YCbCrColorConversion : ColorConversionBenchmark + public YCbCrColorConversion() + : base(3) + { + } + + [Benchmark] + public void Scalar() { - public YCbCrColorConversion() - : base(3) - { - } - - [Benchmark] - public void Scalar() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVector8() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVectorAvx() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values); - } + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVector8() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YCbCrVector(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVectorAvx() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YCbCrAvx(8).ConvertToRgbInplace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index cfc47bdc6e..991d3b0d02 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -4,38 +4,37 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class YccKColorConverter : ColorConversionBenchmark { - [Config(typeof(Config.ShortMultiFramework))] - public class YccKColorConverter : ColorConversionBenchmark + public YccKColorConverter() + : base(4) + { + } + + [Benchmark(Baseline = true)] + public void Scalar() { - public YccKColorConverter() - : base(4) - { - } - - [Benchmark(Baseline = true)] - public void Scalar() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVector8() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values); - } - - [Benchmark] - public void SimdVectorAvx2() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values); - } + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVector8() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YccKVector(8).ConvertToRgbInplace(values); + } + + [Benchmark] + public void SimdVectorAvx2() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.YccKAvx(8).ConvertToRgbInplace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 1fee65fa0b..53d6028295 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -1,76 +1,74 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +public class DecodeJpeg { - public class DecodeJpeg - { - private JpegDecoder decoder; + private JpegDecoder decoder; - private MemoryStream preloadedImageStream; + private MemoryStream preloadedImageStream; - private void GenericSetup(string imageSubpath) - { - this.decoder = new JpegDecoder(); - byte[] bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, imageSubpath)); - this.preloadedImageStream = new MemoryStream(bytes); - } + private void GenericSetup(string imageSubpath) + { + this.decoder = new JpegDecoder(); + byte[] bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, imageSubpath)); + this.preloadedImageStream = new MemoryStream(bytes); + } - private void GenericBechmark() - { - this.preloadedImageStream.Position = 0; - using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream); - } + private void GenericBechmark() + { + this.preloadedImageStream.Position = 0; + using Image img = this.decoder.Decode(DecoderOptions.Default, this.preloadedImageStream); + } - [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] - public void SetupBaselineInterleaved444() => - this.GenericSetup(TestImages.Jpeg.Baseline.Winter444_Interleaved); + [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] + public void SetupBaselineInterleaved444() => + this.GenericSetup(TestImages.Jpeg.Baseline.Winter444_Interleaved); - [GlobalSetup(Target = nameof(JpegBaselineInterleaved420))] - public void SetupBaselineInterleaved420() => - this.GenericSetup(TestImages.Jpeg.Baseline.Hiyamugi); + [GlobalSetup(Target = nameof(JpegBaselineInterleaved420))] + public void SetupBaselineInterleaved420() => + this.GenericSetup(TestImages.Jpeg.Baseline.Hiyamugi); - [GlobalSetup(Target = nameof(JpegBaseline400))] - public void SetupBaselineSingleComponent() => - this.GenericSetup(TestImages.Jpeg.Baseline.Jpeg400); + [GlobalSetup(Target = nameof(JpegBaseline400))] + public void SetupBaselineSingleComponent() => + this.GenericSetup(TestImages.Jpeg.Baseline.Jpeg400); - [GlobalSetup(Target = nameof(JpegProgressiveNonInterleaved420))] - public void SetupProgressiveNoninterleaved420() => - this.GenericSetup(TestImages.Jpeg.Progressive.Winter420_NonInterleaved); + [GlobalSetup(Target = nameof(JpegProgressiveNonInterleaved420))] + public void SetupProgressiveNoninterleaved420() => + this.GenericSetup(TestImages.Jpeg.Progressive.Winter420_NonInterleaved); - [GlobalCleanup] - public void Cleanup() - { - this.preloadedImageStream.Dispose(); - this.preloadedImageStream = null; - } + [GlobalCleanup] + public void Cleanup() + { + this.preloadedImageStream.Dispose(); + this.preloadedImageStream = null; + } - [Benchmark(Description = "Baseline 4:4:4 Interleaved")] - public void JpegBaselineInterleaved444() => this.GenericBechmark(); + [Benchmark(Description = "Baseline 4:4:4 Interleaved")] + public void JpegBaselineInterleaved444() => this.GenericBechmark(); - [Benchmark(Description = "Baseline 4:2:0 Interleaved")] - public void JpegBaselineInterleaved420() => this.GenericBechmark(); + [Benchmark(Description = "Baseline 4:2:0 Interleaved")] + public void JpegBaselineInterleaved420() => this.GenericBechmark(); - [Benchmark(Description = "Baseline 4:0:0 (grayscale)")] - public void JpegBaseline400() => this.GenericBechmark(); + [Benchmark(Description = "Baseline 4:0:0 (grayscale)")] + public void JpegBaseline400() => this.GenericBechmark(); - [Benchmark(Description = "Progressive 4:2:0 Non-Interleaved")] - public void JpegProgressiveNonInterleaved420() => this.GenericBechmark(); - } + [Benchmark(Description = "Progressive 4:2:0 Non-Interleaved")] + public void JpegProgressiveNonInterleaved420() => this.GenericBechmark(); } /* BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT - DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +[Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT | Method | Mean | Error | StdDev | @@ -86,8 +84,8 @@ FRESH BENCHMARKS FOR NEW SPECTRAL CONVERSION SETUP BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT | Method | Mean | Error | StdDev | diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 546051772e..3a3c81b52c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -9,60 +8,59 @@ using SixLabors.ImageSharp.Tests; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeJpegParseStreamOnly { - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeJpegParseStreamOnly - { - [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] - public string TestImage { get; set; } + [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] + public string TestImage { get; set; } - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - private byte[] jpegBytes; + private byte[] jpegBytes; - [GlobalSetup] - public void Setup() - => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + [GlobalSetup] + public void Setup() + => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); + + [Benchmark(Baseline = true, Description = "System.Drawing FULL")] + public SDSize JpegSystemDrawing() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = System.Drawing.Image.FromStream(memoryStream); + return image.Size; + } + + [Benchmark(Description = "JpegDecoderCore.ParseStream")] + public void ParseStream() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); + var options = new JpegDecoderOptions(); + options.GeneralOptions.SkipMetadata = true; - [Benchmark(Baseline = true, Description = "System.Drawing FULL")] - public SDSize JpegSystemDrawing() + using var decoder = new JpegDecoderCore(options); + var spectralConverter = new NoopSpectralConverter(); + decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); + } + + // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels + // Nor we need to allocate final pixel buffer + // Note: this still introduces virtual method call overhead for baseline interleaved images + // There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction + private class NoopSpectralConverter : SpectralConverter + { + public override void ConvertStrideBaseline() { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var image = System.Drawing.Image.FromStream(memoryStream); - return image.Size; } - [Benchmark(Description = "JpegDecoderCore.ParseStream")] - public void ParseStream() + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); - var options = new JpegDecoderOptions(); - options.GeneralOptions.SkipMetadata = true; - - using var decoder = new JpegDecoderCore(options); - var spectralConverter = new NoopSpectralConverter(); - decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); } - // We want to test only stream parsing and scan decoding, we don't need to convert spectral data to actual pixels - // Nor we need to allocate final pixel buffer - // Note: this still introduces virtual method call overhead for baseline interleaved images - // There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction - private class NoopSpectralConverter : SpectralConverter + public override void PrepareForDecoding() { - public override void ConvertStrideBaseline() - { - } - - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - } - - public override void PrepareForDecoding() - { - } } } } @@ -71,10 +69,10 @@ public override void PrepareForDecoding() BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1083 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT - Job-VAJCIU : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT - Job-INPXCR : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT - Job-JRCLOJ : .NET Framework 4.8 (4.8.4390.0), X64 RyuJIT +[Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT +Job-VAJCIU : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT +Job-INPXCR : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT +Job-JRCLOJ : .NET Framework 4.8 (4.8.4390.0), X64 RyuJIT IterationCount=3 LaunchCount=1 WarmupCount=3 | Method | Job | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index d6d699c0c8..bece1de5bd 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -1,41 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// An expensive Jpeg benchmark, running on a wide range of input images, +/// showing aggregate results. +/// +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase { - /// - /// An expensive Jpeg benchmark, running on a wide range of input images, - /// showing aggregate results. - /// - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase - { - protected override IEnumerable InputImageSubfoldersOrFiles - => new[] - { - TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, - TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, - }; + protected override IEnumerable InputImageSubfoldersOrFiles + => new[] + { + TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, + TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + }; - [Params(InputImageCategory.AllImages)] - public override InputImageCategory InputCategory { get; set; } + [Params(InputImageCategory.AllImages)] + public override InputImageCategory InputCategory { get; set; } - [Benchmark] - public void ImageSharp() - => this.ForEachStream(ms => Image.Load(ms)); + [Benchmark] + public void ImageSharp() + => this.ForEachStream(ms => Image.Load(ms)); - [Benchmark(Baseline = true)] - public void SystemDrawing() - => this.ForEachStream(SDImage.FromStream); - } + [Benchmark(Baseline = true)] + public void SystemDrawing() + => this.ForEachStream(SDImage.FromStream); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index f704bef280..035f800a9b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests; @@ -9,67 +8,66 @@ using SDSize = System.Drawing.Size; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Image-specific Jpeg benchmarks +/// +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeJpeg_ImageSpecific { - /// - /// Image-specific Jpeg benchmarks - /// - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeJpeg_ImageSpecific - { - private byte[] jpegBytes; + private byte[] jpegBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); #pragma warning disable SA1115 - [Params( - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, + [Params( + TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, + TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - // 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)] + // 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)] - public string TestImage { get; set; } + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() - { - if (this.jpegBytes == null) - { - this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - } - } - - [Benchmark(Baseline = true)] - public SDSize SystemDrawing() + [GlobalSetup] + public void ReadImages() + { + if (this.jpegBytes == null) { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var image = SDImage.FromStream(memoryStream); - return image.Size; + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); } + } - [Benchmark] - public Size ImageSharp() - { - using var memoryStream = new MemoryStream(this.jpegBytes); - using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream); - return new Size(image.Width, image.Height); - } + [Benchmark(Baseline = true)] + public SDSize SystemDrawing() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; + } - /* - | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |------------------------------- |--------------------- |-----------:|------------:|-----------:|------:|--------:|------:|------:|------:|-----------:| - | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 5.122 ms | 1.3978 ms | 0.0766 ms | 1.00 | 0.00 | - | - | - | 176 B | - | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 11.991 ms | 0.2514 ms | 0.0138 ms | 2.34 | 0.03 | - | - | - | 15816 B | - | | | | | | | | | | | | - | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 14.943 ms | 1.8410 ms | 0.1009 ms | 1.00 | 0.00 | - | - | - | 176 B | - | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 29.759 ms | 1.5452 ms | 0.0847 ms | 1.99 | 0.01 | - | - | - | 16824 B | - | | | | | | | | | | | | - | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 388.229 ms | 382.8946 ms | 20.9877 ms | 1.00 | 0.00 | - | - | - | 216 B | - | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 276.490 ms | 195.5104 ms | 10.7166 ms | 0.71 | 0.01 | - | - | - | 36022368 B | - */ + [Benchmark] + public Size ImageSharp() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream); + return new Size(image.Width, image.Height); } + + /* + | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |------------------------------- |--------------------- |-----------:|------------:|-----------:|------:|--------:|------:|------:|------:|-----------:| + | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 5.122 ms | 1.3978 ms | 0.0766 ms | 1.00 | 0.00 | - | - | - | 176 B | + | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 11.991 ms | 0.2514 ms | 0.0138 ms | 2.34 | 0.03 | - | - | - | 15816 B | + | | | | | | | | | | | | + | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 14.943 ms | 1.8410 ms | 0.1009 ms | 1.00 | 0.00 | - | - | - | 176 B | + | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 29.759 ms | 1.5452 ms | 0.0847 ms | 1.99 | 0.01 | - | - | - | 16824 B | + | | | | | | | | | | | | + | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 388.229 ms | 382.8946 ms | 20.9877 ms | 1.00 | 0.00 | - | - | - | 216 B | + | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 276.490 ms | 195.5104 ms | 10.7166 ms | 0.71 | 0.01 | - | - | - | 36022368 B | + */ } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs index 4280996ffc..d762e8e95e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs @@ -1,95 +1,91 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Drawing.Imaging; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SkiaSharp; -using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Benchmark for performance comparison between other codecs. +/// +/// +/// This benchmarks tests baseline 4:2:0 chroma sampling path. +/// +public class EncodeJpegComparison { - /// - /// Benchmark for performance comparison between other codecs. - /// - /// - /// This benchmarks tests baseline 4:2:0 chroma sampling path. - /// - public class EncodeJpegComparison + // Big enough, 4:4:4 chroma sampling + private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; + + // Change/add parameters for extra benchmarks + [Params(75, 90, 100)] + public int Quality; + + private MemoryStream destinationStream; + + // ImageSharp + private Image imageImageSharp; + private JpegEncoder encoderImageSharp; + + // SkiaSharp + private SKBitmap imageSkiaSharp; + + [GlobalSetup(Target = nameof(BenchmarkImageSharp))] + public void SetupImageSharp() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + + this.imageImageSharp = Image.Load(imageBinaryStream); + this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 }; + + this.destinationStream = new MemoryStream(); + } + + [GlobalCleanup(Target = nameof(BenchmarkImageSharp))] + public void CleanupImageSharp() + { + this.imageImageSharp.Dispose(); + this.imageImageSharp = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + } + + [Benchmark(Description = "ImageSharp")] + public void BenchmarkImageSharp() + { + this.imageImageSharp.SaveAsJpeg(this.destinationStream, this.encoderImageSharp); + this.destinationStream.Seek(0, SeekOrigin.Begin); + } + + [GlobalSetup(Target = nameof(BenchmarkSkiaSharp))] + public void SetupSkiaSharp() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + + this.imageSkiaSharp = SKBitmap.Decode(imageBinaryStream); + + this.destinationStream = new MemoryStream(); + } + + [GlobalCleanup(Target = nameof(BenchmarkSkiaSharp))] + public void CleanupSkiaSharp() + { + this.imageSkiaSharp.Dispose(); + this.imageSkiaSharp = null; + + this.destinationStream.Dispose(); + this.destinationStream = null; + } + + [Benchmark(Description = "SkiaSharp")] + public void BenchmarkSkiaSharp() { - // Big enough, 4:4:4 chroma sampling - private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - - // Change/add parameters for extra benchmarks - [Params(75, 90, 100)] - public int Quality; - - private MemoryStream destinationStream; - - // ImageSharp - private Image imageImageSharp; - private JpegEncoder encoderImageSharp; - - // SkiaSharp - private SKBitmap imageSkiaSharp; - - [GlobalSetup(Target = nameof(BenchmarkImageSharp))] - public void SetupImageSharp() - { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - - this.imageImageSharp = Image.Load(imageBinaryStream); - this.encoderImageSharp = new JpegEncoder { Quality = this.Quality, ColorType = JpegEncodingColor.YCbCrRatio420 }; - - this.destinationStream = new MemoryStream(); - } - - [GlobalCleanup(Target = nameof(BenchmarkImageSharp))] - public void CleanupImageSharp() - { - this.imageImageSharp.Dispose(); - this.imageImageSharp = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - } - - [Benchmark(Description = "ImageSharp")] - public void BenchmarkImageSharp() - { - this.imageImageSharp.SaveAsJpeg(this.destinationStream, this.encoderImageSharp); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } - - [GlobalSetup(Target = nameof(BenchmarkSkiaSharp))] - public void SetupSkiaSharp() - { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - - this.imageSkiaSharp = SKBitmap.Decode(imageBinaryStream); - - this.destinationStream = new MemoryStream(); - } - - [GlobalCleanup(Target = nameof(BenchmarkSkiaSharp))] - public void CleanupSkiaSharp() - { - this.imageSkiaSharp.Dispose(); - this.imageSkiaSharp = null; - - this.destinationStream.Dispose(); - this.destinationStream = null; - } - - [Benchmark(Description = "SkiaSharp")] - public void BenchmarkSkiaSharp() - { - this.imageSkiaSharp.Encode(SKEncodedImageFormat.Jpeg, this.Quality).SaveTo(this.destinationStream); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } + this.imageSkiaSharp.Encode(SKEncodedImageFormat.Jpeg, this.Quality).SaveTo(this.destinationStream); + this.destinationStream.Seek(0, SeekOrigin.Begin); } } @@ -97,8 +93,8 @@ public void BenchmarkSkiaSharp() BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 - [Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT - DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +[Host] : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT +DefaultJob : .NET Core 3.1.21 (CoreCLR 4.700.21.51404, CoreFX 4.700.21.51508), X64 RyuJIT | Method | Quality | Mean | Error | StdDev | diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs index 231ba6c250..98eb0b54dc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs @@ -1,76 +1,73 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Benchmark for all available encoding features of the Jpeg file type. +/// +/// +/// This benchmark does NOT compare ImageSharp to any other jpeg codecs. +/// +public class EncodeJpegFeatures { - /// - /// Benchmark for all available encoding features of the Jpeg file type. - /// - /// - /// This benchmark does NOT compare ImageSharp to any other jpeg codecs. - /// - public class EncodeJpegFeatures - { - // Big enough, 4:4:4 chroma sampling - // No metadata - private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; + // Big enough, 4:4:4 chroma sampling + // No metadata + private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; - public static IEnumerable ColorSpaceValues => new[] - { - JpegEncodingColor.Luminance, - JpegEncodingColor.Rgb, - JpegEncodingColor.YCbCrRatio420, - JpegEncodingColor.YCbCrRatio444, - }; + public static IEnumerable ColorSpaceValues => new[] + { + JpegEncodingColor.Luminance, + JpegEncodingColor.Rgb, + JpegEncodingColor.YCbCrRatio420, + JpegEncodingColor.YCbCrRatio444, + }; - [Params(75, 90, 100)] - public int Quality; + [Params(75, 90, 100)] + public int Quality; - [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] - public JpegEncodingColor TargetColorSpace; + [ParamsSource(nameof(ColorSpaceValues), Priority = -100)] + public JpegEncodingColor TargetColorSpace; - private Image bmpCore; - private JpegEncoder encoder; + private Image bmpCore; + private JpegEncoder encoder; - private MemoryStream destinationStream; + private MemoryStream destinationStream; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); + this.bmpCore = Image.Load(imageBinaryStream); + this.encoder = new JpegEncoder { - using FileStream imageBinaryStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - this.bmpCore = Image.Load(imageBinaryStream); - this.encoder = new JpegEncoder - { - Quality = this.Quality, - ColorType = this.TargetColorSpace, - Interleaved = true, - }; - this.destinationStream = new MemoryStream(); - } + Quality = this.Quality, + ColorType = this.TargetColorSpace, + Interleaved = true, + }; + this.destinationStream = new MemoryStream(); + } - [GlobalCleanup] - public void Cleanup() - { - this.bmpCore.Dispose(); - this.bmpCore = null; + [GlobalCleanup] + public void Cleanup() + { + this.bmpCore.Dispose(); + this.bmpCore = null; - this.destinationStream.Dispose(); - this.destinationStream = null; - } + this.destinationStream.Dispose(); + this.destinationStream = null; + } - [Benchmark] - public void Benchmark() - { - this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder); - this.destinationStream.Seek(0, SeekOrigin.Begin); - } + [Benchmark] + public void Benchmark() + { + this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder); + this.destinationStream.Seek(0, SeekOrigin.Begin); } } @@ -78,8 +75,8 @@ public void Benchmark() BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.202 - [Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT - DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT +[Host] : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT +DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT | Method | TargetColorSpace | Quality | Mean | Error | StdDev | diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index a9cbb418a2..0b977bfbc8 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -1,39 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +[Config(typeof(Config.ShortMultiFramework))] +public class IdentifyJpeg { - [Config(typeof(Config.ShortMultiFramework))] - public class IdentifyJpeg - { - private byte[] jpegBytes; + private byte[] jpegBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)] - public string TestImage { get; set; } + [Params(TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Calliphora)] + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.jpegBytes == null) { - if (this.jpegBytes == null) - { - this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - } + this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); } + } - [Benchmark] - public IImageInfo Identify() - { - using var memoryStream = new MemoryStream(this.jpegBytes); - IImageDecoder decoder = new JpegDecoder(); - return decoder.Identify(DecoderOptions.Default, memoryStream, default); - } + [Benchmark] + public IImageInfo Identify() + { + using var memoryStream = new MemoryStream(this.jpegBytes); + IImageDecoder decoder = new JpegDecoder(); + return decoder.Identify(DecoderOptions.Default, memoryStream, default); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index 153287bb2f..b75d012f97 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -1,139 +1,185 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Drawing; -using System.IO; -using System.Linq; using System.Numerics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +public abstract class MultiImageBenchmarkBase { - public abstract class MultiImageBenchmarkBase - { - protected Dictionary FileNamesToBytes { get; set; } = new Dictionary(); + protected Dictionary FileNamesToBytes { get; set; } = new Dictionary(); - protected Dictionary> FileNamesToImageSharpImages { get; set; } = new Dictionary>(); + protected Dictionary> FileNamesToImageSharpImages { get; set; } = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); + /// + /// The values of this enum separate input files into categories. + /// + public enum InputImageCategory + { /// - /// The values of this enum separate input files into categories. + /// Use all images. /// - public enum InputImageCategory - { - /// - /// Use all images. - /// - AllImages, - - /// - /// Use small images only. - /// - SmallImagesOnly, - - /// - /// Use large images only. - /// - LargeImagesOnly - } - - [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] - public virtual InputImageCategory InputCategory { get; set; } - - protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; - - protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; + AllImages, /// - /// Gets the file names containing these strings are substrings are not processed by the benchmark. + /// Use small images only. /// - protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; + SmallImagesOnly, /// - /// Gets folders containing files OR files to be processed by the benchmark. + /// Use large images only. /// - protected IEnumerable AllFoldersOrFiles - => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); + LargeImagesOnly + } - /// - /// Gets the large image threshold. - /// The images sized above this threshold will be included in. - /// - protected virtual int LargeImageThresholdInBytes => 100000; + [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] + public virtual InputImageCategory InputCategory { get; set; } - protected IEnumerable> EnumeratePairsByBenchmarkSettings( - Dictionary input, - Predicate checkIfSmall) - => this.InputCategory switch - { - InputImageCategory.AllImages => input, - InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), - InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), - _ => throw new ArgumentOutOfRangeException(), - }; + protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; - protected IEnumerable> FileNames2Bytes - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToBytes, - arr => arr.Length < this.LargeImageThresholdInBytes); + protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; + + /// + /// Gets the file names containing these strings are substrings are not processed by the benchmark. + /// + protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; + + /// + /// Gets folders containing files OR files to be processed by the benchmark. + /// + protected IEnumerable AllFoldersOrFiles + => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); + + /// + /// Gets the large image threshold. + /// The images sized above this threshold will be included in. + /// + protected virtual int LargeImageThresholdInBytes => 100000; + + protected IEnumerable> EnumeratePairsByBenchmarkSettings( + Dictionary input, + Predicate checkIfSmall) + => this.InputCategory switch + { + InputImageCategory.AllImages => input, + InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), + InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), + _ => throw new ArgumentOutOfRangeException(), + }; + + protected IEnumerable> FileNames2Bytes + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToBytes, + arr => arr.Length < this.LargeImageThresholdInBytes); + + protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } + + [GlobalSetup] + public virtual void Setup() + { + if (!Vector.IsHardwareAccelerated) + { + throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); + } - protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } + // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); + this.ReadFilesImpl(); + } - [GlobalSetup] - public virtual void Setup() + protected virtual void ReadFilesImpl() + { + foreach (string path in this.AllFoldersOrFiles) { - if (!Vector.IsHardwareAccelerated) + if (File.Exists(path)) { - throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); + this.FileNamesToBytes[path] = File.ReadAllBytes(path); + continue; } - // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); - this.ReadFilesImpl(); + string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray(); + + string[] allFiles = + this.SearchPatterns.SelectMany( + f => + Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) + .Where(fn => !excludeStrings.Any(excludeStr => fn.ToLower().Contains(excludeStr)))).ToArray(); + + foreach (string fn in allFiles) + { + this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + } } + } - protected virtual void ReadFilesImpl() + /// + /// Execute code for each image stream. If the returned object of the operation is it will be disposed. + /// + /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed + protected void ForEachStream(Func operation) + { + foreach (KeyValuePair kv in this.FileNames2Bytes) { - foreach (string path in this.AllFoldersOrFiles) + using var memoryStream = new MemoryStream(kv.Value); + try { - if (File.Exists(path)) - { - this.FileNamesToBytes[path] = File.ReadAllBytes(path); - continue; - } + object obj = operation(memoryStream); + (obj as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); + } + } + } - string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray(); + public abstract class WithImagesPreloaded : MultiImageBenchmarkBase + { + protected override void ReadFilesImpl() + { + base.ReadFilesImpl(); - string[] allFiles = - this.SearchPatterns.SelectMany( - f => - Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) - .Where(fn => !excludeStrings.Any(excludeStr => fn.ToLower().Contains(excludeStr)))).ToArray(); + foreach (KeyValuePair kv in this.FileNamesToBytes) + { + byte[] bytes = kv.Value; + string fn = kv.Key; - foreach (string fn in allFiles) + using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); } + + this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); } } - /// - /// Execute code for each image stream. If the returned object of the operation is it will be disposed. - /// - /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed - protected void ForEachStream(Func operation) + protected IEnumerable>> FileNames2ImageSharpImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToImageSharpImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected IEnumerable> FileNames2SystemDrawingImages + => + this.EnumeratePairsByBenchmarkSettings( + this.FileNamesToSystemDrawingImages, + img => img.Width * img.Height < this.LargeImageThresholdInPixels); + + protected virtual int LargeImageThresholdInPixels => 700000; + + protected void ForEachImageSharpImage(Func, object> operation) { - foreach (KeyValuePair kv in this.FileNames2Bytes) + foreach (KeyValuePair> kv in this.FileNames2ImageSharpImages) { - using var memoryStream = new MemoryStream(kv.Value); try { - object obj = operation(memoryStream); + object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) @@ -143,101 +189,50 @@ protected void ForEachStream(Func operation) } } - public abstract class WithImagesPreloaded : MultiImageBenchmarkBase + protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) { - protected override void ReadFilesImpl() - { - base.ReadFilesImpl(); - - foreach (KeyValuePair kv in this.FileNamesToBytes) + using var workStream = new MemoryStream(); + this.ForEachImageSharpImage( + img => { - byte[] bytes = kv.Value; - string fn = kv.Key; - - using (var ms1 = new MemoryStream(bytes)) - { - this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); - } - - this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); - } - } - - protected IEnumerable>> FileNames2ImageSharpImages - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToImageSharpImages, - img => img.Width * img.Height < this.LargeImageThresholdInPixels); + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); - protected IEnumerable> FileNames2SystemDrawingImages - => - this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToSystemDrawingImages, - img => img.Width * img.Height < this.LargeImageThresholdInPixels); - - protected virtual int LargeImageThresholdInPixels => 700000; + // ReSharper restore AccessToDisposedClosure + return result; + }); + } - protected void ForEachImageSharpImage(Func, object> operation) + protected void ForEachSystemDrawingImage(Func operation) + { + foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { - foreach (KeyValuePair> kv in this.FileNames2ImageSharpImages) + try { - try - { - object obj = operation(kv.Value); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) - { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); - } + object obj = operation(kv.Value); + (obj as IDisposable)?.Dispose(); } - } - - protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) - { - 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; - }); - } - - protected void ForEachSystemDrawingImage(Func operation) - { - foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) + catch (Exception ex) { - try - { - object obj = operation(kv.Value); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) - { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); - } + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } + } - protected void ForEachSystemDrawingImage(Func operation) - { - 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; - }); - } + protected void ForEachSystemDrawingImage(Func operation) + { + 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/Codecs/Png/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs index 69b28b2138..74f5006668 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs -{ - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeFilteredPng - { - private byte[] filter0; - private byte[] filter1; - private byte[] filter2; - private byte[] filter3; - private byte[] averageFilter3bpp; - private byte[] averageFilter4bpp; +namespace SixLabors.ImageSharp.Benchmarks.Codecs; - [GlobalSetup] - public void ReadImages() - { - this.filter0 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter0)); - this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.SubFilter3BytesPerPixel)); - this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.UpFilter)); - this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.PaethFilter3BytesPerPixel)); - this.averageFilter3bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter3BytesPerPixel)); - this.averageFilter4bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter4BytesPerPixel)); - } +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeFilteredPng +{ + private byte[] filter0; + private byte[] filter1; + private byte[] filter2; + private byte[] filter3; + private byte[] averageFilter3bpp; + private byte[] averageFilter4bpp; - [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public Size PngFilter0() - => LoadPng(this.filter0); + [GlobalSetup] + public void ReadImages() + { + this.filter0 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.Filter0)); + this.filter1 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.SubFilter3BytesPerPixel)); + this.filter2 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.UpFilter)); + this.filter3 = File.ReadAllBytes(TestImageFullPath(TestImages.Png.PaethFilter3BytesPerPixel)); + this.averageFilter3bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter3BytesPerPixel)); + this.averageFilter4bpp = File.ReadAllBytes(TestImageFullPath(TestImages.Png.AverageFilter4BytesPerPixel)); + } - [Benchmark(Description = "Sub-filtered PNG file")] - public Size PngFilter1() - => LoadPng(this.filter1); + [Benchmark(Baseline = true, Description = "None-filtered PNG file")] + public Size PngFilter0() + => LoadPng(this.filter0); - [Benchmark(Description = "Up-filtered PNG file")] - public Size PngFilter2() - => LoadPng(this.filter2); + [Benchmark(Description = "Sub-filtered PNG file")] + public Size PngFilter1() + => LoadPng(this.filter1); - [Benchmark(Description = "Average-filtered PNG file (3bpp)")] - public Size PngAvgFilter1() - => LoadPng(this.averageFilter3bpp); + [Benchmark(Description = "Up-filtered PNG file")] + public Size PngFilter2() + => LoadPng(this.filter2); - [Benchmark(Description = "Average-filtered PNG file (4bpp)")] - public Size PngAvgFilter2() - => LoadPng(this.averageFilter4bpp); + [Benchmark(Description = "Average-filtered PNG file (3bpp)")] + public Size PngAvgFilter1() + => LoadPng(this.averageFilter3bpp); - [Benchmark(Description = "Paeth-filtered PNG file")] - public Size PngFilter4() - => LoadPng(this.filter3); + [Benchmark(Description = "Average-filtered PNG file (4bpp)")] + public Size PngAvgFilter2() + => LoadPng(this.averageFilter4bpp); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Size LoadPng(byte[] bytes) - { - using var image = Image.Load(bytes); - return image.Size(); - } + [Benchmark(Description = "Paeth-filtered PNG file")] + public Size PngFilter4() + => LoadPng(this.filter3); - private static string TestImageFullPath(string path) - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Size LoadPng(byte[] bytes) + { + using var image = Image.Load(bytes); + return image.Size(); } + + private static string TestImageFullPath(string path) + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs index 4d41133ef2..a73d62859c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class DecodePng { - [Config(typeof(Config.ShortMultiFramework))] - public class DecodePng - { - private byte[] pngBytes; + private byte[] pngBytes; - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Png.Splash)] - public string TestImage { get; set; } + [Params(TestImages.Png.Splash)] + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.pngBytes == null) { - if (this.pngBytes == null) - { - this.pngBytes = File.ReadAllBytes(this.TestImageFullPath); - } + this.pngBytes = File.ReadAllBytes(this.TestImageFullPath); } + } - [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public SDSize PngSystemDrawing() - { - using var memoryStream = new MemoryStream(this.pngBytes); - using var image = SDImage.FromStream(memoryStream); - return image.Size; - } + [Benchmark(Baseline = true, Description = "System.Drawing Png")] + public SDSize PngSystemDrawing() + { + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; + } - [Benchmark(Description = "ImageSharp Png")] - public Size PngImageSharp() - { - using var memoryStream = new MemoryStream(this.pngBytes); - using var image = Image.Load(memoryStream); - return image.Size(); - } + [Benchmark(Description = "ImageSharp Png")] + public Size PngImageSharp() + { + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = Image.Load(memoryStream); + return image.Size(); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs index 66db135730..26fb0f4a41 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -9,84 +8,83 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +/// +/// Benchmarks saving png files using different quantizers. +/// System.Drawing cannot save indexed png files so we cannot compare. +/// +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeIndexedPng { - /// - /// Benchmarks saving png files using different quantizers. - /// System.Drawing cannot save indexed png files so we cannot compare. - /// - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeIndexedPng - { - // System.Drawing needs this. - private Stream bmpStream; - private Image bmpCore; + // System.Drawing needs this. + private Stream bmpStream; + private Image bmpCore; - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) { - if (this.bmpStream == null) - { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - } + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; } + } - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpStream = null; + this.bmpCore.Dispose(); + } - [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] - public void PngCoreOctree() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] + public void PngCoreOctree() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; + this.bmpCore.SaveAsPng(memoryStream, options); + } - [Benchmark(Description = "ImageSharp Octree NoDither Png")] - public void PngCoreOctreeNoDither() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Description = "ImageSharp Octree NoDither Png")] + public void PngCoreOctreeNoDither() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); + } - [Benchmark(Description = "ImageSharp Palette Png")] - public void PngCorePalette() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Description = "ImageSharp Palette Png")] + public void PngCorePalette() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; + this.bmpCore.SaveAsPng(memoryStream, options); + } - [Benchmark(Description = "ImageSharp Palette NoDither Png")] - public void PngCorePaletteNoDither() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Description = "ImageSharp Palette NoDither Png")] + public void PngCorePaletteNoDither() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); + } - [Benchmark(Description = "ImageSharp Wu Png")] - public void PngCoreWu() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Description = "ImageSharp Wu Png")] + public void PngCoreWu() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; + this.bmpCore.SaveAsPng(memoryStream, options); + } - [Benchmark(Description = "ImageSharp Wu NoDither Png")] - public void PngCoreWuNoDither() - { - using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + [Benchmark(Description = "ImageSharp Wu NoDither Png")] + public void PngCoreWuNoDither() + { + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; + this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs index 08056a7ade..5cbe88fee6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs @@ -2,61 +2,59 @@ // Licensed under the Six Labors Split License. using System.Drawing.Imaging; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodePng { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodePng - { - // System.Drawing needs this. - private Stream bmpStream; - private SDImage bmpDrawing; - private Image bmpCore; + // System.Drawing needs this. + private Stream bmpStream; + private SDImage bmpDrawing; + private Image bmpCore; - [Params(false)] - public bool LargeImage { get; set; } + [Params(false)] + public bool LargeImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.bmpStream == null) { - if (this.bmpStream == null) - { - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.LargeImage ? TestImages.Jpeg.Baseline.Jpeg420Exif : TestImages.Bmp.Car); - this.bmpStream = File.OpenRead(path); - this.bmpCore = Image.Load(this.bmpStream); - this.bmpStream.Position = 0; - this.bmpDrawing = SDImage.FromStream(this.bmpStream); - } + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.LargeImage ? TestImages.Jpeg.Baseline.Jpeg420Exif : TestImages.Bmp.Car); + this.bmpStream = File.OpenRead(path); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + this.bmpDrawing = SDImage.FromStream(this.bmpStream); } + } - [GlobalCleanup] - public void Cleanup() - { - this.bmpStream.Dispose(); - this.bmpStream = null; - this.bmpCore.Dispose(); - this.bmpDrawing.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpStream = null; + this.bmpCore.Dispose(); + this.bmpDrawing.Dispose(); + } - [Benchmark(Baseline = true, Description = "System.Drawing Png")] - public void PngSystemDrawing() - { - using var memoryStream = new MemoryStream(); - this.bmpDrawing.Save(memoryStream, ImageFormat.Png); - } + [Benchmark(Baseline = true, Description = "System.Drawing Png")] + public void PngSystemDrawing() + { + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Png); + } - [Benchmark(Description = "ImageSharp Png")] - public void PngCore() - { - using var memoryStream = new MemoryStream(); - var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; - this.bmpCore.SaveAsPng(memoryStream, encoder); - } + [Benchmark(Description = "ImageSharp Png")] + public void PngCore() + { + using var memoryStream = new MemoryStream(); + var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; + this.bmpCore.SaveAsPng(memoryStream, encoder); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs index 9e60fecacc..363ecf908b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs @@ -2,80 +2,77 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.IO; -using System.Threading; using BenchmarkDotNet.Attributes; using ImageMagick; using Pfim; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs -{ - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeTga - { - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); +namespace SixLabors.ImageSharp.Benchmarks.Codecs; - private readonly PfimConfig pfimConfig = new(allocator: new PfimAllocator()); +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeTga +{ + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - private byte[] data; + private readonly PfimConfig pfimConfig = new(allocator: new PfimAllocator()); - [Params(TestImages.Tga.Bit24BottomLeft)] - public string TestImage { get; set; } + private byte[] data; - [GlobalSetup] - public void SetupData() - => this.data = File.ReadAllBytes(this.TestImageFullPath); + [Params(TestImages.Tga.Bit24BottomLeft)] + public string TestImage { get; set; } - [Benchmark(Baseline = true, Description = "ImageMagick Tga")] - public int TgaImageMagick() - { - var settings = new MagickReadSettings { Format = MagickFormat.Tga }; - using var image = new MagickImage(new MemoryStream(this.data), settings); - return image.Width; - } + [GlobalSetup] + public void SetupData() + => this.data = File.ReadAllBytes(this.TestImageFullPath); - [Benchmark(Description = "ImageSharp Tga")] - public int TgaImageSharp() - { - using var image = Image.Load(this.data); - return image.Width; - } + [Benchmark(Baseline = true, Description = "ImageMagick Tga")] + public int TgaImageMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.Tga }; + using var image = new MagickImage(new MemoryStream(this.data), settings); + return image.Width; + } - [Benchmark(Description = "Pfim Tga")] - public int TgaPfim() - { - using var image = Targa.Create(this.data, this.pfimConfig); - return image.Width; - } + [Benchmark(Description = "ImageSharp Tga")] + public int TgaImageSharp() + { + using var image = Image.Load(this.data); + return image.Width; + } - private class PfimAllocator : IImageAllocator - { - private int rented; - private readonly ArrayPool shared = ArrayPool.Shared; + [Benchmark(Description = "Pfim Tga")] + public int TgaPfim() + { + using var image = Targa.Create(this.data, this.pfimConfig); + return image.Width; + } - public byte[] Rent(int size) => this.shared.Rent(size); + private class PfimAllocator : IImageAllocator + { + private int rented; + private readonly ArrayPool shared = ArrayPool.Shared; - public void Return(byte[] data) - { - Interlocked.Decrement(ref this.rented); - this.shared.Return(data); - } + public byte[] Rent(int size) => this.shared.Rent(size); - public int Rented => this.rented; + public void Return(byte[] data) + { + Interlocked.Decrement(ref this.rented); + this.shared.Return(data); } - /* 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 | - */ + 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 | + */ } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs index 64c15c8fec..935706b41b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs @@ -1,56 +1,54 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeTga { - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeTga - { - private MagickImage tgaMagick; - private Image tga; + private MagickImage tgaMagick; + private Image tga; - private string TestImageFullPath - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Tga.Bit24BottomLeft)] - public string TestImage { get; set; } + [Params(TestImages.Tga.Bit24BottomLeft)] + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.tga == null) { - if (this.tga == null) - { - this.tga = Image.Load(this.TestImageFullPath); - this.tgaMagick = new MagickImage(this.TestImageFullPath); - } + this.tga = Image.Load(this.TestImageFullPath); + this.tgaMagick = new MagickImage(this.TestImageFullPath); } + } - [GlobalCleanup] - public void Cleanup() - { - this.tga.Dispose(); - this.tga = null; - this.tgaMagick.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.tga.Dispose(); + this.tga = null; + this.tgaMagick.Dispose(); + } - [Benchmark(Baseline = true, Description = "Magick Tga")] - public void MagickTga() - { - using var memoryStream = new MemoryStream(); - this.tgaMagick.Write(memoryStream, MagickFormat.Tga); - } + [Benchmark(Baseline = true, Description = "Magick Tga")] + public void MagickTga() + { + using var memoryStream = new MemoryStream(); + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); + } - [Benchmark(Description = "ImageSharp Tga")] - public void ImageSharpTga() - { - using var memoryStream = new MemoryStream(); - this.tga.SaveAsTga(memoryStream); - } + [Benchmark(Description = "ImageSharp Tga")] + public void ImageSharpTga() + { + using var memoryStream = new MemoryStream(); + this.tga.SaveAsTga(memoryStream); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs index a0752fa1f7..4edb251fa7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs @@ -5,86 +5,84 @@ // Use the scripts gen_big.ps1 and gen_medium.ps1 in tests\Images\Input\Tiff\Benchmarks to generate those images. //// #define BIG_TESTS -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeTiff { - [MarkdownExporter] - [HtmlExporter] - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeTiff - { - private string prevImage; + private string prevImage; - private byte[] data; + private byte[] data; #if BIG_TESTS - private static readonly int BufferSize = 1024 * 68; + private static readonly int BufferSize = 1024 * 68; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage)); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage)); - [Params( - TestImages.Tiff.Benchmark_BwFax3, - //// TestImages.Tiff.Benchmark_RgbFax4, // fax4 is not supported yet. - TestImages.Tiff.Benchmark_GrayscaleUncompressed, - TestImages.Tiff.Benchmark_PaletteUncompressed, - TestImages.Tiff.Benchmark_RgbDeflate, - TestImages.Tiff.Benchmark_RgbLzw, - TestImages.Tiff.Benchmark_RgbPackbits, - TestImages.Tiff.Benchmark_RgbUncompressed)] - public string TestImage { get; set; } + [Params( + TestImages.Tiff.Benchmark_BwFax3, + //// TestImages.Tiff.Benchmark_RgbFax4, // fax4 is not supported yet. + TestImages.Tiff.Benchmark_GrayscaleUncompressed, + TestImages.Tiff.Benchmark_PaletteUncompressed, + TestImages.Tiff.Benchmark_RgbDeflate, + TestImages.Tiff.Benchmark_RgbLzw, + TestImages.Tiff.Benchmark_RgbPackbits, + TestImages.Tiff.Benchmark_RgbUncompressed)] + public string TestImage { get; set; } #else - private static readonly int BufferSize = Configuration.Default.StreamProcessingBufferSize; + private static readonly int BufferSize = Configuration.Default.StreamProcessingBufferSize; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params( - TestImages.Tiff.CcittFax3AllTermCodes, - TestImages.Tiff.Fax4Compressed2, - TestImages.Tiff.HuffmanRleAllMakeupCodes, - TestImages.Tiff.Calliphora_GrayscaleUncompressed, - TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, - TestImages.Tiff.Calliphora_RgbDeflate_Predictor, - TestImages.Tiff.Calliphora_RgbLzwPredictor, - TestImages.Tiff.Calliphora_RgbPackbits, - TestImages.Tiff.Calliphora_RgbUncompressed)] - public string TestImage { get; set; } + [Params( + TestImages.Tiff.CcittFax3AllTermCodes, + TestImages.Tiff.Fax4Compressed2, + TestImages.Tiff.HuffmanRleAllMakeupCodes, + TestImages.Tiff.Calliphora_GrayscaleUncompressed, + TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor, + TestImages.Tiff.Calliphora_RgbDeflate_Predictor, + TestImages.Tiff.Calliphora_RgbLzwPredictor, + TestImages.Tiff.Calliphora_RgbPackbits, + TestImages.Tiff.Calliphora_RgbUncompressed)] + public string TestImage { get; set; } #endif - [IterationSetup] - public void ReadImages() + [IterationSetup] + public void ReadImages() + { + if (this.prevImage != this.TestImage) { - if (this.prevImage != this.TestImage) - { - this.data = File.ReadAllBytes(this.TestImageFullPath); - this.prevImage = this.TestImage; - } + this.data = File.ReadAllBytes(this.TestImageFullPath); + this.prevImage = this.TestImage; } + } - [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] - public SDSize TiffSystemDrawing() + [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] + public SDSize TiffSystemDrawing() + { + using (var memoryStream = new MemoryStream(this.data)) + using (var image = SDImage.FromStream(memoryStream)) { - using (var memoryStream = new MemoryStream(this.data)) - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } + return image.Size; } + } - [Benchmark(Description = "ImageSharp Tiff")] - public Size TiffCore() + [Benchmark(Description = "ImageSharp Tiff")] + public Size TiffCore() + { + using (var ms = new MemoryStream(this.data)) + using (var image = Image.Load(ms)) { - using (var ms = new MemoryStream(this.data)) - using (var image = Image.Load(ms)) - { - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs index eea23a7aa6..ab3b0e95ea 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Drawing.Imaging; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Tiff; @@ -12,122 +10,121 @@ using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeTiff { - [MarkdownExporter] - [HtmlExporter] - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeTiff - { - private Stream stream; - private SDImage drawing; - private Image core; + private Stream stream; + private SDImage drawing; + private Image core; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Tiff.Calliphora_RgbUncompressed)] - public string TestImage { get; set; } + [Params(TestImages.Tiff.Calliphora_RgbUncompressed)] + public string TestImage { get; set; } - [Params( - TiffCompression.None, + [Params( + TiffCompression.None, - // System.Drawing does not support Deflate or PackBits - // TiffCompression.Deflate, - // TiffCompression.PackBits, - TiffCompression.Lzw, - TiffCompression.CcittGroup3Fax, - TiffCompression.Ccitt1D)] - public TiffCompression Compression { get; set; } + // System.Drawing does not support Deflate or PackBits + // TiffCompression.Deflate, + // TiffCompression.PackBits, + TiffCompression.Lzw, + TiffCompression.CcittGroup3Fax, + TiffCompression.Ccitt1D)] + public TiffCompression Compression { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.stream == null) { - if (this.stream == null) - { - this.stream = File.OpenRead(this.TestImageFullPath); - this.core = Image.Load(this.stream); - this.stream.Position = 0; - this.drawing = SDImage.FromStream(this.stream); - } + this.stream = File.OpenRead(this.TestImageFullPath); + this.core = Image.Load(this.stream); + this.stream.Position = 0; + this.drawing = SDImage.FromStream(this.stream); } + } - [GlobalCleanup] - public void Cleanup() - { - this.core.Dispose(); - this.drawing.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.core.Dispose(); + this.drawing.Dispose(); + } - [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] - public void SystemDrawing() + [Benchmark(Baseline = true, Description = "System.Drawing Tiff")] + public void SystemDrawing() + { + ImageCodecInfo codec = FindCodecForType("image/tiff"); + using var parameters = new EncoderParameters(1) { - ImageCodecInfo codec = FindCodecForType("image/tiff"); - using var parameters = new EncoderParameters(1) - { - Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } - }; + Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } + }; - using var memoryStream = new MemoryStream(); - this.drawing.Save(memoryStream, codec, parameters); - } + using var memoryStream = new MemoryStream(); + this.drawing.Save(memoryStream, codec, parameters); + } - [Benchmark(Description = "ImageSharp Tiff")] - public void TiffCore() - { - TiffPhotometricInterpretation photometricInterpretation = - IsOneBitCompression(this.Compression) ? - TiffPhotometricInterpretation.WhiteIsZero : - TiffPhotometricInterpretation.Rgb; - - var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; - using var memoryStream = new MemoryStream(); - this.core.SaveAsTiff(memoryStream, encoder); - } + [Benchmark(Description = "ImageSharp Tiff")] + public void TiffCore() + { + TiffPhotometricInterpretation photometricInterpretation = + IsOneBitCompression(this.Compression) ? + TiffPhotometricInterpretation.WhiteIsZero : + TiffPhotometricInterpretation.Rgb; + + var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; + using var memoryStream = new MemoryStream(); + this.core.SaveAsTiff(memoryStream, encoder); + } - private static ImageCodecInfo FindCodecForType(string mimeType) - { - ImageCodecInfo[] imgEncoders = ImageCodecInfo.GetImageEncoders(); + private static ImageCodecInfo FindCodecForType(string mimeType) + { + ImageCodecInfo[] imgEncoders = ImageCodecInfo.GetImageEncoders(); - for (int i = 0; i < imgEncoders.GetLength(0); i++) + for (int i = 0; i < imgEncoders.GetLength(0); i++) + { + if (imgEncoders[i].MimeType == mimeType) { - if (imgEncoders[i].MimeType == mimeType) - { - return imgEncoders[i]; - } + return imgEncoders[i]; } - - return null; } - private static EncoderValue Cast(TiffCompression compression) + return null; + } + + private static EncoderValue Cast(TiffCompression compression) + { + switch (compression) { - switch (compression) - { - case TiffCompression.None: - return EncoderValue.CompressionNone; + case TiffCompression.None: + return EncoderValue.CompressionNone; - case TiffCompression.CcittGroup3Fax: - return EncoderValue.CompressionCCITT3; + case TiffCompression.CcittGroup3Fax: + return EncoderValue.CompressionCCITT3; - case TiffCompression.Ccitt1D: - return EncoderValue.CompressionRle; + case TiffCompression.Ccitt1D: + return EncoderValue.CompressionRle; - case TiffCompression.Lzw: - return EncoderValue.CompressionLZW; + case TiffCompression.Lzw: + return EncoderValue.CompressionLZW; - default: - throw new NotSupportedException(compression.ToString()); - } + default: + throw new NotSupportedException(compression.ToString()); } + } - public static bool IsOneBitCompression(TiffCompression compression) + public static bool IsOneBitCompression(TiffCompression compression) + { + if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax) { - if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax) - { - return true; - } - - return false; + return true; } + + return false; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs index f49310b9c3..34a4ad5931 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; @@ -9,96 +8,95 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.ShortMultiFramework))] +public class DecodeWebp { - [MarkdownExporter] - [HtmlExporter] - [Config(typeof(Config.ShortMultiFramework))] - public class DecodeWebp + private Configuration configuration; + + private byte[] webpLossyBytes; + + private byte[] webpLosslessBytes; + + private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); + + private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); + + [Params(TestImages.Webp.Lossy.Earth)] + public string TestImageLossy { get; set; } + + [Params(TestImages.Webp.Lossless.Earth)] + public string TestImageLossless { get; set; } + + [GlobalSetup] + public void ReadImages() + { + this.configuration = Configuration.CreateDefaultInstance(); + new WebpConfigurationModule().Configure(this.configuration); + + this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); + this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); + } + + [Benchmark(Description = "Magick Lossy Webp")] + public int WebpLossyMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = new MagickImage(memoryStream, settings); + return image.Width; + } + + [Benchmark(Description = "ImageSharp Lossy Webp")] + public int WebpLossy() { - private Configuration configuration; - - private byte[] webpLossyBytes; - - private byte[] webpLosslessBytes; - - private string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossy); - - private string TestImageLosslessFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImageLossless); - - [Params(TestImages.Webp.Lossy.Earth)] - public string TestImageLossy { get; set; } - - [Params(TestImages.Webp.Lossless.Earth)] - public string TestImageLossless { get; set; } - - [GlobalSetup] - public void ReadImages() - { - this.configuration = Configuration.CreateDefaultInstance(); - new WebpConfigurationModule().Configure(this.configuration); - - this.webpLossyBytes ??= File.ReadAllBytes(this.TestImageLossyFullPath); - this.webpLosslessBytes ??= File.ReadAllBytes(this.TestImageLosslessFullPath); - } - - [Benchmark(Description = "Magick Lossy Webp")] - public int WebpLossyMagick() - { - var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = new MagickImage(memoryStream, settings); - return image.Width; - } - - [Benchmark(Description = "ImageSharp Lossy Webp")] - public int WebpLossy() - { - using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = Image.Load(memoryStream); - return image.Height; - } - - [Benchmark(Description = "Magick Lossless Webp")] - public int WebpLosslessMagick() - { - var settings = new MagickReadSettings { Format = MagickFormat.WebP }; - using var memoryStream = new MemoryStream(this.webpLossyBytes); - using var image = new MagickImage(memoryStream, settings); - return image.Width; - } - - [Benchmark(Description = "ImageSharp Lossless Webp")] - public int WebpLossless() - { - using var memoryStream = new MemoryStream(this.webpLosslessBytes); - using var image = Image.Load(memoryStream); - return image.Height; - } - - /* Results 04.11.2021 - * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) - Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores - .NET SDK=6.0.100-rc.2.21505.57 - [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT - Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT - - | Method | Job | Runtime | Arguments | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |--------------------- |---------------------- |---------------------- |------------------------- |-----------:|----------:|--------:|---------:|------:|------:|----------:| - | 'Magick Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 107.9 ms | 28.91 ms | 1.58 ms | - | - | - | 25 KB | - | 'ImageSharp Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 282.3 ms | 25.40 ms | 1.39 ms | 500.0000 | - | - | 2,428 KB | - | 'Magick Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.3 ms | 11.99 ms | 0.66 ms | - | - | - | 16 KB | - | 'ImageSharp Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 280.2 ms | 6.21 ms | 0.34 ms | - | - | - | 2,092 KB | - | 'Magick Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 9.32 ms | 0.51 ms | - | - | - | 15 KB | - | 'ImageSharp Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 295.8 ms | 21.25 ms | 1.16 ms | 500.0000 | - | - | 2,427 KB | - | 'Magick Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.5 ms | 4.07 ms | 0.22 ms | - | - | - | 15 KB | - | 'ImageSharp Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 464.0 ms | 55.70 ms | 3.05 ms | - | - | - | 2,090 KB | - | 'Magick Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 108.0 ms | 29.60 ms | 1.62 ms | - | - | - | 32 KB | - | 'ImageSharp Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 564.9 ms | 29.69 ms | 1.63 ms | - | - | - | 2,436 KB | - | 'Magick Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 4.74 ms | 0.26 ms | - | - | - | 18 KB | - | 'ImageSharp Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,767.5 ms | 106.33 ms | 5.83 ms | - | - | - | 9,729 KB | - */ + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = Image.Load(memoryStream); + return image.Height; } + + [Benchmark(Description = "Magick Lossless Webp")] + public int WebpLosslessMagick() + { + var settings = new MagickReadSettings { Format = MagickFormat.WebP }; + using var memoryStream = new MemoryStream(this.webpLossyBytes); + using var image = new MagickImage(memoryStream, settings); + return image.Width; + } + + [Benchmark(Description = "ImageSharp Lossless Webp")] + public int WebpLossless() + { + using var memoryStream = new MemoryStream(this.webpLosslessBytes); + using var image = Image.Load(memoryStream); + return image.Height; + } + + /* Results 04.11.2021 + * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) + Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores + .NET SDK=6.0.100-rc.2.21505.57 + [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT + Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT + Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT + Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT + + | Method | Job | Runtime | Arguments | TestImageLossy | TestImageLossless | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |--------------------- |---------------------- |---------------------- |------------------------- |-----------:|----------:|--------:|---------:|------:|------:|----------:| + | 'Magick Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 107.9 ms | 28.91 ms | 1.58 ms | - | - | - | 25 KB | + | 'ImageSharp Lossy Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 282.3 ms | 25.40 ms | 1.39 ms | 500.0000 | - | - | 2,428 KB | + | 'Magick Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.3 ms | 11.99 ms | 0.66 ms | - | - | - | 16 KB | + | 'ImageSharp Lossless Webp' | Job-HLWZLL | .NET 5.0 | /p:DebugType=portable | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 280.2 ms | 6.21 ms | 0.34 ms | - | - | - | 2,092 KB | + | 'Magick Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 9.32 ms | 0.51 ms | - | - | - | 15 KB | + | 'ImageSharp Lossy Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 295.8 ms | 21.25 ms | 1.16 ms | 500.0000 | - | - | 2,427 KB | + | 'Magick Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.5 ms | 4.07 ms | 0.22 ms | - | - | - | 15 KB | + | 'ImageSharp Lossless Webp' | Job-ALQPDS | .NET Core 3.1 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 464.0 ms | 55.70 ms | 3.05 ms | - | - | - | 2,090 KB | + | 'Magick Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 108.0 ms | 29.60 ms | 1.62 ms | - | - | - | 32 KB | + | 'ImageSharp Lossy Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 564.9 ms | 29.69 ms | 1.63 ms | - | - | - | 2,436 KB | + | 'Magick Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 106.2 ms | 4.74 ms | 0.26 ms | - | - | - | 18 KB | + | 'ImageSharp Lossless Webp' | Job-RYVVNN | .NET Framework 4.7.2 | Default | Webp/earth_lossy.webp | Webp/earth_lossless.webp | 1,767.5 ms | 106.33 ms | 5.83 ms | - | - | - | 9,729 KB | + */ } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs index ee36b1bd03..65b4ae2c31 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using ImageMagick; using ImageMagick.Formats; @@ -9,135 +8,134 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Codecs +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.ShortMultiFramework))] +public class EncodeWebp { - [MarkdownExporter] - [HtmlExporter] - [Config(typeof(Config.ShortMultiFramework))] - public class EncodeWebp - { - private MagickImage webpMagick; - private Image webp; + private MagickImage webpMagick; + private Image webp; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); - [Params(TestImages.Png.Bike)] // The bike image will have all 3 transforms as lossless webp. - public string TestImage { get; set; } + [Params(TestImages.Png.Bike)] // The bike image will have all 3 transforms as lossless webp. + public string TestImage { get; set; } - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.webp == null) { - if (this.webp == null) - { - this.webp = Image.Load(this.TestImageFullPath); - this.webpMagick = new MagickImage(this.TestImageFullPath); - } + this.webp = Image.Load(this.TestImageFullPath); + this.webpMagick = new MagickImage(this.TestImageFullPath); } + } - [GlobalCleanup] - public void Cleanup() - { - this.webp.Dispose(); - this.webpMagick.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.webp.Dispose(); + this.webpMagick.Dispose(); + } + + [Benchmark(Description = "Magick Webp Lossy")] + public void MagickWebpLossy() + { + using var memoryStream = new MemoryStream(); - [Benchmark(Description = "Magick Webp Lossy")] - public void MagickWebpLossy() + var defines = new WebPWriteDefines { - using var memoryStream = new MemoryStream(); - - var defines = new WebPWriteDefines - { - Lossless = false, - Method = 4, - AlphaCompression = WebPAlphaCompression.None, - FilterStrength = 60, - SnsStrength = 50, - Pass = 1, - - // 100 means off. - NearLossless = 100 - }; - - this.webpMagick.Quality = 75; - this.webpMagick.Write(memoryStream, defines); - } + Lossless = false, + Method = 4, + AlphaCompression = WebPAlphaCompression.None, + FilterStrength = 60, + SnsStrength = 50, + Pass = 1, + + // 100 means off. + NearLossless = 100 + }; + + this.webpMagick.Quality = 75; + this.webpMagick.Write(memoryStream, defines); + } - [Benchmark(Description = "ImageSharp Webp Lossy")] - public void ImageSharpWebpLossy() + [Benchmark(Description = "ImageSharp Webp Lossy")] + public void ImageSharpWebpLossy() + { + using var memoryStream = new MemoryStream(); + this.webp.Save(memoryStream, new WebpEncoder() { - using var memoryStream = new MemoryStream(); - this.webp.Save(memoryStream, new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - Method = WebpEncodingMethod.Level4, - UseAlphaCompression = false, - FilterStrength = 60, - SpatialNoiseShaping = 50, - EntropyPasses = 1 - }); - } + FileFormat = WebpFileFormatType.Lossy, + Method = WebpEncodingMethod.Level4, + UseAlphaCompression = false, + FilterStrength = 60, + SpatialNoiseShaping = 50, + EntropyPasses = 1 + }); + } - [Benchmark(Baseline = true, Description = "Magick Webp Lossless")] - public void MagickWebpLossless() + [Benchmark(Baseline = true, Description = "Magick Webp Lossless")] + public void MagickWebpLossless() + { + using var memoryStream = new MemoryStream(); + var defines = new WebPWriteDefines { - using var memoryStream = new MemoryStream(); - var defines = new WebPWriteDefines - { - Lossless = true, - Method = 4, - - // 100 means off. - NearLossless = 100 - }; - - this.webpMagick.Quality = 75; - this.webpMagick.Write(memoryStream, defines); - } + Lossless = true, + Method = 4, + + // 100 means off. + NearLossless = 100 + }; - [Benchmark(Description = "ImageSharp Webp Lossless")] - public void ImageSharpWebpLossless() + this.webpMagick.Quality = 75; + this.webpMagick.Write(memoryStream, defines); + } + + [Benchmark(Description = "ImageSharp Webp Lossless")] + public void ImageSharpWebpLossless() + { + using var memoryStream = new MemoryStream(); + this.webp.Save(memoryStream, new WebpEncoder() { - using var memoryStream = new MemoryStream(); - this.webp.Save(memoryStream, new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Method = WebpEncodingMethod.Level4, - NearLossless = false, - - // This is equal to exact = false in libwebp, which is the default. - TransparentColorMode = WebpTransparentColorMode.Clear - }); - } + FileFormat = WebpFileFormatType.Lossless, + Method = WebpEncodingMethod.Level4, + NearLossless = false, - /* Results 04.11.2021 - * Summary * - BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) - Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores - .NET SDK=6.0.100-rc.2.21505.57 - [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT - Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT - Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT - - IterationCount=3 LaunchCount=1 WarmupCount=3 - - | Method | Job | Runtime | Arguments | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |--------------------------- |----------- |--------------------- |---------------------- |------------- |----------:|----------:|---------:|------:|--------:|------------:|----------:|----------:|-----------:| - | 'Magick Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 23.33 ms | 1.491 ms | 0.082 ms | 0.15 | 0.00 | - | - | - | 67 KB | - | 'ImageSharp Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 245.80 ms | 24.288 ms | 1.331 ms | 1.53 | 0.01 | 135000.0000 | - | - | 552,713 KB | - | 'Magick Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 160.36 ms | 11.131 ms | 0.610 ms | 1.00 | 0.00 | - | - | - | 518 KB | - | 'ImageSharp Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 313.93 ms | 45.605 ms | 2.500 ms | 1.96 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,670 KB | - | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 23.36 ms | 2.289 ms | 0.125 ms | 0.15 | 0.00 | - | - | - | 67 KB | - | 'ImageSharp Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 254.64 ms | 19.620 ms | 1.075 ms | 1.59 | 0.00 | 135000.0000 | - | - | 552,713 KB | - | 'Magick Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 160.30 ms | 9.549 ms | 0.523 ms | 1.00 | 0.00 | - | - | - | 518 KB | - | 'ImageSharp Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 320.35 ms | 22.924 ms | 1.257 ms | 2.00 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,669 KB | - | | | | | | | | | | | | | | | - | 'Magick Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 23.37 ms | 0.908 ms | 0.050 ms | 0.15 | 0.00 | - | - | - | 68 KB | - | 'ImageSharp Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 378.67 ms | 25.540 ms | 1.400 ms | 2.36 | 0.01 | 135000.0000 | - | - | 554,351 KB | - | 'Magick Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 160.13 ms | 5.115 ms | 0.280 ms | 1.00 | 0.00 | - | - | - | 520 KB | - | 'ImageSharp Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 379.01 ms | 71.192 ms | 3.902 ms | 2.37 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 162,119 KB | - */ + // This is equal to exact = false in libwebp, which is the default. + TransparentColorMode = WebpTransparentColorMode.Clear + }); } + + /* Results 04.11.2021 + * Summary * + BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1320 (21H1/May2021Update) + Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores + .NET SDK=6.0.100-rc.2.21505.57 + [Host] : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT + Job-WQLXJO : .NET 5.0.11 (5.0.1121.47308), X64 RyuJIT + Job-OJJAMD : .NET Core 3.1.20 (CoreCLR 4.700.21.47003, CoreFX 4.700.21.47101), X64 RyuJIT + Job-OMFOAS : .NET Framework 4.8 (4.8.4420.0), X64 RyuJIT + + IterationCount=3 LaunchCount=1 WarmupCount=3 + + | Method | Job | Runtime | Arguments | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |--------------------------- |----------- |--------------------- |---------------------- |------------- |----------:|----------:|---------:|------:|--------:|------------:|----------:|----------:|-----------:| + | 'Magick Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 23.33 ms | 1.491 ms | 0.082 ms | 0.15 | 0.00 | - | - | - | 67 KB | + | 'ImageSharp Webp Lossy' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 245.80 ms | 24.288 ms | 1.331 ms | 1.53 | 0.01 | 135000.0000 | - | - | 552,713 KB | + | 'Magick Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 160.36 ms | 11.131 ms | 0.610 ms | 1.00 | 0.00 | - | - | - | 518 KB | + | 'ImageSharp Webp Lossless' | Job-WQLXJO | .NET 5.0 | /p:DebugType=portable | Png/Bike.png | 313.93 ms | 45.605 ms | 2.500 ms | 1.96 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,670 KB | + | | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 23.36 ms | 2.289 ms | 0.125 ms | 0.15 | 0.00 | - | - | - | 67 KB | + | 'ImageSharp Webp Lossy' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 254.64 ms | 19.620 ms | 1.075 ms | 1.59 | 0.00 | 135000.0000 | - | - | 552,713 KB | + | 'Magick Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 160.30 ms | 9.549 ms | 0.523 ms | 1.00 | 0.00 | - | - | - | 518 KB | + | 'ImageSharp Webp Lossless' | Job-OJJAMD | .NET Core 3.1 | Default | Png/Bike.png | 320.35 ms | 22.924 ms | 1.257 ms | 2.00 | 0.01 | 34000.0000 | 5000.0000 | 2000.0000 | 161,669 KB | + | | | | | | | | | | | | | | | + | 'Magick Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 23.37 ms | 0.908 ms | 0.050 ms | 0.15 | 0.00 | - | - | - | 68 KB | + | 'ImageSharp Webp Lossy' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 378.67 ms | 25.540 ms | 1.400 ms | 2.36 | 0.01 | 135000.0000 | - | - | 554,351 KB | + | 'Magick Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 160.13 ms | 5.115 ms | 0.280 ms | 1.00 | 0.00 | - | - | - | 520 KB | + | 'ImageSharp Webp Lossless' | Job-OMFOAS | .NET Framework 4.7.2 | Default | Png/Bike.png | 379.01 ms | 71.192 ms | 3.902 ms | 2.37 | 0.02 | 34000.0000 | 5000.0000 | 2000.0000 | 162,119 KB | + */ } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs index 8909b2c0d7..e27bf07e99 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using BenchmarkDotNet.Attributes; @@ -9,82 +8,81 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - public abstract class FromRgba32Bytes - where TPixel : unmanaged, IPixel - { - private IMemoryOwner destination; - - private IMemoryOwner source; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - private Configuration configuration; +public abstract class FromRgba32Bytes + where TPixel : unmanaged, IPixel +{ + private IMemoryOwner destination; - [Params( - 128, - 1024, - 2048)] - public int Count { get; set; } + private IMemoryOwner source; - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count); - this.source = this.configuration.MemoryAllocator.Allocate(this.Count * 4); - } + private Configuration configuration; - [GlobalCleanup] - public void Cleanup() - { - this.destination.Dispose(); - this.source.Dispose(); - } + [Params( + 128, + 1024, + 2048)] + public int Count { get; set; } - // [Benchmark] - public void Naive() - { - Span s = this.source.GetSpan(); - Span d = this.destination.GetSpan(); + [GlobalSetup] + public void Setup() + { + this.configuration = Configuration.Default; + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count); + this.source = this.configuration.MemoryAllocator.Allocate(this.Count * 4); + } - for (int i = 0; i < this.Count; i++) - { - int i4 = i * 4; - var c = default(TPixel); - c.FromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); - d[i] = c; - } - } + [GlobalCleanup] + public void Cleanup() + { + this.destination.Dispose(); + this.source.Dispose(); + } - [Benchmark(Baseline = true)] - public void CommonBulk() - { - new PixelOperations().FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); - } + // [Benchmark] + public void Naive() + { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); - [Benchmark] - public void OptimizedBulk() + for (int i = 0; i < this.Count; i++) { - PixelOperations.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); + int i4 = i * 4; + var c = default(TPixel); + c.FromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); + d[i] = c; } } - public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes + [Benchmark(Baseline = true)] + public void CommonBulk() { + new PixelOperations().FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); } - public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes + [Benchmark] + public void OptimizedBulk() { - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // -------------- |------ |-----------:|----------:|----------:|-------:| - // CommonBulk | 128 | 207.1 ns | 3.723 ns | 3.300 ns | 1.00 | - // OptimizedBulk | 128 | 166.5 ns | 1.204 ns | 1.005 ns | 0.80 | - // | | | | | | - // CommonBulk | 1024 | 1,333.9 ns | 12.426 ns | 11.624 ns | 1.00 | - // OptimizedBulk | 1024 | 974.1 ns | 18.803 ns | 16.669 ns | 0.73 | - // | | | | | | - // 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 | + PixelOperations.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); } } + +public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes +{ +} + +public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes +{ + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // -------------- |------ |-----------:|----------:|----------:|-------:| + // CommonBulk | 128 | 207.1 ns | 3.723 ns | 3.300 ns | 1.00 | + // OptimizedBulk | 128 | 166.5 ns | 1.204 ns | 1.005 ns | 0.80 | + // | | | | | | + // CommonBulk | 1024 | 1,333.9 ns | 12.426 ns | 11.624 ns | 1.00 | + // OptimizedBulk | 1024 | 974.1 ns | 18.803 ns | 16.669 ns | 0.73 | + // | | | | | | + // 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 | +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs index bb2985ab71..d1cb616780 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -13,156 +12,155 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - [Config(typeof(Config.ShortCore31))] - public abstract class FromVector4 - where TPixel : unmanaged, IPixel - { - protected IMemoryOwner source; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - protected IMemoryOwner destination; +[Config(typeof(Config.ShortCore31))] +public abstract class FromVector4 + where TPixel : unmanaged, IPixel +{ + protected IMemoryOwner source; - protected Configuration Configuration => Configuration.Default; + protected IMemoryOwner destination; - // [Params(64, 2048)] - [Params(64, 256, 2048)] - public int Count { get; set; } + protected Configuration Configuration => Configuration.Default; - [GlobalSetup] - public void Setup() - { - this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); - this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); - } + // [Params(64, 2048)] + [Params(64, 256, 2048)] + public int Count { get; set; } - [GlobalCleanup] - public void Cleanup() - { - this.destination.Dispose(); - this.source.Dispose(); - } + [GlobalSetup] + public void Setup() + { + this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); + this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); + } - // [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)); - } - } + [GlobalCleanup] + public void Cleanup() + { + this.destination.Dispose(); + this.source.Dispose(); + } - [Benchmark(Baseline = true)] - public void PixelOperations_Base() + // [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++) { - new PixelOperations().FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + Unsafe.Add(ref d, i).FromVector4(Unsafe.Add(ref s, i)); } + } - [Benchmark] - public void PixelOperations_Specialized() - { - PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); - } + [Benchmark(Baseline = true)] + public void PixelOperations_Base() + { + new PixelOperations().FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); } - public class FromVector4Rgba32 : FromVector4 + [Benchmark] + public void PixelOperations_Specialized() { - [Benchmark] - public void FallbackIntrinsics128() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.source.GetSpan(), this.destination.GetSpan()); + } +} - SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(sBytes, dFloats); - } +public class FromVector4Rgba32 : FromVector4 +{ + [Benchmark] + public void FallbackIntrinsics128() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - [Benchmark] - public void ExtendedIntrinsic() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(sBytes, dFloats); + } - SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); - } + [Benchmark] + public void ExtendedIntrinsic() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - [Benchmark] - public void UseHwIntrinsics() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); + } - SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); - } + [Benchmark] + public void UseHwIntrinsics() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - 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 }; + SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); + } - [Benchmark] - public void UseAvx2_Grouped() - { - Span src = MemoryMarshal.Cast(this.source.GetSpan()); - Span dest = MemoryMarshal.Cast(this.destination.GetSpan()); + 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 }; - int n = dest.Length / Vector.Count; + [Benchmark] + public void UseAvx2_Grouped() + { + Span src = MemoryMarshal.Cast(this.source.GetSpan()); + Span dest = MemoryMarshal.Cast(this.destination.GetSpan()); - ref Vector256 sourceBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(src)); - ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + int n = dest.Length / Vector.Count; - ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); - Vector256 mask = Unsafe.As>(ref maskBase); + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(src)); + 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); + var maxBytes = Vector256.Create(255f); - Vector256 f0 = s; - Vector256 f1 = Unsafe.Add(ref s, 1); - Vector256 f2 = Unsafe.Add(ref s, 2); - Vector256 f3 = Unsafe.Add(ref s, 3); + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); - f0 = Avx.Multiply(maxBytes, f0); - f1 = Avx.Multiply(maxBytes, f1); - f2 = Avx.Multiply(maxBytes, f2); - f3 = Avx.Multiply(maxBytes, f3); + 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 = Avx.ConvertToVector256Int32(f0); - Vector256 w1 = Avx.ConvertToVector256Int32(f1); - Vector256 w2 = Avx.ConvertToVector256Int32(f2); - Vector256 w3 = Avx.ConvertToVector256Int32(f3); + f0 = Avx.Multiply(maxBytes, f0); + f1 = Avx.Multiply(maxBytes, f1); + f2 = Avx.Multiply(maxBytes, f2); + f3 = Avx.Multiply(maxBytes, 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(); + Vector256 w0 = Avx.ConvertToVector256Int32(f0); + Vector256 w1 = Avx.ConvertToVector256Int32(f1); + Vector256 w2 = Avx.ConvertToVector256Int32(f2); + Vector256 w3 = Avx.ConvertToVector256Int32(f3); - Unsafe.Add(ref destBase, i) = b; - } - } + 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(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) - { - vf = Avx.Multiply(scale, vf); - return Avx.ConvertToVector256Int32(vf); + Unsafe.Add(ref destBase, i) = b; } + } - // *** 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 | - | - | - | - | + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(scale, vf); + return Avx.ConvertToVector256Int32(vf); } + + // *** 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 | - | - | - | - | } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs index abb355f326..2effbd1d59 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs @@ -4,12 +4,11 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.ShortMultiFramework))] +public class FromVector4_Rgb24 : FromVector4 { - [Config(typeof(Config.ShortMultiFramework))] - public class FromVector4_Rgb24 : FromVector4 - { - } } // 2020-11-02 diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs index cdbfe41863..df308cdd4b 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs @@ -1,87 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Pad3Shuffle4Channel - { - private static readonly DefaultPad3Shuffle4 Control = new DefaultPad3Shuffle4(1, 0, 3, 2); - private static readonly XYZWPad3Shuffle4 ControlFast = default; - private byte[] source; - private byte[] destination; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count * 4 / 3]; - } +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Pad3Shuffle4Channel +{ + private static readonly DefaultPad3Shuffle4 Control = new DefaultPad3Shuffle4(1, 0, 3, 2); + private static readonly XYZWPad3Shuffle4 ControlFast = default; + private byte[] source; + private byte[] destination; - [Params(96, 384, 768, 1536)] - public int Count { get; set; } + [GlobalSetup] + public void Setup() + { + this.source = new byte[this.Count]; + new Random(this.Count).NextBytes(this.source); + this.destination = new byte[this.Count * 4 / 3]; + } - [Benchmark] - public void Pad3Shuffle4() - { - SimdUtils.Pad3Shuffle4(this.source, this.destination, Control); - } + [Params(96, 384, 768, 1536)] + public int Count { get; set; } - [Benchmark] - public void Pad3Shuffle4FastFallback() - { - SimdUtils.Pad3Shuffle4(this.source, this.destination, ControlFast); - } + [Benchmark] + public void Pad3Shuffle4() + { + SimdUtils.Pad3Shuffle4(this.source, this.destination, Control); } - // 2020-10-30 - // ########## - // - // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.403 - // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // - // Runtime=.NET Core 3.1 - // - // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |------------------------- |------------------- |-------------------------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|------:|------:|------:|----------:| - // | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 2. AVX | Empty | 96 | 23.63 ns | 0.175 ns | 0.155 ns | 23.65 ns | 0.15 | 0.01 | - | - | - | - | - // | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 96 | 24.84 ns | 0.376 ns | 0.333 ns | 24.74 ns | 1.57 | 0.06 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 2. AVX | Empty | 384 | 41.41 ns | 0.859 ns | 1.204 ns | 41.33 ns | 0.16 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 384 | 40.74 ns | 0.624 ns | 0.584 ns | 40.72 ns | 0.55 | 0.01 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 2. AVX | Empty | 768 | 62.86 ns | 0.332 ns | 0.277 ns | 62.80 ns | 0.12 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 768 | 64.72 ns | 1.306 ns | 1.090 ns | 64.51 ns | 0.59 | 0.01 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 2. AVX | Empty | 1536 | 110.05 ns | 0.256 ns | 0.214 ns | 110.04 ns | 0.11 | 0.00 | - | - | - | - | - // | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - | - // | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - | + [Benchmark] + public void Pad3Shuffle4FastFallback() + { + SimdUtils.Pad3Shuffle4(this.source, this.destination, ControlFast); + } } + +// 2020-10-30 +// ########## +// +// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) +// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK=3.1.403 +// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// +// Runtime=.NET Core 3.1 +// +// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |------------------------- |------------------- |-------------------------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|------:|------:|------:|----------:| +// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. AVX | Empty | 96 | 23.63 ns | 0.175 ns | 0.155 ns | 23.65 ns | 0.15 | 0.01 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 96 | 24.84 ns | 0.376 ns | 0.333 ns | 24.74 ns | 1.57 | 0.06 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. AVX | Empty | 384 | 41.41 ns | 0.859 ns | 1.204 ns | 41.33 ns | 0.16 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 384 | 40.74 ns | 0.624 ns | 0.584 ns | 40.72 ns | 0.55 | 0.01 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. AVX | Empty | 768 | 62.86 ns | 0.332 ns | 0.277 ns | 62.80 ns | 0.12 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 768 | 64.72 ns | 1.306 ns | 1.090 ns | 64.51 ns | 0.59 | 0.01 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. AVX | Empty | 1536 | 110.05 ns | 0.256 ns | 0.214 ns | 110.04 ns | 0.11 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PremultiplyVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PremultiplyVector4.cs index 7be7e651cf..d30693c6c7 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PremultiplyVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PremultiplyVector4.cs @@ -1,68 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - [Config(typeof(Config.ShortCore31))] - public class PremultiplyVector4 - { - private static readonly Vector4[] Vectors = CreateVectors(); +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - [Benchmark(Baseline = true)] - public void PremultiplyBaseline() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); +[Config(typeof(Config.ShortCore31))] +public class PremultiplyVector4 +{ + private static readonly Vector4[] Vectors = CreateVectors(); - for (int i = 0; i < Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - Premultiply(ref v); - } - } + [Benchmark(Baseline = true)] + public void PremultiplyBaseline() + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - [Benchmark] - public void Premultiply() + for (int i = 0; i < Vectors.Length; i++) { - Numerics.Premultiply(Vectors); + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Premultiply(ref v); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void Premultiply(ref Vector4 source) - { - float w = source.W; - source *= w; - source.W = w; - } + [Benchmark] + public void Premultiply() + { + Numerics.Premultiply(Vectors); + } - private static Vector4[] CreateVectors() - { - var rnd = new Random(42); - return GenerateRandomVectorArray(rnd, 2048, 0, 1); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void Premultiply(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } - private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal) - { - var values = new Vector4[length]; + private static Vector4[] CreateVectors() + { + var rnd = new Random(42); + return GenerateRandomVectorArray(rnd, 2048, 0, 1); + } - for (int i = 0; i < length; i++) - { - ref Vector4 v = ref values[i]; - v.X = GetRandomFloat(rnd, minVal, maxVal); - v.Y = GetRandomFloat(rnd, minVal, maxVal); - v.Z = GetRandomFloat(rnd, minVal, maxVal); - v.W = GetRandomFloat(rnd, minVal, maxVal); - } + private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal) + { + var values = new Vector4[length]; - return values; + for (int i = 0; i < length; i++) + { + ref Vector4 v = ref values[i]; + v.X = GetRandomFloat(rnd, minVal, maxVal); + v.Y = GetRandomFloat(rnd, minVal, maxVal); + v.Z = GetRandomFloat(rnd, minVal, maxVal); + v.W = GetRandomFloat(rnd, minVal, maxVal); } - private static float GetRandomFloat(Random rnd, float minVal, float maxVal) - => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; + return values; } + + private static float GetRandomFloat(Random rnd, float minVal, float maxVal) + => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs index bc586e6eab..1795ac30bb 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs @@ -7,53 +7,52 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +public abstract class Rgb24Bytes + where TPixel : unmanaged, IPixel { - public abstract class Rgb24Bytes - where TPixel : unmanaged, IPixel + private IMemoryOwner source; + + private IMemoryOwner destination; + + private Configuration configuration; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() { - private IMemoryOwner source; - - private IMemoryOwner destination; - - private Configuration configuration; - - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.source = this.configuration.MemoryAllocator.Allocate(this.Count); - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 3); - } - - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } - - [Benchmark(Baseline = true)] - public void CommonBulk() => - new PixelOperations().ToRgb24Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); - - [Benchmark] - public void OptimizedBulk() => - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); + this.configuration = Configuration.Default; + this.source = this.configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 3); } - public class Rgb24Bytes_Rgba32 : Rgb24Bytes + [GlobalCleanup] + public void Cleanup() { + this.source.Dispose(); + this.destination.Dispose(); } + + [Benchmark(Baseline = true)] + public void CommonBulk() => + new PixelOperations().ToRgb24Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + + [Benchmark] + public void OptimizedBulk() => + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); +} + +public class Rgb24Bytes_Rgba32 : Rgb24Bytes +{ } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs index 72da55e52e..7f6f5901ff 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs @@ -1,64 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Shuffle3Channel { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Shuffle3Channel - { - private static readonly DefaultShuffle3 Control = new DefaultShuffle3(1, 0, 2); - private byte[] source; - private byte[] destination; + private static readonly DefaultShuffle3 Control = new DefaultShuffle3(1, 0, 2); + private byte[] source; + private byte[] destination; - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new byte[this.Count]; + new Random(this.Count).NextBytes(this.source); + this.destination = new byte[this.Count]; + } - [Params(96, 384, 768, 1536)] - public int Count { get; set; } + [Params(96, 384, 768, 1536)] + public int Count { get; set; } - [Benchmark] - public void Shuffle3() - { - SimdUtils.Shuffle3(this.source, this.destination, Control); - } + [Benchmark] + public void Shuffle3() + { + SimdUtils.Shuffle3(this.source, this.destination, Control); } - - // 2020-11-02 - // ########## - // - // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.403 - // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // - // Runtime=.NET Core 3.1 - // - // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| - // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - | - // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - | - // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - | - // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - | - // | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | } + +// 2020-11-02 +// ########## +// +// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) +// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK=3.1.403 +// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// +// Runtime=.NET Core 3.1 +// +// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| +// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - | +// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - | +// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - | +// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - | +// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs index 446e7d5dc0..0a0afb7050 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs @@ -1,95 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class Shuffle4Slice3Channel - { - private static readonly DefaultShuffle4Slice3 Control = new DefaultShuffle4Slice3(1, 0, 3, 2); - private static readonly XYZWShuffle4Slice3 ControlFast = default; - private byte[] source; - private byte[] destination; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[(int)(this.Count * (3 / 4F))]; - } +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class Shuffle4Slice3Channel +{ + private static readonly DefaultShuffle4Slice3 Control = new DefaultShuffle4Slice3(1, 0, 3, 2); + private static readonly XYZWShuffle4Slice3 ControlFast = default; + private byte[] source; + private byte[] destination; - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } + [GlobalSetup] + public void Setup() + { + this.source = new byte[this.Count]; + new Random(this.Count).NextBytes(this.source); + this.destination = new byte[(int)(this.Count * (3 / 4F))]; + } - [Benchmark] - public void Shuffle4Slice3() - { - SimdUtils.Shuffle4Slice3(this.source, this.destination, Control); - } + [Params(128, 256, 512, 1024, 2048)] + public int Count { get; set; } - [Benchmark] - public void Shuffle4Slice3FastFallback() - { - SimdUtils.Shuffle4Slice3(this.source, this.destination, ControlFast); - } + [Benchmark] + public void Shuffle4Slice3() + { + SimdUtils.Shuffle4Slice3(this.source, this.destination, Control); } - // 2020-10-29 - // ########## - // - // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.403 - // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // - // Runtime=.NET Core 3.1 - // - // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| - // | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 2. AVX | Empty | 128 | 27.15 ns | 0.556 ns | 0.762 ns | 27.34 ns | 0.41 | 0.03 | - | - | - | - | - // | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 128 | 26.15 ns | 0.113 ns | 0.106 ns | 26.16 ns | 1.01 | 0.02 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 2. AVX | Empty | 256 | 32.61 ns | 0.107 ns | 0.095 ns | 32.62 ns | 0.33 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 256 | 32.16 ns | 0.111 ns | 0.104 ns | 32.16 ns | 0.61 | 0.01 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 2. AVX | Empty | 512 | 51.03 ns | 0.535 ns | 0.501 ns | 51.18 ns | 0.24 | 0.01 | - | - | - | - | - // | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 512 | 50.33 ns | 0.382 ns | 0.339 ns | 50.41 ns | 0.42 | 0.01 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 2. AVX | Empty | 1024 | 77.13 ns | 1.355 ns | 2.264 ns | 76.19 ns | 0.19 | 0.01 | - | - | - | - | - // | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 1024 | 80.25 ns | 1.647 ns | 2.082 ns | 80.98 ns | 0.35 | 0.01 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 2. AVX | Empty | 2048 | 128.41 ns | 0.417 ns | 0.390 ns | 128.24 ns | 0.16 | 0.00 | - | - | - | - | - // | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | | - // | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - | - // | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - | + [Benchmark] + public void Shuffle4Slice3FastFallback() + { + SimdUtils.Shuffle4Slice3(this.source, this.destination, ControlFast); + } } + +// 2020-10-29 +// ########## +// +// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) +// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK=3.1.403 +// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// +// Runtime=.NET Core 3.1 +// +// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| +// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. AVX | Empty | 128 | 27.15 ns | 0.556 ns | 0.762 ns | 27.34 ns | 0.41 | 0.03 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 128 | 26.15 ns | 0.113 ns | 0.106 ns | 26.16 ns | 1.01 | 0.02 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. AVX | Empty | 256 | 32.61 ns | 0.107 ns | 0.095 ns | 32.62 ns | 0.33 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 256 | 32.16 ns | 0.111 ns | 0.104 ns | 32.16 ns | 0.61 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. AVX | Empty | 512 | 51.03 ns | 0.535 ns | 0.501 ns | 51.18 ns | 0.24 | 0.01 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 512 | 50.33 ns | 0.382 ns | 0.339 ns | 50.41 ns | 0.42 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. AVX | Empty | 1024 | 77.13 ns | 1.355 ns | 2.264 ns | 76.19 ns | 0.19 | 0.01 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 1024 | 80.25 ns | 1.647 ns | 2.082 ns | 80.98 ns | 0.35 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. AVX | Empty | 2048 | 128.41 ns | 0.417 ns | 0.390 ns | 128.24 ns | 0.16 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | | | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs index 427d3f318b..6323e1c55c 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs @@ -1,67 +1,65 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class ShuffleByte4Channel { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class ShuffleByte4Channel - { - private byte[] source; - private byte[] destination; + private byte[] source; + private byte[] destination; - [GlobalSetup] - public void Setup() - { - this.source = new byte[this.Count]; - new Random(this.Count).NextBytes(this.source); - this.destination = new byte[this.Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new byte[this.Count]; + new Random(this.Count).NextBytes(this.source); + this.destination = new byte[this.Count]; + } - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } + [Params(128, 256, 512, 1024, 2048)] + public int Count { get; set; } - [Benchmark] - public void Shuffle4Channel() - { - SimdUtils.Shuffle4(this.source, this.destination, default); - } + [Benchmark] + public void Shuffle4Channel() + { + SimdUtils.Shuffle4(this.source, this.destination, default); } - - // 2020-10-29 - // ########## - // - // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.403 - // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // - // Runtime=.NET Core 3.1 - // - // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - | } + +// 2020-10-29 +// ########## +// +// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) +// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK=3.1.403 +// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// +// Runtime=.NET Core 3.1 +// +// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - | +// | | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs index 5c5208c4b1..bdeb880828 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs @@ -1,68 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.HwIntrinsics_SSE_AVX))] +public class ShuffleFloat4Channel { - [Config(typeof(Config.HwIntrinsics_SSE_AVX))] - public class ShuffleFloat4Channel - { - private static readonly byte Control = default(WXYZShuffle4).Control; - private float[] source; - private float[] destination; + private static readonly byte Control = default(WXYZShuffle4).Control; + private float[] source; + private float[] destination; - [GlobalSetup] - public void Setup() - { - this.source = new Random(this.Count).GenerateRandomFloatArray(this.Count, 0, 256); - this.destination = new float[this.Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new Random(this.Count).GenerateRandomFloatArray(this.Count, 0, 256); + this.destination = new float[this.Count]; + } - [Params(128, 256, 512, 1024, 2048)] - public int Count { get; set; } + [Params(128, 256, 512, 1024, 2048)] + public int Count { get; set; } - [Benchmark] - public void Shuffle4Channel() - { - SimdUtils.Shuffle4(this.source, this.destination, Control); - } + [Benchmark] + public void Shuffle4Channel() + { + SimdUtils.Shuffle4(this.source, this.destination, Control); } - - // 2020-10-29 - // ########## - // - // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) - // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - // .NET Core SDK=3.1.403 - // [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT - // - // Runtime=.NET Core 3.1 - // - // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - | - // | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - | - // | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - | - // | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - | - // | | | | | | | | | | | | | - // | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - | - // | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - | - // | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - | } + +// 2020-10-29 +// ########## +// +// BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1) +// Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK=3.1.403 +// [Host] : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 1. No HwIntrinsics : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 2. AVX : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// 3. SSE : .NET Core 3.1.9 (CoreCLR 4.700.20.47201, CoreFX 4.700.20.47203), X64 RyuJIT +// +// Runtime=.NET Core 3.1 +// +// | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - | +// | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - | +// | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - | +// | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - | +// | | | | | | | | | | | | | +// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs index 9f17a22953..b6e0696735 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using BenchmarkDotNet.Attributes; @@ -9,80 +8,79 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - public abstract class ToRgba32Bytes - where TPixel : unmanaged, IPixel - { - private IMemoryOwner source; - - private IMemoryOwner destination; - - private Configuration configuration; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default; - this.source = this.configuration.MemoryAllocator.Allocate(this.Count); - this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 4); - } +public abstract class ToRgba32Bytes + where TPixel : unmanaged, IPixel +{ + private IMemoryOwner source; - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } + private IMemoryOwner destination; - // [Benchmark] - public void Naive() - { - Span s = this.source.GetSpan(); - Span d = this.destination.GetSpan(); - - for (int i = 0; i < this.Count; i++) - { - TPixel c = s[i]; - int i4 = i * 4; - Rgba32 rgba = default; - c.ToRgba32(ref rgba); - d[i4] = rgba.R; - d[i4 + 1] = rgba.G; - d[i4 + 2] = rgba.B; - d[i4 + 3] = rgba.A; - } - } + private Configuration configuration; - [Benchmark(Baseline = true)] - public void CommonBulk() => - new PixelOperations().ToRgba32Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); - - [Benchmark] - public void OptimizedBulk() => - PixelOperations.Instance.ToRgba32Bytes( - this.configuration, - this.source.GetSpan(), - this.destination.GetSpan(), - this.Count); - } + [Params(16, 128, 1024)] + public int Count { get; set; } - public class ToRgba32Bytes_FromRgba32 : ToRgba32Bytes + [GlobalSetup] + public void Setup() { + this.configuration = Configuration.Default; + this.source = this.configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.configuration.MemoryAllocator.Allocate(this.Count * 4); } - public class ToRgba32Bytes_FromArgb32 : ToRgba32Bytes + [GlobalCleanup] + public void Cleanup() { + this.source.Dispose(); + this.destination.Dispose(); } - public class ToRgba32Bytes_FromBgra32 : ToRgba32Bytes + // [Benchmark] + public void Naive() { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); + + for (int i = 0; i < this.Count; i++) + { + TPixel c = s[i]; + int i4 = i * 4; + Rgba32 rgba = default; + c.ToRgba32(ref rgba); + d[i4] = rgba.R; + d[i4 + 1] = rgba.G; + d[i4 + 2] = rgba.B; + d[i4 + 3] = rgba.A; + } } + + [Benchmark(Baseline = true)] + public void CommonBulk() => + new PixelOperations().ToRgba32Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); + + [Benchmark] + public void OptimizedBulk() => + PixelOperations.Instance.ToRgba32Bytes( + this.configuration, + this.source.GetSpan(), + this.destination.GetSpan(), + this.Count); +} + +public class ToRgba32Bytes_FromRgba32 : ToRgba32Bytes +{ +} + +public class ToRgba32Bytes_FromArgb32 : ToRgba32Bytes +{ +} + +public class ToRgba32Bytes_FromBgra32 : ToRgba32Bytes +{ } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index b73076ffa6..31dfd0be93 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using BenchmarkDotNet.Attributes; @@ -10,53 +9,52 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - public abstract class ToVector4 - where TPixel : unmanaged, IPixel - { - protected IMemoryOwner source; +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - protected IMemoryOwner destination; +public abstract class ToVector4 + where TPixel : unmanaged, IPixel +{ + protected IMemoryOwner source; - protected Configuration Configuration => Configuration.Default; + protected IMemoryOwner destination; - [Params(64, 256, 2048)] // 512, 1024 - public int Count { get; set; } + protected Configuration Configuration => Configuration.Default; - [GlobalSetup] - public void Setup() - { - this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); - this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); - } + [Params(64, 256, 2048)] // 512, 1024 + public int Count { get; set; } - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } + [GlobalSetup] + public void Setup() + { + this.source = this.Configuration.MemoryAllocator.Allocate(this.Count); + this.destination = this.Configuration.MemoryAllocator.Allocate(this.Count); + } - // [Benchmark] - public void Naive() - { - Span s = this.source.GetSpan(); - Span d = this.destination.GetSpan(); + [GlobalCleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } - for (int i = 0; i < this.Count; i++) - { - d[i] = s[i].ToVector4(); - } - } + // [Benchmark] + public void Naive() + { + Span s = this.source.GetSpan(); + Span d = this.destination.GetSpan(); - [Benchmark] - public void PixelOperations_Specialized() + for (int i = 0; i < this.Count; i++) { - PixelOperations.Instance.ToVector4( - this.Configuration, - this.source.GetSpan(), - this.destination.GetSpan()); + d[i] = s[i].ToVector4(); } } + + [Benchmark] + public void PixelOperations_Specialized() + { + PixelOperations.Instance.ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); + } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs index e3193ec1be..b2f12cf9d9 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -6,39 +6,38 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.ShortMultiFramework))] +public class ToVector4_Bgra32 : ToVector4 { - [Config(typeof(Config.ShortMultiFramework))] - public class ToVector4_Bgra32 : ToVector4 + [Benchmark(Baseline = true)] + public void PixelOperations_Base() { - [Benchmark(Baseline = true)] - public void PixelOperations_Base() - { - new PixelOperations().ToVector4( - this.Configuration, - this.source.GetSpan(), - this.destination.GetSpan()); - } - - // RESULTS: - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:| - // PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B | - // PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B | - // | | | | | | | | | | - // 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 | + new PixelOperations().ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); } + + // RESULTS: + // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + // ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:| + // PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B | + // PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B | + // PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B | + // PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B | + // PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B | + // | | | | | | | | | | + // PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B | + // PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B | + // | | | | | | | | | | + // 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 | } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs index 69e4b7779f..f22868335d 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs @@ -6,19 +6,18 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.ShortMultiFramework))] +public class ToVector4_Rgb24 : ToVector4 { - [Config(typeof(Config.ShortMultiFramework))] - public class ToVector4_Rgb24 : ToVector4 + [Benchmark(Baseline = true)] + public void PixelOperations_Base() { - [Benchmark(Baseline = true)] - public void PixelOperations_Base() - { - new PixelOperations().ToVector4( - this.Configuration, - this.source.GetSpan(), - this.destination.GetSpan()); - } + new PixelOperations().ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs index ec51e104e9..6c28605161 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,156 +9,155 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; + +[Config(typeof(Config.ShortCore31))] +public class ToVector4_Rgba32 : ToVector4 { - [Config(typeof(Config.ShortCore31))] - public class ToVector4_Rgba32 : ToVector4 + [Benchmark] + public void FallbackIntrinsics128() { - [Benchmark] - public void FallbackIntrinsics128() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(sBytes, dFloats); - } + SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(sBytes, dFloats); + } - [Benchmark] - public void PixelOperations_Base() - => new PixelOperations().ToVector4( - this.Configuration, - this.source.GetSpan(), - this.destination.GetSpan()); + [Benchmark] + public void PixelOperations_Base() + => new PixelOperations().ToVector4( + this.Configuration, + this.source.GetSpan(), + this.destination.GetSpan()); - [Benchmark] - public void ExtendedIntrinsics() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + [Benchmark] + public void ExtendedIntrinsics() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); - } + SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); + } - [Benchmark] - public void HwIntrinsics() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + [Benchmark] + public void HwIntrinsics() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.HwIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); - } + SimdUtils.HwIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); + } - // [Benchmark] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + // [Benchmark] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - int n = dFloats.Length / Vector.Count; + int n = dFloats.Length / Vector.Count; - ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); - ref Vector destBaseU = ref Unsafe.As, Vector>(ref destBase); + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); + ref Vector destBaseU = ref Unsafe.As, Vector>(ref destBase); - for (int i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sourceBase, i); + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sourceBase, i); - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); - ref Vector d = ref Unsafe.Add(ref destBaseU, i * 4); - d = w0; - Unsafe.Add(ref d, 1) = w1; - Unsafe.Add(ref d, 2) = w2; - Unsafe.Add(ref d, 3) = w3; - } + ref Vector d = ref Unsafe.Add(ref destBaseU, i * 4); + d = w0; + Unsafe.Add(ref d, 1) = w1; + Unsafe.Add(ref d, 2) = w2; + Unsafe.Add(ref d, 3) = w3; + } - n = dFloats.Length / Vector.Count; - var scale = new Vector(1f / 255f); + n = dFloats.Length / Vector.Count; + var scale = new Vector(1f / 255f); - for (int i = 0; i < n; i++) - { - ref Vector dRef = ref Unsafe.Add(ref destBase, i); + for (int i = 0; i < n; i++) + { + ref Vector dRef = ref Unsafe.Add(ref destBase, i); - var du = Vector.AsVectorInt32(dRef); - var v = Vector.ConvertToSingle(du); - v *= scale; + var du = Vector.AsVectorInt32(dRef); + var v = Vector.ConvertToSingle(du); + v *= scale; - dRef = v; - } + dRef = v; } + } - // [Benchmark] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() - { - Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); - Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - - int n = dFloats.Length / Vector.Count; - - ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); - ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); - var scale = new Vector(1f / 255f); - - for (int i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sourceBase, i); - - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); - - Vector f0 = ConvertToNormalizedSingle(w0, scale); - Vector f1 = ConvertToNormalizedSingle(w1, scale); - Vector f2 = ConvertToNormalizedSingle(w2, scale); - Vector f3 = ConvertToNormalizedSingle(w3, scale); - - ref Vector d = ref Unsafe.Add(ref destBase, i * 4); - d = f0; - Unsafe.Add(ref d, 1) = f1; - Unsafe.Add(ref d, 2) = f2; - Unsafe.Add(ref d, 3) = f3; - } - } + // [Benchmark] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + int n = dFloats.Length / Vector.Count; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference((ReadOnlySpan)sBytes)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dFloats)); + var scale = new Vector(1f / 255f); + + for (int i = 0; i < n; i++) { - var vi = Vector.AsVectorInt32(u); - var v = Vector.ConvertToSingle(vi); - v *= scale; - return v; + Vector b = Unsafe.Add(ref sourceBase, i); + + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); + + Vector f0 = ConvertToNormalizedSingle(w0, scale); + Vector f1 = ConvertToNormalizedSingle(w1, scale); + Vector f2 = ConvertToNormalizedSingle(w2, scale); + Vector f3 = ConvertToNormalizedSingle(w3, scale); + + ref Vector d = ref Unsafe.Add(ref destBase, i * 4); + d = f0; + Unsafe.Add(ref d, 1) = f1; + Unsafe.Add(ref d, 2) = f2; + Unsafe.Add(ref d, 3) = f3; } + } - /*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! - */ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) + { + 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! + */ } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/UnPremultiplyVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/UnPremultiplyVector4.cs index 0dcead7195..ea36a341fc 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/UnPremultiplyVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/UnPremultiplyVector4.cs @@ -1,68 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk -{ - [Config(typeof(Config.ShortCore31))] - public class UnPremultiplyVector4 - { - private static readonly Vector4[] Vectors = CreateVectors(); +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk; - [Benchmark(Baseline = true)] - public void UnPremultiplyBaseline() - { - ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); +[Config(typeof(Config.ShortCore31))] +public class UnPremultiplyVector4 +{ + private static readonly Vector4[] Vectors = CreateVectors(); - for (int i = 0; i < Vectors.Length; i++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - UnPremultiply(ref v); - } - } + [Benchmark(Baseline = true)] + public void UnPremultiplyBaseline() + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(Vectors); - [Benchmark] - public void UnPremultiply() + for (int i = 0; i < Vectors.Length; i++) { - Numerics.UnPremultiply(Vectors); + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + UnPremultiply(ref v); } + } - [MethodImpl(InliningOptions.ShortMethod)] - private static void UnPremultiply(ref Vector4 source) - { - float w = source.W; - source /= w; - source.W = w; - } + [Benchmark] + public void UnPremultiply() + { + Numerics.UnPremultiply(Vectors); + } - private static Vector4[] CreateVectors() - { - var rnd = new Random(42); - return GenerateRandomVectorArray(rnd, 2048, 0, 1); - } + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnPremultiply(ref Vector4 source) + { + float w = source.W; + source /= w; + source.W = w; + } - private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal) - { - var values = new Vector4[length]; + private static Vector4[] CreateVectors() + { + var rnd = new Random(42); + return GenerateRandomVectorArray(rnd, 2048, 0, 1); + } - for (int i = 0; i < length; i++) - { - ref Vector4 v = ref values[i]; - v.X = GetRandomFloat(rnd, minVal, maxVal); - v.Y = GetRandomFloat(rnd, minVal, maxVal); - v.Z = GetRandomFloat(rnd, minVal, maxVal); - v.W = GetRandomFloat(rnd, minVal, maxVal); - } + private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal) + { + var values = new Vector4[length]; - return values; + for (int i = 0; i < length; i++) + { + ref Vector4 v = ref values[i]; + v.X = GetRandomFloat(rnd, minVal, maxVal); + v.Y = GetRandomFloat(rnd, minVal, maxVal); + v.Z = GetRandomFloat(rnd, minVal, maxVal); + v.W = GetRandomFloat(rnd, minVal, maxVal); } - private static float GetRandomFloat(Random rnd, float minVal, float maxVal) - => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; + return values; } + + private static float GetRandomFloat(Random rnd, float minVal, float maxVal) + => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs index 54d6247ae3..1ff8013b28 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorEquality.cs @@ -5,22 +5,21 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks -{ - using SystemColor = System.Drawing.Color; +namespace SixLabors.ImageSharp.Benchmarks; + +using SystemColor = System.Drawing.Color; - public class ColorEquality +public class ColorEquality +{ + [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] + public bool SystemDrawingColorEqual() { - [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] - public bool SystemDrawingColorEqual() - { - return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); - } + return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); + } - [Benchmark(Description = "ImageSharp Color Equals")] - public bool ColorEqual() - { - return new Rgba32(128, 128, 128, 128).Equals(new Rgba32(128, 128, 128, 128)); - } + [Benchmark(Description = "ImageSharp Color Equals")] + public bool ColorEqual() + { + return new Rgba32(128, 128, 128, 128).Equals(new Rgba32(128, 128, 128, 128)); } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index d16315a466..da09a85232 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -9,28 +9,27 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion; using Illuminants = Colourful.Illuminants; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces; + +public class ColorspaceCieXyzToCieLabConvert { - public class ColorspaceCieXyzToCieLabConvert - { - private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.D50).ToLab(Illuminants.D50).Build(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.D50).ToLab(Illuminants.D50).Build(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() - { - return ColourfulConverter.Convert(XYZColor).L; - } + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.Convert(XYZColor).L; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() - { - return ColorSpaceConverter.ToCieLab(CieXyz).L; - } + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToCieLab(CieXyz).L; } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index 30a7dd9639..c3317c5d94 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -9,28 +9,27 @@ using SixLabors.ImageSharp.ColorSpaces.Conversion; using Illuminants = Colourful.Illuminants; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces; + +public class ColorspaceCieXyzToHunterLabConvert { - public class ColorspaceCieXyzToHunterLabConvert - { - private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.C).ToHunterLab(Illuminants.C).Build(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(Illuminants.C).ToHunterLab(Illuminants.C).Build(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() - { - return ColourfulConverter.Convert(XYZColor).L; - } + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.Convert(XYZColor).L; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() - { - return ColorSpaceConverter.ToHunterLab(CieXyz).L; - } + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToHunterLab(CieXyz).L; } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index c6a51b3ec5..ba213e5b0a 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -8,28 +8,27 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces; + +public class ColorspaceCieXyzToLmsConvert { - public class ColorspaceCieXyzToLmsConvert - { - private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ().ToLMS().Build(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ().ToLMS().Build(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() - { - return ColourfulConverter.Convert(XYZColor).L; - } + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.Convert(XYZColor).L; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() - { - return ColorSpaceConverter.ToLms(CieXyz).L; - } + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToLms(CieXyz).L; } } diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index c8d23e944f..d7a5deafa7 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -8,28 +8,27 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces; + +public class ColorspaceCieXyzToRgbConvert { - public class ColorspaceCieXyzToRgbConvert - { - private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); + private static readonly CieXyz CieXyz = new CieXyz(0.95047F, 1, 1.08883F); - private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); + private static readonly XYZColor XYZColor = new XYZColor(0.95047, 1, 1.08883); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(RGBWorkingSpaces.sRGB.WhitePoint).ToRGB(RGBWorkingSpaces.sRGB).Build(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromXYZ(RGBWorkingSpaces.sRGB.WhitePoint).ToRGB(RGBWorkingSpaces.sRGB).Build(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] - public double ColourfulConvert() - { - return ColourfulConverter.Convert(XYZColor).R; - } + [Benchmark(Baseline = true, Description = "Colourful Convert")] + public double ColourfulConvert() + { + return ColourfulConverter.Convert(XYZColor).R; + } - [Benchmark(Description = "ImageSharp Convert")] - public float ColorSpaceConvert() - { - return ColorSpaceConverter.ToRgb(CieXyz).R; - } + [Benchmark(Description = "ImageSharp Convert")] + public float ColorSpaceConvert() + { + return ColorSpaceConverter.ToRgb(CieXyz).R; } } diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 1d20d3bc98..48b4880d94 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -8,28 +8,27 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces +namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces; + +public class RgbWorkingSpaceAdapt { - public class RgbWorkingSpaceAdapt - { - private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); + private static readonly Rgb Rgb = new Rgb(0.206162F, 0.260277F, 0.746717F, RgbWorkingSpaces.WideGamutRgb); - private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717); + private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }); + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }); - private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.WideGamutRGB).ToRGB(RGBWorkingSpaces.sRGB).Build(); + private static readonly IColorConverter ColourfulConverter = new ConverterBuilder().FromRGB(RGBWorkingSpaces.WideGamutRGB).ToRGB(RGBWorkingSpaces.sRGB).Build(); - [Benchmark(Baseline = true, Description = "Colourful Adapt")] - public RGBColor ColourfulConvert() - { - return ColourfulConverter.Convert(RGBColor); - } + [Benchmark(Baseline = true, Description = "Colourful Adapt")] + public RGBColor ColourfulConvert() + { + return ColourfulConverter.Convert(RGBColor); + } - [Benchmark(Description = "ImageSharp Adapt")] - public Rgb ColorSpaceConvert() - { - return ColorSpaceConverter.Adapt(Rgb); - } + [Benchmark(Description = "ImageSharp Adapt")] + public Rgb ColorSpaceConvert() + { + return ColorSpaceConverter.Adapt(Rgb); } } diff --git a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs index eacfc599e3..14d848bcb7 100644 --- a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs +++ b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs @@ -4,48 +4,47 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +public class YcbCrToRgb { - public class YcbCrToRgb + [Benchmark(Baseline = true, Description = "Floating Point Conversion")] + public Vector3 YcbCrToRgba() { - [Benchmark(Baseline = true, Description = "Floating Point Conversion")] - public Vector3 YcbCrToRgba() - { - int y = 255; - int cb = 128; - int cr = 128; - - int ccb = cb - 128; - int ccr = cr - 128; - - byte r = (byte)Numerics.Clamp(y + (1.402F * ccr), 0, 255); - byte g = (byte)Numerics.Clamp(y - (0.34414F * ccb) - (0.71414F * ccr), 0, 255); - byte b = (byte)Numerics.Clamp(y + (1.772F * ccb), 0, 255); - - return new Vector3(r, g, b); - } - - [Benchmark(Description = "Scaled Integer Conversion")] - public Vector3 YcbCrToRgbaScaled() - { - int y = 255; - int cb = 128; - int cr = 128; - - int ccb = cb - 128; - int ccr = cr - 128; - - // Scale by 1024, add .5F and truncate value - int r0 = 1436 * ccr; // (1.402F * 1024) + .5F - int g0 = 352 * ccb; // (0.34414F * 1024) + .5F - int g1 = 731 * ccr; // (0.71414F * 1024) + .5F - int b0 = 1815 * ccb; // (1.772F * 1024) + .5F - - byte r = (byte)Numerics.Clamp(y + (r0 >> 10), 0, 255); - byte g = (byte)Numerics.Clamp(y - (g0 >> 10) - (g1 >> 10), 0, 255); - byte b = (byte)Numerics.Clamp(y + (b0 >> 10), 0, 255); - - return new Vector3(r, g, b); - } + int y = 255; + int cb = 128; + int cr = 128; + + int ccb = cb - 128; + int ccr = cr - 128; + + byte r = (byte)Numerics.Clamp(y + (1.402F * ccr), 0, 255); + byte g = (byte)Numerics.Clamp(y - (0.34414F * ccb) - (0.71414F * ccr), 0, 255); + byte b = (byte)Numerics.Clamp(y + (1.772F * ccb), 0, 255); + + return new Vector3(r, g, b); + } + + [Benchmark(Description = "Scaled Integer Conversion")] + public Vector3 YcbCrToRgbaScaled() + { + int y = 255; + int cb = 128; + int cr = 128; + + int ccb = cb - 128; + int ccr = cr - 128; + + // Scale by 1024, add .5F and truncate value + int r0 = 1436 * ccr; // (1.402F * 1024) + .5F + int g0 = 352 * ccb; // (0.34414F * 1024) + .5F + int g1 = 731 * ccr; // (0.71414F * 1024) + .5F + int b0 = 1815 * ccb; // (1.772F * 1024) + .5F + + byte r = (byte)Numerics.Clamp(y + (r0 >> 10), 0, 255); + byte g = (byte)Numerics.Clamp(y - (g0 >> 10) - (g1 >> 10), 0, 255); + byte b = (byte)Numerics.Clamp(y + (b0 >> 10), 0, 255); + + return new Vector3(r, g, b); } } diff --git a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs index 4de745005f..5a0c574f17 100644 --- a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs +++ b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs @@ -5,75 +5,74 @@ using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +public partial class Config { - public partial class Config - { - private const string On = "1"; - private const string Off = "0"; + private const string On = "1"; + private const string Off = "0"; - // See https://github.com/SixLabors/ImageSharp/pull/1229#discussion_r440477861 - // * EnableHWIntrinsic - // * EnableSSE - // * EnableSSE2 - // * EnableAES - // * EnablePCLMULQDQ - // * EnableSSE3 - // * EnableSSSE3 - // * EnableSSE41 - // * EnableSSE42 - // * EnablePOPCNT - // * EnableAVX - // * EnableFMA - // * EnableAVX2 - // * EnableBMI1 - // * EnableBMI2 - // * EnableLZCNT - // - // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things - // like `LZCNT`, `BMI1`, or `BMI2` - // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3` - private const string EnableAES = "COMPlus_EnableAES"; - private const string EnableAVX = "COMPlus_EnableAVX"; - private const string EnableAVX2 = "COMPlus_EnableAVX2"; - private const string EnableBMI1 = "COMPlus_EnableBMI1"; - private const string EnableBMI2 = "COMPlus_EnableBMI2"; - private const string EnableFMA = "COMPlus_EnableFMA"; - private const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic"; - private const string EnableLZCNT = "COMPlus_EnableLZCNT"; - private const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ"; - private const string EnablePOPCNT = "COMPlus_EnablePOPCNT"; - private const string EnableSSE = "COMPlus_EnableSSE"; - private const string EnableSSE2 = "COMPlus_EnableSSE2"; - private const string EnableSSE3 = "COMPlus_EnableSSE3"; - private const string EnableSSE3_4 = "COMPlus_EnableSSE3_4"; - private const string EnableSSE41 = "COMPlus_EnableSSE41"; - private const string EnableSSE42 = "COMPlus_EnableSSE42"; - private const string EnableSSSE3 = "COMPlus_EnableSSSE3"; - private const string FeatureSIMD = "COMPlus_FeatureSIMD"; + // See https://github.com/SixLabors/ImageSharp/pull/1229#discussion_r440477861 + // * EnableHWIntrinsic + // * EnableSSE + // * EnableSSE2 + // * EnableAES + // * EnablePCLMULQDQ + // * EnableSSE3 + // * EnableSSSE3 + // * EnableSSE41 + // * EnableSSE42 + // * EnablePOPCNT + // * EnableAVX + // * EnableFMA + // * EnableAVX2 + // * EnableBMI1 + // * EnableBMI2 + // * EnableLZCNT + // + // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things + // like `LZCNT`, `BMI1`, or `BMI2` + // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3` + private const string EnableAES = "COMPlus_EnableAES"; + private const string EnableAVX = "COMPlus_EnableAVX"; + private const string EnableAVX2 = "COMPlus_EnableAVX2"; + private const string EnableBMI1 = "COMPlus_EnableBMI1"; + private const string EnableBMI2 = "COMPlus_EnableBMI2"; + private const string EnableFMA = "COMPlus_EnableFMA"; + private const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic"; + private const string EnableLZCNT = "COMPlus_EnableLZCNT"; + private const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ"; + private const string EnablePOPCNT = "COMPlus_EnablePOPCNT"; + private const string EnableSSE = "COMPlus_EnableSSE"; + private const string EnableSSE2 = "COMPlus_EnableSSE2"; + private const string EnableSSE3 = "COMPlus_EnableSSE3"; + private const string EnableSSE3_4 = "COMPlus_EnableSSE3_4"; + private const string EnableSSE41 = "COMPlus_EnableSSE41"; + private const string EnableSSE42 = "COMPlus_EnableSSE42"; + private const string EnableSSSE3 = "COMPlus_EnableSSSE3"; + private const string FeatureSIMD = "COMPlus_FeatureSIMD"; - public class HwIntrinsics_SSE_AVX : Config + public class HwIntrinsics_SSE_AVX : Config + { + public HwIntrinsics_SSE_AVX() { - public HwIntrinsics_SSE_AVX() + this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core60) + .WithEnvironmentVariables( + new EnvironmentVariable(EnableHWIntrinsic, Off), + new EnvironmentVariable(FeatureSIMD, Off)) + .WithId("1. No HwIntrinsics").AsBaseline()); + + if (Sse.IsSupported) { this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core60) - .WithEnvironmentVariables( - new EnvironmentVariable(EnableHWIntrinsic, Off), - new EnvironmentVariable(FeatureSIMD, Off)) - .WithId("1. No HwIntrinsics").AsBaseline()); - - if (Sse.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core60) - .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off)) - .WithId("2. SSE")); - } + .WithEnvironmentVariables(new EnvironmentVariable(EnableAVX, Off)) + .WithId("2. SSE")); + } - if (Avx.IsSupported) - { - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core60) - .WithId("3. AVX")); - } + if (Avx.IsSupported) + { + this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core60) + .WithId("3. AVX")); } } } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index f05c0af0f4..d95f4a202c 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -11,44 +11,43 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Reports; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +public partial class Config : ManualConfig { - public partial class Config : ManualConfig + public Config() { - public Config() - { - this.AddDiagnoser(MemoryDiagnoser.Default); + this.AddDiagnoser(MemoryDiagnoser.Default); #if OS_WINDOWS - if (this.IsElevated) - { - this.AddDiagnoser(new NativeMemoryProfiler()); - } + if (this.IsElevated) + { + this.AddDiagnoser(new NativeMemoryProfiler()); + } #endif - this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50); - } + this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(50); + } - public class MultiFramework : Config - { - public MultiFramework() => this.AddJob( - Job.Default.WithRuntime(CoreRuntime.Core60).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); - } + public class MultiFramework : Config + { + public MultiFramework() => this.AddJob( + Job.Default.WithRuntime(CoreRuntime.Core60).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); + } - public class ShortMultiFramework : Config - { - public ShortMultiFramework() => this.AddJob( - Job.Default.WithRuntime(CoreRuntime.Core60).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); - } + public class ShortMultiFramework : Config + { + public ShortMultiFramework() => this.AddJob( + Job.Default.WithRuntime(CoreRuntime.Core60).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); + } - public class ShortCore31 : Config - { - public ShortCore31() - => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); - } + public class ShortCore31 : Config + { + public ShortCore31() + => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } #if OS_WINDOWS - private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); #endif - } } diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs index 3c58eac06e..1aac3cff50 100644 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs @@ -1,72 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Compression.Zlib; using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; -namespace SixLabors.ImageSharp.Benchmarks.General -{ - [Config(typeof(Config.ShortMultiFramework))] - public class Adler32Benchmark - { - private byte[] data; - private readonly SharpAdler32 adler = new SharpAdler32(); +namespace SixLabors.ImageSharp.Benchmarks.General; - [Params(1024, 2048, 4096)] - public int Count { get; set; } +[Config(typeof(Config.ShortMultiFramework))] +public class Adler32Benchmark +{ + private byte[] data; + private readonly SharpAdler32 adler = new SharpAdler32(); - [GlobalSetup] - public void SetUp() - { - this.data = new byte[this.Count]; - new Random(1).NextBytes(this.data); - } + [Params(1024, 2048, 4096)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public long SharpZipLibCalculate() - { - this.adler.Reset(); - this.adler.Update(this.data); - return this.adler.Value; - } + [GlobalSetup] + public void SetUp() + { + this.data = new byte[this.Count]; + new Random(1).NextBytes(this.data); + } - [Benchmark] - public uint SixLaborsCalculate() - { - return Adler32.Calculate(this.data); - } + [Benchmark(Baseline = true)] + public long SharpZipLibCalculate() + { + this.adler.Reset(); + this.adler.Update(this.data); + return this.adler.Value; } - // ########## 17/05/2020 ########## - // - // | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| - // | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - | + [Benchmark] + public uint SixLaborsCalculate() + { + return Adler32.Calculate(this.data); + } } + +// ########## 17/05/2020 ########## +// +// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| +// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index 76706e2d4d..f0a36ee1d6 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,130 +1,126 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp; +namespace SixLabors.ImageSharp.Benchmarks.General; -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 | - 'Array access using a jagged array' | 32 | 229.3 ns | 6.028 ns | 17.58 ns | 0.67 | 0.07 | - 'Array access using DenseMatrix' | 32 | 223.2 ns | 5.248 ns | 15.22 ns | 0.65 | 0.07 | - - * - */ - public class Array2D - { - private float[] flatArray; +'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 | + 'Array access using a jagged array' | 32 | 229.3 ns | 6.028 ns | 17.58 ns | 0.67 | 0.07 | + 'Array access using DenseMatrix' | 32 | 223.2 ns | 5.248 ns | 15.22 ns | 0.65 | 0.07 | + + * + */ +public class Array2D +{ + private float[] flatArray; - private float[,] array2D; + private float[,] array2D; - private float[][] jaggedData; + private float[][] jaggedData; - private DenseMatrix matrix; + private DenseMatrix matrix; - [Params(4, 16, 32)] - public int Count { get; set; } + [Params(4, 16, 32)] + public int Count { get; set; } - public int Min { get; private set; } + public int Min { get; private set; } - public int Max { get; private set; } + public int Max { get; private set; } - [GlobalSetup] - public void SetUp() - { - this.flatArray = new float[this.Count * this.Count]; - this.array2D = new float[this.Count, this.Count]; - this.jaggedData = new float[this.Count][]; + [GlobalSetup] + public void SetUp() + { + this.flatArray = new float[this.Count * this.Count]; + this.array2D = new float[this.Count, this.Count]; + this.jaggedData = new float[this.Count][]; - for (int i = 0; i < this.Count; i++) - { - this.jaggedData[i] = new float[this.Count]; - } + for (int i = 0; i < this.Count; i++) + { + this.jaggedData[i] = new float[this.Count]; + } - this.matrix = new DenseMatrix(this.array2D); + this.matrix = new DenseMatrix(this.array2D); - this.Min = (this.Count / 2) - 10; - this.Min = Math.Max(0, this.Min); - this.Max = this.Min + Math.Min(10, this.Count); - } + this.Min = (this.Count / 2) - 10; + this.Min = Math.Max(0, this.Min); + this.Max = this.Min + Math.Min(10, this.Count); + } - [Benchmark(Description = "Emulated 2D array access using flat array")] - public float FlatArrayIndex() + [Benchmark(Description = "Emulated 2D array access using flat array")] + public float FlatArrayIndex() + { + float[] a = this.flatArray; + float s = 0; + int count = this.Count; + for (int i = this.Min; i < this.Max; i++) { - float[] a = this.flatArray; - float s = 0; - int count = this.Count; - for (int i = this.Min; i < this.Max; i++) + for (int j = this.Min; j < this.Max; j++) { - for (int j = this.Min; j < this.Max; j++) - { - ref float v = ref a[(count * i) + j]; - v = i * j; - s += v; - } + ref float v = ref a[(count * i) + j]; + v = i * j; + s += v; } - - return s; } - [Benchmark(Baseline = true, Description = "Array access using 2D array")] - public float Array2DIndex() + return s; + } + + [Benchmark(Baseline = true, Description = "Array access using 2D array")] + public float Array2DIndex() + { + float s = 0; + float[,] a = this.array2D; + for (int i = this.Min; i < this.Max; i++) { - float s = 0; - float[,] a = this.array2D; - for (int i = this.Min; i < this.Max; i++) + for (int j = this.Min; j < this.Max; j++) { - for (int j = this.Min; j < this.Max; j++) - { - ref float v = ref a[i, j]; - v = i * j; - s += v; - } + ref float v = ref a[i, j]; + v = i * j; + s += v; } - - return s; } - [Benchmark(Description = "Array access using a jagged array")] - public float ArrayJaggedIndex() + return s; + } + + [Benchmark(Description = "Array access using a jagged array")] + public float ArrayJaggedIndex() + { + float s = 0; + float[][] a = this.jaggedData; + for (int i = this.Min; i < this.Max; i++) { - float s = 0; - float[][] a = this.jaggedData; - for (int i = this.Min; i < this.Max; i++) + for (int j = this.Min; j < this.Max; j++) { - for (int j = this.Min; j < this.Max; j++) - { - ref float v = ref a[i][j]; - v = i * j; - s += v; - } + ref float v = ref a[i][j]; + v = i * j; + s += v; } - - return s; } - [Benchmark(Description = "Array access using DenseMatrix")] - public float ArrayMatrixIndex() + return s; + } + + [Benchmark(Description = "Array access using DenseMatrix")] + public float ArrayMatrixIndex() + { + float s = 0; + DenseMatrix a = this.matrix; + for (int i = this.Min; i < this.Max; i++) { - float s = 0; - DenseMatrix a = this.matrix; - for (int i = this.Min; i < this.Max; i++) + for (int j = this.Min; j < this.Max; j++) { - for (int j = this.Min; j < this.Max; j++) - { - ref float v = ref a[i, j]; - v = i * j; - s += v; - } + ref float v = ref a[i, j]; + v = i * j; + s += v; } - - return s; } + + return s; } } diff --git a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs index 1c102945e5..034f20e05f 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,59 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General; + +public class ArrayReverse { - public class ArrayReverse - { - [Params(4, 16, 32)] - public int Count { get; set; } + [Params(4, 16, 32)] + public int Count { get; set; } - private byte[] source; + private byte[] source; - private byte[] destination; + private byte[] destination; - [GlobalSetup] - public void SetUp() - { - this.source = new byte[this.Count]; - this.destination = new byte[this.Count]; - } + [GlobalSetup] + public void SetUp() + { + this.source = new byte[this.Count]; + this.destination = new byte[this.Count]; + } - [Benchmark(Baseline = true, Description = "Copy using Array.Reverse()")] - public void ReverseArray() - { - Array.Reverse(this.source, 0, this.Count); - } + [Benchmark(Baseline = true, Description = "Copy using Array.Reverse()")] + public void ReverseArray() + { + Array.Reverse(this.source, 0, this.Count); + } + + [Benchmark(Description = "Reverse using loop")] + public void ReverseLoop() + { + this.ReverseBytes(this.source, 0, this.Count); - [Benchmark(Description = "Reverse using loop")] - public void ReverseLoop() + /* + for (int i = 0; i < this.source.Length / 2; i++) { - 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; - }*/ - } + 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) + public void ReverseBytes(byte[] source, int index, int length) + { + int i = index; + int j = index + length - 1; + while (i < j) { - int i = index; - int j = index + length - 1; - while (i < j) - { - byte temp = source[i]; - source[i] = source[j]; - source[j] = temp; - i++; - j--; - } + byte temp = source[i]; + source[i] = source[j]; + source[j] = temp; + i++; + j--; } } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index b51834a52a..ac87ea5d1f 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,43 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class Abs { - public class Abs - { - [Params(-1, 1)] - public int X { get; set; } + [Params(-1, 1)] + public int X { get; set; } - [Benchmark(Baseline = true, Description = "Maths Abs")] - public int MathAbs() - { - int x = this.X; - return Math.Abs(x); - } + [Benchmark(Baseline = true, Description = "Maths Abs")] + public int MathAbs() + { + int x = this.X; + return Math.Abs(x); + } - [Benchmark(Description = "Conditional Abs")] - public int ConditionalAbs() - { - int x = this.X; - return x < 0 ? -x : x; - } + [Benchmark(Description = "Conditional Abs")] + public int ConditionalAbs() + { + int x = this.X; + return x < 0 ? -x : x; + } - [Benchmark(Description = "Bitwise Abs")] - public int AbsBitwise() - { - int x = this.X; - return (x ^ (x >> 31)) - (x >> 31); - } + [Benchmark(Description = "Bitwise Abs")] + public int AbsBitwise() + { + int x = this.X; + return (x ^ (x >> 31)) - (x >> 31); + } - [Benchmark(Description = "Bitwise Abs With Variable")] - public int AbsBitwiseVer() - { - int x = this.X; - int y = x >> 31; - return (x ^ y) - y; - } + [Benchmark(Description = "Bitwise Abs With Variable")] + public int AbsBitwiseVer() + { + int x = this.X; + int y = x >> 31; + return (x ^ y) - y; } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs index 8cab72f51a..dd0bc28785 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs @@ -1,70 +1,68 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class ClampFloat { - public class ClampFloat + 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 float UsingMathF() { - 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 }; + float acc = 0; - [Benchmark(Baseline = true)] - public float UsingMathF() + for (int i = 0; i < Values.Length; i++) { - float acc = 0; + acc += ClampUsingMathF(Values[i], this.min, this.max); + } - for (int i = 0; i < Values.Length; i++) - { - acc += ClampUsingMathF(Values[i], this.min, this.max); - } + return acc; + } - return acc; - } + [Benchmark] + public float UsingBranching() + { + float acc = 0; - [Benchmark] - public float UsingBranching() + for (int i = 0; i < Values.Length; i++) { - float acc = 0; + acc += ClampUsingBranching(Values[i], this.min, this.max); + } - for (int i = 0; i < Values.Length; i++) - { - acc += ClampUsingBranching(Values[i], this.min, this.max); - } + return acc; + } - return acc; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ClampUsingMathF(float x, float min, float max) + { + return Math.Min(max, Math.Max(min, x)); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ClampUsingMathF(float x, float min, float max) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ClampUsingBranching(float x, float min, float max) + { + if (x >= max) { - return Math.Min(max, Math.Max(min, x)); + return max; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float ClampUsingBranching(float x, float min, float max) + if (x <= min) { - if (x >= max) - { - return max; - } - - if (x <= min) - { - return min; - } - - return x; + return min; } - // RESULTS: - // Method | Mean | Error | StdDev | Scaled | - // --------------- |---------:|----------:|----------:|-------:| - // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | - // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | + return x; } + + // RESULTS: + // Method | Mean | Error | StdDev | Scaled | + // --------------- |---------:|----------:|----------:|-------:| + // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | + // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs index 94c56603fb..40ea0d253b 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampInt32IntoByte.cs @@ -1,92 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class ClampInt32IntoByte { - public class ClampInt32IntoByte + [Params(-1, 0, 255, 256)] + public int Value { get; set; } + + [Benchmark(Baseline = true, Description = "Maths Clamp")] + public byte ClampMaths() { - [Params(-1, 0, 255, 256)] - public int Value { get; set; } + int value = this.Value; + return (byte)Math.Min(Math.Max(byte.MinValue, value), byte.MaxValue); + } - [Benchmark(Baseline = true, Description = "Maths Clamp")] - public byte ClampMaths() - { - int value = this.Value; - return (byte)Math.Min(Math.Max(byte.MinValue, value), byte.MaxValue); - } + [Benchmark(Description = "No Maths Clamp")] + public byte ClampNoMaths() + { + int value = this.Value; + value = value >= byte.MaxValue ? byte.MaxValue : value; + return (byte)(value <= byte.MinValue ? byte.MinValue : value); + } - [Benchmark(Description = "No Maths Clamp")] - public byte ClampNoMaths() - { - int value = this.Value; - value = value >= byte.MaxValue ? byte.MaxValue : value; - return (byte)(value <= byte.MinValue ? byte.MinValue : value); - } + [Benchmark(Description = "No Maths No Equals Clamp")] + public byte ClampNoMathsNoEquals() + { + int value = this.Value; + value = value > byte.MaxValue ? byte.MaxValue : value; + return (byte)(value < byte.MinValue ? byte.MinValue : value); + } - [Benchmark(Description = "No Maths No Equals Clamp")] - public byte ClampNoMathsNoEquals() + [Benchmark(Description = "No Maths Clamp No Ternary")] + public byte ClampNoMathsNoTernary() + { + int value = this.Value; + + if (value >= byte.MaxValue) { - int value = this.Value; - value = value > byte.MaxValue ? byte.MaxValue : value; - return (byte)(value < byte.MinValue ? byte.MinValue : value); + return byte.MaxValue; } - [Benchmark(Description = "No Maths Clamp No Ternary")] - public byte ClampNoMathsNoTernary() + if (value <= byte.MinValue) { - int value = this.Value; - - if (value >= byte.MaxValue) - { - return byte.MaxValue; - } - - if (value <= byte.MinValue) - { - return byte.MinValue; - } - - return (byte)value; + return byte.MinValue; } - [Benchmark(Description = "No Maths No Equals Clamp No Ternary")] - public byte ClampNoMathsEqualsNoTernary() - { - int value = this.Value; + return (byte)value; + } - if (value > byte.MaxValue) - { - return byte.MaxValue; - } + [Benchmark(Description = "No Maths No Equals Clamp No Ternary")] + public byte ClampNoMathsEqualsNoTernary() + { + int value = this.Value; - if (value < byte.MinValue) - { - return byte.MinValue; - } + if (value > byte.MaxValue) + { + return byte.MaxValue; + } - return (byte)value; - } + if (value < byte.MinValue) + { + return byte.MinValue; + } - [Benchmark(Description = "Clamp using Bitwise Abs")] - public byte ClampBitwise() - { - int x = this.Value; - int absMax = byte.MaxValue - x; - x = (x + byte.MaxValue - AbsBitwiseVer(ref absMax)) >> 1; - x = (x + byte.MinValue + AbsBitwiseVer(ref x)) >> 1; + return (byte)value; + } - return (byte)x; - } + [Benchmark(Description = "Clamp using Bitwise Abs")] + public byte ClampBitwise() + { + int x = this.Value; + int absMax = byte.MaxValue - x; + x = (x + byte.MaxValue - AbsBitwiseVer(ref absMax)) >> 1; + x = (x + byte.MinValue + AbsBitwiseVer(ref x)) >> 1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int AbsBitwiseVer(ref int x) - { - int y = x >> 31; - return (x ^ y) - y; - } + return (byte)x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int AbsBitwiseVer(ref int x) + { + int y = x >> 31; + return (x ^ y) - y; } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs index 67c82a2407..d47fcb687e 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampSpan.cs @@ -1,43 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class ClampSpan { - public class ClampSpan + private static readonly int[] A = new int[2048]; + private static readonly int[] B = new int[2048]; + + public void Setup() { - private static readonly int[] A = new int[2048]; - private static readonly int[] B = new int[2048]; + var r = new Random(); - public void Setup() + for (int i = 0; i < A.Length; i++) { - var r = new Random(); - - for (int i = 0; i < A.Length; i++) - { - int x = r.Next(); - A[i] = x; - B[i] = x; - } + int x = r.Next(); + A[i] = x; + B[i] = x; } + } - [Benchmark(Baseline = true)] - public void ClampNoIntrinsics() + [Benchmark(Baseline = true)] + public void ClampNoIntrinsics() + { + for (int i = 0; i < A.Length; i++) { - for (int i = 0; i < A.Length; i++) - { - ref int x = ref A[i]; - x = Numerics.Clamp(x, 64, 128); - } + ref int x = ref A[i]; + x = Numerics.Clamp(x, 64, 128); } + } - [Benchmark] - public void ClampVectorIntrinsics() - { - Numerics.Clamp(B, 64, 128); - } + [Benchmark] + public void ClampVectorIntrinsics() + { + Numerics.Clamp(B, 64, 128); } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs index 6941a7efe3..186f88bb71 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs @@ -5,56 +5,55 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class ClampVector4 { - 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() { - 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 }; + Vector4 acc = Vector4.Zero; - [Benchmark(Baseline = true)] - public Vector4 UsingVectorClamp() + for (int i = 0; i < Values.Length; i++) { - Vector4 acc = Vector4.Zero; - - for (int i = 0; i < Values.Length; i++) - { - acc += ClampUsingVectorClamp(Values[i], this.min, this.max); - } - - return acc; + acc += ClampUsingVectorClamp(Values[i], this.min, this.max); } - [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; + } - return acc; - } + [Benchmark] + public Vector4 UsingVectorMinMax() + { + Vector4 acc = Vector4.Zero; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 ClampUsingVectorClamp(float x, float min, float max) + for (int i = 0; i < Values.Length; i++) { - return Vector4.Clamp(new Vector4(x), new Vector4(min), new Vector4(max)); + acc += ClampUsingVectorMinMax(Values[i], this.min, this.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))); - } + 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)); + } - // 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 | + [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 cbb35a2a5f..937966ad5e 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -3,23 +3,22 @@ using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +[LongRunJob] +public class ModuloPowerOfTwoConstant { - [LongRunJob] - public class ModuloPowerOfTwoConstant - { - private readonly int value = 42; + private readonly int value = 42; - [Benchmark(Baseline = true)] - public int Standard() - { - return this.value % 8; - } + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % 8; + } - [Benchmark] - public int Bitwise() - { - return Numerics.Modulo8(this.value); - } + [Benchmark] + public int Bitwise() + { + return Numerics.Modulo8(this.value); } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs index 48bf0946e2..5973eda5f1 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -3,32 +3,31 @@ using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath -{ - [LongRunJob] - public class ModuloPowerOfTwoVariable - { - private readonly int value = 42; +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - private readonly int m = 32; +[LongRunJob] +public class ModuloPowerOfTwoVariable +{ + private readonly int value = 42; - [Benchmark(Baseline = true)] - public int Standard() - { - return this.value % this.m; - } + private readonly int m = 32; - [Benchmark] - public int Bitwise() - { - return Numerics.ModuloP2(this.value, this.m); - } + [Benchmark(Baseline = true)] + public int Standard() + { + return this.value % this.m; + } - // RESULTS: - // - // Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | - // --------- |----------:|----------:|----------:|----------:|-------:|---------:| - // 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 | + [Benchmark] + public int Bitwise() + { + return Numerics.ModuloP2(this.value, this.m); } + + // RESULTS: + // + // Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | + // --------- |----------:|----------:|----------:|----------:|-------:|---------:| + // 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 | } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs index 2074767ae8..7f447f0bad 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,42 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class Pow { - public class Pow - { - [Params(-1.333F, 1.333F)] - public float X { get; set; } + [Params(-1.333F, 1.333F)] + public float X { get; set; } - [Benchmark(Baseline = true, Description = "Math.Pow 2")] - public float MathPow() - { - float x = this.X; - return (float)Math.Pow(x, 2); - } + [Benchmark(Baseline = true, Description = "Math.Pow 2")] + public float MathPow() + { + float x = this.X; + return (float)Math.Pow(x, 2); + } - [Benchmark(Description = "Pow x2")] - public float PowMult() - { - float x = this.X; - return x * x; - } + [Benchmark(Description = "Pow x2")] + public float PowMult() + { + float x = this.X; + return x * x; + } - [Benchmark(Description = "Math.Pow 3")] - public float MathPow3() - { - float x = this.X; - return (float)Math.Pow(x, 3); - } + [Benchmark(Description = "Math.Pow 3")] + public float MathPow3() + { + float x = this.X; + return (float)Math.Pow(x, 3); + } - [Benchmark(Description = "Pow x3")] - public float PowMult3() - { - float x = this.X; - return x * x * x; - } + [Benchmark(Description = "Pow x3")] + public float PowMult3() + { + float x = this.X; + return x * x * x; } } diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs index 71936d8b08..c90a7f3181 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -1,25 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +public class Round { - public class Round - { - private const float Input = .51F; + private const float Input = .51F; - [Benchmark] - public int ConvertTo() => Convert.ToInt32(Input); + [Benchmark] + public int ConvertTo() => Convert.ToInt32(Input); - [Benchmark] - public int MathRound() => (int)Math.Round(Input); + [Benchmark] + public int MathRound() => (int)Math.Round(Input); - // Results 20th Jan 2019 - // Method | Mean | Error | StdDev | Median | - //---------- |----------:|----------:|----------:|----------:| - // ConvertTo | 3.1967 ns | 0.1234 ns | 0.2129 ns | 3.2340 ns | - // MathRound | 0.0528 ns | 0.0374 ns | 0.1079 ns | 0.0000 ns | - } + // Results 20th Jan 2019 + // Method | Mean | Error | StdDev | Median | + //---------- |----------:|----------:|----------:|----------:| + // ConvertTo | 3.1967 ns | 0.1234 ns | 0.2129 ns | 3.2340 ns | + // MathRound | 0.0528 ns | 0.0374 ns | 0.1079 ns | 0.0000 ns | } diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs index 7712fc4e69..6b27cb3db0 100644 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -5,42 +5,41 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General; + +public class Buffer2D_DangerousGetRowSpan { - public class Buffer2D_DangerousGetRowSpan + private const int Height = 1024; + + [Params(0.5, 2.0, 10.0)] + public double SizeMegaBytes { get; set; } + + private Buffer2D buffer; + + [GlobalSetup] + public unsafe void Setup() { - private const int Height = 1024; - - [Params(0.5, 2.0, 10.0)] - public double SizeMegaBytes { get; set; } - - private Buffer2D buffer; - - [GlobalSetup] - public unsafe void Setup() - { - int totalElements = (int)(1024 * 1024 * this.SizeMegaBytes) / sizeof(Rgba32); - - int width = totalElements / Height; - MemoryAllocator allocator = Configuration.Default.MemoryAllocator; - this.buffer = allocator.Allocate2D(width, Height); - } - - [GlobalCleanup] - public void Cleanup() => this.buffer.Dispose(); - - [Benchmark] - public int DangerousGetRowSpan() => - this.buffer.DangerousGetRowSpan(1).Length + - this.buffer.DangerousGetRowSpan(Height - 1).Length; - - // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 - // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores - // - // | Method | SizeMegaBytes | Mean | Error | StdDev | - // |-------------------- |-------------- |----------:|----------:|----------:| - // | DangerousGetRowSpan | 0.5 | 7.498 ns | 0.1784 ns | 0.3394 ns | - // | DangerousGetRowSpan | 2 | 6.542 ns | 0.1565 ns | 0.3659 ns | - // | DangerousGetRowSpan | 10 | 38.556 ns | 0.6604 ns | 0.8587 ns | + int totalElements = (int)(1024 * 1024 * this.SizeMegaBytes) / sizeof(Rgba32); + + int width = totalElements / Height; + MemoryAllocator allocator = Configuration.Default.MemoryAllocator; + this.buffer = allocator.Allocate2D(width, Height); } + + [GlobalCleanup] + public void Cleanup() => this.buffer.Dispose(); + + [Benchmark] + public int DangerousGetRowSpan() => + this.buffer.DangerousGetRowSpan(1).Length + + this.buffer.DangerousGetRowSpan(Height - 1).Length; + + // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 + // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores + // + // | Method | SizeMegaBytes | Mean | Error | StdDev | + // |-------------------- |-------------- |----------:|----------:|----------:| + // | DangerousGetRowSpan | 0.5 | 7.498 ns | 0.1784 ns | 0.3394 ns | + // | DangerousGetRowSpan | 2 | 6.542 ns | 0.1565 ns | 0.3659 ns | + // | DangerousGetRowSpan | 10 | 38.556 ns | 0.6604 ns | 0.8587 ns | } diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 102876c7b8..3929f7c5ac 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -1,228 +1,226 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General; + +/// +/// Compare different methods for copying native and/or managed buffers. +/// Conclusions: +/// - Span.CopyTo() has terrible performance on classic .NET Framework +/// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) +/// +[Config(typeof(Config.ShortMultiFramework))] +public class CopyBuffers { - /// - /// Compare different methods for copying native and/or managed buffers. - /// Conclusions: - /// - Span.CopyTo() has terrible performance on classic .NET Framework - /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) - /// - [Config(typeof(Config.ShortMultiFramework))] - public class CopyBuffers + private byte[] destArray; + + private MemoryHandle destHandle; + + private Memory destMemory; + + private byte[] sourceArray; + + private MemoryHandle sourceHandle; + + private Memory sourceMemory; + + [Params(10, 50, 100, 1000, 10000)] + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + this.sourceArray = new byte[this.Count]; + this.sourceMemory = new Memory(this.sourceArray); + this.sourceHandle = this.sourceMemory.Pin(); + + this.destArray = new byte[this.Count]; + this.destMemory = new Memory(this.destArray); + this.destHandle = this.destMemory.Pin(); + } + + [GlobalCleanup] + public void Cleanup() + { + this.sourceHandle.Dispose(); + this.destHandle.Dispose(); + } + + [Benchmark(Baseline = true, Description = "Array.Copy()")] + public void ArrayCopy() + { + Array.Copy(this.sourceArray, this.destArray, this.Count); + } + + [Benchmark(Description = "Buffer.BlockCopy()")] + public void BufferBlockCopy() + { + Buffer.BlockCopy(this.sourceArray, 0, this.destArray, 0, this.Count); + } + + [Benchmark(Description = "Buffer.MemoryCopy()")] + public unsafe void BufferMemoryCopy() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); + } + + [Benchmark(Description = "Marshal.Copy()")] + public unsafe void MarshalCopy() + { + void* pinnedDestination = this.destHandle.Pointer; + Marshal.Copy(this.sourceArray, 0, (IntPtr)pinnedDestination, this.Count); + } + + [Benchmark(Description = "Span.CopyTo()")] + public void SpanCopyTo() + { + this.sourceMemory.Span.CopyTo(this.destMemory.Span); + } + + [Benchmark(Description = "Unsafe.CopyBlock(ref)")] + public void UnsafeCopyBlockReferences() { - private byte[] destArray; - - private MemoryHandle destHandle; - - private Memory destMemory; - - private byte[] sourceArray; - - private MemoryHandle sourceHandle; - - private Memory sourceMemory; - - [Params(10, 50, 100, 1000, 10000)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.sourceArray = new byte[this.Count]; - this.sourceMemory = new Memory(this.sourceArray); - this.sourceHandle = this.sourceMemory.Pin(); - - this.destArray = new byte[this.Count]; - this.destMemory = new Memory(this.destArray); - this.destHandle = this.destMemory.Pin(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.sourceHandle.Dispose(); - this.destHandle.Dispose(); - } - - [Benchmark(Baseline = true, Description = "Array.Copy()")] - public void ArrayCopy() - { - Array.Copy(this.sourceArray, this.destArray, this.Count); - } - - [Benchmark(Description = "Buffer.BlockCopy()")] - public void BufferBlockCopy() - { - Buffer.BlockCopy(this.sourceArray, 0, this.destArray, 0, this.Count); - } - - [Benchmark(Description = "Buffer.MemoryCopy()")] - public unsafe void BufferMemoryCopy() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); - } - - [Benchmark(Description = "Marshal.Copy()")] - public unsafe void MarshalCopy() - { - void* pinnedDestination = this.destHandle.Pointer; - Marshal.Copy(this.sourceArray, 0, (IntPtr)pinnedDestination, this.Count); - } - - [Benchmark(Description = "Span.CopyTo()")] - public void SpanCopyTo() - { - this.sourceMemory.Span.CopyTo(this.destMemory.Span); - } - - [Benchmark(Description = "Unsafe.CopyBlock(ref)")] - public void UnsafeCopyBlockReferences() - { - Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlock(ptr)")] - public unsafe void UnsafeCopyBlockPointers() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")] - public void UnsafeCopyBlockUnalignedReferences() - { - Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); - } - - [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")] - public unsafe void UnsafeCopyBlockUnalignedPointers() - { - void* pinnedDestination = this.destHandle.Pointer; - void* pinnedSource = this.sourceHandle.Pointer; - Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count); - } - - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC - // .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.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 - // - // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| - // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - | - // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - | - // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | | - // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - | - // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - | - // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - | - // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - | - // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - | - // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - | + Unsafe.CopyBlock(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); } + + [Benchmark(Description = "Unsafe.CopyBlock(ptr)")] + public unsafe void UnsafeCopyBlockPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlock(pinnedDestination, pinnedSource, (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ref)")] + public void UnsafeCopyBlockUnalignedReferences() + { + Unsafe.CopyBlockUnaligned(ref this.destArray[0], ref this.sourceArray[0], (uint)this.Count); + } + + [Benchmark(Description = "Unsafe.CopyBlockUnaligned(ptr)")] + public unsafe void UnsafeCopyBlockUnalignedPointers() + { + void* pinnedDestination = this.destHandle.Pointer; + void* pinnedSource = this.sourceHandle.Pointer; + Unsafe.CopyBlockUnaligned(pinnedDestination, pinnedSource, (uint)this.Count); + } + + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .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.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 + // + // | Method | Job | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |------------------------------- |----- |-------- |------ |-----------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | Array.Copy() | Clr | Clr | 10 | 23.636 ns | 2.5299 ns | 0.1387 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10 | 11.420 ns | 2.3341 ns | 0.1279 ns | 0.48 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10 | 2.861 ns | 0.5059 ns | 0.0277 ns | 0.12 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10 | 14.870 ns | 2.4541 ns | 0.1345 ns | 0.63 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10 | 31.906 ns | 1.2213 ns | 0.0669 ns | 1.35 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10 | 3.513 ns | 0.7392 ns | 0.0405 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10 | 3.053 ns | 0.2010 ns | 0.0110 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10 | 3.497 ns | 0.4911 ns | 0.0269 ns | 0.15 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10 | 3.109 ns | 0.5958 ns | 0.0327 ns | 0.13 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10 | 19.709 ns | 2.1867 ns | 0.1199 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10 | 7.377 ns | 1.1582 ns | 0.0635 ns | 0.37 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10 | 2.581 ns | 1.1607 ns | 0.0636 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10 | 15.197 ns | 2.8446 ns | 0.1559 ns | 0.77 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10 | 25.394 ns | 0.9782 ns | 0.0536 ns | 1.29 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10 | 2.254 ns | 0.1590 ns | 0.0087 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10 | 1.878 ns | 0.1035 ns | 0.0057 ns | 0.10 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10 | 2.263 ns | 0.1383 ns | 0.0076 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10 | 1.877 ns | 0.0602 ns | 0.0033 ns | 0.10 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 50 | 35.068 ns | 5.9137 ns | 0.3242 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 50 | 23.299 ns | 2.3797 ns | 0.1304 ns | 0.66 | 0.01 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 50 | 3.598 ns | 4.8536 ns | 0.2660 ns | 0.10 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 50 | 27.720 ns | 4.6602 ns | 0.2554 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 50 | 35.673 ns | 16.2972 ns | 0.8933 ns | 1.02 | 0.03 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 50 | 5.534 ns | 2.8119 ns | 0.1541 ns | 0.16 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 50 | 4.511 ns | 0.9555 ns | 0.0524 ns | 0.13 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 50 | 5.613 ns | 1.6679 ns | 0.0914 ns | 0.16 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 50 | 4.884 ns | 7.3153 ns | 0.4010 ns | 0.14 | 0.01 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 50 | 20.232 ns | 1.5720 ns | 0.0862 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 50 | 8.142 ns | 0.7860 ns | 0.0431 ns | 0.40 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 50 | 2.962 ns | 0.0611 ns | 0.0033 ns | 0.15 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 50 | 16.802 ns | 2.9686 ns | 0.1627 ns | 0.83 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 50 | 26.571 ns | 0.9228 ns | 0.0506 ns | 1.31 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 50 | 2.219 ns | 0.7191 ns | 0.0394 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 50 | 1.751 ns | 0.1884 ns | 0.0103 ns | 0.09 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 50 | 2.177 ns | 0.4489 ns | 0.0246 ns | 0.11 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 50 | 1.806 ns | 0.1063 ns | 0.0058 ns | 0.09 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 100 | 39.158 ns | 4.3068 ns | 0.2361 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 100 | 27.623 ns | 0.4611 ns | 0.0253 ns | 0.71 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 100 | 5.018 ns | 0.5689 ns | 0.0312 ns | 0.13 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 100 | 33.527 ns | 1.9019 ns | 0.1042 ns | 0.86 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 100 | 35.604 ns | 2.7039 ns | 0.1482 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 100 | 7.853 ns | 0.4925 ns | 0.0270 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 100 | 7.406 ns | 1.9733 ns | 0.1082 ns | 0.19 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 100 | 7.822 ns | 0.6837 ns | 0.0375 ns | 0.20 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 100 | 7.392 ns | 1.2832 ns | 0.0703 ns | 0.19 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 100 | 22.909 ns | 2.9754 ns | 0.1631 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 100 | 10.687 ns | 1.1262 ns | 0.0617 ns | 0.47 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 100 | 4.063 ns | 0.1607 ns | 0.0088 ns | 0.18 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 100 | 18.067 ns | 4.0557 ns | 0.2223 ns | 0.79 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 100 | 28.352 ns | 1.2762 ns | 0.0700 ns | 1.24 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 100 | 4.130 ns | 0.2013 ns | 0.0110 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 100 | 4.096 ns | 0.2460 ns | 0.0135 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 100 | 4.160 ns | 0.3174 ns | 0.0174 ns | 0.18 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 100 | 3.480 ns | 1.1683 ns | 0.0640 ns | 0.15 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 1000 | 49.059 ns | 2.0729 ns | 0.1136 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 1000 | 38.270 ns | 23.6970 ns | 1.2989 ns | 0.78 | 0.03 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 1000 | 27.599 ns | 6.8328 ns | 0.3745 ns | 0.56 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 1000 | 42.752 ns | 5.1357 ns | 0.2815 ns | 0.87 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 1000 | 69.983 ns | 2.1860 ns | 0.1198 ns | 1.43 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 1000 | 44.822 ns | 0.1625 ns | 0.0089 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 1000 | 45.072 ns | 1.4053 ns | 0.0770 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 1000 | 45.306 ns | 5.2646 ns | 0.2886 ns | 0.92 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 1000 | 44.813 ns | 0.9117 ns | 0.0500 ns | 0.91 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 1000 | 51.907 ns | 3.1827 ns | 0.1745 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 1000 | 40.700 ns | 3.1488 ns | 0.1726 ns | 0.78 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 1000 | 23.711 ns | 1.3004 ns | 0.0713 ns | 0.46 | 0.00 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 1000 | 42.586 ns | 2.5390 ns | 0.1392 ns | 0.82 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 1000 | 44.109 ns | 4.5604 ns | 0.2500 ns | 0.85 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 1000 | 33.926 ns | 5.1633 ns | 0.2830 ns | 0.65 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 1000 | 33.267 ns | 0.2708 ns | 0.0148 ns | 0.64 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 1000 | 34.018 ns | 2.3238 ns | 0.1274 ns | 0.66 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 1000 | 33.667 ns | 2.1983 ns | 0.1205 ns | 0.65 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Clr | Clr | 10000 | 153.429 ns | 6.1735 ns | 0.3384 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Clr | Clr | 10000 | 201.373 ns | 4.3670 ns | 0.2394 ns | 1.31 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Clr | Clr | 10000 | 211.768 ns | 71.3510 ns | 3.9110 ns | 1.38 | 0.02 | - | - | - | - | + // | Marshal.Copy() | Clr | Clr | 10000 | 215.299 ns | 17.2677 ns | 0.9465 ns | 1.40 | 0.01 | - | - | - | - | + // | Span.CopyTo() | Clr | Clr | 10000 | 486.325 ns | 32.4445 ns | 1.7784 ns | 3.17 | 0.01 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Clr | Clr | 10000 | 452.314 ns | 33.0593 ns | 1.8121 ns | 2.95 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Clr | Clr | 10000 | 455.600 ns | 56.7534 ns | 3.1108 ns | 2.97 | 0.02 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Clr | Clr | 10000 | 452.279 ns | 8.6457 ns | 0.4739 ns | 2.95 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Clr | Clr | 10000 | 453.146 ns | 12.3776 ns | 0.6785 ns | 2.95 | 0.00 | - | - | - | - | + // | | | | | | | | | | | | | | + // | Array.Copy() | Core | Core | 10000 | 204.508 ns | 3.1652 ns | 0.1735 ns | 1.00 | 0.00 | - | - | - | - | + // | Buffer.BlockCopy() | Core | Core | 10000 | 193.345 ns | 1.3742 ns | 0.0753 ns | 0.95 | 0.00 | - | - | - | - | + // | Buffer.MemoryCopy() | Core | Core | 10000 | 196.978 ns | 18.3279 ns | 1.0046 ns | 0.96 | 0.01 | - | - | - | - | + // | Marshal.Copy() | Core | Core | 10000 | 206.878 ns | 6.9938 ns | 0.3834 ns | 1.01 | 0.00 | - | - | - | - | + // | Span.CopyTo() | Core | Core | 10000 | 215.733 ns | 15.4824 ns | 0.8486 ns | 1.05 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ref) | Core | Core | 10000 | 186.894 ns | 8.7617 ns | 0.4803 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlock(ptr) | Core | Core | 10000 | 186.662 ns | 10.6059 ns | 0.5813 ns | 0.91 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ref) | Core | Core | 10000 | 187.489 ns | 13.1527 ns | 0.7209 ns | 0.92 | 0.00 | - | - | - | - | + // | Unsafe.CopyBlockUnaligned(ptr) | Core | Core | 10000 | 186.586 ns | 4.6274 ns | 0.2536 ns | 0.91 | 0.00 | - | - | - | - | } diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs index c64e5ed86e..fdc9e26b60 100644 --- a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs @@ -1,72 +1,70 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Compression.Zlib; using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; -namespace SixLabors.ImageSharp.Benchmarks.General -{ - [Config(typeof(Config.ShortMultiFramework))] - public class Crc32Benchmark - { - private byte[] data; - private readonly SharpCrc32 crc = new SharpCrc32(); +namespace SixLabors.ImageSharp.Benchmarks.General; - [Params(1024, 2048, 4096)] - public int Count { get; set; } +[Config(typeof(Config.ShortMultiFramework))] +public class Crc32Benchmark +{ + private byte[] data; + private readonly SharpCrc32 crc = new SharpCrc32(); - [GlobalSetup] - public void SetUp() - { - this.data = new byte[this.Count]; - new Random(1).NextBytes(this.data); - } + [Params(1024, 2048, 4096)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public long SharpZipLibCalculate() - { - this.crc.Reset(); - this.crc.Update(this.data); - return this.crc.Value; - } + [GlobalSetup] + public void SetUp() + { + this.data = new byte[this.Count]; + new Random(1).NextBytes(this.data); + } - [Benchmark] - public long SixLaborsCalculate() - { - return Crc32.Calculate(this.data); - } + [Benchmark(Baseline = true)] + public long SharpZipLibCalculate() + { + this.crc.Reset(); + this.crc.Update(this.data); + return this.crc.Value; } - // ########## 17/05/2020 ########## - // - // | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - // |--------------------- |-------------- |------ |-------------:|-------------:|-----------:|------:|--------:|------:|------:|------:|----------:| - // | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 2,797.77 ns | 278.697 ns | 15.276 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 1024 | 2,275.56 ns | 216.100 ns | 11.845 ns | 0.81 | 0.01 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 2,923.43 ns | 2,656.882 ns | 145.633 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 1024 | 2,257.79 ns | 75.081 ns | 4.115 ns | 0.77 | 0.04 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 2,764.14 ns | 86.281 ns | 4.729 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 1024 | 49.32 ns | 1.813 ns | 0.099 ns | 0.02 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 5,603.71 ns | 427.240 ns | 23.418 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 2048 | 4,525.02 ns | 33.931 ns | 1.860 ns | 0.81 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 5,563.32 ns | 49.337 ns | 2.704 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 2048 | 4,519.61 ns | 29.837 ns | 1.635 ns | 0.81 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 5,543.37 ns | 518.551 ns | 28.424 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 2048 | 89.07 ns | 3.312 ns | 0.182 ns | 0.02 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 11,396.95 ns | 373.450 ns | 20.470 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET 4.7.2 | 4096 | 9,070.35 ns | 271.083 ns | 14.859 ns | 0.80 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 11,127.81 ns | 239.177 ns | 13.110 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 2.1 | 4096 | 9,050.46 ns | 230.916 ns | 12.657 ns | 0.81 | 0.00 | - | - | - | - | - // | | | | | | | | | | | | | - // | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 11,098.62 ns | 687.978 ns | 37.710 ns | 1.00 | 0.00 | - | - | - | - | - // | SixLaborsCalculate | .NET Core 3.1 | 4096 | 168.11 ns | 3.633 ns | 0.199 ns | 0.02 | 0.00 | - | - | - | - | + [Benchmark] + public long SixLaborsCalculate() + { + return Crc32.Calculate(this.data); + } } + +// ########## 17/05/2020 ########## +// +// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------------------- |-------------- |------ |-------------:|-------------:|-----------:|------:|--------:|------:|------:|------:|----------:| +// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 2,797.77 ns | 278.697 ns | 15.276 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 2,275.56 ns | 216.100 ns | 11.845 ns | 0.81 | 0.01 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 2,923.43 ns | 2,656.882 ns | 145.633 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 2,257.79 ns | 75.081 ns | 4.115 ns | 0.77 | 0.04 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 2,764.14 ns | 86.281 ns | 4.729 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 49.32 ns | 1.813 ns | 0.099 ns | 0.02 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 5,603.71 ns | 427.240 ns | 23.418 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 4,525.02 ns | 33.931 ns | 1.860 ns | 0.81 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 5,563.32 ns | 49.337 ns | 2.704 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 4,519.61 ns | 29.837 ns | 1.635 ns | 0.81 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 5,543.37 ns | 518.551 ns | 28.424 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 89.07 ns | 3.312 ns | 0.182 ns | 0.02 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 11,396.95 ns | 373.450 ns | 20.470 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 9,070.35 ns | 271.083 ns | 14.859 ns | 0.80 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 11,127.81 ns | 239.177 ns | 13.110 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 9,050.46 ns | 230.916 ns | 12.657 ns | 0.81 | 0.00 | - | - | - | - | +// | | | | | | | | | | | | | +// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 11,098.62 ns | 687.978 ns | 37.710 ns | 1.00 | 0.00 | - | - | - | - | +// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 168.11 ns | 3.633 ns | 0.199 ns | 0.02 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs index b58b07fb03..50a628ee3c 100644 --- a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs @@ -5,24 +5,23 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +public class GetSetPixel { - public class GetSetPixel + [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] + public System.Drawing.Color GetSetSystemDrawing() { - [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] - public System.Drawing.Color GetSetSystemDrawing() - { - using var source = new Bitmap(400, 400); - source.SetPixel(200, 200, System.Drawing.Color.White); - return source.GetPixel(200, 200); - } + using var source = new Bitmap(400, 400); + source.SetPixel(200, 200, System.Drawing.Color.White); + return source.GetPixel(200, 200); + } - [Benchmark(Description = "ImageSharp GetSet pixel")] - public Rgba32 GetSetImageSharp() - { - using var image = new Image(400, 400); - image[200, 200] = Color.White; - return image[200, 200]; - } + [Benchmark(Description = "ImageSharp GetSet pixel")] + public Rgba32 GetSetImageSharp() + { + using var image = new Image(400, 400); + image[200, 200] = Color.White; + return image[200, 200]; } } diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs index 4c3e2e6838..a193e41bd3 100644 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs @@ -1,279 +1,276 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.IO; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Benchmarks.IO +namespace SixLabors.ImageSharp.Benchmarks.IO; + +/// +/// A readonly stream wrapper that add a secondary level buffer in addition to native stream +/// buffered reading to reduce the overhead of small incremental reads. +/// +internal sealed unsafe class BufferedReadStreamWrapper : IDisposable { /// - /// A readonly stream wrapper that add a secondary level buffer in addition to native stream - /// buffered reading to reduce the overhead of small incremental reads. + /// The length, in bytes, of the underlying buffer. /// - internal sealed unsafe class BufferedReadStreamWrapper : IDisposable - { - /// - /// The length, in bytes, of the underlying buffer. - /// - public const int BufferLength = 8192; - - private const int MaxBufferIndex = BufferLength - 1; - - private readonly Stream stream; + public const int BufferLength = 8192; - private readonly byte[] readBuffer; + private const int MaxBufferIndex = BufferLength - 1; - private MemoryHandle readBufferHandle; + private readonly Stream stream; - private readonly byte* pinnedReadBuffer; + private readonly byte[] readBuffer; - // Index within our buffer, not reader position. - private int readBufferIndex; + private MemoryHandle readBufferHandle; - // Matches what the stream position would be without buffering - private long readerPosition; + private readonly byte* pinnedReadBuffer; - private bool isDisposed; + // Index within our buffer, not reader position. + private int readBufferIndex; - /// - /// Initializes a new instance of the class. - /// - /// The input stream. - public BufferedReadStreamWrapper(Stream stream) - { - Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); - Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); - - // Ensure all underlying buffers have been flushed before we attempt to read the stream. - // User streams may have opted to throw from Flush if CanWrite is false - // (although the abstract Stream does not do so). - if (stream.CanWrite) - { - stream.Flush(); - } + // Matches what the stream position would be without buffering + private long readerPosition; - this.stream = stream; - this.Position = (int)stream.Position; - this.Length = stream.Length; + private bool isDisposed; - this.readBuffer = ArrayPool.Shared.Rent(BufferLength); - this.readBufferHandle = new Memory(this.readBuffer).Pin(); - this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; + /// + /// Initializes a new instance of the class. + /// + /// The input stream. + public BufferedReadStreamWrapper(Stream stream) + { + Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable."); + Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable."); - // This triggers a full read on first attempt. - this.readBufferIndex = BufferLength; + // Ensure all underlying buffers have been flushed before we attempt to read the stream. + // User streams may have opted to throw from Flush if CanWrite is false + // (although the abstract Stream does not do so). + if (stream.CanWrite) + { + stream.Flush(); } - /// - /// Gets the length, in bytes, of the stream. - /// - public long Length { get; } + this.stream = stream; + this.Position = (int)stream.Position; + this.Length = stream.Length; - /// - /// Gets or sets the current position within the stream. - /// - public long Position - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.readerPosition; + this.readBuffer = ArrayPool.Shared.Rent(BufferLength); + this.readBufferHandle = new Memory(this.readBuffer).Pin(); + this.pinnedReadBuffer = (byte*)this.readBufferHandle.Pointer; - [MethodImpl(MethodImplOptions.NoInlining)] - set - { - // Only reset readBufferIndex if we are out of bounds of our working buffer - // otherwise we should simply move the value by the diff. - if (this.IsInReadBuffer(value, out long index)) - { - this.readBufferIndex = (int)index; - this.readerPosition = value; - } - else - { - // Base stream seek will throw for us if invalid. - this.stream.Seek(value, SeekOrigin.Begin); - this.readerPosition = value; - this.readBufferIndex = BufferLength; - } - } - } + // This triggers a full read on first attempt. + this.readBufferIndex = BufferLength; + } + /// + /// Gets the length, in bytes, of the stream. + /// + public long Length { get; } + + /// + /// Gets or sets the current position within the stream. + /// + public long Position + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadByte() + get => this.readerPosition; + + [MethodImpl(MethodImplOptions.NoInlining)] + set { - if (this.readerPosition >= this.Length) + // Only reset readBufferIndex if we are out of bounds of our working buffer + // otherwise we should simply move the value by the diff. + if (this.IsInReadBuffer(value, out long index)) { - return -1; + this.readBufferIndex = (int)index; + this.readerPosition = value; } - - // Our buffer has been read. - // We need to refill and start again. - if (this.readBufferIndex > MaxBufferIndex) + else { - this.FillReadBuffer(); + // Base stream seek will throw for us if invalid. + this.stream.Seek(value, SeekOrigin.Begin); + this.readerPosition = value; + this.readBufferIndex = BufferLength; } + } + } - this.readerPosition++; - return this.pinnedReadBuffer[this.readBufferIndex++]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadByte() + { + if (this.readerPosition >= this.Length) + { + return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Read(byte[] buffer, int offset, int count) + // Our buffer has been read. + // We need to refill and start again. + if (this.readBufferIndex > MaxBufferIndex) { - // Too big for our buffer. Read directly from the stream. - if (count > BufferLength) - { - return this.ReadToBufferDirectSlow(buffer, offset, count); - } + this.FillReadBuffer(); + } - // Too big for remaining buffer but less than entire buffer length - // Copy to buffer then read from there. - if (count + this.readBufferIndex > BufferLength) - { - return this.ReadToBufferViaCopySlow(buffer, offset, count); - } + this.readerPosition++; + return this.pinnedReadBuffer[this.readBufferIndex++]; + } - return this.ReadToBufferViaCopyFast(buffer, offset, count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(byte[] buffer, int offset, int count) + { + // Too big for our buffer. Read directly from the stream. + if (count > BufferLength) + { + return this.ReadToBufferDirectSlow(buffer, offset, count); } - public void Flush() + // Too big for remaining buffer but less than entire buffer length + // Copy to buffer then read from there. + if (count + this.readBufferIndex > BufferLength) { - // Reset the stream position to match reader position. - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - this.readerPosition = (int)this.stream.Position; - } - - // Reset to trigger full read on next attempt. - this.readBufferIndex = BufferLength; + return this.ReadToBufferViaCopySlow(buffer, offset, count); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Seek(long offset, SeekOrigin origin) + return this.ReadToBufferViaCopyFast(buffer, offset, count); + } + + public void Flush() + { + // Reset the stream position to match reader position. + if (this.readerPosition != this.stream.Position) { - switch (origin) - { - case SeekOrigin.Begin: - this.Position = offset; - break; + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); + this.readerPosition = (int)this.stream.Position; + } - case SeekOrigin.Current: - this.Position += offset; - break; + // Reset to trigger full read on next attempt. + this.readBufferIndex = BufferLength; + } - case SeekOrigin.End: - this.Position = this.Length - offset; - break; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + this.Position = offset; + break; - return this.readerPosition; + case SeekOrigin.Current: + this.Position += offset; + break; + + case SeekOrigin.End: + this.Position = this.Length - offset; + break; } - /// - public void Dispose() + return this.readerPosition; + } + + /// + public void Dispose() + { + if (!this.isDisposed) { - if (!this.isDisposed) - { - this.isDisposed = true; - this.readBufferHandle.Dispose(); - ArrayPool.Shared.Return(this.readBuffer); - this.Flush(); - } + this.isDisposed = true; + this.readBufferHandle.Dispose(); + ArrayPool.Shared.Return(this.readBuffer); + this.Flush(); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsInReadBuffer(long newPosition, out long index) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsInReadBuffer(long newPosition, out long index) + { + index = newPosition - this.readerPosition + this.readBufferIndex; + return index > -1 && index < BufferLength; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void FillReadBuffer() + { + if (this.readerPosition != this.stream.Position) { - index = newPosition - this.readerPosition + this.readBufferIndex; - return index > -1 && index < BufferLength; + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); } - [MethodImpl(MethodImplOptions.NoInlining)] - private void FillReadBuffer() - { - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - } + this.stream.Read(this.readBuffer, 0, BufferLength); + this.readBufferIndex = 0; + } - this.stream.Read(this.readBuffer, 0, BufferLength); - this.readBufferIndex = 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) + { + int n = this.GetCopyCount(count); + this.CopyBytes(buffer, offset, n); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopyFast(byte[] buffer, int offset, int count) - { - int n = this.GetCopyCount(count); - this.CopyBytes(buffer, offset, n); + this.readerPosition += n; + this.readBufferIndex += n; - this.readerPosition += n; - this.readBufferIndex += n; + return n; + } - return n; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) + { + // Refill our buffer then copy. + this.FillReadBuffer(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count) - { - // Refill our buffer then copy. - this.FillReadBuffer(); + return this.ReadToBufferViaCopyFast(buffer, offset, count); + } - return this.ReadToBufferViaCopyFast(buffer, offset, count); + [MethodImpl(MethodImplOptions.NoInlining)] + private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) + { + // Read to target but don't copy to our read buffer. + if (this.readerPosition != this.stream.Position) + { + this.stream.Seek(this.readerPosition, SeekOrigin.Begin); } - [MethodImpl(MethodImplOptions.NoInlining)] - private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count) - { - // Read to target but don't copy to our read buffer. - if (this.readerPosition != this.stream.Position) - { - this.stream.Seek(this.readerPosition, SeekOrigin.Begin); - } + int n = this.stream.Read(buffer, offset, count); + this.Position += n; - int n = this.stream.Read(buffer, offset, count); - this.Position += n; + return n; + } - return n; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetCopyCount(int count) + { + long n = this.Length - this.readerPosition; + if (n > count) + { + return count; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetCopyCount(int count) + if (n < 0) { - long n = this.Length - this.readerPosition; - if (n > count) - { - return count; - } - - if (n < 0) - { - return 0; - } - - return (int)n; + return 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyBytes(byte[] buffer, int offset, int count) + return (int)n; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyBytes(byte[] buffer, int offset, int count) + { + // Same as MemoryStream. + if (count < 9) { - // Same as MemoryStream. - if (count < 9) - { - int byteCount = count; - int read = this.readBufferIndex; - byte* pinned = this.pinnedReadBuffer; - - while (--byteCount > -1) - { - buffer[offset + byteCount] = pinned[read + byteCount]; - } - } - else + int byteCount = count; + int read = this.readBufferIndex; + byte* pinned = this.pinnedReadBuffer; + + while (--byteCount > -1) { - Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); + buffer[offset + byteCount] = pinned[read + byteCount]; } } + else + { + Buffer.BlockCopy(this.readBuffer, this.readBufferIndex, buffer, offset, count); + } } } diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs index bf91d0f1cb..2a926d1cd8 100644 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs @@ -1,261 +1,258 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Benchmarks.IO +namespace SixLabors.ImageSharp.Benchmarks.IO; + +[Config(typeof(Config.ShortMultiFramework))] +public class BufferedStreams { - [Config(typeof(Config.ShortMultiFramework))] - public class BufferedStreams + private readonly byte[] buffer = CreateTestBytes(); + private readonly byte[] chunk1 = new byte[2]; + private readonly byte[] chunk2 = new byte[2]; + + private MemoryStream stream1; + private MemoryStream stream2; + private MemoryStream stream3; + private MemoryStream stream4; + private MemoryStream stream5; + private MemoryStream stream6; + private ChunkedMemoryStream chunkedMemoryStream1; + private ChunkedMemoryStream chunkedMemoryStream2; + private BufferedReadStream bufferedStream1; + private BufferedReadStream bufferedStream2; + private BufferedReadStream bufferedStream3; + private BufferedReadStream bufferedStream4; + private BufferedReadStreamWrapper bufferedStreamWrap1; + private BufferedReadStreamWrapper bufferedStreamWrap2; + + [GlobalSetup] + public void CreateStreams() { - private readonly byte[] buffer = CreateTestBytes(); - private readonly byte[] chunk1 = new byte[2]; - private readonly byte[] chunk2 = new byte[2]; - - private MemoryStream stream1; - private MemoryStream stream2; - private MemoryStream stream3; - private MemoryStream stream4; - private MemoryStream stream5; - private MemoryStream stream6; - private ChunkedMemoryStream chunkedMemoryStream1; - private ChunkedMemoryStream chunkedMemoryStream2; - private BufferedReadStream bufferedStream1; - private BufferedReadStream bufferedStream2; - private BufferedReadStream bufferedStream3; - private BufferedReadStream bufferedStream4; - private BufferedReadStreamWrapper bufferedStreamWrap1; - private BufferedReadStreamWrapper bufferedStreamWrap2; - - [GlobalSetup] - public void CreateStreams() - { - this.stream1 = new MemoryStream(this.buffer); - this.stream2 = new MemoryStream(this.buffer); - this.stream3 = new MemoryStream(this.buffer); - this.stream4 = new MemoryStream(this.buffer); - this.stream5 = new MemoryStream(this.buffer); - this.stream6 = new MemoryStream(this.buffer); - this.stream6 = new MemoryStream(this.buffer); - - this.chunkedMemoryStream1 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); - this.chunkedMemoryStream1.Write(this.buffer); - this.chunkedMemoryStream1.Position = 0; - - this.chunkedMemoryStream2 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); - this.chunkedMemoryStream2.Write(this.buffer); - this.chunkedMemoryStream2.Position = 0; - - this.bufferedStream1 = new BufferedReadStream(Configuration.Default, this.stream3); - this.bufferedStream2 = new BufferedReadStream(Configuration.Default, this.stream4); - this.bufferedStream3 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream1); - this.bufferedStream4 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream2); - this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5); - this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6); - } - - [GlobalCleanup] - public void DestroyStreams() - { - this.bufferedStream1?.Dispose(); - this.bufferedStream2?.Dispose(); - this.bufferedStream3?.Dispose(); - this.bufferedStream4?.Dispose(); - this.bufferedStreamWrap1?.Dispose(); - this.bufferedStreamWrap2?.Dispose(); - this.chunkedMemoryStream1?.Dispose(); - this.chunkedMemoryStream2?.Dispose(); - this.stream1?.Dispose(); - this.stream2?.Dispose(); - this.stream3?.Dispose(); - this.stream4?.Dispose(); - this.stream5?.Dispose(); - this.stream6?.Dispose(); - } - - [Benchmark] - public int StandardStreamRead() - { - int r = 0; - Stream stream = this.stream1; - byte[] b = this.chunk1; + this.stream1 = new MemoryStream(this.buffer); + this.stream2 = new MemoryStream(this.buffer); + this.stream3 = new MemoryStream(this.buffer); + this.stream4 = new MemoryStream(this.buffer); + this.stream5 = new MemoryStream(this.buffer); + this.stream6 = new MemoryStream(this.buffer); + this.stream6 = new MemoryStream(this.buffer); + + this.chunkedMemoryStream1 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); + this.chunkedMemoryStream1.Write(this.buffer); + this.chunkedMemoryStream1.Position = 0; + + this.chunkedMemoryStream2 = new ChunkedMemoryStream(Configuration.Default.MemoryAllocator); + this.chunkedMemoryStream2.Write(this.buffer); + this.chunkedMemoryStream2.Position = 0; + + this.bufferedStream1 = new BufferedReadStream(Configuration.Default, this.stream3); + this.bufferedStream2 = new BufferedReadStream(Configuration.Default, this.stream4); + this.bufferedStream3 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream1); + this.bufferedStream4 = new BufferedReadStream(Configuration.Default, this.chunkedMemoryStream2); + this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5); + this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6); + } - for (int i = 0; i < stream.Length / 2; i++) - { - r += stream.Read(b, 0, 2); - } + [GlobalCleanup] + public void DestroyStreams() + { + this.bufferedStream1?.Dispose(); + this.bufferedStream2?.Dispose(); + this.bufferedStream3?.Dispose(); + this.bufferedStream4?.Dispose(); + this.bufferedStreamWrap1?.Dispose(); + this.bufferedStreamWrap2?.Dispose(); + this.chunkedMemoryStream1?.Dispose(); + this.chunkedMemoryStream2?.Dispose(); + this.stream1?.Dispose(); + this.stream2?.Dispose(); + this.stream3?.Dispose(); + this.stream4?.Dispose(); + this.stream5?.Dispose(); + this.stream6?.Dispose(); + } - return r; - } + [Benchmark] + public int StandardStreamRead() + { + int r = 0; + Stream stream = this.stream1; + byte[] b = this.chunk1; - [Benchmark] - public int BufferedReadStreamRead() + for (int i = 0; i < stream.Length / 2; i++) { - int r = 0; - BufferedReadStream reader = this.bufferedStream1; - byte[] b = this.chunk2; + r += stream.Read(b, 0, 2); + } - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamRead() + { + int r = 0; + BufferedReadStream reader = this.bufferedStream1; + byte[] b = this.chunk2; - [Benchmark] - public int BufferedReadStreamChunkedRead() + for (int i = 0; i < reader.Length / 2; i++) { - int r = 0; - BufferedReadStream reader = this.bufferedStream3; - byte[] b = this.chunk2; + r += reader.Read(b, 0, 2); + } - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamChunkedRead() + { + int r = 0; + BufferedReadStream reader = this.bufferedStream3; + byte[] b = this.chunk2; - [Benchmark] - public int BufferedReadStreamWrapRead() + for (int i = 0; i < reader.Length / 2; i++) { - int r = 0; - BufferedReadStreamWrapper reader = this.bufferedStreamWrap1; - byte[] b = this.chunk2; + r += reader.Read(b, 0, 2); + } - for (int i = 0; i < reader.Length / 2; i++) - { - r += reader.Read(b, 0, 2); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamWrapRead() + { + int r = 0; + BufferedReadStreamWrapper reader = this.bufferedStreamWrap1; + byte[] b = this.chunk2; - [Benchmark(Baseline = true)] - public int StandardStreamReadByte() + for (int i = 0; i < reader.Length / 2; i++) { - int r = 0; - Stream stream = this.stream2; + r += reader.Read(b, 0, 2); + } - for (int i = 0; i < stream.Length; i++) - { - r += stream.ReadByte(); - } + return r; + } - return r; - } + [Benchmark(Baseline = true)] + public int StandardStreamReadByte() + { + int r = 0; + Stream stream = this.stream2; - [Benchmark] - public int BufferedReadStreamReadByte() + for (int i = 0; i < stream.Length; i++) { - int r = 0; - BufferedReadStream reader = this.bufferedStream2; + r += stream.ReadByte(); + } - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamReadByte() + { + int r = 0; + BufferedReadStream reader = this.bufferedStream2; - [Benchmark] - public int BufferedReadStreamChunkedReadByte() + for (int i = 0; i < reader.Length; i++) { - int r = 0; - BufferedReadStream reader = this.bufferedStream4; + r += reader.ReadByte(); + } - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamChunkedReadByte() + { + int r = 0; + BufferedReadStream reader = this.bufferedStream4; - [Benchmark] - public int BufferedReadStreamWrapReadByte() + for (int i = 0; i < reader.Length; i++) { - int r = 0; - BufferedReadStreamWrapper reader = this.bufferedStreamWrap2; + r += reader.ReadByte(); + } - for (int i = 0; i < reader.Length; i++) - { - r += reader.ReadByte(); - } + return r; + } - return r; - } + [Benchmark] + public int BufferedReadStreamWrapReadByte() + { + int r = 0; + BufferedReadStreamWrapper reader = this.bufferedStreamWrap2; - [Benchmark] - public int ArrayReadByte() + for (int i = 0; i < reader.Length; i++) { - byte[] b = this.buffer; - int r = 0; - for (int i = 0; i < b.Length; i++) - { - r += b[i]; - } - - return r; + r += reader.ReadByte(); } - private static byte[] CreateTestBytes() - { - var buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3]; - var random = new Random(); - random.NextBytes(buffer); + return r; + } - return buffer; + [Benchmark] + public int ArrayReadByte() + { + byte[] b = this.buffer; + int r = 0; + for (int i = 0; i < b.Length; i++) + { + r += b[i]; } + + return r; } - /* - BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1) - Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores - .NET Core SDK=3.1.401 - [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT - Job-OKZLUV : .NET Framework 4.8 (4.8.4084.0), X64 RyuJIT - Job-CPYMXV : .NET Core 2.1.21 (CoreCLR 4.6.29130.01, CoreFX 4.6.29130.02), X64 RyuJIT - Job-BSGVGU : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT - - IterationCount=3 LaunchCount=1 WarmupCount=3 - - | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - |---------------------------------- |----------- |-------------- |-----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| - | StandardStreamRead | Job-OKZLUV | .NET 4.7.2 | 66.785 us | 15.768 us | 0.8643 us | 0.83 | 0.01 | - | - | - | - | - | BufferedReadStreamRead | Job-OKZLUV | .NET 4.7.2 | 97.389 us | 17.658 us | 0.9679 us | 1.21 | 0.01 | - | - | - | - | - | BufferedReadStreamChunkedRead | Job-OKZLUV | .NET 4.7.2 | 96.006 us | 16.286 us | 0.8927 us | 1.20 | 0.02 | - | - | - | - | - | BufferedReadStreamWrapRead | Job-OKZLUV | .NET 4.7.2 | 37.064 us | 14.640 us | 0.8024 us | 0.46 | 0.02 | - | - | - | - | - | StandardStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 80.315 us | 26.676 us | 1.4622 us | 1.00 | 0.00 | - | - | - | - | - | BufferedReadStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 118.706 us | 38.013 us | 2.0836 us | 1.48 | 0.00 | - | - | - | - | - | BufferedReadStreamChunkedReadByte | Job-OKZLUV | .NET 4.7.2 | 115.437 us | 33.352 us | 1.8282 us | 1.44 | 0.01 | - | - | - | - | - | BufferedReadStreamWrapReadByte | Job-OKZLUV | .NET 4.7.2 | 16.449 us | 11.400 us | 0.6249 us | 0.20 | 0.00 | - | - | - | - | - | ArrayReadByte | Job-OKZLUV | .NET 4.7.2 | 10.416 us | 1.866 us | 0.1023 us | 0.13 | 0.00 | - | - | - | - | - | | | | | | | | | | | | | - | StandardStreamRead | Job-CPYMXV | .NET Core 2.1 | 71.425 us | 50.441 us | 2.7648 us | 0.82 | 0.03 | - | - | - | - | - | BufferedReadStreamRead | Job-CPYMXV | .NET Core 2.1 | 32.816 us | 6.655 us | 0.3648 us | 0.38 | 0.01 | - | - | - | - | - | BufferedReadStreamChunkedRead | Job-CPYMXV | .NET Core 2.1 | 31.995 us | 7.751 us | 0.4249 us | 0.37 | 0.01 | - | - | - | - | - | BufferedReadStreamWrapRead | Job-CPYMXV | .NET Core 2.1 | 31.970 us | 4.170 us | 0.2286 us | 0.37 | 0.01 | - | - | - | - | - | StandardStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 86.909 us | 18.565 us | 1.0176 us | 1.00 | 0.00 | - | - | - | - | - | BufferedReadStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 14.596 us | 10.889 us | 0.5969 us | 0.17 | 0.01 | - | - | - | - | - | BufferedReadStreamChunkedReadByte | Job-CPYMXV | .NET Core 2.1 | 13.629 us | 1.569 us | 0.0860 us | 0.16 | 0.00 | - | - | - | - | - | BufferedReadStreamWrapReadByte | Job-CPYMXV | .NET Core 2.1 | 13.566 us | 1.743 us | 0.0956 us | 0.16 | 0.00 | - | - | - | - | - | ArrayReadByte | Job-CPYMXV | .NET Core 2.1 | 9.771 us | 6.658 us | 0.3650 us | 0.11 | 0.00 | - | - | - | - | - | | | | | | | | | | | | | - | StandardStreamRead | Job-BSGVGU | .NET Core 3.1 | 53.265 us | 65.819 us | 3.6078 us | 0.81 | 0.05 | - | - | - | - | - | BufferedReadStreamRead | Job-BSGVGU | .NET Core 3.1 | 33.163 us | 9.569 us | 0.5245 us | 0.51 | 0.01 | - | - | - | - | - | BufferedReadStreamChunkedRead | Job-BSGVGU | .NET Core 3.1 | 33.001 us | 6.114 us | 0.3351 us | 0.50 | 0.01 | - | - | - | - | - | BufferedReadStreamWrapRead | Job-BSGVGU | .NET Core 3.1 | 29.448 us | 7.120 us | 0.3902 us | 0.45 | 0.01 | - | - | - | - | - | StandardStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 65.619 us | 6.732 us | 0.3690 us | 1.00 | 0.00 | - | - | - | - | - | BufferedReadStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 13.989 us | 3.464 us | 0.1899 us | 0.21 | 0.00 | - | - | - | - | - | BufferedReadStreamChunkedReadByte | Job-BSGVGU | .NET Core 3.1 | 13.806 us | 1.710 us | 0.0938 us | 0.21 | 0.00 | - | - | - | - | - | BufferedReadStreamWrapReadByte | Job-BSGVGU | .NET Core 3.1 | 13.690 us | 1.523 us | 0.0835 us | 0.21 | 0.00 | - | - | - | - | - | ArrayReadByte | Job-BSGVGU | .NET Core 3.1 | 10.792 us | 8.228 us | 0.4510 us | 0.16 | 0.01 | - | - | - | - | - */ + private static byte[] CreateTestBytes() + { + var buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3]; + var random = new Random(); + random.NextBytes(buffer); + + return buffer; + } } + +/* +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1) +Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=3.1.401 + [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + Job-OKZLUV : .NET Framework 4.8 (4.8.4084.0), X64 RyuJIT + Job-CPYMXV : .NET Core 2.1.21 (CoreCLR 4.6.29130.01, CoreFX 4.6.29130.02), X64 RyuJIT + Job-BSGVGU : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT + +IterationCount=3 LaunchCount=1 WarmupCount=3 + +| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | +|---------------------------------- |----------- |-------------- |-----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| +| StandardStreamRead | Job-OKZLUV | .NET 4.7.2 | 66.785 us | 15.768 us | 0.8643 us | 0.83 | 0.01 | - | - | - | - | +| BufferedReadStreamRead | Job-OKZLUV | .NET 4.7.2 | 97.389 us | 17.658 us | 0.9679 us | 1.21 | 0.01 | - | - | - | - | +| BufferedReadStreamChunkedRead | Job-OKZLUV | .NET 4.7.2 | 96.006 us | 16.286 us | 0.8927 us | 1.20 | 0.02 | - | - | - | - | +| BufferedReadStreamWrapRead | Job-OKZLUV | .NET 4.7.2 | 37.064 us | 14.640 us | 0.8024 us | 0.46 | 0.02 | - | - | - | - | +| StandardStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 80.315 us | 26.676 us | 1.4622 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | Job-OKZLUV | .NET 4.7.2 | 118.706 us | 38.013 us | 2.0836 us | 1.48 | 0.00 | - | - | - | - | +| BufferedReadStreamChunkedReadByte | Job-OKZLUV | .NET 4.7.2 | 115.437 us | 33.352 us | 1.8282 us | 1.44 | 0.01 | - | - | - | - | +| BufferedReadStreamWrapReadByte | Job-OKZLUV | .NET 4.7.2 | 16.449 us | 11.400 us | 0.6249 us | 0.20 | 0.00 | - | - | - | - | +| ArrayReadByte | Job-OKZLUV | .NET 4.7.2 | 10.416 us | 1.866 us | 0.1023 us | 0.13 | 0.00 | - | - | - | - | +| | | | | | | | | | | | | +| StandardStreamRead | Job-CPYMXV | .NET Core 2.1 | 71.425 us | 50.441 us | 2.7648 us | 0.82 | 0.03 | - | - | - | - | +| BufferedReadStreamRead | Job-CPYMXV | .NET Core 2.1 | 32.816 us | 6.655 us | 0.3648 us | 0.38 | 0.01 | - | - | - | - | +| BufferedReadStreamChunkedRead | Job-CPYMXV | .NET Core 2.1 | 31.995 us | 7.751 us | 0.4249 us | 0.37 | 0.01 | - | - | - | - | +| BufferedReadStreamWrapRead | Job-CPYMXV | .NET Core 2.1 | 31.970 us | 4.170 us | 0.2286 us | 0.37 | 0.01 | - | - | - | - | +| StandardStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 86.909 us | 18.565 us | 1.0176 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | Job-CPYMXV | .NET Core 2.1 | 14.596 us | 10.889 us | 0.5969 us | 0.17 | 0.01 | - | - | - | - | +| BufferedReadStreamChunkedReadByte | Job-CPYMXV | .NET Core 2.1 | 13.629 us | 1.569 us | 0.0860 us | 0.16 | 0.00 | - | - | - | - | +| BufferedReadStreamWrapReadByte | Job-CPYMXV | .NET Core 2.1 | 13.566 us | 1.743 us | 0.0956 us | 0.16 | 0.00 | - | - | - | - | +| ArrayReadByte | Job-CPYMXV | .NET Core 2.1 | 9.771 us | 6.658 us | 0.3650 us | 0.11 | 0.00 | - | - | - | - | +| | | | | | | | | | | | | +| StandardStreamRead | Job-BSGVGU | .NET Core 3.1 | 53.265 us | 65.819 us | 3.6078 us | 0.81 | 0.05 | - | - | - | - | +| BufferedReadStreamRead | Job-BSGVGU | .NET Core 3.1 | 33.163 us | 9.569 us | 0.5245 us | 0.51 | 0.01 | - | - | - | - | +| BufferedReadStreamChunkedRead | Job-BSGVGU | .NET Core 3.1 | 33.001 us | 6.114 us | 0.3351 us | 0.50 | 0.01 | - | - | - | - | +| BufferedReadStreamWrapRead | Job-BSGVGU | .NET Core 3.1 | 29.448 us | 7.120 us | 0.3902 us | 0.45 | 0.01 | - | - | - | - | +| StandardStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 65.619 us | 6.732 us | 0.3690 us | 1.00 | 0.00 | - | - | - | - | +| BufferedReadStreamReadByte | Job-BSGVGU | .NET Core 3.1 | 13.989 us | 3.464 us | 0.1899 us | 0.21 | 0.00 | - | - | - | - | +| BufferedReadStreamChunkedReadByte | Job-BSGVGU | .NET Core 3.1 | 13.806 us | 1.710 us | 0.0938 us | 0.21 | 0.00 | - | - | - | - | +| BufferedReadStreamWrapReadByte | Job-BSGVGU | .NET Core 3.1 | 13.690 us | 1.523 us | 0.0835 us | 0.21 | 0.00 | - | - | - | - | +| ArrayReadByte | Job-BSGVGU | .NET Core 3.1 | 10.792 us | 8.228 us | 0.4510 us | 0.16 | 0.01 | - | - | - | - | +*/ diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs index 484eaf30d1..8820406af6 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs @@ -4,27 +4,26 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public interface ITestPixel + where T : struct, ITestPixel { - public interface ITestPixel - where T : struct, ITestPixel - { - void FromRgba32(Rgba32 source); + void FromRgba32(Rgba32 source); - void FromRgba32(ref Rgba32 source); + void FromRgba32(ref Rgba32 source); - void FromBytes(byte r, byte g, byte b, byte a); + void FromBytes(byte r, byte g, byte b, byte a); - void FromVector4(Vector4 source); + void FromVector4(Vector4 source); - void FromVector4(ref Vector4 source); + void FromVector4(ref Vector4 source); - Rgba32 ToRgba32(); + Rgba32 ToRgba32(); - void CopyToRgba32(ref Rgba32 dest); + void CopyToRgba32(ref Rgba32 dest); - Vector4 ToVector4(); + Vector4 ToVector4(); - void CopyToVector4(ref Vector4 dest); - } + void CopyToVector4(ref Vector4 dest); } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs index 1dcacd708e..d418c45fe3 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; @@ -10,184 +9,183 @@ using SixLabors.ImageSharp.PixelFormats.Utils; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public abstract class PixelConversion_ConvertFromRgba32 { - public abstract class PixelConversion_ConvertFromRgba32 + internal struct ConversionRunner + where T : struct, ITestPixel { - 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]; - } + public ConversionRunner(int count) + { + this.Dest = new T[count]; + this.Source = new Rgba32[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByRefConversion() - { - int count = this.Dest.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByRefConversion() + { + 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++) - { - Unsafe.Add(ref destBaseRef, i).FromRgba32(ref Unsafe.Add(ref sourceBaseRef, i)); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromRgba32(ref Unsafe.Add(ref sourceBaseRef, i)); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByValConversion() - { - int count = this.Dest.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByValConversion() + { + 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++) - { - Unsafe.Add(ref destBaseRef, i).FromRgba32(Unsafe.Add(ref sourceBaseRef, i)); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i).FromRgba32(Unsafe.Add(ref sourceBaseRef, i)); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunFromBytesConversion() - { - int count = this.Dest.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunFromBytesConversion() + { + 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++) - { - ref Rgba32 s = ref Unsafe.Add(ref sourceBaseRef, i); - Unsafe.Add(ref destBaseRef, i).FromBytes(s.R, s.G, s.B, s.A); - } + for (int i = 0; i < count; i++) + { + ref Rgba32 s = ref Unsafe.Add(ref sourceBaseRef, i); + Unsafe.Add(ref destBaseRef, i).FromBytes(s.R, s.G, s.B, s.A); } } + } - internal ConversionRunner CompatibleMemLayoutRunner; + internal ConversionRunner CompatibleMemLayoutRunner; - internal ConversionRunner PermutedRunnerRgbaToArgb; + internal ConversionRunner PermutedRunnerRgbaToArgb; - [Params(256, 2048)] - public int Count { get; set; } + [Params(256, 2048)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); - this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); - } + [GlobalSetup] + public void Setup() + { + this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); + this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); } +} - public class PixelConversion_ConvertFromRgba32_Compatible : PixelConversion_ConvertFromRgba32 +public class PixelConversion_ConvertFromRgba32_Compatible : PixelConversion_ConvertFromRgba32 +{ + [Benchmark(Baseline = true)] + public void ByRef() { - [Benchmark(Baseline = true)] - public void ByRef() - { - this.CompatibleMemLayoutRunner.RunByRefConversion(); - } - - [Benchmark] - public void ByVal() - { - this.CompatibleMemLayoutRunner.RunByValConversion(); - } - - [Benchmark] - public void FromBytes() - { - 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]); + this.CompatibleMemLayoutRunner.RunByRefConversion(); + } - for (int i = 0; i < this.Count; i++) - { - Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, i); - } - } + [Benchmark] + public void ByVal() + { + this.CompatibleMemLayoutRunner.RunByValConversion(); + } - /* 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 | */ + [Benchmark] + public void FromBytes() + { + this.CompatibleMemLayoutRunner.RunFromBytesConversion(); } - public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 + [Benchmark] + public void Inline() { - [Benchmark(Baseline = true)] - public void ByRef() - { - this.PermutedRunnerRgbaToArgb.RunByRefConversion(); - } + ref Rgba32 sBase = ref this.CompatibleMemLayoutRunner.Source[0]; + ref Rgba32 dBase = ref Unsafe.As(ref this.CompatibleMemLayoutRunner.Dest[0]); - [Benchmark] - public void ByVal() + for (int i = 0; i < this.Count; i++) { - this.PermutedRunnerRgbaToArgb.RunByValConversion(); + Unsafe.Add(ref dBase, i) = Unsafe.Add(ref sBase, i); } + } - [Benchmark] - public void FromBytes() - { - this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); - } + /* 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 | */ +} - [Benchmark] - public void InlineShuffle() - { - ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; - ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Dest[0]; +public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 +{ + [Benchmark(Baseline = true)] + public void ByRef() + { + this.PermutedRunnerRgbaToArgb.RunByRefConversion(); + } - for (int i = 0; i < this.Count; i++) - { - Rgba32 s = Unsafe.Add(ref sBase, i); - ref TestArgb d = ref Unsafe.Add(ref dBase, i); + [Benchmark] + public void ByVal() + { + this.PermutedRunnerRgbaToArgb.RunByValConversion(); + } - d.R = s.R; - d.G = s.G; - d.B = s.B; - d.A = s.A; - } - } + [Benchmark] + public void FromBytes() + { + this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); + } + + [Benchmark] + public void InlineShuffle() + { + ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; + ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Dest[0]; - [Benchmark] - public void PixelConverter_Rgba32_ToArgb32() + for (int i = 0; i < this.Count; i++) { - Span source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); - Span dest = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Dest); + Rgba32 s = Unsafe.Add(ref sBase, i); + ref TestArgb d = ref Unsafe.Add(ref dBase, i); - PixelConverter.FromRgba32.ToArgb32(source, dest); + d.R = s.R; + d.G = s.G; + d.B = s.B; + d.A = s.A; } + } - /* - RESULTS: - | Method | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | - |------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:| - | ByRef | 256 | 288.84 ns | 19.601 ns | 52.319 ns | 268.10 ns | 1.00 | 0.00 | - | ByVal | 256 | 267.97 ns | 1.831 ns | 1.713 ns | 267.85 ns | 0.77 | 0.18 | - | FromBytes | 256 | 266.81 ns | 2.427 ns | 2.270 ns | 266.47 ns | 0.76 | 0.18 | - | InlineShuffle | 256 | 291.41 ns | 5.820 ns | 5.444 ns | 290.17 ns | 0.83 | 0.19 | - | PixelConverter_Rgba32_ToArgb32 | 256 | 38.62 ns | 0.431 ns | 0.403 ns | 38.68 ns | 0.11 | 0.03 | - | | | | | | | | | - | ByRef | 2048 | 2,197.69 ns | 15.826 ns | 14.804 ns | 2,197.25 ns | 1.00 | 0.00 | - | ByVal | 2048 | 2,226.81 ns | 44.266 ns | 62.054 ns | 2,197.17 ns | 1.03 | 0.04 | - | FromBytes | 2048 | 2,181.35 ns | 18.033 ns | 16.868 ns | 2,185.97 ns | 0.99 | 0.01 | - | InlineShuffle | 2048 | 2,233.10 ns | 27.673 ns | 24.531 ns | 2,229.78 ns | 1.02 | 0.01 | - | PixelConverter_Rgba32_ToArgb32 | 2048 | 139.90 ns | 2.152 ns | 3.825 ns | 138.70 ns | 0.06 | 0.00 | - */ + [Benchmark] + public void PixelConverter_Rgba32_ToArgb32() + { + Span source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); + Span dest = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Dest); + + PixelConverter.FromRgba32.ToArgb32(source, dest); } + + /* + RESULTS: + | Method | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | + |------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:| + | ByRef | 256 | 288.84 ns | 19.601 ns | 52.319 ns | 268.10 ns | 1.00 | 0.00 | + | ByVal | 256 | 267.97 ns | 1.831 ns | 1.713 ns | 267.85 ns | 0.77 | 0.18 | + | FromBytes | 256 | 266.81 ns | 2.427 ns | 2.270 ns | 266.47 ns | 0.76 | 0.18 | + | InlineShuffle | 256 | 291.41 ns | 5.820 ns | 5.444 ns | 290.17 ns | 0.83 | 0.19 | + | PixelConverter_Rgba32_ToArgb32 | 256 | 38.62 ns | 0.431 ns | 0.403 ns | 38.68 ns | 0.11 | 0.03 | + | | | | | | | | | + | ByRef | 2048 | 2,197.69 ns | 15.826 ns | 14.804 ns | 2,197.25 ns | 1.00 | 0.00 | + | ByVal | 2048 | 2,226.81 ns | 44.266 ns | 62.054 ns | 2,197.17 ns | 1.03 | 0.04 | + | FromBytes | 2048 | 2,181.35 ns | 18.033 ns | 16.868 ns | 2,185.97 ns | 0.99 | 0.01 | + | InlineShuffle | 2048 | 2,233.10 ns | 27.673 ns | 24.531 ns | 2,229.78 ns | 1.02 | 0.01 | + | PixelConverter_Rgba32_ToArgb32 | 2048 | 139.90 ns | 2.152 ns | 3.825 ns | 138.70 ns | 0.06 | 0.00 | + */ } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs index de0e32e8b2..a167ade12b 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs @@ -9,138 +9,137 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public class PixelConversion_ConvertFromVector4 { - public class PixelConversion_ConvertFromVector4 + [StructLayout(LayoutKind.Sequential)] + private struct TestRgbaVector : ITestPixel { - [StructLayout(LayoutKind.Sequential)] - private struct TestRgbaVector : ITestPixel + private Vector4 v; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 p) { - private Vector4 v; + this.v = p; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 p) - { - this.v = p; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(ref Vector4 p) + { + this.v = p; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(ref Vector4 p) - { - this.v = p; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() => this.v; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() => this.v; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + dest = this.v; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToVector4(ref Vector4 dest) - { - dest = this.v; - } + 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 void FromRgba32(Rgba32 source) => throw new System.NotImplementedException(); + public Rgba32 ToRgba32() => throw new System.NotImplementedException(); - public void FromRgba32(ref Rgba32 source) => throw new System.NotImplementedException(); + public void CopyToRgba32(ref Rgba32 dest) => throw new System.NotImplementedException(); + } - public void FromBytes(byte r, byte g, byte b, byte a) => throw new System.NotImplementedException(); + private struct ConversionRunner + where T : struct, ITestPixel + { + private T[] dest; - public Rgba32 ToRgba32() => throw new System.NotImplementedException(); + private Vector4[] source; - public void CopyToRgba32(ref Rgba32 dest) => throw new System.NotImplementedException(); + public ConversionRunner(int count) + { + this.dest = new T[count]; + this.source = new Vector4[count]; } - private struct ConversionRunner - where T : struct, ITestPixel + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByRefConversion() { - private T[] dest; + int count = this.dest.Length; - private Vector4[] source; + ref T destBaseRef = ref this.dest[0]; + ref Vector4 sourceBaseRef = ref this.source[0]; - public ConversionRunner(int count) + for (int i = 0; i < count; i++) { - this.dest = new T[count]; - this.source = new Vector4[count]; + Unsafe.Add(ref destBaseRef, i).FromVector4(ref Unsafe.Add(ref sourceBaseRef, i)); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByRefConversion() - { - int count = this.dest.Length; - - ref T destBaseRef = ref this.dest[0]; - ref Vector4 sourceBaseRef = ref this.source[0]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunByValConversion() + { + int count = this.dest.Length; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref destBaseRef, i).FromVector4(ref Unsafe.Add(ref sourceBaseRef, i)); - } - } + ref T destBaseRef = ref this.dest[0]; + ref Vector4 sourceBaseRef = ref this.source[0]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunByValConversion() + for (int i = 0; i < count; i++) { - int count = this.dest.Length; - - ref T destBaseRef = ref this.dest[0]; - ref Vector4 sourceBaseRef = ref this.source[0]; - - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref destBaseRef, i).FromVector4(Unsafe.Add(ref sourceBaseRef, i)); - } + Unsafe.Add(ref destBaseRef, i).FromVector4(Unsafe.Add(ref sourceBaseRef, i)); } } + } - private ConversionRunner nonVectorRunner; - - private ConversionRunner vectorRunner; + private ConversionRunner nonVectorRunner; - [Params(32)] - public int Count { get; set; } + private ConversionRunner vectorRunner; - [GlobalSetup] - public void Setup() - { - this.nonVectorRunner = new ConversionRunner(this.Count); - this.vectorRunner = new ConversionRunner(this.Count); - } + [Params(32)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public void VectorByRef() - { - this.vectorRunner.RunByRefConversion(); - } + [GlobalSetup] + public void Setup() + { + this.nonVectorRunner = new ConversionRunner(this.Count); + this.vectorRunner = new ConversionRunner(this.Count); + } - [Benchmark] - public void VectorByVal() - { - this.vectorRunner.RunByValConversion(); - } + [Benchmark(Baseline = true)] + public void VectorByRef() + { + this.vectorRunner.RunByRefConversion(); + } - [Benchmark] - public void NonVectorByRef() - { - this.nonVectorRunner.RunByRefConversion(); - } + [Benchmark] + public void VectorByVal() + { + this.vectorRunner.RunByValConversion(); + } - [Benchmark] - public void NonVectorByVal() - { - this.nonVectorRunner.RunByValConversion(); - } + [Benchmark] + public void NonVectorByRef() + { + this.nonVectorRunner.RunByRefConversion(); } - /* - * Results: - * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | - * --------------- |------ |----------- |---------- |------- |-------------- | - * VectorByRef | 32 | 23.6678 ns | 0.1141 ns | 1.00 | 0.00 | - * 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 - */ + [Benchmark] + public void NonVectorByVal() + { + this.nonVectorRunner.RunByValConversion(); + } } + +/* + * Results: + * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | + * --------------- |------ |----------- |---------- |------- |-------------- | + * VectorByRef | 32 | 23.6678 ns | 0.1141 ns | 1.00 | 0.00 | + * 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 + */ diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs index a9cd385056..cd8307614e 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -6,106 +6,105 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? +/// 1. Rgba32 ToRgba32(); +/// OR +/// 2. void CopyToRgba32(ref Rgba32 dest); +/// ? +/// +public class PixelConversion_ConvertToRgba32 { - /// - /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? - /// 1. Rgba32 ToRgba32(); - /// OR - /// 2. void CopyToRgba32(ref Rgba32 dest); - /// ? - /// - public class PixelConversion_ConvertToRgba32 + private struct ConversionRunner + where T : struct, ITestPixel { - private struct ConversionRunner - where T : struct, ITestPixel - { - private T[] source; + private T[] source; - private Rgba32[] dest; + private Rgba32[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Rgba32[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Rgba32[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref Unsafe.Add(ref destBaseRef, i)); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref Unsafe.Add(ref destBaseRef, i)); } } + } - private ConversionRunner compatibleMemoryLayoutRunner; - - private ConversionRunner permutedRunner; + private ConversionRunner compatibleMemoryLayoutRunner; - [Params(32)] - public int Count { get; set; } + private ConversionRunner permutedRunner; - [GlobalSetup] - public void Setup() - { - this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunner = new ConversionRunner(this.Count); - } + [Params(32)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public void CompatibleRetval() - { - this.compatibleMemoryLayoutRunner.RunRetvalConversion(); - } + [GlobalSetup] + public void Setup() + { + this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); + this.permutedRunner = new ConversionRunner(this.Count); + } - [Benchmark] - public void CompatibleCopyTo() - { - this.compatibleMemoryLayoutRunner.RunCopyToConversion(); - } + [Benchmark(Baseline = true)] + public void CompatibleRetval() + { + this.compatibleMemoryLayoutRunner.RunRetvalConversion(); + } - [Benchmark] - public void PermutedRetval() - { - this.permutedRunner.RunRetvalConversion(); - } + [Benchmark] + public void CompatibleCopyTo() + { + this.compatibleMemoryLayoutRunner.RunCopyToConversion(); + } - [Benchmark] - public void PermutedCopyTo() - { - this.permutedRunner.RunCopyToConversion(); - } + [Benchmark] + public void PermutedRetval() + { + this.permutedRunner.RunRetvalConversion(); } - /* - * Results: - * - * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | - * --------------- |------ |------------ |---------- |------- |-------------- | - * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | - * CompatibleCopyTo | 128 | 89.4112 ns | 2.2901 ns | 1.00 | 0.03 | - * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | - * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | - */ + [Benchmark] + public void PermutedCopyTo() + { + this.permutedRunner.RunCopyToConversion(); + } } + +/* + * Results: + * + * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | + * --------------- |------ |------------ |---------- |------- |-------------- | + * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | + * CompatibleCopyTo | 128 | 89.4112 ns | 2.2901 ns | 1.00 | 0.03 | + * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | + * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | + */ diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs index 40048ec21a..9ea0c04c77 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs @@ -6,110 +6,109 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation { - public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation + private struct ConversionRunner + where T : struct, ITestPixel { - private struct ConversionRunner - where T : struct, ITestPixel - { - private T[] source; + private T[] source; - private Rgba32[] dest; + private Rgba32[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Rgba32[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Rgba32[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - Rgba32 temp; + Rgba32 temp; - for (int i = 0; i < count; i++) - { - temp = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); + for (int i = 0; i < count; i++) + { + temp = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); - // manipulate pixel before saving to dest buffer: - temp.A = 0; + // manipulate pixel before saving to dest buffer: + temp.A = 0; - Unsafe.Add(ref destBaseRef, i) = temp; - } + Unsafe.Add(ref destBaseRef, i) = temp; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Rgba32 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; - Rgba32 temp = default; + Rgba32 temp = default; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref temp); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref temp); - // manipulate pixel before saving to dest buffer: - temp.A = 0; + // manipulate pixel before saving to dest buffer: + temp.A = 0; - Unsafe.Add(ref destBaseRef, i) = temp; - } + Unsafe.Add(ref destBaseRef, i) = temp; } } + } - private ConversionRunner compatibleMemoryLayoutRunner; - - private ConversionRunner permutedRunner; + private ConversionRunner compatibleMemoryLayoutRunner; - [Params(32)] - public int Count { get; set; } + private ConversionRunner permutedRunner; - [GlobalSetup] - public void Setup() - { - this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunner = new ConversionRunner(this.Count); - } + [Params(32)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public void CompatibleRetval() - { - this.compatibleMemoryLayoutRunner.RunRetvalConversion(); - } + [GlobalSetup] + public void Setup() + { + this.compatibleMemoryLayoutRunner = new ConversionRunner(this.Count); + this.permutedRunner = new ConversionRunner(this.Count); + } - [Benchmark] - public void CompatibleCopyTo() - { - this.compatibleMemoryLayoutRunner.RunCopyToConversion(); - } + [Benchmark(Baseline = true)] + public void CompatibleRetval() + { + this.compatibleMemoryLayoutRunner.RunRetvalConversion(); + } - [Benchmark] - public void PermutedRetval() - { - this.permutedRunner.RunRetvalConversion(); - } + [Benchmark] + public void CompatibleCopyTo() + { + this.compatibleMemoryLayoutRunner.RunCopyToConversion(); + } - [Benchmark] - public void PermutedCopyTo() - { - this.permutedRunner.RunCopyToConversion(); - } + [Benchmark] + public void PermutedRetval() + { + this.permutedRunner.RunRetvalConversion(); } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ----------------- |------ |----------:|----------:|----------:|-------:|---------:| - // CompatibleRetval | 32 | 53.05 ns | 0.1865 ns | 0.1557 ns | 1.00 | 0.00 | - // 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 | + [Benchmark] + public void PermutedCopyTo() + { + this.permutedRunner.RunCopyToConversion(); + } } + +// RESULTS: +// Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | +// ----------------- |------ |----------:|----------:|----------:|-------:|---------:| +// CompatibleRetval | 32 | 53.05 ns | 0.1865 ns | 0.1557 ns | 1.00 | 0.00 | +// 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 | diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs index 65afa2a2fe..c03560e106 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -5,79 +5,78 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public class PixelConversion_ConvertToVector4 { - public class PixelConversion_ConvertToVector4 + private struct ConversionRunner + where T : struct, ITestPixel { - private struct ConversionRunner - where T : struct, ITestPixel - { - private T[] source; + private T[] source; - private Vector4[] dest; + private Vector4[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Vector4[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Vector4[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref Unsafe.Add(ref destBaseRef, i)); - } + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref Unsafe.Add(ref destBaseRef, i)); } } + } - private ConversionRunner runner; - - [Params(32)] - public int Count { get; set; } + private ConversionRunner runner; - [GlobalSetup] - public void Setup() - { - this.runner = new ConversionRunner(this.Count); - } + [Params(32)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public void UseRetval() - { - this.runner.RunRetvalConversion(); - } + [GlobalSetup] + public void Setup() + { + this.runner = new ConversionRunner(this.Count); + } - [Benchmark] - public void UseCopyTo() - { - this.runner.RunCopyToConversion(); - } + [Benchmark(Baseline = true)] + public void UseRetval() + { + this.runner.RunRetvalConversion(); + } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // ---------- |------ |---------:|---------:|---------:|-------:| - // 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 | + [Benchmark] + public void UseCopyTo() + { + this.runner.RunCopyToConversion(); } + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // ---------- |------ |---------:|---------:|---------:|-------:| + // 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 | } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs index 61d5893692..462bbbf7b3 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs @@ -5,93 +5,92 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation { - public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation + private struct ConversionRunner + where T : struct, ITestPixel { - private struct ConversionRunner - where T : struct, ITestPixel - { - private T[] source; + private T[] source; - private Vector4[] dest; + private Vector4[] dest; - public ConversionRunner(int count) - { - this.source = new T[count]; - this.dest = new Vector4[count]; - } + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Vector4[count]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunRetvalConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - Vector4 temp; + Vector4 temp; - for (int i = 0; i < count; i++) - { - temp = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); + for (int i = 0; i < count; i++) + { + temp = Unsafe.Add(ref sourceBaseRef, i).ToVector4(); - // manipulate pixel before saving to dest buffer: - temp.W = 0; + // manipulate pixel before saving to dest buffer: + temp.W = 0; - Unsafe.Add(ref destBaseRef, i) = temp; - } + Unsafe.Add(ref destBaseRef, i) = temp; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RunCopyToConversion() - { - int count = this.source.Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; - ref T sourceBaseRef = ref this.source[0]; - ref Vector4 destBaseRef = ref this.dest[0]; + ref T sourceBaseRef = ref this.source[0]; + ref Vector4 destBaseRef = ref this.dest[0]; - Vector4 temp = default; + Vector4 temp = default; - for (int i = 0; i < count; i++) - { - Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref temp); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToVector4(ref temp); - // manipulate pixel before saving to dest buffer: - temp.W = 0; + // manipulate pixel before saving to dest buffer: + temp.W = 0; - Unsafe.Add(ref destBaseRef, i) = temp; - } + Unsafe.Add(ref destBaseRef, i) = temp; } } + } - private ConversionRunner runner; - - [Params(32)] - public int Count { get; set; } + private ConversionRunner runner; - [GlobalSetup] - public void Setup() - { - this.runner = new ConversionRunner(this.Count); - } + [Params(32)] + public int Count { get; set; } - [Benchmark(Baseline = true)] - public void UseRetval() - { - this.runner.RunRetvalConversion(); - } + [GlobalSetup] + public void Setup() + { + this.runner = new ConversionRunner(this.Count); + } - [Benchmark] - public void UseCopyTo() - { - this.runner.RunCopyToConversion(); - } + [Benchmark(Baseline = true)] + public void UseRetval() + { + this.runner.RunRetvalConversion(); + } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------- |------ |---------:|---------:|---------:|-------:|---------:| - // 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 | + [Benchmark] + public void UseCopyTo() + { + this.runner.RunCopyToConversion(); } + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // ---------- |------ |---------:|---------:|---------:|-------:|---------:| + // 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 | } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs index 252ff90589..86ac928af9 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_PackFromRgbPlanes.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -9,280 +8,279 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public unsafe class PixelConversion_PackFromRgbPlanes { - public unsafe class PixelConversion_PackFromRgbPlanes - { - private byte[] rBuf; - private byte[] gBuf; - private byte[] bBuf; - private Rgb24[] rgbBuf; - private Rgba32[] rgbaBuf; + private byte[] rBuf; + private byte[] gBuf; + private byte[] bBuf; + private Rgb24[] rgbBuf; + private Rgba32[] rgbaBuf; - private float[] rFloat; - private float[] gFloat; - private float[] bFloat; + private float[] rFloat; + private float[] gFloat; + private float[] bFloat; - private float[] rgbaFloat; + private float[] rgbaFloat; - [Params(1024)] - public int Count { get; set; } + [Params(1024)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.rBuf = new byte[this.Count]; - this.gBuf = new byte[this.Count]; - this.bBuf = new byte[this.Count]; - this.rgbBuf = new Rgb24[this.Count + 3]; // padded - this.rgbaBuf = new Rgba32[this.Count]; + [GlobalSetup] + public void Setup() + { + this.rBuf = new byte[this.Count]; + this.gBuf = new byte[this.Count]; + this.bBuf = new byte[this.Count]; + this.rgbBuf = new Rgb24[this.Count + 3]; // padded + this.rgbaBuf = new Rgba32[this.Count]; - this.rFloat = new float[this.Count]; - this.gFloat = new float[this.Count]; - this.bFloat = new float[this.Count]; + this.rFloat = new float[this.Count]; + this.gFloat = new float[this.Count]; + this.bFloat = new float[this.Count]; - this.rgbaFloat = new float[this.Count * 4]; - } + this.rgbaFloat = new float[this.Count * 4]; + } - // [Benchmark] - public void Rgb24_Scalar_PerElement_Pinned() + // [Benchmark] + public void Rgb24_Scalar_PerElement_Pinned() + { + fixed (byte* r = &this.rBuf[0]) { - fixed (byte* r = &this.rBuf[0]) + fixed (byte* g = &this.gBuf[0]) { - fixed (byte* g = &this.gBuf[0]) + fixed (byte* b = &this.bBuf[0]) { - fixed (byte* b = &this.bBuf[0]) + fixed (Rgb24* rgb = &this.rgbBuf[0]) { - fixed (Rgb24* rgb = &this.rgbBuf[0]) + for (int i = 0; i < this.Count; i++) { - for (int i = 0; i < this.Count; i++) - { - Rgb24* d = rgb + i; - d->R = r[i]; - d->G = g[i]; - d->B = b[i]; - } + Rgb24* d = rgb + i; + d->R = r[i]; + d->G = g[i]; + d->B = b[i]; } } } } } + } - [Benchmark] - public void Rgb24_Scalar_PerElement_Span() - { - Span r = this.rBuf; - Span g = this.rBuf; - Span b = this.rBuf; - Span rgb = this.rgbBuf; + [Benchmark] + public void Rgb24_Scalar_PerElement_Span() + { + Span r = this.rBuf; + Span g = this.rBuf; + Span b = this.rBuf; + Span rgb = this.rgbBuf; - for (int i = 0; i < r.Length; i++) - { - ref Rgb24 d = ref rgb[i]; - d.R = r[i]; - d.G = g[i]; - d.B = b[i]; - } + for (int i = 0; i < r.Length; i++) + { + ref Rgb24 d = ref rgb[i]; + d.R = r[i]; + d.G = g[i]; + d.B = b[i]; } + } - [Benchmark] - public void Rgb24_Scalar_PerElement_Unsafe() - { - ref byte r = ref this.rBuf[0]; - ref byte g = ref this.rBuf[0]; - ref byte b = ref this.rBuf[0]; - ref Rgb24 rgb = ref this.rgbBuf[0]; + [Benchmark] + public void Rgb24_Scalar_PerElement_Unsafe() + { + ref byte r = ref this.rBuf[0]; + ref byte g = ref this.rBuf[0]; + ref byte b = ref this.rBuf[0]; + ref Rgb24 rgb = ref this.rgbBuf[0]; - for (int i = 0; i < this.Count; i++) - { - ref Rgb24 d = ref Unsafe.Add(ref rgb, i); - d.R = Unsafe.Add(ref r, i); - d.G = Unsafe.Add(ref g, i); - d.B = Unsafe.Add(ref b, i); - } + for (int i = 0; i < this.Count; i++) + { + ref Rgb24 d = ref Unsafe.Add(ref rgb, i); + d.R = Unsafe.Add(ref r, i); + d.G = Unsafe.Add(ref g, i); + d.B = Unsafe.Add(ref b, i); } + } - [Benchmark] - public void Rgb24_Scalar_PerElement_Batched8() - { - ref Byte8 r = ref Unsafe.As(ref this.rBuf[0]); - ref Byte8 g = ref Unsafe.As(ref this.rBuf[0]); - ref Byte8 b = ref Unsafe.As(ref this.rBuf[0]); - ref Rgb24 rgb = ref this.rgbBuf[0]; + [Benchmark] + public void Rgb24_Scalar_PerElement_Batched8() + { + ref Byte8 r = ref Unsafe.As(ref this.rBuf[0]); + ref Byte8 g = ref Unsafe.As(ref this.rBuf[0]); + ref Byte8 b = ref Unsafe.As(ref this.rBuf[0]); + ref Rgb24 rgb = ref this.rgbBuf[0]; - int count = this.Count / 8; - for (int i = 0; i < count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 8); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - ref Rgb24 d4 = ref Unsafe.Add(ref d0, 4); - ref Rgb24 d5 = ref Unsafe.Add(ref d0, 5); - ref Rgb24 d6 = ref Unsafe.Add(ref d0, 6); - ref Rgb24 d7 = ref Unsafe.Add(ref d0, 7); - - ref Byte8 rr = ref Unsafe.Add(ref r, i); - ref Byte8 gg = ref Unsafe.Add(ref g, i); - ref Byte8 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - - d4.R = rr.V4; - d4.G = gg.V4; - d4.B = bb.V4; - - d5.R = rr.V5; - d5.G = gg.V5; - d5.B = bb.V5; - - d6.R = rr.V6; - d6.G = gg.V6; - d6.B = bb.V6; - - d7.R = rr.V7; - d7.G = gg.V7; - d7.B = bb.V7; - } + int count = this.Count / 8; + for (int i = 0; i < count; i++) + { + ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 8); + ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); + ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); + ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); + ref Rgb24 d4 = ref Unsafe.Add(ref d0, 4); + ref Rgb24 d5 = ref Unsafe.Add(ref d0, 5); + ref Rgb24 d6 = ref Unsafe.Add(ref d0, 6); + ref Rgb24 d7 = ref Unsafe.Add(ref d0, 7); + + ref Byte8 rr = ref Unsafe.Add(ref r, i); + ref Byte8 gg = ref Unsafe.Add(ref g, i); + ref Byte8 bb = ref Unsafe.Add(ref b, i); + + d0.R = rr.V0; + d0.G = gg.V0; + d0.B = bb.V0; + + d1.R = rr.V1; + d1.G = gg.V1; + d1.B = bb.V1; + + d2.R = rr.V2; + d2.G = gg.V2; + d2.B = bb.V2; + + d3.R = rr.V3; + d3.G = gg.V3; + d3.B = bb.V3; + + d4.R = rr.V4; + d4.G = gg.V4; + d4.B = bb.V4; + + d5.R = rr.V5; + d5.G = gg.V5; + d5.B = bb.V5; + + d6.R = rr.V6; + d6.G = gg.V6; + d6.B = bb.V6; + + d7.R = rr.V7; + d7.G = gg.V7; + d7.B = bb.V7; } + } - [Benchmark] - public void Rgb24_Scalar_PerElement_Batched4() - { - ref Byte4 r = ref Unsafe.As(ref this.rBuf[0]); - ref Byte4 g = ref Unsafe.As(ref this.rBuf[0]); - ref Byte4 b = ref Unsafe.As(ref this.rBuf[0]); - ref Rgb24 rgb = ref this.rgbBuf[0]; + [Benchmark] + public void Rgb24_Scalar_PerElement_Batched4() + { + ref Byte4 r = ref Unsafe.As(ref this.rBuf[0]); + ref Byte4 g = ref Unsafe.As(ref this.rBuf[0]); + ref Byte4 b = ref Unsafe.As(ref this.rBuf[0]); + ref Rgb24 rgb = ref this.rgbBuf[0]; - int count = this.Count / 4; - for (int i = 0; i < count; i++) - { - ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); - ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); - ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); - ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); - - ref Byte4 rr = ref Unsafe.Add(ref r, i); - ref Byte4 gg = ref Unsafe.Add(ref g, i); - ref Byte4 bb = ref Unsafe.Add(ref b, i); - - d0.R = rr.V0; - d0.G = gg.V0; - d0.B = bb.V0; - - d1.R = rr.V1; - d1.G = gg.V1; - d1.B = bb.V1; - - d2.R = rr.V2; - d2.G = gg.V2; - d2.B = bb.V2; - - d3.R = rr.V3; - d3.G = gg.V3; - d3.B = bb.V3; - } + int count = this.Count / 4; + for (int i = 0; i < count; i++) + { + ref Rgb24 d0 = ref Unsafe.Add(ref rgb, i * 4); + ref Rgb24 d1 = ref Unsafe.Add(ref d0, 1); + ref Rgb24 d2 = ref Unsafe.Add(ref d0, 2); + ref Rgb24 d3 = ref Unsafe.Add(ref d0, 3); + + ref Byte4 rr = ref Unsafe.Add(ref r, i); + ref Byte4 gg = ref Unsafe.Add(ref g, i); + ref Byte4 bb = ref Unsafe.Add(ref b, i); + + d0.R = rr.V0; + d0.G = gg.V0; + d0.B = bb.V0; + + d1.R = rr.V1; + d1.G = gg.V1; + d1.B = bb.V1; + + d2.R = rr.V2; + d2.G = gg.V2; + d2.B = bb.V2; + + d3.R = rr.V3; + d3.G = gg.V3; + d3.B = bb.V3; } + } - [Benchmark(Baseline = true)] - public void Rgba32_Avx2_Float() - { - ref Vector256 rBase = ref Unsafe.As>(ref this.rFloat[0]); - ref Vector256 gBase = ref Unsafe.As>(ref this.gFloat[0]); - ref Vector256 bBase = ref Unsafe.As>(ref this.bFloat[0]); - ref Vector256 resultBase = ref Unsafe.As>(ref this.rgbaFloat[0]); + [Benchmark(Baseline = true)] + public void Rgba32_Avx2_Float() + { + ref Vector256 rBase = ref Unsafe.As>(ref this.rFloat[0]); + ref Vector256 gBase = ref Unsafe.As>(ref this.gFloat[0]); + ref Vector256 bBase = ref Unsafe.As>(ref this.bFloat[0]); + ref Vector256 resultBase = ref Unsafe.As>(ref this.rgbaFloat[0]); - int count = this.Count / Vector256.Count; + int count = this.Count / Vector256.Count; - ref byte control = ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskEvenOdd8x32); - Vector256 vcontrol = Unsafe.As>(ref control); + ref byte control = ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskEvenOdd8x32); + Vector256 vcontrol = Unsafe.As>(ref control); - var va = Vector256.Create(1F); + var va = Vector256.Create(1F); - for (int i = 0; i < count; i++) - { - Vector256 r = Unsafe.Add(ref rBase, i); - Vector256 g = Unsafe.Add(ref gBase, i); - Vector256 b = Unsafe.Add(ref bBase, i); + for (int i = 0; i < count; i++) + { + Vector256 r = Unsafe.Add(ref rBase, i); + Vector256 g = Unsafe.Add(ref gBase, i); + Vector256 b = Unsafe.Add(ref bBase, i); - r = Avx2.PermuteVar8x32(r, vcontrol); - g = Avx2.PermuteVar8x32(g, vcontrol); - b = Avx2.PermuteVar8x32(b, vcontrol); + r = Avx2.PermuteVar8x32(r, vcontrol); + g = Avx2.PermuteVar8x32(g, vcontrol); + b = Avx2.PermuteVar8x32(b, vcontrol); - Vector256 vte = Avx.UnpackLow(r, b); - Vector256 vto = Avx.UnpackLow(g, va); + Vector256 vte = Avx.UnpackLow(r, b); + Vector256 vto = Avx.UnpackLow(g, va); - ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4); + ref Vector256 destination = ref Unsafe.Add(ref resultBase, i * 4); - destination = Avx.UnpackLow(vte, vto); - Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto); + destination = Avx.UnpackLow(vte, vto); + Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto); - vte = Avx.UnpackHigh(r, b); - vto = Avx.UnpackHigh(g, va); + vte = Avx.UnpackHigh(r, b); + vto = Avx.UnpackHigh(g, va); - Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto); - Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto); - } + Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto); + Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto); } + } - [Benchmark] - public void Rgb24_Avx2_Bytes() - { - ReadOnlySpan r = this.rBuf; - ReadOnlySpan g = this.rBuf; - ReadOnlySpan b = this.rBuf; - Span rgb = this.rgbBuf; - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); - } + [Benchmark] + public void Rgb24_Avx2_Bytes() + { + ReadOnlySpan r = this.rBuf; + ReadOnlySpan g = this.rBuf; + ReadOnlySpan b = this.rBuf; + Span rgb = this.rgbBuf; + SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); + } - [Benchmark] - public void Rgba32_Avx2_Bytes() - { - ReadOnlySpan r = this.rBuf; - ReadOnlySpan g = this.rBuf; - ReadOnlySpan b = this.rBuf; - Span rgb = this.rgbaBuf; - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); - } + [Benchmark] + public void Rgba32_Avx2_Bytes() + { + ReadOnlySpan r = this.rBuf; + ReadOnlySpan g = this.rBuf; + ReadOnlySpan b = this.rBuf; + Span rgb = this.rgbaBuf; + SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref r, ref g, ref b, ref rgb); + } #pragma warning disable SA1132 - private struct Byte8 - { - public byte V0, V1, V2, V3, V4, V5, V6, V7; - } + private struct Byte8 + { + public byte V0, V1, V2, V3, V4, V5, V6, V7; + } - private struct Byte4 - { - public byte V0, V1, V2, V3; - } + private struct Byte4 + { + public byte V0, V1, V2, V3; + } #pragma warning restore - // Results @ Anton's PC, 2020 Dec 05 - // .NET Core 3.1.1 - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // - // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | - // |--------------------------------- |------ |-----------:|---------:|---------:|------:|--------:| - // | Rgb24_Scalar_PerElement_Span | 1024 | 1,634.6 ns | 26.56 ns | 24.84 ns | 3.12 | 0.05 | - // | Rgb24_Scalar_PerElement_Unsafe | 1024 | 1,284.7 ns | 4.70 ns | 4.16 ns | 2.46 | 0.01 | - // | Rgb24_Scalar_PerElement_Batched8 | 1024 | 1,182.3 ns | 5.12 ns | 4.27 ns | 2.26 | 0.01 | - // | Rgb24_Scalar_PerElement_Batched4 | 1024 | 1,146.2 ns | 16.38 ns | 14.52 ns | 2.19 | 0.02 | - // | Rgba32_Avx2_Float | 1024 | 522.7 ns | 1.78 ns | 1.39 ns | 1.00 | 0.00 | - // | Rgb24_Avx2_Bytes | 1024 | 243.3 ns | 1.56 ns | 1.30 ns | 0.47 | 0.00 | - // | Rgba32_Avx2_Bytes | 1024 | 146.0 ns | 2.48 ns | 2.32 ns | 0.28 | 0.01 | - } + // Results @ Anton's PC, 2020 Dec 05 + // .NET Core 3.1.1 + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | + // |--------------------------------- |------ |-----------:|---------:|---------:|------:|--------:| + // | Rgb24_Scalar_PerElement_Span | 1024 | 1,634.6 ns | 26.56 ns | 24.84 ns | 3.12 | 0.05 | + // | Rgb24_Scalar_PerElement_Unsafe | 1024 | 1,284.7 ns | 4.70 ns | 4.16 ns | 2.46 | 0.01 | + // | Rgb24_Scalar_PerElement_Batched8 | 1024 | 1,182.3 ns | 5.12 ns | 4.27 ns | 2.26 | 0.01 | + // | Rgb24_Scalar_PerElement_Batched4 | 1024 | 1,146.2 ns | 16.38 ns | 14.52 ns | 2.19 | 0.02 | + // | Rgba32_Avx2_Float | 1024 | 522.7 ns | 1.78 ns | 1.39 ns | 1.00 | 0.00 | + // | Rgb24_Avx2_Bytes | 1024 | 243.3 ns | 1.56 ns | 1.30 ns | 0.47 | 0.00 | + // | Rgba32_Avx2_Bytes | 1024 | 146.0 ns | 2.48 ns | 2.32 ns | 0.28 | 0.01 | } 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 98802e5331..9ae36cc8b7 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs @@ -1,179 +1,177 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +public class PixelConversion_Rgba32_To_Argb32 { - public class PixelConversion_Rgba32_To_Argb32 - { - private Rgba32[] source; + private Rgba32[] source; - private Argb32[] dest; + private Argb32[] dest; - [Params(64)] - public int Count { get; set; } + [Params(64)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.source = new Rgba32[this.Count]; - this.dest = new Argb32[this.Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new Rgba32[this.Count]; + this.dest = new Argb32[this.Count]; + } - [Benchmark(Baseline = true)] - public void Default() - { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.dest[0]; - - for (int i = 0; i < this.Count; i++) - { - Rgba32 s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i).FromRgba32(s); - } - } + [Benchmark(Baseline = true)] + public void Default() + { + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel + for (int i = 0; i < this.Count; i++) { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - - for (int i = 0; i < source.Length; i++) - { - Rgba32 s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i).FromRgba32(s); - } + Rgba32 s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Default_GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : unmanaged, IPixel + { + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - [Benchmark] - public void Default_Generic() + for (int i = 0; i < source.Length; i++) { - Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + Rgba32 s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); } + } + + [Benchmark] + public void Default_Generic() + { + Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + } + + [Benchmark] + public void Default_Group2() + { + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; - [Benchmark] - public void Default_Group2() + for (int i = 0; i < this.Count; i += 2) { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.dest[0]; - - 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); - - ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); - d0.FromRgba32(s0); - Unsafe.Add(ref d0, 1).FromRgba32(s1); - } + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + Rgba32 s1 = Unsafe.Add(ref s0, 1); + + ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); + d0.FromRgba32(s0); + Unsafe.Add(ref d0, 1).FromRgba32(s1); } + } + + [Benchmark] + public void Default_Group4() + { + ref Rgba32 sBase = ref this.source[0]; + ref Argb32 dBase = ref this.dest[0]; - [Benchmark] - public void Default_Group4() + for (int i = 0; i < this.Count; i += 4) { - ref Rgba32 sBase = ref this.source[0]; - ref Argb32 dBase = ref this.dest[0]; - - for (int i = 0; i < this.Count; i += 4) - { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); - ref Argb32 d1 = ref Unsafe.Add(ref d0, 1); - ref Argb32 d2 = ref Unsafe.Add(ref d1, 1); - - d0.FromRgba32(s0); - d1.FromRgba32(s1); - d2.FromRgba32(s2); - Unsafe.Add(ref d2, 1).FromRgba32(s3); - } + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref Argb32 d0 = ref Unsafe.Add(ref dBase, i); + ref Argb32 d1 = ref Unsafe.Add(ref d0, 1); + ref Argb32 d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); } + } + + [Benchmark] + public void BitOps() + { + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); - [Benchmark] - public void BitOps() + for (int i = 0; i < this.Count; i++) { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.dest[0]); - - for (int i = 0; i < this.Count; i++) - { - uint s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = FromRgba32.ToArgb32(s); - } + uint s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i) = FromRgba32.ToArgb32(s); } + } - [Benchmark] - public void BitOps_GroupAsULong() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.dest[0]); + [Benchmark] + public void BitOps_GroupAsULong() + { + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - for (int i = 0; i < this.Count / 2; i++) - { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); - lo = FromRgba32.ToArgb32(lo); - hi = FromRgba32.ToArgb32(hi); + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); + lo = FromRgba32.ToArgb32(lo); + hi = FromRgba32.ToArgb32(hi); - s = (ulong)(hi << 32) | lo; + s = (ulong)(hi << 32) | lo; - Unsafe.Add(ref dBase, i) = s; - } + Unsafe.Add(ref dBase, i) = s; } + } - public static class FromRgba32 + public static class FromRgba32 + { + /// + /// Converts a packed to . + /// + /// The argb value. + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedRgba) { - /// - /// Converts a packed to . - /// - /// The argb value. - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToArgb32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // ROL(8, packedRgba) = [bb gg rr aa] - return (packedRgba << 8) | (packedRgba >> 24); - } - - /// - /// Converts a packed to . - /// - /// The bgra value. - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToBgra32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // tmp1 = [aa 00 gg 00] - // tmp2 = [00 bb 00 rr] - // tmp3=ROL(16, tmp2) = [00 rr 00 bb] - // tmp1 + tmp3 = [aa rr gg bb] - uint tmp1 = packedRgba & 0xFF00FF00; - uint tmp2 = packedRgba & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - return tmp1 + tmp3; - } + // packedRgba = [aa bb gg rr] + // ROL(8, packedRgba) = [bb gg rr aa] + return (packedRgba << 8) | (packedRgba >> 24); } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | - // -------------------- |------ |----------:|----------:|----------:|-------:| - // Default | 64 | 107.33 ns | 1.0633 ns | 0.9426 ns | 1.00 | - // Default_Generic | 64 | 111.15 ns | 0.3789 ns | 0.3544 ns | 1.04 | - // Default_Group2 | 64 | 90.36 ns | 0.7779 ns | 0.6896 ns | 0.84 | - // Default_Group4 | 64 | 82.39 ns | 0.2726 ns | 0.2550 ns | 0.77 | - // 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 | + /// + /// Converts a packed to . + /// + /// The bgra value. + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 bb 00 rr] + // tmp3=ROL(16, tmp2) = [00 rr 00 bb] + // tmp1 + tmp3 = [aa rr gg bb] + uint tmp1 = packedRgba & 0xFF00FF00; + uint tmp2 = packedRgba & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } } + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | + // -------------------- |------ |----------:|----------:|----------:|-------:| + // Default | 64 | 107.33 ns | 1.0633 ns | 0.9426 ns | 1.00 | + // Default_Generic | 64 | 111.15 ns | 0.3789 ns | 0.3544 ns | 1.04 | + // Default_Group2 | 64 | 90.36 ns | 0.7779 ns | 0.6896 ns | 0.84 | + // Default_Group4 | 64 | 82.39 ns | 0.2726 ns | 0.2550 ns | 0.77 | + // 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 | } 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 580a0fd906..b7b9392379 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,389 +9,388 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tuples; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +// [MonoJob] +// [RyuJitX64Job] +public class PixelConversion_Rgba32_To_Bgra32 { - // [MonoJob] - // [RyuJitX64Job] - public class PixelConversion_Rgba32_To_Bgra32 - { - private Rgba32[] source; + private Rgba32[] source; - private Bgra32[] dest; + private Bgra32[] dest; - [StructLayout(LayoutKind.Sequential)] - private struct Tuple4OfUInt32 + [StructLayout(LayoutKind.Sequential)] + private struct Tuple4OfUInt32 + { + private uint v0; + private uint v1; + private uint v2; + private uint v3; + + public void ConvertMe() { - 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; } + [Params(64)] + public int Count { get; set; } - [GlobalSetup] - public void Setup() - { - this.source = new Rgba32[this.Count]; - this.dest = new Bgra32[this.Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new Rgba32[this.Count]; + this.dest = new Bgra32[this.Count]; + } - [Benchmark(Baseline = true)] - public void Default() - { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - - for (int i = 0; i < this.Count; i++) - { - ref Rgba32 s = ref Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i).FromRgba32(s); - } - } + [Benchmark(Baseline = true)] + public void Default() + { + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel + for (int i = 0; i < this.Count; i++) { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - - for (int i = 0; i < source.Length; i++) - { - ref Rgba32 s = ref Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i).FromRgba32(s); - } + ref Rgba32 s = ref Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); } + } - [Benchmark] - public void Default_Generic() - { - Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Default_GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : unmanaged, IPixel + { + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - [Benchmark] - public void Default_Group2() + for (int i = 0; i < source.Length; i++) { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - - 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); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - d0.FromRgba32(s0); - Unsafe.Add(ref d0, 1).FromRgba32(s1); - } + ref Rgba32 s = ref Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i).FromRgba32(s); } + } + + [Benchmark] + public void Default_Generic() + { + Default_GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + } - [Benchmark] - public void Default_Group4() + [Benchmark] + public void Default_Group2() + { + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count; i += 2) { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - - for (int i = 0; i < this.Count; i += 4) - { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); - ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); - - d0.FromRgba32(s0); - d1.FromRgba32(s1); - d2.FromRgba32(s2); - Unsafe.Add(ref d2, 1).FromRgba32(s3); - } + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + Rgba32 s1 = Unsafe.Add(ref s0, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + d0.FromRgba32(s0); + Unsafe.Add(ref d0, 1).FromRgba32(s1); } + } + + [Benchmark] + public void Default_Group4() + { + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Group4GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : unmanaged, IPixel + for (int i = 0; i < this.Count; i += 4) { - ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); - ref TPixel dBase = ref MemoryMarshal.GetReference(dest); - - for (int i = 0; i < source.Length; i += 4) - { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - Rgba32 s3 = Unsafe.Add(ref s2, 1); - - ref TPixel d0 = ref Unsafe.Add(ref dBase, i); - ref TPixel d1 = ref Unsafe.Add(ref d0, 1); - ref TPixel d2 = ref Unsafe.Add(ref d1, 1); - - d0.FromRgba32(s0); - d1.FromRgba32(s1); - d2.FromRgba32(s2); - Unsafe.Add(ref d2, 1).FromRgba32(s3); - } + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); + ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); } + } - // [Benchmark] - public void Default_Group4_Generic() + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Group4GenericImpl(ReadOnlySpan source, Span dest) + where TPixel : unmanaged, IPixel + { + ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); + ref TPixel dBase = ref MemoryMarshal.GetReference(dest); + + for (int i = 0; i < source.Length; i += 4) { - Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + Rgba32 s3 = Unsafe.Add(ref s2, 1); + + ref TPixel d0 = ref Unsafe.Add(ref dBase, i); + ref TPixel d1 = ref Unsafe.Add(ref d0, 1); + ref TPixel d2 = ref Unsafe.Add(ref d1, 1); + + d0.FromRgba32(s0); + d1.FromRgba32(s1); + d2.FromRgba32(s2); + Unsafe.Add(ref d2, 1).FromRgba32(s3); } + } - // [Benchmark] - public void Default_Group8() + // [Benchmark] + public void Default_Group4_Generic() + { + Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); + } + + // [Benchmark] + public void Default_Group8() + { + ref Rgba32 sBase = ref this.source[0]; + ref Bgra32 dBase = ref this.dest[0]; + + for (int i = 0; i < this.Count / 4; i += 4) { - ref Rgba32 sBase = ref this.source[0]; - ref Bgra32 dBase = ref this.dest[0]; - - for (int i = 0; i < this.Count / 4; i += 4) - { - ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); - ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); - ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); - ref Rgba32 s3 = ref Unsafe.Add(ref s1, 1); - - ref Rgba32 s4 = ref Unsafe.Add(ref s3, 1); - ref Rgba32 s5 = ref Unsafe.Add(ref s4, 1); - ref Rgba32 s6 = ref Unsafe.Add(ref s5, 1); - Rgba32 s7 = Unsafe.Add(ref s6, 1); - - ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); - ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); - ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); - ref Bgra32 d3 = ref Unsafe.Add(ref d2, 1); - ref Bgra32 d4 = ref Unsafe.Add(ref d3, 1); - - 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); - d3.FromRgba32(s3); - - d4.FromRgba32(s4); - d5.FromRgba32(s5); - d6.FromRgba32(s6); - Unsafe.Add(ref d6, 1).FromRgba32(s7); - } + ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); + ref Rgba32 s1 = ref Unsafe.Add(ref s0, 1); + ref Rgba32 s2 = ref Unsafe.Add(ref s1, 1); + ref Rgba32 s3 = ref Unsafe.Add(ref s1, 1); + + ref Rgba32 s4 = ref Unsafe.Add(ref s3, 1); + ref Rgba32 s5 = ref Unsafe.Add(ref s4, 1); + ref Rgba32 s6 = ref Unsafe.Add(ref s5, 1); + Rgba32 s7 = Unsafe.Add(ref s6, 1); + + ref Bgra32 d0 = ref Unsafe.Add(ref dBase, i); + ref Bgra32 d1 = ref Unsafe.Add(ref d0, 1); + ref Bgra32 d2 = ref Unsafe.Add(ref d1, 1); + ref Bgra32 d3 = ref Unsafe.Add(ref d2, 1); + ref Bgra32 d4 = ref Unsafe.Add(ref d3, 1); + + 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); + d3.FromRgba32(s3); + + d4.FromRgba32(s4); + d5.FromRgba32(s5); + d6.FromRgba32(s6); + Unsafe.Add(ref d6, 1).FromRgba32(s7); } + } + + [Benchmark] + public void BitOps() + { + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); - [Benchmark] - public void BitOps() + for (int i = 0; i < this.Count; i++) { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.dest[0]); - - for (int i = 0; i < this.Count; i++) - { - uint s = Unsafe.Add(ref sBase, i); - Unsafe.Add(ref dBase, i) = FromRgba32.ToBgra32(s); - } + uint s = Unsafe.Add(ref sBase, i); + Unsafe.Add(ref dBase, i) = FromRgba32.ToBgra32(s); } + } - [Benchmark] - public void Bitops_Tuple() + [Benchmark] + public void Bitops_Tuple() + { + ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); + ref Tuple4OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + + for (int i = 0; i < this.Count / 4; i++) { - ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - ref Tuple4OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); - - for (int i = 0; i < this.Count / 4; i++) - { - ref Tuple4OfUInt32 d = ref Unsafe.Add(ref dBase, i); - d = Unsafe.Add(ref sBase, i); - d.ConvertMe(); - } + ref Tuple4OfUInt32 d = ref Unsafe.Add(ref dBase, i); + d = Unsafe.Add(ref sBase, i); + d.ConvertMe(); } + } - // [Benchmark] - public void Bitops_SingleTuple() - { - ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); + // [Benchmark] + public void Bitops_SingleTuple() + { + ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - for (int i = 0; i < this.Count / 4; i++) - { - Unsafe.Add(ref sBase, i).ConvertMe(); - } + for (int i = 0; i < this.Count / 4; i++) + { + Unsafe.Add(ref sBase, i).ConvertMe(); } + } - // [Benchmark] - public void Bitops_Simd() - { - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + // [Benchmark] + public void Bitops_Simd() + { + 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++) - { - BitopsSimdImpl(ref Unsafe.Add(ref sBase, i), ref Unsafe.Add(ref dBase, i)); - } + for (int i = 0; i < this.Count / 8; i++) + { + BitopsSimdImpl(ref Unsafe.Add(ref sBase, i), ref Unsafe.Add(ref dBase, i)); } + } #pragma warning disable SA1132 // Do not combine fields - [StructLayout(LayoutKind.Sequential)] - private struct B - { - public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; - } + [StructLayout(LayoutKind.Sequential)] + private struct B + { + public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; + } - [StructLayout(LayoutKind.Sequential)] - private struct C - { - public uint Tmp3, Tmp6, Tmp9, Tmp12, Tmp15, Tmp18, Tmp21, Tmp24; - } + [StructLayout(LayoutKind.Sequential)] + private struct C + { + 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 s, ref Octet d) - { - Vector sVec = Unsafe.As, Vector>(ref s); - var aMask = new Vector(0xFF00FF00); - var bMask = new Vector(0x00FF00FF); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BitopsSimdImpl(ref Octet s, ref Octet d) + { + Vector sVec = Unsafe.As, Vector>(ref s); + var aMask = new Vector(0xFF00FF00); + var bMask = new Vector(0x00FF00FF); - Vector aa = sVec & aMask; - Vector bb = sVec & bMask; + Vector aa = sVec & aMask; + Vector bb = sVec & bMask; - B b = Unsafe.As, B>(ref bb); + B b = Unsafe.As, B>(ref bb); - C c = default; + 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; + Vector cc = Unsafe.As>(ref c); + Vector dd = aa + cc; - d = Unsafe.As, Octet>(ref dd); - } + d = Unsafe.As, Octet>(ref dd); + } + + // [Benchmark] + public void BitOps_Group2() + { + ref uint sBase = ref Unsafe.As(ref this.source[0]); + ref uint dBase = ref Unsafe.As(ref this.dest[0]); - // [Benchmark] - public void BitOps_Group2() + for (int i = 0; i < this.Count; i++) { - ref uint sBase = ref Unsafe.As(ref this.source[0]); - ref uint dBase = ref Unsafe.As(ref this.dest[0]); - - for (int i = 0; i < this.Count; i++) - { - ref uint s0 = ref Unsafe.Add(ref sBase, i); - uint s1 = Unsafe.Add(ref s0, 1); - - ref uint d0 = ref Unsafe.Add(ref dBase, i); - d0 = FromRgba32.ToBgra32(s0); - Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); - } + ref uint s0 = ref Unsafe.Add(ref sBase, i); + uint s1 = Unsafe.Add(ref s0, 1); + + ref uint d0 = ref Unsafe.Add(ref dBase, i); + d0 = FromRgba32.ToBgra32(s0); + Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); } + } - [Benchmark] - public void BitOps_GroupAsULong() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.dest[0]); + [Benchmark] + public void BitOps_GroupAsULong() + { + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - for (int i = 0; i < this.Count / 2; i++) - { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); - lo = FromRgba32.ToBgra32(lo); - hi = FromRgba32.ToBgra32(hi); + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); + lo = FromRgba32.ToBgra32(lo); + hi = FromRgba32.ToBgra32(hi); - s = (ulong)(hi << 32) | lo; + s = (ulong)(hi << 32) | lo; - Unsafe.Add(ref dBase, i) = s; - } + Unsafe.Add(ref dBase, i) = s; } + } - // [Benchmark] - public void BitOps_GroupAsULong_V2() - { - ref ulong sBase = ref Unsafe.As(ref this.source[0]); - ref ulong dBase = ref Unsafe.As(ref this.dest[0]); + // [Benchmark] + public void BitOps_GroupAsULong_V2() + { + ref ulong sBase = ref Unsafe.As(ref this.source[0]); + ref ulong dBase = ref Unsafe.As(ref this.dest[0]); - for (int i = 0; i < this.Count / 2; i++) - { - ulong s = Unsafe.Add(ref sBase, i); - uint lo = (uint)s; - uint hi = (uint)(s >> 32); + for (int i = 0; i < this.Count / 2; i++) + { + ulong s = Unsafe.Add(ref sBase, i); + uint lo = (uint)s; + uint hi = (uint)(s >> 32); - uint tmp1 = lo & 0xFF00FF00; - uint tmp4 = hi & 0xFF00FF00; + uint tmp1 = lo & 0xFF00FF00; + uint tmp4 = hi & 0xFF00FF00; - uint tmp2 = lo & 0x00FF00FF; - uint tmp5 = hi & 0x00FF00FF; + uint tmp2 = lo & 0x00FF00FF; + uint tmp5 = hi & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - uint tmp6 = (tmp5 << 16) | (tmp5 >> 16); + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + uint tmp6 = (tmp5 << 16) | (tmp5 >> 16); - lo = tmp1 + tmp3; - hi = tmp4 + tmp6; + lo = tmp1 + tmp3; + hi = tmp4 + tmp6; - s = (ulong)(hi << 32) | lo; + s = (ulong)(hi << 32) | lo; - Unsafe.Add(ref dBase, i) = s; - } + Unsafe.Add(ref dBase, i) = s; } + } - public static class FromRgba32 + public static class FromRgba32 + { + /// + /// Converts a packed to . + /// + /// The argb value. + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToArgb32(uint packedRgba) { - /// - /// Converts a packed to . - /// - /// The argb value. - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToArgb32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // ROL(8, packedRgba) = [bb gg rr aa] - return (packedRgba << 8) | (packedRgba >> 24); - } - - /// - /// Converts a packed to . - /// - /// The bgra value. - [MethodImpl(InliningOptions.ShortMethod)] - public static uint ToBgra32(uint packedRgba) - { - // packedRgba = [aa bb gg rr] - // tmp1 = [aa 00 gg 00] - // tmp2 = [00 bb 00 rr] - // tmp3=ROL(16, tmp2) = [00 rr 00 bb] - // tmp1 + tmp3 = [aa rr gg bb] - uint tmp1 = packedRgba & 0xFF00FF00; - uint tmp2 = packedRgba & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); - return tmp1 + tmp3; - } + // packedRgba = [aa bb gg rr] + // ROL(8, packedRgba) = [bb gg rr aa] + return (packedRgba << 8) | (packedRgba >> 24); } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| - // Default | 64 | 82.67 ns | 0.6737 ns | 0.5625 ns | 1.00 | 0.00 | - // Default_Generic | 64 | 88.73 ns | 1.7959 ns | 1.7638 ns | 1.07 | 0.02 | - // Default_Group2 | 64 | 91.03 ns | 1.5237 ns | 1.3508 ns | 1.10 | 0.02 | - // Default_Group4 | 64 | 86.62 ns | 1.5737 ns | 1.4720 ns | 1.05 | 0.02 | - // BitOps | 64 | 57.45 ns | 0.6067 ns | 0.5066 ns | 0.69 | 0.01 | - // Bitops_Tuple | 64 | 75.47 ns | 1.1824 ns | 1.1060 ns | 0.91 | 0.01 | - // BitOps_GroupAsULong | 64 | 65.42 ns | 0.7157 ns | 0.6695 ns | 0.79 | 0.01 | + /// + /// Converts a packed to . + /// + /// The bgra value. + [MethodImpl(InliningOptions.ShortMethod)] + public static uint ToBgra32(uint packedRgba) + { + // packedRgba = [aa bb gg rr] + // tmp1 = [aa 00 gg 00] + // tmp2 = [00 bb 00 rr] + // tmp3=ROL(16, tmp2) = [00 rr 00 bb] + // tmp1 + tmp3 = [aa rr gg bb] + uint tmp1 = packedRgba & 0xFF00FF00; + uint tmp2 = packedRgba & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + return tmp1 + tmp3; + } } + + // RESULTS: + // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| + // Default | 64 | 82.67 ns | 0.6737 ns | 0.5625 ns | 1.00 | 0.00 | + // Default_Generic | 64 | 88.73 ns | 1.7959 ns | 1.7638 ns | 1.07 | 0.02 | + // Default_Group2 | 64 | 91.03 ns | 1.5237 ns | 1.3508 ns | 1.10 | 0.02 | + // Default_Group4 | 64 | 86.62 ns | 1.5737 ns | 1.4720 ns | 1.05 | 0.02 | + // BitOps | 64 | 57.45 ns | 0.6067 ns | 0.5066 ns | 0.69 | 0.01 | + // Bitops_Tuple | 64 | 75.47 ns | 1.1824 ns | 1.1060 ns | 0.91 | 0.01 | + // BitOps_GroupAsULong | 64 | 65.42 ns | 0.7157 ns | 0.6695 ns | 0.79 | 0.01 | } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs index 6800788031..84698a0e19 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs @@ -6,89 +6,88 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +[StructLayout(LayoutKind.Sequential)] +public struct TestArgb : ITestPixel { - [StructLayout(LayoutKind.Sequential)] - public struct TestArgb : ITestPixel - { - public byte A; - public byte R; - public byte G; - public byte B; + public byte A; + public byte R; + public byte G; + public byte B; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 p) - { - this.R = p.R; - this.G = p.G; - this.B = p.B; - this.A = p.A; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 p) + { + this.R = p.R; + this.G = p.G; + this.B = p.B; + this.A = p.A; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(ref Rgba32 p) - { - this.R = p.R; - this.G = p.G; - this.B = p.B; - this.A = p.A; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(ref Rgba32 p) + { + this.R = p.R; + this.G = p.G; + this.B = p.B; + this.A = p.A; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBytes(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBytes(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(Vector4 p) - { - this.R = (byte)p.X; - this.G = (byte)p.Y; - this.B = (byte)p.Z; - this.A = (byte)p.W; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(Vector4 p) + { + this.R = (byte)p.X; + this.G = (byte)p.Y; + this.B = (byte)p.Z; + this.A = (byte)p.W; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromVector4(ref Vector4 p) - { - this.R = (byte)p.X; - this.G = (byte)p.Y; - this.B = (byte)p.Z; - this.A = (byte)p.W; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromVector4(ref Vector4 p) + { + this.R = (byte)p.X; + this.G = (byte)p.Y; + this.B = (byte)p.Z; + this.A = (byte)p.W; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ToRgba32() - { - return new Rgba32(this.R, this.G, this.B, this.A); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return new Rgba32(this.R, this.G, this.B, this.A); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToRgba32(ref Rgba32 dest) - { - dest.R = this.R; - dest.G = this.G; - dest.B = this.B; - dest.A = this.A; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4(this.R, this.G, this.B, this.A); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToVector4(ref Vector4 dest) - { - dest.X = this.R; - dest.Y = this.G; - dest.Z = this.B; - dest.W = this.A; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + dest.X = this.R; + dest.Y = this.G; + dest.Z = this.B; + dest.W = this.A; } } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs index a739635899..76449c9d95 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs @@ -6,71 +6,70 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +[StructLayout(LayoutKind.Sequential)] +public struct TestRgba : ITestPixel { - [StructLayout(LayoutKind.Sequential)] - public struct TestRgba : ITestPixel - { - public byte R; - public byte G; - public byte B; - public byte A; + public byte R; + public byte G; + public byte B; + public byte A; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(Rgba32 source) - { - this = Unsafe.As(ref source); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(Rgba32 source) + { + this = Unsafe.As(ref source); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromRgba32(ref Rgba32 source) - { - this = Unsafe.As(ref source); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromRgba32(ref Rgba32 source) + { + this = Unsafe.As(ref source); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FromBytes(byte r, byte g, byte b, byte a) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromBytes(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - public void FromVector4(Vector4 source) - { - throw new System.NotImplementedException(); - } + public void FromVector4(Vector4 source) + { + throw new System.NotImplementedException(); + } - public void FromVector4(ref Vector4 source) - { - throw new System.NotImplementedException(); - } + public void FromVector4(ref Vector4 source) + { + throw new System.NotImplementedException(); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32 ToRgba32() - { - return Unsafe.As(ref this); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return Unsafe.As(ref this); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToRgba32(ref Rgba32 dest) - { - dest = Unsafe.As(ref this); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest = Unsafe.As(ref this); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ToVector4() + { + return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyToVector4(ref Vector4 dest) - { - var tmp = new Vector4(this.R, this.G, this.B, this.A); - tmp *= new Vector4(1f / 255f); - dest = tmp; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToVector4(ref Vector4 dest) + { + var tmp = new Vector4(this.R, this.G, this.B, this.A); + tmp *= new Vector4(1f / 255f); + dest = tmp; } } diff --git a/tests/ImageSharp.Benchmarks/General/StructCasting.cs b/tests/ImageSharp.Benchmarks/General/StructCasting.cs index fbb5cd566c..f8432112e3 100644 --- a/tests/ImageSharp.Benchmarks/General/StructCasting.cs +++ b/tests/ImageSharp.Benchmarks/General/StructCasting.cs @@ -4,28 +4,27 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General; + +public class StructCasting { - public class StructCasting + [Benchmark(Baseline = true)] + public short ExplicitCast() { - [Benchmark(Baseline = true)] - public short ExplicitCast() - { - int x = 5 * 2; - return (short)x; - } + int x = 5 * 2; + return (short)x; + } - [Benchmark] - public short UnsafeCast() - { - int x = 5 * 2; - return Unsafe.As(ref x); - } + [Benchmark] + public short UnsafeCast() + { + int x = 5 * 2; + return Unsafe.As(ref x); + } - [Benchmark] - public short UnsafeCastRef() - { - return Unsafe.As(ref Unsafe.AsRef(5 * 2)); - } + [Benchmark] + public short UnsafeCastRef() + { + return Unsafe.As(ref Unsafe.AsRef(5 * 2)); } } diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index 921625bbd6..2cd6a5a52a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,62 +1,60 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General +namespace SixLabors.ImageSharp.Benchmarks.General; + +/// +/// Has it any effect on performance to store SIMD constants as static readonly fields? Is it OK to always inline them? +/// Spoiler: the difference seems to be statistically insignificant! +/// +public class Vector4Constants { - /// - /// Has it any effect on performance to store SIMD constants as static readonly fields? Is it OK to always inline them? - /// Spoiler: the difference seems to be statistically insignificant! - /// - public class Vector4Constants + private static readonly Vector4 A = new Vector4(1.2f); + private static readonly Vector4 B = new Vector4(3.4f); + private static readonly Vector4 C = new Vector4(5.6f); + private static readonly Vector4 D = new Vector4(7.8f); + + private Random random; + + private Vector4 parameter; + + [GlobalSetup] + public void Setup() { - private static readonly Vector4 A = new Vector4(1.2f); - private static readonly Vector4 B = new Vector4(3.4f); - private static readonly Vector4 C = new Vector4(5.6f); - private static readonly Vector4 D = new Vector4(7.8f); - - private Random random; - - private Vector4 parameter; - - [GlobalSetup] - public void Setup() - { - this.random = new Random(42); - this.parameter = new Vector4( - this.GetRandomFloat(), - this.GetRandomFloat(), - this.GetRandomFloat(), - this.GetRandomFloat()); - } - - [Benchmark(Baseline = true)] - public Vector4 Static() - { - Vector4 p = this.parameter; - - 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; - } - - [Benchmark] - public Vector4 Inlined() - { - 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)); - var z = Vector4.Min(p, new Vector4(1.2f)); - var w = Vector4.Max(p, new Vector4(2.3f)); - return x + y + z + w; - } - - private float GetRandomFloat() => (float)this.random.NextDouble(); + this.random = new Random(42); + this.parameter = new Vector4( + this.GetRandomFloat(), + this.GetRandomFloat(), + this.GetRandomFloat(), + this.GetRandomFloat()); } + + [Benchmark(Baseline = true)] + public Vector4 Static() + { + Vector4 p = this.parameter; + + 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; + } + + [Benchmark] + public Vector4 Inlined() + { + 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)); + var z = Vector4.Min(p, new Vector4(1.2f)); + var w = Vector4.Max(p, new Vector4(2.3f)); + return x + y + z + w; + } + + private float GetRandomFloat() => (float)this.random.NextDouble(); } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs index 7b74f346b9..4d8d9b1ed6 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -4,53 +4,52 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class BitwiseOrUInt32 { - public class BitwiseOrUInt32 - { - private uint[] input; + private uint[] input; - private uint[] result; + private uint[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - private uint testValue; + private uint testValue; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } + this.input[i] = (uint)i; } + } - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] | v; - } + this.result[i] = this.input[i] | v; } + } + + [Benchmark] + public void Simd() + { + var v = new Vector(this.testValue); - [Benchmark] - public void Simd() + for (int i = 0; i < this.input.Length; i += Vector.Count) { - var v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); - a = Vector.BitwiseOr(a, v); - a.CopyTo(this.result, i); - } + var a = new Vector(this.input, i); + a = Vector.BitwiseOr(a, v); + a.CopyTo(this.result, i); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs index 4abeac0d73..1b2c56ab97 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -4,53 +4,52 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class DivFloat { - public class DivFloat - { - private float[] input; + private float[] input; - private float[] result; + private float[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - private float testValue; + private float testValue; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } + this.input[i] = (uint)i; } + } - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } + this.result[i] = this.input[i] / v; } + } + + [Benchmark] + public void Simd() + { + var v = new Vector(this.testValue); - [Benchmark] - public void Simd() + for (int i = 0; i < this.input.Length; i += Vector.Count) { - var v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); - a = a / v; - a.CopyTo(this.result, i); - } + var a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs index 83b56199ea..d102164e2c 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -4,55 +4,54 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class DivUInt32 { - public class DivUInt32 - { - private uint[] input; + private uint[] input; + + private uint[] result; - private uint[] result; + [Params(32)] + public int InputSize { get; set; } - [Params(32)] - public int InputSize { get; set; } + private uint testValue; - private uint testValue; + [GlobalSetup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; - [GlobalSetup] - public void Setup() + for (int i = 0; i < this.InputSize; i++) { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } + this.input[i] = (uint)i; } + } - [Benchmark(Baseline = true)] - public void Standard() - { - uint v = this.testValue; + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; } + } - [Benchmark] - public void Simd() - { - var v = new Vector(this.testValue); + [Benchmark] + public void Simd() + { + var v = new Vector(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + var a = new Vector(this.input, i); - a = a / v; - a.CopyTo(this.result, i); - } + a = a / v; + a.CopyTo(this.result, i); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index 5b5904a773..9c95c22e0f 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -4,69 +4,68 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization -{ +namespace ImageSharp.Benchmarks.General.Vectorization; + #pragma warning disable SA1649 // File name should match first type name - public class DivFloat : SIMDBenchmarkBase.Divide +public class DivFloat : SIMDBenchmarkBase.Divide #pragma warning restore SA1649 // File name should match first type name - { - protected override float GetTestValue() => 42; +{ + protected override float GetTestValue() => 42; - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } + this.result[i] = this.input[i] / v; } } +} - public class Divide : SIMDBenchmarkBase.Divide - { - protected override uint GetTestValue() => 42; +public class Divide : SIMDBenchmarkBase.Divide +{ + protected override uint GetTestValue() => 42; - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } + this.result[i] = this.input[i] / v; } } +} - public class DivInt32 : SIMDBenchmarkBase.Divide - { - protected override int GetTestValue() => 42; +public class DivInt32 : SIMDBenchmarkBase.Divide +{ + protected override int GetTestValue() => 42; - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - int v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] / v; - } + this.result[i] = this.input[i] / v; } } +} - public class DivInt16 : SIMDBenchmarkBase.Divide - { - protected override short GetTestValue() => 42; +public class DivInt16 : SIMDBenchmarkBase.Divide +{ + 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() + [Benchmark(Baseline = true)] + public void Standard() + { + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - short v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = (short)(this.input[i] / v); - } + this.result[i] = (short)(this.input[i] / v); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs index 7c24c717eb..a2eb8d417a 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -4,66 +4,65 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class MulFloat { - public class MulFloat - { - private float[] input; + private float[] input; - private float[] result; + private float[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - private float testValue; + private float testValue; - [GlobalSetup] - public void Setup() - { - this.input = new float[this.InputSize]; - this.result = new float[this.InputSize]; - this.testValue = 42; + [GlobalSetup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = i; - } + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = i; } + } - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - float v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } + this.result[i] = this.input[i] * v; } + } - [Benchmark] - public void SimdMultiplyByVector() - { - var v = new Vector(this.testValue); + [Benchmark] + public void SimdMultiplyByVector() + { + var v = new Vector(this.testValue); - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + var a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); } + } - [Benchmark] - public void SimdMultiplyByScalar() - { - float v = this.testValue; + [Benchmark] + public void SimdMultiplyByScalar() + { + float v = this.testValue; - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + var a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index eebfc1ab7b..a234970a51 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -4,53 +4,52 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class MulUInt32 { - public class MulUInt32 - { - private uint[] input; + private uint[] input; - private uint[] result; + private uint[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - private uint testValue; + private uint testValue; - [GlobalSetup] - public void Setup() + [GlobalSetup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) { - this.input = new uint[this.InputSize]; - this.result = new uint[this.InputSize]; - this.testValue = 42; - - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } + this.input[i] = (uint)i; } + } - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } + this.result[i] = this.input[i] * v; } + } + + [Benchmark] + public void Simd() + { + var v = new Vector(this.testValue); - [Benchmark] - public void Simd() + for (int i = 0; i < this.input.Length; i += Vector.Count) { - var v = new Vector(this.testValue); - - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - var a = new Vector(this.input, i); - a = a * v; - a.CopyTo(this.result, i); - } + var a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index 423a62bc6c..fe48c3301b 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -4,52 +4,51 @@ using System.Numerics; using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization -{ +namespace ImageSharp.Benchmarks.General.Vectorization; + #pragma warning disable SA1649 // File name should match first type name - public class MulUInt32 : SIMDBenchmarkBase.Multiply +public class MulUInt32 : SIMDBenchmarkBase.Multiply #pragma warning restore SA1649 // File name should match first type name - { - protected override uint GetTestValue() => 42u; +{ + protected override uint GetTestValue() => 42u; - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - uint v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } + this.result[i] = this.input[i] * v; } } +} - public class MulInt32 : SIMDBenchmarkBase.Multiply +public class MulInt32 : SIMDBenchmarkBase.Multiply +{ + [Benchmark(Baseline = true)] + public void Standard() { - [Benchmark(Baseline = true)] - public void Standard() + int v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - int v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = this.input[i] * v; - } + this.result[i] = this.input[i] * v; } } +} - public class MulInt16 : SIMDBenchmarkBase.Multiply - { - protected override short GetTestValue() => 42; +public class MulInt16 : SIMDBenchmarkBase.Multiply +{ + 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() + [Benchmark(Baseline = true)] + public void Standard() + { + short v = this.testValue; + for (int i = 0; i < this.input.Length; i++) { - short v = this.testValue; - for (int i = 0; i < this.input.Length; i++) - { - this.result[i] = (short)(this.input[i] * v); - } + this.result[i] = (short)(this.input[i] * v); } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs index bbb0b6bde9..29b90accc5 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -5,58 +5,57 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class Premultiply { - public class Premultiply + [Benchmark(Baseline = true)] + public Vector4 PremultiplyByVal() + { + var input = new Vector4(.5F); + return Vector4Utils.Premultiply(input); + } + + [Benchmark] + public Vector4 PremultiplyByRef() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRef(ref input); + return input; + } + + [Benchmark] + public Vector4 PremultiplyRefWithPropertyAssign() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); + return input; + } +} + +internal static class Vector4Utils +{ + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Premultiply(Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRef(ref Vector4 source) { - [Benchmark(Baseline = true)] - public Vector4 PremultiplyByVal() - { - var input = new Vector4(.5F); - return Vector4Utils.Premultiply(input); - } - - [Benchmark] - public Vector4 PremultiplyByRef() - { - var input = new Vector4(.5F); - Vector4Utils.PremultiplyRef(ref input); - return input; - } - - [Benchmark] - public Vector4 PremultiplyRefWithPropertyAssign() - { - var input = new Vector4(.5F); - Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); - return input; - } + float w = source.W; + source *= w; + source.W = w; } - internal static class Vector4Utils + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) { - [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Premultiply(Vector4 source) - { - float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PremultiplyRef(ref Vector4 source) - { - float w = source.W; - source *= w; - source.W = w; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) - { - float w = source.W; - source *= new Vector4(w) { W = 1 }; - } + float w = source.W; + source *= new Vector4(w) { W = 1 }; } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs index 584e29df27..7d626d7856 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -5,58 +5,57 @@ using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +public class ReinterpretUInt32AsFloat { - public class ReinterpretUInt32AsFloat - { - private uint[] input; + private uint[] input; - private float[] result; + private float[] result; - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - [StructLayout(LayoutKind.Explicit)] - private struct UIntFloatUnion - { - [FieldOffset(0)] - public float F; + [StructLayout(LayoutKind.Explicit)] + private struct UIntFloatUnion + { + [FieldOffset(0)] + public float F; - [FieldOffset(0)] - public uint I; - } + [FieldOffset(0)] + public uint I; + } - [GlobalSetup] - public void Setup() + [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 = new uint[this.InputSize]; - this.result = new float[this.InputSize]; - for (int i = 0; i < this.InputSize; i++) - { - this.input[i] = (uint)i; - } + this.input[i] = (uint)i; } + } - [Benchmark(Baseline = true)] - public void Standard() + [Benchmark(Baseline = true)] + public void Standard() + { + UIntFloatUnion u = default; + for (int i = 0; i < this.input.Length; i++) { - 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; } + } - [Benchmark] - public void Simd() + [Benchmark] + public void Simd() + { + 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); - var b = Vector.AsVectorSingle(a); - b.CopyTo(this.result, i); - } + var a = new Vector(this.input, i); + 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 4c92895e96..90d81a0583 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -5,64 +5,63 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace ImageSharp.Benchmarks.General.Vectorization +namespace ImageSharp.Benchmarks.General.Vectorization; + +public abstract class SIMDBenchmarkBase + where T : struct { - public abstract class SIMDBenchmarkBase - where T : struct - { - protected T[] input; + protected T[] input; - protected T[] result; + protected T[] result; - protected T testValue; + protected T testValue; - protected Vector testVector; + protected Vector testVector; - protected virtual T GetTestValue() => default; + protected virtual T GetTestValue() => default; - protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); + protected virtual Vector GetTestVector() => new Vector(this.GetTestValue()); - [Params(32)] - public int InputSize { get; set; } + [Params(32)] + public int InputSize { get; set; } - [GlobalSetup] - public virtual void Setup() - { - this.input = new T[this.InputSize]; - this.result = new T[this.InputSize]; - this.testValue = this.GetTestValue(); - this.testVector = this.GetTestVector(); - } + [GlobalSetup] + public virtual void Setup() + { + this.input = new T[this.InputSize]; + this.result = new T[this.InputSize]; + this.testValue = this.GetTestValue(); + this.testVector = this.GetTestVector(); + } - public abstract class Multiply : SIMDBenchmarkBase + public abstract class Multiply : SIMDBenchmarkBase + { + [Benchmark] + public void Simd() { - [Benchmark] - public void Simd() - { - Vector v = this.testVector; + Vector v = this.testVector; - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = Unsafe.As>(ref this.input[i]); - a = a * v; - Unsafe.As>(ref this.result[i]) = a; - } + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a * v; + Unsafe.As>(ref this.result[i]) = a; } } + } - public abstract class Divide : SIMDBenchmarkBase + public abstract class Divide : SIMDBenchmarkBase + { + [Benchmark] + public void Simd() { - [Benchmark] - public void Simd() - { - Vector v = this.testVector; + Vector v = this.testVector; - for (int i = 0; i < this.input.Length; i += Vector.Count) - { - Vector a = Unsafe.As>(ref this.input[i]); - a = a / v; - Unsafe.As>(ref this.result[i]) = a; - } + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = Unsafe.As>(ref this.input[i]); + a = a / v; + Unsafe.As>(ref this.result[i]) = a; } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 42c8bd25ce..63d363c688 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -5,104 +5,103 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +[Config(typeof(Config.ShortMultiFramework))] +public class UInt32ToSingle { - [Config(typeof(Config.ShortMultiFramework))] - public class UInt32ToSingle - { - private float[] data; + private float[] data; - private const int Count = 32; + private const int Count = 32; - [GlobalSetup] - public void Setup() - { - this.data = new float[Count]; - } + [GlobalSetup] + public void Setup() + { + this.data = new float[Count]; + } - [Benchmark(Baseline = true)] - public void MagicMethod() - { - ref Vector b = ref Unsafe.As>(ref this.data[0]); + [Benchmark(Baseline = true)] + public void MagicMethod() + { + ref Vector b = ref Unsafe.As>(ref this.data[0]); - int n = Count / Vector.Count; + int n = Count / Vector.Count; - var bVec = new Vector(256.0f / 255.0f); - var magicFloat = new Vector(32768.0f); - var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f - var mask = new Vector(255); + var bVec = new Vector(256.0f / 255.0f); + var magicFloat = new Vector(32768.0f); + var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f + var mask = new Vector(255); - for (int i = 0; i < n; i++) - { - ref Vector df = ref Unsafe.Add(ref b, i); + for (int i = 0; i < n; i++) + { + ref Vector df = ref Unsafe.Add(ref b, i); - var vi = Vector.AsVectorUInt32(df); - vi &= mask; - vi |= magicInt; + var vi = Vector.AsVectorUInt32(df); + vi &= mask; + vi |= magicInt; - var vf = Vector.AsVectorSingle(vi); - vf = (vf - magicFloat) * bVec; + var vf = Vector.AsVectorSingle(vi); + vf = (vf - magicFloat) * bVec; - df = vf; - } + df = vf; } + } - [Benchmark] - public void StandardSimd() - { - int n = Count / Vector.Count; + [Benchmark] + public void StandardSimd() + { + int n = Count / Vector.Count; - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - ref Vector bu = ref Unsafe.As, Vector>(ref bf); + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + ref Vector bu = ref Unsafe.As, Vector>(ref bf); - var scale = new Vector(1f / 255f); + var scale = new Vector(1f / 255f); - for (int i = 0; i < n; i++) - { - Vector u = Unsafe.Add(ref bu, i); - Vector v = Vector.ConvertToSingle(u); - v *= scale; - Unsafe.Add(ref bf, i) = v; - } + for (int i = 0; i < n; i++) + { + Vector u = Unsafe.Add(ref bu, i); + Vector v = Vector.ConvertToSingle(u); + v *= scale; + Unsafe.Add(ref bf, i) = v; } + } - [Benchmark] - public void StandardSimdFromInt() - { - int n = Count / Vector.Count; + [Benchmark] + public void StandardSimdFromInt() + { + int n = Count / Vector.Count; - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - ref Vector bu = ref Unsafe.As, Vector>(ref bf); + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + ref Vector bu = ref Unsafe.As, Vector>(ref bf); - var scale = new Vector(1f / 255f); + var scale = new Vector(1f / 255f); - for (int i = 0; i < n; i++) - { - Vector u = Unsafe.Add(ref bu, i); - Vector v = Vector.ConvertToSingle(u); - v *= scale; - Unsafe.Add(ref bf, i) = v; - } + for (int i = 0; i < n; i++) + { + Vector u = Unsafe.Add(ref bu, i); + Vector v = Vector.ConvertToSingle(u); + v *= scale; + Unsafe.Add(ref bf, i) = v; } + } - [Benchmark] - public void StandardSimdFromInt_RefCast() - { - int n = Count / Vector.Count; + [Benchmark] + public void StandardSimdFromInt_RefCast() + { + int n = Count / Vector.Count; - ref Vector bf = ref Unsafe.As>(ref this.data[0]); - var scale = new Vector(1f / 255f); + ref Vector bf = ref Unsafe.As>(ref this.data[0]); + var scale = new Vector(1f / 255f); - for (int i = 0; i < n; i++) - { - ref Vector fRef = ref Unsafe.Add(ref bf, i); + for (int i = 0; i < n; i++) + { + ref Vector fRef = ref Unsafe.Add(ref bf, i); - Vector du = Vector.AsVectorInt32(fRef); - Vector v = Vector.ConvertToSingle(du); - v *= scale; + Vector du = Vector.AsVectorInt32(fRef); + Vector v = Vector.ConvertToSingle(du); + v *= scale; - fRef = v; - } + fRef = v; } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs index 8a733c156b..07ace06686 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs @@ -1,112 +1,111 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; + +/// +/// This benchmark compares different methods for fetching memory data into +/// checking if JIT has limitations. Normally SIMD acceleration should be here for all methods. +/// +public class VectorFetching { - using System; - using System.Numerics; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using BenchmarkDotNet.Attributes; - - /// - /// This benchmark compares different methods for fetching memory data into - /// checking if JIT has limitations. Normally SIMD acceleration should be here for all methods. - /// - public class VectorFetching - { - private float testValue; + private float testValue; - private float[] data; + private float[] data; - [Params(64)] - public int InputSize { get; set; } + [Params(64)] + public int InputSize { get; set; } - [GlobalSetup] - public void Setup() - { - this.data = new float[this.InputSize]; - this.testValue = 42; + [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; - } + for (int i = 0; i < this.InputSize; i++) + { + this.data[i] = i; } + } - [Benchmark(Baseline = true)] - public void Baseline() + [Benchmark(Baseline = true)] + public void Baseline() + { + float v = this.testValue; + for (int i = 0; i < this.data.Length; i++) { - float v = this.testValue; - for (int i = 0; i < this.data.Length; i++) - { - this.data[i] = this.data[i] * v; - } + this.data[i] = this.data[i] * v; } + } + + [Benchmark] + public void FetchWithVectorConstructor() + { + var v = new Vector(this.testValue); - [Benchmark] - public void FetchWithVectorConstructor() + for (int i = 0; i < this.data.Length; i += Vector.Count) { - var v = new Vector(this.testValue); - - for (int i = 0; i < this.data.Length; i += Vector.Count) - { - var a = new Vector(this.data, i); - a = a * v; - a.CopyTo(this.data, i); - } + var a = new Vector(this.data, i); + a = a * v; + a.CopyTo(this.data, i); } + } - [Benchmark] - public void FetchWithUnsafeCast() - { - var v = new Vector(this.testValue); - ref Vector start = ref Unsafe.As>(ref this.data[0]); + [Benchmark] + public void FetchWithUnsafeCast() + { + var v = new Vector(this.testValue); + ref Vector start = ref Unsafe.As>(ref this.data[0]); - int n = this.InputSize / Vector.Count; + int n = this.InputSize / Vector.Count; - for (int i = 0; i < n; i++) - { - ref Vector p = ref Unsafe.Add(ref start, i); + for (int i = 0; i < n; i++) + { + ref Vector p = ref Unsafe.Add(ref start, i); - Vector a = p; - a = a * v; + Vector a = p; + a = a * v; - p = a; - } + p = a; } + } - [Benchmark] - public void FetchWithUnsafeCastNoTempVector() - { - var v = new Vector(this.testValue); - ref Vector start = ref Unsafe.As>(ref this.data[0]); + [Benchmark] + public void FetchWithUnsafeCastNoTempVector() + { + var v = new Vector(this.testValue); + ref Vector start = ref Unsafe.As>(ref this.data[0]); - int n = this.InputSize / Vector.Count; + int n = this.InputSize / Vector.Count; - for (int i = 0; i < n; i++) - { - ref Vector a = ref Unsafe.Add(ref start, i); - a = a * v; - } + for (int i = 0; i < n; i++) + { + ref Vector a = ref Unsafe.Add(ref start, i); + a = a * v; } + } - [Benchmark] - public void FetchWithUnsafeCastFromReference() - { - var v = new Vector(this.testValue); + [Benchmark] + public void FetchWithUnsafeCastFromReference() + { + var v = new Vector(this.testValue); - var span = new Span(this.data); + var span = new Span(this.data); - ref Vector start = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); + ref Vector start = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - int n = this.InputSize / Vector.Count; + int n = this.InputSize / Vector.Count; - for (int i = 0; i < n; i++) - { - ref Vector a = ref Unsafe.Add(ref start, i); - a = a * v; - } + for (int i = 0; i < n; i++) + { + ref Vector a = ref Unsafe.Add(ref start, i); + a = a * v; } } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index c4a28085f0..429475ffd3 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -7,60 +7,59 @@ using SixLabors.ImageSharp.Tuples; -namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; + +[Config(typeof(Config.ShortMultiFramework))] +public class WidenBytesToUInt32 { - [Config(typeof(Config.ShortMultiFramework))] - public class WidenBytesToUInt32 - { - private byte[] source; + private byte[] source; - private uint[] dest; + private uint[] dest; - private const int Count = 64; + private const int Count = 64; - [GlobalSetup] - public void Setup() - { - this.source = new byte[Count]; - this.dest = new uint[Count]; - } + [GlobalSetup] + public void Setup() + { + this.source = new byte[Count]; + this.dest = new uint[Count]; + } - [Benchmark(Baseline = true)] - public void Standard() - { - const int N = Count / 8; + [Benchmark(Baseline = true)] + public void Standard() + { + const int N = Count / 8; - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet 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++) - { - Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); - } + for (int i = 0; i < N; i++) + { + Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); } + } - [Benchmark] - public void Simd() - { - int n = Count / Vector.Count; + [Benchmark] + public void Simd() + { + int n = Count / Vector.Count; - ref Vector sBase = ref Unsafe.As>(ref this.source[0]); - ref Vector dBase = ref Unsafe.As>(ref this.dest[0]); + ref Vector sBase = ref Unsafe.As>(ref this.source[0]); + ref Vector dBase = ref Unsafe.As>(ref this.dest[0]); - for (int i = 0; i < n; i++) - { - Vector b = Unsafe.Add(ref sBase, i); + for (int i = 0; i < n; i++) + { + Vector b = Unsafe.Add(ref sBase, i); - Vector.Widen(b, out Vector s0, out Vector s1); - Vector.Widen(s0, out Vector w0, out Vector w1); - Vector.Widen(s1, out Vector w2, out Vector w3); + Vector.Widen(b, out Vector s0, out Vector s1); + Vector.Widen(s0, out Vector w0, out Vector w1); + Vector.Widen(s1, out Vector w2, out Vector w3); - ref Vector d = ref Unsafe.Add(ref dBase, i * 4); - d = w0; - Unsafe.Add(ref d, 1) = w1; - Unsafe.Add(ref d, 2) = w2; - Unsafe.Add(ref d, 3) = w3; - } + ref Vector d = ref Unsafe.Add(ref dBase, i * 4); + d = w0; + Unsafe.Add(ref d, 1) = w1; + Unsafe.Add(ref d, 2) = w2; + Unsafe.Add(ref d, 3) = w3; } } } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index dd9d05cd46..04621695c6 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -1,83 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using BenchmarkDotNet.Attributes; -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave -{ - // See README.md for instructions about initialization. - [MemoryDiagnoser] - [ShortRunJob] - public class LoadResizeSaveStressBenchmarks - { - private LoadResizeSaveStressRunner runner; +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave; - // private const JpegKind Filter = JpegKind.Progressive; - private const JpegKind Filter = JpegKind.Any; +// See README.md for instructions about initialization. +[MemoryDiagnoser] +[ShortRunJob] +public class LoadResizeSaveStressBenchmarks +{ + private LoadResizeSaveStressRunner runner; - [GlobalSetup] - public void Setup() - { - this.runner = new LoadResizeSaveStressRunner() - { - ImageCount = Environment.ProcessorCount, - Filter = Filter - }; - Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); - this.runner.Init(); - } - - private void ForEachImage(Action action, int maxDegreeOfParallelism) - { - this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; - this.runner.ForEachImageParallel(action); - } + // private const JpegKind Filter = JpegKind.Progressive; + private const JpegKind Filter = JpegKind.Any; - public int[] ParallelismValues { get; } = + [GlobalSetup] + public void Setup() + { + this.runner = new LoadResizeSaveStressRunner() { - // Environment.ProcessorCount, - // Environment.ProcessorCount / 2, - // Environment.ProcessorCount / 4, - 1 + ImageCount = Environment.ProcessorCount, + Filter = Filter }; + Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); + this.runner.Init(); + } - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaBitmapDecodeToTargetSize(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapDecodeToTargetSize, maxDegreeOfParallelism); - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); + private void ForEachImage(Action action, int maxDegreeOfParallelism) + { + this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.runner.ForEachImageParallel(action); } + + public int[] ParallelismValues { get; } = + { + // Environment.ProcessorCount, + // Environment.ProcessorCount / 2, + // Environment.ProcessorCount / 4, + 1 + }; + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(ParallelismValues))] + public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaBitmapDecodeToTargetSize(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapDecodeToTargetSize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); } /* BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.300 - [Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT - ShortRun : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT +[Host] : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT +ShortRun : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index da71ce4dac..ce2d1625f6 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -1,23 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; using SkiaSharp; using ImageSharpImage = SixLabors.ImageSharp.Image; @@ -25,334 +17,333 @@ using NetVipsImage = NetVips.Image; using SystemDrawingImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave; + +public enum JpegKind { - public enum JpegKind - { - Baseline = 1, - Progressive = 2, - Any = Baseline | Progressive - } + Baseline = 1, + Progressive = 2, + Any = Baseline | Progressive +} - public class LoadResizeSaveStressRunner - { - private const int Quality = 75; +public class LoadResizeSaveStressRunner +{ + private const int Quality = 75; - // Set the quality for ImagSharp - private readonly JpegEncoder imageSharpJpegEncoder = new() { Quality = Quality }; - private readonly ImageCodecInfo systemDrawingJpegCodec = - ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); + // Set the quality for ImagSharp + private readonly JpegEncoder imageSharpJpegEncoder = new() { Quality = Quality }; + private readonly ImageCodecInfo systemDrawingJpegCodec = + ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); - public string[] Images { get; private set; } + public string[] Images { get; private set; } - public double TotalProcessedMegapixels { get; private set; } + public double TotalProcessedMegapixels { get; private set; } - public Size LastProcessedImageSize { get; private set; } + public Size LastProcessedImageSize { get; private set; } - private string outputDirectory; + private string outputDirectory; - public int ImageCount { get; set; } = int.MaxValue; + public int ImageCount { get; set; } = int.MaxValue; - public int MaxDegreeOfParallelism { get; set; } = -1; + public int MaxDegreeOfParallelism { get; set; } = -1; - public JpegKind Filter { get; set; } = JpegKind.Any; + public JpegKind Filter { get; set; } = JpegKind.Any; - public int ThumbnailSize { get; set; } = 150; + public int ThumbnailSize { get; set; } = 150; - private static readonly string[] ProgressiveFiles = + private static readonly string[] ProgressiveFiles = + { + "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", + "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", + "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", + "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", + "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", + "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", + "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", + "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", + "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", + "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", + "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", + "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", + "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", + "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", + "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", + "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", + "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", + "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", + "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", + "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", + "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", + "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", + "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", + "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", + "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", + "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", + "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", + "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", + "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", + "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", + "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", + }; + + public void Init() + { + if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) { - "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", - "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", - "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", - "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", - "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", - "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", - "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", - "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", - "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", - "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", - "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", - "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", - "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", - "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", - "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", - "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", - "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", - "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", - "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", - "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", - "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", - "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", - "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", - "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", - "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", - "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", - "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", - "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", - "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", - "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", - "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", - }; + // Workaround ImageMagick issue + OpenCL.IsEnabled = false; + } - public void Init() + string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); + if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) { - if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) - { - // Workaround ImageMagick issue - OpenCL.IsEnabled = false; - } + throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); + } - string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); - if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) - { - throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); - } + // Get at most this.ImageCount images from there + bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); - // Get at most this.ImageCount images from there - bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); + this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); - this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); + // Create the output directory next to the images directory + this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); - // Create the output directory next to the images directory - this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + static JpegKind GetJpegType(string f) => + ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) + ? JpegKind.Progressive + : JpegKind.Baseline; + } - static JpegKind GetJpegType(string f) => - ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) - ? JpegKind.Progressive - : JpegKind.Baseline; - } + public void ForEachImageParallel(Action action) => Parallel.ForEach( + this.Images, + new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, + action); - public void ForEachImageParallel(Action action) => Parallel.ForEach( - this.Images, - new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, - action); + public Task ForEachImageParallelAsync(Func action) + { + int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0 + ? this.MaxDegreeOfParallelism + : Environment.ProcessorCount; + int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism); - public Task ForEachImageParallelAsync(Func action) + List tasks = new(); + for (int i = 0; i < this.Images.Length; i += partitionSize) { - int maxDegreeOfParallelism = this.MaxDegreeOfParallelism > 0 - ? this.MaxDegreeOfParallelism - : Environment.ProcessorCount; - int partitionSize = (int)Math.Ceiling((double)this.Images.Length / maxDegreeOfParallelism); - - List tasks = new(); - for (int i = 0; i < this.Images.Length; i += partitionSize) - { - int end = Math.Min(i + partitionSize, this.Images.Length); - Task task = RunPartition(i, end); - tasks.Add(task); - } + int end = Math.Min(i + partitionSize, this.Images.Length); + Task task = RunPartition(i, end); + tasks.Add(task); + } - return Task.WhenAll(tasks); + return Task.WhenAll(tasks); - Task RunPartition(int start, int end) => Task.Run(async () => + Task RunPartition(int start, int end) => Task.Run(async () => + { + for (int i = start; i < end; i++) { - for (int i = start; i < end; i++) - { - await action(this.Images[i]); - } - }); - } + await action(this.Images[i]); + } + }); + } - private void LogImageProcessed(int width, int height) - { - this.LastProcessedImageSize = new Size(width, height); - double pixels = width * (double)height; - this.TotalProcessedMegapixels += pixels / 1_000_000.0; - } + private void LogImageProcessed(int width, int height) + { + this.LastProcessedImageSize = new Size(width, height); + double pixels = width * (double)height; + this.TotalProcessedMegapixels += pixels / 1_000_000.0; + } - private string OutputPath(string inputPath, [CallerMemberName] string postfix = null) => - Path.Combine( - this.outputDirectory, - Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); + private string OutputPath(string inputPath, [CallerMemberName] string postfix = null) => + Path.Combine( + this.outputDirectory, + Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); - private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) + private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) + { + int width, height; + if (inWidth > inHeight) { - int width, height; - if (inWidth > inHeight) - { - width = outSize; - height = (int)Math.Round(inHeight * outSize / (double)inWidth); - } - else - { - width = (int)Math.Round(inWidth * outSize / (double)inHeight); - height = outSize; - } - - return (width, height); + width = outSize; + height = (int)Math.Round(inHeight * outSize / (double)inWidth); } - - public void SystemDrawingResize(string input) + else { - using var image = SystemDrawingImage.FromFile(input, true); - this.LogImageProcessed(image.Width, image.Height); - - (int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); - var resized = new Bitmap(scaled.Width, scaled.Height); - using var graphics = Graphics.FromImage(resized); - using var attributes = new ImageAttributes(); - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.CompositingQuality = CompositingQuality.AssumeLinear; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); - - // Save the results - using var encoderParams = new EncoderParameters(1); - using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); - encoderParams.Param[0] = qualityParam; - resized.Save(this.OutputPath(input), this.systemDrawingJpegCodec, encoderParams); + width = (int)Math.Round(inWidth * outSize / (double)inHeight); + height = outSize; } - public void ImageSharpResize(string input) - { - using FileStream inputStream = File.Open(input, FileMode.Open); - using FileStream outputStream = File.Open(this.OutputPath(input), FileMode.Create); - - // Resize it to fit a 150x150 square - DecoderOptions options = new() - { - TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) - }; - - var decoder = new JpegDecoder(); - using ImageSharpImage image = decoder.Decode(options, inputStream); - this.LogImageProcessed(image.Width, image.Height); + return (width, height); + } - // Reduce the size of the file - image.Metadata.ExifProfile = null; - image.Metadata.XmpProfile = null; - image.Metadata.IccProfile = null; - image.Metadata.IptcProfile = null; + public void SystemDrawingResize(string input) + { + using var image = SystemDrawingImage.FromFile(input, true); + this.LogImageProcessed(image.Width, image.Height); + + (int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); + var resized = new Bitmap(scaled.Width, scaled.Height); + using var graphics = Graphics.FromImage(resized); + using var attributes = new ImageAttributes(); + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.AssumeLinear; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + + // Save the results + using var encoderParams = new EncoderParameters(1); + using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); + encoderParams.Param[0] = qualityParam; + resized.Save(this.OutputPath(input), this.systemDrawingJpegCodec, encoderParams); + } - // Save the results - image.Save(outputStream, this.imageSharpJpegEncoder); - } + public void ImageSharpResize(string input) + { + using FileStream inputStream = File.Open(input, FileMode.Open); + using FileStream outputStream = File.Open(this.OutputPath(input), FileMode.Create); - public async Task ImageSharpResizeAsync(string input) + // Resize it to fit a 150x150 square + DecoderOptions options = new() { - using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); + TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) + }; - // Resize it to fit a 150x150 square. - DecoderOptions options = new() - { - TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) - }; + var decoder = new JpegDecoder(); + using ImageSharpImage image = decoder.Decode(options, inputStream); + this.LogImageProcessed(image.Width, image.Height); - using ImageSharpImage image = await ImageSharpImage.LoadAsync(options, input); - this.LogImageProcessed(image.Width, image.Height); + // Reduce the size of the file + image.Metadata.ExifProfile = null; + image.Metadata.XmpProfile = null; + image.Metadata.IccProfile = null; + image.Metadata.IptcProfile = null; - // Reduce the size of the file - image.Metadata.ExifProfile = null; - image.Metadata.XmpProfile = null; - image.Metadata.IccProfile = null; - image.Metadata.IptcProfile = null; + // Save the results + image.Save(outputStream, this.imageSharpJpegEncoder); + } - // Save the results - await image.SaveAsync(output, this.imageSharpJpegEncoder); - } + public async Task ImageSharpResizeAsync(string input) + { + using FileStream output = File.Open(this.OutputPath(input), FileMode.Create); - public void MagickResize(string input) + // Resize it to fit a 150x150 square. + DecoderOptions options = new() { - using var image = new MagickImage(input); - this.LogImageProcessed(image.Width, image.Height); + TargetSize = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize) + }; - // Resize it to fit a 150x150 square - image.Resize(this.ThumbnailSize, this.ThumbnailSize); + using ImageSharpImage image = await ImageSharpImage.LoadAsync(options, input); + this.LogImageProcessed(image.Width, image.Height); - // Reduce the size of the file - image.Strip(); + // Reduce the size of the file + image.Metadata.ExifProfile = null; + image.Metadata.XmpProfile = null; + image.Metadata.IccProfile = null; + image.Metadata.IptcProfile = null; - // Set the quality - image.Quality = Quality; + // Save the results + await image.SaveAsync(output, this.imageSharpJpegEncoder); + } - // Save the results - image.Write(this.OutputPath(input)); - } + public void MagickResize(string input) + { + using var image = new MagickImage(input); + this.LogImageProcessed(image.Width, image.Height); - public void MagicScalerResize(string input) - { - var settings = new ProcessImageSettings() - { - Width = this.ThumbnailSize, - Height = this.ThumbnailSize, - ResizeMode = CropScaleMode.Max, - SaveFormat = FileFormat.Jpeg, - JpegQuality = Quality, - JpegSubsampleMode = ChromaSubsampleMode.Subsample420 - }; - - // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? - using var output = new FileStream(this.OutputPath(input), FileMode.Create); - MagicImageProcessor.ProcessImage(input, output, settings); - } + // Resize it to fit a 150x150 square + image.Resize(this.ThumbnailSize, this.ThumbnailSize); - public void SkiaCanvasResize(string input) - { - using var original = SKBitmap.Decode(input); - this.LogImageProcessed(original.Width, original.Height); - (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); - using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType)); - using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; - SKCanvas canvas = surface.Canvas; - canvas.Scale((float)scaled.Width / original.Width); - canvas.DrawBitmap(original, 0, 0, paint); - canvas.Flush(); - - using FileStream output = File.OpenWrite(this.OutputPath(input)); - surface.Snapshot() - .Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); - } + // Reduce the size of the file + image.Strip(); - public void SkiaBitmapResize(string input) + // Set the quality + image.Quality = Quality; + + // Save the results + image.Write(this.OutputPath(input)); + } + + public void MagicScalerResize(string input) + { + var settings = new ProcessImageSettings() { - using var original = SKBitmap.Decode(input); - this.LogImageProcessed(original.Width, original.Height); - (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); - using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); - if (resized == null) - { - return; - } + Width = this.ThumbnailSize, + Height = this.ThumbnailSize, + ResizeMode = CropScaleMode.Max, + SaveFormat = FileFormat.Jpeg, + JpegQuality = Quality, + JpegSubsampleMode = ChromaSubsampleMode.Subsample420 + }; - using var image = SKImage.FromBitmap(resized); - using FileStream output = File.OpenWrite(this.OutputPath(input)); - image.Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); - } + // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? + using var output = new FileStream(this.OutputPath(input), FileMode.Create); + MagicImageProcessor.ProcessImage(input, output, settings); + } + + public void SkiaCanvasResize(string input) + { + using var original = SKBitmap.Decode(input); + this.LogImageProcessed(original.Width, original.Height); + (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType)); + using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; + SKCanvas canvas = surface.Canvas; + canvas.Scale((float)scaled.Width / original.Width); + canvas.DrawBitmap(original, 0, 0, paint); + canvas.Flush(); + + using FileStream output = File.OpenWrite(this.OutputPath(input)); + surface.Snapshot() + .Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } - public void SkiaBitmapDecodeToTargetSize(string input) + public void SkiaBitmapResize(string input) + { + using var original = SKBitmap.Decode(input); + this.LogImageProcessed(original.Width, original.Height); + (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); + if (resized == null) { - using var codec = SKCodec.Create(input); + return; + } - SKImageInfo info = codec.Info; - this.LogImageProcessed(info.Width, info.Height); - (int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); - SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width); + using var image = SKImage.FromBitmap(resized); + using FileStream output = File.OpenWrite(this.OutputPath(input)); + image.Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } - using var original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height)); - using SKBitmap resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); - if (resized == null) - { - return; - } + public void SkiaBitmapDecodeToTargetSize(string input) + { + using var codec = SKCodec.Create(input); - using var image = SKImage.FromBitmap(resized); + SKImageInfo info = codec.Info; + this.LogImageProcessed(info.Width, info.Height); + (int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); + SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width); - using FileStream output = File.OpenWrite(this.OutputPath(input, nameof(this.SkiaBitmapDecodeToTargetSize))); - image.Encode(SKEncodedImageFormat.Jpeg, Quality) - .SaveTo(output); + using var original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height)); + using SKBitmap resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); + if (resized == null) + { + return; } - public void NetVipsResize(string input) - { - // Thumbnail to fit a 150x150 square - using var thumb = NetVipsImage.Thumbnail(input, this.ThumbnailSize, this.ThumbnailSize); + using var image = SKImage.FromBitmap(resized); - // Save the results - thumb.Jpegsave(this.OutputPath(input), q: Quality, strip: true); - } + using FileStream output = File.OpenWrite(this.OutputPath(input, nameof(this.SkiaBitmapDecodeToTargetSize))); + image.Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void NetVipsResize(string input) + { + // Thumbnail to fit a 150x150 square + using var thumb = NetVipsImage.Thumbnail(input, this.ThumbnailSize, this.ThumbnailSize); + + // Save the results + thumb.Jpegsave(this.OutputPath(input), q: Quality, strip: true); } } diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 6c06b5e40c..68956c880f 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Numerics; using BenchmarkDotNet.Attributes; @@ -9,88 +8,87 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; -namespace SixLabors.ImageSharp.Benchmarks -{ - public class PorterDuffBulkVsPixel - { - private Configuration Configuration => Configuration.Default; +namespace SixLabors.ImageSharp.Benchmarks; - private void BulkVectorConvert( - Span destination, - Span background, - Span source, - Span amount) - where TPixel : unmanaged, IPixel - { - Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); +public class PorterDuffBulkVsPixel +{ + private Configuration Configuration => Configuration.Default; - using IMemoryOwner buffer = - Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3); - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + private void BulkVectorConvert( + Span destination, + Span background, + Span source, + Span amount) + where TPixel : unmanaged, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); - PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); + using IMemoryOwner buffer = + Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3); + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); + PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); } - private void BulkPixelConvert( - Span destination, - Span background, - Span source, - Span amount) - where TPixel : unmanaged, IPixel - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); + } - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(destination[i], source[i], amount[i]); - } - } + private void BulkPixelConvert( + Span destination, + Span background, + Span source, + Span amount) + where TPixel : unmanaged, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - [Benchmark(Description = "ImageSharp BulkVectorConvert")] - public Size BulkVectorConvert() + for (int i = 0; i < destination.Length; i++) { - using var image = new Image(800, 800); - using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); - amounts.GetSpan().Fill(1); + destination[i] = PorterDuffFunctions.NormalSrcOver(destination[i], source[i], amount[i]); + } + } - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.DangerousGetRowSpan(y); - this.BulkVectorConvert(span, span, span, amounts.GetSpan()); - } + [Benchmark(Description = "ImageSharp BulkVectorConvert")] + public Size BulkVectorConvert() + { + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); - return new Size(image.Width, image.Height); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.DangerousGetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } - [Benchmark(Description = "ImageSharp BulkPixelConvert")] - public Size BulkPixelConvert() - { - using var image = new Image(800, 800); - using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); - amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.DangerousGetRowSpan(y); - this.BulkPixelConvert(span, span, span, amounts.GetSpan()); - } + return new Size(image.Width, image.Height); + } - return new Size(image.Width, image.Height); + [Benchmark(Description = "ImageSharp BulkPixelConvert")] + public Size BulkPixelConvert() + { + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.DangerousGetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } + + return new Size(image.Width, image.Height); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs index a1732fa8b3..2bfc437582 100644 --- a/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs @@ -5,16 +5,15 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class BokehBlur { - [Config(typeof(Config.MultiFramework))] - public class BokehBlur + [Benchmark] + public void Blur() { - [Benchmark] - public void Blur() - { - using var image = new Image(Configuration.Default, 400, 400, Color.White); - image.Mutate(c => c.BokehBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.BokehBlur()); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/Crop.cs b/tests/ImageSharp.Benchmarks/Processing/Crop.cs index 8631b97d35..d802d3293a 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Crop.cs @@ -9,32 +9,31 @@ using SDRectangle = System.Drawing.Rectangle; using SDSize = System.Drawing.Size; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class Crop { - [Config(typeof(Config.MultiFramework))] - public class Crop + [Benchmark(Baseline = true, Description = "System.Drawing Crop")] + public SDSize CropSystemDrawing() { - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public SDSize CropSystemDrawing() - { - using var source = new Bitmap(800, 800); - using var destination = new Bitmap(100, 100); - using var graphics = Graphics.FromImage(destination); + using var source = new Bitmap(800, 800); + using var destination = new Bitmap(100, 100); + using var graphics = Graphics.FromImage(destination); - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + 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; - } + return destination.Size; + } - [Benchmark(Description = "ImageSharp Crop")] - public Size CropImageSharp() - { - using var image = new Image(800, 800); - image.Mutate(x => x.Crop(100, 100)); - return new Size(image.Width, image.Height); - } + [Benchmark(Description = "ImageSharp Crop")] + public Size CropImageSharp() + { + using var image = new Image(800, 800); + image.Mutate(x => x.Crop(100, 100)); + return new Size(image.Width, image.Height); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs index 68984005e9..9371906965 100644 --- a/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +[Config(typeof(Config.MultiFramework))] +public class DetectEdges { - [Config(typeof(Config.MultiFramework))] - public class DetectEdges - { - private Image image; + private Image image; - [GlobalSetup] - public void ReadImage() + [GlobalSetup] + public void ReadImage() + { + if (this.image == null) { - if (this.image == null) - { - this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car))); - } + this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car))); } + } - [GlobalCleanup] - public void Cleanup() - { - this.image.Dispose(); - this.image = null; - } + [GlobalCleanup] + public void Cleanup() + { + this.image.Dispose(); + this.image = null; + } - [Benchmark(Description = "ImageSharp DetectEdges")] - public void ImageProcessorCoreDetectEdges() - { - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr)); - this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel)); - } + [Benchmark(Description = "ImageSharp DetectEdges")] + public void ImageProcessorCoreDetectEdges() + { + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kayyali)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Kirsch)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian3x3)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Laplacian5x5)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.LaplacianOfGaussian)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Prewitt)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.RobertsCross)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Robinson)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Scharr)); + this.image.Mutate(x => x.DetectEdges(KnownEdgeDetectorKernels.Sobel)); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs index acb5a03590..7d6a52af25 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs @@ -5,28 +5,27 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class Diffuse { - [Config(typeof(Config.MultiFramework))] - public class Diffuse + [Benchmark] + public Size DoDiffuse() { - [Benchmark] - public Size DoDiffuse() - { - using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); - image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); - return image.Size(); - } + return image.Size(); + } - [Benchmark] - public Size DoDither() - { - using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); - image.Mutate(x => x.Dither()); + [Benchmark] + public Size DoDither() + { + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither()); - return image.Size(); - } + return image.Size(); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs index da6ba005af..0123f4d3b8 100644 --- a/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs @@ -5,16 +5,15 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Samplers; + +[Config(typeof(Config.MultiFramework))] +public class GaussianBlur { - [Config(typeof(Config.MultiFramework))] - public class GaussianBlur + [Benchmark] + public void Blur() { - [Benchmark] - public void Blur() - { - using var image = new Image(Configuration.Default, 400, 400, Color.White); - image.Mutate(c => c.GaussianBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.GaussianBlur()); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs index 299ac9b2f3..6292b793ec 100644 --- a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs +++ b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs @@ -1,52 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Normalization; using SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class HistogramEqualization { - [Config(typeof(Config.MultiFramework))] - public class HistogramEqualization - { - private Image image; + private Image image; - [GlobalSetup] - public void ReadImages() + [GlobalSetup] + public void ReadImages() + { + if (this.image == null) { - if (this.image == null) - { - this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.HistogramEqImage))); - } + this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.HistogramEqImage))); } + } - [GlobalCleanup] - public void Cleanup() - { - this.image.Dispose(); - this.image = null; - } + [GlobalCleanup] + public void Cleanup() + { + this.image.Dispose(); + this.image = null; + } - [Benchmark(Description = "Global Histogram Equalization")] - public void GlobalHistogramEqualization() - => this.image.Mutate(img => img.HistogramEqualization( - new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.Global - })); + [Benchmark(Description = "Global Histogram Equalization")] + public void GlobalHistogramEqualization() + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.Global + })); - [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] - public void AdaptiveHistogramEqualization() - => this.image.Mutate(img => img.HistogramEqualization( - new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation - })); - } + [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] + public void AdaptiveHistogramEqualization() + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation + })); } diff --git a/tests/ImageSharp.Benchmarks/Processing/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs index 0f9e95819a..ae993cd25b 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Resize.cs @@ -3,8 +3,6 @@ using System.Drawing; using System.Drawing.Drawing2D; -using System.Globalization; -using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; @@ -12,238 +10,237 @@ using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +[Config(typeof(Config.MultiFramework))] +public abstract class Resize + where TPixel : unmanaged, IPixel { - [Config(typeof(Config.MultiFramework))] - public abstract class Resize - where TPixel : unmanaged, IPixel - { - private byte[] bytes = null; + private byte[] bytes = null; - private Image sourceImage; + private Image sourceImage; - private SDImage sourceBitmap; + private SDImage sourceBitmap; - protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule()); + protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule()); - protected int DestSize { get; private set; } + protected int DestSize { get; private set; } - [GlobalSetup] - public virtual void Setup() + [GlobalSetup] + public virtual void Setup() + { + if (this.bytes is null) { - if (this.bytes is null) - { - this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake)); + this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake)); - this.sourceImage = Image.Load(this.bytes); + this.sourceImage = Image.Load(this.bytes); - var ms1 = new MemoryStream(this.bytes); - this.sourceBitmap = SDImage.FromStream(ms1); - this.DestSize = this.sourceBitmap.Width / 2; - } + var ms1 = new MemoryStream(this.bytes); + this.sourceBitmap = SDImage.FromStream(ms1); + this.DestSize = this.sourceBitmap.Width / 2; } + } - [GlobalCleanup] - public void Cleanup() - { - this.bytes = null; - this.sourceImage.Dispose(); - this.sourceBitmap.Dispose(); - } + [GlobalCleanup] + public void Cleanup() + { + this.bytes = null; + this.sourceImage.Dispose(); + this.sourceBitmap.Dispose(); + } - [Benchmark(Baseline = true)] - public int SystemDrawing() + [Benchmark(Baseline = true)] + public int SystemDrawing() + { + using (var destination = new Bitmap(this.DestSize, this.DestSize)) { - using (var destination = new Bitmap(this.DestSize, this.DestSize)) + using (var g = Graphics.FromImage(destination)) { - using (var g = Graphics.FromImage(destination)) - { - g.CompositingMode = CompositingMode.SourceCopy; - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.SmoothingMode = SmoothingMode.HighQuality; - - g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize); - } - - return destination.Width; + g.CompositingMode = CompositingMode.SourceCopy; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.SmoothingMode = SmoothingMode.HighQuality; + + g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize); } + + return destination.Width; } + } - [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] - public int ImageSharp_P1() => this.RunImageSharpResize(1); + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] + public int ImageSharp_P1() => this.RunImageSharpResize(1); - // Parallel cases have been disabled for fast benchmark execution. - // Uncomment, if you are interested in parallel speedup + // 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) - { - this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; + protected int RunImageSharpResize(int maxDegreeOfParallelism) + { + this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; - using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) - { - return clone.Width; - } + using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) + { + return clone.Width; } - - protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); } - public class Resize_Bicubic_Rgba32 : Resize - { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS - 2019 April - ResizeWorker: - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC - // .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.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 - // - // 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 | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B | - // | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B | - } + protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); +} - /// - /// Is it worth to set a larger working buffer limit for resize? - /// Conclusion: It doesn't really have an effect. - /// - public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32 - { - [Params(128, 512, 1024, 8 * 1024)] - public int WorkingBufferSizeHintInKilobytes { get; set; } +public class Resize_Bicubic_Rgba32 : Resize +{ + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + + // RESULTS - 2019 April - ResizeWorker: + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.706 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742189 Hz, Resolution=364.6722 ns, Timer=TSC + // .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.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 + // + // 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 | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032-400 | 75.32 ms | 34.143 ms | 1.8715 ms | 0.63 | 0.02 | - | - | - | 16384 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032-400 | 120.33 ms | 6.669 ms | 0.3656 ms | 1.00 | 0.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032-400 | 88.56 ms | 1.864 ms | 0.1022 ms | 0.74 | 0.00 | - | - | - | 15568 B | +} - public override void Setup() - { - this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; - base.Setup(); - } - } +/// +/// Is it worth to set a larger working buffer limit for resize? +/// Conclusion: It doesn't really have an effect. +/// +public class Resize_Bicubic_Rgba32_CompareWorkBufferSizes : Resize_Bicubic_Rgba32 +{ + [Params(128, 512, 1024, 8 * 1024)] + public int WorkingBufferSizeHintInKilobytes { get; set; } - public class Resize_Bicubic_Bgra32 : Resize + public override void Setup() { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [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 | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | - // | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | + this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; + base.Setup(); } +} - public class Resize_Bicubic_Rgb24 : Resize - { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [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 | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | - // | | | | | | | | | | | | | | - // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | - // '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_Bicubic_Bgra32 : Resize +{ + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [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 | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 104.71 ms | 16.078 ms | 0.8813 ms | 0.88 | - | - | - | 45056 B | + // | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 121.58 ms | 50.084 ms | 2.7453 ms | 1.00 | - | - | - | 96 B | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | +} + +public class Resize_Bicubic_Rgb24 : Resize +{ + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [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 | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 99.36 ms | 11.356 ms | 0.6224 ms | 0.82 | 0.02 | - | - | - | 45056 B | + // | | | | | | | | | | | | | | + // SystemDrawing | Core | Core | 3032 | 400 | 118.06 ms | 15.667 ms | 0.8587 ms | 1.00 | 0.00 | - | - | - | 96 B | + // '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 : Resize +public class Resize_BicubicCompand_Rgba32 : Resize +{ + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); + + // RESULTS (2019 April): + // + // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) + // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores + // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC + // .NET Core SDK=2.1.602 + // [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 | + // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | + // | | | | | | | | | | | | | | + // 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 | +} + +public class Resize_Bicubic_Compare_Rgba32_Rgb24 +{ + private Resize_Bicubic_Rgb24 rgb24; + private Resize_Bicubic_Rgba32 rgba32; + + [GlobalSetup] + public void Setup() { - protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); - - // RESULTS (2019 April): - // - // BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.648 (1803/April2018Update/Redstone4) - // Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores - // Frequency=2742192 Hz, Resolution=364.6718 ns, Timer=TSC - // .NET Core SDK=2.1.602 - // [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 | - // 'ImageSharp, MaxDegreeOfParallelism = 1' | Clr | Clr | 3032 | 400 | 132.2 ms | 15.976 ms | 0.8757 ms | 1.10 | 0.04 | - | - | - | 16384 B | - // | | | | | | | | | | | | | | - // 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 | + this.rgb24 = new Resize_Bicubic_Rgb24(); + this.rgb24.Setup(); + this.rgba32 = new Resize_Bicubic_Rgba32(); + this.rgba32.Setup(); } - public class Resize_Bicubic_Compare_Rgba32_Rgb24 + [GlobalCleanup] + public void Cleanup() { - private Resize_Bicubic_Rgb24 rgb24; - private Resize_Bicubic_Rgba32 rgba32; - - [GlobalSetup] - public void Setup() - { - this.rgb24 = new Resize_Bicubic_Rgb24(); - this.rgb24.Setup(); - this.rgba32 = new Resize_Bicubic_Rgba32(); - this.rgba32.Setup(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.rgb24.Cleanup(); - this.rgba32.Cleanup(); - } + this.rgb24.Cleanup(); + this.rgba32.Cleanup(); + } - [Benchmark] - public void SystemDrawing() => this.rgba32.SystemDrawing(); + [Benchmark] + public void SystemDrawing() => this.rgba32.SystemDrawing(); - [Benchmark(Baseline = true)] - public void Rgba32() => this.rgba32.ImageSharp_P1(); + [Benchmark(Baseline = true)] + public void Rgba32() => this.rgba32.ImageSharp_P1(); - [Benchmark] - public void Rgb24() => this.rgb24.ImageSharp_P1(); - } + [Benchmark] + public void Rgb24() => this.rgb24.ImageSharp_P1(); } diff --git a/tests/ImageSharp.Benchmarks/Processing/Rotate.cs b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs index 313c5ba961..c392d99644 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs @@ -5,19 +5,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class Rotate { - [Config(typeof(Config.MultiFramework))] - public class Rotate + [Benchmark] + public Size DoRotate() { - [Benchmark] - public Size DoRotate() - { - using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); - image.Mutate(x => x.Rotate(37.5F)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Rotate(37.5F)); - return image.Size(); - } + return image.Size(); } } diff --git a/tests/ImageSharp.Benchmarks/Processing/Skew.cs b/tests/ImageSharp.Benchmarks/Processing/Skew.cs index 7a7d38def9..bc64382c19 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Skew.cs @@ -5,19 +5,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Processing +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.MultiFramework))] +public class Skew { - [Config(typeof(Config.MultiFramework))] - public class Skew + [Benchmark] + public Size DoSkew() { - [Benchmark] - public Size DoSkew() - { - using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); - image.Mutate(x => x.Skew(20, 10)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Skew(20, 10)); - return image.Size(); - } + return image.Size(); } } diff --git a/tests/ImageSharp.Benchmarks/Program.cs b/tests/ImageSharp.Benchmarks/Program.cs index 153e12d4b8..75e5a8233e 100644 --- a/tests/ImageSharp.Benchmarks/Program.cs +++ b/tests/ImageSharp.Benchmarks/Program.cs @@ -3,18 +3,17 @@ using BenchmarkDotNet.Running; -namespace SixLabors.ImageSharp.Benchmarks +namespace SixLabors.ImageSharp.Benchmarks; + +public class Program { - public class Program - { - /// - /// The main. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) => BenchmarkSwitcher - .FromAssembly(typeof(Program).Assembly) - .Run(args); - } + /// + /// The main. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) => BenchmarkSwitcher + .FromAssembly(typeof(Program).Assembly) + .Run(args); } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 9932990930..248912b14f 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -1,144 +1,141 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Text; -using System.Threading; using CommandLine; using CommandLine.Text; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; + +// See ImageSharp.Benchmarks/LoadResizeSave/README.md +internal class LoadResizeSaveParallelMemoryStress { - // See ImageSharp.Benchmarks/LoadResizeSave/README.md - internal class LoadResizeSaveParallelMemoryStress + private LoadResizeSaveParallelMemoryStress() { - private LoadResizeSaveParallelMemoryStress() + this.Benchmarks = new LoadResizeSaveStressRunner() { - this.Benchmarks = new LoadResizeSaveStressRunner() - { - Filter = JpegKind.Baseline, - }; - this.Benchmarks.Init(); - } + Filter = JpegKind.Baseline, + }; + this.Benchmarks.Init(); + } - private int gcFrequency; + private int gcFrequency; - private int leakFrequency; + private int leakFrequency; - private int imageCounter; + private int imageCounter; - public LoadResizeSaveStressRunner Benchmarks { get; } + public LoadResizeSaveStressRunner Benchmarks { get; } - public static void Run(string[] args) + public static void Run(string[] args) + { + Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); + Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); + CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; + + var lrs = new LoadResizeSaveParallelMemoryStress(); + if (options != null) { - Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); - Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); - CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; + lrs.Benchmarks.MaxDegreeOfParallelism = options.MaxDegreeOfParallelism; + } - var lrs = new LoadResizeSaveParallelMemoryStress(); - if (options != null) - { - lrs.Benchmarks.MaxDegreeOfParallelism = options.MaxDegreeOfParallelism; - } + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Stopwatch timer; - Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); - Stopwatch timer; + if (options == null || !(options.ImageSharp || options.AsyncImageSharp)) + { + RunBenchmarkSwitcher(lrs, out timer); + } + else + { + Console.WriteLine("Running ImageSharp with options:"); + Console.WriteLine(options.ToString()); - if (options == null || !(options.ImageSharp || options.AsyncImageSharp)) + if (!options.KeepDefaultAllocator) { - RunBenchmarkSwitcher(lrs, out timer); + Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); } - else - { - Console.WriteLine("Running ImageSharp with options:"); - Console.WriteLine(options.ToString()); - - if (!options.KeepDefaultAllocator) - { - Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); - } - lrs.leakFrequency = options.LeakFrequency; - lrs.gcFrequency = options.GcFrequency; + lrs.leakFrequency = options.LeakFrequency; + lrs.gcFrequency = options.GcFrequency; - timer = Stopwatch.StartNew(); - try + timer = Stopwatch.StartNew(); + try + { + for (int i = 0; i < options.RepeatCount; i++) { - for (int i = 0; i < options.RepeatCount; i++) + if (options.AsyncImageSharp) { - if (options.AsyncImageSharp) - { - lrs.ImageSharpBenchmarkParallelAsync(); - } - else - { - lrs.ImageSharpBenchmarkParallel(); - } - - Console.WriteLine("OK"); + lrs.ImageSharpBenchmarkParallelAsync(); } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - - timer.Stop(); - - if (options.ReleaseRetainedResourcesAtEnd) - { - Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); - } - - int finalGcCount = -Math.Min(0, options.GcFrequency); - - if (finalGcCount > 0) - { - Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}"); - Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait."); - for (int i = 0; i < finalGcCount; i++) + else { - Thread.Sleep(3000); - GC.Collect(); - GC.WaitForPendingFinalizers(); + lrs.ImageSharpBenchmarkParallel(); } + + Console.WriteLine("OK"); } } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } - var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); - Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}"); - Console.WriteLine(stats.GetMarkdown()); - if (options?.FileOutput != null) + timer.Stop(); + + if (options.ReleaseRetainedResourcesAtEnd) { - PrintFileOutput(options.FileOutput, stats); + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); } - if (options != null && options.PauseAtEnd) + int finalGcCount = -Math.Min(0, options.GcFrequency); + + if (finalGcCount > 0) { - Console.WriteLine("Press ENTER"); - Console.ReadLine(); + Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}"); + Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait."); + for (int i = 0; i < finalGcCount; i++) + { + Thread.Sleep(3000); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } } } - private static void PrintFileOutput(string fileOutput, Stats stats) + var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); + Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}"); + Console.WriteLine(stats.GetMarkdown()); + if (options?.FileOutput != null) { - string[] ss = fileOutput.Split(';'); - string fileName = ss[0]; - string content = ss[1] - .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) - .Replace("EOL", Environment.NewLine); - File.AppendAllText(fileName, content); + PrintFileOutput(options.FileOutput, stats); } - private static void RunBenchmarkSwitcher(LoadResizeSaveParallelMemoryStress lrs, out Stopwatch timer) + if (options != null && options.PauseAtEnd) { - Console.WriteLine(@"Choose a library for image resizing stress test: + Console.WriteLine("Press ENTER"); + Console.ReadLine(); + } + } + + private static void PrintFileOutput(string fileOutput, Stats stats) + { + string[] ss = fileOutput.Split(';'); + string fileName = ss[0]; + string content = ss[1] + .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) + .Replace("EOL", Environment.NewLine); + File.AppendAllText(fileName, content); + } + + private static void RunBenchmarkSwitcher(LoadResizeSaveParallelMemoryStress lrs, out Stopwatch timer) + { + Console.WriteLine(@"Choose a library for image resizing stress test: 1. System.Drawing 2. ImageSharp @@ -149,210 +146,209 @@ 6. NetVips 7. ImageMagick "); - ConsoleKey key = Console.ReadKey().Key; - if (key < ConsoleKey.D1 || key > ConsoleKey.D6) - { - Console.WriteLine("Unrecognized command."); - Environment.Exit(-1); - } - - timer = Stopwatch.StartNew(); + ConsoleKey key = Console.ReadKey().Key; + if (key < ConsoleKey.D1 || key > ConsoleKey.D6) + { + Console.WriteLine("Unrecognized command."); + Environment.Exit(-1); + } - switch (key) - { - case ConsoleKey.D1: - lrs.SystemDrawingBenchmarkParallel(); - break; - case ConsoleKey.D2: - lrs.ImageSharpBenchmarkParallel(); - break; - case ConsoleKey.D3: - lrs.MagicScalerBenchmarkParallel(); - break; - case ConsoleKey.D4: - lrs.SkiaBitmapBenchmarkParallel(); - break; - case ConsoleKey.D5: - lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel(); - break; - case ConsoleKey.D6: - lrs.NetVipsBenchmarkParallel(); - break; - case ConsoleKey.D7: - lrs.MagickBenchmarkParallel(); - break; - } + timer = Stopwatch.StartNew(); - timer.Stop(); + switch (key) + { + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaBitmapBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D7: + lrs.MagickBenchmarkParallel(); + break; } - private struct Stats - { - public double TotalSeconds { get; } + timer.Stop(); + } - public double TotalMegapixels { get; } + private struct Stats + { + public double TotalSeconds { get; } - public double MegapixelsPerSec { get; } + public double TotalMegapixels { get; } - public double MegapixelsPerSecPerCpu { get; } + public double MegapixelsPerSec { get; } - public Stats(Stopwatch sw, double totalMegapixels) - { - this.TotalMegapixels = totalMegapixels; - this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; - this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; - this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; - } + public double MegapixelsPerSecPerCpu { get; } - public string GetMarkdown() - { - var bld = new StringBuilder(); - bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); - bld.AppendLine( - $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); - - bld.Append("| "); - bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); - bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); - bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); - bld.AppendLine(" |"); - - return bld.ToString(); - - static string L(string header) => new('-', header.Length); - static string F(string column) => $"{{0,{column.Length}:f3}}"; - } + public Stats(Stopwatch sw, double totalMegapixels) + { + this.TotalMegapixels = totalMegapixels; + this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; + this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; + this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; } - private class CommandLineOptions + public string GetMarkdown() { - [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] - public bool AsyncImageSharp { get; set; } + var bld = new StringBuilder(); + bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); + bld.AppendLine( + $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); + + bld.Append("| "); + bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendLine(" |"); + + return bld.ToString(); + + static string L(string header) => new('-', header.Length); + static string F(string column) => $"{{0,{column.Length}:f3}}"; + } + } - [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] - public bool ImageSharp { get; set; } + private class CommandLineOptions + { + [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] + public bool AsyncImageSharp { get; set; } - [Option('d', "default-allocator", Required = false, Default = false, HelpText = "Keep default MemoryAllocator and ignore all settings")] - public bool KeepDefaultAllocator { get; set; } + [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] + public bool ImageSharp { get; set; } - [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] - public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; + [Option('d', "default-allocator", Required = false, Default = false, HelpText = "Keep default MemoryAllocator and ignore all settings")] + public bool KeepDefaultAllocator { get; set; } - [Option('s', "poolsize", Required = false, Default = 4096, HelpText = "The size of the pool in MegaBytes")] - public int MaxPoolSizeMegaBytes { get; set; } = 4096; + [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] + public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; - [Option('u', "max-nonpool", Required = false, Default = 32, HelpText = "Maximum size of non-pooled contiguous blocks in MegaBytes")] - public int MaxCapacityOfNonPoolBuffersMegaBytes { get; set; } = 32; + [Option('s', "poolsize", Required = false, Default = 4096, HelpText = "The size of the pool in MegaBytes")] + public int MaxPoolSizeMegaBytes { get; set; } = 4096; - [Option('p', "parallelism", Required = false, Default = -1, HelpText = "Level of parallelism")] - public int MaxDegreeOfParallelism { get; set; } = -1; + [Option('u', "max-nonpool", Required = false, Default = 32, HelpText = "Maximum size of non-pooled contiguous blocks in MegaBytes")] + public int MaxCapacityOfNonPoolBuffersMegaBytes { get; set; } = 32; - [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] - public int RepeatCount { get; set; } = 1; + [Option('p', "parallelism", Required = false, Default = -1, HelpText = "Level of parallelism")] + public int MaxDegreeOfParallelism { get; set; } = -1; - [Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")] - public int GcFrequency { get; set; } + [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] + public int RepeatCount { get; set; } = 1; - [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] - public bool ReleaseRetainedResourcesAtEnd { get; set; } + [Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")] + public int GcFrequency { get; set; } - [Option('w', "pause", Required = false, Default = false, HelpText = "Specify to pause and wait for user input after the execution")] - public bool PauseAtEnd { get; set; } + [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] + public bool ReleaseRetainedResourcesAtEnd { get; set; } - [Option('f', "file", Required = false, Default = null, HelpText = "Specify to print the execution time to a file. Format: ';' see the code for formatstr semantics.")] - public string FileOutput { get; set; } + [Option('w', "pause", Required = false, Default = false, HelpText = "Specify to pause and wait for user input after the execution")] + public bool PauseAtEnd { get; set; } - [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] - public int? TrimTimeSeconds { get; set; } + [Option('f', "file", Required = false, Default = null, HelpText = "Specify to print the execution time to a file. Format: ';' see the code for formatstr semantics.")] + public string FileOutput { get; set; } - [Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")] - public int LeakFrequency { get; set; } + [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] + public int? TrimTimeSeconds { get; set; } - public static CommandLineOptions Parse(string[] args) - { - CommandLineOptions result = null; - ParserResult parserResult = Parser.Default.ParseArguments(args).WithParsed(o => - { - result = o; - }); + [Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")] + public int LeakFrequency { get; set; } - if (result == null) - { - Console.WriteLine(HelpText.RenderUsageText(parserResult)); - } + public static CommandLineOptions Parse(string[] args) + { + CommandLineOptions result = null; + ParserResult parserResult = Parser.Default.ParseArguments(args).WithParsed(o => + { + result = o; + }); - return result; + if (result == null) + { + Console.WriteLine(HelpText.RenderUsageText(parserResult)); } - public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; + return result; + } + + public override string ToString() => + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.AsyncImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; - public MemoryAllocator CreateMemoryAllocator() + public MemoryAllocator CreateMemoryAllocator() + { + if (this.TrimTimeSeconds.HasValue) { - if (this.TrimTimeSeconds.HasValue) - { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), - new UniformUnmanagedMemoryPool.TrimSettings - { - TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 - }); - } - else - { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); - } + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), + new UniformUnmanagedMemoryPool.TrimSettings + { + TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 + }); + } + else + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); } - - private static long B(double megaBytes) => (long)(megaBytes * 1024 * 1024); } - private void ForEachImage(Action action) => this.Benchmarks.ForEachImageParallel(action); + private static long B(double megaBytes) => (long)(megaBytes * 1024 * 1024); + } + + private void ForEachImage(Action action) => this.Benchmarks.ForEachImageParallel(action); - private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - private void ImageSharpBenchmarkParallel() => - this.ForEachImage(f => + private void ImageSharpBenchmarkParallel() => + this.ForEachImage(f => + { + int cnt = Interlocked.Increment(ref this.imageCounter); + this.Benchmarks.ImageSharpResize(f); + if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0) { - int cnt = Interlocked.Increment(ref this.imageCounter); - this.Benchmarks.ImageSharpResize(f); - if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0) - { - _ = Configuration.Default.MemoryAllocator.Allocate(1 << 16); - Size size = this.Benchmarks.LastProcessedImageSize; - _ = new Image(size.Width, size.Height); - } + _ = Configuration.Default.MemoryAllocator.Allocate(1 << 16); + Size size = this.Benchmarks.LastProcessedImageSize; + _ = new Image(size.Width, size.Height); + } - if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0) - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - } - }); + if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + }); - private void ImageSharpBenchmarkParallelAsync() => - this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f)) - .GetAwaiter() - .GetResult(); + private void ImageSharpBenchmarkParallelAsync() => + this.Benchmarks.ForEachImageParallelAsync(f => this.Benchmarks.ImageSharpResizeAsync(f)) + .GetAwaiter() + .GetResult(); - private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); + private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); - private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); + private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); - private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); - } + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 5b5dedaab3..b5c8b70cde 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -using System.Threading; -using SixLabors.ImageSharp.Memory.Internals; -using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; using Xunit.Abstractions; @@ -14,68 +10,67 @@ #pragma warning disable SA1515 #pragma warning disable SA1512 -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; + +public class Program { - public class Program + private class ConsoleOutput : ITestOutputHelper { - private class ConsoleOutput : ITestOutputHelper - { - public void WriteLine(string message) => Console.WriteLine(message); + public void WriteLine(string message) => Console.WriteLine(message); - public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - } + public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); + } - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) + /// + /// The main entry point. Useful for executing benchmarks and performance unit tests manually, + /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. + /// + /// + /// The arguments to pass to the program. + /// + public static void Main(string[] args) + { + try { - try - { - LoadResizeSaveParallelMemoryStress.Run(args); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - - // RunJpegEncoderProfilingTests(); - // RunJpegColorProfilingTests(); - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); - - // Console.ReadLine(); + LoadResizeSaveParallelMemoryStress.Run(args); } - - private static Version GetNetCoreVersion() + catch (Exception ex) { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - Console.WriteLine(assembly.Location); - string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) - { - return Version.Parse(assemblyPath[netCoreAppIndex + 1]); - } - - return null; + Console.WriteLine(ex); } - private static void RunResizeProfilingTest() - { - var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); - test.ResizeBicubic(4000, 4000); - } + // RunJpegEncoderProfilingTests(); + // RunJpegColorProfilingTests(); + // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); + // RunResizeProfilingTest(); - private static void RunToVector4ProfilingTest() + // Console.ReadLine(); + } + + private static Version GetNetCoreVersion() + { + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + Console.WriteLine(assembly.Location); + string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { - var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput()); - tests.Benchmark_ToVector4(); + return Version.Parse(assemblyPath[netCoreAppIndex + 1]); } + + return null; + } + + private static void RunResizeProfilingTest() + { + var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); + test.ResizeBicubic(4000, 4000); + } + + private static void RunToVector4ProfilingTest() + { + var tests = new PixelOperationsTests.Rgba32_OperationsTests(new ConsoleOutput()); + tests.Benchmark_ToVector4(); } } diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 6ecb606f53..03e062cc0d 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -1,151 +1,147 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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 -namespace SixLabors.ImageSharp.Tests.Advanced +namespace SixLabors.ImageSharp.Tests.Advanced; + +public class AdvancedImageExtensionsTests { - public class AdvancedImageExtensionsTests + public class GetPixelMemoryGroup { - public class GetPixelMemoryGroup + [Theory] + [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 { - [Theory] - [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 - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - - using Image image = provider.GetImage(); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - // Act: - IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + using Image image = provider.GetImage(); - // Assert: - VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); - } + // Act: + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - [Theory] - [WithBlankImages(16, 16, PixelTypes.Rgba32)] - public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + // Assert: + VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); + } - IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - Memory memory = memoryGroup.Single(); + [Theory] + [WithBlankImages(16, 16, PixelTypes.Rgba32)] + public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - image.Mutate(c => c.Resize(8, 8)); + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + Memory memory = memoryGroup.Single(); - Assert.False(memoryGroup.IsValid); - Assert.ThrowsAny(() => _ = memoryGroup[0]); - Assert.ThrowsAny(() => _ = memory.Span); - } + image.Mutate(c => c.Resize(8, 8)); - [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]; + Assert.False(memoryGroup.IsValid); + Assert.ThrowsAny(() => _ = memoryGroup[0]); + Assert.ThrowsAny(() => _ = memory.Span); + } - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory sourceBuffer)); + [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]; - sourceBuffer.CopyTo(targetBuffer); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory sourceBuffer)); - var managerOfExternalMemory = new TestMemoryManager(targetBuffer); + sourceBuffer.CopyTo(targetBuffer); - Memory externalMemory = managerOfExternalMemory.Memory; + var managerOfExternalMemory = new TestMemoryManager(targetBuffer); - using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) - { - VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); - } + Memory externalMemory = managerOfExternalMemory.Memory; - // Make sure externalMemory works after destruction: - VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); } - private static void VerifyMemoryGroupDataMatchesTestPattern( - TestImageProvider provider, - IMemoryGroup memoryGroup, - Size size) - where TPixel : unmanaged, IPixel + // Make sure externalMemory works after destruction: + VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); + } + + private static void VerifyMemoryGroupDataMatchesTestPattern( + TestImageProvider provider, + IMemoryGroup memoryGroup, + Size size) + where TPixel : unmanaged, IPixel + { + Assert.True(memoryGroup.IsValid); + Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); + Assert.True(memoryGroup.BufferLength % size.Width == 0); + + int cnt = 0; + for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) { - Assert.True(memoryGroup.IsValid); - Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); - Assert.True(memoryGroup.BufferLength % size.Width == 0); - - int cnt = 0; - for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) - { - int y = cnt / size.Width; - int x = cnt % size.Width; - - TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); - TPixel actual = memoryGroup.GetElementAt(i); - Assert.Equal(expected, actual); - } + int y = cnt / size.Width; + int x = cnt % size.Width; + + TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); + TPixel actual = memoryGroup.GetElementAt(i); + Assert.Equal(expected, actual); } } + } - [Theory] - [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] - public void DangerousGetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void DangerousGetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + + using Image image = provider.GetImage(); + + for (int y = 0; y < image.Height; y++) { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + // Act: + Memory rowMemoryFromImage = image.DangerousGetPixelRowMemory(y); + Memory rowMemoryFromFrame = image.Frames.RootFrame.DangerousGetPixelRowMemory(y); + Span spanFromImage = rowMemoryFromImage.Span; + Span spanFromFrame = rowMemoryFromFrame.Span; - using Image image = provider.GetImage(); + Assert.Equal(spanFromFrame.Length, spanFromImage.Length); + Assert.True(Unsafe.AreSame(ref spanFromFrame[0], ref spanFromImage[0])); - for (int y = 0; y < image.Height; y++) + // Assert: + for (int x = 0; x < image.Width; x++) { - // Act: - Memory rowMemoryFromImage = image.DangerousGetPixelRowMemory(y); - Memory rowMemoryFromFrame = image.Frames.RootFrame.DangerousGetPixelRowMemory(y); - Span spanFromImage = rowMemoryFromImage.Span; - Span spanFromFrame = rowMemoryFromFrame.Span; - - Assert.Equal(spanFromFrame.Length, spanFromImage.Length); - Assert.True(Unsafe.AreSame(ref spanFromFrame[0], ref spanFromImage[0])); - - // Assert: - for (int x = 0; x < image.Width; x++) - { - Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), spanFromImage[x]); - } + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), spanFromImage[x]); } } + } - [Theory] - [WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)] - public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + [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.DangerousGetPixelRowMemory(3); - Memory memory10 = image.DangerousGetPixelRowMemory(10); + Memory memory3 = image.DangerousGetPixelRowMemory(3); + Memory memory10 = image.DangerousGetPixelRowMemory(10); - image.Mutate(c => c.Resize(8, 8)); + image.Mutate(c => c.Resize(8, 8)); - Assert.ThrowsAny(() => _ = memory3.Span); - Assert.ThrowsAny(() => _ = memory10.Span); - } + Assert.ThrowsAny(() => _ = memory3.Span); + Assert.ThrowsAny(() => _ = memory10.Span); } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 6003ca9a21..4091e0cd40 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -3,104 +3,101 @@ using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ColorTests { - public partial class ColorTests + public class CastFrom { - public class CastFrom + [Fact] + public void Rgba64() { - [Fact] - public void Rgba64() - { - var source = new Rgba64(100, 2222, 3333, 4444); - - // Act: - Color color = source; - - // Assert: - Rgba64 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - var source = new Rgba32(1, 22, 33, 231); - - // Act: - Color color = source; - - // Assert: - Rgba32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - var source = new Argb32(1, 22, 33, 231); - - // Act: - Color color = source; - - // Assert: - Argb32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - var source = new Bgra32(1, 22, 33, 231); - - // Act: - Color color = source; - - // Assert: - Bgra32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - var source = new Abgr32(1, 22, 33, 231); - - // Act: - Color color = source; - - // Assert: - Abgr32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - var source = new Rgb24(1, 22, 231); - - // Act: - Color color = source; - - // Assert: - Rgb24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - var source = new Bgr24(1, 22, 231); - - // Act: - Color color = source; - - // Assert: - Bgr24 data = color.ToPixel(); - Assert.Equal(source, data); - } + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + Color color = source; + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + Color color = source; + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + Color color = source; + + // Assert: + Bgr24 data = color.ToPixel(); + Assert.Equal(source, data); } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index e11fdcbb07..dacec71449 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -4,160 +4,157 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ColorTests { - public partial class ColorTests + public class CastTo { - public class CastTo + [Fact] + public void Rgba64() { - [Fact] - public void Rgba64() - { - var source = new Rgba64(100, 2222, 3333, 4444); - - // Act: - var color = new Color(source); - - // Assert: - Rgba64 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - var source = new Rgba32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Rgba32 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - var source = new Argb32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Argb32 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - var source = new Bgra32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Bgra32 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - var source = new Abgr32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Abgr32 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - var source = new Rgb24(1, 22, 231); - - // Act: - var color = new Color(source); - - // Assert: - Rgb24 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - var source = new Bgr24(1, 22, 231); - - // Act: - var color = new Color(source); - - // Assert: - Bgr24 data = color; - Assert.Equal(source, data); - } - - [Fact] - public void Vector4Constructor() - { - // Act: - Color color = new(Vector4.One); - - // Assert: - Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); - Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel()); - Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel()); - Assert.Equal(new L8(255), color.ToPixel()); - } - - [Fact] - public void GenericPixelRoundTrip() - { - AssertGenericPixelRoundTrip(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); - AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1)); - AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254)); - } - - private static void AssertGenericPixelRoundTrip(TPixel source) - where TPixel : unmanaged, IPixel - { - // Act: - var color = Color.FromPixel(source); - - // Assert: - TPixel actual = color.ToPixel(); - Assert.Equal(source, actual); - } - - [Fact] - public void GenericPixelDifferentPrecision() - { - AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535)); - AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255)); - AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255)); - AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255)); - } - - private static void AssertGenericPixelDifferentPrecision(TPixel source, TPixel2 expected) - where TPixel : unmanaged, IPixel - where TPixel2 : unmanaged, IPixel - { - // Act: - var color = Color.FromPixel(source); - - // Assert: - TPixel2 actual = color.ToPixel(); - Assert.Equal(expected, actual); - } + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + var color = new Color(source); + + // Assert: + Rgba64 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgba32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Argb32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgra32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgb24 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgr24 data = color; + Assert.Equal(source, data); + } + + [Fact] + public void Vector4Constructor() + { + // Act: + Color color = new(Vector4.One); + + // Assert: + Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); + Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel()); + Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel()); + Assert.Equal(new L8(255), color.ToPixel()); + } + + [Fact] + public void GenericPixelRoundTrip() + { + AssertGenericPixelRoundTrip(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); + AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254)); + } + + private static void AssertGenericPixelRoundTrip(TPixel source) + where TPixel : unmanaged, IPixel + { + // Act: + var color = Color.FromPixel(source); + + // Assert: + TPixel actual = color.ToPixel(); + Assert.Equal(source, actual); + } + + [Fact] + public void GenericPixelDifferentPrecision() + { + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535)); + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255)); + } + + private static void AssertGenericPixelDifferentPrecision(TPixel source, TPixel2 expected) + where TPixel : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + { + // Act: + var color = Color.FromPixel(source); + + // Assert: + TPixel2 actual = color.ToPixel(); + Assert.Equal(expected, actual); } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs index 1266ab7f23..3ea8623d94 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs @@ -3,104 +3,101 @@ using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ColorTests { - public partial class ColorTests + public class ConstructFrom { - public class ConstructFrom + [Fact] + public void Rgba64() { - [Fact] - public void Rgba64() - { - var source = new Rgba64(100, 2222, 3333, 4444); - - // Act: - var color = new Color(source); - - // Assert: - Rgba64 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgba32() - { - var source = new Rgba32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Rgba32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Argb32() - { - var source = new Argb32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Argb32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgra32() - { - var source = new Bgra32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Bgra32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Abgr32() - { - var source = new Abgr32(1, 22, 33, 231); - - // Act: - var color = new Color(source); - - // Assert: - Abgr32 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Rgb24() - { - var source = new Rgb24(1, 22, 231); - - // Act: - var color = new Color(source); - - // Assert: - Rgb24 data = color.ToPixel(); - Assert.Equal(source, data); - } - - [Fact] - public void Bgr24() - { - var source = new Bgr24(1, 22, 231); - - // Act: - var color = new Color(source); - - // Assert: - Bgr24 data = color.ToPixel(); - Assert.Equal(source, data); - } + var source = new Rgba64(100, 2222, 3333, 4444); + + // Act: + var color = new Color(source); + + // Assert: + Rgba64 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgba32() + { + var source = new Rgba32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgba32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Argb32() + { + var source = new Argb32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Argb32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgra32() + { + var source = new Bgra32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgra32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Rgb24() + { + var source = new Rgb24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Rgb24 data = color.ToPixel(); + Assert.Equal(source, data); + } + + [Fact] + public void Bgr24() + { + var source = new Bgr24(1, 22, 231); + + // Act: + var color = new Color(source); + + // Assert: + Bgr24 data = color.ToPixel(); + Assert.Equal(source, data); } } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index 2f6414925e..85e6eba78c 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -1,221 +1,217 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ColorTests { - public partial class ColorTests + [Fact] + public void WithAlpha() { - [Fact] - public void WithAlpha() + var c1 = Color.FromRgba(111, 222, 55, 255); + Color c2 = c1.WithAlpha(0.5f); + + var expected = new Rgba32(111, 222, 55, 128); + + Assert.Equal(expected, (Rgba32)c2); + } + + [Fact] + public void Equality_WhenTrue() + { + Color c1 = new Rgba64(100, 2000, 3000, 40000); + Color c2 = new Rgba64(100, 2000, 3000, 40000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1 == c2); + Assert.False(c1 != c2); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Color c1 = new Rgba64(100, 2000, 3000, 40000); + Color c2 = new Rgba64(101, 2000, 3000, 40000); + Color c3 = new Rgba64(100, 2000, 3000, 40001); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + + Assert.False(c1 == c2); + Assert.True(c1 != c2); + + Assert.False(c1.Equals(null)); + } + + [Fact] + public void ToHex() + { + string expected = "ABCD1234"; + var color = Color.ParseHex(expected); + string actual = color.ToHex(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void WebSafePalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); + + for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) { - var c1 = Color.FromRgba(111, 222, 55, 255); - Color c2 = c1.WithAlpha(0.5f); + Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); + } + } - var expected = new Rgba32(111, 222, 55, 128); + [Fact] + public void WernerPalette_IsCorrect() + { + Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(expected, (Rgba32)c2); + for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); } + } + public class FromHex + { [Fact] - public void Equality_WhenTrue() + public void ShortHex() { - Color c1 = new Rgba64(100, 2000, 3000, 40000); - Color c2 = new Rgba64(100, 2000, 3000, 40000); - - Assert.True(c1.Equals(c2)); - Assert.True(c1 == c2); - Assert.False(c1 != c2); - Assert.True(c1.GetHashCode() == c2.GetHashCode()); + 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 Equality_WhenFalse() + public void TryShortHex() { - Color c1 = new Rgba64(100, 2000, 3000, 40000); - Color c2 = new Rgba64(101, 2000, 3000, 40000); - Color c3 = new Rgba64(100, 2000, 3000, 40001); + Assert.True(Color.TryParseHex("#fff", out Color actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); - Assert.False(c1.Equals(c2)); - Assert.False(c2.Equals(c3)); - Assert.False(c3.Equals(c1)); + Assert.True(Color.TryParseHex("fff", out actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); - Assert.False(c1 == c2); - Assert.True(c1 != c2); - - Assert.False(c1.Equals(null)); + Assert.True(Color.TryParseHex("000f", out actual)); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)actual); } [Fact] - public void ToHex() + public void LeadingPoundIsOptional() { - string expected = "ABCD1234"; - var color = Color.ParseHex(expected); - string actual = color.ToHex(); - - Assert.Equal(expected, actual); + 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 WebSafePalette_IsCorrect() + public void ThrowsOnEmpty() { - Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); - - for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) - { - Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); - } + Assert.Throws(() => Color.ParseHex(string.Empty)); } [Fact] - public void WernerPalette_IsCorrect() + public void ThrowsOnInvalid() { - Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); - - for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) - { - Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); - } + Assert.Throws(() => Color.ParseHex("!")); } - public class FromHex + [Fact] + public void ThrowsOnNull() { - [Fact] - public void ShortHex() - { - 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.ParseHex("#008080")); - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080")); - } - - [Fact] - public void ThrowsOnEmpty() - { - Assert.Throws(() => Color.ParseHex(string.Empty)); - } - - [Fact] - public void ThrowsOnInvalid() - { - Assert.Throws(() => Color.ParseHex("!")); - } - - [Fact] - public void ThrowsOnNull() - { - Assert.Throws(() => Color.ParseHex(null)); - } + Assert.Throws(() => Color.ParseHex(null)); + } - [Fact] - public void FalseOnEmpty() - { - Assert.False(Color.TryParseHex(string.Empty, out Color _)); - } + [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 FalseOnInvalid() + { + Assert.False(Color.TryParseHex("!", out Color _)); + } - [Fact] - public void FalseOnNull() - { - Assert.False(Color.TryParseHex(null, out Color _)); - } + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParseHex(null, out Color _)); } + } - public class FromString + public class FromString + { + [Fact] + public void ColorNames() { - [Fact] - public void ColorNames() + foreach (string name in ReferencePalette.ColorNames.Keys) { - 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())); - } + 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() + [Fact] + public void TryColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) { - foreach (string name in ReferencePalette.ColorNames.Keys) - { - Rgba32 expected = ReferencePalette.ColorNames[name]; + Rgba32 expected = ReferencePalette.ColorNames[name]; - Assert.True(Color.TryParse(name, out Color actual)); - Assert.Equal(expected, (Rgba32)actual); + 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(name.ToLowerInvariant(), out actual)); + Assert.Equal(expected, (Rgba32)actual); - Assert.True(Color.TryParse(expected.ToHex(), 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 ThrowsOnEmpty() + { + Assert.Throws(() => Color.Parse(string.Empty)); + } - [Fact] - public void ThrowsOnInvalid() - { - Assert.Throws(() => Color.Parse("!")); - } + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.Parse("!")); + } - [Fact] - public void ThrowsOnNull() - { - Assert.Throws(() => Color.Parse(null)); - } + [Fact] + public void ThrowsOnNull() + { + Assert.Throws(() => Color.Parse(null)); + } - [Fact] - public void FalseOnEmpty() - { - Assert.False(Color.TryParse(string.Empty, out Color _)); - } + [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 FalseOnInvalid() + { + Assert.False(Color.TryParse("!", out Color _)); + } - [Fact] - public void FalseOnNull() - { - Assert.False(Color.TryParse(null, 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 5031a463d8..8e26961092 100644 --- a/tests/ImageSharp.Tests/Color/ReferencePalette.cs +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -1,432 +1,428 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +internal static class ReferencePalette { - internal static class ReferencePalette + /// + /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. + /// + public static readonly Color[] WebSafeColors = { - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static readonly Color[] WebSafeColors = - { - 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 - }; + 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 Color[] WernerColors = + /// + /// 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 Color[] WernerColors = + { + 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) { - 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") + { 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 } }; - - 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 } - }; - } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs index 0f476e1e05..52cb63d217 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -3,44 +3,42 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieLabTests { - /// - /// Tests the struct. - /// - public class CieLabTests + [Fact] + public void CieLabConstructorAssignsFields() { - [Fact] - public void CieLabConstructorAssignsFields() - { - const float l = 75F; - const float a = -64F; - const float b = 87F; - var cieLab = new CieLab(l, a, b); + const float l = 75F; + const float a = -64F; + const float b = 87F; + var cieLab = new CieLab(l, a, b); - Assert.Equal(l, cieLab.L); - Assert.Equal(a, cieLab.A); - Assert.Equal(b, cieLab.B); - } + Assert.Equal(l, cieLab.L); + Assert.Equal(a, cieLab.A); + Assert.Equal(b, cieLab.B); + } - [Fact] - public void CieLabEquality() - { - var x = default(CieLab); - var y = new CieLab(Vector3.One); + [Fact] + public void CieLabEquality() + { + var x = default(CieLab); + var y = new CieLab(Vector3.One); - Assert.True(default(CieLab) == default(CieLab)); - 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(new CieLab(1, 0, 1) == default(CieLab)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieLab) == default(CieLab)); + 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(new CieLab(1, 0, 1) == default(CieLab)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs index b9d12c1e08..83e4d8a59a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieLchTests { - /// - /// Tests the struct. - /// - public class CieLchTests + [Fact] + public void CieLchConstructorAssignsFields() { - [Fact] - public void CieLchConstructorAssignsFields() - { - const float l = 75F; - const float c = 64F; - const float h = 287F; - var cieLch = new CieLch(l, c, h); + const float l = 75F; + const float c = 64F; + const float h = 287F; + var cieLch = new CieLch(l, c, h); - Assert.Equal(l, cieLch.L); - Assert.Equal(c, cieLch.C); - Assert.Equal(h, cieLch.H); - } + Assert.Equal(l, cieLch.L); + Assert.Equal(c, cieLch.C); + Assert.Equal(h, cieLch.H); + } - [Fact] - public void CieLchEquality() - { - var x = default(CieLch); - var y = new CieLch(Vector3.One); + [Fact] + public void CieLchEquality() + { + var x = default(CieLch); + var y = new CieLch(Vector3.One); - Assert.True(default(CieLch) == default(CieLch)); - Assert.False(default(CieLch) != default(CieLch)); - Assert.Equal(default(CieLch), default(CieLch)); - Assert.Equal(new CieLch(1, 0, 1), new CieLch(1, 0, 1)); - Assert.Equal(new CieLch(Vector3.One), new CieLch(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieLch) == default(CieLch)); + Assert.False(default(CieLch) != default(CieLch)); + Assert.Equal(default(CieLch), default(CieLch)); + Assert.Equal(new CieLch(1, 0, 1), new CieLch(1, 0, 1)); + Assert.Equal(new CieLch(Vector3.One), new CieLch(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs index b7ce9b6e4b..f6c68aae91 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieLchuvTests { - /// - /// Tests the struct. - /// - public class CieLchuvTests + [Fact] + public void CieLchuvConstructorAssignsFields() { - [Fact] - public void CieLchuvConstructorAssignsFields() - { - const float l = 75F; - const float c = 64F; - const float h = 287F; - var cieLchuv = new CieLchuv(l, c, h); + const float l = 75F; + const float c = 64F; + const float h = 287F; + var cieLchuv = new CieLchuv(l, c, h); - Assert.Equal(l, cieLchuv.L); - Assert.Equal(c, cieLchuv.C); - Assert.Equal(h, cieLchuv.H); - } + Assert.Equal(l, cieLchuv.L); + Assert.Equal(c, cieLchuv.C); + Assert.Equal(h, cieLchuv.H); + } - [Fact] - public void CieLchuvEquality() - { - var x = default(CieLchuv); - var y = new CieLchuv(Vector3.One); + [Fact] + public void CieLchuvEquality() + { + var x = default(CieLchuv); + var y = new CieLchuv(Vector3.One); - Assert.True(default(CieLchuv) == default(CieLchuv)); - Assert.False(default(CieLchuv) != default(CieLchuv)); - Assert.Equal(default(CieLchuv), default(CieLchuv)); - Assert.Equal(new CieLchuv(1, 0, 1), new CieLchuv(1, 0, 1)); - Assert.Equal(new CieLchuv(Vector3.One), new CieLchuv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieLchuv) == default(CieLchuv)); + Assert.False(default(CieLchuv) != default(CieLchuv)); + Assert.Equal(default(CieLchuv), default(CieLchuv)); + Assert.Equal(new CieLchuv(1, 0, 1), new CieLchuv(1, 0, 1)); + Assert.Equal(new CieLchuv(Vector3.One), new CieLchuv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs index 41dd9c8aed..0ebf1bdeaa 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieLuvTests { - /// - /// Tests the struct. - /// - public class CieLuvTests + [Fact] + public void CieLuvConstructorAssignsFields() { - [Fact] - public void CieLuvConstructorAssignsFields() - { - const float l = 75F; - const float c = -64F; - const float h = 87F; - var cieLuv = new CieLuv(l, c, h); + const float l = 75F; + const float c = -64F; + const float h = 87F; + var cieLuv = new CieLuv(l, c, h); - Assert.Equal(l, cieLuv.L); - Assert.Equal(c, cieLuv.U); - Assert.Equal(h, cieLuv.V); - } + Assert.Equal(l, cieLuv.L); + Assert.Equal(c, cieLuv.U); + Assert.Equal(h, cieLuv.V); + } - [Fact] - public void CieLuvEquality() - { - var x = default(CieLuv); - var y = new CieLuv(Vector3.One); + [Fact] + public void CieLuvEquality() + { + var x = default(CieLuv); + var y = new CieLuv(Vector3.One); - Assert.True(default(CieLuv) == default(CieLuv)); - Assert.False(default(CieLuv) != default(CieLuv)); - Assert.Equal(default(CieLuv), default(CieLuv)); - Assert.Equal(new CieLuv(1, 0, 1), new CieLuv(1, 0, 1)); - Assert.Equal(new CieLuv(Vector3.One), new CieLuv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieLuv) == default(CieLuv)); + Assert.False(default(CieLuv) != default(CieLuv)); + Assert.Equal(default(CieLuv), default(CieLuv)); + Assert.Equal(new CieLuv(1, 0, 1), new CieLuv(1, 0, 1)); + Assert.Equal(new CieLuv(Vector3.One), new CieLuv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs index 1aa3059c91..061d6c432f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -2,42 +2,40 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieXyChromaticityCoordinatesTests { - /// - /// Tests the struct. - /// - public class CieXyChromaticityCoordinatesTests + [Fact] + public void CieXyChromaticityCoordinatesConstructorAssignsFields() { - [Fact] - public void CieXyChromaticityCoordinatesConstructorAssignsFields() - { - const float x = .75F; - const float y = .64F; - var coordinates = new CieXyChromaticityCoordinates(x, y); + const float x = .75F; + const float y = .64F; + var coordinates = new CieXyChromaticityCoordinates(x, y); - Assert.Equal(x, coordinates.X); - Assert.Equal(y, coordinates.Y); - } + Assert.Equal(x, coordinates.X); + Assert.Equal(y, coordinates.Y); + } - [Fact] - public void CieXyChromaticityCoordinatesEquality() - { - var x = default(CieXyChromaticityCoordinates); - var y = new CieXyChromaticityCoordinates(1, 1); + [Fact] + public void CieXyChromaticityCoordinatesEquality() + { + var x = default(CieXyChromaticityCoordinates); + var y = new CieXyChromaticityCoordinates(1, 1); - Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); - 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(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); + 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(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs index ac1352414d..f1eb126401 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieXyyTests { - /// - /// Tests the struct. - /// - public class CieXyyTests + [Fact] + public void CieXyyConstructorAssignsFields() { - [Fact] - public void CieXyyConstructorAssignsFields() - { - const float x = 75F; - const float y = 64F; - const float yl = 287F; - var cieXyy = new CieXyy(x, y, yl); + const float x = 75F; + const float y = 64F; + const float yl = 287F; + var cieXyy = new CieXyy(x, y, yl); - Assert.Equal(x, cieXyy.X); - Assert.Equal(y, cieXyy.Y); - Assert.Equal(y, cieXyy.Y); - } + Assert.Equal(x, cieXyy.X); + Assert.Equal(y, cieXyy.Y); + Assert.Equal(y, cieXyy.Y); + } - [Fact] - public void CieXyyEquality() - { - var x = default(CieXyy); - var y = new CieXyy(Vector3.One); + [Fact] + public void CieXyyEquality() + { + var x = default(CieXyy); + var y = new CieXyy(Vector3.One); - Assert.True(default(CieXyy) == default(CieXyy)); - Assert.False(default(CieXyy) != default(CieXyy)); - Assert.Equal(default(CieXyy), default(CieXyy)); - Assert.Equal(new CieXyy(1, 0, 1), new CieXyy(1, 0, 1)); - Assert.Equal(new CieXyy(Vector3.One), new CieXyy(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieXyy) == default(CieXyy)); + Assert.False(default(CieXyy) != default(CieXyy)); + Assert.Equal(default(CieXyy), default(CieXyy)); + Assert.Equal(new CieXyy(1, 0, 1), new CieXyy(1, 0, 1)); + Assert.Equal(new CieXyy(Vector3.One), new CieXyy(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs index 574b6ce356..6de961cf1b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CieXyzTests { - /// - /// Tests the struct. - /// - public class CieXyzTests + [Fact] + public void CieXyzConstructorAssignsFields() { - [Fact] - public void CieXyzConstructorAssignsFields() - { - const float x = 75F; - const float y = 64F; - const float z = 287F; - var cieXyz = new CieXyz(x, y, z); + const float x = 75F; + const float y = 64F; + const float z = 287F; + var cieXyz = new CieXyz(x, y, z); - Assert.Equal(x, cieXyz.X); - Assert.Equal(y, cieXyz.Y); - Assert.Equal(z, cieXyz.Z); - } + Assert.Equal(x, cieXyz.X); + Assert.Equal(y, cieXyz.Y); + Assert.Equal(z, cieXyz.Z); + } - [Fact] - public void CieXyzEquality() - { - var x = default(CieXyz); - var y = new CieXyz(Vector3.One); + [Fact] + public void CieXyzEquality() + { + var x = default(CieXyz); + var y = new CieXyz(Vector3.One); - Assert.True(default(CieXyz) == default(CieXyz)); - Assert.False(default(CieXyz) != default(CieXyz)); - Assert.Equal(default(CieXyz), default(CieXyz)); - Assert.Equal(new CieXyz(1, 0, 1), new CieXyz(1, 0, 1)); - Assert.Equal(new CieXyz(Vector3.One), new CieXyz(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(CieXyz) == default(CieXyz)); + Assert.False(default(CieXyz) != default(CieXyz)); + Assert.Equal(default(CieXyz), default(CieXyz)); + Assert.Equal(new CieXyz(1, 0, 1), new CieXyz(1, 0, 1)); + Assert.Equal(new CieXyz(Vector3.One), new CieXyz(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs index fc81019923..b4e55ed24c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs @@ -3,44 +3,42 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class CmykTests { - /// - /// Tests the struct. - /// - public class CmykTests + [Fact] + public void CmykConstructorAssignsFields() { - [Fact] - public void CmykConstructorAssignsFields() - { - const float c = .75F; - const float m = .64F; - const float y = .87F; - const float k = .334F; - var cmyk = new Cmyk(c, m, y, k); + const float c = .75F; + const float m = .64F; + const float y = .87F; + const float k = .334F; + var cmyk = new Cmyk(c, m, y, k); - Assert.Equal(c, cmyk.C); - Assert.Equal(m, cmyk.M); - Assert.Equal(y, cmyk.Y); - Assert.Equal(k, cmyk.K); - } + Assert.Equal(c, cmyk.C); + Assert.Equal(m, cmyk.M); + Assert.Equal(y, cmyk.Y); + Assert.Equal(k, cmyk.K); + } - [Fact] - public void CmykEquality() - { - var x = default(Cmyk); - var y = new Cmyk(Vector4.One); + [Fact] + public void CmykEquality() + { + var x = default(Cmyk); + var y = new Cmyk(Vector4.One); - Assert.True(default(Cmyk) == default(Cmyk)); - Assert.False(default(Cmyk) != default(Cmyk)); - Assert.Equal(default(Cmyk), default(Cmyk)); - Assert.Equal(new Cmyk(1, 0, 1, 0), new Cmyk(1, 0, 1, 0)); - Assert.Equal(new Cmyk(Vector4.One), new Cmyk(Vector4.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(Cmyk) == default(Cmyk)); + Assert.False(default(Cmyk) != default(Cmyk)); + Assert.Equal(default(Cmyk), default(Cmyk)); + Assert.Equal(new Cmyk(1, 0, 1, 0), new Cmyk(1, 0, 1, 0)); + Assert.Equal(new Cmyk(Vector4.One), new Cmyk(Vector4.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs index 95bf446c8f..3fb3598909 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -1,116 +1,113 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.ColorSpaces.Companding; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding +namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding; + +/// +/// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online. +/// +public class CompandingTests { - /// - /// Tests various companding algorithms. Expanded numbers are hand calculated from formulas online. - /// - public class CompandingTests + private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); + + [Fact] + public void Rec2020Companding_IsCorrect() { - private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); + const float input = .667F; + float e = Rec2020Companding.Expand(input); + float c = Rec2020Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4484759F, input); + } - [Fact] - public void Rec2020Companding_IsCorrect() - { - const float input = .667F; - float e = Rec2020Companding.Expand(input); - float c = Rec2020Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .4484759F, input); - } + [Fact] + public void Rec709Companding_IsCorrect() + { + const float input = .667F; + float e = Rec709Companding.Expand(input); + float c = Rec709Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4483577F, input); + } - [Fact] - public void Rec709Companding_IsCorrect() - { - const float input = .667F; - float e = Rec709Companding.Expand(input); - float c = Rec709Companding.Compress(e); - CompandingIsCorrectImpl(e, c, .4483577F, input); - } + [Fact] + public void SRgbCompanding_IsCorrect() + { + const float input = .667F; + float e = SRgbCompanding.Expand(input); + float c = SRgbCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .40242353F, input); + } - [Fact] - public void SRgbCompanding_IsCorrect() - { - const float input = .667F; - float e = SRgbCompanding.Expand(input); - float c = SRgbCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .40242353F, input); - } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void SRgbCompanding_Expand_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + var expected = new Vector4[source.Length]; - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void SRgbCompanding_Expand_VectorSpan(int length) + for (int i = 0; i < source.Length; i++) { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - var expected = new Vector4[source.Length]; + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Expand(ref s); + e = s; + } - for (int i = 0; i < source.Length; i++) - { - Vector4 s = source[i]; - ref Vector4 e = ref expected[i]; - SRgbCompanding.Expand(ref s); - e = s; - } + SRgbCompanding.Expand(source); - SRgbCompanding.Expand(source); + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void SRgbCompanding_Compress_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + var expected = new Vector4[source.Length]; - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - public void SRgbCompanding_Compress_VectorSpan(int length) + for (int i = 0; i < source.Length; i++) { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - var expected = new Vector4[source.Length]; - - for (int i = 0; i < source.Length; i++) - { - Vector4 s = source[i]; - ref Vector4 e = ref expected[i]; - SRgbCompanding.Compress(ref s); - e = s; - } + Vector4 s = source[i]; + ref Vector4 e = ref expected[i]; + SRgbCompanding.Compress(ref s); + e = s; + } - SRgbCompanding.Compress(source); + SRgbCompanding.Compress(source); - Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); - } + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } - [Fact] - public void GammaCompanding_IsCorrect() - { - const float gamma = 2.2F; - const float input = .667F; - float e = GammaCompanding.Expand(input, gamma); - float c = GammaCompanding.Compress(e, gamma); - CompandingIsCorrectImpl(e, c, .41027668F, input); - } + [Fact] + public void GammaCompanding_IsCorrect() + { + const float gamma = 2.2F; + const float input = .667F; + float e = GammaCompanding.Expand(input, gamma); + float c = GammaCompanding.Compress(e, gamma); + CompandingIsCorrectImpl(e, c, .41027668F, input); + } - [Fact] - public void LCompanding_IsCorrect() - { - const float input = .667F; - float e = LCompanding.Expand(input); - float c = LCompanding.Compress(e); - CompandingIsCorrectImpl(e, c, .36236193F, input); - } + [Fact] + public void LCompanding_IsCorrect() + { + const float input = .667F; + float e = LCompanding.Expand(input); + float c = LCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .36236193F, input); + } - private static void CompandingIsCorrectImpl(float e, float c, float expanded, float compressed) - { - Assert.Equal(expanded, e, FloatComparer); - Assert.Equal(compressed, c, FloatComparer); - } + private static void CompandingIsCorrectImpl(float e, float c, float expanded, float compressed) + { + Assert.Equal(expanded, e, FloatComparer); + Assert.Equal(compressed, c, FloatComparer); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs index aa710ccafd..d66a73b5a1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -1,240 +1,238 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Allows the approximate comparison of colorspace component values. +/// +internal readonly struct ApproximateColorSpaceComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer { + private readonly float epsilon; + /// - /// Allows the approximate comparison of colorspace component values. + /// Initializes a new instance of the class. /// - internal readonly struct ApproximateColorSpaceComparer : - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer - { - 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 bool Equals(Rgb x, Rgb y) - { - return this.Equals(x.R, y.R) - && this.Equals(x.G, y.G) - && this.Equals(x.B, y.B); - } + /// The comparison error difference epsilon to use. + public ApproximateColorSpaceComparer(float epsilon = 1F) => this.epsilon = epsilon; - /// - public int GetHashCode(Rgb obj) => obj.GetHashCode(); + /// + public bool Equals(Rgb x, Rgb y) + { + return this.Equals(x.R, y.R) + && this.Equals(x.G, y.G) + && this.Equals(x.B, y.B); + } - /// - public bool Equals(LinearRgb x, LinearRgb y) - { - return this.Equals(x.R, y.R) - && this.Equals(x.G, y.G) - && this.Equals(x.B, y.B); - } + /// + public int GetHashCode(Rgb obj) => obj.GetHashCode(); - /// - public int GetHashCode(LinearRgb obj) => obj.GetHashCode(); + /// + public bool Equals(LinearRgb x, LinearRgb y) + { + return this.Equals(x.R, y.R) + && this.Equals(x.G, y.G) + && this.Equals(x.B, y.B); + } - /// - public bool Equals(CieLab x, CieLab y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.A, y.A) - && this.Equals(x.B, y.B); - } + /// + public int GetHashCode(LinearRgb obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieLab obj) => obj.GetHashCode(); + /// + public bool Equals(CieLab x, CieLab y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.A, y.A) + && this.Equals(x.B, y.B); + } - /// - public bool Equals(CieLch x, CieLch y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.C, y.C) - && this.Equals(x.H, y.H); - } + /// + public int GetHashCode(CieLab obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieLch obj) => obj.GetHashCode(); + /// + public bool Equals(CieLch x, CieLch y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.C, y.C) + && this.Equals(x.H, y.H); + } - /// - public bool Equals(CieLchuv x, CieLchuv y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.C, y.C) - && this.Equals(x.H, y.H); - } + /// + public int GetHashCode(CieLch obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieLchuv obj) => obj.GetHashCode(); + /// + public bool Equals(CieLchuv x, CieLchuv y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.C, y.C) + && this.Equals(x.H, y.H); + } - /// - public bool Equals(CieLuv x, CieLuv y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.U, y.U) - && this.Equals(x.V, y.V); - } + /// + public int GetHashCode(CieLchuv obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieLuv obj) => obj.GetHashCode(); + /// + public bool Equals(CieLuv x, CieLuv y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.U, y.U) + && this.Equals(x.V, y.V); + } - /// - public bool Equals(CieXyz x, CieXyz y) - { - return this.Equals(x.X, y.X) - && this.Equals(x.Y, y.Y) - && this.Equals(x.Z, y.Z); - } + /// + public int GetHashCode(CieLuv obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieXyz obj) => obj.GetHashCode(); + /// + public bool Equals(CieXyz x, CieXyz y) + { + return this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Z, y.Z); + } - /// - public bool Equals(CieXyy x, CieXyy y) - { - return this.Equals(x.X, y.X) - && this.Equals(x.Y, y.Y) - && this.Equals(x.Yl, y.Yl); - } + /// + public int GetHashCode(CieXyz obj) => obj.GetHashCode(); - /// - public int GetHashCode(CieXyy obj) => obj.GetHashCode(); + /// + public bool Equals(CieXyy x, CieXyy y) + { + return this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Yl, y.Yl); + } - /// - public bool Equals(Cmyk x, Cmyk y) - { - return this.Equals(x.C, y.C) - && this.Equals(x.M, y.M) - && this.Equals(x.Y, y.Y) - && this.Equals(x.K, y.K); - } + /// + public int GetHashCode(CieXyy obj) => obj.GetHashCode(); - /// - public int GetHashCode(Cmyk obj) => obj.GetHashCode(); + /// + public bool Equals(Cmyk x, Cmyk y) + { + return this.Equals(x.C, y.C) + && this.Equals(x.M, y.M) + && this.Equals(x.Y, y.Y) + && this.Equals(x.K, y.K); + } - /// - public bool Equals(HunterLab x, HunterLab y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.A, y.A) - && this.Equals(x.B, y.B); - } + /// + public int GetHashCode(Cmyk obj) => obj.GetHashCode(); - /// - public int GetHashCode(HunterLab obj) => obj.GetHashCode(); + /// + public bool Equals(HunterLab x, HunterLab y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.A, y.A) + && this.Equals(x.B, y.B); + } - /// - public bool Equals(Hsl x, Hsl y) - { - return this.Equals(x.H, y.H) - && this.Equals(x.S, y.S) - && this.Equals(x.L, y.L); - } + /// + public int GetHashCode(HunterLab obj) => obj.GetHashCode(); - /// - public int GetHashCode(Hsl obj) => obj.GetHashCode(); + /// + public bool Equals(Hsl x, Hsl y) + { + return this.Equals(x.H, y.H) + && this.Equals(x.S, y.S) + && this.Equals(x.L, y.L); + } - /// - public bool Equals(Hsv x, Hsv y) - { - return this.Equals(x.H, y.H) - && this.Equals(x.S, y.S) - && this.Equals(x.V, y.V); - } + /// + public int GetHashCode(Hsl obj) => obj.GetHashCode(); - /// - public int GetHashCode(Hsv obj) => obj.GetHashCode(); + /// + public bool Equals(Hsv x, Hsv y) + { + return this.Equals(x.H, y.H) + && this.Equals(x.S, y.S) + && this.Equals(x.V, y.V); + } - /// - public bool Equals(Lms x, Lms y) - { - return this.Equals(x.L, y.L) - && this.Equals(x.M, y.M) - && this.Equals(x.S, y.S); - } + /// + public int GetHashCode(Hsv obj) => obj.GetHashCode(); - /// - public int GetHashCode(Lms obj) => obj.GetHashCode(); + /// + public bool Equals(Lms x, Lms y) + { + return this.Equals(x.L, y.L) + && this.Equals(x.M, y.M) + && this.Equals(x.S, y.S); + } - /// - public bool Equals(YCbCr x, YCbCr y) - { - return this.Equals(x.Y, y.Y) - && this.Equals(x.Cb, y.Cb) - && this.Equals(x.Cr, y.Cr); - } + /// + public int GetHashCode(Lms obj) => obj.GetHashCode(); - /// - public int GetHashCode(YCbCr obj) => obj.GetHashCode(); + /// + public bool Equals(YCbCr x, YCbCr y) + { + return this.Equals(x.Y, y.Y) + && this.Equals(x.Cb, y.Cb) + && this.Equals(x.Cr, y.Cr); + } + + /// + public int GetHashCode(YCbCr obj) => obj.GetHashCode(); - /// - public bool Equals(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + /// + public bool Equals(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); - /// - public int GetHashCode(CieXyChromaticityCoordinates obj) => obj.GetHashCode(); + /// + public int GetHashCode(CieXyChromaticityCoordinates obj) => obj.GetHashCode(); - /// - public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y) => this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); + /// + public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y) => this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); - /// - public int GetHashCode(RgbPrimariesChromaticityCoordinates obj) => obj.GetHashCode(); + /// + public int GetHashCode(RgbPrimariesChromaticityCoordinates obj) => obj.GetHashCode(); - /// - public bool Equals(GammaWorkingSpace x, GammaWorkingSpace y) + /// + public bool Equals(GammaWorkingSpace x, GammaWorkingSpace y) + { + if (x is GammaWorkingSpace g1 && y is GammaWorkingSpace g2) { - if (x is GammaWorkingSpace g1 && y is GammaWorkingSpace g2) - { - return this.Equals(g1.Gamma, g2.Gamma) - && this.Equals(g1.WhitePoint, g2.WhitePoint) - && this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates); - } - - return false; + return this.Equals(g1.Gamma, g2.Gamma) + && this.Equals(g1.WhitePoint, g2.WhitePoint) + && this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates); } - /// - public int GetHashCode(GammaWorkingSpace obj) => obj.GetHashCode(); + return false; + } - /// - public bool Equals(RgbWorkingSpace x, RgbWorkingSpace y) - { - return this.Equals(x.WhitePoint, y.WhitePoint) - && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); - } + /// + public int GetHashCode(GammaWorkingSpace obj) => obj.GetHashCode(); + + /// + public bool Equals(RgbWorkingSpace x, RgbWorkingSpace y) + { + return this.Equals(x.WhitePoint, y.WhitePoint) + && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); + } - /// - public int GetHashCode(RgbWorkingSpace obj) => obj.GetHashCode(); + /// + public int GetHashCode(RgbWorkingSpace obj) => obj.GetHashCode(); - private bool Equals(float x, float y) - { - float d = x - y; - return d >= -this.epsilon && d <= this.epsilon; - } + private bool Equals(float x, float y) + { + float d = x - y; + return d >= -this.epsilon && d <= this.epsilon; } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs index e2afb4d6d9..d71f0fa5e0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs @@ -1,95 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieLabAndCieLchConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - public class CieLabAndCieLchConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, 50, 180, 100, -50, 0)] - [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] - [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] - [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] - [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] - public void Convert_Lch_to_Lab(float l, float c, float h, float l2, float a, float b) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new CieLab(l2, a, b); + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieLab(l2, a, b); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, -50, 0, 100, 50, 180)] - [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] - [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] - [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] - [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] - public void Convert_Lab_to_Lch(float l, float a, float b, float l2, float c, float h) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new CieLch(l2, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + public void Convert_Lab_to_Lch(float l, float a, float b, float l2, float c, float h) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLch(l2, c, h); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs index 9c2a686233..d8378217e4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs @@ -1,83 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieLabAndCieLchuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - public class CieLabAndCieLchuvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(30.66194, 200, 352.7564, 31.95653, 116.8745, 2.388602)] + public void Convert_Lchuv_to_Lab(float l, float c, float h, float l2, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.66194, 200, 352.7564, 31.95653, 116.8745, 2.388602)] - public void Convert_Lchuv_to_Lab(float l, float c, float h, float l2, float a, float b) - { - // Arrange - var input = new CieLchuv(l, c, h); - var expected = new CieLab(l2, a, b); + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLab(l2, a, b); - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 30.66194, 200, 352.7564)] - public void Convert_Lab_to_Lchuv(float l, float a, float b, float l2, float c, float h) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new CieLchuv(l2, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 30.66194, 200, 352.7564)] + public void Convert_Lab_to_Lchuv(float l, float a, float b, float l2, float c, float h) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLchuv(l2, c, h); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLchuv[5]; + Span actualSpan = new CieLchuv[5]; - // Act - var actual = Converter.ToCieLchuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs index fd465d384a..220ef4f220 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs @@ -1,83 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieLabAndCieLuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - public class CieLabAndCieLuvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(10, 36.0555, 303.6901, 10.0151367, -23.9644356, 17.0226)] + public void Convert_CieLuv_to_CieLab(float l, float u, float v, float l2, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(10, 36.0555, 303.6901, 10.0151367, -23.9644356, 17.0226)] - public void Convert_CieLuv_to_CieLab(float l, float u, float v, float l2, float a, float b) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new CieLab(l2, a, b); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieLab(l2, a, b); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(10.0151367, -23.9644356, 17.0226, 10.0000038, -12.830183, 15.1829338)] - public void Convert_CieLab_to_CieLuv(float l, float a, float b, float l2, float u, float v) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new CieLuv(l2, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(10.0151367, -23.9644356, 17.0226, 10.0000038, -12.830183, 15.1829338)] + public void Convert_CieLab_to_CieLuv(float l, float a, float b, float l2, float u, float v) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieLuv(l2, u, v); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs index 325c11c06e..49ea4502fc 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndCieXyyConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndCieXyyConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.8644734, 0.06098868, 0.06509002, 36.05552, 275.6228, 10.01517)] + public void Convert_CieXyy_to_CieLab(float x, float y, float yl, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.8644734, 0.06098868, 0.06509002, 36.05552, 275.6228, 10.01517)] - public void Convert_CieXyy_to_CieLab(float x, float y, float yl, float l, float a, float b) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new CieLab(l, a, b); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLab(l, a, b); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 0.8644734, 0.06098868, 0.06509002)] - public void Convert_CieLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.8644734, 0.06098868, 0.06509002)] + public void Convert_CieLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs index dda567167b..7f9470e202 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndCmykConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndCmykConversionTests + [Theory] + [InlineData(0, 0, 0, 1, 0, 0, 0)] + [InlineData(0, 1, 0.6156551, 5.960464E-08, 55.063, 82.54871, 23.16506)] + public void Convert_Cmyk_to_CieLab(float c, float m, float y, float k, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 1, 0, 0, 0)] - [InlineData(0, 1, 0.6156551, 5.960464E-08, 55.063, 82.54871, 23.16506)] - public void Convert_Cmyk_to_CieLab(float c, float m, float y, float k, float l, float a, float b) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new CieLab(l, a, b); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLab(l, a, b); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(36.0555, 303.6901, 10.01514, 0, 1, 0.6156551, 5.960464E-08)] - public void Convert_CieLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(36.0555, 303.6901, 10.01514, 0, 1, 0.6156551, 5.960464E-08)] + public void Convert_CieLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs index 34c36d722c..1af6a9315f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(336.9393, 1, 0.5, 55.063, 82.54868, 23.16508)] + public void Convert_Hsl_to_CieLab(float h, float s, float ll, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(336.9393, 1, 0.5, 55.063, 82.54868, 23.16508)] - public void Convert_Hsl_to_CieLab(float h, float s, float ll, float l, float a, float b) - { - // Arrange - var input = new Hsl(h, s, ll); - var expected = new CieLab(l, a, b); + // Arrange + var input = new Hsl(h, s, ll); + var expected = new CieLab(l, a, b); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.5)] - public void Convert_CieLab_to_Hsl(float l, float a, float b, float h, float s, float ll) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new Hsl(h, s, ll); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.5)] + public void Convert_CieLab_to_Hsl(float l, float a, float b, float h, float s, float ll) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Hsl(h, s, ll); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = Converter.ToHsl(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs index 86f307eb3e..c7c3ad19b5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(336.9393, 1, 0.9999999, 55.063, 82.54871, 23.16504)] + public void Convert_Hsv_to_CieLab(float h, float s, float v, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(336.9393, 1, 0.9999999, 55.063, 82.54871, 23.16504)] - public void Convert_Hsv_to_CieLab(float h, float s, float v, float l, float a, float b) - { - // Arrange - var input = new Hsv(h, s, v); - var expected = new CieLab(l, a, b); + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieLab(l, a, b); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.9999999)] - public void Convert_CieLab_to_Hsv(float l, float a, float b, float h, float s, float v) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new Hsv(h, s, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 336.9393, 1, 0.9999999)] + public void Convert_CieLab_to_Hsv(float l, float a, float b, float h, float s, float v) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Hsv(h, s, v); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = Converter.ToHsv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs index 8435d25a89..81fff6e144 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndHunterLabConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndHunterLabConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(27.51646, 556.9392, -0.03974226, 36.05554, 275.6227, 10.01519)] + public void Convert_HunterLab_to_CieLab(float l2, float a2, float b2, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(27.51646, 556.9392, -0.03974226, 36.05554, 275.6227, 10.01519)] - public void Convert_HunterLab_to_CieLab(float l2, float a2, float b2, float l, float a, float b) - { - // Arrange - var input = new HunterLab(l2, a2, b2); - var expected = new CieLab(l, a, b); + // Arrange + var input = new HunterLab(l2, a2, b2); + var expected = new CieLab(l, a, b); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 27.51646, 556.9392, -0.03974226)] - public void Convert_CieLab_to_HunterLab(float l, float a, float b, float l2, float a2, float b2) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new HunterLab(l2, a2, b2); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 27.51646, 556.9392, -0.03974226)] + public void Convert_CieLab_to_HunterLab(float l, float a, float b, float l2, float a2, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new HunterLab(l2, a2, b2); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = Converter.ToHunterLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs index 9ecb4c7427..68547d8a22 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndLinearRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndLinearRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0.1221596, 55.063, 82.54871, 23.16505)] + public void Convert_LinearRgb_to_CieLab(float r, float g, float b2, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 0, 0.1221596, 55.063, 82.54871, 23.16505)] - public void Convert_LinearRgb_to_CieLab(float r, float g, float b2, float l, float a, float b) - { - // Arrange - var input = new LinearRgb(r, g, b2); - var expected = new CieLab(l, a, b); + // Arrange + var input = new LinearRgb(r, g, b2); + var expected = new CieLab(l, a, b); - Span inputSpan = new LinearRgb[5]; - inputSpan.Fill(input); + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 1, 0, 0.1221596)] - public void Convert_CieLab_to_LinearRgb(float l, float a, float b, float r, float g, float b2) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new LinearRgb(r, g, b2); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 1, 0, 0.1221596)] + public void Convert_CieLab_to_LinearRgb(float l, float a, float b, float r, float g, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new LinearRgb(r, g, b2); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new LinearRgb[5]; + Span actualSpan = new LinearRgb[5]; - // Act - var actual = Converter.ToLinearRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs index d85eae24c4..2918b6f62b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndLmsConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndLmsConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.8303261, -0.5776886, 0.1133359, 36.05553, 275.6228, 10.01518)] + public void Convert_Lms_to_CieLab(float l2, float m, float s, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.8303261, -0.5776886, 0.1133359, 36.05553, 275.6228, 10.01518)] - public void Convert_Lms_to_CieLab(float l2, float m, float s, float l, float a, float b) - { - // Arrange - var input = new Lms(l2, m, s); - var expected = new CieLab(l, a, b); + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLab(l, a, b); - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 0.8303261, -0.5776886, 0.1133359)] - public void Convert_CieLab_to_Lms(float l, float a, float b, float l2, float m, float s) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new Lms(l2, m, s); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.8303261, -0.5776886, 0.1133359)] + public void Convert_CieLab_to_Lms(float l, float a, float b, float l2, float m, float s) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Lms(l2, m, s); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Lms[5]; + Span actualSpan = new Lms[5]; - // Act - var actual = Converter.ToLms(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs index c80b3ae549..3acf695d12 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.9999999, 0, 0.384345, 55.063, 82.54871, 23.16505)] + public void Convert_Rgb_to_CieLab(float r, float g, float b2, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.9999999, 0, 0.384345, 55.063, 82.54871, 23.16505)] - public void Convert_Rgb_to_CieLab(float r, float g, float b2, float l, float a, float b) - { - // Arrange - var input = new Rgb(r, g, b2); - var expected = new CieLab(l, a, b); + // Arrange + var input = new Rgb(r, g, b2); + var expected = new CieLab(l, a, b); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 303.6901, 10.01514, 0.9999999, 0, 0.384345)] - public void Convert_CieLab_to_Rgb(float l, float a, float b, float r, float g, float b2) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new Rgb(r, g, b2); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 303.6901, 10.01514, 0.9999999, 0, 0.384345)] + public void Convert_CieLab_to_Rgb(float l, float a, float b, float r, float g, float b2) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new Rgb(r, g, b2); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = Converter.ToRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs index d35288dba4..f13e989a85 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLabAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLabAndYCbCrConversionTests + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(87.4179, 133.9763, 247.5308, 55.06287, 82.54838, 23.1697)] + public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(87.4179, 133.9763, 247.5308, 55.06287, 82.54838, 23.1697)] - public void Convert_YCbCr_to_CieLab(float y, float cb, float cr, float l, float a, float b) - { - // Arrange - var input = new YCbCr(y, cb, cr); - var expected = new CieLab(l, a, b); + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLab(l, a, b); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = Converter.ToCieLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(36.0555, 303.6901, 10.01514, 87.4179, 133.9763, 247.5308)] - public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr) - { - // Arrange - var input = new CieLab(l, a, b); - var expected = new YCbCr(y, cb, cr); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 303.6901, 10.01514, 87.4179, 133.9763, 247.5308)] + public void Convert_CieLab_to_YCbCr(float l, float a, float b, float y, float cb, float cr) + { + // Arrange + var input = new CieLab(l, a, b); + var expected = new YCbCr(y, cb, cr); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = Converter.ToYCbCr(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs index 6a7cd25d34..b50d47aeba 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndCieLuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndCieLuvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 34.89777, 187.6642, -7.181467)] + public void Convert_CieLch_to_CieLuv(float l, float c, float h, float l2, float u, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 34.89777, 187.6642, -7.181467)] - public void Convert_CieLch_to_CieLuv(float l, float c, float h, float l2, float u, float v) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new CieLuv(l2, u, v); + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieLuv(l2, u, v); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(34.89777, 187.6642, -7.181467, 36.05552, 103.6901, 10.01514)] - public void Convert_CieLuv_to_CieLch(float l2, float u, float v, float l, float c, float h) - { - // Arrange - var input = new CieLuv(l2, u, v); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(34.89777, 187.6642, -7.181467, 36.05552, 103.6901, 10.01514)] + public void Convert_CieLuv_to_CieLch(float l2, float u, float v, float l, float c, float h) + { + // Arrange + var input = new CieLuv(l2, u, v); + var expected = new CieLch(l, c, h); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs index 85fd58f286..93f0828861 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndCieXyyConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndCieXyyConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.6529307, 0.2147411, 0.08447381)] + public void Convert_CieLch_to_CieXyy(float l, float c, float h, float x, float y, float yl) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.6529307, 0.2147411, 0.08447381)] - public void Convert_CieLch_to_CieXyy(float l, float c, float h, float x, float y, float yl) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new CieXyy(x, y, yl); + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.6529307, 0.2147411, 0.08447381, 36.05552, 103.6901, 10.01515)] - public void Convert_CieXyy_to_CieLch(float x, float y, float yl, float l, float c, float h) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.6529307, 0.2147411, 0.08447381, 36.05552, 103.6901, 10.01515)] + public void Convert_CieXyy_to_CieLch(float x, float y, float yl, float l, float c, float h) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLch(l, c, h); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs index 929b0926c9..0bc76562b1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.4207301)] + public void Convert_CieLch_to_Hsl(float l, float c, float h, float h2, float s, float l2) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.4207301)] - public void Convert_CieLch_to_Hsl(float l, float c, float h, float h2, float s, float l2) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new Hsl(h2, s, l2); + // Arrange + var input = new CieLch(l, c, h); + var expected = new Hsl(h2, s, l2); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = Converter.ToHsl(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(341.959, 1, 0.4207301, 46.13444, 78.0637, 22.90503)] - public void Convert_Hsl_to_CieLch(float h2, float s, float l2, float l, float c, float h) - { - // Arrange - var input = new Hsl(h2, s, l2); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(341.959, 1, 0.4207301, 46.13444, 78.0637, 22.90503)] + public void Convert_Hsl_to_CieLch(float h2, float s, float l2, float l, float c, float h) + { + // Arrange + var input = new Hsl(h2, s, l2); + var expected = new CieLch(l, c, h); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs index 0c04b4f629..bb28c03946 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.8414602)] + public void Convert_CieLch_to_Hsv(float l, float c, float h, float h2, float s, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 341.959, 1, 0.8414602)] - public void Convert_CieLch_to_Hsv(float l, float c, float h, float h2, float s, float v) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new Hsv(h2, s, v); + // Arrange + var input = new CieLch(l, c, h); + var expected = new Hsv(h2, s, v); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = Converter.ToHsv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(341.959, 1, 0.8414602, 46.13444, 78.0637, 22.90501)] - public void Convert_Hsv_to_CieLch(float h2, float s, float v, float l, float c, float h) - { - // Arrange - var input = new Hsv(h2, s, v); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(341.959, 1, 0.8414602, 46.13444, 78.0637, 22.90501)] + public void Convert_Hsv_to_CieLch(float h2, float s, float v, float l, float c, float h) + { + // Arrange + var input = new Hsv(h2, s, v); + var expected = new CieLch(l, c, h); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs index 93d8933f00..9892ac8f57 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndHunterLabConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndHunterLabConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 29.41358, 106.6302, 9.102425)] + public void Convert_CieLch_to_HunterLab(float l, float c, float h, float l2, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 29.41358, 106.6302, 9.102425)] - public void Convert_CieLch_to_HunterLab(float l, float c, float h, float l2, float a, float b) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new HunterLab(l2, a, b); + // Arrange + var input = new CieLch(l, c, h); + var expected = new HunterLab(l2, a, b); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = Converter.ToHunterLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(29.41358, 106.6302, 9.102425, 36.05551, 103.6901, 10.01515)] - public void Convert_HunterLab_to_CieLch(float l2, float a, float b, float l, float c, float h) - { - // Arrange - var input = new HunterLab(l2, a, b); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(29.41358, 106.6302, 9.102425, 36.05551, 103.6901, 10.01515)] + public void Convert_HunterLab_to_CieLch(float l2, float a, float b, float l, float c, float h) + { + // Arrange + var input = new HunterLab(l2, a, b); + var expected = new CieLch(l, c, h); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs index 2ac9ad0c80..3ecfadf113 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndLinearRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndLinearRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.6765013, 0, 0.05209038)] + public void Convert_CieLch_to_LinearRgb(float l, float c, float h, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.6765013, 0, 0.05209038)] - public void Convert_CieLch_to_LinearRgb(float l, float c, float h, float r, float g, float b) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new LinearRgb(r, g, b); + // Arrange + var input = new CieLch(l, c, h); + var expected = new LinearRgb(r, g, b); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new LinearRgb[5]; + Span actualSpan = new LinearRgb[5]; - // Act - var actual = Converter.ToLinearRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.6765013, 0, 0.05209038, 46.13445, 78.06367, 22.90504)] - public void Convert_LinearRgb_to_CieLch(float r, float g, float b, float l, float c, float h) - { - // Arrange - var input = new LinearRgb(r, g, b); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.6765013, 0, 0.05209038, 46.13445, 78.06367, 22.90504)] + public void Convert_LinearRgb_to_CieLch(float r, float g, float b, float l, float c, float h) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieLch(l, c, h); - Span inputSpan = new LinearRgb[5]; - inputSpan.Fill(input); + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs index 88a25ba59b..7a19391211 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndLmsConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndLmsConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.2440057, -0.04603009, 0.05780027)] + public void Convert_CieLch_to_Lms(float l, float c, float h, float l2, float m, float s) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.2440057, -0.04603009, 0.05780027)] - public void Convert_CieLch_to_Lms(float l, float c, float h, float l2, float m, float s) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new Lms(l2, m, s); + // Arrange + var input = new CieLch(l, c, h); + var expected = new Lms(l2, m, s); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new Lms[5]; + Span actualSpan = new Lms[5]; - // Act - var actual = Converter.ToLms(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.2440057, -0.04603009, 0.05780027, 36.05552, 103.6901, 10.01515)] - public void Convert_Lms_to_CieLch(float l2, float m, float s, float l, float c, float h) - { - // Arrange - var input = new Lms(l2, m, s); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.2440057, -0.04603009, 0.05780027, 36.05552, 103.6901, 10.01515)] + public void Convert_Lms_to_CieLch(float l2, float m, float s, float l, float c, float h) + { + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLch(l, c, h); - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs index f512f8e821..013e79534d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.8414602, 0, 0.2530123)] + public void Convert_CieLch_to_Rgb(float l, float c, float h, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.8414602, 0, 0.2530123)] - public void Convert_CieLch_to_Rgb(float l, float c, float h, float r, float g, float b) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new Rgb(r, g, b); + // Arrange + var input = new CieLch(l, c, h); + var expected = new Rgb(r, g, b); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = Converter.ToRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.8414602, 0, 0.2530123, 46.13444, 78.0637, 22.90503)] - public void Convert_Rgb_to_CieLch(float r, float g, float b, float l, float c, float h) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.8414602, 0, 0.2530123, 46.13444, 78.0637, 22.90503)] + public void Convert_Rgb_to_CieLch(float r, float g, float b, float l, float c, float h) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieLch(l, c, h); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs index 09b8de845a..bcde97102f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchAndYCbCrConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 103.6901, 10.01514, 71.5122, 124.053, 230.0401)] + public void Convert_CieLch_to_YCbCr(float l, float c, float h, float y, float cb, float cr) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(36.0555, 103.6901, 10.01514, 71.5122, 124.053, 230.0401)] - public void Convert_CieLch_to_YCbCr(float l, float c, float h, float y, float cb, float cr) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new YCbCr(y, cb, cr); + // Arrange + var input = new CieLch(l, c, h); + var expected = new YCbCr(y, cb, cr); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = Converter.ToYCbCr(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(71.5122, 124.053, 230.0401, 46.23178, 78.1114, 22.7662)] - public void Convert_YCbCr_to_CieLch(float y, float cb, float cr, float l, float c, float h) - { - // Arrange - var input = new YCbCr(y, cb, cr); - var expected = new CieLch(l, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(71.5122, 124.053, 230.0401, 46.23178, 78.1114, 22.7662)] + public void Convert_YCbCr_to_CieLch(float y, float cb, float cr, float l, float c, float h) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLch(l, c, h); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs index 8bc790d8fd..1098de91e0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchuvAndCieLchConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchuvAndCieLchConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.73742, 64.79149, 30.1786, 36.0555, 103.6901, 10.01513)] + public void Convert_CieLch_to_CieLchuv(float l2, float c2, float h2, float l, float c, float h) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.73742, 64.79149, 30.1786, 36.0555, 103.6901, 10.01513)] - public void Convert_CieLch_to_CieLchuv(float l2, float c2, float h2, float l, float c, float h) - { - // Arrange - var input = new CieLch(l2, c2, h2); - var expected = new CieLchuv(l, c, h); + // Arrange + var input = new CieLch(l2, c2, h2); + var expected = new CieLchuv(l, c, h); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLchuv[5]; + Span actualSpan = new CieLchuv[5]; - // Act - var actual = Converter.ToCieLchuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(36.0555, 103.6901, 10.01514, 36.73742, 64.79149, 30.1786)] - public void Convert_CieLchuv_to_CieLch(float l, float c, float h, float l2, float c2, float h2) - { - // Arrange - var input = new CieLchuv(l, c, h); - var expected = new CieLch(l2, c2, h2); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(36.0555, 103.6901, 10.01514, 36.73742, 64.79149, 30.1786)] + public void Convert_CieLchuv_to_CieLch(float l, float c, float h, float l2, float c2, float h2) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLch(l2, c2, h2); - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs index dcc905fbc2..ba3bc9e799 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -1,96 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieLchuvAndCieLuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - public class CieLchuvAndCieLuvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, 50, 180, 100, -50, 0)] + [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] + [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] + [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] + [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] + public void Convert_CieLchuv_to_CieLuv(float l, float c, float h, float l2, float u, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 106.8391, 40.8526, 54.2917, 80.8125, 69.8851)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, 50, 180, 100, -50, 0)] - [InlineData(10, 36.0555, 56.3099, 10, 20, 30)] - [InlineData(10, 36.0555, 123.6901, 10, -20, 30)] - [InlineData(10, 36.0555, 303.6901, 10, 20, -30)] - [InlineData(10, 36.0555, 236.3099, 10, -20, -30)] - public void Convert_CieLchuv_to_CieLuv(float l, float c, float h, float l2, float u, float v) - { - // Arrange - var input = new CieLchuv(l, c, h); - var expected = new CieLuv(l2, u, v); + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieLuv(l2, u, v); - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] - [InlineData(100, 0, 0, 100, 0, 0)] - [InlineData(100, -50, 0, 100, 50, 180)] - [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] - [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] - [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] - [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] - [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] - public void Convert_CieLuv_to_CieLchuv(float l, float u, float v, float l2, float c, float h) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new CieLchuv(l2, c, h); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(54.2917, 80.8125, 69.8851, 54.2917, 106.8391, 40.8526)] + [InlineData(100, 0, 0, 100, 0, 0)] + [InlineData(100, -50, 0, 100, 50, 180)] + [InlineData(10, 20, 30, 10, 36.0555, 56.3099)] + [InlineData(10, -20, 30, 10, 36.0555, 123.6901)] + [InlineData(10, 20, -30, 10, 36.0555, 303.6901)] + [InlineData(10, -20, -30, 10, 36.0555, 236.3099)] + [InlineData(37.3511, 24.1720, 16.0684, 37.3511, 29.0255, 33.6141)] + public void Convert_CieLuv_to_CieLchuv(float l, float u, float v, float l2, float c, float h) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieLchuv(l2, c, h); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLchuv[5]; + Span actualSpan = new CieLchuv[5]; - // Act - var actual = Converter.ToCieLchuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs index cae1ce3ed4..176a0e2175 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLchuvAndCmykConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLchuvAndCmykConversionTests + [Theory] + [InlineData(0, 0, 0, 1, 0, 0, 0)] + [InlineData(0, 0.8576171, 0.7693201, 0.3440427, 36.0555, 103.6901, 10.01514)] + public void Convert_Cmyk_to_CieLchuv(float c2, float m, float y, float k, float l, float c, float h) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 1, 0, 0, 0)] - [InlineData(0, 0.8576171, 0.7693201, 0.3440427, 36.0555, 103.6901, 10.01514)] - public void Convert_Cmyk_to_CieLchuv(float c2, float m, float y, float k, float l, float c, float h) - { - // Arrange - var input = new Cmyk(c2, m, y, k); - var expected = new CieLchuv(l, c, h); + // Arrange + var input = new Cmyk(c2, m, y, k); + var expected = new CieLchuv(l, c, h); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLchuv[5]; + Span actualSpan = new CieLchuv[5]; - // Act - var actual = Converter.ToCieLchuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(36.0555, 103.6901, 10.01514, 0, 0.8576171, 0.7693201, 0.3440427)] - public void Convert_CieLchuv_to_Cmyk(float l, float c, float h, float c2, float m, float y, float k) - { - // Arrange - var input = new CieLchuv(l, c, h); - var expected = new Cmyk(c2, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(36.0555, 103.6901, 10.01514, 0, 0.8576171, 0.7693201, 0.3440427)] + public void Convert_CieLchuv_to_Cmyk(float l, float c, float h, float c2, float m, float y, float k) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new Cmyk(c2, m, y, k); - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs index b981f3dd2e..e32cc6ebfc 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndCieXyyConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndCieXyyConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 103.6901, 10.01514, 0.5646762, 0.2932749, 0.09037033)] + public void Convert_CieLuv_to_CieXyy(float l, float u, float v, float x, float y, float yl) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 103.6901, 10.01514, 0.5646762, 0.2932749, 0.09037033)] - public void Convert_CieLuv_to_CieXyy(float l, float u, float v, float x, float y, float yl) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new CieXyy(x, y, yl); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5646762, 0.2932749, 0.09037033, 36.0555, 103.6901, 10.01514)] - public void Convert_CieXyy_to_CieLuv(float x, float y, float yl, float l, float u, float v) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5646762, 0.2932749, 0.09037033, 36.0555, 103.6901, 10.01514)] + public void Convert_CieXyy_to_CieLuv(float x, float y, float yl, float l, float u, float v) + { + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new CieLuv(l, u, v); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs index c6636cb71f..95f07465ab 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.7115612, 0.3765343)] + public void Convert_CieLuv_to_Hsl(float l, float u, float v, float h, float s, float l2) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.7115612, 0.3765343)] - public void Convert_CieLuv_to_Hsl(float l, float u, float v, float h, float s, float l2) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new Hsl(h, s, l2); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Hsl(h, s, l2); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = Converter.ToHsl(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(347.3767, 0.7115612, 0.3765343, 36.0555, 93.69012, 10.01514)] - public void Convert_Hsl_to_CieLuv(float h, float s, float l2, float l, float u, float v) - { - // Arrange - var input = new Hsl(h, s, l2); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(347.3767, 0.7115612, 0.3765343, 36.0555, 93.69012, 10.01514)] + public void Convert_Hsl_to_CieLuv(float h, float s, float l2, float l, float u, float v) + { + // Arrange + var input = new Hsl(h, s, l2); + var expected = new CieLuv(l, u, v); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs index 11e3530292..ddb90f0ef9 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.8314762, 0.6444615)] + public void Convert_CieLuv_to_Hsv(float l, float u, float v, float h, float s, float v2) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 347.3767, 0.8314762, 0.6444615)] - public void Convert_CieLuv_to_Hsv(float l, float u, float v, float h, float s, float v2) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new Hsv(h, s, v2); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Hsv(h, s, v2); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = Converter.ToHsv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(347.3767, 0.8314762, 0.6444615, 36.0555, 93.69012, 10.01514)] - public void Convert_Hsv_to_CieLuv(float h, float s, float v2, float l, float u, float v) - { - // Arrange - var input = new Hsv(h, s, v2); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(347.3767, 0.8314762, 0.6444615, 36.0555, 93.69012, 10.01514)] + public void Convert_Hsv_to_CieLuv(float h, float s, float v2, float l, float u, float v) + { + // Arrange + var input = new Hsv(h, s, v2); + var expected = new CieLuv(l, u, v); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs index 03a05a53ca..38449e4b77 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndHunterLabConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndHunterLabConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 30.19531, 46.4312, 11.16259)] + public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 30.19531, 46.4312, 11.16259)] - public void Convert_CieLuv_to_HunterLab(float l, float u, float v, float l2, float a, float b) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new HunterLab(l2, a, b); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new HunterLab(l2, a, b); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = Converter.ToHunterLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(30.19531, 46.4312, 11.16259, 36.0555, 93.6901, 10.01514)] - public void Convert_HunterLab_to_CieLuv(float l2, float a, float b, float l, float u, float v) - { - // Arrange - var input = new HunterLab(l2, a, b); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(30.19531, 46.4312, 11.16259, 36.0555, 93.6901, 10.01514)] + public void Convert_HunterLab_to_CieLuv(float l2, float a, float b, float l, float u, float v) + { + // Arrange + var input = new HunterLab(l2, a, b); + var expected = new CieLuv(l, u, v); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs index 9bf78746da..f3bd936043 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndLinearRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndLinearRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.3729299, 0.01141088, 0.04014909)] + public void Convert_CieLuv_to_LinearRgb(float l, float u, float v, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 0.3729299, 0.01141088, 0.04014909)] - public void Convert_CieLuv_to_LinearRgb(float l, float u, float v, float r, float g, float b) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new LinearRgb(r, g, b); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new LinearRgb(r, g, b); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new LinearRgb[5]; + Span actualSpan = new LinearRgb[5]; - // Act - var actual = Converter.ToLinearRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.3729299, 0.01141088, 0.04014909, 36.0555, 93.6901, 10.01511)] - public void Convert_LinearRgb_to_CieLuv(float r, float g, float b, float l, float u, float v) - { - // Arrange - var input = new LinearRgb(r, g, b); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.3729299, 0.01141088, 0.04014909, 36.0555, 93.6901, 10.01511)] + public void Convert_LinearRgb_to_CieLuv(float r, float g, float b, float l, float u, float v) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieLuv(l, u, v); - Span inputSpan = new LinearRgb[5]; - inputSpan.Fill(input); + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs index e029687b26..ac90a7ba41 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndLmsConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndLmsConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.164352, 0.03267485, 0.0483408)] + public void Convert_CieLuv_to_Lms(float l, float u, float v, float l2, float m, float s) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 0.164352, 0.03267485, 0.0483408)] - public void Convert_CieLuv_to_Lms(float l, float u, float v, float l2, float m, float s) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new Lms(l2, m, s); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Lms(l2, m, s); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Lms[5]; + Span actualSpan = new Lms[5]; - // Act - var actual = Converter.ToLms(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.164352, 0.03267485, 0.0483408, 36.0555, 93.69009, 10.01514)] - public void Convert_Lms_to_CieLuv(float l2, float m, float s, float l, float u, float v) - { - // Arrange - var input = new Lms(l2, m, s); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.164352, 0.03267485, 0.0483408, 36.0555, 93.69009, 10.01514)] + public void Convert_Lms_to_CieLuv(float l2, float m, float s, float l, float u, float v) + { + // Arrange + var input = new Lms(l2, m, s); + var expected = new CieLuv(l, u, v); - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs index 5797992c24..b2e308fce0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(36.0555, 93.6901, 10.01514, 0.6444615, 0.1086071, 0.2213444)] + public void Convert_CieLuv_to_Rgb(float l, float u, float v, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(36.0555, 93.6901, 10.01514, 0.6444615, 0.1086071, 0.2213444)] - public void Convert_CieLuv_to_Rgb(float l, float u, float v, float r, float g, float b) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new Rgb(r, g, b); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Rgb(r, g, b); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = Converter.ToRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.6444615, 0.1086071, 0.2213444, 36.0555, 93.69012, 10.01514)] - public void Convert_Rgb_to_CieLuv(float r, float g, float b, float l, float u, float v) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.6444615, 0.1086071, 0.2213444, 36.0555, 93.69012, 10.01514)] + public void Convert_Rgb_to_CieLuv(float r, float g, float b, float l, float u, float v) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieLuv(l, u, v); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs index fd9e56b7cb..f7c6372b1a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieLuvAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieLuvAndYCbCrConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(36.0555, 93.6901, 10.01514, 71.8283, 119.3174, 193.9839)] + public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(36.0555, 93.6901, 10.01514, 71.8283, 119.3174, 193.9839)] - public void Convert_CieLuv_to_YCbCr(float l, float u, float v, float y, float cb, float cr) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new YCbCr(y, cb, cr); + // Arrange + var input = new CieLuv(l, u, v); + var expected = new YCbCr(y, cb, cr); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = Converter.ToYCbCr(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(71.8283, 119.3174, 193.9839, 36.00565, 93.44593, 10.2234)] - public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v) - { - // Arrange - var input = new YCbCr(y, cb, cr); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(71.8283, 119.3174, 193.9839, 36.00565, 93.44593, 10.2234)] + public void Convert_YCbCr_to_CieLuv(float y, float cb, float cr, float l, float u, float v) + { + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new CieLuv(l, u, v); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs index 90bd3363bc..bae9140029 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.211263)] + public void Convert_CieXyy_to_Hsl(float x, float y, float yl, float h, float s, float l) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.211263)] - public void Convert_CieXyy_to_Hsl(float x, float y, float yl, float h, float s, float l) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new Hsl(h, s, l); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Hsl(h, s, l); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = Converter.ToHsl(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.211263, 0.3, 0.6, 0.1067051)] - public void Convert_Hsl_to_CieXyy(float h, float s, float l, float x, float y, float yl) - { - // Arrange - var input = new Hsl(h, s, l); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.211263, 0.3, 0.6, 0.1067051)] + public void Convert_Hsl_to_CieXyy(float h, float s, float l, float x, float y, float yl) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - CieXyy actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyy actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs index f7b82c0d9e..7de91cc322 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.4225259)] + public void Convert_CieXyy_to_Hsv(float x, float y, float yl, float h, float s, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.4225259)] - public void Convert_CieXyy_to_Hsv(float x, float y, float yl, float h, float s, float v) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new Hsv(h, s, v); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Hsv(h, s, v); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = Converter.ToHsv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.4225259, 0.3, 0.6, 0.1067051)] - public void Convert_Hsv_to_CieXyy(float h, float s, float v, float x, float y, float yl) - { - // Arrange - var input = new Hsv(h, s, v); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.4225259, 0.3, 0.6, 0.1067051)] + public void Convert_Hsv_to_CieXyy(float h, float s, float v, float x, float y, float yl) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs index e8503540be..40b58b7048 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndHunterLabConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndHunterLabConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 31.46263, -32.81796, 28.64938)] + public void Convert_CieXyy_to_HunterLab(float x, float y, float yl, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 31.46263, -32.81796, 28.64938)] - public void Convert_CieXyy_to_HunterLab(float x, float y, float yl, float l, float a, float b) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new HunterLab(l, a, b); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new HunterLab(l, a, b); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = Converter.ToHunterLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(31.46263, -32.81796, 28.64938, 0.3605552, 0.9369011, 0.1001514)] - public void Convert_HunterLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) - { - // Arrange - var input = new HunterLab(l, a, b); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(31.46263, -32.81796, 28.64938, 0.3605552, 0.9369011, 0.1001514)] + public void Convert_HunterLab_to_CieXyy(float l, float a, float b, float x, float y, float yl) + { + // Arrange + var input = new HunterLab(l, a, b); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - CieXyy actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyy actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs index 58f0663e03..062f54abc3 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndLinearRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndLinearRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.1492062, 0)] + public void Convert_CieXyy_to_LinearRgb(float x, float y, float yl, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.1492062, 0)] - public void Convert_CieXyy_to_LinearRgb(float x, float y, float yl, float r, float g, float b) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new LinearRgb(r, g, b); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new LinearRgb(r, g, b); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new LinearRgb[5]; + Span actualSpan = new LinearRgb[5]; - // Act - var actual = Converter.ToLinearRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 0.1492062, 0, 0.3, 0.6, 0.1067051)] - public void Convert_LinearRgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) - { - // Arrange - var input = new LinearRgb(r, g, b); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0.1492062, 0, 0.3, 0.6, 0.1067051)] + public void Convert_LinearRgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) + { + // Arrange + var input = new LinearRgb(r, g, b); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new LinearRgb[5]; - inputSpan.Fill(input); + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs index 610a7569ea..8e9cbc5ffc 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndLmsConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndLmsConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0.06631134, 0.1415282, -0.03809926)] + public void Convert_CieXyy_to_Lms(float x, float y, float yl, float l, float m, float s) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 0.06631134, 0.1415282, -0.03809926)] - public void Convert_CieXyy_to_Lms(float x, float y, float yl, float l, float m, float s) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new Lms(l, m, s); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Lms(l, m, s); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new Lms[5]; + Span actualSpan = new Lms[5]; - // Act - var actual = Converter.ToLms(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.06631134, 0.1415282, -0.03809926, 0.360555, 0.9369009, 0.1001514)] - public void Convert_Lms_to_CieXyy(float l, float m, float s, float x, float y, float yl) - { - // Arrange - var input = new Lms(l, m, s); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.06631134, 0.1415282, -0.03809926, 0.360555, 0.9369009, 0.1001514)] + public void Convert_Lms_to_CieXyy(float l, float m, float s, float x, float y, float yl) + { + // Arrange + var input = new Lms(l, m, s); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - CieXyy actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyy actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs index e49d3abeb0..0a7cd68429 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndRgbConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndRgbConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.4225259, 0)] + public void Convert_CieXyy_to_Rgb(float x, float y, float yl, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 0, 0.4225259, 0)] - public void Convert_CieXyy_to_Rgb(float x, float y, float yl, float r, float g, float b) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new Rgb(r, g, b); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new Rgb(r, g, b); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = Converter.ToRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 0.4225259, 0, 0.3, 0.6, 0.1067051)] - public void Convert_Rgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0.4225259, 0, 0.3, 0.6, 0.1067051)] + public void Convert_Rgb_to_CieXyy(float r, float g, float b, float x, float y, float yl) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - CieXyy actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyy actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs index d154aa246a..efacebcf19 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyyAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyyAndYCbCrConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(0.360555, 0.936901, 0.1001514, 63.24579, 92.30826, 82.88884)] + public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(0.360555, 0.936901, 0.1001514, 63.24579, 92.30826, 82.88884)] - public void Convert_CieXyy_to_YCbCr(float x, float y, float yl, float y2, float cb, float cr) - { - // Arrange - var input = new CieXyy(x, y, yl); - var expected = new YCbCr(y2, cb, cr); + // Arrange + var input = new CieXyy(x, y, yl); + var expected = new YCbCr(y2, cb, cr); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = Converter.ToYCbCr(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(63.24579, 92.30826, 82.88884, 0.3, 0.6, 0.1072441)] - public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl) - { - // Arrange - var input = new YCbCr(y2, cb, cr); - var expected = new CieXyy(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(63.24579, 92.30826, 82.88884, 0.3, 0.6, 0.1072441)] + public void Convert_YCbCr_to_CieXyy(float y2, float cb, float cr, float x, float y, float yl) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - CieXyy actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyy actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs index a5a4941abf..3ea4228e5e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs @@ -1,96 +1,93 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieXyzAndCieLabConversionTest { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to (). /// - /// - /// Test data generated using: - /// - /// - public class CieXyzAndCieLabConversionTest + [Theory] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] + [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] + [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] + [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] + [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] + [InlineData(10, -400, 20, 0, 0.011260, 0)] + public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to (). - /// - [Theory] - [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 431.0345, 0, 0.95047, 0, 0)] - [InlineData(100, -431.0345, 172.4138, 0, 1, 0)] - [InlineData(0, 0, -172.4138, 0, 0, 1.08883)] - [InlineData(45.6398, 39.8753, 35.2091, 0.216938, 0.150041, 0.048850)] - [InlineData(77.1234, -40.1235, 78.1120, 0.358530, 0.517372, 0.076273)] - [InlineData(10, -400, 20, 0, 0.011260, 0)] - public void Convert_Lab_to_Xyz(float l, float a, float b, float x, float y, float z) - { - // Arrange - var input = new CieLab(l, a, b, Illuminants.D65); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + // Arrange + var input = new CieLab(l, a, b, Illuminants.D65); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new CieLab[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from () to . - /// - [Theory] - [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] - [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] - [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] - [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] - public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) - { - // Arrange - var input = new CieXyz(x, y, z); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieLab(l, a, b); + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0, 431.0345, 0)] + [InlineData(0, 1, 0, 100, -431.0345, 172.4138)] + [InlineData(0, 0, 1.08883, 0, 0, -172.4138)] + [InlineData(0.216938, 0.150041, 0.048850, 45.6398, 39.8753, 35.2091)] + public void Convert_Xyz_to_Lab(float x, float y, float z, float l, float a, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieLab(l, a, b); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLab[5]; + Span actualSpan = new CieLab[5]; - // Act - var actual = converter.ToCieLab(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieLab(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs index 2ff85ba147..698c9add60 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyzAndCieLchConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyzAndCieLchConversionTests + [Theory] + [InlineData(0.360555, 0.936901, 0.1001514, 97.50815, 155.8035, 139.323)] + public void Convert_CieXyz_to_CieLch(float x, float y, float yl, float l, float c, float h) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.360555, 0.936901, 0.1001514, 97.50815, 155.8035, 139.323)] - public void Convert_CieXyz_to_CieLch(float x, float y, float yl, float l, float c, float h) - { - // Arrange - var input = new CieXyz(x, y, yl); - var expected = new CieLch(l, c, h); + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new CieLch(l, c, h); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(97.50815, 155.8035, 139.323, 0.3605551, 0.936901, 0.1001514)] - public void Convert_CieLch_to_CieXyz(float l, float c, float h, float x, float y, float yl) - { - // Arrange - var input = new CieLch(l, c, h); - var expected = new CieXyz(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(97.50815, 155.8035, 139.323, 0.3605551, 0.936901, 0.1001514)] + public void Convert_CieLch_to_CieXyz(float l, float c, float h, float x, float y, float yl) + { + // Arrange + var input = new CieLch(l, c, h); + var expected = new CieXyz(x, y, yl); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - CieXyz actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyz actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs index c750467175..a4caf4c857 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyzAndCieLchuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyzAndCieLchuvConversionTests + [Theory] + [InlineData(0.360555, 0.936901, 0.1001514, 97.50697, 183.3831, 133.6321)] + public void Convert_CieXyz_to_CieLchuv(float x, float y, float yl, float l, float c, float h) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.360555, 0.936901, 0.1001514, 97.50697, 183.3831, 133.6321)] - public void Convert_CieXyz_to_CieLchuv(float x, float y, float yl, float l, float c, float h) - { - // Arrange - var input = new CieXyz(x, y, yl); - var expected = new CieLchuv(l, c, h); + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new CieLchuv(l, c, h); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLchuv[5]; + Span actualSpan = new CieLchuv[5]; - // Act - var actual = Converter.ToCieLchuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(97.50697, 183.3831, 133.6321, 0.360555, 0.936901, 0.1001515)] - public void Convert_CieLchuv_to_CieXyz(float l, float c, float h, float x, float y, float yl) - { - // Arrange - var input = new CieLchuv(l, c, h); - var expected = new CieXyz(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(97.50697, 183.3831, 133.6321, 0.360555, 0.936901, 0.1001515)] + public void Convert_CieLchuv_to_CieXyz(float l, float c, float h, float x, float y, float yl) + { + // Arrange + var input = new CieLchuv(l, c, h); + var expected = new CieXyz(x, y, yl); - Span inputSpan = new CieLchuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - CieXyz actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyz actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs index 5817dca8b0..9e3381b40d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs @@ -1,95 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieXyzAndCieLuvConversionTest { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to (). /// - /// - /// Test data generated using: - /// - /// - public class CieXyzAndCieLuvConversionTest + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 100, 50, 0, 0, 0)] + [InlineData(0.1, 100, 50, 0.000493, 0.000111, 0)] + [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] + [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] + [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] + [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] + public void Convert_Luv_to_Xyz(float l, float u, float v, float x, float y, float z) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to (). - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 100, 50, 0, 0, 0)] - [InlineData(0.1, 100, 50, 0.000493, 0.000111, 0)] - [InlineData(70.0000, 86.3525, 2.8240, 0.569310, 0.407494, 0.365843)] - [InlineData(10.0000, -1.2345, -10.0000, 0.012191, 0.011260, 0.025939)] - [InlineData(100, 0, 0, 0.950470, 1.000000, 1.088830)] - [InlineData(1, 1, 1, 0.001255, 0.001107, 0.000137)] - public void Convert_Luv_to_Xyz(float l, float u, float v, float x, float y, float z) - { - // Arrange - var input = new CieLuv(l, u, v, Illuminants.D65); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + // Arrange + var input = new CieLuv(l, u, v, Illuminants.D65); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from () to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.000493, 0.000111, 0, 0.1003, 0.9332, -0.0070)] - [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] - [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] - [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] - [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] - public void Convert_Xyz_to_Luv(float x, float y, float z, float l, float u, float v) - { - // Arrange - var input = new CieXyz(x, y, z); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieLuv(l, u, v); + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.000493, 0.000111, 0, 0.1003, 0.9332, -0.0070)] + [InlineData(0.569310, 0.407494, 0.365843, 70.0000, 86.3524, 2.8240)] + [InlineData(0.012191, 0.011260, 0.025939, 9.9998, -1.2343, -9.9999)] + [InlineData(0.950470, 1.000000, 1.088830, 100, 0, 0)] + [InlineData(0.001255, 0.001107, 0.000137, 0.9999, 0.9998, 1.0004)] + public void Convert_Xyz_to_Luv(float x, float y, float z, float l, float u, float v) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieLuv(l, u, v); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = converter.ToCieLuv(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieLuv(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs index 0cd258bd9e..5ef5c4d21a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieXyzAndCieXyyConversionTest { - /// - /// Tests - conversions. - /// - /// - /// Test data generated using: - /// - /// - public class CieXyzAndCieXyyConversionTest - { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - [Theory] - [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] - [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] - [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] - [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] - public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) - { - var input = new CieXyy(x, y, yl); - var expected = new CieXyz(xyzX, xyzY, xyzZ); + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0, 0, 0, 0.538842, 0.000000, 0.000000)] + public void Convert_xyY_to_XYZ(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + var input = new CieXyy(x, y, yl); + var expected = new CieXyz(xyzX, xyzY, xyzZ); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = ColorSpaceConverter.ToCieXyz(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToCieXyz(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - [Theory] - [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] - [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] - [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] - [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] - public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) - { - var input = new CieXyz(xyzX, xyzY, xyzZ); - var expected = new CieXyy(x, y, yl); + [Theory] + [InlineData(0.436075, 0.222504, 0.013932, 0.648427, 0.330856, 0.222504)] + [InlineData(0.964220, 1.000000, 0.825210, 0.345669, 0.358496, 1.000000)] + [InlineData(0.434119, 0.356820, 0.369447, 0.374116, 0.307501, 0.356820)] + [InlineData(0.231809, 0, 0.077528, 0.749374, 0.000000, 0.000000)] + public void Convert_XYZ_to_xyY(float xyzX, float xyzY, float xyzZ, float x, float y, float yl) + { + var input = new CieXyz(xyzX, xyzY, xyzZ); + var expected = new CieXyy(x, y, yl); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = ColorSpaceConverter.ToCieXyy(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToCieXyy(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs index 290ee785fa..634d03a502 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyzAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyzAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.5)] + public void Convert_CieXyz_to_Hsl(float x, float y, float yl, float h, float s, float l) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.5)] - public void Convert_CieXyz_to_Hsl(float x, float y, float yl, float h, float s, float l) - { - // Arrange - var input = new CieXyz(x, y, yl); - var expected = new Hsl(h, s, l); + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new Hsl(h, s, l); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = Converter.ToHsl(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.5, 0.3575761, 0.7151522, 0.119192)] - public void Convert_Hsl_to_CieXyz(float h, float s, float l, float x, float y, float yl) - { - // Arrange - var input = new Hsl(h, s, l); - var expected = new CieXyz(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.5, 0.3575761, 0.7151522, 0.119192)] + public void Convert_Hsl_to_CieXyz(float h, float s, float l, float x, float y, float yl) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new CieXyz(x, y, yl); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs index f2339d73a5..ccedd7b755 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyzAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyzAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.9999999)] + public void Convert_CieXyz_to_Hsv(float x, float y, float yl, float h, float s, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.360555, 0.936901, 0.1001514, 120, 1, 0.9999999)] - public void Convert_CieXyz_to_Hsv(float x, float y, float yl, float h, float s, float v) - { - // Arrange - var input = new CieXyz(x, y, yl); - var expected = new Hsv(h, s, v); + // Arrange + var input = new CieXyz(x, y, yl); + var expected = new Hsv(h, s, v); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = Converter.ToHsv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(120, 1, 0.9999999, 0.3575761, 0.7151522, 0.119192)] - public void Convert_Hsv_to_CieXyz(float h, float s, float v, float x, float y, float yl) - { - // Arrange - var input = new Hsv(h, s, v); - var expected = new CieXyz(x, y, yl); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(120, 1, 0.9999999, 0.3575761, 0.7151522, 0.119192)] + public void Convert_Hsv_to_CieXyz(float h, float s, float v, float x, float y, float yl) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new CieXyz(x, y, yl); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - CieXyz actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyz actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs index 7935c375d6..af7087ba23 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs @@ -1,118 +1,115 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class CieXyzAndHunterLabConversionTest { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to (). /// - /// - /// Test data generated using: - /// - /// - public class CieXyzAndHunterLabConversionTest + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 + public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to (). - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(100, 0, 0, 0.98074, 1, 1.18232)] // C white point is HunterLab 100, 0, 0 - public void Convert_HunterLab_to_Xyz(float l, float a, float b, float x, float y, float z) - { - // Arrange - var input = new HunterLab(l, a, b); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.C }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + // Arrange + var input = new HunterLab(l, a, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.C }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to (). - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) - public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) - { - // Arrange - var input = new HunterLab(l, a, b); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + /// + /// Tests conversion from to (). + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(100, 0, 0, 0.95047, 1, 1.08883)] // D65 white point is HunerLab 100, 0, 0 (adaptation to C performed) + public void Convert_HunterLab_to_Xyz_D65(float l, float a, float b, float x, float y, float z) + { + // Arrange + var input = new HunterLab(l, a, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from () to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) - public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) - { - // Arrange - var input = new CieXyz(x, y, z); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new HunterLab(l, a, b); + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 1, 1.08883, 100, 0, 0)] // D65 white point is HunterLab 100, 0, 0 (adaptation to C performed) + public void Convert_Xyz_D65_to_HunterLab(float x, float y, float z, float l, float a, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new HunterLab(l, a, b); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = converter.ToHunterLab(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToHunterLab(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs index 993a982445..33bdc6e935 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs @@ -1,91 +1,88 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using original colorful library. +/// +public class CieXyzAndLmsConversionTest { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from () to . /// - /// - /// Test data generated using original colorful library. - /// - public class CieXyzAndLmsConversionTest + [Theory] + [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] + [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] + [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] + [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] + public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from () to . - /// - [Theory] - [InlineData(0.941428535, 1.040417467, 1.089532651, 0.95047, 1, 1.08883)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.850765697, -0.713042594, 0.036973283, 0.95047, 0, 0)] - [InlineData(0.2664, 1.7135, -0.0685, 0, 1, 0)] - [InlineData(-0.175737162, 0.039960061, 1.121059368, 0, 0, 1.08883)] - [InlineData(0.2262677362, 0.0961411609, 0.0484570397, 0.216938, 0.150041, 0.048850)] - public void Convert_Lms_to_CieXyz(float l, float m, float s, float x, float y, float z) - { - // Arrange - var input = new Lms(l, m, s); - var converter = new ColorSpaceConverter(); - var expected = new CieXyz(x, y, z); + // Arrange + var input = new Lms(l, m, s); + var converter = new ColorSpaceConverter(); + var expected = new CieXyz(x, y, z); - Span inputSpan = new Lms[5]; - inputSpan.Fill(input); + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from () to . - /// - [Theory] - [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] - [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] - [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] - [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] - public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) - { - // Arrange - var input = new CieXyz(x, y, z); - var converter = new ColorSpaceConverter(); - var expected = new Lms(l, m, s); + /// + /// Tests conversion from () to . + /// + [Theory] + [InlineData(0.95047, 1, 1.08883, 0.941428535, 1.040417467, 1.089532651)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.95047, 0, 0, 0.850765697, -0.713042594, 0.036973283)] + [InlineData(0, 1, 0, 0.2664, 1.7135, -0.0685)] + [InlineData(0, 0, 1.08883, -0.175737162, 0.039960061, 1.121059368)] + [InlineData(0.216938, 0.150041, 0.048850, 0.2262677362, 0.0961411609, 0.0484570397)] + public void Convert_CieXyz_to_Lms(float x, float y, float z, float l, float m, float s) + { + // Arrange + var input = new CieXyz(x, y, z); + var converter = new ColorSpaceConverter(); + var expected = new Lms(l, m, s); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Lms[5]; + Span actualSpan = new Lms[5]; - // Act - var actual = converter.ToLms(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToLms(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs index 52ee78fd36..ba67e605a6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CieXyzAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CieXyzAndYCbCrConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(0.360555, 0.936901, 0.1001514, 149.685, 43.52769, 21.23457)] + public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(0.360555, 0.936901, 0.1001514, 149.685, 43.52769, 21.23457)] - public void Convert_CieXyz_to_YCbCr(float x, float y, float z, float y2, float cb, float cr) - { - // Arrange - var input = new CieXyz(x, y, z); - var expected = new YCbCr(y2, cb, cr); + // Arrange + var input = new CieXyz(x, y, z); + var expected = new YCbCr(y2, cb, cr); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = Converter.ToYCbCr(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(149.685, 43.52769, 21.23457, 0.3575761, 0.7151522, 0.119192)] - public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z) - { - // Arrange - var input = new YCbCr(y2, cb, cr); - var expected = new CieXyz(x, y, z); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(149.685, 43.52769, 21.23457, 0.3575761, 0.7151522, 0.119192)] + public void Convert_YCbCr_to_CieXyz(float y2, float cb, float cr, float x, float y, float z) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new CieXyz(x, y, z); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - CieXyz actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + CieXyz actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs index 6c9e92bece..9def3e1b20 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndCieLchConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndCieLchConversionTests + [Theory] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.85025, 64.77041, 118.2425)] + public void Convert_Cmyk_to_CieLch(float c, float m, float y, float k, float l, float c2, float h) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.85025, 64.77041, 118.2425)] - public void Convert_Cmyk_to_CieLch(float c, float m, float y, float k, float l, float c2, float h) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new CieLch(l, c2, h); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLch(l, c2, h); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLch[5]; + Span actualSpan = new CieLch[5]; - // Act - var actual = Converter.ToCieLch(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(100, 3.81656E-05, 218.6598, 0, 1.192093E-07, 0, 5.960464E-08)] - [InlineData(62.85025, 64.77041, 118.2425, 0.286581, 0, 0.7975187, 0.34983)] - public void Convert_CieLch_to_Cmyk(float l, float c2, float h, float c, float m, float y, float k) - { - // Arrange - var input = new CieLch(l, c2, h); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(100, 3.81656E-05, 218.6598, 0, 1.192093E-07, 0, 5.960464E-08)] + [InlineData(62.85025, 64.77041, 118.2425, 0.286581, 0, 0.7975187, 0.34983)] + public void Convert_CieLch_to_Cmyk(float l, float c2, float h, float c, float m, float y, float k) + { + // Arrange + var input = new CieLch(l, c2, h); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new CieLch[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs index f95c6fc468..9ae0fb7119 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndCieLuvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndCieLuvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 100, -1.937151E-05, 0)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.66017, -24.01712, 68.29556)] + public void Convert_Cmyk_to_CieLuv(float c, float m, float y, float k, float l, float u, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 100, -1.937151E-05, 0)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 62.66017, -24.01712, 68.29556)] - public void Convert_Cmyk_to_CieLuv(float c, float m, float y, float k, float l, float u, float v) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new CieLuv(l, u, v); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieLuv(l, u, v); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieLuv[5]; + Span actualSpan = new CieLuv[5]; - // Act - var actual = Converter.ToCieLuv(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(100, -1.937151E-05, 0, 3.576279E-07, 0, 0, 5.960464E-08)] - [InlineData(62.66017, -24.01712, 68.29556, 0.2865804, 0, 0.7975189, 0.3498302)] - public void Convert_CieLuv_to_Cmyk(float l, float u, float v, float c, float m, float y, float k) - { - // Arrange - var input = new CieLuv(l, u, v); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(100, -1.937151E-05, 0, 3.576279E-07, 0, 0, 5.960464E-08)] + [InlineData(62.66017, -24.01712, 68.29556, 0.2865804, 0, 0.7975189, 0.3498302)] + public void Convert_CieLuv_to_Cmyk(float l, float u, float v, float c, float m, float y, float k) + { + // Arrange + var input = new CieLuv(l, u, v); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new CieLuv[5]; - inputSpan.Fill(input); + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs index b7fe306e99..270db9d205 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndCieXyyConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndCieXyyConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0.3127266, 0.3290231, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.3628971, 0.5289949, 0.3118104)] + public void Convert_Cmyk_to_CieXyy(float c, float m, float y, float k, float x, float y2, float yl) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0.3127266, 0.3290231, 1)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.3628971, 0.5289949, 0.3118104)] - public void Convert_Cmyk_to_CieXyy(float c, float m, float y, float k, float x, float y2, float yl) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new CieXyy(x, y2, yl); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieXyy(x, y2, yl); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyy[5]; + Span actualSpan = new CieXyy[5]; - // Act - var actual = Converter.ToCieXyy(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.3127266, 0.3290231, 1, 0, 0, 0, 5.960464E-08)] - [InlineData(0.3628971, 0.5289949, 0.3118104, 0.2865805, 0, 0.7975187, 0.3498302)] - public void Convert_CieXyy_to_Cmyk(float x, float y2, float yl, float c, float m, float y, float k) - { - // Arrange - var input = new CieXyy(x, y2, yl); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.3127266, 0.3290231, 1, 0, 0, 0, 5.960464E-08)] + [InlineData(0.3628971, 0.5289949, 0.3118104, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_CieXyy_to_Cmyk(float x, float y2, float yl, float c, float m, float y, float k) + { + // Arrange + var input = new CieXyy(x, y2, yl); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new CieXyy[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs index 3f4dcd4a3c..972948c71d 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndCieXyzConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndCieXyzConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0.9504699, 1, 1.08883)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.2139058, 0.3118104, 0.0637231)] + public void Convert_Cmyk_to_CieXyz(float c, float m, float y, float k, float x, float y2, float z) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0.9504699, 1, 1.08883)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 0.2139058, 0.3118104, 0.0637231)] - public void Convert_Cmyk_to_CieXyz(float c, float m, float y, float k, float x, float y2, float z) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new CieXyz(x, y2, z); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new CieXyz(x, y2, z); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = Converter.ToCieXyz(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0.9504699, 1, 1.08883, 1.192093E-07, 0, 0, 5.960464E-08)] - [InlineData(0.2139058, 0.3118104, 0.0637231, 0.2865805, 0, 0.7975187, 0.3498302)] - public void Convert_CieXyz_to_Cmyk(float x, float y2, float z, float c, float m, float y, float k) - { - // Arrange - var input = new CieXyz(x, y2, z); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0.9504699, 1, 1.08883, 1.192093E-07, 0, 0, 5.960464E-08)] + [InlineData(0.2139058, 0.3118104, 0.0637231, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_CieXyz_to_Cmyk(float x, float y2, float z, float c, float m, float y, float k) + { + // Arrange + var input = new CieXyz(x, y2, z); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs index 095b5e1255..2b00942aca 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndHslConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndHslConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.6632275, 0.3909085)] + public void Convert_Cmyk_to_Hsl(float c, float m, float y, float k, float h, float s, float l) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.6632275, 0.3909085)] - public void Convert_Cmyk_to_Hsl(float c, float m, float y, float k, float h, float s, float l) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new Hsl(h, s, l); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Hsl(h, s, l); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = ColorSpaceConverter.ToHsl(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToHsl(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 1, 0, 0, 0, 0)] - [InlineData(81.56041, 0.6632275, 0.3909085, 0.2865805, 0, 0.7975187, 0.3498302)] - public void Convert_Hsl_to_Cmyk(float h, float s, float l, float c, float m, float y, float k) - { - // Arrange - var input = new Hsl(h, s, l); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 1, 0, 0, 0, 0)] + [InlineData(81.56041, 0.6632275, 0.3909085, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_Hsl_to_Cmyk(float h, float s, float l, float c, float m, float y, float k) + { + // Arrange + var input = new Hsl(h, s, l); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = ColorSpaceConverter.ToCmyk(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToCmyk(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs index 770840e9be..f158fb5f9e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndHsvConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndHsvConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.7975187, 0.6501698)] + public void Convert_Cmyk_to_Hsv(float c, float m, float y, float k, float h, float s, float v) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 81.56041, 0.7975187, 0.6501698)] - public void Convert_Cmyk_to_Hsv(float c, float m, float y, float k, float h, float s, float v) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new Hsv(h, s, v); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Hsv(h, s, v); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = ColorSpaceConverter.ToHsv(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToHsv(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 1, 0, 0, 0, 0)] - [InlineData(81.56041, 0.7975187, 0.6501698, 0.2865805, 0, 0.7975187, 0.3498302)] - public void Convert_Hsv_to_Cmyk(float h, float s, float v, float c, float m, float y, float k) - { - // Arrange - var input = new Hsv(h, s, v); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 1, 0, 0, 0, 0)] + [InlineData(81.56041, 0.7975187, 0.6501698, 0.2865805, 0, 0.7975187, 0.3498302)] + public void Convert_Hsv_to_Cmyk(float h, float s, float v, float c, float m, float y, float k) + { + // Arrange + var input = new Hsv(h, s, v); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = ColorSpaceConverter.ToCmyk(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToCmyk(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs index 65fa4b9ae9..9832a0d715 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndHunterLabConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndHunterLabConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 99.99999, 0, -1.66893E-05)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 55.66742, -27.21679, 31.73834)] + public void Convert_Cmyk_to_HunterLab(float c, float m, float y, float k, float l, float a, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 99.99999, 0, -1.66893E-05)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 55.66742, -27.21679, 31.73834)] - public void Convert_Cmyk_to_HunterLab(float c, float m, float y, float k, float l, float a, float b) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new HunterLab(l, a, b); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new HunterLab(l, a, b); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new HunterLab[5]; + Span actualSpan = new HunterLab[5]; - // Act - var actual = Converter.ToHunterLab(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(99.99999, 0, -1.66893E-05, 1.192093E-07, 1.192093E-07, 0, 5.960464E-08)] - [InlineData(55.66742, -27.21679, 31.73834, 0.2865806, 0, 0.7975186, 0.3498301)] - public void Convert_HunterLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) - { - // Arrange - var input = new HunterLab(l, a, b); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(99.99999, 0, -1.66893E-05, 1.192093E-07, 1.192093E-07, 0, 5.960464E-08)] + [InlineData(55.66742, -27.21679, 31.73834, 0.2865806, 0, 0.7975186, 0.3498301)] + public void Convert_HunterLab_to_Cmyk(float l, float a, float b, float c, float m, float y, float k) + { + // Arrange + var input = new HunterLab(l, a, b); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new HunterLab[5]; - inputSpan.Fill(input); + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs index a67af332d7..1e8bc52e2f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs @@ -1,79 +1,76 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +public class CmykAndYCbCrConversionTests { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - public class CmykAndYCbCrConversionTests + [Theory] + [InlineData(0, 0, 0, 0, 255, 128, 128)] + [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 136.5134, 69.90555, 114.9948)] + public void Convert_Cmyk_to_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0002F); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 255, 128, 128)] - [InlineData(0.360555, 0.1036901, 0.818514, 0.274615, 136.5134, 69.90555, 114.9948)] - public void Convert_Cmyk_to_YCbCr(float c, float m, float y, float k, float y2, float cb, float cr) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new YCbCr(y2, cb, cr); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new YCbCr(y2, cb, cr); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = ColorSpaceConverter.ToYCbCr(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToYCbCr(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(255, 128, 128, 0, 0, 0, 5.960464E-08)] - [InlineData(136.5134, 69.90555, 114.9948, 0.2891567, 0, 0.7951807, 0.3490196)] - public void Convert_YCbCr_to_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k) - { - // Arrange - var input = new YCbCr(y2, cb, cr); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(255, 128, 128, 0, 0, 0, 5.960464E-08)] + [InlineData(136.5134, 69.90555, 114.9948, 0.2891567, 0, 0.7951807, 0.3490196)] + public void Convert_YCbCr_to_Cmyk(float y2, float cb, float cr, float c, float m, float y, float k) + { + // Arrange + var input = new YCbCr(y2, cb, cr); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = Converter.ToCmyk(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs index ceafcae675..bbebf96b32 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs @@ -3,178 +3,176 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests methods. +/// Test data generated using: +/// +/// +/// +public class ColorConverterAdaptTest { - /// - /// Tests methods. - /// Test data generated using: - /// - /// - /// - public class ColorConverterAdaptTest + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] + public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + // Arrange + var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); + var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); + var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + + // Action + Rgb actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + } - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 1, 1, 1)] - [InlineData(0.206162, 0.260277, 0.746717, 0.220000, 0.130000, 0.780000)] - public void Adapt_RGB_WideGamutRGB_To_sRGB(float r1, float g1, float b1, float r2, float g2, float b2) - { - // Arrange - var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); - var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); - var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; - var converter = new ColorSpaceConverter(options); - - // Action - Rgb actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 1, 1, 1)] - [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] - public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) - { - // Arrange - var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); - var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); - var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; - var converter = new ColorSpaceConverter(options); - - // Action - Rgb actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] - public void Adapt_Lab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) - { - // Arrange - var input = new CieLab(l1, a1, b1, Illuminants.D65); - var expected = new CieLab(l2, a2, b2); - var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; - var converter = new ColorSpaceConverter(options); - - // Action - CieLab actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] - public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) - { - // Arrange - var input = new CieXyz(x1, y1, z1); - var expected = new CieXyz(x2, y2, z2); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; - var converter = new ColorSpaceConverter(options); - - // Action - CieXyz actual = converter.Adapt(input, Illuminants.D65); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] - public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) - { - // Arrange - var input = new CieXyz(x1, y1, z1); - var expected = new CieXyz(x2, y2, z2); - var options = new ColorSpaceConverterOptions - { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), - WhitePoint = Illuminants.D50 - }; - - var converter = new ColorSpaceConverter(options); - - // Action - CieXyz actual = converter.Adapt(input, Illuminants.D65); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22.1090755, 32.2102661, 1.153463)] - public void Adapt_HunterLab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 1, 1, 1)] + [InlineData(0.220000, 0.130000, 0.780000, 0.206162, 0.260277, 0.746717)] + public void Adapt_RGB_SRGB_To_WideGamutRGB(float r1, float g1, float b1, float r2, float g2, float b2) + { + // Arrange + var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.SRgb); + var expected = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); + var options = new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; + var converter = new ColorSpaceConverter(options); + + // Action + Rgb actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected.WorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] + public void Adapt_Lab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + var input = new CieLab(l1, a1, b1, Illuminants.D65); + var expected = new CieLab(l2, a2, b2); + var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLab actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.510286, 0.501489, 0.378970)] + public void Adapt_Xyz_D65_To_D50_Bradford(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + var input = new CieXyz(x1, y1, z1); + var expected = new CieXyz(x2, y2, z2); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + CieXyz actual = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] + public void Adapt_Xyz_D65_To_D50_XyzScaling(float x1, float y1, float z1, float x2, float y2, float z2) + { + // Arrange + var input = new CieXyz(x1, y1, z1); + var expected = new CieXyz(x2, y2, z2); + var options = new ColorSpaceConverterOptions { - // Arrange - var input = new HunterLab(l1, a1, b1, Illuminants.D65); - var expected = new HunterLab(l2, a2, b2); - var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; - var converter = new ColorSpaceConverter(options); - - // Action - HunterLab actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22, 33, 0.9999999)] - public void Adapt_CieLchuv_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + WhitePoint = Illuminants.D50 + }; + + var converter = new ColorSpaceConverter(options); + + // Action + CieXyz actual = converter.Adapt(input, Illuminants.D65); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22.1090755, 32.2102661, 1.153463)] + public void Adapt_HunterLab_D65_To_D50(float l1, float a1, float b1, float l2, float a2, float b2) + { + // Arrange + var input = new HunterLab(l1, a1, b1, Illuminants.D65); + var expected = new HunterLab(l2, a2, b2); + var options = new ColorSpaceConverterOptions { TargetLabWhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + + // Action + HunterLab actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(22, 33, 1, 22, 33, 0.9999999)] + public void Adapt_CieLchuv_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + { + // Arrange + var input = new CieLchuv(l1, c1, h1, Illuminants.D65); + var expected = new CieLchuv(l2, c2, h2); + var options = new ColorSpaceConverterOptions { - // Arrange - var input = new CieLchuv(l1, c1, h1, Illuminants.D65); - var expected = new CieLchuv(l2, c2, h2); - var options = new ColorSpaceConverterOptions - { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), - TargetLabWhitePoint = Illuminants.D50 - }; - var converter = new ColorSpaceConverter(options); - - // Action - CieLchuv actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } - - [Theory] - [InlineData(22, 33, 1, 22, 33, 0.9999999)] - public void Adapt_CieLch_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + TargetLabWhitePoint = Illuminants.D50 + }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLchuv actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + } + + [Theory] + [InlineData(22, 33, 1, 22, 33, 0.9999999)] + public void Adapt_CieLch_D65_To_D50_XyzScaling(float l1, float c1, float h1, float l2, float c2, float h2) + { + // Arrange + var input = new CieLch(l1, c1, h1, Illuminants.D65); + var expected = new CieLch(l2, c2, h2); + var options = new ColorSpaceConverterOptions { - // Arrange - var input = new CieLch(l1, c1, h1, Illuminants.D65); - var expected = new CieLch(l2, c2, h2); - var options = new ColorSpaceConverterOptions - { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), - TargetLabWhitePoint = Illuminants.D50 - }; - var converter = new ColorSpaceConverter(options); - - // Action - CieLch actual = converter.Adapt(input); - - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); - } + ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), + TargetLabWhitePoint = Illuminants.D50 + }; + var converter = new ColorSpaceConverter(options); + + // Action + CieLch actual = converter.Adapt(input); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs index aef0cf3339..0119f4f3a4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs @@ -1,173 +1,170 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class RgbAndCieXyzConversionTest { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from () + /// to (default sRGB working space). /// - /// - /// Test data generated using: - /// - /// - public class RgbAndCieXyzConversionTest + [Theory] + [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] + [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] + [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] + [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] + public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) { - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from () - /// to (default sRGB working space). - /// - [Theory] - [InlineData(0.96422, 1.00000, 0.82521, 1, 1, 1)] - [InlineData(0.00000, 1.00000, 0.00000, 0, 1, 0)] - [InlineData(0.96422, 0.00000, 0.00000, 1, 0, 0.292064)] - [InlineData(0.00000, 0.00000, 0.82521, 0, 0.181415, 1)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.297676, 0.267854, 0.045504, 0.720315, 0.509999, 0.168112)] - public void Convert_XYZ_D50_to_SRGB(float x, float y, float z, float r, float g, float b) - { - // Arrange - var input = new CieXyz(x, y, z); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; - var converter = new ColorSpaceConverter(options); - var expected = new Rgb(r, g, b); + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + var expected = new Rgb(r, g, b); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = converter.ToRgb(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion - /// from () - /// to (default sRGB working space). - /// - [Theory] - [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] - [InlineData(0, 1.000000, 0, 0, 1, 0)] - [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] - [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] - public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) - { - // Arrange - var input = new CieXyz(x, y, z); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; - var converter = new ColorSpaceConverter(options); - var expected = new Rgb(r, g, b); + /// + /// Tests conversion + /// from () + /// to (default sRGB working space). + /// + [Theory] + [InlineData(0.950470, 1.000000, 1.088830, 1, 1, 1)] + [InlineData(0, 1.000000, 0, 0, 1, 0)] + [InlineData(0.950470, 0, 0, 1, 0, 0.254967)] + [InlineData(0, 0, 1.088830, 0, 0.235458, 1)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0.297676, 0.267854, 0.045504, 0.754903, 0.501961, 0.099998)] + public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) + { + // Arrange + var input = new CieXyz(x, y, z); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + var converter = new ColorSpaceConverter(options); + var expected = new Rgb(r, g, b); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = converter.ToRgb(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from (default sRGB working space) - /// to (). - /// - [Theory] - [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] - [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] - [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] - [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] - public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) - { - // Arrange - var input = new Rgb(r, g, b); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.964220, 1.000000, 0.825210)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.436075, 0.222504, 0.013932)] + [InlineData(0, 1, 0, 0.385065, 0.716879, 0.0971045)] + [InlineData(0, 0, 1, 0.143080, 0.060617, 0.714173)] + [InlineData(0.754902, 0.501961, 0.100000, 0.315757, 0.273323, 0.035506)] + public void Convert_SRGB_to_XYZ_D50(float r, float g, float b, float x, float y, float z) + { + // Arrange + var input = new Rgb(r, g, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D50 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from (default sRGB working space) - /// to (). - /// - [Theory] - [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] - [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] - [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] - [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] - public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) - { - // Arrange - var input = new Rgb(r, g, b); - var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; - var converter = new ColorSpaceConverter(options); - var expected = new CieXyz(x, y, z); + /// + /// Tests conversion from (default sRGB working space) + /// to (). + /// + [Theory] + [InlineData(1, 1, 1, 0.950470, 1.000000, 1.088830)] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 0, 0, 0.412456, 0.212673, 0.019334)] + [InlineData(0, 1, 0, 0.357576, 0.715152, 0.119192)] + [InlineData(0, 0, 1, 0.1804375, 0.072175, 0.950304)] + [InlineData(0.754902, 0.501961, 0.100000, 0.297676, 0.267854, 0.045504)] + public void Convert_SRGB_to_XYZ_D65(float r, float g, float b, float x, float y, float z) + { + // Arrange + var input = new Rgb(r, g, b); + var options = new ColorSpaceConverterOptions { WhitePoint = Illuminants.D65 }; + var converter = new ColorSpaceConverter(options); + var expected = new CieXyz(x, y, z); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - // Act - var actual = converter.ToCieXyz(input); - converter.Convert(inputSpan, actualSpan); + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs index 04abcf9788..942b34db33 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs @@ -1,87 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +/// +public class RgbAndCmykConversionTest { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - /// - public class RgbAndCmykConversionTest + [Theory] + [InlineData(1, 1, 1, 1, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 1, 1, 1)] + [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] + public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) { - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(1, 1, 1, 1, 0, 0, 0)] - [InlineData(0, 0, 0, 0, 1, 1, 1)] - [InlineData(0, 0.84, 0.037, 0.365, 0.635, 0.1016, 0.6115)] - public void Convert_Cmyk_To_Rgb(float c, float m, float y, float k, float r, float g, float b) - { - // Arrange - var input = new Cmyk(c, m, y, k); - var expected = new Rgb(r, g, b); + // Arrange + var input = new Cmyk(c, m, y, k); + var expected = new Rgb(r, g, b); - Span inputSpan = new Cmyk[5]; - inputSpan.Fill(input); + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = ColorSpaceConverter.ToRgb(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToRgb(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(1, 1, 1, 0, 0, 0, 0)] - [InlineData(0, 0, 0, 0, 0, 0, 1)] - [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] - public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new Cmyk(c, m, y, k); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(1, 1, 1, 0, 0, 0, 0)] + [InlineData(0, 0, 0, 0, 0, 0, 1)] + [InlineData(0.635, 0.1016, 0.6115, 0, 0.84, 0.037, 0.365)] + public void Convert_Rgb_To_Cmyk(float r, float g, float b, float c, float m, float y, float k) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Cmyk(c, m, y, k); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new Cmyk[5]; + Span actualSpan = new Cmyk[5]; - // Act - var actual = ColorSpaceConverter.ToCmyk(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToCmyk(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs index f1075f8666..8b448a0426 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -1,93 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +/// +public class RgbAndHslConversionTest { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - /// - public class RgbAndHslConversionTest + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 1, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 1, 1)] + [InlineData(0, 1, .5F, 1, 0, 0)] + [InlineData(120, 1, .5F, 0, 1, 0)] + [InlineData(240, 1, .5F, 0, 0, 1)] + public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) { - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 1, 1, 1, 1, 1)] - [InlineData(360, 1, 1, 1, 1, 1)] - [InlineData(0, 1, .5F, 1, 0, 0)] - [InlineData(120, 1, .5F, 0, 1, 0)] - [InlineData(240, 1, .5F, 0, 0, 1)] - public void Convert_Hsl_To_Rgb(float h, float s, float l, float r, float g, float b) - { - // Arrange - var input = new Hsl(h, s, l); - var expected = new Rgb(r, g, b); + // Arrange + var input = new Hsl(h, s, l); + var expected = new Rgb(r, g, b); - Span inputSpan = new Hsl[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = ColorSpaceConverter.ToRgb(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToRgb(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 0, 0, 1)] - [InlineData(1, 0, 0, 0, 1, .5F)] - [InlineData(0, 1, 0, 120, 1, .5F)] - [InlineData(0, 0, 1, 240, 1, .5F)] - [InlineData(0.7, 0.8, 0.6, 90, 0.3333, 0.7F)] - public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new Hsl(h, s, l); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, .5F)] + [InlineData(0, 1, 0, 120, 1, .5F)] + [InlineData(0, 0, 1, 240, 1, .5F)] + [InlineData(0.7, 0.8, 0.6, 90, 0.3333, 0.7F)] + public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Hsl(h, s, l); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsl[5]; + Span actualSpan = new Hsl[5]; - // Act - var actual = ColorSpaceConverter.ToHsl(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToHsl(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs index 2d801b5405..d80aa6c329 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs @@ -1,91 +1,88 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated using: +/// +/// +public class RgbAndHsvConversionTest { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated using: - /// - /// - public class RgbAndHsvConversionTest + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(0, 0, 1, 1, 1, 1)] + [InlineData(360, 1, 1, 1, 0, 0)] + [InlineData(0, 1, 1, 1, 0, 0)] + [InlineData(120, 1, 1, 0, 1, 0)] + [InlineData(240, 1, 1, 0, 0, 1)] + public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) { - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0, 0, 1, 1, 1, 1)] - [InlineData(360, 1, 1, 1, 0, 0)] - [InlineData(0, 1, 1, 1, 0, 0)] - [InlineData(120, 1, 1, 0, 1, 0)] - [InlineData(240, 1, 1, 0, 0, 1)] - public void Convert_Hsv_To_Rgb(float h, float s, float v, float r, float g, float b) - { - // Arrange - var input = new Hsv(h, s, v); - var expected = new Rgb(r, g, b); + // Arrange + var input = new Hsv(h, s, v); + var expected = new Rgb(r, g, b); - Span inputSpan = new Hsv[5]; - inputSpan.Fill(input); + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = ColorSpaceConverter.ToRgb(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToRgb(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(1, 1, 1, 0, 0, 1)] - [InlineData(1, 0, 0, 0, 1, 1)] - [InlineData(0, 1, 0, 120, 1, 1)] - [InlineData(0, 0, 1, 240, 1, 1)] - public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new Hsv(h, s, v); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 0, 0)] + [InlineData(1, 1, 1, 0, 0, 1)] + [InlineData(1, 0, 0, 0, 1, 1)] + [InlineData(0, 1, 0, 120, 1, 1)] + [InlineData(0, 0, 1, 240, 1, 1)] + public void Convert_Rgb_To_Hsv(float r, float g, float b, float h, float s, float v) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new Hsv(h, s, v); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new Hsv[5]; + Span actualSpan = new Hsv[5]; - // Act - var actual = ColorSpaceConverter.ToHsv(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToHsv(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs index 0b4193c1ae..eb4eb0bbff 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs @@ -1,86 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +/// +/// Tests - conversions. +/// +/// +/// Test data generated mathematically +/// +public class RgbAndYCbCrConversionTest { + private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.001F); + /// - /// Tests - conversions. + /// Tests conversion from to . /// - /// - /// Test data generated mathematically - /// - public class RgbAndYCbCrConversionTest + [Theory] + [InlineData(255, 128, 128, 1, 1, 1)] + [InlineData(0, 128, 128, 0, 0, 0)] + [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] + public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) { - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.001F); - - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(255, 128, 128, 1, 1, 1)] - [InlineData(0, 128, 128, 0, 0, 0)] - [InlineData(128, 128, 128, 0.502, 0.502, 0.502)] - public void Convert_YCbCr_To_Rgb(float y, float cb, float cr, float r, float g, float b) - { - // Arrange - var input = new YCbCr(y, cb, cr); - var expected = new Rgb(r, g, b); + // Arrange + var input = new YCbCr(y, cb, cr); + var expected = new Rgb(r, g, b); - Span inputSpan = new YCbCr[5]; - inputSpan.Fill(input); + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); - Span actualSpan = new Rgb[5]; + Span actualSpan = new Rgb[5]; - // Act - var actual = Converter.ToRgb(input); - Converter.Convert(inputSpan, actualSpan); + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } + } - /// - /// Tests conversion from to . - /// - [Theory] - [InlineData(0, 0, 0, 0, 128, 128)] - [InlineData(1, 1, 1, 255, 128, 128)] - [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] - [InlineData(1, 0, 0, 76.245, 84.972, 255)] - public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) - { - // Arrange - var input = new Rgb(r, g, b); - var expected = new YCbCr(y, cb, cr); + /// + /// Tests conversion from to . + /// + [Theory] + [InlineData(0, 0, 0, 0, 128, 128)] + [InlineData(1, 1, 1, 255, 128, 128)] + [InlineData(0.5, 0.5, 0.5, 127.5, 128, 128)] + [InlineData(1, 0, 0, 76.245, 84.972, 255)] + public void Convert_Rgb_To_YCbCr(float r, float g, float b, float y, float cb, float cr) + { + // Arrange + var input = new Rgb(r, g, b); + var expected = new YCbCr(y, cb, cr); - Span inputSpan = new Rgb[5]; - inputSpan.Fill(input); + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); - Span actualSpan = new YCbCr[5]; + Span actualSpan = new YCbCr[5]; - // Act - var actual = ColorSpaceConverter.ToYCbCr(input); - ColorSpaceConverter.Convert(inputSpan, actualSpan); + // Act + var actual = ColorSpaceConverter.ToYCbCr(input); + ColorSpaceConverter.Convert(inputSpan, actualSpan); - // Assert - Assert.Equal(expected, actual, ColorSpaceComparer); + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs index 734b1439b8..9e33ad1689 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -1,41 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion; + +public class VonKriesChromaticAdaptationTests { - public class VonKriesChromaticAdaptationTests + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + public static readonly TheoryData WhitePoints = new TheoryData { - 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] - [MemberData(nameof(WhitePoints))] - public void SingleAndBulkTransformYieldIdenticalResults(CieXyz sourceWhitePoint, CieXyz destinationWhitePoint) - { - var adaptation = new VonKriesChromaticAdaptation(); - var input = new CieXyz(1, 0, 1); - CieXyz expected = adaptation.Transform(input, sourceWhitePoint, destinationWhitePoint); + [Theory] + [MemberData(nameof(WhitePoints))] + public void SingleAndBulkTransformYieldIdenticalResults(CieXyz sourceWhitePoint, CieXyz destinationWhitePoint) + { + var adaptation = new VonKriesChromaticAdaptation(); + var input = new CieXyz(1, 0, 1); + CieXyz expected = adaptation.Transform(input, sourceWhitePoint, destinationWhitePoint); - Span inputSpan = new CieXyz[5]; - inputSpan.Fill(input); + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); - Span actualSpan = new CieXyz[5]; + Span actualSpan = new CieXyz[5]; - adaptation.Transform(inputSpan, actualSpan, sourceWhitePoint, destinationWhitePoint); + adaptation.Transform(inputSpan, actualSpan, sourceWhitePoint, destinationWhitePoint); - for (int i = 0; i < inputSpan.Length; i++) - { - Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); - } + for (int i = 0; i < inputSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/HslTests.cs b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs index 8e8fb5f1ae..a8702488a6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HslTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class HslTests { - /// - /// Tests the struct. - /// - public class HslTests + [Fact] + public void HslConstructorAssignsFields() { - [Fact] - public void HslConstructorAssignsFields() - { - const float h = 275F; - const float s = .64F; - const float l = .87F; - var hsl = new Hsl(h, s, l); + const float h = 275F; + const float s = .64F; + const float l = .87F; + var hsl = new Hsl(h, s, l); - Assert.Equal(h, hsl.H); - Assert.Equal(s, hsl.S); - Assert.Equal(l, hsl.L); - } + Assert.Equal(h, hsl.H); + Assert.Equal(s, hsl.S); + Assert.Equal(l, hsl.L); + } - [Fact] - public void HslEquality() - { - var x = default(Hsl); - var y = new Hsl(Vector3.One); + [Fact] + public void HslEquality() + { + var x = default(Hsl); + var y = new Hsl(Vector3.One); - Assert.True(default(Hsl) == default(Hsl)); - Assert.False(default(Hsl) != default(Hsl)); - Assert.Equal(default(Hsl), default(Hsl)); - Assert.Equal(new Hsl(1, 0, 1), new Hsl(1, 0, 1)); - Assert.Equal(new Hsl(Vector3.One), new Hsl(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(Hsl) == default(Hsl)); + Assert.False(default(Hsl) != default(Hsl)); + Assert.Equal(default(Hsl), default(Hsl)); + Assert.Equal(new Hsl(1, 0, 1), new Hsl(1, 0, 1)); + Assert.Equal(new Hsl(Vector3.One), new Hsl(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs index dad3346291..caedd3f171 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class HsvTests { - /// - /// Tests the struct. - /// - public class HsvTests + [Fact] + public void HsvConstructorAssignsFields() { - [Fact] - public void HsvConstructorAssignsFields() - { - const float h = 275F; - const float s = .64F; - const float v = .87F; - var hsv = new Hsv(h, s, v); + const float h = 275F; + const float s = .64F; + const float v = .87F; + var hsv = new Hsv(h, s, v); - Assert.Equal(h, hsv.H); - Assert.Equal(s, hsv.S); - Assert.Equal(v, hsv.V); - } + Assert.Equal(h, hsv.H); + Assert.Equal(s, hsv.S); + Assert.Equal(v, hsv.V); + } - [Fact] - public void HsvEquality() - { - var x = default(Hsv); - var y = new Hsv(Vector3.One); + [Fact] + public void HsvEquality() + { + var x = default(Hsv); + var y = new Hsv(Vector3.One); - Assert.True(default(Hsv) == default(Hsv)); - Assert.False(default(Hsv) != default(Hsv)); - Assert.Equal(default(Hsv), default(Hsv)); - Assert.Equal(new Hsv(1, 0, 1), new Hsv(1, 0, 1)); - Assert.Equal(new Hsv(Vector3.One), new Hsv(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(Hsv) == default(Hsv)); + Assert.False(default(Hsv) != default(Hsv)); + Assert.Equal(default(Hsv), default(Hsv)); + Assert.Equal(new Hsv(1, 0, 1), new Hsv(1, 0, 1)); + Assert.Equal(new Hsv(Vector3.One), new Hsv(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs index 62ea3668f8..9c97c4c91b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -3,43 +3,41 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class HunterLabTests { - /// - /// Tests the struct. - /// - public class HunterLabTests + [Fact] + public void HunterLabConstructorAssignsFields() { - [Fact] - public void HunterLabConstructorAssignsFields() - { - const float l = 75F; - const float a = -64F; - const float b = 87F; - var hunterLab = new HunterLab(l, a, b); + const float l = 75F; + const float a = -64F; + const float b = 87F; + var hunterLab = new HunterLab(l, a, b); - Assert.Equal(l, hunterLab.L); - Assert.Equal(a, hunterLab.A); - Assert.Equal(b, hunterLab.B); - } + Assert.Equal(l, hunterLab.L); + Assert.Equal(a, hunterLab.A); + Assert.Equal(b, hunterLab.B); + } - [Fact] - public void HunterLabEquality() - { - var x = default(HunterLab); - var y = new HunterLab(Vector3.One); + [Fact] + public void HunterLabEquality() + { + var x = default(HunterLab); + var y = new HunterLab(Vector3.One); - Assert.True(default(HunterLab) == default(HunterLab)); - 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)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(HunterLab) == default(HunterLab)); + 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)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs index 53db418fd3..ff2d151344 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class LinearRgbTests { - /// - /// Tests the struct. - /// - public class LinearRgbTests + [Fact] + public void LinearRgbConstructorAssignsFields() { - [Fact] - public void LinearRgbConstructorAssignsFields() - { - const float r = .75F; - const float g = .64F; - const float b = .87F; - var rgb = new LinearRgb(r, g, b); + const float r = .75F; + const float g = .64F; + const float b = .87F; + var rgb = new LinearRgb(r, g, b); - Assert.Equal(r, rgb.R); - Assert.Equal(g, rgb.G); - Assert.Equal(b, rgb.B); - } + Assert.Equal(r, rgb.R); + Assert.Equal(g, rgb.G); + Assert.Equal(b, rgb.B); + } - [Fact] - public void LinearRgbEquality() - { - var x = default(LinearRgb); - var y = new LinearRgb(Vector3.One); + [Fact] + public void LinearRgbEquality() + { + var x = default(LinearRgb); + var y = new LinearRgb(Vector3.One); - Assert.True(default(LinearRgb) == default(LinearRgb)); - Assert.False(default(LinearRgb) != default(LinearRgb)); - Assert.Equal(default(LinearRgb), default(LinearRgb)); - Assert.Equal(new LinearRgb(1, 0, 1), new LinearRgb(1, 0, 1)); - Assert.Equal(new LinearRgb(Vector3.One), new LinearRgb(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(LinearRgb) == default(LinearRgb)); + Assert.False(default(LinearRgb) != default(LinearRgb)); + Assert.Equal(default(LinearRgb), default(LinearRgb)); + Assert.Equal(new LinearRgb(1, 0, 1), new LinearRgb(1, 0, 1)); + Assert.Equal(new LinearRgb(Vector3.One), new LinearRgb(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs index 3ca49e0f97..5e8840664e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -3,43 +3,41 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class LmsTests { - /// - /// Tests the struct. - /// - public class LmsTests + [Fact] + public void LmsConstructorAssignsFields() { - [Fact] - public void LmsConstructorAssignsFields() - { - const float l = 75F; - const float m = -64F; - const float s = 87F; - var lms = new Lms(l, m, s); + const float l = 75F; + const float m = -64F; + const float s = 87F; + 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] - public void LmsEquality() - { - var x = default(Lms); - var y = new Lms(Vector3.One); + [Fact] + public void LmsEquality() + { + var x = default(Lms); + var y = new Lms(Vector3.One); - Assert.True(default(Lms) == default(Lms)); - 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)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(Lms) == default(Lms)); + 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)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs index 57f27db987..be96b79f46 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs @@ -4,80 +4,78 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class RgbTests { - /// - /// Tests the struct. - /// - public class RgbTests + [Fact] + public void RgbConstructorAssignsFields() { - [Fact] - public void RgbConstructorAssignsFields() - { - const float r = .75F; - const float g = .64F; - const float b = .87F; - var rgb = new Rgb(r, g, b); + const float r = .75F; + const float g = .64F; + const float b = .87F; + var rgb = new Rgb(r, g, b); - Assert.Equal(r, rgb.R); - Assert.Equal(g, rgb.G); - Assert.Equal(b, rgb.B); - } + Assert.Equal(r, rgb.R); + Assert.Equal(g, rgb.G); + Assert.Equal(b, rgb.B); + } - [Fact] - public void RgbEquality() - { - var x = default(Rgb); - var y = new Rgb(Vector3.One); + [Fact] + public void RgbEquality() + { + var x = default(Rgb); + var y = new Rgb(Vector3.One); - Assert.True(default(Rgb) == default(Rgb)); - Assert.False(default(Rgb) != default(Rgb)); - Assert.Equal(default(Rgb), default(Rgb)); - Assert.Equal(new Rgb(1, 0, 1), new Rgb(1, 0, 1)); - Assert.Equal(new Rgb(Vector3.One), new Rgb(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(Rgb) == default(Rgb)); + Assert.False(default(Rgb) != default(Rgb)); + Assert.Equal(default(Rgb), default(Rgb)); + Assert.Equal(new Rgb(1, 0, 1), new Rgb(1, 0, 1)); + Assert.Equal(new Rgb(Vector3.One), new Rgb(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } - [Fact] - public void RgbAndRgb24Operators() - { - const byte r = 64; - const byte g = 128; - const byte b = 255; + [Fact] + public void RgbAndRgb24Operators() + { + const byte r = 64; + const byte g = 128; + const byte b = 255; - Rgb24 rgb24 = new Rgb(r / 255F, g / 255F, b / 255F); - Rgb rgb2 = rgb24; + Rgb24 rgb24 = new Rgb(r / 255F, g / 255F, b / 255F); + Rgb rgb2 = rgb24; - Assert.Equal(r, rgb24.R); - Assert.Equal(g, rgb24.G); - Assert.Equal(b, rgb24.B); + Assert.Equal(r, rgb24.R); + Assert.Equal(g, rgb24.G); + Assert.Equal(b, rgb24.B); - Assert.Equal(r / 255F, rgb2.R); - Assert.Equal(g / 255F, rgb2.G); - Assert.Equal(b / 255F, rgb2.B); - } + Assert.Equal(r / 255F, rgb2.R); + Assert.Equal(g / 255F, rgb2.G); + Assert.Equal(b / 255F, rgb2.B); + } - [Fact] - public void RgbAndRgba32Operators() - { - const byte r = 64; - const byte g = 128; - const byte b = 255; + [Fact] + public void RgbAndRgba32Operators() + { + const byte r = 64; + const byte g = 128; + const byte b = 255; - Rgba32 rgba32 = new Rgb(r / 255F, g / 255F, b / 255F); - Rgb rgb2 = rgba32; + Rgba32 rgba32 = new Rgb(r / 255F, g / 255F, b / 255F); + Rgb rgb2 = rgba32; - Assert.Equal(r, rgba32.R); - Assert.Equal(g, rgba32.G); - Assert.Equal(b, rgba32.B); + Assert.Equal(r, rgba32.R); + Assert.Equal(g, rgba32.G); + Assert.Equal(b, rgba32.B); - Assert.Equal(r / 255F, rgb2.R); - Assert.Equal(g / 255F, rgb2.G); - Assert.Equal(b / 255F, rgb2.B); - } + Assert.Equal(r / 255F, rgb2.R); + Assert.Equal(g / 255F, rgb2.G); + Assert.Equal(b / 255F, rgb2.B); } } diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs index 48e695fe04..2ae0824f28 100644 --- a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -4,62 +4,60 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +public class StringRepresentationTests { - 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)" }, - }; + 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)" }, + }; - [Theory] - [MemberData(nameof(TestData))] - public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); - } + [Theory] + [MemberData(nameof(TestData))] + public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); } diff --git a/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs index 87c39f8fc7..efab45c3cb 100644 --- a/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs @@ -3,42 +3,40 @@ using System.Numerics; using SixLabors.ImageSharp.ColorSpaces; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces; + +/// +/// Tests the struct. +/// +public class YCbCrTests { - /// - /// Tests the struct. - /// - public class YCbCrTests + [Fact] + public void YCbCrConstructorAssignsFields() { - [Fact] - public void YCbCrConstructorAssignsFields() - { - const float y = 75F; - const float cb = 64F; - const float cr = 87F; - var yCbCr = new YCbCr(y, cb, cr); + const float y = 75F; + const float cb = 64F; + const float cr = 87F; + var yCbCr = new YCbCr(y, cb, cr); - Assert.Equal(y, yCbCr.Y); - Assert.Equal(cb, yCbCr.Cb); - Assert.Equal(cr, yCbCr.Cr); - } + Assert.Equal(y, yCbCr.Y); + Assert.Equal(cb, yCbCr.Cb); + Assert.Equal(cr, yCbCr.Cr); + } - [Fact] - public void YCbCrEquality() - { - var x = default(YCbCr); - var y = new YCbCr(Vector3.One); + [Fact] + public void YCbCrEquality() + { + var x = default(YCbCr); + var y = new YCbCr(Vector3.One); - Assert.True(default(YCbCr) == default(YCbCr)); - Assert.False(default(YCbCr) != default(YCbCr)); - Assert.Equal(default(YCbCr), default(YCbCr)); - Assert.Equal(new YCbCr(1, 0, 1), new YCbCr(1, 0, 1)); - Assert.Equal(new YCbCr(Vector3.One), new YCbCr(Vector3.One)); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - Assert.False(x.GetHashCode().Equals(y.GetHashCode())); - } + Assert.True(default(YCbCr) == default(YCbCr)); + Assert.False(default(YCbCr) != default(YCbCr)); + Assert.Equal(default(YCbCr), default(YCbCr)); + Assert.Equal(new YCbCr(1, 0, 1), new YCbCr(1, 0, 1)); + Assert.Equal(new YCbCr(Vector3.One), new YCbCr(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Common/ConstantsTests.cs b/tests/ImageSharp.Tests/Common/ConstantsTests.cs index d36b95ce0f..c47d3816b6 100644 --- a/tests/ImageSharp.Tests/Common/ConstantsTests.cs +++ b/tests/ImageSharp.Tests/Common/ConstantsTests.cs @@ -1,16 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit; +namespace SixLabors.ImageSharp.Tests.Common; -namespace SixLabors.ImageSharp.Tests.Common +public class ConstantsTests { - public class ConstantsTests + [Fact] + public void Epsilon() { - [Fact] - public void Epsilon() - { - Assert.Equal(0.001f, Constants.Epsilon); - } + Assert.Equal(0.001f, Constants.Epsilon); } } diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index 54627afcc5..190bfe1dc6 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -1,32 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Common; + +public class EncoderExtensionsTests { - public class EncoderExtensionsTests + [Fact] + public void GetString_EmptyBuffer_ReturnsEmptyString() { - [Fact] - public void GetString_EmptyBuffer_ReturnsEmptyString() - { - var buffer = default(ReadOnlySpan); + var buffer = default(ReadOnlySpan); - string result = Encoding.UTF8.GetString(buffer); + string result = Encoding.UTF8.GetString(buffer); - Assert.Equal(string.Empty, result); - } + Assert.Equal(string.Empty, result); + } - [Fact] - public void GetString_Buffer_ReturnsString() - { - var buffer = new ReadOnlySpan(new byte[] { 73, 109, 97, 103, 101, 83, 104, 97, 114, 112 }); + [Fact] + public void GetString_Buffer_ReturnsString() + { + var buffer = new ReadOnlySpan(new byte[] { 73, 109, 97, 103, 101, 83, 104, 97, 114, 112 }); - string result = Encoding.UTF8.GetString(buffer); + string result = Encoding.UTF8.GetString(buffer); - Assert.Equal("ImageSharp", result); - } + Assert.Equal("ImageSharp", result); } } diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs index 6f5df43135..2b92769436 100644 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -1,61 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Common; + +public class NumericsTests { - public class NumericsTests - { - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public NumericsTests(ITestOutputHelper output) => this.Output = output; + public NumericsTests(ITestOutputHelper output) => this.Output = output; - public static TheoryData IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue }; + public static TheoryData IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue }; - private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor); + private static uint DivideCeil_ReferenceImplementation(uint value, uint divisor) => (uint)MathF.Ceiling((float)value / divisor); - [Fact] - public void DivideCeil_DivideZero() - { - uint expected = 0; - uint actual = Numerics.DivideCeil(0, 100); + [Fact] + public void DivideCeil_DivideZero() + { + uint expected = 0; + uint actual = Numerics.DivideCeil(0, 100); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [InlineData(1, 100)] - public void DivideCeil_RandomValues(int seed, int count) + [Theory] + [InlineData(1, 100)] + public void DivideCeil_RandomValues(int seed, int count) + { + var rng = new Random(seed); + for (int i = 0; i < count; i++) { - var rng = new Random(seed); - for (int i = 0; i < count; i++) - { - uint value = (uint)rng.Next(); - uint divisor = (uint)rng.Next(); + uint value = (uint)rng.Next(); + uint divisor = (uint)rng.Next(); - uint expected = DivideCeil_ReferenceImplementation(value, divisor); - uint actual = Numerics.DivideCeil(value, divisor); + uint expected = DivideCeil_ReferenceImplementation(value, divisor); + uint actual = Numerics.DivideCeil(value, divisor); - Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}"); - } + Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}"); } + } - private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max; + private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max; - [Theory] - [MemberData(nameof(IsOutOfRangeTestData))] - public void IsOutOfRange(int value) - { - const int min = 7; - const int max = 92; + [Theory] + [MemberData(nameof(IsOutOfRangeTestData))] + public void IsOutOfRange(int value) + { + const int min = 7; + const int max = 92; - bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); - bool actual = Numerics.IsOutOfRange(value, min, max); + bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); + bool actual = Numerics.IsOutOfRange(value, min, max); - Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); - } + Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); } } diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index 673dc9bc6d..9727731b6e 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -1,399 +1,396 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Common +namespace SixLabors.ImageSharp.Tests.Common; + +public partial class SimdUtilsTests { - public partial class SimdUtilsTests + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void BulkShuffleFloat4Channel(int count) { - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffleFloat4Channel(int count) + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - // No need to test multiple shuffle controls as the - // pipeline is always the same. - int size = FeatureTestRunner.Deserialize(serialized); - byte control = default(WZYXShuffle4).Control; - - TestShuffleFloat4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, control), - control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + // No need to test multiple shuffle controls as the + // pipeline is always the same. + int size = FeatureTestRunner.Deserialize(serialized); + byte control = default(WZYXShuffle4).Control; + + TestShuffleFloat4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, control), + control); } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffleByte4Channel(int count) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + } + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void BulkShuffleByte4Channel(int count) + { + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4 to the generic utils method. - WXYZShuffle4 wxyz = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz), - wxyz.Control); - - WZYXShuffle4 wzyx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx), - wzyx.Control); - - YZWXShuffle4 yzwx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx), - yzwx.Control); - - ZYXWShuffle4 zyxw = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw), - zyxw.Control); - - var xwyz = new DefaultShuffle4(2, 1, 3, 0); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); - - var yyyy = new DefaultShuffle4(1, 1, 1, 1); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); - - var wwww = new DefaultShuffle4(3, 3, 3, 3); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + int size = FeatureTestRunner.Deserialize(serialized); + + // These cannot be expressed as a theory as you cannot + // use RemoteExecutor within generic methods nor pass + // IShuffle4 to the generic utils method. + WXYZShuffle4 wxyz = default; + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz), + wxyz.Control); + + WZYXShuffle4 wzyx = default; + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx), + wzyx.Control); + + YZWXShuffle4 yzwx = default; + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx), + yzwx.Control); + + ZYXWShuffle4 zyxw = default; + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw), + zyxw.Control); + + var xwyz = new DefaultShuffle4(2, 1, 3, 0); + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz), + xwyz.Control); + + var yyyy = new DefaultShuffle4(1, 1, 1, 1); + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy), + yyyy.Control); + + var wwww = new DefaultShuffle4(3, 3, 3, 3); + TestShuffleByte4Channel( + size, + (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww), + wwww.Control); } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy3))] - public void BulkShuffleByte3Channel(int count) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + } + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy3))] + public void BulkShuffleByte3Channel(int count) + { + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle3 to the generic utils method. - var zyx = new DefaultShuffle3(0, 1, 2); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx), - zyx.Control); - - var xyz = new DefaultShuffle3(2, 1, 0); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz), - xyz.Control); - - var yyy = new DefaultShuffle3(1, 1, 1); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy), - yyy.Control); - - var zzz = new DefaultShuffle3(2, 2, 2); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz), - zzz.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + int size = FeatureTestRunner.Deserialize(serialized); + + // These cannot be expressed as a theory as you cannot + // use RemoteExecutor within generic methods nor pass + // IShuffle3 to the generic utils method. + var zyx = new DefaultShuffle3(0, 1, 2); + TestShuffleByte3Channel( + size, + (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx), + zyx.Control); + + var xyz = new DefaultShuffle3(2, 1, 0); + TestShuffleByte3Channel( + size, + (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz), + xyz.Control); + + var yyy = new DefaultShuffle3(1, 1, 1); + TestShuffleByte3Channel( + size, + (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy), + yyy.Control); + + var zzz = new DefaultShuffle3(2, 2, 2); + TestShuffleByte3Channel( + size, + (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz), + zzz.Control); } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy3))] - public void BulkPad3Shuffle4Channel(int count) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + } + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy3))] + public void BulkPad3Shuffle4Channel(int count) + { + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IPad3Shuffle4 to the generic utils method. - XYZWPad3Shuffle4 xyzw = default; - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw), - xyzw.Control); - - var xwyz = new DefaultPad3Shuffle4(2, 1, 3, 0); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); - - var yyyy = new DefaultPad3Shuffle4(1, 1, 1, 1); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); - - var wwww = new DefaultPad3Shuffle4(3, 3, 3, 3); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + int size = FeatureTestRunner.Deserialize(serialized); + + // These cannot be expressed as a theory as you cannot + // use RemoteExecutor within generic methods nor pass + // IPad3Shuffle4 to the generic utils method. + XYZWPad3Shuffle4 xyzw = default; + TestPad3Shuffle4Channel( + size, + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw), + xyzw.Control); + + var xwyz = new DefaultPad3Shuffle4(2, 1, 3, 0); + TestPad3Shuffle4Channel( + size, + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz), + xwyz.Control); + + var yyyy = new DefaultPad3Shuffle4(1, 1, 1, 1); + TestPad3Shuffle4Channel( + size, + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy), + yyyy.Control); + + var wwww = new DefaultPad3Shuffle4(3, 3, 3, 3); + TestPad3Shuffle4Channel( + size, + (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww), + wwww.Control); } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void BulkShuffle4Slice3Channel(int count) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + } + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void BulkShuffle4Slice3Channel(int count) + { + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - int size = FeatureTestRunner.Deserialize(serialized); - - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4Slice3 to the generic utils method. - XYZWShuffle4Slice3 xyzw = default; - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw), - xyzw.Control); - - var xwyz = new DefaultShuffle4Slice3(2, 1, 3, 0); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz), - xwyz.Control); - - var yyyy = new DefaultShuffle4Slice3(1, 1, 1, 1); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy), - yyyy.Control); - - var wwww = new DefaultShuffle4Slice3(3, 3, 3, 3); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww), - wwww.Control); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + int size = FeatureTestRunner.Deserialize(serialized); + + // These cannot be expressed as a theory as you cannot + // use RemoteExecutor within generic methods nor pass + // IShuffle4Slice3 to the generic utils method. + XYZWShuffle4Slice3 xyzw = default; + TestShuffle4Slice3Channel( + size, + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw), + xyzw.Control); + + var xwyz = new DefaultShuffle4Slice3(2, 1, 3, 0); + TestShuffle4Slice3Channel( + size, + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz), + xwyz.Control); + + var yyyy = new DefaultShuffle4Slice3(1, 1, 1, 1); + TestShuffle4Slice3Channel( + size, + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy), + yyyy.Control); + + var wwww = new DefaultShuffle4Slice3(3, 3, 3, 3); + TestShuffle4Slice3Channel( + size, + (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww), + wwww.Control); } - private static void TestShuffleFloat4Channel( - int count, - Action, Memory> convert, - byte control) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + } + + private static void TestShuffleFloat4Channel( + int count, + Action, Memory> convert, + byte control) + { + float[] source = new Random(count).GenerateRandomFloatArray(count, 0, 256); + var result = new float[count]; + + float[] expected = new float[count]; + + SimdUtils.Shuffle.InverseMmShuffle( + control, + out int p3, + out int p2, + out int p1, + out int p0); + + for (int i = 0; i < expected.Length; i += 4) { - float[] source = new Random(count).GenerateRandomFloatArray(count, 0, 256); - var result = new float[count]; + expected[i] = source[p0 + i]; + expected[i + 1] = source[p1 + i]; + expected[i + 2] = source[p2 + i]; + expected[i + 3] = source[p3 + i]; + } - float[] expected = new float[count]; + convert(source, result); - SimdUtils.Shuffle.InverseMmShuffle( - control, - out int p3, - out int p2, - out int p1, - out int p0); + Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5F)); + } - for (int i = 0; i < expected.Length; i += 4) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - expected[i + 3] = source[p3 + i]; - } + private static void TestShuffleByte4Channel( + int count, + Action, Memory> convert, + byte control) + { + byte[] source = new byte[count]; + new Random(count).NextBytes(source); + var result = new byte[count]; - convert(source, result); + byte[] expected = new byte[count]; - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5F)); - } + SimdUtils.Shuffle.InverseMmShuffle( + control, + out int p3, + out int p2, + out int p1, + out int p0); - private static void TestShuffleByte4Channel( - int count, - Action, Memory> convert, - byte control) + for (int i = 0; i < expected.Length; i += 4) { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - var result = new byte[count]; - - byte[] expected = new byte[count]; - - SimdUtils.Shuffle.InverseMmShuffle( - control, - out int p3, - out int p2, - out int p1, - out int p0); - - for (int i = 0; i < expected.Length; i += 4) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - expected[i + 3] = source[p3 + i]; - } - - convert(source, result); - - Assert.Equal(expected, result); + expected[i] = source[p0 + i]; + expected[i + 1] = source[p1 + i]; + expected[i + 2] = source[p2 + i]; + expected[i + 3] = source[p3 + i]; } - private static void TestShuffleByte3Channel( - int count, - Action, Memory> convert, - byte control) + convert(source, result); + + Assert.Equal(expected, result); + } + + private static void TestShuffleByte3Channel( + int count, + Action, Memory> convert, + byte control) + { + byte[] source = new byte[count]; + new Random(count).NextBytes(source); + var result = new byte[count]; + + byte[] expected = new byte[count]; + + SimdUtils.Shuffle.InverseMmShuffle( + control, + out int _, + out int p2, + out int p1, + out int p0); + + for (int i = 0; i < expected.Length; i += 3) { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - var result = new byte[count]; + expected[i] = source[p0 + i]; + expected[i + 1] = source[p1 + i]; + expected[i + 2] = source[p2 + i]; + } + + convert(source, result); + + Assert.Equal(expected, result); + } - byte[] expected = new byte[count]; + private static void TestPad3Shuffle4Channel( + int count, + Action, Memory> convert, + byte control) + { + byte[] source = new byte[count]; + new Random(count).NextBytes(source); - SimdUtils.Shuffle.InverseMmShuffle( - control, - out int _, - out int p2, - out int p1, - out int p0); + var result = new byte[count * 4 / 3]; - for (int i = 0; i < expected.Length; i += 3) - { - expected[i] = source[p0 + i]; - expected[i + 1] = source[p1 + i]; - expected[i + 2] = source[p2 + i]; - } + byte[] expected = new byte[result.Length]; - convert(source, result); + SimdUtils.Shuffle.InverseMmShuffle( + control, + out int p3, + out int p2, + out int p1, + out int p0); - Assert.Equal(expected, result); + for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) + { + expected[p0 + i] = source[j]; + expected[p1 + i] = source[j + 1]; + expected[p2 + i] = source[j + 2]; + expected[p3 + i] = byte.MaxValue; } - private static void TestPad3Shuffle4Channel( - int count, - Action, Memory> convert, - byte control) + Span temp = stackalloc byte[4]; + for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); - - var result = new byte[count * 4 / 3]; - - byte[] expected = new byte[result.Length]; - - SimdUtils.Shuffle.InverseMmShuffle( - control, - out int p3, - out int p2, - out int p1, - out int p0); - - for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) - { - expected[p0 + i] = source[j]; - expected[p1 + i] = source[j + 1]; - expected[p2 + i] = source[j + 2]; - expected[p3 + i] = byte.MaxValue; - } - - Span temp = stackalloc byte[4]; - for (int i = 0, j = 0; i < expected.Length; i += 4, j += 3) - { - temp[0] = source[j]; - temp[1] = source[j + 1]; - temp[2] = source[j + 2]; - temp[3] = byte.MaxValue; - - expected[i] = temp[p0]; - expected[i + 1] = temp[p1]; - expected[i + 2] = temp[p2]; - expected[i + 3] = temp[p3]; - } - - convert(source, result); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], result[i]); - } - - Assert.Equal(expected, result); + temp[0] = source[j]; + temp[1] = source[j + 1]; + temp[2] = source[j + 2]; + temp[3] = byte.MaxValue; + + expected[i] = temp[p0]; + expected[i + 1] = temp[p1]; + expected[i + 2] = temp[p2]; + expected[i + 3] = temp[p3]; } - private static void TestShuffle4Slice3Channel( - int count, - Action, Memory> convert, - byte control) + convert(source, result); + + for (int i = 0; i < expected.Length; i++) { - byte[] source = new byte[count]; - new Random(count).NextBytes(source); + Assert.Equal(expected[i], result[i]); + } - var result = new byte[count * 3 / 4]; + Assert.Equal(expected, result); + } - byte[] expected = new byte[result.Length]; + private static void TestShuffle4Slice3Channel( + int count, + Action, Memory> convert, + byte control) + { + byte[] source = new byte[count]; + new Random(count).NextBytes(source); - SimdUtils.Shuffle.InverseMmShuffle( - control, - out int _, - out int p2, - out int p1, - out int p0); + var result = new byte[count * 3 / 4]; - for (int i = 0, j = 0; i < expected.Length; i += 3, j += 4) - { - expected[i] = source[p0 + j]; - expected[i + 1] = source[p1 + j]; - expected[i + 2] = source[p2 + j]; - } + byte[] expected = new byte[result.Length]; - convert(source, result); + SimdUtils.Shuffle.InverseMmShuffle( + control, + out int _, + out int p2, + out int p1, + out int p0); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], result[i]); - } + for (int i = 0, j = 0; i < expected.Length; i += 3, j += 4) + { + expected[i] = source[p0 + j]; + expected[i + 1] = source[p1 + j]; + expected[i + 2] = source[p2 + j]; + } + + convert(source, result); - Assert.Equal(expected, result); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], result[i]); } + + Assert.Equal(expected, result); } } diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 1ba3c64de9..3e8e171645 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -1,374 +1,370 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Common -{ - public partial class SimdUtilsTests - { - private ITestOutputHelper Output { get; } +namespace SixLabors.ImageSharp.Tests.Common; - public SimdUtilsTests(ITestOutputHelper output) => this.Output = output; +public partial class SimdUtilsTests +{ + private ITestOutputHelper Output { get; } - private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); + public SimdUtilsTests(ITestOutputHelper output) => this.Output = output; - private static int Re(float f) => (int)Math.Round(f, MidpointRounding.ToEven); + private static int R(float f) => (int)Math.Round(f, MidpointRounding.AwayFromZero); - // TODO: Move this to a proper test class! - [Theory] - [InlineData(0.32, 54.5, -3.5, -4.1)] - [InlineData(5.3, 536.4, 4.5, 8.1)] - public void PseudoRound(float x, float y, float z, float w) - { - var v = new Vector4(x, y, z, w); + private static int Re(float f) => (int)Math.Round(f, MidpointRounding.ToEven); - Vector4 actual = v.PseudoRound(); + // TODO: Move this to a proper test class! + [Theory] + [InlineData(0.32, 54.5, -3.5, -4.1)] + [InlineData(5.3, 536.4, 4.5, 8.1)] + public void PseudoRound(float x, float y, float z, float w) + { + var v = new Vector4(x, y, z, w); - Assert.Equal(R(v.X), (int)actual.X); - Assert.Equal(R(v.Y), (int)actual.Y); - Assert.Equal(R(v.Z), (int)actual.Z); - Assert.Equal(R(v.W), (int)actual.W); - } + Vector4 actual = v.PseudoRound(); - private static Vector CreateExactTestVector1() - { - float[] data = new float[Vector.Count]; + Assert.Equal(R(v.X), (int)actual.X); + Assert.Equal(R(v.Y), (int)actual.Y); + Assert.Equal(R(v.Z), (int)actual.Z); + Assert.Equal(R(v.W), (int)actual.W); + } - data[0] = 0.1f; - data[1] = 0.4f; - data[2] = 0.5f; - data[3] = 0.9f; + private static Vector CreateExactTestVector1() + { + float[] data = new float[Vector.Count]; - for (int i = 4; i < Vector.Count; i++) - { - data[i] = data[i - 4] + 100f; - } + data[0] = 0.1f; + data[1] = 0.4f; + data[2] = 0.5f; + data[3] = 0.9f; - return new Vector(data); + for (int i = 4; i < Vector.Count; i++) + { + data[i] = data[i - 4] + 100f; } - private static Vector CreateRandomTestVector(int seed, float min, float max) - { - float[] data = new float[Vector.Count]; + return new Vector(data); + } - var rnd = new Random(seed); + private static Vector CreateRandomTestVector(int seed, float min, float max) + { + float[] data = new float[Vector.Count]; - for (int i = 0; i < Vector.Count; i++) - { - float v = ((float)rnd.NextDouble() * (max - min)) + min; - data[i] = v; - } + var rnd = new Random(seed); - return new Vector(data); + for (int i = 0; i < Vector.Count; i++) + { + float v = ((float)rnd.NextDouble() * (max - min)) + min; + data[i] = v; } - [Fact] - public void FastRound() - { - Vector v = CreateExactTestVector1(); - Vector r = v.FastRound(); + return new Vector(data); + } - this.Output.WriteLine(r.ToString()); + [Fact] + public void FastRound() + { + Vector v = CreateExactTestVector1(); + Vector r = v.FastRound(); - AssertEvenRoundIsCorrect(r, v); - } + this.Output.WriteLine(r.ToString()); - [Theory] - [InlineData(1, 1f)] - [InlineData(1, 10f)] - [InlineData(1, 1000f)] - [InlineData(42, 1f)] - [InlineData(42, 10f)] - [InlineData(42, 1000f)] - public void FastRound_RandomValues(int seed, float scale) - { - Vector v = CreateRandomTestVector(seed, -scale * 0.5f, scale * 0.5f); - Vector r = v.FastRound(); + AssertEvenRoundIsCorrect(r, v); + } - this.Output.WriteLine(v.ToString()); - this.Output.WriteLine(r.ToString()); + [Theory] + [InlineData(1, 1f)] + [InlineData(1, 10f)] + [InlineData(1, 1000f)] + [InlineData(42, 1f)] + [InlineData(42, 10f)] + [InlineData(42, 1000f)] + public void FastRound_RandomValues(int seed, float scale) + { + Vector v = CreateRandomTestVector(seed, -scale * 0.5f, scale * 0.5f); + Vector r = v.FastRound(); - AssertEvenRoundIsCorrect(r, v); - } + this.Output.WriteLine(v.ToString()); + this.Output.WriteLine(r.ToString()); - private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) - { - if (!SimdUtils.HasVector8) - { - this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); - return true; - } + AssertEvenRoundIsCorrect(r, v); + } - return false; + private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) + { + if (!SimdUtils.HasVector8) + { + this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); + return true; } - public static readonly TheoryData ArraySizesDivisibleBy8 = new() { 0, 8, 16, 1024 }; - public static readonly TheoryData ArraySizesDivisibleBy4 = new() { 0, 4, 8, 28, 1020 }; - public static readonly TheoryData ArraySizesDivisibleBy3 = new() { 0, 3, 9, 36, 957 }; - public static readonly TheoryData ArraySizesDivisibleBy32 = new() { 0, 32, 512 }; + return false; + } + + public static readonly TheoryData ArraySizesDivisibleBy8 = new() { 0, 8, 16, 1024 }; + public static readonly TheoryData ArraySizesDivisibleBy4 = new() { 0, 4, 8, 28, 1020 }; + public static readonly TheoryData ArraySizesDivisibleBy3 = new() { 0, 3, 9, 36, 957 }; + public static readonly TheoryData ArraySizesDivisibleBy32 = new() { 0, 32, 512 }; - public static readonly TheoryData ArbitraryArraySizes = new() { 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 }; + public static readonly TheoryData ArbitraryArraySizes = new() { 0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 63, 64, 255, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520 }; - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( - count, - (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void FallbackIntrinsics128_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy32))] - public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( - count, - (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy32))] - public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count) + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void HwIntrinsics_BulkConvertByteToNormalizedFloat(int count) + { + if (!Sse2.IsSupported) { - if (!Sse2.IsSupported) - { - return; - } + return; + } - static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFloat( - FeatureTestRunner.Deserialize(serialized), - (s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); + static void RunTest(string serialized) => TestImpl_BulkConvertByteToNormalizedFloat( + FeatureTestRunner.Deserialize(serialized), + (s, d) => SimdUtils.HwIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41); + } - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( - count, - (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void BulkConvertByteToNormalizedFloat(int count) => TestImpl_BulkConvertByteToNormalizedFloat( + count, + (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); - private static void TestImpl_BulkConvertByteToNormalizedFloat( - int count, - Action, Memory> convert) - { - byte[] source = new Random(count).GenerateRandomByteArray(count); - float[] result = new float[count]; - float[] expected = source.Select(b => b / 255f).ToArray(); + private static void TestImpl_BulkConvertByteToNormalizedFloat( + int count, + Action, Memory> convert) + { + byte[] source = new Random(count).GenerateRandomByteArray(count); + float[] result = new float[count]; + float[] expected = source.Select(b => b / 255f).ToArray(); - convert(source, result); + convert(source, result); - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); - } + Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); + } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy4))] - public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - count, - (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); - - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy32))] - public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - count, - (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); - - [Theory] - [InlineData(1234)] - public void ExtendedIntrinsics_ConvertToSingle(short scale) - { - int n = Vector.Count; - short[] sData = new Random(scale).GenerateRandomInt16Array(2 * n, (short)-scale, scale); - float[] fData = sData.Select(u => (float)u).ToArray(); + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy4))] + public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + + [Theory] + [InlineData(1234)] + public void ExtendedIntrinsics_ConvertToSingle(short scale) + { + int n = Vector.Count; + short[] sData = new Random(scale).GenerateRandomInt16Array(2 * n, (short)-scale, scale); + float[] fData = sData.Select(u => (float)u).ToArray(); - var source = new Vector(sData); + var source = new Vector(sData); - var expected1 = new Vector(fData, 0); - var expected2 = new Vector(fData, n); + var expected1 = new Vector(fData, 0); + var expected2 = new Vector(fData, n); - // Act: - SimdUtils.ExtendedIntrinsics.ConvertToSingle(source, out Vector actual1, out Vector actual2); + // Act: + SimdUtils.ExtendedIntrinsics.ConvertToSingle(source, out Vector actual1, out Vector actual2); - // Assert: - Assert.Equal(expected1, actual1); - Assert.Equal(expected2, actual2); - } + // Assert: + Assert.Equal(expected1, actual1); + Assert.Equal(expected2, actual2); + } - [Theory] - [MemberData(nameof(ArraySizesDivisibleBy32))] - public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void HwIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + if (!Sse2.IsSupported) { - if (!Sse2.IsSupported) - { - return; - } + return; + } - static void RunTest(string serialized) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - FeatureTestRunner.Deserialize(serialized), - (s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + static void RunTest(string serialized) => TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + FeatureTestRunner.Deserialize(serialized), + (s, d) => SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + count, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void BulkConvertNormalizedFloatToByteClampOverflows(int count) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); - // For small values, let's stress test the implementation a bit: - if (count is > 0 and < 10) + // For small values, let's stress test the implementation a bit: + if (count is > 0 and < 10) + { + for (int i = 0; i < 20; i++) { - for (int i = 0; i < 20; i++) - { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - count, - (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), - i + 42); - } + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), + i + 42); } } + } - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( - count, - (r, g, b, actual) => - SimdUtils.PackFromRgbPlanes(r, g, b, actual)); - - [Theory] - [MemberData(nameof(ArbitraryArraySizes))] - public void PackFromRgbPlanes_Rgba32(int count) => TestPackFromRgbPlanes( - count, - (r, g, b, actual) => - SimdUtils.PackFromRgbPlanes(r, g, b, actual)); - - [Fact] - public void PackFromRgbPlanesAvx2Reduce_Rgb24() + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( + count, + (r, g, b, actual) => + SimdUtils.PackFromRgbPlanes(r, g, b, actual)); + + [Theory] + [MemberData(nameof(ArbitraryArraySizes))] + public void PackFromRgbPlanes_Rgba32(int count) => TestPackFromRgbPlanes( + count, + (r, g, b, actual) => + SimdUtils.PackFromRgbPlanes(r, g, b, actual)); + + [Fact] + public void PackFromRgbPlanesAvx2Reduce_Rgb24() + { + if (!Avx2.IsSupported) { - if (!Avx2.IsSupported) - { - return; - } - - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); - const int padding = 4; - var d = new Rgb24[32 + padding]; - - ReadOnlySpan rr = r.AsSpan(); - ReadOnlySpan gg = g.AsSpan(); - ReadOnlySpan bb = b.AsSpan(); - Span dd = d.AsSpan(); + return; + } - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); + byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); + byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); + byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); + const int padding = 4; + var d = new Rgb24[32 + padding]; - for (int i = 0; i < 32; i++) - { - Assert.Equal(i, d[i].R); - Assert.Equal(i + 100, d[i].G); - Assert.Equal(i + 200, d[i].B); - } + ReadOnlySpan rr = r.AsSpan(); + ReadOnlySpan gg = g.AsSpan(); + ReadOnlySpan bb = b.AsSpan(); + Span dd = d.AsSpan(); - Assert.Equal(0, rr.Length); - Assert.Equal(0, gg.Length); - Assert.Equal(0, bb.Length); - Assert.Equal(padding, dd.Length); - } + SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); - [Fact] - public void PackFromRgbPlanesAvx2Reduce_Rgba32() + for (int i = 0; i < 32; i++) { - if (!Avx2.IsSupported) - { - return; - } + Assert.Equal(i, d[i].R); + Assert.Equal(i + 100, d[i].G); + Assert.Equal(i + 200, d[i].B); + } - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); + Assert.Equal(0, rr.Length); + Assert.Equal(0, gg.Length); + Assert.Equal(0, bb.Length); + Assert.Equal(padding, dd.Length); + } - var d = new Rgba32[32]; + [Fact] + public void PackFromRgbPlanesAvx2Reduce_Rgba32() + { + if (!Avx2.IsSupported) + { + return; + } - ReadOnlySpan rr = r.AsSpan(); - ReadOnlySpan gg = g.AsSpan(); - ReadOnlySpan bb = b.AsSpan(); - Span dd = d.AsSpan(); + byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); + byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); + byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); - SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); + var d = new Rgba32[32]; - for (int i = 0; i < 32; i++) - { - Assert.Equal(i, d[i].R); - Assert.Equal(i + 100, d[i].G); - Assert.Equal(i + 200, d[i].B); - Assert.Equal(255, d[i].A); - } + ReadOnlySpan rr = r.AsSpan(); + ReadOnlySpan gg = g.AsSpan(); + ReadOnlySpan bb = b.AsSpan(); + Span dd = d.AsSpan(); - Assert.Equal(0, rr.Length); - Assert.Equal(0, gg.Length); - Assert.Equal(0, bb.Length); - Assert.Equal(0, dd.Length); - } + SimdUtils.HwIntrinsics.PackFromRgbPlanesAvx2Reduce(ref rr, ref gg, ref bb, ref dd); - internal static void TestPackFromRgbPlanes(int count, Action packMethod) - where TPixel : unmanaged, IPixel + for (int i = 0; i < 32; i++) { - var rnd = new Random(42); - byte[] r = rnd.GenerateRandomByteArray(count); - byte[] g = rnd.GenerateRandomByteArray(count); - byte[] b = rnd.GenerateRandomByteArray(count); + Assert.Equal(i, d[i].R); + Assert.Equal(i + 100, d[i].G); + Assert.Equal(i + 200, d[i].B); + Assert.Equal(255, d[i].A); + } - var expected = new TPixel[count]; - for (int i = 0; i < count; i++) - { - expected[i].FromRgb24(new Rgb24(r[i], g[i], b[i])); - } + Assert.Equal(0, rr.Length); + Assert.Equal(0, gg.Length); + Assert.Equal(0, bb.Length); + Assert.Equal(0, dd.Length); + } - var actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 - packMethod(r, g, b, actual); + internal static void TestPackFromRgbPlanes(int count, Action packMethod) + where TPixel : unmanaged, IPixel + { + var rnd = new Random(42); + byte[] r = rnd.GenerateRandomByteArray(count); + byte[] g = rnd.GenerateRandomByteArray(count); + byte[] b = rnd.GenerateRandomByteArray(count); - Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan()[..count])); + var expected = new TPixel[count]; + for (int i = 0; i < count; i++) + { + expected[i].FromRgb24(new Rgb24(r[i], g[i], b[i])); } - private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( - int count, - Action, - Memory> convert, - int seed = -1) - { - seed = seed > 0 ? seed : count; - float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); - byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); - byte[] actual = new byte[count]; + var actual = new TPixel[count + 3]; // padding for Rgb24 AVX2 + packMethod(r, g, b, actual); - convert(source, actual); + Assert.True(expected.AsSpan().SequenceEqual(actual.AsSpan()[..count])); + } - Assert.Equal(expected, actual); - } + private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + int count, + Action, + Memory> convert, + int seed = -1) + { + seed = seed > 0 ? seed : count; + float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); + byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); + byte[] actual = new byte[count]; + + convert(source, actual); - private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); + Assert.Equal(expected, actual); + } - private static void AssertEvenRoundIsCorrect(Vector r, Vector v) + private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); + + private static void AssertEvenRoundIsCorrect(Vector r, Vector v) + { + for (int i = 0; i < Vector.Count; i++) { - for (int i = 0; i < Vector.Count; i++) - { - int actual = (int)r[i]; - int expected = Re(v[i]); + int actual = (int)r[i]; + int expected = Re(v[i]); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } } } diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 8e826bbc5c..1931109448 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -1,117 +1,111 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Common; -namespace SixLabors.ImageSharp.Tests.Common +public class StreamExtensionsTests { - public class StreamExtensionsTests + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Skip_CountZeroOrLower_PositionNotChanged(int count) { - [Theory] - [InlineData(0)] - [InlineData(-1)] - public void Skip_CountZeroOrLower_PositionNotChanged(int count) + using (var memStream = new MemoryStream(5)) { - using (var memStream = new MemoryStream(5)) - { - memStream.Position = 4; - memStream.Skip(count); + memStream.Position = 4; + memStream.Skip(count); - Assert.Equal(4, memStream.Position); - } + Assert.Equal(4, memStream.Position); } + } - [Fact] - public void Skip_SeekableStream_SeekIsCalled() + [Fact] + public void Skip_SeekableStream_SeekIsCalled() + { + using (var seekableStream = new SeekableStream(4)) { - using (var seekableStream = new SeekableStream(4)) - { - seekableStream.Skip(4); + seekableStream.Skip(4); - Assert.Equal(4, seekableStream.Offset); - Assert.Equal(SeekOrigin.Current, seekableStream.Loc); - } + Assert.Equal(4, seekableStream.Offset); + Assert.Equal(SeekOrigin.Current, seekableStream.Loc); } + } - [Fact] - public void Skip_NonSeekableStream_BytesAreRead() + [Fact] + public void Skip_NonSeekableStream_BytesAreRead() + { + using (var nonSeekableStream = new NonSeekableStream()) { - using (var nonSeekableStream = new NonSeekableStream()) - { - nonSeekableStream.Skip(5); + nonSeekableStream.Skip(5); - Assert.Equal(3, nonSeekableStream.Counts.Count); + Assert.Equal(3, nonSeekableStream.Counts.Count); - Assert.Equal(5, nonSeekableStream.Counts[0]); - Assert.Equal(3, nonSeekableStream.Counts[1]); - Assert.Equal(1, nonSeekableStream.Counts[2]); - } + Assert.Equal(5, nonSeekableStream.Counts[0]); + Assert.Equal(3, nonSeekableStream.Counts[1]); + Assert.Equal(1, nonSeekableStream.Counts[2]); } + } - [Fact] - public void Skip_EofStream_NoExceptionIsThrown() + [Fact] + public void Skip_EofStream_NoExceptionIsThrown() + { + using (var eofStream = new EofStream(7)) { - using (var eofStream = new EofStream(7)) - { - eofStream.Skip(7); + eofStream.Skip(7); - Assert.Equal(0, eofStream.Position); - } + Assert.Equal(0, eofStream.Position); } + } + + private class SeekableStream : MemoryStream + { + public long Offset; + public SeekOrigin Loc; - private class SeekableStream : MemoryStream + public SeekableStream(int capacity) + : base(capacity) { - public long Offset; - public SeekOrigin Loc; - - public SeekableStream(int capacity) - : base(capacity) - { - } - - public override long Seek(long offset, SeekOrigin loc) - { - this.Offset = offset; - this.Loc = loc; - return base.Seek(offset, loc); - } } - private class NonSeekableStream : MemoryStream + public override long Seek(long offset, SeekOrigin loc) { - public override bool CanSeek => false; - - public List Counts = new List(); + this.Offset = offset; + this.Loc = loc; + return base.Seek(offset, loc); + } + } - public NonSeekableStream() - : base(4) - { - } + private class NonSeekableStream : MemoryStream + { + public override bool CanSeek => false; - public override int Read(byte[] buffer, int offset, int count) - { - this.Counts.Add(count); + public List Counts = new List(); - return Math.Min(2, count); - } + public NonSeekableStream() + : base(4) + { } - private class EofStream : MemoryStream + public override int Read(byte[] buffer, int offset, int count) { - public override bool CanSeek => false; + this.Counts.Add(count); - public EofStream(int capacity) - : base(capacity) - { - } + return Math.Min(2, count); + } + } - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } + private class EofStream : MemoryStream + { + public override bool CanSeek => false; + + public EofStream(int capacity) + : base(capacity) + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; } } } diff --git a/tests/ImageSharp.Tests/Common/Tuple8.cs b/tests/ImageSharp.Tests/Common/Tuple8.cs index a3d9a87718..47e9e1128c 100644 --- a/tests/ImageSharp.Tests/Common/Tuple8.cs +++ b/tests/ImageSharp.Tests/Common/Tuple8.cs @@ -3,99 +3,98 @@ using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Common.Tuples +namespace SixLabors.ImageSharp.Common.Tuples; + +/// +/// Contains value type tuples of 8 elements. +/// TODO: We should T4 this stuff to be DRY +/// +internal static class Tuple8 { /// - /// Contains value type tuples of 8 elements. - /// TODO: We should T4 this stuff to be DRY + /// Value type tuple of 8 -s /// - internal static class Tuple8 + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] + public struct OfUInt32 { - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] - public struct OfUInt32 + [FieldOffset(0 * sizeof(uint))] + public uint V0; + + [FieldOffset(1 * sizeof(uint))] + public uint V1; + + [FieldOffset(2 * sizeof(uint))] + public uint V2; + + [FieldOffset(3 * sizeof(uint))] + public uint V3; + + [FieldOffset(4 * sizeof(uint))] + public uint V4; + + [FieldOffset(5 * sizeof(uint))] + public uint V5; + + [FieldOffset(6 * sizeof(uint))] + public uint V6; + + [FieldOffset(7 * sizeof(uint))] + public uint V7; + + public override string ToString() { - [FieldOffset(0 * sizeof(uint))] - public uint V0; + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } - [FieldOffset(1 * sizeof(uint))] - public uint V1; + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct OfByte + { + [FieldOffset(0)] + public byte V0; - [FieldOffset(2 * sizeof(uint))] - public uint V2; + [FieldOffset(1)] + public byte V1; - [FieldOffset(3 * sizeof(uint))] - public uint V3; + [FieldOffset(2)] + public byte V2; - [FieldOffset(4 * sizeof(uint))] - public uint V4; + [FieldOffset(3)] + public byte V3; - [FieldOffset(5 * sizeof(uint))] - public uint V5; + [FieldOffset(4)] + public byte V4; - [FieldOffset(6 * sizeof(uint))] - public uint V6; + [FieldOffset(5)] + public byte V5; - [FieldOffset(7 * sizeof(uint))] - public uint V7; + [FieldOffset(6)] + public byte V6; - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } + [FieldOffset(7)] + public byte V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; } /// - /// Value type tuple of 8 -s + /// Sets the values of this tuple by casting all elements of the given tuple to . /// - [StructLayout(LayoutKind.Explicit, Size = 8)] - public struct OfByte + public void LoadFrom(ref OfUInt32 i) { - [FieldOffset(0)] - public byte V0; - - [FieldOffset(1)] - public byte V1; - - [FieldOffset(2)] - public byte V2; - - [FieldOffset(3)] - public byte V3; - - [FieldOffset(4)] - public byte V4; - - [FieldOffset(5)] - public byte V5; - - [FieldOffset(6)] - public byte V6; - - [FieldOffset(7)] - public byte V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - - /// - /// Sets the values of this tuple by casting all elements of the given tuple to . - /// - public void LoadFrom(ref OfUInt32 i) - { - this.V0 = (byte)i.V0; - this.V1 = (byte)i.V1; - this.V2 = (byte)i.V2; - this.V3 = (byte)i.V3; - this.V4 = (byte)i.V4; - this.V5 = (byte)i.V5; - this.V6 = (byte)i.V6; - this.V7 = (byte)i.V7; - } + this.V0 = (byte)i.V0; + this.V1 = (byte)i.V1; + this.V2 = (byte)i.V2; + this.V3 = (byte)i.V3; + this.V4 = (byte)i.V4; + this.V5 = (byte)i.V5; + this.V6 = (byte)i.V6; + this.V7 = (byte)i.V7; } } } diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 4a1b89124c..a3419eb27d 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -1,192 +1,187 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using Microsoft.DotNet.RemoteExecutor; using Moq; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tests.Memory; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the configuration class. +/// +public class ConfigurationTests { + public Configuration ConfigurationEmpty { get; } + + public Configuration DefaultConfiguration { get; } + + private readonly int expectedDefaultConfigurationCount = 8; + + public ConfigurationTests() + { + // The shallow copy of configuration should behave exactly like the default configuration, + // so by using the copy, we test both the default and the copy. + this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); + this.ConfigurationEmpty = new Configuration(); + } + + [Fact] + public void DefaultsToLocalFileSystem() + { + Assert.IsType(this.DefaultConfiguration.FileSystem); + Assert.IsType(this.ConfigurationEmpty.FileSystem); + } + /// - /// Tests the configuration class. + /// Test that the default configuration is not null. /// - public class ConfigurationTests - { - public Configuration ConfigurationEmpty { get; } + [Fact] + public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null); - public Configuration DefaultConfiguration { get; } + /// + /// Test that the default configuration read origin options is set to begin. + /// + [Fact] + public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); - private readonly int expectedDefaultConfigurationCount = 8; + /// + /// Test that the default configuration parallel options max degrees of parallelism matches the + /// environment processor count. + /// + [Fact] + public void TestDefaultConfigurationMaxDegreeOfParallelism() + { + Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); - public ConfigurationTests() - { - // The shallow copy of configuration should behave exactly like the default configuration, - // so by using the copy, we test both the default and the copy. - this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); - this.ConfigurationEmpty = new Configuration(); - } + var cfg = new Configuration(); + Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); + } - [Fact] - public void DefaultsToLocalFileSystem() + [Theory] + [InlineData(-3, true)] + [InlineData(-2, true)] + [InlineData(-1, false)] + [InlineData(0, true)] + [InlineData(1, false)] + [InlineData(5, false)] + public void MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) + { + var cfg = new Configuration(); + if (throws) { - Assert.IsType(this.DefaultConfiguration.FileSystem); - Assert.IsType(this.ConfigurationEmpty.FileSystem); + Assert.Throws( + () => cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism); } - - /// - /// Test that the default configuration is not null. - /// - [Fact] - public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null); - - /// - /// Test that the default configuration read origin options is set to begin. - /// - [Fact] - public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); - - /// - /// Test that the default configuration parallel options max degrees of parallelism matches the - /// environment processor count. - /// - [Fact] - public void TestDefaultConfigurationMaxDegreeOfParallelism() + else { - Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); - - var cfg = new Configuration(); - Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); + cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; + Assert.Equal(maxDegreeOfParallelism, cfg.MaxDegreeOfParallelism); } + } - [Theory] - [InlineData(-3, true)] - [InlineData(-2, true)] - [InlineData(-1, false)] - [InlineData(0, true)] - [InlineData(1, false)] - [InlineData(5, false)] - public void MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) - { - var cfg = new Configuration(); - if (throws) - { - Assert.Throws( - () => cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism); - } - else - { - cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism; - Assert.Equal(maxDegreeOfParallelism, cfg.MaxDegreeOfParallelism); - } - } + [Fact] + public void ConstructorCallConfigureOnFormatProvider() + { + var provider = new Mock(); + var config = new Configuration(provider.Object); - [Fact] - public void ConstructorCallConfigureOnFormatProvider() - { - var provider = new Mock(); - var config = new Configuration(provider.Object); + provider.Verify(x => x.Configure(config)); + } - provider.Verify(x => x.Configure(config)); - } + [Fact] + public void AddFormatCallsConfig() + { + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); - [Fact] - public void AddFormatCallsConfig() - { - var provider = new Mock(); - var config = new Configuration(); - config.Configure(provider.Object); + provider.Verify(x => x.Configure(config)); + } - provider.Verify(x => x.Configure(config)); - } + [Fact] + public void ConfigurationCannotAddDuplicates() + { + Configuration config = this.DefaultConfiguration; - [Fact] - public void ConfigurationCannotAddDuplicates() - { - Configuration config = this.DefaultConfiguration; + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); - Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); + config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); - config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); + } - Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); - } + [Fact] + public void DefaultConfigurationHasCorrectFormatCount() + { + var config = Configuration.CreateDefaultInstance(); - [Fact] - public void DefaultConfigurationHasCorrectFormatCount() - { - var config = Configuration.CreateDefaultInstance(); + Assert.Equal(this.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); + } - [Fact] - public void WorkingBufferSizeHint_DefaultIsCorrect() - { - Configuration config = this.DefaultConfiguration; - Assert.True(config.WorkingBufferSizeHintInBytes > 1024); - } + [Fact] + public void StreamBufferSize_DefaultIsCorrect() + { + Configuration config = this.DefaultConfiguration; + Assert.True(config.StreamProcessingBufferSize == 8096); + } - [Fact] - public void StreamBufferSize_DefaultIsCorrect() - { - Configuration config = this.DefaultConfiguration; - Assert.True(config.StreamProcessingBufferSize == 8096); - } + [Fact] + public void StreamBufferSize_CannotGoBelowMinimum() + { + var config = new Configuration(); - [Fact] - public void StreamBufferSize_CannotGoBelowMinimum() - { - var config = new Configuration(); + Assert.Throws( + () => config.StreamProcessingBufferSize = 0); + } - Assert.Throws( - () => config.StreamProcessingBufferSize = 0); - } + [Fact] + public void MemoryAllocator_Setter_Roundtrips() + { + MemoryAllocator customAllocator = new SimpleGcMemoryAllocator(); + var config = new Configuration() { MemoryAllocator = customAllocator }; + Assert.Same(customAllocator, config.MemoryAllocator); + } - [Fact] - public void MemoryAllocator_Setter_Roundtrips() - { - MemoryAllocator customAllocator = new SimpleGcMemoryAllocator(); - var config = new Configuration() { MemoryAllocator = customAllocator }; - Assert.Same(customAllocator, config.MemoryAllocator); - } + [Fact] + public void MemoryAllocator_SetNull_ThrowsArgumentNullException() + { + var config = new Configuration(); + Assert.Throws(() => config.MemoryAllocator = null); + } - [Fact] - public void MemoryAllocator_SetNull_ThrowsArgumentNullException() - { - var config = new Configuration(); - Assert.Throws(() => config.MemoryAllocator = null); - } + [Fact] + public void InheritsDefaultMemoryAllocatorInstance() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [Fact] - public void InheritsDefaultMemoryAllocatorInstance() + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - var c1 = new Configuration(); - var c2 = new Configuration(new MockConfigurationModule()); - var c3 = Configuration.CreateDefaultInstance(); - - Assert.Same(MemoryAllocator.Default, Configuration.Default.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c1.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c2.MemoryAllocator); - Assert.Same(MemoryAllocator.Default, c3.MemoryAllocator); - } + var c1 = new Configuration(); + var c2 = new Configuration(new MockConfigurationModule()); + var c3 = Configuration.CreateDefaultInstance(); + + Assert.Same(MemoryAllocator.Default, Configuration.Default.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c1.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c2.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c3.MemoryAllocator); } + } - private class MockConfigurationModule : IConfigurationModule + private class MockConfigurationModule : IConfigurationModule + { + public void Configure(Configuration configuration) { - public void Configure(Configuration configuration) - { - } } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs index f1a1a5d54a..26129c5998 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs @@ -6,70 +6,67 @@ using SixLabors.ImageSharp.Processing.Processors.Drawing; using SixLabors.ImageSharp.Tests.Processing; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Drawing; -namespace SixLabors.ImageSharp.Tests.Drawing +public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest { - public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest + [Fact] + public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext() { - [Fact] - public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easly defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - this.operations.DrawImage(null, 0.5f); - DrawImageProcessor dip = this.Verify(); + this.operations.DrawImage(null, 0.5f); + DrawImageProcessor dip = this.Verify(); - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); - } + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); + } - [Fact] - public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easly defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + [Fact] + public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f); - DrawImageProcessor dip = this.Verify(); + this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f); + DrawImageProcessor dip = this.Verify(); - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); - } + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); + } - [Fact] - public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easly defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + [Fact] + public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - this.operations.DrawImage(null, Point.Empty, 0.5f); - DrawImageProcessor dip = this.Verify(); + this.operations.DrawImage(null, Point.Empty, 0.5f); + DrawImageProcessor dip = this.Verify(); - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); - } + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(this.options.ColorBlendingMode, dip.ColorBlendingMode); + } - [Fact] - public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext() - { - // non-default values as we cant easly defect usage otherwise - this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; - this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; + [Fact] + public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFromContext() + { + // non-default values as we cant easly defect usage otherwise + this.options.AlphaCompositionMode = PixelAlphaCompositionMode.Xor; + this.options.ColorBlendingMode = PixelColorBlendingMode.Screen; - this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f); - DrawImageProcessor dip = this.Verify(); + this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f); + DrawImageProcessor dip = this.Verify(); - Assert.Equal(0.5, dip.Opacity); - Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); - Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); - } + Assert.Equal(0.5, dip.Opacity); + Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode); + Assert.Equal(PixelColorBlendingMode.Multiply, dip.ColorBlendingMode); } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 7043d3972c..1129c4e27d 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -1,200 +1,195 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Drawing; -namespace SixLabors.ImageSharp.Tests.Drawing +[GroupOutput("Drawing")] +public class DrawImageTests { - [GroupOutput("Drawing")] - public class DrawImageTests + public static readonly TheoryData BlendingModes = new TheoryData + { + PixelColorBlendingMode.Normal, + PixelColorBlendingMode.Multiply, + PixelColorBlendingMode.Add, + PixelColorBlendingMode.Subtract, + PixelColorBlendingMode.Screen, + PixelColorBlendingMode.Darken, + PixelColorBlendingMode.Lighten, + PixelColorBlendingMode.Overlay, + PixelColorBlendingMode.HardLight, + }; + + [Theory] + [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] + public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) + where TPixel : unmanaged, IPixel { - public static readonly TheoryData BlendingModes = new TheoryData - { - PixelColorBlendingMode.Normal, - PixelColorBlendingMode.Multiply, - PixelColorBlendingMode.Add, - PixelColorBlendingMode.Subtract, - PixelColorBlendingMode.Screen, - PixelColorBlendingMode.Darken, - PixelColorBlendingMode.Lighten, - PixelColorBlendingMode.Overlay, - PixelColorBlendingMode.HardLight, - }; - - [Theory] - [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] - public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) - where TPixel : unmanaged, IPixel + using (Image background = provider.GetImage()) + using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) { - using (Image background = provider.GetImage()) - using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) - { - background.Mutate(x => x.DrawImage(source, mode, 1F)); - background.DebugSave( - provider, - new { mode = mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - var comparer = ImageComparer.TolerantPercentage(0.01F); - background.CompareToReferenceOutput( - comparer, - provider, - new { mode = mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + background.Mutate(x => x.DrawImage(source, mode, 1F)); + background.DebugSave( + provider, + new { mode = mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01F); + background.CompareToReferenceOutput( + comparer, + provider, + new { mode = mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgr24, TestImages.Png.Bike, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.75f)] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] - - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Multiply, 0.5f)] - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Add, 0.5f)] - [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Subtract, 0.5f)] - - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] - public void WorksWithDifferentConfigurations( - TestImageProvider provider, - string brushImage, - PixelColorBlendingMode mode, - float opacity) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgr24, TestImages.Png.Bike, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.75f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] + + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Multiply, 0.5f)] + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Add, 0.5f)] + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Subtract, 0.5f)] + + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] + public void WorksWithDifferentConfigurations( + TestImageProvider provider, + string brushImage, + PixelColorBlendingMode mode, + float opacity) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) { - using (Image image = provider.GetImage()) - using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) + var size = new Size(image.Width * 3 / 4, image.Height * 3 / 4); + var position = new Point(image.Width / 8, image.Height / 8); + blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic)); + image.Mutate(x => x.DrawImage(blend, position, mode, opacity)); + FormattableString testInfo = $"{System.IO.Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}"; + + var encoder = new PngEncoder(); + + if (provider.PixelType == PixelTypes.Rgba64) { - var size = new Size(image.Width * 3 / 4, image.Height * 3 / 4); - var position = new Point(image.Width / 8, image.Height / 8); - blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic)); - image.Mutate(x => x.DrawImage(blend, position, mode, opacity)); - FormattableString testInfo = $"{System.IO.Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}"; - - var encoder = new PngEncoder(); - - if (provider.PixelType == PixelTypes.Rgba64) - { - encoder.BitDepth = PngBitDepth.Bit16; - } - - image.DebugSave(provider, testInfo, encoder: encoder); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - testInfo); + encoder.BitDepth = PngBitDepth.Bit16; } + + image.DebugSave(provider, testInfo, encoder: encoder); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + testInfo); } + } - [Theory] - [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void DrawImageOfDifferentPixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; + [Theory] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void DrawImageOfDifferentPixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; - using (Image image = provider.GetImage()) - using (Image brushImage = provider.PixelType == PixelTypes.Rgba32 - ? (Image)Image.Load(brushData) - : Image.Load(brushData)) - { - image.Mutate(c => c.DrawImage(brushImage, 0.5f)); + using (Image image = provider.GetImage()) + using (Image brushImage = provider.PixelType == PixelTypes.Rgba32 + ? (Image)Image.Load(brushData) + : Image.Load(brushData)) + { + image.Mutate(c => c.DrawImage(brushImage, 0.5f)); - image.DebugSave(provider, appendSourceFileOrDescription: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - appendSourceFileOrDescription: false); - } + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + appendSourceFileOrDescription: false); } + } - [Theory] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] - [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] - public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) + [Theory] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] + public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(50, 50)) { - using (Image background = provider.GetImage()) - using (var overlay = new Image(50, 50)) - { - Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem)); - overlayMem.Span.Fill(Color.Black); - - background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); - - background.DebugSave( - provider, - testOutputDetails: $"{x}_{y}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - background.CompareToReferenceOutput( - provider, - testOutputDetails: $"{x}_{y}", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem)); + overlayMem.Span.Fill(Color.Black); + + background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); + + background.DebugSave( + provider, + testOutputDetails: $"{x}_{y}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + background.CompareToReferenceOutput( + provider, + testOutputDetails: $"{x}_{y}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } + } - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - public void DrawTransformed(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + public void DrawTransformed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - using (Image image = provider.GetImage()) - using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(45F) - .AppendScale(new SizeF(.25F, .25F)) - .AppendTranslation(new PointF(10, 10)); - - // Apply a background color so we can see the translation. - blend.Mutate(x => x.Transform(builder)); - blend.Mutate(x => x.BackgroundColor(Color.HotPink)); - - // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor - var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); - image.Mutate(x => x.DrawImage(blend, position, .75F)); - - image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.002f), - provider, - appendSourceFileOrDescription: false, - appendPixelTypeToFileName: false); - } + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(45F) + .AppendScale(new SizeF(.25F, .25F)) + .AppendTranslation(new PointF(10, 10)); + + // Apply a background color so we can see the translation. + blend.Mutate(x => x.Transform(builder)); + blend.Mutate(x => x.BackgroundColor(Color.HotPink)); + + // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor + var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); + image.Mutate(x => x.DrawImage(blend, position, .75F)); + + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.002f), + provider, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); } + } - [Theory] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)] - public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) + [Theory] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, -30)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, -30)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, 130, 130)] + [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32, -30, 130)] + public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) + { + using (Image background = provider.GetImage()) + using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black)) { - using (Image background = provider.GetImage()) - using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black)) - { - ImageProcessingException ex = Assert.Throws(Test); + ImageProcessingException ex = Assert.Throws(Test); - Assert.Contains("does not overlap", ex.ToString()); + Assert.Contains("does not overlap", ex.ToString()); - void Test() - { - background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions())); - } + void Test() + { + background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions())); } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 248c7c41cf..4bc59a1e1b 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; @@ -12,40 +10,59 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp +namespace SixLabors.ImageSharp.Tests.Formats.Bmp; + +[Trait("Format", "Bmp")] +[ValidateDisposedMemoryAllocations] +public class BmpDecoderTests { - [Trait("Format", "Bmp")] - [ValidateDisposedMemoryAllocations] - public class BmpDecoderTests - { - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly string[] MiscBmpFiles = Miscellaneous; + public static readonly string[] MiscBmpFiles = Miscellaneous; - public static readonly string[] BitfieldsBmpFiles = BitFields; + public static readonly string[] BitfieldsBmpFiles = BitFields; - private static BmpDecoder BmpDecoder => new(); + private static BmpDecoder BmpDecoder => new(); + + public static readonly TheoryData RatioFiles = + new() + { + { 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 : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - public static readonly TheoryData RatioFiles = - new() + if (TestEnvironment.IsWindows) { - { 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 : unmanaged, IPixel + image.CompareToOriginal(provider); + } + } + + [Theory] + [WithFile(Car, PixelTypes.Rgba32)] + [WithFile(F, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps_WithLimitedAllocatorBufferCapacity( + TestImageProvider provider) + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider, nonContiguousBuffersStr); if (TestEnvironment.IsWindows) { @@ -53,517 +70,495 @@ public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider< } } - [Theory] - [WithFile(Car, PixelTypes.Rgba32)] - [WithFile(F, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps_WithLimitedAllocatorBufferCapacity( - TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose(); + } - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + [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); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); + Assert.IsType(ex.InnerException); + } - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider, nonContiguousBuffersStr); + [Theory] + [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } - } + [Theory] + [WithFile(Bit16Inverted, PixelTypes.Rgba32)] + [WithFile(Bit8Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, providerDump, "Disco").Dispose(); - } + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32)] + [WithFile(Bit1Pal1, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); + } - [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); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); - Assert.IsType(ex.InnerException); - } + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32)] + [WithFile(Bit2Color, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_2Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - [Theory] - [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // Reference decoder cant decode 2-bit, compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } - [Theory] - [WithFile(Bit16Inverted, PixelTypes.Rgba32)] - [WithFile(Bit8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32)] - [WithFile(Bit1Pal1, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); - } + [Theory] + [WithFile(Bit8, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32)] - [WithFile(Bit2Color, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_2Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + [Theory] + [WithFile(Bit16, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - // Reference decoder cant decode 2-bit, compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(Rgba32v4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit8, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(RLE4Cut, PixelTypes.Rgba32)] + [WithFile(RLE4Delta, PixelTypes.Rgba32)] + [WithFile(Rle4Delta320240, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; - [Theory] - [WithFile(Bit16, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(RLE4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; - [Theory] - [WithFile(Rgba32v4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(RLE4Cut, PixelTypes.Rgba32)] - [WithFile(RLE4Delta, PixelTypes.Rgba32)] - [WithFile(Rle4Delta320240, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(RLE8Cut, PixelTypes.Rgba32)] + [WithFile(RLE8Delta, PixelTypes.Rgba32)] + [WithFile(Rle8Delta320240, PixelTypes.Rgba32)] + [WithFile(Rle8Blank160120, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + if (TestEnvironment.IsWindows) { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; - - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); } + } - [Theory] - [WithFile(RLE4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; - BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; - - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(RLE8Cut, PixelTypes.Rgba32)] + [WithFile(RLE8Delta, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } - [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] - [WithFile(RLE8Delta, PixelTypes.Rgba32)] - [WithFile(Rle8Delta320240, PixelTypes.Rgba32)] - [WithFile(Rle8Blank160120, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [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) { - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); - } + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } - [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] - [WithFile(RLE8Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } - [Theory] - [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 + [Theory] + [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) { - if (enforceDiscontiguousBuffers) - { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); - } - - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } - [Theory] - [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); - } + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; + using Image image = provider.GetImage(BmpDecoder, options); + image.DebugSave(provider); - BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }; - using Image image = provider.GetImage(BmpDecoder, options); - image.DebugSave(provider); + // Neither System.Drawing nor MagickReferenceDecoder decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } - // Neither System.Drawing nor MagickReferenceDecoder decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + [Theory] + [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - [Theory] - [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + // Neither System.Drawing nor MagickReferenceDecoder decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } - // Neither System.Drawing nor MagickReferenceDecoder decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + [Theory] + [WithFile(Bit32Rgba, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } - [Theory] - [WithFile(Bit32Rgba, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + [Theory] + [WithFile(Rgba321010102, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + + // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel + // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, + // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. + // The total difference without the alpha channel is still: 0.0204% + // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation. + image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); + } - [Theory] - [WithFile(Rgba321010102, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - - // Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel - // seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3, - // which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set. - // The total difference without the alpha channel is still: 0.0204% - // Exporting the image as PNG with GIMP yields to the same result as the ImageSharp implementation. - image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder()); - } + [Theory] + [WithFile(WinBmpv2, PixelTypes.Rgba32)] + [WithFile(CoreHeader, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - [Theory] - [WithFile(WinBmpv2, PixelTypes.Rgba32)] - [WithFile(CoreHeader, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + // Do not validate. Reference files will fail validation. + image.CompareToOriginal(provider, new MagickReferenceDecoder(false)); + } - // Do not validate. Reference files will fail validation. - image.CompareToOriginal(provider, new MagickReferenceDecoder(false)); - } + [Theory] + [WithFile(WinBmpv3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(WinBmpv3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + + [Theory] + [WithFile(OversizedPalette, PixelTypes.Rgba32)] + [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + if (TestEnvironment.IsWindows) { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); image.CompareToOriginal(provider); } + } - [Theory] - [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => Assert.Throws(() => { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + using (provider.GetImage(BmpDecoder)) + { + } + }); - [Theory] - [WithFile(OversizedPalette, PixelTypes.Rgba32)] - [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] + [WithFile(Rgb24png, PixelTypes.Rgba32)] + public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => Assert.Throws(() => { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - if (TestEnvironment.IsWindows) + using (provider.GetImage(BmpDecoder)) { - image.CompareToOriginal(provider); } - } + }); - [Theory] - [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsInvalidImageContentException_OnInvalidPaletteSize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => - { - using (provider.GetImage(BmpDecoder)) - { - } - }); - - [Theory] - [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] - [WithFile(Rgb24png, PixelTypes.Rgba32)] - public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => - { - using (provider.GetImage(BmpDecoder)) - { - } - }); - - [Theory] - [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + [Theory] + [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } - [Theory] - [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, new MagickReferenceDecoder()); - } + [Theory] + [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } - [Theory] - [WithFile(WinBmpv4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(WinBmpv4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(WinBmpv5, PixelTypes.Rgba32)] - [WithFile(V5Header, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(WinBmpv5, PixelTypes.Rgba32)] + [WithFile(V5Header, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Pal8Offset, PixelTypes.Rgba32)] - public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(Pal8Offset, PixelTypes.Rgba32)] + public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(F, CommonNonDefaultPixelTypes)] - public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(F, CommonNonDefaultPixelTypes)] + public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [WithFile(Bit8Palette4, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + [Theory] + [WithFile(Bit8Palette4, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - [Theory] - [InlineData(Bit32Rgb, 32)] - [InlineData(Bit32Rgba, 32)] - [InlineData(Car, 24)] - [InlineData(F, 24)] - [InlineData(NegHeight, 24)] - [InlineData(Bit16, 16)] - [InlineData(Bit16Inverted, 16)] - [InlineData(Bit8, 8)] - [InlineData(Bit8Inverted, 8)] - [InlineData(Bit4, 4)] - [InlineData(Bit1, 1)] - [InlineData(Bit1Pal1, 1)] - public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); - } + [Theory] + [InlineData(Bit32Rgb, 32)] + [InlineData(Bit32Rgba, 32)] + [InlineData(Car, 24)] + [InlineData(F, 24)] + [InlineData(NegHeight, 24)] + [InlineData(Bit16, 16)] + [InlineData(Bit16Inverted, 16)] + [InlineData(Bit8, 8)] + [InlineData(Bit8Inverted, 8)] + [InlineData(Bit4, 4)] + [InlineData(Bit1, 1)] + [InlineData(Bit1Pal1, 1)] + public void Identify_DetectsCorrectPixelType(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedPixelSize, imageInfo.PixelType?.BitsPerPixel); + } - [Theory] - [InlineData(Bit32Rgb, 127, 64)] - [InlineData(Car, 600, 450)] - [InlineData(Bit16, 127, 64)] - [InlineData(Bit16Inverted, 127, 64)] - [InlineData(Bit8, 127, 64)] - [InlineData(Bit8Inverted, 127, 64)] - [InlineData(RLE8, 491, 272)] - [InlineData(RLE8Inverted, 491, 272)] - public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - } + [Theory] + [InlineData(Bit32Rgb, 127, 64)] + [InlineData(Car, 600, 450)] + [InlineData(Bit16, 127, 64)] + [InlineData(Bit16Inverted, 127, 64)] + [InlineData(Bit8, 127, 64)] + [InlineData(Bit8Inverted, 127, 64)] + [InlineData(RLE8, 491, 272)] + [InlineData(RLE8Inverted, 491, 272)] + public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new BmpDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new BmpDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - [Theory] - [WithFile(Os2v2Short, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + [Theory] + [WithFile(Os2v2Short, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } - [Theory] - [WithFile(Os2v2, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + [Theory] + [WithFile(Os2v2, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // System.Drawing can not decode this image. MagickReferenceDecoder can decode it, - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + // System.Drawing can not decode this image. MagickReferenceDecoder can decode it, + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); + } - [Theory] - [WithFile(Os2BitmapArray, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArray9s, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayDiamond, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySkater, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySpade, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArraySunflower, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayMarble, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayWarpd, PixelTypes.Rgba32)] - [WithFile(Os2BitmapArrayPines, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(BmpDecoder); - image.DebugSave(provider); + [Theory] + [WithFile(Os2BitmapArray, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArray9s, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArrayDiamond, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArraySkater, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArraySpade, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArraySunflower, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArrayMarble, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArrayWarpd, PixelTypes.Rgba32)] + [WithFile(Os2BitmapArrayPines, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider); - // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. - // Compare to reference output instead. - image.CompareToReferenceOutput(provider, extension: "png"); - } + // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file. + // Compare to reference output instead. + image.CompareToReferenceOutput(provider, extension: "png"); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 3bef0dbcac..8743e77bd3 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Metadata; @@ -11,397 +9,394 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp +namespace SixLabors.ImageSharp.Tests.Formats.Bmp; + +[Trait("Format", "Bmp")] +public class BmpEncoderTests { - [Trait("Format", "Bmp")] - public class BmpEncoderTests - { - private static BmpDecoder BmpDecoder => new(); + private static BmpDecoder BmpDecoder => new(); - private static BmpEncoder BmpEncoder => new(); + private static BmpEncoder BmpEncoder => new(); - public static readonly TheoryData BitsPerPixel = - new() - { - BmpBitsPerPixel.Pixel24, - BmpBitsPerPixel.Pixel32 - }; + public static readonly TheoryData BitsPerPixel = + new() + { + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 + }; - public static readonly TheoryData RatioFiles = - new() - { - { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, - { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } - }; + public static readonly TheoryData RatioFiles = + new() + { + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } + }; - public static readonly TheoryData BmpBitsPerPixelFiles = - new() - { - { Bit1, BmpBitsPerPixel.Pixel1 }, - { Bit2, BmpBitsPerPixel.Pixel2 }, - { Bit4, BmpBitsPerPixel.Pixel4 }, - { Bit8, BmpBitsPerPixel.Pixel8 }, - { Rgb16, BmpBitsPerPixel.Pixel16 }, - { Car, BmpBitsPerPixel.Pixel24 }, - { Bit32Rgb, BmpBitsPerPixel.Pixel32 } - }; + public static readonly TheoryData BmpBitsPerPixelFiles = + new() + { + { Bit1, BmpBitsPerPixel.Pixel1 }, + { Bit2, BmpBitsPerPixel.Pixel2 }, + { Bit4, BmpBitsPerPixel.Pixel4 }, + { Bit8, BmpBitsPerPixel.Pixel8 }, + { Rgb16, BmpBitsPerPixel.Pixel16 }, + { Car, BmpBitsPerPixel.Pixel24 }, + { Bit32Rgb, BmpBitsPerPixel.Pixel32 } + }; + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, BmpEncoder); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - input.Save(memStream, BmpEncoder); - - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + [Theory] + [MemberData(nameof(BmpBitsPerPixelFiles))] + public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) + { + var testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, BmpEncoder); - [Theory] - [MemberData(nameof(BmpBitsPerPixelFiles))] - public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) - { - var testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - input.Save(memStream, BmpEncoder); + memStream.Position = 0; + using var output = Image.Load(memStream); + BmpMetadata meta = output.Metadata.GetBmpMetadata(); - memStream.Position = 0; - using var output = Image.Load(memStream); - BmpMetadata meta = output.Metadata.GetBmpMetadata(); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); + } - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] + [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [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 : 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 : 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 : 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 : 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 : 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 : 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 : unmanaged, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: false); + + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] + public void Encode_4Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // Oddly the difference only happens locally but we'll not test for that. + // I suspect the issue is with the reference codec. + ImageComparer comparer = TestEnvironment.IsFramework + ? ImageComparer.TolerantPercentage(0.0161F) + : ImageComparer.Exact; - [Theory] - [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] - public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - - [Theory] - [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] - [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] - [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] - [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 : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [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 : 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 : 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 : 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 : 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 : 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 : 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 : unmanaged, IPixel => - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: false); - - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] - public void Encode_4Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // Oddly the difference only happens locally but we'll not test for that. - // I suspect the issue is with the reference codec. - ImageComparer comparer = TestEnvironment.IsFramework - ? ImageComparer.TolerantPercentage(0.0161F) - : ImageComparer.Exact; + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, customComparer: comparer); + } - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, customComparer: comparer); - } + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] + public void Encode_4Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // Oddly the difference only happens locally but we'll not test for that. + // I suspect the issue is with the reference codec. + ImageComparer comparer = TestEnvironment.IsFramework + ? ImageComparer.TolerantPercentage(0.0161F) + : ImageComparer.Exact; - [Theory] - [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] - public void Encode_4Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - // Oddly the difference only happens locally but we'll not test for that. - // I suspect the issue is with the reference codec. - ImageComparer comparer = TestEnvironment.IsFramework - ? ImageComparer.TolerantPercentage(0.0161F) - : ImageComparer.Exact; + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); + } - TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer); - } + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] + public void Encode_2Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; + using var memoryStream = new MemoryStream(); + using Image input = provider.GetImage(BmpDecoder); + + // act + encoder.Encode(input, memoryStream); + memoryStream.Position = 0; + + // assert + using var actual = Image.Load(memoryStream); + ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); + Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); + } + + [Theory] + [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] + public void Encode_2Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; + using var memoryStream = new MemoryStream(); + using Image input = provider.GetImage(BmpDecoder); + + // act + encoder.Encode(input, memoryStream); + memoryStream.Position = 0; + + // assert + using var actual = Image.Load(memoryStream); + ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); + Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); + } - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] - public void Encode_2Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] + public void Encode_1Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] + public void Encode_1Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] + public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: true); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) { - // arrange - var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; - using var memoryStream = new MemoryStream(); - using Image input = provider.GetImage(BmpDecoder); - - // act - encoder.Encode(input, memoryStream); - memoryStream.Position = 0; - - // assert - using var actual = Image.Load(memoryStream); - ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); - Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); + return; } - [Theory] - [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)] - public void Encode_2Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel + using Image image = provider.GetImage(); + var encoder = new BmpEncoder { - // arrange - var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel }; - using var memoryStream = new MemoryStream(); - using Image input = provider.GetImage(BmpDecoder); - - // act - encoder.Encode(input, memoryStream); - memoryStream.Position = 0; - - // assert - using var actual = Image.Load(memoryStream); - ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual); - Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image"); - } + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new WuQuantizer() + }; + + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + + // Use the default decoder to test our encoded image. This verifies the content. + // We do not verify the reference image though as some are invalid. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using FileStream stream = File.OpenRead(actualOutputFile); + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + referenceImage.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + extension: "bmp", + appendPixelTypeToFileName: false, + decoder: new MagickReferenceDecoder(false)); + } - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] - public void Encode_1Bit_WithV3Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); - - [Theory] - [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] - public void Encode_1Bit_WithV4Header_Works( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - - [Theory] - [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] - public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: true); - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - using Image image = provider.GetImage(); - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer() - }; - - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using FileStream stream = File.OpenRead(actualOutputFile); - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(false)); + return; } - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using Image image = provider.GetImage(); + var encoder = new BmpEncoder { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - using Image image = provider.GetImage(); - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer() - }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - - // Use the default decoder to test our encoded image. This verifies the content. - // We do not verify the reference image though as some are invalid. - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using FileStream stream = File.OpenRead(actualOutputFile); - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); - referenceImage.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.01f), - provider, - extension: "bmp", - appendPixelTypeToFileName: false, - decoder: new MagickReferenceDecoder(false)); - } + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer() + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + + // Use the default decoder to test our encoded image. This verifies the content. + // We do not verify the reference image though as some are invalid. + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using FileStream stream = File.OpenRead(actualOutputFile); + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + referenceImage.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + extension: "bmp", + appendPixelTypeToFileName: false, + decoder: new MagickReferenceDecoder(false)); + } - [Theory] - [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] - [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] - public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] - public void Encode_PreservesColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(new BmpDecoder(), new()); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + [Theory] + [WithFile(IccProfile, PixelTypes.Rgba32)] + public void Encode_PreservesColorProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image input = provider.GetImage(new BmpDecoder(), new()); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - using var memStream = new MemoryStream(); - input.Save(memStream, new BmpEncoder()); + using var memStream = new MemoryStream(); + input.Save(memStream, new BmpEncoder()); - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); + } - [Theory] - [InlineData(1, 66535)] - [InlineData(66535, 1)] - public void Encode_WorksWithSizeGreaterThen65k(int width, int height) + [Theory] + [InlineData(1, 66535)] + [InlineData(66535, 1)] + public void Encode_WorksWithSizeGreaterThen65k(int width, int height) + { + Exception exception = Record.Exception(() => { - Exception exception = Record.Exception(() => - { - using Image image = new Image(width, height); - using var memStream = new MemoryStream(); - image.Save(memStream, BmpEncoder); - }); - - Assert.Null(exception); - } + using Image image = new Image(width, height); + using var memStream = new MemoryStream(); + image.Save(memStream, BmpEncoder); + }); + + Assert.Null(exception); + } - [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 + [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, // if set to true, will write a V4 header, otherwise a V3 header. + IQuantizer quantizer = null, + ImageComparer customComparer = null, + IImageDecoder referenceDecoder = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel != BmpBitsPerPixel.Pixel32) { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); - TestBmpEncoderCore(provider, bitsPerPixel); + image.Mutate(c => c.MakeOpaque()); } - private static void TestBmpEncoderCore( - TestImageProvider provider, - BmpBitsPerPixel bitsPerPixel, - bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. - IQuantizer quantizer = null, - ImageComparer customComparer = null, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel + var encoder = new BmpEncoder { - using Image image = provider.GetImage(); - - // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. - if (bitsPerPixel != BmpBitsPerPixel.Pixel32) - { - image.Mutate(c => c.MakeOpaque()); - } - - var encoder = new BmpEncoder - { - BitsPerPixel = bitsPerPixel, - SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree - }; - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); - } + BitsPerPixel = bitsPerPixel, + SupportTransparency = supportTransparency, + Quantizer = quantizer ?? KnownQuantizers.Octree + }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs index 115dde3c12..d8ec7c9009 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs @@ -1,25 +1,22 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Bmp; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Bmp +namespace SixLabors.ImageSharp.Tests.Formats.Bmp; + +[Trait("Format", "Bmp")] +public class BmpFileHeaderTests { - [Trait("Format", "Bmp")] - public class BmpFileHeaderTests + [Fact] + public void TestWrite() { - [Fact] - public void TestWrite() - { - var header = new BmpFileHeader(1, 2, 3, 4); + var header = new BmpFileHeader(1, 2, 3, 4); - var buffer = new byte[14]; + var buffer = new byte[14]; - header.WriteTo(buffer); + header.WriteTo(buffer); - Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); - } + Assert.Equal("AQACAAAAAwAAAAQAAAA=", Convert.ToBase64String(buffer)); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index cc1395a6c3..adfd926b78 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -1,65 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Bmp +namespace SixLabors.ImageSharp.Tests.Formats.Bmp; + +[Trait("Format", "Bmp")] +public class BmpMetadataTests { - [Trait("Format", "Bmp")] - public class BmpMetadataTests + [Fact] + public void CloneIsDeep() { - [Fact] - public void CloneIsDeep() - { - var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 }; - var clone = (BmpMetadata)meta.DeepClone(); + var meta = new BmpMetadata { BitsPerPixel = BmpBitsPerPixel.Pixel24, InfoHeaderType = BmpInfoHeaderType.Os2Version2 }; + var clone = (BmpMetadata)meta.DeepClone(); - clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; - clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2; + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + clone.InfoHeaderType = BmpInfoHeaderType.WinVersion2; - Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); - Assert.False(meta.InfoHeaderType.Equals(clone.InfoHeaderType)); - } + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + Assert.False(meta.InfoHeaderType.Equals(clone.InfoHeaderType)); + } - [Theory] - [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] - [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] - [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] - [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] - [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] - [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] - [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] - [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] - public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) + [Theory] + [InlineData(WinBmpv2, BmpInfoHeaderType.WinVersion2)] + [InlineData(WinBmpv3, BmpInfoHeaderType.WinVersion3)] + [InlineData(WinBmpv4, BmpInfoHeaderType.WinVersion4)] + [InlineData(WinBmpv5, BmpInfoHeaderType.WinVersion5)] + [InlineData(Os2v2Short, BmpInfoHeaderType.Os2Version2Short)] + [InlineData(Rgb32h52AdobeV3, BmpInfoHeaderType.AdobeVersion3)] + [InlineData(Rgba32bf56AdobeV3, BmpInfoHeaderType.AdobeVersion3WithAlpha)] + [InlineData(Os2v2, BmpInfoHeaderType.Os2Version2)] + public void Identify_DetectsCorrectBitmapInfoHeaderType(string imagePath, BmpInfoHeaderType expectedInfoHeaderType) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); - } + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + BmpMetadata bitmapMetadata = imageInfo.Metadata.GetBmpMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedInfoHeaderType, bitmapMetadata.InfoHeaderType); } + } - [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] - public void Decoder_CanReadColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(IccProfile, PixelTypes.Rgba32)] + public void Decoder_CanReadColorProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) { - using (Image image = provider.GetImage(new BmpDecoder())) - { - ImageSharp.Metadata.ImageMetadata metaData = image.Metadata; - Assert.NotNull(metaData); - Assert.NotNull(metaData.IccProfile); - Assert.Equal(16, metaData.IccProfile.Entries.Length); - } + ImageSharp.Metadata.ImageMetadata metaData = image.Metadata; + Assert.NotNull(metaData); + Assert.NotNull(metaData.IccProfile); + Assert.Equal(16, metaData.IccProfile.Entries.Length); } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs index ca1add6bbb..fbf9b20a9c 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/ImageExtensionsTest.cs @@ -1,155 +1,151 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Bmp +namespace SixLabors.ImageSharp.Tests.Formats.Bmp; + +public class ImageExtensionsTest { - public class ImageExtensionsTest + [Fact] + public void SaveAsBmp_Path() { - [Fact] - public void SaveAsBmp_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsBmp_Path.bmp"); - - using (var image = new Image(10, 10)) - { - image.SaveAsBmp(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsBmp_Path.bmp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(file); } - [Fact] - public async Task SaveAsBmpAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp"); + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsBmpAsync(file); - } + [Fact] + public async Task SaveAsBmpAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsBmpAsync_Path.bmp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(file); } - [Fact] - public void SaveAsBmp_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp"); + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsBmp(file, new BmpEncoder()); - } + [Fact] + public void SaveAsBmp_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsBmp_Path_Encoder.bmp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(file, new BmpEncoder()); } - [Fact] - public async Task SaveAsBmpAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp"); + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsBmpAsync(file, new BmpEncoder()); - } + [Fact] + public async Task SaveAsBmpAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsBmpAsync_Path_Encoder.bmp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(file, new BmpEncoder()); } - [Fact] - public void SaveAsBmp_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsBmp(memoryStream); - } + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsBmp_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(memoryStream); } - [Fact] - public async Task SaveAsBmpAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsBmpAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsBmpAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(memoryStream); } - [Fact] - public void SaveAsBmp_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsBmp(memoryStream, new BmpEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsBmp_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsBmp(memoryStream, new BmpEncoder()); } - [Fact] - public async Task SaveAsBmpAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/bmp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsBmpAsync(memoryStream, new BmpEncoder()); - } + [Fact] + public async Task SaveAsBmpAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsBmpAsync(memoryStream, new BmpEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/bmp", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/bmp", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index d19f4862be..fa5e4bdeff 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,239 +1,233 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats +namespace SixLabors.ImageSharp.Tests.Formats; + +public class GeneralFormatTests { - public class GeneralFormatTests - { - /// - /// A collection made up of one file for each image format. - /// - public static readonly IEnumerable DefaultFiles = - new[] - { - TestImages.Bmp.Car, - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Png.Splash, - TestImages.Gif.Trans - }; - - /// - /// The collection of image files to test against. - /// - protected static readonly List Files = new() + /// + /// A collection made up of one file for each image format. + /// + public static readonly IEnumerable DefaultFiles = + new[] { - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - TestFile.Create(TestImages.Bmp.Car), - TestFile.Create(TestImages.Png.Splash), - TestFile.Create(TestImages.Gif.Rings), + TestImages.Bmp.Car, + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Png.Splash, + TestImages.Gif.Trans }; - [Theory] - [WithFileCollection(nameof(DefaultFiles), PixelTypes.Rgba32)] - public void ResolutionShouldChange(TestImageProvider provider) - where TPixel : unmanaged, IPixel + /// + /// The collection of image files to test against. + /// + protected static readonly List Files = new() + { + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + TestFile.Create(TestImages.Bmp.Car), + TestFile.Create(TestImages.Png.Splash), + TestFile.Create(TestImages.Gif.Rings), + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), PixelTypes.Rgba32)] + public void ResolutionShouldChange(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.Metadata.VerticalResolution = 150; + image.Metadata.HorizontalResolution = 150; + image.DebugSave(provider); + } + + [Fact] + public void ImageCanEncodeToString() + { + string path = TestEnvironment.CreateOutputDirectory("ToString"); + + foreach (TestFile file in Files) { - using Image image = provider.GetImage(); - image.Metadata.VerticalResolution = 150; - image.Metadata.HorizontalResolution = 150; - image.DebugSave(provider); + using Image image = file.CreateRgba32Image(); + string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); + File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); } + } - [Fact] - public void ImageCanEncodeToString() - { - string path = TestEnvironment.CreateOutputDirectory("ToString"); + [Fact] + public void DecodeThenEncodeImageFromStreamShouldSucceed() + { + string path = TestEnvironment.CreateOutputDirectory("Encode"); - foreach (TestFile file in Files) - { - using Image image = file.CreateRgba32Image(); - string filename = Path.Combine(path, $"{file.FileNameWithoutExtension}.txt"); - File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); - } + foreach (TestFile file in Files) + { + using Image image = file.CreateRgba32Image(); + image.Save(Path.Combine(path, file.FileName)); } + } - [Fact] - public void DecodeThenEncodeImageFromStreamShouldSucceed() + public static readonly TheoryData QuantizerNames = + new() { - string path = TestEnvironment.CreateOutputDirectory("Encode"); + nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.WebSafe), + nameof(KnownQuantizers.Werner), + nameof(KnownQuantizers.Wu) + }; + + [Theory] + [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 : unmanaged, IPixel + { + IQuantizer quantizer = GetQuantizer(quantizerName); + + using (Image image = provider.GetImage()) + { + image.DebugSave(provider, new PngEncoder { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); + } + + provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); + } + + private static IQuantizer GetQuantizer(string name) + { + PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); + return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); + } + + [Fact] + public void ImageCanConvertFormat() + { + string path = TestEnvironment.CreateOutputDirectory("Format"); - foreach (TestFile file in Files) + foreach (TestFile file in Files) + { + using Image image = file.CreateRgba32Image(); + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) { - using Image image = file.CreateRgba32Image(); - image.Save(Path.Combine(path, file.FileName)); + image.SaveAsBmp(output); } - } - public static readonly TheoryData QuantizerNames = - new() + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) { - nameof(KnownQuantizers.Octree), - nameof(KnownQuantizers.WebSafe), - nameof(KnownQuantizers.Werner), - nameof(KnownQuantizers.Wu) - }; - - [Theory] - [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 : unmanaged, IPixel - { - IQuantizer quantizer = GetQuantizer(quantizerName); + image.SaveAsJpeg(output); + } - using (Image image = provider.GetImage()) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) { - image.DebugSave(provider, new PngEncoder { ColorType = PngColorType.Palette, Quantizer = quantizer }, testOutputDetails: quantizerName); + image.SaveAsPbm(output); } - provider.Configuration.MemoryAllocator.ReleaseRetainedResources(); - } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) + { + image.SaveAsPng(output); + } - private static IQuantizer GetQuantizer(string name) - { - PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); - } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) + { + image.SaveAsGif(output); + } - [Fact] - public void ImageCanConvertFormat() - { - string path = TestEnvironment.CreateOutputDirectory("Format"); + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) + { + image.SaveAsTga(output); + } - foreach (TestFile file in Files) + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) { - using Image image = file.CreateRgba32Image(); - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.bmp"))) - { - image.SaveAsBmp(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.jpg"))) - { - image.SaveAsJpeg(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) - { - image.SaveAsPbm(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) - { - image.SaveAsPng(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.gif"))) - { - image.SaveAsGif(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tga"))) - { - image.SaveAsTga(output); - } - - using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.tiff"))) - { - image.SaveAsTiff(output); - } + image.SaveAsTiff(output); } } + } - [Fact] - public void ImageShouldPreservePixelByteOrderWhenSerialized() - { - string path = TestEnvironment.CreateOutputDirectory("Serialized"); + [Fact] + public void ImageShouldPreservePixelByteOrderWhenSerialized() + { + string path = TestEnvironment.CreateOutputDirectory("Serialized"); - foreach (TestFile file in Files) + foreach (TestFile file in Files) + { + byte[] serialized; + using (var image = Image.Load(file.Bytes, out IImageFormat mimeType)) + using (var memoryStream = new MemoryStream()) { - byte[] serialized; - using (var image = Image.Load(file.Bytes, out IImageFormat mimeType)) - using (var memoryStream = new MemoryStream()) - { - image.Save(memoryStream, mimeType); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } - - using var image2 = Image.Load(serialized); - image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); + image.Save(memoryStream, mimeType); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); } - } - [Theory] - [InlineData(10, 10, "pbm")] - [InlineData(100, 100, "pbm")] - [InlineData(100, 10, "pbm")] - [InlineData(10, 100, "pbm")] - [InlineData(10, 10, "png")] - [InlineData(100, 100, "png")] - [InlineData(100, 10, "png")] - [InlineData(10, 100, "png")] - [InlineData(10, 10, "gif")] - [InlineData(100, 100, "gif")] - [InlineData(100, 10, "gif")] - [InlineData(10, 100, "gif")] - [InlineData(10, 10, "bmp")] - [InlineData(100, 100, "bmp")] - [InlineData(100, 10, "bmp")] - [InlineData(10, 100, "bmp")] - [InlineData(10, 10, "jpg")] - [InlineData(100, 100, "jpg")] - [InlineData(100, 10, "jpg")] - [InlineData(10, 100, "jpg")] - [InlineData(100, 100, "tga")] - [InlineData(100, 10, "tga")] - [InlineData(10, 100, "tga")] - [InlineData(100, 100, "tiff")] - [InlineData(100, 10, "tiff")] - [InlineData(10, 100, "tiff")] - - public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) - { - using var image = Image.LoadPixelData(new Rgba32[width * height], width, height); - using var memoryStream = new MemoryStream(); - IImageFormat format = GetFormat(extension); - image.Save(memoryStream, format); - memoryStream.Position = 0; + using var image2 = Image.Load(serialized); + image2.Save($"{path}{Path.DirectorySeparatorChar}{file.FileName}"); + } + } - IImageInfo imageInfo = Image.Identify(memoryStream); + [Theory] + [InlineData(10, 10, "pbm")] + [InlineData(100, 100, "pbm")] + [InlineData(100, 10, "pbm")] + [InlineData(10, 100, "pbm")] + [InlineData(10, 10, "png")] + [InlineData(100, 100, "png")] + [InlineData(100, 10, "png")] + [InlineData(10, 100, "png")] + [InlineData(10, 10, "gif")] + [InlineData(100, 100, "gif")] + [InlineData(100, 10, "gif")] + [InlineData(10, 100, "gif")] + [InlineData(10, 10, "bmp")] + [InlineData(100, 100, "bmp")] + [InlineData(100, 10, "bmp")] + [InlineData(10, 100, "bmp")] + [InlineData(10, 10, "jpg")] + [InlineData(100, 100, "jpg")] + [InlineData(100, 10, "jpg")] + [InlineData(10, 100, "jpg")] + [InlineData(100, 100, "tga")] + [InlineData(100, 10, "tga")] + [InlineData(10, 100, "tga")] + [InlineData(100, 100, "tiff")] + [InlineData(100, 10, "tiff")] + [InlineData(10, 100, "tiff")] + + public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) + { + using var image = Image.LoadPixelData(new Rgba32[width * height], width, height); + using var memoryStream = new MemoryStream(); + IImageFormat format = GetFormat(extension); + image.Save(memoryStream, format); + memoryStream.Position = 0; - Assert.Equal(imageInfo.Width, width); - Assert.Equal(imageInfo.Height, height); - memoryStream.Position = 0; + IImageInfo imageInfo = Image.Identify(memoryStream); - imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); + Assert.Equal(imageInfo.Width, width); + Assert.Equal(imageInfo.Height, height); + memoryStream.Position = 0; - Assert.Equal(format, detectedFormat); - } + imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); - [Fact] - public void IdentifyReturnsNullWithInvalidStream() - { - byte[] invalid = new byte[10]; + Assert.Equal(format, detectedFormat); + } - using var memoryStream = new MemoryStream(invalid); - IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); + [Fact] + public void IdentifyReturnsNullWithInvalidStream() + { + byte[] invalid = new byte[10]; - Assert.Null(imageInfo); - Assert.Null(format); - } + using var memoryStream = new MemoryStream(invalid); + IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); - private static IImageFormat GetFormat(string format) - => Configuration.Default.ImageFormats - .FirstOrDefault(x => x.FileExtensions.Contains(format)); + Assert.Null(imageInfo); + Assert.Null(format); } + + private static IImageFormat GetFormat(string format) + => Configuration.Default.ImageFormats + .FirstOrDefault(x => x.FileExtensions.Contains(format)); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 3a8ae6f111..2a9d2b791d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; @@ -11,296 +9,293 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif; + +[Trait("Format", "Gif")] +[ValidateDisposedMemoryAllocations] +public class GifDecoderTests { - [Trait("Format", "Gif")] - [ValidateDisposedMemoryAllocations] - public class GifDecoderTests + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + + private static GifDecoder GifDecoder => new(); + + public static readonly string[] MultiFrameTestFiles = { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + TestImages.Gif.Giphy, TestImages.Gif.Kumin + }; - private static GifDecoder GifDecoder => new(); + [Theory] + [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] + public void Decode_VerifyAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } - public static readonly string[] MultiFrameTestFiles = + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void GifDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - TestImages.Gif.Giphy, TestImages.Gif.Kumin + TargetSize = new() { Width = 150, Height = 150 }, + MaxFrames = 1 }; - [Theory] - [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] - public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(GifDecoder, options); - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void GifDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 }, - MaxFrames = 1 - }; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - using Image image = provider.GetImage(GifDecoder, options); + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.0001F), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + [Fact] + public unsafe void Decode_NonTerminatedFinalFrame() + { + var testFile = TestFile.Create(TestImages.Gif.Rings); - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0001F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + int length = testFile.Bytes.Length - 2; - [Fact] - public unsafe void Decode_NonTerminatedFinalFrame() + fixed (byte* data = testFile.Bytes.AsSpan(0, length)) { - var testFile = TestFile.Create(TestImages.Gif.Rings); - - int length = testFile.Bytes.Length - 2; - - fixed (byte* data = testFile.Bytes.AsSpan(0, length)) - { - using var stream = new UnmanagedMemoryStream(data, length); - using Image image = GifDecoder.Decode(DecoderOptions.Default, stream); - Assert.Equal((200, 200), (image.Width, image.Height)); - } + using var stream = new UnmanagedMemoryStream(data, length); + using Image image = GifDecoder.Decode(DecoderOptions.Default, stream); + Assert.Equal((200, 200), (image.Width, image.Height)); } + } - [Theory] - [WithFile(TestImages.Gif.Trans, TestPixelTypes)] - public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + [Theory] + [WithFile(TestImages.Gif.Trans, TestPixelTypes)] + public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - [Theory] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 93)] - [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] - [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32, 36)] - public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Assert.Equal(expectedFrameCount, image.Frames.Count); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + [Theory] + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 93)] + [WithFile(TestImages.Gif.Rings, PixelTypes.Rgba32, 1)] + [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32, 36)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider, int expectedFrameCount) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Assert.Equal(expectedFrameCount, image.Frames.Count); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(new GifDecoder(), options); - Assert.Equal(1, image.Frames.Count); - } + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void CanDecodeJustOneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new GifDecoder(), options); + Assert.Equal(1, image.Frames.Count); + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void CanDecodeAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(new GifDecoder()); - Assert.True(image.Frames.Count > 1); - } + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void CanDecodeAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(new GifDecoder()); + Assert.True(image.Frames.Count > 1); + } - [Theory] - [InlineData(TestImages.Gif.Cheers, 8)] - [InlineData(TestImages.Gif.Giphy, 8)] - [InlineData(TestImages.Gif.Rings, 8)] - [InlineData(TestImages.Gif.Trans, 8)] - public void DetectPixelSize(string imagePath, int expectedPixelSize) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); - } + [Theory] + [InlineData(TestImages.Gif.Cheers, 8)] + [InlineData(TestImages.Gif.Giphy, 8)] + [InlineData(TestImages.Gif.Rings, 8)] + [InlineData(TestImages.Gif.Trans, 8)] + public void DetectPixelSize(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + } - [Theory] - [WithFile(TestImages.Gif.ZeroSize, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.ZeroWidth, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.ZeroHeight, PixelTypes.Rgba32)] - public void Decode_WithInvalidDimensions_DoesThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(GifDecoder); - }); - Assert.NotNull(ex); - Assert.Contains("Width or height should not be 0", ex.Message); - } + [Theory] + [WithFile(TestImages.Gif.ZeroSize, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.ZeroWidth, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.ZeroHeight, PixelTypes.Rgba32)] + public void Decode_WithInvalidDimensions_DoesThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(GifDecoder); + }); + Assert.NotNull(ex); + Assert.Contains("Width or height should not be 0", ex.Message); + } - [Theory] - [WithFile(TestImages.Gif.MaxWidth, PixelTypes.Rgba32, 65535, 1)] - [WithFile(TestImages.Gif.MaxHeight, PixelTypes.Rgba32, 1, 65535)] - public void Decode_WithMaxDimensions_Works(TestImageProvider provider, int expectedWidth, int expectedHeight) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(GifDecoder); - Assert.Equal(expectedWidth, image.Width); - Assert.Equal(expectedHeight, image.Height); - } + [Theory] + [WithFile(TestImages.Gif.MaxWidth, PixelTypes.Rgba32, 65535, 1)] + [WithFile(TestImages.Gif.MaxHeight, PixelTypes.Rgba32, 1, 65535)] + public void Decode_WithMaxDimensions_Works(TestImageProvider provider, int expectedWidth, int expectedHeight) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(GifDecoder); + Assert.Equal(expectedWidth, image.Width); + Assert.Equal(expectedHeight, image.Height); + } - [Fact] - public void CanDecodeIntermingledImages() + [Fact] + public void CanDecodeIntermingledImages() + { + using (var kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) + using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) + using (var kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) { - using (var kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) - using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) - using (var kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) + for (int i = 0; i < kumin1.Frames.Count; i++) { - for (int i = 0; i < kumin1.Frames.Count; i++) - { - ImageFrame first = kumin1.Frames[i]; - ImageFrame second = kumin2.Frames[i]; + ImageFrame first = kumin1.Frames[i]; + ImageFrame second = kumin2.Frames[i]; - Assert.True(second.DangerousTryGetSinglePixelMemory(out Memory secondMemory)); + Assert.True(second.DangerousTryGetSinglePixelMemory(out Memory secondMemory)); - first.ComparePixelBufferTo(secondMemory.Span); - } + first.ComparePixelBufferTo(secondMemory.Span); } } + } - // https://github.com/SixLabors/ImageSharp/issues/1503 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1530, PixelTypes.Rgba32)] - public void Issue1530_BadDescriptorDimensions(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - } + // https://github.com/SixLabors/ImageSharp/issues/1503 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1530, PixelTypes.Rgba32)] + public void Issue1530_BadDescriptorDimensions(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } - // https://github.com/SixLabors/ImageSharp/issues/405 - [Theory] - [WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)] - [WithFile(TestImages.Gif.Issues.BadAppExtLength_2, PixelTypes.Rgba32)] - public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); + // https://github.com/SixLabors/ImageSharp/issues/405 + [Theory] + [WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Issues.BadAppExtLength_2, PixelTypes.Rgba32)] + public void Issue405_BadApplicationExtensionBlockLength(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - // https://github.com/SixLabors/ImageSharp/issues/1668 - [Theory] - [WithFile(TestImages.Gif.Issues.InvalidColorIndex, PixelTypes.Rgba32)] - public void Issue1668_InvalidColorIndex(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); + // https://github.com/SixLabors/ImageSharp/issues/1668 + [Theory] + [WithFile(TestImages.Gif.Issues.InvalidColorIndex, PixelTypes.Rgba32)] + public void Issue1668_InvalidColorIndex(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - [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); - InvalidImageContentException 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_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException 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) + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity( + TestImageProvider provider) + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider - = BasicSerializer.Deserialize>(providerDump); - - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + TestImageProvider provider + = BasicSerializer.Deserialize>(providerDump); - using Image image = provider.GetImage(GifDecoder); - image.DebugSave(provider, nonContiguousBuffersStr); - image.CompareToOriginal(provider); - } + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); + using Image image = provider.GetImage(GifDecoder); + image.DebugSave(provider, nonContiguousBuffersStr); + image.CompareToOriginal(provider); } - // https://github.com/SixLabors/ImageSharp/issues/1962 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1962NoColorTable, PixelTypes.Rgba32)] - public void Issue1962(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider); + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + // https://github.com/SixLabors/ImageSharp/issues/1962 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1962NoColorTable, PixelTypes.Rgba32)] + public void Issue1962(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider); - // https://github.com/SixLabors/ImageSharp/issues/2012 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2012EmptyXmp, PixelTypes.Rgba32)] - public void Issue2012EmptyXmp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + // https://github.com/SixLabors/ImageSharp/issues/2012 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue2012EmptyXmp, PixelTypes.Rgba32)] + public void Issue2012EmptyXmp(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - // https://github.com/SixLabors/ImageSharp/issues/2012 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue2012BadMinCode, PixelTypes.Rgba32)] - public void Issue2012BadMinCode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(); - image.DebugSave(provider); - }); - - Assert.NotNull(ex); - Assert.Contains("Gif Image does not contain a valid LZW minimum code.", ex.Message); - } + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); + } - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - [Theory] - [WithFile(TestImages.Gif.Issues.DeferredClearCode, PixelTypes.Rgba32)] - public void IssueDeferredClearCode(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + // https://github.com/SixLabors/ImageSharp/issues/2012 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue2012BadMinCode, PixelTypes.Rgba32)] + public void Issue2012BadMinCode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(); + image.DebugSave(provider); + }); - image.DebugSave(provider); - image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); - } + Assert.NotNull(ex); + Assert.Contains("Gif Image does not contain a valid LZW minimum code.", ex.Message); + } + + // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 + [Theory] + [WithFile(TestImages.Gif.Issues.DeferredClearCode, PixelTypes.Rgba32)] + public void IssueDeferredClearCode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(ImageComparer.Exact, provider); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 597798b634..d5365e8f73 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -1,222 +1,218 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif; + +[Trait("Format", "Gif")] +public class GifEncoderTests { - [Trait("Format", "Gif")] - public class GifEncoderTests + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); + + public static readonly TheoryData RatioFiles = + new() { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); + { 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 RatioFiles = - new() + public GifEncoderTests() + { + // Free the pool on 32 bit: + if (!TestEnvironment.Is64BitProcess) { - { 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 } - }; + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); + } + } - public GifEncoderTests() + [Theory] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) + where TPixel : unmanaged, IPixel + { + if (limitAllocationBuffer) { - // Free the pool on 32 bit: - if (!TestEnvironment.Is64BitProcess) - { - Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); - } + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); } - [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes, false)] - [WithTestPatternImages(100, 100, TestPixelTypes, false)] - public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) - where TPixel : unmanaged, IPixel + using (Image image = provider.GetImage()) { - if (limitAllocationBuffer) + var encoder = new GifEncoder { - 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(new QuantizerOptions { Dither = null }) - }; - - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "gif", encoder); - } + // Use the palette quantizer without dithering to ensure results + // are consistent + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) + }; - // Compare encoded result - string path = provider.Utility.GetTestOutputFileName("gif", null, true); - using (var encoded = Image.Load(path)) - { - encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif"); - } + // Always save as we need to compare the encoded output. + provider.Utility.SaveTestOutputFile(image, "gif", encoder); } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + // Compare encoded result + string path = provider.Utility.GetTestOutputFileName("gif", null, true); + using (var encoded = Image.Load(path)) { - var options = new GifEncoder(); + encoded.CompareToReferenceOutput(ValidatorComparer, provider, null, "gif"); + } + } - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var options = new GifEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } } } + } - [Fact] - public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() - { - var options = new GifEncoder(); + [Fact] + public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() + { + var options = new GifEncoder(); - var testFile = TestFile.Create(TestImages.Gif.Rings); + var testFile = TestFile.Create(TestImages.Gif.Rings); - using (Image input = testFile.CreateRgba32Image()) + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - GifMetadata metadata = output.Metadata.GetGifMetadata(); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } + GifMetadata metadata = output.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); } } } + } - [Theory] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] - public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] + public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var encoder = new GifEncoder { - var encoder = new GifEncoder - { - ColorTableMode = GifColorTableMode.Global, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) - }; + ColorTableMode = GifColorTableMode.Global, + Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) + }; - // Always save as we need to compare the encoded output. - provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global"); + // Always save as we need to compare the encoded output. + provider.Utility.SaveTestOutputFile(image, "gif", encoder, "global"); - encoder.ColorTableMode = GifColorTableMode.Local; - provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); + encoder.ColorTableMode = GifColorTableMode.Local; + provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); - var fileInfoGlobal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "global")); - var fileInfoLocal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "local")); + var fileInfoGlobal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "global")); + var fileInfoLocal = new FileInfo(provider.Utility.GetTestOutputFileName("gif", "local")); - Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length); - } + Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length); } + } - [Theory] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 427500, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 200000, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 100000, 0.1)] - [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 50000, 0.1)] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 4000000, 0.01)] - [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 1000000, 0.01)] - public void Encode_GlobalPalette_DefaultPixelSamplingStrategy(TestImageProvider provider, int maxPixels, double scanRatio) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + [Theory] + [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 427500, 0.1)] + [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 200000, 0.1)] + [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 100000, 0.1)] + [WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 50000, 0.1)] + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 4000000, 0.01)] + [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 1000000, 0.01)] + public void Encode_GlobalPalette_DefaultPixelSamplingStrategy(TestImageProvider provider, int maxPixels, double scanRatio) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - var encoder = new GifEncoder() - { - ColorTableMode = GifColorTableMode.Global, - GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio) - }; + var encoder = new GifEncoder() + { + ColorTableMode = GifColorTableMode.Global, + GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio) + }; - string testOutputFile = provider.Utility.SaveTestOutputFile( - image, - "gif", - encoder, - testOutputDetails: $"{maxPixels}_{scanRatio}", - appendPixelTypeToFileName: false); - - // TODO: For proper regression testing of gifs, use a multi-frame reference output, or find a working reference decoder. - // IImageDecoder referenceDecoder = TestEnvironment.Ge - // ReferenceDecoder(testOutputFile); - // using var encoded = Image.Load(testOutputFile, referenceDecoder); - // ValidatorComparer.VerifySimilarity(image, encoded); - } + string testOutputFile = provider.Utility.SaveTestOutputFile( + image, + "gif", + encoder, + testOutputDetails: $"{maxPixels}_{scanRatio}", + appendPixelTypeToFileName: false); + + // TODO: For proper regression testing of gifs, use a multi-frame reference output, or find a working reference decoder. + // IImageDecoder referenceDecoder = TestEnvironment.Ge + // ReferenceDecoder(testOutputFile); + // using var encoded = Image.Load(testOutputFile, referenceDecoder); + // ValidatorComparer.VerifySimilarity(image, encoded); + } - [Fact] - public void NonMutatingEncodePreservesPaletteCount() + [Fact] + public void NonMutatingEncodePreservesPaletteCount() + { + using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes)) + using (var outStream = new MemoryStream()) { - using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes)) - using (var outStream = new MemoryStream()) - { - inStream.Position = 0; - - var image = Image.Load(inStream); - GifMetadata metaData = image.Metadata.GetGifMetadata(); - GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata(); - GifColorTableMode colorMode = metaData.ColorTableMode; - var encoder = new GifEncoder - { - ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) - }; + inStream.Position = 0; - image.Save(outStream, encoder); - outStream.Position = 0; + var image = Image.Load(inStream); + GifMetadata metaData = image.Metadata.GetGifMetadata(); + GifFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetGifMetadata(); + GifColorTableMode colorMode = metaData.ColorTableMode; + var encoder = new GifEncoder + { + ColorTableMode = colorMode, + Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) + }; - outStream.Position = 0; - var clone = Image.Load(outStream); + image.Save(outStream, encoder); + outStream.Position = 0; - GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata(); - Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode); + outStream.Position = 0; + var clone = Image.Load(outStream); - // Gifiddle and Cyotek GifInfo say this image has 64 colors. - Assert.Equal(64, frameMetadata.ColorTableLength); + GifMetadata cloneMetadata = clone.Metadata.GetGifMetadata(); + Assert.Equal(metaData.ColorTableMode, cloneMetadata.ColorTableMode); - for (int i = 0; i < image.Frames.Count; i++) - { - GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata(); - GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata(); + // Gifiddle and Cyotek GifInfo say this image has 64 colors. + Assert.Equal(64, frameMetadata.ColorTableLength); - Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength); - Assert.Equal(ifm.FrameDelay, cifm.FrameDelay); - } + for (int i = 0; i < image.Frames.Count; i++) + { + GifFrameMetadata ifm = image.Frames[i].Metadata.GetGifMetadata(); + GifFrameMetadata cifm = clone.Frames[i].Metadata.GetGifMetadata(); - image.Dispose(); - clone.Dispose(); + Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength); + Assert.Equal(ifm.FrameDelay, cifm.FrameDelay); } + + image.Dispose(); + clone.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs index 300ca3d9cd..9a8b41d541 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetadataTests.cs @@ -2,32 +2,30 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Gif; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif; + +[Trait("Format", "Gif")] +public class GifFrameMetadataTests { - [Trait("Format", "Gif")] - public class GifFrameMetadataTests + [Fact] + public void CloneIsDeep() { - [Fact] - public void CloneIsDeep() + var meta = new GifFrameMetadata { - var meta = new GifFrameMetadata - { - FrameDelay = 1, - DisposalMethod = GifDisposalMethod.RestoreToBackground, - ColorTableLength = 2 - }; + FrameDelay = 1, + DisposalMethod = GifDisposalMethod.RestoreToBackground, + ColorTableLength = 2 + }; - var clone = (GifFrameMetadata)meta.DeepClone(); + var clone = (GifFrameMetadata)meta.DeepClone(); - clone.FrameDelay = 2; - clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; - clone.ColorTableLength = 1; + clone.FrameDelay = 2; + clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; + clone.ColorTableLength = 1; - Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); - Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); - Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); - } + Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); + Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); + Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index d2c667a6b8..2d66842e88 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -1,196 +1,189 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Gif; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +[Trait("Format", "Gif")] +public class GifMetadataTests { - [Trait("Format", "Gif")] - public class GifMetadataTests - { - public static readonly TheoryData RatioFiles = - new() - { - { 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() - { - { TestImages.Gif.Cheers, 0 }, - { TestImages.Gif.Receipt, 1 }, - { TestImages.Gif.Rings, 1 } - }; - - [Fact] - public void CloneIsDeep() - { - var meta = new GifMetadata - { - RepeatCount = 1, - ColorTableMode = GifColorTableMode.Global, - GlobalColorTableLength = 2, - Comments = new List { "Foo" } - }; - - var clone = (GifMetadata)meta.DeepClone(); - - clone.RepeatCount = 2; - clone.ColorTableMode = GifColorTableMode.Local; - clone.GlobalColorTableLength = 1; - - Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); - Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); - Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); - Assert.False(meta.Comments.Equals(clone.Comments)); - Assert.True(meta.Comments.SequenceEqual(clone.Comments)); - } - - [Fact] - public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + public static readonly TheoryData RatioFiles = + new() { - var testFile = TestFile.Create(TestImages.Gif.Rings); + { 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 } + }; - using Image image = testFile.CreateRgba32Image(new GifDecoder()); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(1, metadata.Comments.Count); - Assert.Equal("ImageSharp", metadata.Comments[0]); - } + public static readonly TheoryData RepeatFiles = + new() + { + { TestImages.Gif.Cheers, 0 }, + { TestImages.Gif.Receipt, 1 }, + { TestImages.Gif.Rings, 1 } + }; - [Fact] - public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetadata { - DecoderOptions options = new() - { - SkipMetadata = true - }; + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2, + Comments = new List { "Foo" } + }; + + var clone = (GifMetadata)meta.DeepClone(); + + clone.RepeatCount = 2; + clone.ColorTableMode = GifColorTableMode.Local; + clone.GlobalColorTableLength = 1; + + Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); + Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); + Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); + Assert.False(meta.Comments.Equals(clone.Comments)); + Assert.True(meta.Comments.SequenceEqual(clone.Comments)); + } - var testFile = TestFile.Create(TestImages.Gif.Rings); + [Fact] + public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() + { + var testFile = TestFile.Create(TestImages.Gif.Rings); - using Image image = testFile.CreateRgba32Image(new GifDecoder(), options); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(0, metadata.Comments.Count); - } + using Image image = testFile.CreateRgba32Image(new GifDecoder()); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(1, metadata.Comments.Count); + Assert.Equal("ImageSharp", metadata.Comments[0]); + } - [Fact] - public void Decode_CanDecodeLargeTextComment() + [Fact] + public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() + { + DecoderOptions options = new() { - var testFile = TestFile.Create(TestImages.Gif.LargeComment); + SkipMetadata = true + }; - using Image image = testFile.CreateRgba32Image(new GifDecoder()); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } + var testFile = TestFile.Create(TestImages.Gif.Rings); - [Fact] - public void Encode_PreservesTextData() - { - var decoder = new GifDecoder(); - var testFile = TestFile.Create(TestImages.Gif.LargeComment); - - using Image input = testFile.CreateRgba32Image(decoder); - using var memoryStream = new MemoryStream(); - input.Save(memoryStream, new GifEncoder()); - memoryStream.Position = 0; - - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); - GifMetadata metadata = image.Metadata.GetGifMetadata(); - Assert.Equal(2, metadata.Comments.Count); - Assert.Equal(new string('c', 349), metadata.Comments[0]); - Assert.Equal("ImageSharp", metadata.Comments[1]); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new GifDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new GifDecoder(); - IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new GifDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new GifDecoder(); - using Image image = await decoder.DecodeAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [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(DecoderOptions.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(DecoderOptions.Default, stream); - GifMetadata meta = image.Metadata.GetGifMetadata(); - Assert.Equal(repeatCount, meta.RepeatCount); - } + using Image image = testFile.CreateRgba32Image(new GifDecoder(), options); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(0, metadata.Comments.Count); + } + + [Fact] + public void Decode_CanDecodeLargeTextComment() + { + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using Image image = testFile.CreateRgba32Image(new GifDecoder()); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); + } + + [Fact] + public void Encode_PreservesTextData() + { + var decoder = new GifDecoder(); + var testFile = TestFile.Create(TestImages.Gif.LargeComment); + + using Image input = testFile.CreateRgba32Image(decoder); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, new GifEncoder()); + memoryStream.Position = 0; + + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); + GifMetadata metadata = image.Metadata.GetGifMetadata(); + Assert.Equal(2, metadata.Comments.Count); + Assert.Equal(new string('c', 349), metadata.Comments[0]); + Assert.Equal("ImageSharp", metadata.Comments[1]); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new GifDecoder(); + using Image image = await decoder.DecodeAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + + [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(DecoderOptions.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(DecoderOptions.Default, stream); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs index 949315c2ad..8aac817b15 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/ImageExtensionsTest.cs @@ -1,155 +1,151 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Gif +namespace SixLabors.ImageSharp.Tests.Formats.Gif; + +public class ImageExtensionsTest { - public class ImageExtensionsTest + [Fact] + public void SaveAsGif_Path() { - [Fact] - public void SaveAsGif_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsGif_Path.gif"); - - using (var image = new Image(10, 10)) - { - image.SaveAsGif(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsGif_Path.gif"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsGif(file); } - [Fact] - public async Task SaveAsGifAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif"); + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsGifAsync(file); - } + [Fact] + public async Task SaveAsGifAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsGifAsync_Path.gif"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(file); } - [Fact] - public void SaveAsGif_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif"); + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsGif(file, new GifEncoder()); - } + [Fact] + public void SaveAsGif_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsGif_Path_Encoder.gif"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsGif(file, new GifEncoder()); } - [Fact] - public async Task SaveAsGifAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif"); + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsGifAsync(file, new GifEncoder()); - } + [Fact] + public async Task SaveAsGifAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsGifAsync_Path_Encoder.gif"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(file, new GifEncoder()); } - [Fact] - public void SaveAsGif_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsGif(memoryStream); - } + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsGif_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsGif(memoryStream); } - [Fact] - public async Task SaveAsGifAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsGifAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsGifAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(memoryStream); } - [Fact] - public void SaveAsGif_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsGif(memoryStream, new GifEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsGif_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsGif(memoryStream, new GifEncoder()); } - [Fact] - public async Task SaveAsGifAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/gif", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsGifAsync(memoryStream, new GifEncoder()); - } + [Fact] + public async Task SaveAsGifAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsGifAsync(memoryStream, new GifEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/gif", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/gif", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index 11a84c70cd..c602bc91bb 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -3,19 +3,16 @@ using SixLabors.ImageSharp.Formats.Gif; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections +public class GifGraphicControlExtensionTests { - public class GifGraphicControlExtensionTests + [Fact] + public void TestPackedValue() { - [Fact] - public void TestPackedValue() - { - Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false)); - Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true)); - Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false)); - Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false)); - } + Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false)); + Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true)); + Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false)); + Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false)); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs index c9ed2ef4fc..51fe2fa2ea 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs @@ -3,22 +3,19 @@ using SixLabors.ImageSharp.Formats.Gif; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections +public class GifImageDescriptorTests { - public class GifImageDescriptorTests + [Fact] + public void TestPackedValue() { - [Fact] - public void TestPackedValue() - { - Assert.Equal(129, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable - Assert.Equal(65, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag - Assert.Equal(33, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag - Assert.Equal(225, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all - Assert.Equal(8, GifImageDescriptor.GetPackedValue(false, false, false, 8)); - Assert.Equal(228, GifImageDescriptor.GetPackedValue(true, true, true, 4)); - Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8)); - } + Assert.Equal(129, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable + Assert.Equal(65, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag + Assert.Equal(33, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag + Assert.Equal(225, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all + Assert.Equal(8, GifImageDescriptor.GetPackedValue(false, false, false, 8)); + Assert.Equal(228, GifImageDescriptor.GetPackedValue(true, true, true, 4)); + Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8)); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs index 5abcba9b8f..7f93edb37b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs @@ -3,21 +3,18 @@ using SixLabors.ImageSharp.Formats.Gif; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections; -namespace SixLabors.ImageSharp.Tests.Formats.Gif.Sections +public class GifLogicalScreenDescriptorTests { - public class GifLogicalScreenDescriptorTests + [Fact] + public void TestPackedValue() { - [Fact] - public void TestPackedValue() - { - Assert.Equal(0, GifLogicalScreenDescriptor.GetPackedValue(false, 0, false, 0)); - Assert.Equal(128, GifLogicalScreenDescriptor.GetPackedValue(true, 0, false, 0)); // globalColorTableFlag - Assert.Equal(8, GifLogicalScreenDescriptor.GetPackedValue(false, 0, true, 0)); // sortFlag - Assert.Equal(48, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 0)); - Assert.Equal(155, GifLogicalScreenDescriptor.GetPackedValue(true, 1, true, 3)); - Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); - } + Assert.Equal(0, GifLogicalScreenDescriptor.GetPackedValue(false, 0, false, 0)); + Assert.Equal(128, GifLogicalScreenDescriptor.GetPackedValue(true, 0, false, 0)); // globalColorTableFlag + Assert.Equal(8, GifLogicalScreenDescriptor.GetPackedValue(false, 0, true, 0)); // sortFlag + Assert.Equal(48, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 0)); + Assert.Equal(155, GifLogicalScreenDescriptor.GetPackedValue(true, 1, true, 3)); + Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); } } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 24145b1bff..06fe2601cc 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Linq; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; @@ -15,119 +12,117 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats +namespace SixLabors.ImageSharp.Tests.Formats; + +public class ImageFormatManagerTests { - public class ImageFormatManagerTests - { - public ImageFormatManager FormatsManagerEmpty { get; } + public ImageFormatManager FormatsManagerEmpty { get; } - public ImageFormatManager DefaultFormatsManager { get; } + public ImageFormatManager DefaultFormatsManager { get; } - public ImageFormatManagerTests() - { - this.DefaultFormatsManager = Configuration.CreateDefaultInstance().ImageFormatsManager; - this.FormatsManagerEmpty = new ImageFormatManager(); - } + public ImageFormatManagerTests() + { + this.DefaultFormatsManager = Configuration.CreateDefaultInstance().ImageFormatsManager; + this.FormatsManagerEmpty = new ImageFormatManager(); + } - [Fact] - 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()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - } + [Fact] + 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()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + } - [Fact] - public void AddImageFormatDetectorNullThrows() - => Assert.Throws(() => this.DefaultFormatsManager.AddImageFormatDetector(null)); + [Fact] + public void AddImageFormatDetectorNullThrows() + => Assert.Throws(() => this.DefaultFormatsManager.AddImageFormatDetector(null)); - [Fact] - public void RegisterNullMimeTypeEncoder() - { - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, new Mock().Object)); - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null)); - Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, null)); - } + [Fact] + public void RegisterNullMimeTypeEncoder() + { + Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, new Mock().Object)); + Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null)); + Assert.Throws(() => this.DefaultFormatsManager.SetEncoder(null, null)); + } - [Fact] - public void RegisterNullSetDecoder() - { - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, new Mock().Object)); - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null)); - Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, null)); - } + [Fact] + public void RegisterNullSetDecoder() + { + Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, new Mock().Object)); + Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null)); + Assert.Throws(() => this.DefaultFormatsManager.SetDecoder(null, null)); + } - [Fact] - public void RegisterMimeTypeEncoderReplacesLast() - { - IImageEncoder encoder1 = new Mock().Object; - this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); - IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder1, found); - - IImageEncoder encoder2 = new Mock().Object; - this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); - IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder2, found2); - Assert.NotEqual(found, found2); - } + [Fact] + public void RegisterMimeTypeEncoderReplacesLast() + { + IImageEncoder encoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); + IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder1, found); + + IImageEncoder encoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); + IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } - [Fact] - public void RegisterMimeTypeDecoderReplacesLast() - { - IImageDecoder decoder1 = new Mock().Object; - this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); - IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder1, found); - - IImageDecoder decoder2 = new Mock().Object; - this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); - IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder2, found2); - Assert.NotEqual(found, found2); - } + [Fact] + public void RegisterMimeTypeDecoderReplacesLast() + { + IImageDecoder decoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); + IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder1, found); + + IImageDecoder decoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); + IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); + } - [Fact] - public void AddFormatCallsConfig() - { - var provider = new Mock(); - var config = new Configuration(); - config.Configure(provider.Object); + [Fact] + public void AddFormatCallsConfig() + { + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); - provider.Verify(x => x.Configure(config)); - } + provider.Verify(x => x.Configure(config)); + } - [Fact] - public void DetectFormatAllocatesCleanBuffer() + [Fact] + public void DetectFormatAllocatesCleanBuffer() + { + byte[] jpegImage; + using (var buffer = new MemoryStream()) { - byte[] jpegImage; - using (var buffer = new MemoryStream()) - { - using var image = new Image(100, 100); - image.SaveAsJpeg(buffer); - jpegImage = buffer.ToArray(); - } - - byte[] invalidImage = { 1, 2, 3 }; - - Assert.Equal(Image.DetectFormat(jpegImage), JpegFormat.Instance); - Assert.True(Image.DetectFormat(invalidImage) is null); + using var image = new Image(100, 100); + image.SaveAsJpeg(buffer); + jpegImage = buffer.ToArray(); } + + byte[] invalidImage = { 1, 2, 3 }; + + Assert.Equal(Image.DetectFormat(jpegImage), JpegFormat.Instance); + Assert.True(Image.DetectFormat(invalidImage) is null); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs index 019c6ef45d..740b410feb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/AdobeMarkerTests.cs @@ -4,80 +4,77 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public class AdobeMarkerTests { - [Trait("Format", "Jpg")] - public class AdobeMarkerTests + // Taken from actual test image + private readonly byte[] bytes = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x2 }; + + // Altered components + private readonly byte[] bytes2 = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x1, 0x1, 0x1 }; + + [Fact] + public void MarkerLengthIsCorrect() + { + Assert.Equal(12, AdobeMarker.Length); + } + + [Fact] + public void MarkerReturnsCorrectParsedValue() + { + bool isAdobe = AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + + Assert.True(isAdobe); + Assert.Equal(100, marker.DCTEncodeVersion); + Assert.Equal(0, marker.APP14Flags0); + Assert.Equal(0, marker.APP14Flags1); + Assert.Equal(JpegConstants.Adobe.ColorTransformYcck, marker.ColorTransform); + } + + [Fact] + public void MarkerIgnoresIncorrectValue() + { + bool isAdobe = AdobeMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out AdobeMarker marker); + + Assert.False(isAdobe); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerEqualityIsCorrect() { - // Taken from actual test image - private readonly byte[] bytes = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x2 }; - - // Altered components - private readonly byte[] bytes2 = { 0x41, 0x64, 0x6F, 0x62, 0x65, 0x0, 0x64, 0x0, 0x0, 0x1, 0x1, 0x1 }; - - [Fact] - public void MarkerLengthIsCorrect() - { - Assert.Equal(12, AdobeMarker.Length); - } - - [Fact] - public void MarkerReturnsCorrectParsedValue() - { - bool isAdobe = AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - - Assert.True(isAdobe); - Assert.Equal(100, marker.DCTEncodeVersion); - Assert.Equal(0, marker.APP14Flags0); - Assert.Equal(0, marker.APP14Flags1); - Assert.Equal(JpegConstants.Adobe.ColorTransformYcck, marker.ColorTransform); - } - - [Fact] - public void MarkerIgnoresIncorrectValue() - { - bool isAdobe = AdobeMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out AdobeMarker marker); - - Assert.False(isAdobe); - Assert.Equal(default, marker); - } - - [Fact] - public void MarkerEqualityIsCorrect() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); - - Assert.True(marker.Equals(marker2)); - } - - [Fact] - public void MarkerInEqualityIsCorrect() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); - - Assert.False(marker.Equals(marker2)); - } - - [Fact] - public void MarkerHashCodeIsReplicable() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); - - Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); - } - - [Fact] - public void MarkerHashCodeIsUnique() - { - AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); - AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); - - Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); - } + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); + + Assert.True(marker.Equals(marker2)); + } + + [Fact] + public void MarkerInEqualityIsCorrect() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); + + Assert.False(marker.Equals(marker2)); + } + + [Fact] + public void MarkerHashCodeIsReplicable() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker2); + + Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); + } + + [Fact] + public void MarkerHashCodeIsUnique() + { + AdobeMarker.TryParse(this.bytes, out AdobeMarker marker); + AdobeMarker.TryParse(this.bytes2, out AdobeMarker marker2); + + Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 3d75d34481..3e4bcae6b7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -3,483 +3,479 @@ // Uncomment this to turn unit tests into benchmarks: // #define BENCHMARKING -using System; -using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class Block8x8FTests : JpegFixture { - [Trait("Format", "Jpg")] - public partial class Block8x8FTests : JpegFixture - { #if BENCHMARKING - public const int Times = 1000000; + public const int Times = 1000000; #else - public const int Times = 1; + public const int Times = 1; #endif - public Block8x8FTests(ITestOutputHelper output) - : base(output) + public Block8x8FTests(ITestOutputHelper output) + : base(output) + { + } + + private bool SkipOnNonAvx2Runner() + { + if (!SimdUtils.HasVector8) { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return true; } - private bool SkipOnNonAvx2Runner() - { - if (!SimdUtils.HasVector8) - { - this.Output.WriteLine("AVX2 not supported, skipping!"); - return true; - } + return false; + } - return false; - } + [Fact] + public void Indexer() + { + float sum = 0; + this.Measure( + Times, + () => + { + var block = default(Block8x8F); - [Fact] - public void Indexer() - { - float sum = 0; - this.Measure( - Times, - () => + for (int i = 0; i < Block8x8F.Size; i++) { - var block = default(Block8x8F); - - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = i; - } - - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += block[i]; - } - }); - Assert.Equal(sum, 64f * 63f * 0.5f); - } - - [Fact] - public void Indexer_ReferenceBenchmarkWithArray() - { - float sum = 0; + block[i] = i; + } - this.Measure( - Times, - () => + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) { - // Block8x8F block = new Block8x8F(); - float[] block = new float[64]; - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = i; - } - - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += block[i]; - } - }); - Assert.Equal(sum, 64f * 63f * 0.5f); - } + sum += block[i]; + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); + } - [Fact] - public void Load_Store_FloatArray() - { - float[] data = new float[Block8x8F.Size]; - float[] mirror = new float[Block8x8F.Size]; + [Fact] + public void Indexer_ReferenceBenchmarkWithArray() + { + float sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) + this.Measure( + Times, + () => { - data[i] = i; - } + // Block8x8F block = new Block8x8F(); + float[] block = new float[64]; + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = i; + } - this.Measure( - Times, - () => + sum = 0; + for (int i = 0; i < Block8x8F.Size; i++) { - var b = default(Block8x8F); - b.LoadFrom(data); - b.ScaledCopyTo(mirror); - }); + sum += block[i]; + } + }); + Assert.Equal(sum, 64f * 63f * 0.5f); + } - Assert.Equal(data, mirror); + [Fact] + public void Load_Store_FloatArray() + { + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; - // PrintLinearData((Span)mirror); + for (int i = 0; i < Block8x8F.Size; i++) + { + data[i] = i; } - [Fact] - public void TransposeInplace() - { - static void RunTest() + this.Measure( + Times, + () => { - float[] expected = Create8x8FloatData(); - ReferenceImplementations.Transpose8x8(expected); + var b = default(Block8x8F); + b.LoadFrom(data); + b.ScaledCopyTo(mirror); + }); + + Assert.Equal(data, mirror); - var block8x8 = default(Block8x8F); - block8x8.LoadFrom(Create8x8FloatData()); + // PrintLinearData((Span)mirror); + } - block8x8.TransposeInplace(); + [Fact] + public void TransposeInplace() + { + static void RunTest() + { + float[] expected = Create8x8FloatData(); + ReferenceImplementations.Transpose8x8(expected); - float[] actual = new float[64]; - block8x8.ScaledCopyTo(actual); + var block8x8 = default(Block8x8F); + block8x8.LoadFrom(Create8x8FloatData()); - Assert.Equal(expected, actual); - } + block8x8.TransposeInplace(); + + float[] actual = new float[64]; + block8x8.ScaledCopyTo(actual); - // This method has only 2 implementations: - // 1. AVX - // 2. Scalar - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); + Assert.Equal(expected, actual); } - private static float[] Create8x8ColorCropTestData() + // This method has only 2 implementations: + // 1. AVX + // 2. Scalar + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); + } + + private static float[] Create8x8ColorCropTestData() + { + float[] result = new float[64]; + for (int i = 0; i < 8; i++) { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - 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); } - - return result; } - [Fact] - public void NormalizeColors() + return result; + } + + [Fact] + public void NormalizeColors() + { + var block = default(Block8x8F); + float[] input = Create8x8ColorCropTestData(); + block.LoadFrom(input); + this.Output.WriteLine("Input:"); + this.PrintLinearData(input); + + Block8x8F dest = block; + dest.NormalizeColorsInPlace(255); + + float[] array = new float[64]; + dest.ScaledCopyTo(array); + this.Output.WriteLine("Result:"); + this.PrintLinearData(array); + foreach (float val in array) { - var block = default(Block8x8F); - float[] input = Create8x8ColorCropTestData(); - block.LoadFrom(input); - this.Output.WriteLine("Input:"); - this.PrintLinearData(input); - - Block8x8F dest = block; - dest.NormalizeColorsInPlace(255); - - float[] array = new float[64]; - dest.ScaledCopyTo(array); - this.Output.WriteLine("Result:"); - this.PrintLinearData(array); - foreach (float val in array) - { - Assert.InRange(val, 0, 255); - } + Assert.InRange(val, 0, 255); } + } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundAvx2(int seed) + [Theory] + [InlineData(1)] + [InlineData(2)] + public void NormalizeColorsAndRoundAvx2(int seed) + { + if (this.SkipOnNonAvx2Runner()) { - if (this.SkipOnNonAvx2Runner()) - { - return; - } + return; + } - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); + Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); + Block8x8F expected = source; + expected.NormalizeColorsInPlace(255); + expected.RoundInPlace(); - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector8(255); + Block8x8F actual = source; + actual.NormalizeColorsAndRoundInPlaceVector8(255); - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } + this.Output.WriteLine(expected.ToString()); + this.Output.WriteLine(actual.ToString()); + this.CompareBlocks(expected, actual, 0); + } - [Theory] - [InlineData(1, 2)] - [InlineData(2, 1)] - public void Quantize(int srcSeed, int qtSeed) + [Theory] + [InlineData(1, 2)] + [InlineData(2, 1)] + public void Quantize(int srcSeed, int qtSeed) + { + static void RunTest(string srcSeedSerialized, string qtSeedSerialized) { - static void RunTest(string srcSeedSerialized, string qtSeedSerialized) - { - int srcSeed = FeatureTestRunner.Deserialize(srcSeedSerialized); - int qtSeed = FeatureTestRunner.Deserialize(qtSeedSerialized); + int srcSeed = FeatureTestRunner.Deserialize(srcSeedSerialized); + int qtSeed = FeatureTestRunner.Deserialize(qtSeedSerialized); - Block8x8F source = CreateRandomFloatBlock(-2000, 2000, srcSeed); + Block8x8F source = CreateRandomFloatBlock(-2000, 2000, srcSeed); - // Quantization code is used only in jpeg where it's guaranteed that - // qunatization valus are greater than 1 - // Quantize method supports negative numbers by very small numbers can cause troubles - Block8x8F quant = CreateRandomFloatBlock(1, 2000, qtSeed); + // Quantization code is used only in jpeg where it's guaranteed that + // qunatization valus are greater than 1 + // Quantize method supports negative numbers by very small numbers can cause troubles + Block8x8F quant = CreateRandomFloatBlock(1, 2000, qtSeed); - // Reference implementation quantizes given block via division - Block8x8 expected = default; - ReferenceImplementations.Quantize(ref source, ref expected, ref quant, ZigZag.TransposingOrder); + // Reference implementation quantizes given block via division + Block8x8 expected = default; + ReferenceImplementations.Quantize(ref source, ref expected, ref quant, ZigZag.TransposingOrder); - // Actual current implementation quantizes given block via multiplication - // With quantization table reciprocal - for (int i = 0; i < Block8x8F.Size; i++) - { - quant[i] = 1f / quant[i]; - } - - Block8x8 actual = default; - Block8x8F.Quantize(ref source, ref actual, ref quant); - - Assert.True(CompareBlocks(expected, actual, 1, out int diff), $"Blocks are not equal, diff={diff}"); + // Actual current implementation quantizes given block via multiplication + // With quantization table reciprocal + for (int i = 0; i < Block8x8F.Size; i++) + { + quant[i] = 1f / quant[i]; } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - srcSeed, - qtSeed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + Block8x8 actual = default; + Block8x8F.Quantize(ref source, ref actual, ref quant); + + Assert.True(CompareBlocks(expected, actual, 1, out int diff), $"Blocks are not equal, diff={diff}"); } - [Fact] - public void RoundInto() - { - float[] data = Create8x8RandomFloatData(-1000, 1000); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + srcSeed, + qtSeed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + } + + [Fact] + public void RoundInto() + { + float[] data = Create8x8RandomFloatData(-1000, 1000); - var source = default(Block8x8F); - source.LoadFrom(data); - var dest = default(Block8x8); + var source = default(Block8x8F); + source.LoadFrom(data); + var dest = default(Block8x8); - source.RoundInto(ref dest); + source.RoundInto(ref dest); - for (int i = 0; i < Block8x8.Size; i++) - { - float expectedFloat = data[i]; - short expectedShort = (short)Math.Round(expectedFloat); - short actualShort = dest[i]; + for (int i = 0; i < Block8x8.Size; i++) + { + float expectedFloat = data[i]; + short expectedShort = (short)Math.Round(expectedFloat); + short actualShort = dest[i]; - Assert.Equal(expectedShort, actualShort); - } + Assert.Equal(expectedShort, actualShort); } + } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void RoundInPlaceSlow(int seed) - { - Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void RoundInPlaceSlow(int seed) + { + Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); - Block8x8F d = s; - d.RoundInPlace(); + Block8x8F d = s; + d.RoundInPlace(); - this.Output.WriteLine(s.ToString()); - this.Output.WriteLine(d.ToString()); + this.Output.WriteLine(s.ToString()); + this.Output.WriteLine(d.ToString()); - for (int i = 0; i < 64; i++) - { - float expected = (float)Math.Round(s[i]); - float actual = d[i]; + for (int i = 0; i < 64; i++) + { + float expected = (float)Math.Round(s[i]); + float actual = d[i]; - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } + } - [Fact] - public void MultiplyInPlace_ByOtherBlock() + [Fact] + public void MultiplyInPlace_ByOtherBlock() + { + static void RunTest() { - static void RunTest() - { - Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); - Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); + Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); + Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); - Block8x8F actual = original; + Block8x8F actual = original; - actual.MultiplyInPlace(ref m); + actual.MultiplyInPlace(ref m); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(original[i] * m[i], actual[i]); - } + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal(original[i] * m[i], actual[i]); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); } - [Fact] - public void AddToAllInPlace() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + } + + [Fact] + public void AddToAllInPlace() + { + static void RunTest() { - static void RunTest() - { - Block8x8F original = CreateRandomFloatBlock(-500, 500); + Block8x8F original = CreateRandomFloatBlock(-500, 500); - Block8x8F actual = original; - actual.AddInPlace(42f); + Block8x8F actual = original; + actual.AddInPlace(42f); - for (int i = 0; i < 64; i++) - { - Assert.Equal(original[i] + 42f, actual[i]); - } + for (int i = 0; i < 64; i++) + { + Assert.Equal(original[i] + 42f, actual[i]); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); } - [Fact] - public void MultiplyInPlace_ByScalar() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + } + + [Fact] + public void MultiplyInPlace_ByScalar() + { + static void RunTest() { - static void RunTest() - { - Block8x8F original = CreateRandomFloatBlock(-500, 500); + Block8x8F original = CreateRandomFloatBlock(-500, 500); - Block8x8F actual = original; - actual.MultiplyInPlace(42f); + Block8x8F actual = original; + actual.MultiplyInPlace(42f); - for (int i = 0; i < 64; i++) - { - Assert.Equal(original[i] * 42f, actual[i]); - } + for (int i = 0; i < 64; i++) + { + Assert.Equal(original[i] * 42f, actual[i]); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); } - [Fact] - public void LoadFromUInt16Scalar() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + } + + [Fact] + public void LoadFromUInt16Scalar() + { + if (this.SkipOnNonAvx2Runner()) { - if (this.SkipOnNonAvx2Runner()) - { - return; - } + return; + } - short[] data = Create8x8ShortData(); + short[] data = Create8x8ShortData(); - var source = Block8x8.Load(data); + var source = Block8x8.Load(data); - Block8x8F dest = default; - dest.LoadFromInt16Scalar(ref source); + Block8x8F dest = default; + dest.LoadFromInt16Scalar(ref source); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(data[i], dest[i]); - } + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal(data[i], dest[i]); } + } - [Fact] - public void LoadFromUInt16ExtendedAvx2() + [Fact] + public void LoadFromUInt16ExtendedAvx2() + { + if (this.SkipOnNonAvx2Runner()) { - if (this.SkipOnNonAvx2Runner()) - { - return; - } + return; + } - short[] data = Create8x8ShortData(); + short[] data = Create8x8ShortData(); - var source = Block8x8.Load(data); + var source = Block8x8.Load(data); - Block8x8F dest = default; - dest.LoadFromInt16ExtendedAvx2(ref source); + Block8x8F dest = default; + dest.LoadFromInt16ExtendedAvx2(ref source); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(data[i], dest[i]); - } + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal(data[i], dest[i]); } + } - [Fact] - public void EqualsToScalar_AllOne() + [Fact] + public void EqualsToScalar_AllOne() + { + static void RunTest() { - static void RunTest() + // Fill matrix with valid value + Block8x8F block = default; + for (int i = 0; i < Block8x8F.Size; i++) { - // Fill matrix with valid value - Block8x8F block = default; - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = 1; - } - - bool isEqual = block.EqualsToScalar(1); - Assert.True(isEqual); + block[i] = 1; } - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + bool isEqual = block.EqualsToScalar(1); + Assert.True(isEqual); } - [Theory] - [InlineData(10)] - public void EqualsToScalar_OneOffEachPosition(int equalsTo) + // 2 paths: + // 1. DisableFMA - call avx implementation + // 3. DisableAvx2 - call fallback code of float implementation + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(10)] + public void EqualsToScalar_OneOffEachPosition(int equalsTo) + { + static void RunTest(string serializedEqualsTo) { - static void RunTest(string serializedEqualsTo) - { - int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); - const int offValue = 0; + int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); + const int offValue = 0; - // Fill matrix with valid value - Block8x8F block = default; - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = equalsTo; - } + // Fill matrix with valid value + Block8x8F block = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = equalsTo; + } - // Assert with invalid values at different positions - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = offValue; + // Assert with invalid values at different positions + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = offValue; - bool isEqual = block.EqualsToScalar(equalsTo); - Assert.False(isEqual, $"False equality:\n{block}"); + bool isEqual = block.EqualsToScalar(equalsTo); + Assert.False(isEqual, $"False equality:\n{block}"); - // restore valid value for next iteration assertion - block[i] = equalsTo; - } + // restore valid value for next iteration assertion + block[i] = equalsTo; } - - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - equalsTo, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } - [Theory] - [InlineData(39)] - public void EqualsToScalar_Valid(int equalsTo) - { - static void RunTest(string serializedEqualsTo) - { - int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); + // 2 paths: + // 1. DisableFMA - call avx implementation + // 3. DisableAvx2 - call fallback code of float implementation + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + equalsTo, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - // Fill matrix with valid value - Block8x8F block = default; - for (int i = 0; i < Block8x8F.Size; i++) - { - block[i] = equalsTo; - } + [Theory] + [InlineData(39)] + public void EqualsToScalar_Valid(int equalsTo) + { + static void RunTest(string serializedEqualsTo) + { + int equalsTo = FeatureTestRunner.Deserialize(serializedEqualsTo); - // Assert - bool isEqual = block.EqualsToScalar(equalsTo); - Assert.True(isEqual); + // Fill matrix with valid value + Block8x8F block = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + block[i] = equalsTo; } - // 2 paths: - // 1. DisableFMA - call avx implementation - // 3. DisableAvx2 - call fallback code of float implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - equalsTo, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + // Assert + bool isEqual = block.EqualsToScalar(equalsTo); + Assert.True(isEqual); } + + // 2 paths: + // 1. DisableFMA - call avx implementation + // 3. DisableAvx2 - call fallback code of float implementation + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + equalsTo, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index eaa512dd3f..798ea30407 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -1,292 +1,289 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class Block8x8Tests : JpegFixture { - [Trait("Format", "Jpg")] - public class Block8x8Tests : JpegFixture + public Block8x8Tests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void Construct_And_Indexer_Get() { - public Block8x8Tests(ITestOutputHelper output) - : base(output) + short[] data = Create8x8ShortData(); + + var block = Block8x8.Load(data); + + for (int i = 0; i < Block8x8.Size; i++) { + Assert.Equal(data[i], block[i]); } + } - [Fact] - public void Construct_And_Indexer_Get() - { - short[] data = Create8x8ShortData(); + [Fact] + public void Indexer_Set() + { + Block8x8 block = default; - var block = Block8x8.Load(data); + block[17] = 17; + block[42] = 42; - for (int i = 0; i < Block8x8.Size; i++) - { - Assert.Equal(data[i], block[i]); - } - } + Assert.Equal(0, block[0]); + Assert.Equal(17, block[17]); + Assert.Equal(42, block[42]); + } - [Fact] - public void Indexer_Set() - { - Block8x8 block = default; + [Fact] + public void AsFloatBlock() + { + short[] data = Create8x8ShortData(); - block[17] = 17; - block[42] = 42; + var source = Block8x8.Load(data); - Assert.Equal(0, block[0]); - Assert.Equal(17, block[17]); - Assert.Equal(42, block[42]); - } + Block8x8F dest = source.AsFloatBlock(); - [Fact] - public void AsFloatBlock() + for (int i = 0; i < Block8x8F.Size; i++) { - short[] data = Create8x8ShortData(); + Assert.Equal(data[i], dest[i]); + } + } - var source = Block8x8.Load(data); + [Fact] + public void ToArray() + { + short[] data = Create8x8ShortData(); + var block = Block8x8.Load(data); - Block8x8F dest = source.AsFloatBlock(); + short[] result = block.ToArray(); - for (int i = 0; i < Block8x8F.Size; i++) - { - Assert.Equal(data[i], dest[i]); - } - } + Assert.Equal(data, result); + } - [Fact] - public void ToArray() - { - short[] data = Create8x8ShortData(); - var block = Block8x8.Load(data); + [Fact] + public void Equality_WhenFalse() + { + short[] data = Create8x8ShortData(); + var block1 = Block8x8.Load(data); + var block2 = Block8x8.Load(data); - short[] result = block.ToArray(); + block1[0] = 42; + block2[0] = 666; - Assert.Equal(data, result); - } + Assert.NotEqual(block1, block2); + } - [Fact] - public void Equality_WhenFalse() - { - short[] data = Create8x8ShortData(); - var block1 = Block8x8.Load(data); - var block2 = Block8x8.Load(data); + [Fact] + public void IndexerXY() + { + Block8x8 block = default; + block[(8 * 3) + 5] = 42; - block1[0] = 42; - block2[0] = 666; + short value = block[5, 3]; - Assert.NotEqual(block1, block2); - } + Assert.Equal(42, value); + } - [Fact] - public void IndexerXY() - { - Block8x8 block = default; - block[(8 * 3) + 5] = 42; + [Fact] + public void TotalDifference() + { + short[] data = Create8x8ShortData(); + var block1 = Block8x8.Load(data); + var block2 = Block8x8.Load(data); - short value = block[5, 3]; + block2[10] += 7; + block2[63] += 8; - Assert.Equal(42, value); - } + long d = Block8x8.TotalDifference(ref block1, ref block2); + + Assert.Equal(15, d); + } - [Fact] - public void TotalDifference() + [Fact] + public void GetLastNonZeroIndex_AllZero() + { + static void RunTest() { - short[] data = Create8x8ShortData(); - var block1 = Block8x8.Load(data); - var block2 = Block8x8.Load(data); + Block8x8 data = default; - block2[10] += 7; - block2[63] += 8; + nint expected = -1; - long d = Block8x8.TotalDifference(ref block1, ref block2); + nint actual = data.GetLastNonZeroIndex(); - Assert.Equal(15, d); + Assert.Equal(expected, actual); } - [Fact] - public void GetLastNonZeroIndex_AllZero() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Fact] + public void GetLastNonZeroIndex_AllNonZero() + { + static void RunTest() { - static void RunTest() + Block8x8 data = default; + for (int i = 0; i < Block8x8.Size; i++) { - Block8x8 data = default; - - nint expected = -1; + data[i] = 10; + } - nint actual = data.GetLastNonZeroIndex(); + nint expected = Block8x8.Size - 1; - Assert.Equal(expected, actual); - } + nint actual = data.GetLastNonZeroIndex(); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + Assert.Equal(expected, actual); } - [Fact] - public void GetLastNonZeroIndex_AllNonZero() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastNonZeroIndex_RandomFilledSingle(int seed) + { + static void RunTest(string seedSerialized) { - static void RunTest() + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) { Block8x8 data = default; - for (int i = 0; i < Block8x8.Size; i++) - { - data[i] = 10; - } - nint expected = Block8x8.Size - 1; + int setIndex = rng.Next(1, Block8x8.Size); + data[setIndex] = (short)rng.Next(-2000, 2000); + + nint expected = setIndex; nint actual = data.GetLastNonZeroIndex(); Assert.Equal(expected, actual); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledSingle(int seed) + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastNonZeroIndex_RandomFilledPartially(int seed) + { + static void RunTest(string seedSerialized) { - static void RunTest(string seedSerialized) + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); + + for (int i = 0; i < 1000; i++) { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - var rng = new Random(seed); + Block8x8 data = default; - for (int i = 0; i < 1000; i++) + int lastIndex = rng.Next(1, Block8x8.Size); + short fillValue = (short)rng.Next(-2000, 2000); + for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) { - Block8x8 data = default; - - int setIndex = rng.Next(1, Block8x8.Size); - data[setIndex] = (short)rng.Next(-2000, 2000); + data[dataIndex] = fillValue; + } - nint expected = setIndex; + int expected = lastIndex; - nint actual = data.GetLastNonZeroIndex(); + nint actual = data.GetLastNonZeroIndex(); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledPartially(int seed) - { - static void RunTest(string seedSerialized) - { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - var rng = new Random(seed); - - for (int i = 0; i < 1000; i++) - { - Block8x8 data = default; + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - int lastIndex = rng.Next(1, Block8x8.Size); - short fillValue = (short)rng.Next(-2000, 2000); - for (int dataIndex = 0; dataIndex <= lastIndex; dataIndex++) - { - data[dataIndex] = fillValue; - } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetLastNonZeroIndex_RandomFilledFragmented(int seed) + { + static void RunTest(string seedSerialized) + { + int seed = FeatureTestRunner.Deserialize(seedSerialized); + var rng = new Random(seed); - int expected = lastIndex; + for (int i = 0; i < 1000; i++) + { + Block8x8 data = default; - nint actual = data.GetLastNonZeroIndex(); + short fillValue = (short)rng.Next(-2000, 2000); - Assert.Equal(expected, actual); + // first filled chunk + int firstChunkStart = rng.Next(0, Block8x8.Size / 2); + int firstChunkEnd = rng.Next(firstChunkStart, Block8x8.Size / 2); + for (int dataIdx = firstChunkStart; dataIdx <= firstChunkEnd; dataIdx++) + { + data[dataIdx] = fillValue; } - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetLastNonZeroIndex_RandomFilledFragmented(int seed) - { - static void RunTest(string seedSerialized) - { - int seed = FeatureTestRunner.Deserialize(seedSerialized); - var rng = new Random(seed); - - for (int i = 0; i < 1000; i++) + // second filled chunk, there might be a spot with zero(s) between first and second chunk + int secondChunkStart = rng.Next(firstChunkEnd, Block8x8.Size); + int secondChunkEnd = rng.Next(secondChunkStart, Block8x8.Size); + for (int dataIdx = secondChunkStart; dataIdx <= secondChunkEnd; dataIdx++) { - Block8x8 data = default; - - short fillValue = (short)rng.Next(-2000, 2000); - - // first filled chunk - int firstChunkStart = rng.Next(0, Block8x8.Size / 2); - int firstChunkEnd = rng.Next(firstChunkStart, Block8x8.Size / 2); - for (int dataIdx = firstChunkStart; dataIdx <= firstChunkEnd; dataIdx++) - { - data[dataIdx] = fillValue; - } - - // second filled chunk, there might be a spot with zero(s) between first and second chunk - int secondChunkStart = rng.Next(firstChunkEnd, Block8x8.Size); - int secondChunkEnd = rng.Next(secondChunkStart, Block8x8.Size); - for (int dataIdx = secondChunkStart; dataIdx <= secondChunkEnd; dataIdx++) - { - data[dataIdx] = fillValue; - } + data[dataIdx] = fillValue; + } - int expected = secondChunkEnd; + int expected = secondChunkEnd; - nint actual = data.GetLastNonZeroIndex(); + nint actual = data.GetLastNonZeroIndex(); - Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\nInput matrix: {data}"); - } + Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\nInput matrix: {data}"); } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } - [Fact] - public void TransposeInplace() - { - static void RunTest() - { - short[] expected = Create8x8ShortData(); - ReferenceImplementations.Transpose8x8(expected); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - var block8x8 = default(Block8x8); - block8x8.LoadFrom(Create8x8ShortData()); + [Fact] + public void TransposeInplace() + { + static void RunTest() + { + short[] expected = Create8x8ShortData(); + ReferenceImplementations.Transpose8x8(expected); - block8x8.TransposeInplace(); + var block8x8 = default(Block8x8); + block8x8.LoadFrom(Create8x8ShortData()); - short[] actual = new short[64]; - block8x8.CopyTo(actual); + block8x8.TransposeInplace(); - Assert.Equal(expected, actual); - } + short[] actual = new short[64]; + block8x8.CopyTo(actual); - // This method has only 1 implementation: - // 1. Scalar - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); + Assert.Equal(expected, actual); } + + // This method has only 1 implementation: + // 1. Scalar + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index fc5e8f6bd3..5853ff37a5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -1,170 +1,119 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public static class DCTTests { - [Trait("Format", "Jpg")] - public static class DCTTests - { - // size of input values is 10 bit max - private const float MaxInputValue = 1023; - private const float MinInputValue = -1024; + // size of input values is 10 bit max + private const float MaxInputValue = 1023; + private const float MinInputValue = -1024; - // output value range is 12 bit max - private const float MaxOutputValue = 4096; - private const float NormalizationValue = MaxOutputValue / 2; + // output value range is 12 bit max + private const float MaxOutputValue = 4096; + private const float NormalizationValue = MaxOutputValue / 2; - internal static Block8x8F CreateBlockFromScalar(float value) + internal static Block8x8F CreateBlockFromScalar(float value) + { + Block8x8F result = default; + for (int i = 0; i < Block8x8F.Size; i++) { - Block8x8F result = default; - for (int i = 0; i < Block8x8F.Size; i++) - { - result[i] = value; - } + result[i] = value; + } + + return result; + } - return result; + public class FastFloatingPoint : JpegFixture + { + public FastFloatingPoint(ITestOutputHelper output) + : base(output) + { } - public class FastFloatingPoint : JpegFixture + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - public FastFloatingPoint(ITestOutputHelper output) - : base(output) - { - } + float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToNonOptimized(int seed) - { - float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); + var srcBlock = Block8x8F.Load(sourceArray); - var srcBlock = Block8x8F.Load(sourceArray); + // reference + Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref srcBlock); - // reference - Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref srcBlock); + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + // Dequantization using unit matrix - no values are upscaled + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - // Dequantization using unit matrix - no values are upscaled - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + // This step is needed to apply adjusting multipliers to the input block + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + srcBlock.MultiplyInPlace(ref dequantMatrix); - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInplace(); - srcBlock.MultiplyInPlace(ref dequantMatrix); + // IDCT calculation + FloatingPointDCT.TransformIDCT(ref srcBlock); - // IDCT calculation - FloatingPointDCT.TransformIDCT(ref srcBlock); + this.CompareBlocks(expected, srcBlock, 1f); + } - this.CompareBlocks(expected, srcBlock, 1f); - } + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public void LLM_TransformIDCT_CompareToAccurate(int seed) + { + float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToAccurate(int seed) - { - float[] sourceArray = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); + var srcBlock = Block8x8F.Load(sourceArray); - var srcBlock = Block8x8F.Load(sourceArray); + // reference + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); - // reference - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + // Dequantization using unit matrix - no values are upscaled + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - // Dequantization using unit matrix - no values are upscaled - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + // This step is needed to apply adjusting multipliers to the input block + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInplace(); - srcBlock.MultiplyInPlace(ref dequantMatrix); + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + srcBlock.MultiplyInPlace(ref dequantMatrix); - // IDCT calculation - FloatingPointDCT.TransformIDCT(ref srcBlock); + // IDCT calculation + FloatingPointDCT.TransformIDCT(ref srcBlock); - this.CompareBlocks(expected, srcBlock, 1f); - } + this.CompareBlocks(expected, srcBlock, 1f); + } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformIDCT(int seed) + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformIDCT(int seed) + { + static void RunTest(string serialized) { - static void RunTest(string serialized) - { - int seed = FeatureTestRunner.Deserialize(serialized); - - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - var srcBlock = default(Block8x8F); - srcBlock.LoadFrom(src); - - float[] expectedDest = new float[64]; - float[] temp = new float[64]; - - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); - - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); - - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - srcBlock.MultiplyInPlace(ref dequantMatrix); - - // testee - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInplace(); - FloatingPointDCT.TransformIDCT(ref srcBlock); - - float[] actualDest = srcBlock.ToArray(); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } + int seed = FeatureTestRunner.Deserialize(serialized); - // 4 paths: - // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call sse implementation - // 4. DisableHWIntrinsic - call Vector4 fallback implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_4x4(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 4, 4); + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); @@ -183,193 +132,240 @@ public void TranformIDCT_4x4(int seed) // Dequantization using unit matrix - no values are upscaled // as quant matrix is all 1's // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + FloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + srcBlock.MultiplyInPlace(ref dequantMatrix); // testee // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); - ScaledFloatingPointDCT.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); - - Span expectedSpan = expectedDest.AsSpan(); - Span actualSpan = srcBlock.ToArray().AsSpan(); + FloatingPointDCT.TransformIDCT(ref srcBlock); - // resulting matrix is 4x4 - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x)); - } - } + float[] actualDest = srcBlock.ToArray(); - static void AssertScaledElementEquality(Span expected, Span actual) - { - float average2x2 = 0f; - for (int y = 0; y < 2; y++) - { - int y8 = y * 8; - for (int x = 0; x < 2; x++) - { - float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); - average2x2 += clamped; - } - } + Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); + } - average2x2 = MathF.Round(average2x2 / 4f); + // 4 paths: + // 1. AllowAll - call avx/fma implementation + // 2. DisableFMA - call avx without fma implementation + // 3. DisableAvx - call sse implementation + // 4. DisableHWIntrinsic - call Vector4 fallback implementation + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + } - Assert.Equal((int)average2x2, (int)actual[0]); - } - } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TranformIDCT_4x4(int seed) + { + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 4, 4); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_2x2(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 2, 2); - var srcBlock = default(Block8x8F); - srcBlock.LoadFrom(src); + float[] expectedDest = new float[64]; + float[] temp = new float[64]; - float[] expectedDest = new float[64]; - float[] temp = new float[64]; + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + // testee + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + ScaledFloatingPointDCT.TransformIDCT_4x4(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); - // testee - // IDCT implementation tranforms blocks after transposition - srcBlock.TransposeInplace(); - ScaledFloatingPointDCT.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); + Span expectedSpan = expectedDest.AsSpan(); + Span actualSpan = srcBlock.ToArray().AsSpan(); - Span expectedSpan = expectedDest.AsSpan(); - Span actualSpan = srcBlock.ToArray().AsSpan(); + // resulting matrix is 4x4 + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x)); + } + } - // resulting matrix is 2x2 + static void AssertScaledElementEquality(Span expected, Span actual) + { + float average2x2 = 0f; for (int y = 0; y < 2; y++) { + int y8 = y * 8; for (int x = 0; x < 2; x++) { - AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x)); + float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); + average2x2 += clamped; } } - static void AssertScaledElementEquality(Span expected, Span actual) - { - float average4x4 = 0f; - for (int y = 0; y < 4; y++) - { - int y8 = y * 8; - for (int x = 0; x < 4; x++) - { - float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); - average4x4 += clamped; - } - } - - average4x4 = MathF.Round(average4x4 / 16f); + average2x2 = MathF.Round(average2x2 / 4f); - Assert.Equal((int)average4x4, (int)actual[0]); - } + Assert.Equal((int)average2x2, (int)actual[0]); } + } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TranformIDCT_1x1(int seed) - { - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 1, 1); - var srcBlock = default(Block8x8F); - srcBlock.LoadFrom(src); + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TranformIDCT_2x2(int seed) + { + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 2, 2); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); - float[] expectedDest = new float[64]; - float[] temp = new float[64]; + float[] expectedDest = new float[64]; + float[] temp = new float[64]; - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); - // testee - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // before applying IDCT - Block8x8F dequantMatrix = CreateBlockFromScalar(1); + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's - // This step is needed to apply adjusting multipliers to the input block - ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - // testee - // IDCT implementation tranforms blocks after transposition - // But DC lays on main diagonal which is not changed by transposition - float actual = ScaledFloatingPointDCT.TransformIDCT_1x1( - srcBlock[0], - dequantMatrix[0], - NormalizationValue, - MaxOutputValue); + // testee + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); + ScaledFloatingPointDCT.TransformIDCT_2x2(ref srcBlock, ref dequantMatrix, NormalizationValue, MaxOutputValue); - float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + Span expectedSpan = expectedDest.AsSpan(); + Span actualSpan = srcBlock.ToArray().AsSpan(); - Assert.Equal((int)actual, (int)expected); + // resulting matrix is 2x2 + for (int y = 0; y < 2; y++) + { + for (int x = 0; x < 2; x++) + { + AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x)); + } } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void TransformFDCT(int seed) + static void AssertScaledElementEquality(Span expected, Span actual) { - static void RunTest(string serialized) + float average4x4 = 0f; + for (int y = 0; y < 4; y++) { - int seed = FeatureTestRunner.Deserialize(serialized); + int y8 = y * 8; + for (int x = 0; x < 4; x++) + { + float clamped = Numerics.Clamp(expected[y8 + x] + NormalizationValue, 0, MaxOutputValue); + average4x4 += clamped; + } + } + + average4x4 = MathF.Round(average4x4 / 16f); + + Assert.Equal((int)average4x4, (int)actual[0]); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TranformIDCT_1x1(int seed) + { + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed, 1, 1); + var srcBlock = default(Block8x8F); + srcBlock.LoadFrom(src); + + float[] expectedDest = new float[64]; + float[] temp = new float[64]; + + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); + + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); + + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + ScaledFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + + // testee + // IDCT implementation tranforms blocks after transposition + // But DC lays on main diagonal which is not changed by transposition + float actual = ScaledFloatingPointDCT.TransformIDCT_1x1( + srcBlock[0], + dequantMatrix[0], + NormalizationValue, + MaxOutputValue); + + float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + + Assert.Equal((int)actual, (int)expected); + } - Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); - var block = default(Block8x8F); - block.LoadFrom(src); + [Theory] + [InlineData(1)] + [InlineData(2)] + public void TransformFDCT(int seed) + { + static void RunTest(string serialized) + { + int seed = FeatureTestRunner.Deserialize(serialized); - float[] expectedDest = new float[64]; - float[] temp1 = new float[64]; + Span src = Create8x8RandomFloatData(MinInputValue, MaxInputValue, seed); + var block = default(Block8x8F); + block.LoadFrom(src); - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + float[] expectedDest = new float[64]; + float[] temp1 = new float[64]; - // testee - // Second transpose call is done by Quantize step - // Do this manually here just to be complient to the reference implementation - FloatingPointDCT.TransformFDCT(ref block); - block.TransposeInplace(); + // reference + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); - // Part of the IDCT calculations is fused into the quantization step - // We must multiply input block with adjusted no-quantization matrix - // after applying FDCT - Block8x8F quantMatrix = CreateBlockFromScalar(1); - FloatingPointDCT.AdjustToFDCT(ref quantMatrix); - block.MultiplyInPlace(ref quantMatrix); + // testee + // Second transpose call is done by Quantize step + // Do this manually here just to be complient to the reference implementation + FloatingPointDCT.TransformFDCT(ref block); + block.TransposeInplace(); - float[] actualDest = block.ToArray(); + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // after applying FDCT + Block8x8F quantMatrix = CreateBlockFromScalar(1); + FloatingPointDCT.AdjustToFDCT(ref quantMatrix); + block.MultiplyInPlace(ref quantMatrix); - Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f)); - } + float[] actualDest = block.ToArray(); - // 4 paths: - // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call Vector4 implementation - // 4. DisableHWIntrinsic - call scalar fallback implementation - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f)); } + + // 4 paths: + // 1. AllowAll - call avx/fma implementation + // 2. DisableFMA - call avx without fma implementation + // 3. DisableAvx - call Vector4 implementation + // 4. DisableHWIntrinsic - call scalar fallback implementation + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs index c6a1ed96ee..b89d9ad27a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -1,89 +1,84 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class HuffmanScanEncoderTests { - [Trait("Format", "Jpg")] - public class HuffmanScanEncoderTests + private ITestOutputHelper Output { get; } + + public HuffmanScanEncoderTests(ITestOutputHelper output) { - private ITestOutputHelper Output { get; } + this.Output = output; + } - public HuffmanScanEncoderTests(ITestOutputHelper output) + private static int GetHuffmanEncodingLength_Reference(uint number) + { + int bits = 0; + if (number > 32767) { - this.Output = output; + number >>= 16; + bits += 16; } - private static int GetHuffmanEncodingLength_Reference(uint number) + if (number > 127) { - int bits = 0; - if (number > 32767) - { - number >>= 16; - bits += 16; - } - - if (number > 127) - { - number >>= 8; - bits += 8; - } - - if (number > 7) - { - number >>= 4; - bits += 4; - } - - if (number > 1) - { - number >>= 2; - bits += 2; - } - - if (number > 0) - { - bits++; - } - - return bits; + number >>= 8; + bits += 8; } - [Fact] - public void GetHuffmanEncodingLength_Zero() + if (number > 7) { - int expected = 0; - - int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); + number >>= 4; + bits += 4; + } - Assert.Equal(expected, actual); + if (number > 1) + { + number >>= 2; + bits += 2; } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void GetHuffmanEncodingLength_Random(int seed) + if (number > 0) { - int maxNumber = 1 << 16; + bits++; + } + + return bits; + } + + [Fact] + public void GetHuffmanEncodingLength_Zero() + { + int expected = 0; + + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(0); + + Assert.Equal(expected, actual); + } - var rng = new Random(seed); - for (int i = 0; i < 1000; i++) - { - uint number = (uint)rng.Next(0, maxNumber); + [Theory] + [InlineData(1)] + [InlineData(2)] + public void GetHuffmanEncodingLength_Random(int seed) + { + int maxNumber = 1 << 16; - int expected = GetHuffmanEncodingLength_Reference(number); + var rng = new Random(seed); + for (int i = 0; i < 1000; i++) + { + uint number = (uint)rng.Next(0, maxNumber); - int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); + int expected = GetHuffmanEncodingLength_Reference(number); - Assert.Equal(expected, actual); - } + int actual = HuffmanScanEncoder.GetHuffmanEncodingLength(number); + + Assert.Equal(expected, actual); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs index 5a48759fbd..0e799612da 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ImageExtensionsTest.cs @@ -1,156 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class ImageExtensionsTest { - [Trait("Format", "Jpg")] - public class ImageExtensionsTest + [Fact] + public void SaveAsJpeg_Path() { - [Fact] - public void SaveAsJpeg_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg"); - - using (var image = new Image(10, 10)) - { - image.SaveAsJpeg(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsJpeg_Path.jpg"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(file); } - [Fact] - public async Task SaveAsJpegAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg"); + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsJpegAsync(file); - } + [Fact] + public async Task SaveAsJpegAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsJpegAsync_Path.jpg"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(file); } - [Fact] - public void SaveAsJpeg_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg"); + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsJpeg(file, new JpegEncoder()); - } + [Fact] + public void SaveAsJpeg_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsJpeg_Path_Encoder.jpg"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(file, new JpegEncoder()); } - [Fact] - public async Task SaveAsJpegAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg"); + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsJpegAsync(file, new JpegEncoder()); - } + [Fact] + public async Task SaveAsJpegAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsJpegAsync_Path_Encoder.jpg"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(file, new JpegEncoder()); } - [Fact] - public void SaveAsJpeg_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsJpeg(memoryStream); - } + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsJpeg_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(memoryStream); } - [Fact] - public async Task SaveAsJpegAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsJpegAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsJpegAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(memoryStream); } - [Fact] - public void SaveAsJpeg_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsJpeg(memoryStream, new JpegEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsJpeg_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsJpeg(memoryStream, new JpegEncoder()); } - [Fact] - public async Task SaveAsJpegAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/jpeg", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsJpegAsync(memoryStream, new JpegEncoder()); - } + [Fact] + public async Task SaveAsJpegAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsJpegAsync(memoryStream, new JpegEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/jpeg", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/jpeg", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs index d688d6ce1e..3890c20e93 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JFifMarkerTests.cs @@ -3,93 +3,91 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Metadata; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class JFifMarkerTests { - [Trait("Format", "Jpg")] - public class JFifMarkerTests + // Taken from actual test image + private readonly byte[] bytes = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x60, 0x0, 0x60, 0x0 }; + + // Altered components + private readonly byte[] bytes2 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x48, 0x0, 0x48, 0x0 }; + + // Incorrect density values. Zero is invalid. + private readonly byte[] bytes3 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + [Fact] + public void MarkerLengthIsCorrect() + { + Assert.Equal(13, JFifMarker.Length); + } + + [Fact] + public void MarkerReturnsCorrectParsedValue() + { + bool isJFif = JFifMarker.TryParse(this.bytes, out JFifMarker marker); + + Assert.True(isJFif); + Assert.Equal(1, marker.MajorVersion); + Assert.Equal(1, marker.MinorVersion); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); + Assert.Equal(96, marker.XDensity); + Assert.Equal(96, marker.YDensity); + } + + [Fact] + public void MarkerIgnoresIncorrectValue() + { + bool isJFif = JFifMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out JFifMarker marker); + + Assert.False(isJFif); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerIgnoresCorrectHeaderButInvalidDensities() { - // Taken from actual test image - private readonly byte[] bytes = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x60, 0x0, 0x60, 0x0 }; - - // Altered components - private readonly byte[] bytes2 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x48, 0x0, 0x48, 0x0 }; - - // Incorrect density values. Zero is invalid. - private readonly byte[] bytes3 = { 0x4A, 0x46, 0x49, 0x46, 0x0, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0 }; - - [Fact] - public void MarkerLengthIsCorrect() - { - Assert.Equal(13, JFifMarker.Length); - } - - [Fact] - public void MarkerReturnsCorrectParsedValue() - { - bool isJFif = JFifMarker.TryParse(this.bytes, out JFifMarker marker); - - Assert.True(isJFif); - Assert.Equal(1, marker.MajorVersion); - Assert.Equal(1, marker.MinorVersion); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, marker.DensityUnits); - Assert.Equal(96, marker.XDensity); - Assert.Equal(96, marker.YDensity); - } - - [Fact] - public void MarkerIgnoresIncorrectValue() - { - bool isJFif = JFifMarker.TryParse(new byte[] { 0, 0, 0, 0 }, out JFifMarker marker); - - Assert.False(isJFif); - Assert.Equal(default, marker); - } - - [Fact] - public void MarkerIgnoresCorrectHeaderButInvalidDensities() - { - bool isJFif = JFifMarker.TryParse(this.bytes3, out JFifMarker marker); - - Assert.False(isJFif); - Assert.Equal(default, marker); - } - - [Fact] - public void MarkerEqualityIsCorrect() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes, out JFifMarker marker2); - - Assert.True(marker.Equals(marker2)); - } - - [Fact] - public void MarkerInEqualityIsCorrect() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); - - Assert.False(marker.Equals(marker2)); - } - - [Fact] - public void MarkerHashCodeIsReplicable() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes, out JFifMarker marker2); - - Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); - } - - [Fact] - public void MarkerHashCodeIsUnique() - { - JFifMarker.TryParse(this.bytes, out JFifMarker marker); - JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); - - Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); - } + bool isJFif = JFifMarker.TryParse(this.bytes3, out JFifMarker marker); + + Assert.False(isJFif); + Assert.Equal(default, marker); + } + + [Fact] + public void MarkerEqualityIsCorrect() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes, out JFifMarker marker2); + + Assert.True(marker.Equals(marker2)); + } + + [Fact] + public void MarkerInEqualityIsCorrect() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); + + Assert.False(marker.Equals(marker2)); + } + + [Fact] + public void MarkerHashCodeIsReplicable() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes, out JFifMarker marker2); + + Assert.True(marker.GetHashCode().Equals(marker2.GetHashCode())); + } + + [Fact] + public void MarkerHashCodeIsUnique() + { + JFifMarker.TryParse(this.bytes, out JFifMarker marker); + JFifMarker.TryParse(this.bytes2, out JFifMarker marker2); + + Assert.False(marker.GetHashCode().Equals(marker2.GetHashCode())); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index deeff0992a..a939f1b687 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -1,437 +1,434 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class JpegColorConverterTests { - [Trait("Format", "Jpg")] - public class JpegColorConverterTests - { - private const float MaxColorChannelValue = 255f; + private const float MaxColorChannelValue = 255f; - private const float Precision = 0.1F / 255; + private const float Precision = 0.1F / 255; - private const int TestBufferLength = 40; + private const int TestBufferLength = 40; - private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; + private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); - private static readonly ColorSpaceConverter ColorSpaceConverter = new(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new(); - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; + public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - public JpegColorConverterTests(ITestOutputHelper output) - => this.Output = output; + public JpegColorConverterTests(ITestOutputHelper output) + => this.Output = output; - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - [Fact] - public void GetConverterThrowsExceptionOnInvalidColorSpace() - { - var invalidColorSpace = (JpegColorSpace)(-1); - Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); - } + [Fact] + public void GetConverterThrowsExceptionOnInvalidColorSpace() + { + var invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); + } - [Fact] - public void GetConverterThrowsExceptionOnInvalidPrecision() - { - // Valid precisions: 8 & 12 bit - int invalidPrecision = 9; - Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); - } + [Fact] + public void GetConverterThrowsExceptionOnInvalidPrecision() + { + // Valid precisions: 8 & 12 bit + int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); + } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 8)] - [InlineData(JpegColorSpace.Grayscale, 12)] - [InlineData(JpegColorSpace.Ycck, 8)] - [InlineData(JpegColorSpace.Ycck, 12)] - [InlineData(JpegColorSpace.Cmyk, 8)] - [InlineData(JpegColorSpace.Cmyk, 12)] - [InlineData(JpegColorSpace.RGB, 8)] - [InlineData(JpegColorSpace.RGB, 12)] - [InlineData(JpegColorSpace.YCbCr, 8)] - [InlineData(JpegColorSpace.YCbCr, 12)] - internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) - { - var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); + [Theory] + [InlineData(JpegColorSpace.Grayscale, 8)] + [InlineData(JpegColorSpace.Grayscale, 12)] + [InlineData(JpegColorSpace.Ycck, 8)] + [InlineData(JpegColorSpace.Ycck, 12)] + [InlineData(JpegColorSpace.Cmyk, 8)] + [InlineData(JpegColorSpace.Cmyk, 12)] + [InlineData(JpegColorSpace.RGB, 8)] + [InlineData(JpegColorSpace.RGB, 12)] + [InlineData(JpegColorSpace.YCbCr, 8)] + [InlineData(JpegColorSpace.YCbCr, 12)] + internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) + { + var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - Assert.NotNull(converter); - Assert.True(converter.IsAvailable); - Assert.Equal(colorSpace, converter.ColorSpace); - Assert.Equal(precision, converter.Precision); - } + Assert.NotNull(converter); + Assert.True(converter.IsAvailable); + Assert.Equal(colorSpace, converter.ColorSpace); + Assert.Equal(precision, converter.Precision); + } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 1)] - [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.Cmyk, 4)] - [InlineData(JpegColorSpace.RGB, 3)] - [InlineData(JpegColorSpace.YCbCr, 3)] - internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) - { - var converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - ValidateConversion( - converter, - componentCount, - 1); - } + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1)] + [InlineData(JpegColorSpace.Ycck, 4)] + [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.RGB, 3)] + [InlineData(JpegColorSpace.YCbCr, 3)] + internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) + { + var converter = JpegColorConverterBase.GetConverter(colorSpace, 8); + ValidateConversion( + converter, + componentCount, + 1); + } + + [Theory] + [MemberData(nameof(Seeds))] + public void FromYCbCrBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); + [Theory] + [MemberData(nameof(Seeds))] + public void FromYCbCrVector(int seed) + { + var converter = new JpegColorConverterBase.YCbCrVector(8); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector(int seed) + if (!converter.IsAvailable) { - var converter = new JpegColorConverterBase.YCbCrVector(8); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - IntrinsicsConfig); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.YCbCrVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); + } - static void RunTest(string arg) => - ValidateConversion( - new JpegColorConverterBase.YCbCrVector(8), - 3, - FeatureTestRunner.Deserialize(arg)); - } + [Theory] + [MemberData(nameof(Seeds))] + public void FromCmykBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed); - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.CmykScalar(8), 4, seed); + [Theory] + [MemberData(nameof(Seeds))] + public void FromCmykVector(int seed) + { + var converter = new JpegColorConverterBase.CmykVector(8); - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector(int seed) + if (!converter.IsAvailable) { - var converter = new JpegColorConverterBase.CmykVector(8); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - IntrinsicsConfig); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.CmykVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); + } - static void RunTest(string arg) => - ValidateConversion( - new JpegColorConverterBase.CmykVector(8), - 4, - FeatureTestRunner.Deserialize(arg)); - } + [Theory] + [MemberData(nameof(Seeds))] + public void FromGrayscaleBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed); - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayscaleBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.GrayscaleScalar(8), 1, seed); + [Theory] + [MemberData(nameof(Seeds))] + public void FromGrayscaleVector(int seed) + { + var converter = new JpegColorConverterBase.GrayScaleVector(8); - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayscaleVector(int seed) + if (!converter.IsAvailable) { - var converter = new JpegColorConverterBase.GrayScaleVector(8); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - IntrinsicsConfig); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.GrayScaleVector(8), + 1, + FeatureTestRunner.Deserialize(arg)); + } - static void RunTest(string arg) => - ValidateConversion( - new JpegColorConverterBase.GrayScaleVector(8), - 1, - FeatureTestRunner.Deserialize(arg)); - } + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed); - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.RgbScalar(8), 3, seed); + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbVector(int seed) + { + var converter = new JpegColorConverterBase.RgbVector(8); - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector(int seed) + if (!converter.IsAvailable) { - var converter = new JpegColorConverterBase.RgbVector(8); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - IntrinsicsConfig); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.RgbVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); + } - static void RunTest(string arg) => - ValidateConversion( - new JpegColorConverterBase.RgbVector(8), - 3, - FeatureTestRunner.Deserialize(arg)); - } + [Theory] + [MemberData(nameof(Seeds))] + public void FromYccKBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) => - this.TestConverter(new JpegColorConverterBase.YccKScalar(8), 4, seed); + [Theory] + [MemberData(nameof(Seeds))] + public void FromYccKVector(int seed) + { + var converter = new JpegColorConverterBase.YccKVector(8); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector(int seed) + if (!converter.IsAvailable) { - var converter = new JpegColorConverterBase.YccKVector(8); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + IntrinsicsConfig); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - seed, - IntrinsicsConfig); + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.YccKVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); + } - static void RunTest(string arg) => - ValidateConversion( - new JpegColorConverterBase.YccKVector(8), - 4, - FeatureTestRunner.Deserialize(arg)); + [Theory] + [MemberData(nameof(Seeds))] + public void FromYCbCrAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromCmykAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromGrayscaleAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromYccKAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed); + + private void TestConverter( + JpegColorConverterBase converter, + int componentCount, + int seed) + { + if (!converter.IsAvailable) + { + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; } - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.YCbCrAvx(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.CmykAvx(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayscaleAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.GrayscaleAvx(8), 1, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.RgbAvx(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.YccKAvx(8), 4, seed); - - private void TestConverter( - JpegColorConverterBase converter, - int componentCount, - int seed) - { - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } + ValidateConversion( + converter, + componentCount, + seed); + } - ValidateConversion( - converter, - componentCount, - seed); - } + private static JpegColorConverterBase.ComponentValues CreateRandomValues( + int length, + int componentCount, + int seed) + { + var rnd = new Random(seed); - private static JpegColorConverterBase.ComponentValues CreateRandomValues( - int length, - int componentCount, - int seed) + var buffers = new Buffer2D[componentCount]; + for (int i = 0; i < componentCount; i++) { - var rnd = new Random(seed); + float[] values = new float[length]; - var buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) + for (int j = 0; j < values.Length; j++) { - float[] values = new float[length]; - - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } - - // no need to dispose when buffer is not array owner - var memory = new Memory(values); - var source = MemoryGroup.Wrap(memory); - buffers[i] = new Buffer2D(source, values.Length, 1); + values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; } - return new JpegColorConverterBase.ComponentValues(buffers, 0); + // no need to dispose when buffer is not array owner + var memory = new Memory(values); + var source = MemoryGroup.Wrap(memory); + buffers[i] = new Buffer2D(source, values.Length, 1); } - private static void ValidateConversion( - JpegColorConverterBase converter, - int componentCount, - int seed) - { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues values = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); + return new JpegColorConverterBase.ComponentValues(buffers, 0); + } - converter.ConvertToRgbInplace(values); + private static void ValidateConversion( + JpegColorConverterBase converter, + int componentCount, + int seed) + { + JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); + JpegColorConverterBase.ComponentValues values = new( + original.ComponentCount, + original.Component0.ToArray(), + original.Component1.ToArray(), + original.Component2.ToArray(), + original.Component3.ToArray()); - for (int i = 0; i < TestBufferLength; i++) - { - Validate(converter.ColorSpace, original, values, i); - } - } + converter.ConvertToRgbInplace(values); - private static void Validate( - JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues original, - in JpegColorConverterBase.ComponentValues result, - int i) + for (int i = 0; i < TestBufferLength; i++) { - switch (colorSpace) - { - case JpegColorSpace.Grayscale: - ValidateGrayScale(original, result, i); - break; - case JpegColorSpace.Ycck: - ValidateCyyK(original, result, i); - break; - case JpegColorSpace.Cmyk: - ValidateCmyk(original, result, i); - break; - case JpegColorSpace.RGB: - ValidateRgb(original, result, i); - break; - case JpegColorSpace.YCbCr: - ValidateYCbCr(original, result, i); - break; - default: - Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); - break; - } + Validate(converter.ColorSpace, original, values, i); } + } - private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + private static void Validate( + JpegColorSpace colorSpace, + in JpegColorConverterBase.ComponentValues original, + in JpegColorConverterBase.ComponentValues result, + int i) + { + switch (colorSpace) { - float y = values.Component0[i]; - float cb = values.Component1[i]; - float cr = values.Component2[i]; - var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr)); + case JpegColorSpace.Grayscale: + ValidateGrayScale(original, result, i); + break; + case JpegColorSpace.Ycck: + ValidateCyyK(original, result, i); + break; + case JpegColorSpace.Cmyk: + ValidateCmyk(original, result, i); + break; + case JpegColorSpace.RGB: + ValidateRgb(original, result, i); + break; + case JpegColorSpace.YCbCr: + ValidateYCbCr(original, result, i); + break; + default: + Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); + break; + } + } - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + { + float y = values.Component0[i]; + float cb = values.Component1[i]; + float cr = values.Component2[i]; + var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr)); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round( - y - (0.344136F * cb) - (0.714136F * cr), - MidpointRounding.AwayFromZero)) * k; - float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - var expected = new Rgb(r, g, b); - - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + } - private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float r = values.Component0[i] / MaxColorChannelValue; - float g = values.Component1[i] / MaxColorChannelValue; - float b = values.Component2[i] / MaxColorChannelValue; - var expected = new Rgb(r, g, b); + private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + { + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + float g = (255F - (float)Math.Round( + y - (0.344136F * cb) - (0.714136F * cr), + MidpointRounding.AwayFromZero)) * k; + float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + + r /= MaxColorChannelValue; + g /= MaxColorChannelValue; + b /= MaxColorChannelValue; + var expected = new Rgb(r, g, b); + + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + } - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + { + float r = values.Component0[i] / MaxColorChannelValue; + float g = values.Component1[i] / MaxColorChannelValue; + float b = values.Component2[i] / MaxColorChannelValue; + var expected = new Rgb(r, g, b); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i] / MaxColorChannelValue; - var expected = new Rgb(y, y, y); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + } + + private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + { + float y = values.Component0[i] / MaxColorChannelValue; + var expected = new Rgb(y, y, y); - var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); + var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + } - private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / MaxColorChannelValue; + private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + { + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / MaxColorChannelValue; - float r = c * k / MaxColorChannelValue; - float g = m * k / MaxColorChannelValue; - float b = y * k / MaxColorChannelValue; - var expected = new Rgb(r, g, b); + float r = c * k / MaxColorChannelValue; + float g = m * k / MaxColorChannelValue; + float b = y * k / MaxColorChannelValue; + var expected = new Rgb(r, g, b); - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 77b93081d2..f8a562c4c8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -4,68 +4,66 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class JpegDecoderTests { - [Trait("Format", "Jpg")] - public partial class JpegDecoderTests + [Theory] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgb24, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, true)] + [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgb24, true)] + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgb24, false)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24, true)] - [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgb24, true)] - public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) - where TPixel : unmanaged, IPixel + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - - if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) - { - provider.LimitAllocatorBufferCapacity().InPixels(16_000); - } - - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput( - GetImageComparer(provider), - provider, - appendPixelTypeToFileName: false); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixels(16_000); } - string providerDump = BasicSerializer.Serialize(provider); - RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); - // RemoteExecutor.Invoke( - // RunTest, - // providerDump, - // enforceDiscontiguousBuffers ? "Disco" : string.Empty) - // .Dispose(); + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + GetImageComparer(provider), + provider, + appendPixelTypeToFileName: false); } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding01, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding02, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingGray, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingInterleaved, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingWithRestart, PixelTypes.Rgb24)] - public void DecodeJpeg_WithArithmeticCoding(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Tolerant(0.002f), ReferenceDecoder); - } + string providerDump = BasicSerializer.Serialize(provider); + RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); + + // RemoteExecutor.Invoke( + // RunTest, + // providerDump, + // enforceDiscontiguousBuffers ? "Disco" : string.Empty) + // .Dispose(); + } - [Theory] - [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] - public void UnrecoverableImage_Throws_InvalidImageContentException(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage); + [Theory] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding01, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding02, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingGray, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingInterleaved, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingWithRestart, PixelTypes.Rgb24)] + public void DecodeJpeg_WithArithmeticCoding(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Tolerant(0.002f), ReferenceDecoder); } + + [Theory] + [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] + public void UnrecoverableImage_Throws_InvalidImageContentException(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 b9a31d0137..85fad3f826 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -1,130 +1,126 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public partial class JpegDecoderTests { - [Trait("Format", "Jpg")] - public partial class JpegDecoderTests + public static string[] BaselineTestJpegs = { - public static string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Ycck, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Turtle420, - TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, - TestImages.Jpeg.Baseline.Jpeg444, - TestImages.Jpeg.Baseline.Jpeg422, - TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, - TestImages.Jpeg.Baseline.YcckSubsample1222, - TestImages.Jpeg.Baseline.Bad.BadRST, - TestImages.Jpeg.Issues.MultiHuffmanBaseline394, - TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, - TestImages.Jpeg.Issues.InvalidEOI695, - TestImages.Jpeg.Issues.ExifResizeOutOfRange696, - TestImages.Jpeg.Issues.InvalidAPP0721, - TestImages.Jpeg.Issues.ExifGetString750Load, - TestImages.Jpeg.Issues.ExifGetString750Transform, - TestImages.Jpeg.Issues.BadSubSampling1076, + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, + TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, + TestImages.Jpeg.Baseline.Jpeg444, + TestImages.Jpeg.Baseline.Jpeg422, + TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, + TestImages.Jpeg.Baseline.YcckSubsample1222, + TestImages.Jpeg.Baseline.Bad.BadRST, + TestImages.Jpeg.Issues.MultiHuffmanBaseline394, + TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696, + TestImages.Jpeg.Issues.InvalidAPP0721, + TestImages.Jpeg.Issues.ExifGetString750Load, + TestImages.Jpeg.Issues.ExifGetString750Transform, + TestImages.Jpeg.Issues.BadSubSampling1076, - // LibJpeg can open this despite the invalid density units. - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, + // LibJpeg can open this despite the invalid density units. + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, - // LibJpeg can open this despite incorrect colorspace metadata. - TestImages.Jpeg.Issues.IncorrectColorspace855, + // LibJpeg can open this despite incorrect colorspace metadata. + TestImages.Jpeg.Issues.IncorrectColorspace855, - // High depth images - TestImages.Jpeg.Baseline.Testorig12bit, + // High depth images + TestImages.Jpeg.Baseline.Testorig12bit, - // Grayscale jpeg with 2x2 sampling factors (not a usual thing to encounter in the wild) - TestImages.Jpeg.Baseline.GrayscaleSampling2x2, - }; + // Grayscale jpeg with 2x2 sampling factors (not a usual thing to encounter in the wild) + TestImages.Jpeg.Baseline.GrayscaleSampling2x2, + }; - public static string[] ProgressiveTestJpegs = - { - TestImages.Jpeg.Progressive.Fb, - TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, - TestImages.Jpeg.Progressive.Bad.BadEOF, - TestImages.Jpeg.Issues.BadCoeffsProgressive178, - TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, - TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, - TestImages.Jpeg.Issues.BadZigZagProgressive385, - TestImages.Jpeg.Progressive.Bad.ExifUndefType, - TestImages.Jpeg.Issues.NoEoiProgressive517, - TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.DhtHasWrongLength624, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C - }; + public static string[] ProgressiveTestJpegs = + { + TestImages.Jpeg.Progressive.Fb, + TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, + TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Issues.BadCoeffsProgressive178, + TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, + TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, + TestImages.Jpeg.Issues.NoEoiProgressive517, + TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.DhtHasWrongLength624, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C + }; - public static string[] UnsupportedTestJpegs = - { - // Invalid componentCount value (2 or > 4) - TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, - TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, + public static string[] UnsupportedTestJpegs = + { + // Invalid componentCount value (2 or > 4) + TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, + TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, - // Lossless jpeg - TestImages.Jpeg.Baseline.Lossless - }; + // Lossless jpeg + TestImages.Jpeg.Baseline.Lossless + }; - public static string[] UnrecoverableTestJpegs = - { - TestImages.Jpeg.Issues.CriticalEOF214, - TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, - TestImages.Jpeg.Issues.Fuzz.DivideByZeroException821, - TestImages.Jpeg.Issues.Fuzz.DivideByZeroException822, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824G, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824H, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825A, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825C, - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825D, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, - TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, - TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, - TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085, - }; + public static string[] UnrecoverableTestJpegs = + { + TestImages.Jpeg.Issues.CriticalEOF214, + TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, + TestImages.Jpeg.Issues.Fuzz.DivideByZeroException821, + TestImages.Jpeg.Issues.Fuzz.DivideByZeroException822, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824E, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824F, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824G, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824H, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825A, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825C, + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825D, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826A, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826B, + TestImages.Jpeg.Issues.Fuzz.ArgumentException826C, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, + TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, + TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085, + }; - private static readonly Dictionary CustomToleranceValues = new() - { - // Baseline: - [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, - [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + private static readonly Dictionary CustomToleranceValues = new() + { + // Baseline: + [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, + [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, - [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, - [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, - // Progressive: - [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, - [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, - [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, - [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, - [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, - [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, - [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, - [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, - }; - } + // Progressive: + [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, + [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, + [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, + [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, + [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, + [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, + [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, + [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, + }; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs index 564e191bbd..6cf6250cb1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Internal.cs @@ -1,60 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class JpegDecoderTests { - [Trait("Format", "Jpg")] - public partial class JpegDecoderTests + [Theory] + [InlineData(1, 0, JpegColorSpace.Grayscale)] + [InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] + [InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] + [InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] + [InlineData(4, JpegConstants.Adobe.ColorTransformYcck, JpegColorSpace.Ycck)] + internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte componentCount, byte adobeFlag, JpegColorSpace expectedColorSpace) + { + byte[] adobeMarkerPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, adobeFlag }; + ProfileResolver.AdobeMarker.CopyTo(adobeMarkerPayload); + _ = AdobeMarker.TryParse(adobeMarkerPayload, out AdobeMarker adobeMarker); + + JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker); + + Assert.Equal(expectedColorSpace, actualColorSpace); + } + + [Theory] + [InlineData(2)] + [InlineData(5)] + public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) { - [Theory] - [InlineData(1, 0, JpegColorSpace.Grayscale)] - [InlineData(3, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.RGB)] - [InlineData(3, JpegConstants.Adobe.ColorTransformYCbCr, JpegColorSpace.YCbCr)] - [InlineData(4, JpegConstants.Adobe.ColorTransformUnknown, JpegColorSpace.Cmyk)] - [InlineData(4, JpegConstants.Adobe.ColorTransformYcck, JpegColorSpace.Ycck)] - internal void DeduceJpegColorSpaceAdobeMarker_ShouldReturnValidColorSpace(byte componentCount, byte adobeFlag, JpegColorSpace expectedColorSpace) - { - byte[] adobeMarkerPayload = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, adobeFlag }; - ProfileResolver.AdobeMarker.CopyTo(adobeMarkerPayload); - _ = AdobeMarker.TryParse(adobeMarkerPayload, out AdobeMarker adobeMarker); - - JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker); - - Assert.Equal(expectedColorSpace, actualColorSpace); - } - - [Theory] - [InlineData(2)] - [InlineData(5)] - public void DeduceJpegColorSpaceAdobeMarker_ShouldThrowOnUnsupportedComponentCount(byte componentCount) - { - AdobeMarker adobeMarker = default; - - Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); - } - - [Theory] - [InlineData(1, JpegColorSpace.Grayscale)] - [InlineData(3, JpegColorSpace.YCbCr)] - [InlineData(4, JpegColorSpace.Cmyk)] - internal void DeduceJpegColorSpace_ShouldReturnValidColorSpace(byte componentCount, JpegColorSpace expectedColorSpace) - { - JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount); - - Assert.Equal(expectedColorSpace, actualColorSpace); - } - - [Theory] - [InlineData(2)] - [InlineData(5)] - public void DeduceJpegColorSpace_ShouldThrowOnUnsupportedComponentCount(byte componentCount) - => Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount)); + AdobeMarker adobeMarker = default; + + Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount, ref adobeMarker)); + } + + [Theory] + [InlineData(1, JpegColorSpace.Grayscale)] + [InlineData(3, JpegColorSpace.YCbCr)] + [InlineData(4, JpegColorSpace.Cmyk)] + internal void DeduceJpegColorSpace_ShouldReturnValidColorSpace(byte componentCount, JpegColorSpace expectedColorSpace) + { + JpegColorSpace actualColorSpace = JpegDecoderCore.DeduceJpegColorSpace(componentCount); + + Assert.Equal(expectedColorSpace, actualColorSpace); } + + [Theory] + [InlineData(2)] + [InlineData(5)] + public void DeduceJpegColorSpace_ShouldThrowOnUnsupportedComponentCount(byte componentCount) + => Assert.Throws(() => JpegDecoderCore.DeduceJpegColorSpace(componentCount)); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index f6d9ba4a3e..3603000f6c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; @@ -12,381 +9,378 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class JpegDecoderTests { - [Trait("Format", "Jpg")] - public partial class JpegDecoderTests + // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. + // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) + public static readonly TheoryData MetadataTestData = + new() { - // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. - // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) - public static readonly TheoryData MetadataTestData = - new() - { - { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, - { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, - { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, - { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { 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 }, - { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, - { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, - { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - { true, TestImages.Jpeg.Issues.IdentifyMultiFrame1211, 24, true, true }, - }; - - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Issues.MultipleApp01932, 400, 400, PixelResolutionUnit.PixelsPerInch } - }; - - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 }, - { TestImages.Jpeg.Issues.IncorrectQuality845, 98 }, - { TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, 89 }, - { TestImages.Jpeg.Progressive.Winter420_NonInterleaved, 80 } - }; - - [Theory] - [MemberData(nameof(MetadataTestData))] - public void MetadataIsParsedCorrectly( - bool useIdentify, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) => TestMetadataImpl( - useIdentify, - JpegDecoder, - imagePath, - expectedPixelSize, - exifProfilePresent, - iccProfilePresent); - - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new JpegDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, + { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, + { false, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, + { false, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { 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 }, + { true, TestImages.Jpeg.Baseline.Ycck, 32, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, + { true, TestImages.Jpeg.Baseline.Snake, 24, true, true }, + { true, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, + { true, TestImages.Jpeg.Issues.IdentifyMultiFrame1211, 24, true, true }, + }; + + public static readonly TheoryData RatioFiles = + new() + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Issues.MultipleApp01932, 400, 400, PixelResolutionUnit.PixelsPerInch } + }; + + public static readonly TheoryData QualityFiles = + new() + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 }, + { TestImages.Jpeg.Issues.IncorrectQuality845, 98 }, + { TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, 89 }, + { TestImages.Jpeg.Progressive.Winter420_NonInterleaved, 80 } + }; + + [Theory] + [MemberData(nameof(MetadataTestData))] + public void MetadataIsParsedCorrectly( + bool useIdentify, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) => TestMetadataImpl( + useIdentify, + JpegDecoder, + imagePath, + expectedPixelSize, + exifProfilePresent, + iccProfilePresent); + + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new JpegDecoder(); - IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = await decoder.IdentifyAsync(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - [Theory] - [MemberData(nameof(QualityFiles))] - public void Identify_VerifyQuality(string imagePath, int quality) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new JpegDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } + [Theory] + [MemberData(nameof(QualityFiles))] + public void Identify_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new JpegDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } - [Theory] - [MemberData(nameof(QualityFiles))] - public void Decode_VerifyQuality(string imagePath, int quality) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } + [Theory] + [MemberData(nameof(QualityFiles))] + public void Decode_VerifyQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + using Image image = JpegDecoder.Decode(DecoderOptions.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } - [Theory] - [MemberData(nameof(QualityFiles))] - public async Task Decode_VerifyQualityAsync(string imagePath, int quality) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } + [Theory] + [MemberData(nameof(QualityFiles))] + public async Task Decode_VerifyQualityAsync(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + using Image image = await JpegDecoder.DecodeAsync(DecoderOptions.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); + } - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)] - [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] - public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegEncodingColor.Luminance)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegEncodingColor.YCbCrRatio420)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegEncodingColor.YCbCrRatio444)] + [InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegEncodingColor.Rgb)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegEncodingColor.Cmyk)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegEncodingColor.YCbCrRatio410)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegEncodingColor.YCbCrRatio411)] + public void Identify_DetectsCorrectColorType(string imagePath, JpegEncodingColor expectedColorType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo image = JpegDecoder.Identify(DecoderOptions.Default, stream); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] + public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + JpegMetadata meta = image.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgb24, JpegEncodingColor.Cmyk)] - public void Decode_DetectsCorrectColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) - where TPixel : unmanaged, IPixel + private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + if (useIdentify) { - using Image image = provider.GetImage(JpegDecoder); - JpegMetadata meta = image.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); + IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default); + test(imageInfo); } - - private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) + else { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - if (useIdentify) - { - IImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream, default); - test(imageInfo); - } - else - { - using Image img = decoder.Decode(DecoderOptions.Default, stream, default); - test(img); - } + using Image img = decoder.Decode(DecoderOptions.Default, stream, default); + test(img); } + } + + private static void TestMetadataImpl( + bool useIdentify, + IImageDecoder decoder, + string imagePath, + int expectedPixelSize, + bool exifProfilePresent, + bool iccProfilePresent) => TestImageInfo( + imagePath, + decoder, + useIdentify, + imageInfo => + { + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.PixelType); - private static void TestMetadataImpl( - bool useIdentify, - IImageDecoder decoder, - string imagePath, - int expectedPixelSize, - bool exifProfilePresent, - bool iccProfilePresent) => TestImageInfo( - imagePath, - decoder, - useIdentify, - imageInfo => + if (useIdentify) { - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.PixelType); - - if (useIdentify) - { - Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); - } - else - { - // When full Image decoding is performed, BitsPerPixel will match TPixel - int bpp32 = Unsafe.SizeOf() * 8; - Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); - } - - ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; - - if (exifProfilePresent) - { - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - } - else - { - Assert.Null(exifProfile); - } - - IccProfile iccProfile = imageInfo.Metadata.IccProfile; - - if (iccProfilePresent) - { - Assert.NotNull(iccProfile); - Assert.NotEmpty(iccProfile.Entries); - } - else - { - Assert.Null(iccProfile); - } - }); - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) - { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); + } + else + { + // When full Image decoding is performed, BitsPerPixel will match TPixel + int bpp32 = Unsafe.SizeOf() * 8; + Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel); + } - // Snake.jpg has both Exif and ICC profiles defined: - var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); + ExifProfile exifProfile = imageInfo.Metadata.ExifProfile; - using Image image = testFile.CreateRgba32Image(JpegDecoder, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.ExifProfile); - Assert.NotNull(image.Metadata.IccProfile); - } - } + if (exifProfilePresent) + { + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + } + else + { + Assert.Null(exifProfile); + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) => TestImageInfo( - TestImages.Jpeg.Baseline.Floorplan, - JpegDecoder, - useIdentify, - imageInfo => - { - Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(300, imageInfo.Metadata.VerticalResolution); - }); + IccProfile iccProfile = imageInfo.Metadata.IccProfile; - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) => TestImageInfo( - TestImages.Jpeg.Baseline.Jpeg420Exif, - JpegDecoder, - useIdentify, - imageInfo => + if (iccProfilePresent) + { + Assert.NotNull(iccProfile); + Assert.NotEmpty(iccProfile.Entries); + } + else { - Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(72, imageInfo.Metadata.VerticalResolution); - }); - - [Theory] - [WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)] - public void Decode_WithInvalidIptcTag_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel + Assert.Null(iccProfile); + } + }); + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + + // Snake.jpg has both Exif and ICC profiles defined: + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Snake); + + using Image image = testFile.CreateRgba32Image(JpegDecoder, options); + if (ignoreMetadata) { - Exception ex = Record.Exception(() => - { - using Image image = provider.GetImage(JpegDecoder); - }); - Assert.Null(ex); + Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.IccProfile); } - - [Theory] - [WithFile(TestImages.Jpeg.Issues.ExifNullArrayTag, PixelTypes.Rgba32)] - public void Clone_WithNullRationalArrayTag_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel + else { - Exception ex = Record.Exception(() => + Assert.NotNull(image.Metadata.ExifProfile); + Assert.NotNull(image.Metadata.IccProfile); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) => TestImageInfo( + TestImages.Jpeg.Baseline.Floorplan, + JpegDecoder, + useIdentify, + imageInfo => { - using Image image = provider.GetImage(JpegDecoder); - ExifProfile clone = image.Metadata.ExifProfile.DeepClone(); + Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(300, imageInfo.Metadata.VerticalResolution); }); - Assert.Null(ex); - } - [Fact] - public void EncodedStringTags_WriteAndRead() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) => TestImageInfo( + TestImages.Jpeg.Baseline.Jpeg420Exif, + JpegDecoder, + useIdentify, + imageInfo => + { + Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(72, imageInfo.Metadata.VerticalResolution); + }); + + [Theory] + [WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)] + public void Decode_WithInvalidIptcTag_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => { - using var memoryStream = new MemoryStream(); - using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora))) - { - var exif = new ExifProfile(); + using Image image = provider.GetImage(JpegDecoder); + }); + Assert.Null(ex); + } + + [Theory] + [WithFile(TestImages.Jpeg.Issues.ExifNullArrayTag, PixelTypes.Rgba32)] + public void Clone_WithNullRationalArrayTag_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(JpegDecoder); + ExifProfile clone = image.Metadata.ExifProfile.DeepClone(); + }); + Assert.Null(ex); + } - exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06"); + [Fact] + public void EncodedStringTags_WriteAndRead() + { + using var memoryStream = new MemoryStream(); + using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora))) + { + var exif = new ExifProfile(); - exif.SetValue(ExifTag.XPTitle, "A bit of test metadata for image title"); - exif.SetValue(ExifTag.XPComment, "A bit of test metadata for image comment"); - exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); - exif.SetValue(ExifTag.XPKeywords, "Keyword1;Keyword2"); - exif.SetValue(ExifTag.XPSubject, "This is a subject"); + exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06"); - // exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "ビッ")); - exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "eng comment text (JIS)")); + exif.SetValue(ExifTag.XPTitle, "A bit of test metadata for image title"); + exif.SetValue(ExifTag.XPComment, "A bit of test metadata for image comment"); + exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); + exif.SetValue(ExifTag.XPKeywords, "Keyword1;Keyword2"); + exif.SetValue(ExifTag.XPSubject, "This is a subject"); - exif.SetValue(ExifTag.GPSProcessingMethod, new EncodedString(EncodedString.CharacterCode.ASCII, "GPS processing method (ASCII)")); - exif.SetValue(ExifTag.GPSAreaInformation, new EncodedString(EncodedString.CharacterCode.Unicode, "GPS area info (Unicode)")); + // exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "ビッ")); + exif.SetValue(ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.JIS, "eng comment text (JIS)")); - image.Metadata.ExifProfile = exif; + exif.SetValue(ExifTag.GPSProcessingMethod, new EncodedString(EncodedString.CharacterCode.ASCII, "GPS processing method (ASCII)")); + exif.SetValue(ExifTag.GPSAreaInformation, new EncodedString(EncodedString.CharacterCode.Unicode, "GPS area info (Unicode)")); - image.Save(memoryStream, new JpegEncoder()); - } + image.Metadata.ExifProfile = exif; - memoryStream.Seek(0, SeekOrigin.Begin); - using (var image = Image.Load(memoryStream)) - { - ExifProfile exif = image.Metadata.ExifProfile; - VerifyEncodedStrings(exif); - } + image.Save(memoryStream, new JpegEncoder()); } - [Fact] - public void EncodedStringTags_Read() + memoryStream.Seek(0, SeekOrigin.Begin); + using (var image = Image.Load(memoryStream)) { - using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)); ExifProfile exif = image.Metadata.ExifProfile; VerifyEncodedStrings(exif); } + } - private static void VerifyEncodedStrings(ExifProfile exif) - { - Assert.NotNull(exif); + [Fact] + public void EncodedStringTags_Read() + { + using var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora_EncodedStrings)); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyEncodedStrings(exif); + } - Assert.Equal("2022-01-06", exif.GetValue(ExifTag.GPSDateStamp).Value); + private static void VerifyEncodedStrings(ExifProfile exif) + { + Assert.NotNull(exif); - Assert.Equal("A bit of test metadata for image title", exif.GetValue(ExifTag.XPTitle).Value); - Assert.Equal("A bit of test metadata for image comment", exif.GetValue(ExifTag.XPComment).Value); - Assert.Equal("Dan Petitt", exif.GetValue(ExifTag.XPAuthor).Value); - Assert.Equal("Keyword1;Keyword2", exif.GetValue(ExifTag.XPKeywords).Value); - Assert.Equal("This is a subject", exif.GetValue(ExifTag.XPSubject).Value); + Assert.Equal("2022-01-06", exif.GetValue(ExifTag.GPSDateStamp).Value); - Assert.Equal("eng comment text (JIS)", exif.GetValue(ExifTag.UserComment).Value.Text); - Assert.Equal(EncodedString.CharacterCode.JIS, exif.GetValue(ExifTag.UserComment).Value.Code); + Assert.Equal("A bit of test metadata for image title", exif.GetValue(ExifTag.XPTitle).Value); + Assert.Equal("A bit of test metadata for image comment", exif.GetValue(ExifTag.XPComment).Value); + Assert.Equal("Dan Petitt", exif.GetValue(ExifTag.XPAuthor).Value); + Assert.Equal("Keyword1;Keyword2", exif.GetValue(ExifTag.XPKeywords).Value); + Assert.Equal("This is a subject", exif.GetValue(ExifTag.XPSubject).Value); - Assert.Equal("GPS processing method (ASCII)", exif.GetValue(ExifTag.GPSProcessingMethod).Value.Text); - Assert.Equal(EncodedString.CharacterCode.ASCII, exif.GetValue(ExifTag.GPSProcessingMethod).Value.Code); + Assert.Equal("eng comment text (JIS)", exif.GetValue(ExifTag.UserComment).Value.Text); + Assert.Equal(EncodedString.CharacterCode.JIS, exif.GetValue(ExifTag.UserComment).Value.Code); - Assert.Equal("GPS area info (Unicode)", (string)exif.GetValue(ExifTag.GPSAreaInformation).Value); - Assert.Equal(EncodedString.CharacterCode.Unicode, exif.GetValue(ExifTag.GPSAreaInformation).Value.Code); - } + Assert.Equal("GPS processing method (ASCII)", exif.GetValue(ExifTag.GPSProcessingMethod).Value.Text); + Assert.Equal(EncodedString.CharacterCode.ASCII, exif.GetValue(ExifTag.GPSProcessingMethod).Value.Code); + + Assert.Equal("GPS area info (Unicode)", (string)exif.GetValue(ExifTag.GPSAreaInformation).Value); + Assert.Equal(EncodedString.CharacterCode.Unicode, exif.GetValue(ExifTag.GPSAreaInformation).Value.Code); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 36c0cbab4d..8e07d9ac91 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -5,23 +5,54 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class JpegDecoderTests { - [Trait("Format", "Jpg")] - public partial class JpegDecoderTests + public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; + + [Theory] + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgb24)] + public void DecodeProgressiveJpeg(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + + provider.Utility.TestName = DecodeProgressiveJpegOutputName; + image.CompareToReferenceOutput( + GetImageComparer(provider), + provider, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive01, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive02, PixelTypes.Rgb24)] + public void DecodeProgressiveJpeg_WithArithmeticCoding(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Tolerant(0.004f), ReferenceDecoder); + } - [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgb24)] + public void DecodeProgressiveJpeg_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + TestImageProvider provider = + BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider, nonContiguousBuffersStr); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( @@ -30,44 +61,11 @@ public void DecodeProgressiveJpeg(TestImageProvider provider) appendPixelTypeToFileName: false); } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive01, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive02, PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg_WithArithmeticCoding(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Tolerant(0.004f), ReferenceDecoder); - } - - [Theory] - [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgb24)] - public void DecodeProgressiveJpeg_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) - { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - - provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + string providerDump = BasicSerializer.Serialize(provider); - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider, nonContiguousBuffersStr); - - provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput( - GetImageComparer(provider), - provider, - appendPixelTypeToFileName: false); - } - - string providerDump = BasicSerializer.Serialize(provider); - - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco").Dispose(); - } + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco").Dispose(); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 684716dcfb..deca7f69fa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -1,11 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; @@ -16,346 +11,344 @@ using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - // TODO: Scatter test cases into multiple test classes - [Trait("Format", "Jpg")] - [ValidateDisposedMemoryAllocations] - public partial class JpegDecoderTests - { - private static MagickReferenceDecoder ReferenceDecoder => new(); +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; +// TODO: Scatter test cases into multiple test classes +[Trait("Format", "Jpg")] +[ValidateDisposedMemoryAllocations] +public partial class JpegDecoderTests +{ + private static MagickReferenceDecoder ReferenceDecoder => new(); - private const float BaselineTolerance = 0.001F / 100; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; - private const float ProgressiveTolerance = 0.2F / 100; + private const float BaselineTolerance = 0.001F / 100; - private static ImageComparer GetImageComparer(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string file = provider.SourceFileOrDescription; + private const float ProgressiveTolerance = 0.2F / 100; - if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) - { - bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0; - tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; - } - - return ImageComparer.Tolerant(tolerance); - } + private static ImageComparer GetImageComparer(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + string file = provider.SourceFileOrDescription; - private static bool SkipTest(ITestImageProvider provider) + if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) { - string[] largeImagesToSkipOn32Bit = - { - TestImages.Jpeg.Baseline.Jpeg420Exif, - TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, - TestImages.Jpeg.Issues.BadZigZagProgressive385, - TestImages.Jpeg.Issues.NoEoiProgressive517, - TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.InvalidEOI695, - TestImages.Jpeg.Issues.ExifResizeOutOfRange696, - TestImages.Jpeg.Issues.ExifGetString750Transform - }; - - return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); + bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0; + tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; } - public JpegDecoderTests(ITestOutputHelper output) => this.Output = output; + return ImageComparer.Tolerant(tolerance); + } - private ITestOutputHelper Output { get; } + private static bool SkipTest(ITestImageProvider provider) + { + string[] largeImagesToSkipOn32Bit = + { + TestImages.Jpeg.Baseline.Jpeg420Exif, + TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Issues.NoEoiProgressive517, + TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696, + TestImages.Jpeg.Issues.ExifGetString750Transform + }; - private static JpegDecoder JpegDecoder => new(); + return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); + } - [Fact] - public void ParseStream_BasicPropertiesAreCorrect() - { - JpegDecoderOptions options = new(); - byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; - using var ms = new MemoryStream(bytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - using var decoder = new JpegDecoderCore(options); - using Image image = decoder.Decode(bufferedStream, cancellationToken: default); - - // I don't know why these numbers are different. All I know is that the decoder works - // and spectral data is exactly correct also. - // VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); - VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31); - } + public JpegDecoderTests(ITestOutputHelper output) => this.Output = output; - public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; + private ITestOutputHelper Output { get; } - [Fact] - public void Decode_NonGeneric_CreatesRgb24Image() - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using var image = Image.Load(file); - Assert.IsType>(image); - } + private static JpegDecoder JpegDecoder => new(); - [Fact] - public async Task DecodeAsync_NonGeneric_CreatesRgb24Image() - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using Image image = await Image.LoadAsync(file); - Assert.IsType>(image); - } + [Fact] + public void ParseStream_BasicPropertiesAreCorrect() + { + JpegDecoderOptions options = new(); + byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; + using var ms = new MemoryStream(bytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + using var decoder = new JpegDecoderCore(options); + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + + // I don't know why these numbers are different. All I know is that the decoder works + // and spectral data is exactly correct also. + // VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); + VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 44, 62, 22, 31, 22, 31); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] - public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - appendPixelTypeToFileName: false); - } + [Fact] + public void Decode_NonGeneric_CreatesRgb24Image() + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using var image = Image.Load(file); + Assert.IsType>(image); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; - using Image image = provider.GetImage(JpegDecoder, options); + [Fact] + public async Task DecodeAsync_NonGeneric_CreatesRgb24Image() + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using Image image = await Image.LoadAsync(file); + Assert.IsType>(image); + } - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] + public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + appendPixelTypeToFileName: false); + } - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + using Image image = provider.GetImage(JpegDecoder, options); - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Resize_Bicubic(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 }, - Sampler = KnownResamplers.Bicubic - }; - using Image image = provider.GetImage(JpegDecoder, options); + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Resize_Bicubic(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() + { + TargetSize = new() { Width = 150, Height = 150 }, + Sampler = KnownResamplers.Bicubic + }; + using Image image = provider.GetImage(JpegDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_IDCT_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_IDCT_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() { - DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() - { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.IdctOnly - }; + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.IdctOnly + }; - using Image image = provider.GetImage(JpegDecoder, specializedOptions); + using Image image = provider.GetImage(JpegDecoder, specializedOptions); - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_Scale_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_Scale_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() { - DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() - { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.ScaleOnly - }; + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.ScaleOnly + }; - using Image image = provider.GetImage(JpegDecoder, specializedOptions); + using Image image = provider.GetImage(JpegDecoder, specializedOptions); - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] - public void JpegDecoder_Decode_Specialized_Combined_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgb24)] + public void JpegDecoder_Decode_Specialized_Combined_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; + JpegDecoderOptions specializedOptions = new() { - DecoderOptions options = new() { TargetSize = new() { Width = 150, Height = 150 } }; - JpegDecoderOptions specializedOptions = new() - { - GeneralOptions = options, - ResizeMode = JpegDecoderResizeMode.Combined - }; + GeneralOptions = options, + ResizeMode = JpegDecoderResizeMode.Combined + }; - using Image image = provider.GetImage(JpegDecoder, specializedOptions); + using Image image = provider.GetImage(JpegDecoder, specializedOptions); - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Tolerant(BaselineTolerance), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); - this.Output.WriteLine(ex.Message); - Assert.IsType(ex.InnerException); - } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public void Decode_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public async Task DecodeAsync_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] - public async Task DecodeAsync_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Fact] + public async Task DecodeAsync_IsCancellable() + { + var cts = new CancellationTokenSource(); + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using var pausedStream = new PausedStream(file); + pausedStream.OnWaiting(_ => { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); - InvalidImageContentException ex = await Assert.ThrowsAsync(() => provider.GetImageAsync(JpegDecoder)); - this.Output.WriteLine(ex.Message); - Assert.IsType(ex.InnerException); - } + cts.Cancel(); + pausedStream.Release(); + }); - [Fact] - public async Task DecodeAsync_IsCancellable() + var configuration = Configuration.CreateDefaultInstance(); + configuration.FileSystem = new SingleStreamFileSystem(pausedStream); + DecoderOptions options = new() { - var cts = new CancellationTokenSource(); - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using var pausedStream = new PausedStream(file); - pausedStream.OnWaiting(_ => - { - cts.Cancel(); - pausedStream.Release(); - }); + Configuration = configuration + }; - var configuration = Configuration.CreateDefaultInstance(); - configuration.FileSystem = new SingleStreamFileSystem(pausedStream); - DecoderOptions options = new() - { - Configuration = configuration - }; + await Assert.ThrowsAsync(async () => + { + using Image image = await Image.LoadAsync(options, "someFakeFile", cts.Token); + }); + } - await Assert.ThrowsAsync(async () => - { - using Image image = await Image.LoadAsync(options, "someFakeFile", cts.Token); - }); - } + [Fact] + public async Task Identify_IsCancellable() + { + var cts = new CancellationTokenSource(); - [Fact] - public async Task Identify_IsCancellable() + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using var pausedStream = new PausedStream(file); + pausedStream.OnWaiting(_ => { - var cts = new CancellationTokenSource(); - - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); - using var pausedStream = new PausedStream(file); - pausedStream.OnWaiting(_ => - { - cts.Cancel(); - pausedStream.Release(); - }); + cts.Cancel(); + pausedStream.Release(); + }); - var configuration = Configuration.CreateDefaultInstance(); - configuration.FileSystem = new SingleStreamFileSystem(pausedStream); - DecoderOptions options = new() - { - Configuration = configuration - }; + var configuration = Configuration.CreateDefaultInstance(); + configuration.FileSystem = new SingleStreamFileSystem(pausedStream); + DecoderOptions options = new() + { + Configuration = configuration + }; - await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(options, "someFakeFile", cts.Token)); - } + await Assert.ThrowsAsync(async () => await Image.IdentifyAsync(options, "someFakeFile", cts.Token)); + } - [Theory] - [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] - public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => Assert.Throws(() => - { - using Image image = provider.GetImage(JpegDecoder); - }); - - // https://github.com/SixLabors/ImageSharp/pull/1732 - [Theory] - [WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)] - public void Issue1732_DecodesWithRgbColorSpace(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] + public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => Assert.Throws(() => { using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + }); - // https://github.com/SixLabors/ImageSharp/issues/2057 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)] - public void Issue2057_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // https://github.com/SixLabors/ImageSharp/pull/1732 + [Theory] + [WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)] + public void Issue1732_DecodesWithRgbColorSpace(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - // https://github.com/SixLabors/ImageSharp/issues/2133 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2133_DeduceColorSpace, PixelTypes.Rgba32)] - public void Issue2133_DeduceColorSpace(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // https://github.com/SixLabors/ImageSharp/issues/2057 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2057App1Parsing, PixelTypes.Rgba32)] + public void Issue2057_DecodeWorks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } - // https://github.com/SixLabors/ImageSharp/issues/2133 - [Theory] - [WithFile(TestImages.Jpeg.Issues.Issue2136_ScanMarkerExtraneousBytes, PixelTypes.Rgba32)] - public void Issue2136_DecodeWorks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider); - } + // https://github.com/SixLabors/ImageSharp/issues/2133 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2133_DeduceColorSpace, PixelTypes.Rgba32)] + public void Issue2133_DeduceColorSpace(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + // https://github.com/SixLabors/ImageSharp/issues/2133 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2136_ScanMarkerExtraneousBytes, PixelTypes.Rgba32)] + public void Issue2136_DecodeWorks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs index 23b2f749d8..7e8f13ed3d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -11,173 +8,170 @@ using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public partial class JpegEncoderTests { - [Trait("Format", "Jpg")] - public partial class JpegEncoderTests - { - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; - - public static readonly TheoryData QualityFiles = - new() - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; - - [Fact] - public void Encode_PreservesIptcProfile() + public static readonly TheoryData RatioFiles = + new() { - // arrange - using var input = new Image(1, 1); - var expectedProfile = new IptcProfile(); - expectedProfile.SetValue(IptcTag.Country, "ESPAÑA"); - expectedProfile.SetValue(IptcTag.City, "unit-test-city"); - input.Metadata.IptcProfile = expectedProfile; - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IptcProfile actual = output.Metadata.IptcProfile; - Assert.NotNull(actual); - IEnumerable values = expectedProfile.Values; - Assert.Equal(values, actual.Values); - } + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; - [Fact] - public void Encode_PreservesExifProfile() + public static readonly TheoryData QualityFiles = + new() { - // arrange - using var input = new Image(1, 1); - input.Metadata.ExifProfile = new ExifProfile(); - input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ExifProfile actual = output.Metadata.ExifProfile; - Assert.NotNull(actual); - IReadOnlyList values = input.Metadata.ExifProfile.Values; - Assert.Equal(values, actual.Values); - } + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; - [Fact] - public void Encode_PreservesIccProfile() - { - // arrange - using var input = new Image(1, 1); - input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); - - // act - using var memStream = new MemoryStream(); - input.Save(memStream, JpegEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - IccProfile actual = output.Metadata.IccProfile; - Assert.NotNull(actual); - IccProfile values = input.Metadata.IccProfile; - Assert.Equal(values.Entries, actual.Entries); - } + [Fact] + public void Encode_PreservesIptcProfile() + { + // arrange + using var input = new Image(1, 1); + var expectedProfile = new IptcProfile(); + expectedProfile.SetValue(IptcTag.Country, "ESPAÑA"); + expectedProfile.SetValue(IptcTag.City, "unit-test-city"); + input.Metadata.IptcProfile = expectedProfile; + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IptcProfile actual = output.Metadata.IptcProfile; + Assert.NotNull(actual); + IEnumerable values = expectedProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesExifProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.ExifProfile = new ExifProfile(); + input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile actual = output.Metadata.ExifProfile; + Assert.NotNull(actual); + IReadOnlyList values = input.Metadata.ExifProfile.Values; + Assert.Equal(values, actual.Values); + } + + [Fact] + public void Encode_PreservesIccProfile() + { + // arrange + using var input = new Image(1, 1); + input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); + + // act + using var memStream = new MemoryStream(); + input.Save(memStream, JpegEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + IccProfile actual = output.Metadata.IccProfile; + Assert.NotNull(actual); + IccProfile values = input.Metadata.IccProfile; + Assert.Equal(values.Entries, actual.Entries); + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] - public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)] + public void Encode_WithValidExifProfile_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => { - Exception ex = Record.Exception(() => - { - var encoder = new JpegEncoder(); - var stream = new MemoryStream(); + var encoder = new JpegEncoder(); + var stream = new MemoryStream(); - using Image image = provider.GetImage(JpegDecoder); - image.Save(stream, encoder); - }); + using Image image = provider.GetImage(JpegDecoder); + image.Save(stream, encoder); + }); - Assert.Null(ex); - } + Assert.Null(ex); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); } } } + } - [Theory] - [MemberData(nameof(QualityFiles))] - public void Encode_PreservesQuality(string imagePath, int quality) + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreservesQuality(string imagePath, int quality) + { + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) { - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, JpegEncoder); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, JpegEncoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(quality, meta.Quality); - } + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(quality, meta.Quality); } } } + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] - public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) - where TPixel : unmanaged, IPixel - { - // arrange - using Image input = provider.GetImage(JpegDecoder); - using var memoryStream = new MemoryStream(); - - // act - input.Save(memoryStream, JpegEncoder); - - // assert - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); - JpegMetadata meta = output.Metadata.GetJpegMetadata(); - Assert.Equal(expectedColorType, meta.ColorType); - } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgb24, JpegEncodingColor.Luminance)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgb24, JpegEncodingColor.Rgb)] + public void Encode_PreservesColorType(TestImageProvider provider, JpegEncodingColor expectedColorType) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(JpegDecoder); + using var memoryStream = new MemoryStream(); + + // act + input.Save(memoryStream, JpegEncoder); + + // assert + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); + JpegMetadata meta = output.Metadata.GetJpegMetadata(); + Assert.Equal(expectedColorType, meta.ColorType); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index a3cf66eeb5..0e92be4285 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -1,246 +1,240 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public partial class JpegEncoderTests { - [Trait("Format", "Jpg")] - public partial class JpegEncoderTests - { - private static JpegEncoder JpegEncoder => new(); + private static JpegEncoder JpegEncoder => new(); - private static JpegDecoder JpegDecoder => new(); + private static JpegDecoder JpegDecoder => new(); - private static readonly TheoryData TestQualities = new() - { - 40, - 80, - 100, - }; + private static readonly TheoryData TestQualities = new() + { + 40, + 80, + 100, + }; - public static readonly TheoryData NonSubsampledEncodingSetups = new() - { - { JpegEncodingColor.Rgb, 100, 0.0238f / 100 }, - { JpegEncodingColor.Rgb, 80, 1.3044f / 100 }, - { JpegEncodingColor.Rgb, 40, 2.9879f / 100 }, - { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 }, - { JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 }, - { JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 }, - }; + public static readonly TheoryData NonSubsampledEncodingSetups = new() + { + { JpegEncodingColor.Rgb, 100, 0.0238f / 100 }, + { JpegEncodingColor.Rgb, 80, 1.3044f / 100 }, + { JpegEncodingColor.Rgb, 40, 2.9879f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 100, 0.0780f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 80, 1.4585f / 100 }, + { JpegEncodingColor.YCbCrRatio444, 40, 3.1413f / 100 }, + }; + + public static readonly TheoryData SubsampledEncodingSetups = new() + { + { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 }, + { JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 }, + { JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 }, + { JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 }, + { JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 }, + }; + + public static readonly TheoryData CmykEncodingSetups = new() + { + { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 }, + { JpegEncodingColor.Cmyk, 80, 0.3922f / 100 }, + { JpegEncodingColor.Cmyk, 40, 0.6488f / 100 }, + }; - public static readonly TheoryData SubsampledEncodingSetups = new() - { - { JpegEncodingColor.YCbCrRatio422, 100, 0.4895f / 100 }, - { JpegEncodingColor.YCbCrRatio422, 80, 1.6043f / 100 }, - { JpegEncodingColor.YCbCrRatio422, 40, 3.1996f / 100 }, - { JpegEncodingColor.YCbCrRatio420, 100, 0.5790f / 100 }, - { JpegEncodingColor.YCbCrRatio420, 80, 1.6692f / 100 }, - { JpegEncodingColor.YCbCrRatio420, 40, 3.2324f / 100 }, - { JpegEncodingColor.YCbCrRatio411, 100, 0.6868f / 100 }, - { JpegEncodingColor.YCbCrRatio411, 80, 1.7139f / 100 }, - { JpegEncodingColor.YCbCrRatio411, 40, 3.2634f / 100 }, - { JpegEncodingColor.YCbCrRatio410, 100, 0.7357f / 100 }, - { JpegEncodingColor.YCbCrRatio410, 80, 1.7495f / 100 }, - { JpegEncodingColor.YCbCrRatio410, 40, 3.2911f / 100 }, - }; + public static readonly TheoryData YcckEncodingSetups = new() + { + { JpegEncodingColor.Ycck, 100, 0.0356f / 100 }, + { JpegEncodingColor.Ycck, 80, 0.1245f / 100 }, + { JpegEncodingColor.Ycck, 40, 0.2663f / 100 }, + }; - public static readonly TheoryData CmykEncodingSetups = new() - { - { JpegEncodingColor.Cmyk, 100, 0.0159f / 100 }, - { JpegEncodingColor.Cmyk, 80, 0.3922f / 100 }, - { JpegEncodingColor.Cmyk, 40, 0.6488f / 100 }, - }; + public static readonly TheoryData LuminanceEncodingSetups = new() + { + { JpegEncodingColor.Luminance, 100, 0.0175f / 100 }, + { JpegEncodingColor.Luminance, 80, 0.6730f / 100 }, + { JpegEncodingColor.Luminance, 40, 0.9941f / 100 }, + }; + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] + [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] + public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - public static readonly TheoryData YcckEncodingSetups = new() + var encoder = new JpegEncoder { - { JpegEncodingColor.Ycck, 100, 0.0356f / 100 }, - { JpegEncodingColor.Ycck, 80, 0.1245f / 100 }, - { JpegEncodingColor.Ycck, 40, 0.2663f / 100 }, + Quality = quality, + ColorType = colorType, + Interleaved = false, }; + string info = $"{colorType}-Q{quality}"; - public static readonly TheoryData LuminanceEncodingSetups = new() - { - { JpegEncodingColor.Luminance, 100, 0.0175f / 100 }, - { JpegEncodingColor.Luminance, 80, 0.6730f / 100 }, - { JpegEncodingColor.Luminance, 40, 0.9941f / 100 }, - }; + ImageComparer comparer = new TolerantImageComparer(tolerance); - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeBaseline_Interleaved(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, tolerance); - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(NonSubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(SubsampledEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Png.BikeGrayscale, nameof(LuminanceEncodingSetups), PixelTypes.L8)] - [WithFile(TestImages.Jpeg.Baseline.Cmyk, nameof(CmykEncodingSetups), PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Baseline.Ycck, nameof(YcckEncodingSetups), PixelTypes.Rgb24)] - public void EncodeBaseline_NonInterleavedMode(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); + } - var encoder = new JpegEncoder - { - Quality = quality, - ColorType = colorType, - Interleaved = false, - }; - string info = $"{colorType}-Q{quality}"; + [Theory] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); + + [Theory] + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] + [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] + public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); + + [Theory] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] + [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] + public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); + + [Theory] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); + + [Theory] + [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] + public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] + [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) + where TPixel : unmanaged, IPixel + { + ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444 + ? ImageComparer.TolerantPercentage(0.1f) + : ImageComparer.TolerantPercentage(5f); - ImageComparer comparer = new TolerantImageComparer(tolerance); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + TestJpegEncoderCore(provider, colorType, 100, comparer); + } - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "jpg"); - } + [Theory] + [InlineData(JpegEncodingColor.YCbCrRatio420)] + [InlineData(JpegEncodingColor.YCbCrRatio444)] + public async Task Encode_IsCancellable(JpegEncodingColor colorType) + { + var cts = new CancellationTokenSource(); + using var pausedStream = new PausedStream(new MemoryStream()); + pausedStream.OnWaiting(s => + { + // after some writing + if (s.Position >= 500) + { + cts.Cancel(); + pausedStream.Release(); + } + else + { + // allows this/next wait to unblock + pausedStream.Next(); + } + }); - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 600, 400, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 158, 24, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 153, 21, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 138, 24, PixelTypes.Rgb24)] - public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); - - [Theory] - [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] - [WithSolidFilledImages(nameof(NonSubsampledEncodingSetups), 1, 1, 255, 100, 50, 255, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 143, 81, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 7, 5, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 48, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 73, 71, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 24, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 46, 8, PixelTypes.Rgb24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 51, 7, PixelTypes.Rgb24)] - public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, ImageComparer.Tolerant(0.12f)); - - [Theory] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgb24, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] - [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] - public void EncodeBaseline_Grayscale(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, JpegEncodingColor.Luminance, quality); - - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 96, 96, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality); - - [Theory] - [WithTestPatternImages(nameof(NonSubsampledEncodingSetups), 48, 48, PixelTypes.Rgb24 | PixelTypes.Bgr24)] - public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f)); - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(587, 821, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio444)] - [WithTestPatternImages(677, 683, PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - [WithSolidFilledImages(400, 400, nameof(Color.Red), PixelTypes.Rgb24, JpegEncodingColor.YCbCrRatio420)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegEncodingColor colorType) - where TPixel : unmanaged, IPixel + using var image = new Image(5000, 5000); + await Assert.ThrowsAsync(async () => { - ImageComparer comparer = colorType == JpegEncodingColor.YCbCrRatio444 - ? ImageComparer.TolerantPercentage(0.1f) - : ImageComparer.TolerantPercentage(5f); + var encoder = new JpegEncoder() { ColorType = colorType }; + await image.SaveAsync(pausedStream, encoder, cts.Token); + }); + } - provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); - TestJpegEncoderCore(provider, colorType, 100, comparer); - } + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) + { + float tolerance = 0.015f; // ~1.5% - [Theory] - [InlineData(JpegEncodingColor.YCbCrRatio420)] - [InlineData(JpegEncodingColor.YCbCrRatio444)] - public async Task Encode_IsCancellable(JpegEncodingColor colorType) + if (quality < 50) { - var cts = new CancellationTokenSource(); - using var pausedStream = new PausedStream(new MemoryStream()); - pausedStream.OnWaiting(s => - { - // after some writing - if (s.Position >= 500) - { - cts.Cancel(); - pausedStream.Release(); - } - else - { - // allows this/next wait to unblock - pausedStream.Next(); - } - }); - - using var image = new Image(5000, 5000); - await Assert.ThrowsAsync(async () => - { - var encoder = new JpegEncoder() { ColorType = colorType }; - await image.SaveAsync(pausedStream, encoder, cts.Token); - }); + tolerance *= 4.5f; } - - /// - /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation - /// - private static ImageComparer GetComparer(int quality, JpegEncodingColor? colorType) + else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) { - float tolerance = 0.015f; // ~1.5% - - if (quality < 50) - { - tolerance *= 4.5f; - } - else if (quality < 75 || colorType == JpegEncodingColor.YCbCrRatio420) + tolerance *= 2.0f; + if (colorType == JpegEncodingColor.YCbCrRatio420) { tolerance *= 2.0f; - if (colorType == JpegEncodingColor.YCbCrRatio420) - { - tolerance *= 2.0f; - } } - - return ImageComparer.Tolerant(tolerance); } - private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality) - where TPixel : unmanaged, IPixel - => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); + return ImageComparer.Tolerant(tolerance); + } - private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) - where TPixel : unmanaged, IPixel - => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, GetComparer(quality, colorType)); - private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, ImageComparer comparer) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, float tolerance) + where TPixel : unmanaged, IPixel + => TestJpegEncoderCore(provider, colorType, quality, new TolerantImageComparer(tolerance)); - var encoder = new JpegEncoder - { - Quality = quality, - ColorType = colorType - }; - string info = $"{colorType}-Q{quality}"; + private static void TestJpegEncoderCore(TestImageProvider provider, JpegEncodingColor colorType, int quality, ImageComparer comparer) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); - } + var encoder = new JpegEncoder + { + Quality = quality, + ColorType = colorType + }; + string info = $"{colorType}-Q{quality}"; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs index c212058ae7..df74d266cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegFileMarkerTests.cs @@ -3,24 +3,22 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class JpegFileMarkerTests { - [Trait("Format", "Jpg")] - public class JpegFileMarkerTests + [Fact] + public void MarkerConstructorAssignsProperties() { - [Fact] - public void MarkerConstructorAssignsProperties() - { - const byte app1 = JpegConstants.Markers.APP1; - const int position = 5; - var marker = new JpegFileMarker(app1, position); + const byte app1 = JpegConstants.Markers.APP1; + const int position = 5; + var marker = new JpegFileMarker(app1, position); - Assert.Equal(app1, marker.Marker); - Assert.Equal(position, marker.Position); - Assert.False(marker.Invalid); - Assert.Equal(app1.ToString("X"), marker.ToString()); - } + Assert.Equal(app1, marker.Marker); + Assert.Equal(position, marker.Position); + Assert.False(marker.Invalid); + Assert.Equal(app1.ToString("X"), marker.ToString()); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index ce9ce79795..05f22667dc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -2,61 +2,59 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class JpegMetadataTests { - [Trait("Format", "Jpg")] - public class JpegMetadataTests + [Fact] + public void CloneIsDeep() { - [Fact] - public void CloneIsDeep() - { - var meta = new JpegMetadata { ColorType = JpegEncodingColor.Luminance }; - var clone = (JpegMetadata)meta.DeepClone(); + var meta = new JpegMetadata { ColorType = JpegEncodingColor.Luminance }; + var clone = (JpegMetadata)meta.DeepClone(); - clone.ColorType = JpegEncodingColor.YCbCrRatio420; + clone.ColorType = JpegEncodingColor.YCbCrRatio420; - Assert.False(meta.ColorType.Equals(clone.ColorType)); - } + Assert.False(meta.ColorType.Equals(clone.ColorType)); + } - [Fact] - public void Quality_DefaultQuality() - { - var meta = new JpegMetadata(); + [Fact] + public void Quality_DefaultQuality() + { + var meta = new JpegMetadata(); - Assert.Equal(meta.Quality, ImageSharp.Formats.Jpeg.Components.Quantization.DefaultQualityFactor); - } + Assert.Equal(meta.Quality, ImageSharp.Formats.Jpeg.Components.Quantization.DefaultQualityFactor); + } - [Fact] - public void Quality_LuminanceOnlyQuality() - { - int quality = 50; + [Fact] + public void Quality_LuminanceOnlyQuality() + { + int quality = 50; - var meta = new JpegMetadata { LuminanceQuality = quality }; + var meta = new JpegMetadata { LuminanceQuality = quality }; - Assert.Equal(meta.Quality, quality); - } + Assert.Equal(meta.Quality, quality); + } - [Fact] - public void Quality_BothComponentsQuality() - { - int quality = 50; + [Fact] + public void Quality_BothComponentsQuality() + { + int quality = 50; - var meta = new JpegMetadata { LuminanceQuality = quality, ChrominanceQuality = quality }; + var meta = new JpegMetadata { LuminanceQuality = quality, ChrominanceQuality = quality }; - Assert.Equal(meta.Quality, quality); - } + Assert.Equal(meta.Quality, quality); + } - [Fact] - public void Quality_ReturnsMaxQuality() - { - int qualityLuma = 50; - int qualityChroma = 30; + [Fact] + public void Quality_ReturnsMaxQuality() + { + int qualityLuma = 50; + int qualityChroma = 30; - var meta = new JpegMetadata { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma }; + var meta = new JpegMetadata { LuminanceQuality = qualityLuma, ChrominanceQuality = qualityChroma }; - Assert.Equal(meta.Quality, qualityLuma); - } + Assert.Equal(meta.Quality, qualityLuma); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index bef7d0c4e8..a51fd2e51b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,58 +1,54 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public class LibJpegToolsTests { - [Trait("Format", "Jpg")] - public class LibJpegToolsTests + [Fact] + public void RunDumpJpegCoeffsTool() { - [Fact] - public void RunDumpJpegCoeffsTool() + if (!TestEnvironment.IsWindows) { - if (!TestEnvironment.IsWindows) - { - return; - } + return; + } - string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); - string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); - string outputFile = Path.Combine(outputDir, "progress.dctdump"); + string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); + string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); + string outputFile = Path.Combine(outputDir, "progress.dctdump"); - LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); + LibJpegTools.RunDumpJpegCoeffsTool(inputFile, outputFile); - Assert.True(File.Exists(outputFile)); - } + Assert.True(File.Exists(outputFile)); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] - public void ExtractSpectralData(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] + public void ExtractSpectralData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.IsWindows) { - if (!TestEnvironment.IsWindows) - { - return; - } + return; + } - string testImage = provider.SourceFileOrDescription; - LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); + string testImage = provider.SourceFileOrDescription; + LibJpegTools.SpectralData data = LibJpegTools.ExtractSpectralData(testImage); - Assert.True(data.ComponentCount == 3); - Assert.True(data.Components.Length == 3); + Assert.True(data.ComponentCount == 3); + Assert.True(data.Components.Length == 3); - VerifyJpeg.SaveSpectralImage(provider, data); + VerifyJpeg.SaveSpectralImage(provider, data); - // I knew this one well: - if (testImage == TestImages.Jpeg.Progressive.Progress) - { - VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31); - } + // I knew this one well: + if (testImage == TestImages.Jpeg.Progressive.Progress) + { + VerifyJpeg.VerifyComponentSizes3(data.Components, 43, 61, 22, 31, 22, 31); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index 70a99e909e..b202fd9ec3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -6,129 +6,126 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class ParseStreamTests { - [Trait("Format", "Jpg")] - public class ParseStreamTests + private ITestOutputHelper Output { get; } + + public ParseStreamTests(ITestOutputHelper output) { - private ITestOutputHelper Output { get; } + this.Output = output; + } - public ParseStreamTests(ITestOutputHelper output) - { - this.Output = output; - } + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)] + [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] + public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) + { + var expectedColorSpace = (JpegColorSpace)expectedColorSpaceValue; - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.Grayscale)] - [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)] - public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue) + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile, metaDataOnly: true)) { - var expectedColorSpace = (JpegColorSpace)expectedColorSpaceValue; - - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile, metaDataOnly: true)) - { - Assert.Equal(expectedColorSpace, decoder.ColorSpace); - } + Assert.Equal(expectedColorSpace, decoder.ColorSpace); } + } - [Fact] - public void ComponentScalingIsCorrect_1ChannelJpeg() + [Fact] + public void ComponentScalingIsCorrect_1ChannelJpeg() + { + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) { - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(TestImages.Jpeg.Baseline.Jpeg400)) - { - Assert.Equal(1, decoder.Frame.ComponentCount); - Assert.Equal(1, decoder.Components.Length); + Assert.Equal(1, decoder.Frame.ComponentCount); + Assert.Equal(1, decoder.Components.Length); - Size expectedSizeInBlocks = decoder.Frame.PixelSize.DivideRoundUp(8); + Size expectedSizeInBlocks = decoder.Frame.PixelSize.DivideRoundUp(8); - Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize); + Assert.Equal(expectedSizeInBlocks, decoder.Frame.McuSize); - var uniform1 = new Size(1, 1); - IJpegComponent c0 = decoder.Components[0]; - VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); - } + var uniform1 = new Size(1, 1); + IJpegComponent c0 = decoder.Components[0]; + VerifyJpeg.VerifyComponent(c0, expectedSizeInBlocks, uniform1, uniform1); } + } + + [Theory] + [InlineData(TestImages.Jpeg.Baseline.Jpeg444)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)] + [InlineData(TestImages.Jpeg.Baseline.Testorig420)] + [InlineData(TestImages.Jpeg.Baseline.Ycck)] + [InlineData(TestImages.Jpeg.Baseline.Cmyk)] + public void PrintComponentData(string imageFile) + { + var sb = new StringBuilder(); - [Theory] - [InlineData(TestImages.Jpeg.Baseline.Jpeg444)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Small)] - [InlineData(TestImages.Jpeg.Baseline.Testorig420)] - [InlineData(TestImages.Jpeg.Baseline.Ycck)] - [InlineData(TestImages.Jpeg.Baseline.Cmyk)] - public void PrintComponentData(string imageFile) + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { - var sb = new StringBuilder(); + sb.AppendLine(imageFile); + sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}"); + IJpegComponent c0 = decoder.Components[0]; + IJpegComponent c1 = decoder.Components[1]; - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) - { - sb.AppendLine(imageFile); - sb.AppendLine($"Size:{decoder.Frame.PixelSize} MCU:{decoder.Frame.McuSize}"); - IJpegComponent c0 = decoder.Components[0]; - IJpegComponent c1 = decoder.Components[1]; + sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); + sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); + } - sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); - sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); - } + this.Output.WriteLine(sb.ToString()); + } - this.Output.WriteLine(sb.ToString()); - } + public static readonly TheoryData ComponentVerificationData = new() + { + { TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) }, + { 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) }, + }; + + [Theory] + [MemberData(nameof(ComponentVerificationData))] + public void ComponentScalingIsCorrect_MultiChannelJpeg( + string imageFile, + int componentCount, + object expectedLumaFactors, + object expectedChromaFactors) + { + var fLuma = (Size)expectedLumaFactors; + var fChroma = (Size)expectedChromaFactors; - public static readonly TheoryData ComponentVerificationData = new() - { - { TestImages.Jpeg.Baseline.Jpeg444, 3, new Size(1, 1), new Size(1, 1) }, - { 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) }, - }; - - [Theory] - [MemberData(nameof(ComponentVerificationData))] - public void ComponentScalingIsCorrect_MultiChannelJpeg( - string imageFile, - int componentCount, - object expectedLumaFactors, - object expectedChromaFactors) + using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) { - var fLuma = (Size)expectedLumaFactors; - var fChroma = (Size)expectedChromaFactors; + Assert.Equal(componentCount, decoder.Frame.ComponentCount); + Assert.Equal(componentCount, decoder.Components.Length); - using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) - { - Assert.Equal(componentCount, decoder.Frame.ComponentCount); - Assert.Equal(componentCount, decoder.Components.Length); - - IJpegComponent c0 = decoder.Components[0]; - IJpegComponent c1 = decoder.Components[1]; - IJpegComponent c2 = decoder.Components[2]; + IJpegComponent c0 = decoder.Components[0]; + IJpegComponent c1 = decoder.Components[1]; + IJpegComponent c2 = decoder.Components[2]; - var uniform1 = new Size(1, 1); + var uniform1 = new Size(1, 1); - Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma); + Size expectedLumaSizeInBlocks = decoder.Frame.McuSize.MultiplyBy(fLuma); - Size divisor = fLuma.DivideBy(fChroma); + Size divisor = fLuma.DivideBy(fChroma); - Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); + Size expectedChromaSizeInBlocks = expectedLumaSizeInBlocks.DivideRoundUp(divisor); - VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); - VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); - VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); + VerifyJpeg.VerifyComponent(c0, expectedLumaSizeInBlocks, fLuma, uniform1); + VerifyJpeg.VerifyComponent(c1, expectedChromaSizeInBlocks, fChroma, divisor); + VerifyJpeg.VerifyComponent(c2, expectedChromaSizeInBlocks, fChroma, divisor); - if (componentCount == 4) - { - IJpegComponent c3 = decoder.Components[2]; - VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); - } + if (componentCount == 4) + { + IJpegComponent c3 = decoder.Components[2]; + VerifyJpeg.VerifyComponent(c3, expectedLumaSizeInBlocks, fLuma, uniform1); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs index e08f8888dc..4a63edca11 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -4,76 +4,73 @@ using System.Text; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +[Trait("Format", "Jpg")] +public class ProfileResolverTests { - [Trait("Format", "Jpg")] - public class ProfileResolverTests - { - private static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); - private static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); - private static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); - private static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + private static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + private static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + private static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + private static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); - [Fact] - public void ProfileResolverHasCorrectJFifMarker() - { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectJFifMarker() + { + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); + } - [Fact] - public void ProfileResolverHasCorrectExifMarker() - { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectExifMarker() + { + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); + } - [Fact] - public void ProfileResolverHasCorrectIccMarker() - { - Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectIccMarker() + { + Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); + } - [Fact] - public void ProfileResolverHasCorrectAdobeMarker() - { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); - } + [Fact] + public void ProfileResolverHasCorrectAdobeMarker() + { + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); + } - [Fact] - public void ProfileResolverCanResolveJFifMarker() - { - Assert.True(ProfileResolver.IsProfile(JFifMarker, ProfileResolver.JFifMarker)); - } + [Fact] + public void ProfileResolverCanResolveJFifMarker() + { + Assert.True(ProfileResolver.IsProfile(JFifMarker, ProfileResolver.JFifMarker)); + } - [Fact] - public void ProfileResolverCanResolveExifMarker() - { - Assert.True(ProfileResolver.IsProfile(ExifMarker, ProfileResolver.ExifMarker)); - } + [Fact] + public void ProfileResolverCanResolveExifMarker() + { + Assert.True(ProfileResolver.IsProfile(ExifMarker, ProfileResolver.ExifMarker)); + } - [Fact] - public void ProfileResolverCanResolveIccMarker() - { - Assert.True(ProfileResolver.IsProfile(IccMarker, ProfileResolver.IccMarker)); - } + [Fact] + public void ProfileResolverCanResolveIccMarker() + { + Assert.True(ProfileResolver.IsProfile(IccMarker, ProfileResolver.IccMarker)); + } - [Fact] - public void ProfileResolverCanResolveAdobeMarker() - { - Assert.True(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.AdobeMarker)); - } + [Fact] + public void ProfileResolverCanResolveAdobeMarker() + { + Assert.True(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.AdobeMarker)); + } - [Fact] - public void ProfileResolverCorrectlyReportsNonMarker() - { - Assert.False(ProfileResolver.IsProfile(IccMarker, ProfileResolver.AdobeMarker)); - } + [Fact] + public void ProfileResolverCorrectlyReportsNonMarker() + { + Assert.False(ProfileResolver.IsProfile(IccMarker, ProfileResolver.AdobeMarker)); + } - [Fact] - public void ProfileResolverCanHandleIncorrectLength() - { - Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); - } + [Fact] + public void ProfileResolverCanHandleIncorrectLength() + { + Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs index 725b95f07b..dd17a3ab2c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs @@ -2,44 +2,42 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components; -using Xunit; using JpegQuantization = SixLabors.ImageSharp.Formats.Jpeg.Components.Quantization; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class QuantizationTests { - [Trait("Format", "Jpg")] - public class QuantizationTests + [Fact] + public void QualityEstimationFromStandardEncoderTables_Luminance() { - [Fact] - public void QualityEstimationFromStandardEncoderTables_Luminance() + int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; + int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; + for (int quality = firstIndex; quality <= lastIndex; quality++) { - int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; - int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; - for (int quality = firstIndex; quality <= lastIndex; quality++) - { - Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality); - int estimatedQuality = JpegQuantization.EstimateLuminanceQuality(ref table); + Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality); + int estimatedQuality = JpegQuantization.EstimateLuminanceQuality(ref table); - Assert.True( - quality.Equals(estimatedQuality), - $"Failed to estimate luminance quality for standard table at quality level {quality}"); - } + Assert.True( + quality.Equals(estimatedQuality), + $"Failed to estimate luminance quality for standard table at quality level {quality}"); } + } - [Fact] - public void QualityEstimationFromStandardEncoderTables_Chrominance() + [Fact] + public void QualityEstimationFromStandardEncoderTables_Chrominance() + { + int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; + int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; + for (int quality = firstIndex; quality <= lastIndex; quality++) { - int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; - int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; - for (int quality = firstIndex; quality <= lastIndex; quality++) - { - Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality); - int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table); + Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality); + int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table); - Assert.True( - quality.Equals(estimatedQuality), - $"Failed to estimate chrominance quality for standard table at quality level {quality}"); - } + Assert.True( + quality.Equals(estimatedQuality), + $"Failed to estimate chrominance quality for standard table at quality level {quality}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs index 5ba9f7c981..cd93adefd8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -3,38 +3,35 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class ReferenceImplementationsTests { - [Trait("Format", "Jpg")] - public partial class ReferenceImplementationsTests + public class AccurateDCT : JpegFixture { - public class AccurateDCT : JpegFixture + public AccurateDCT(ITestOutputHelper output) + : base(output) { - public AccurateDCT(ITestOutputHelper output) - : base(output) - { - } + } - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void ForwardThenInverse(int seed) - { - float[] data = Create8x8RandomFloatData(-1000, 1000, seed); + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void ForwardThenInverse(int seed) + { + float[] data = Create8x8RandomFloatData(-1000, 1000, seed); - var b0 = default(Block8x8F); - b0.LoadFrom(data); + var b0 = default(Block8x8F); + b0.LoadFrom(data); - Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); - Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); + Block8x8F b1 = ReferenceImplementations.AccurateDCT.TransformFDCT(ref b0); + Block8x8F b2 = ReferenceImplementations.AccurateDCT.TransformIDCT(ref b1); - this.CompareBlocks(b0, b2, 1e-4f); - } + this.CompareBlocks(b0, b2, 1e-4f); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 31acb3b882..46c6ee2da6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -4,104 +4,101 @@ // ReSharper disable InconsistentNaming using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class ReferenceImplementationsTests { - [Trait("Format", "Jpg")] - public partial class ReferenceImplementationsTests + public class FloatingPointDCT : JpegFixture { - public class FloatingPointDCT : JpegFixture + public FloatingPointDCT(ITestOutputHelper output) + : base(output) + { + } + + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void LLM_ForwardThenInverse(int seed, int startAt) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); + float[] original = data.ConvertAllToFloat(); + float[] src = data.ConvertAllToFloat(); + 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); + + this.CompareBlocks(original, src, 0.1f); + } + + // [Fact] + public void LLM_CalcConstants() + { + ReferenceImplementations.LLM_FloatingPoint_DCT.PrintConstants(this.Output); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + float[] sourceArray = Create8x8RandomFloatData(-range, range, seed); + + var source = Block8x8F.Load(sourceArray); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); + + this.CompareBlocks(expected, actual, 0.1f); + } + + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) { - public FloatingPointDCT(ITestOutputHelper output) - : base(output) - { - } - - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void LLM_ForwardThenInverse(int seed, int startAt) - { - int[] data = Create8x8RandomIntData(-1000, 1000, seed); - float[] original = data.ConvertAllToFloat(); - float[] src = data.ConvertAllToFloat(); - 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); - - this.CompareBlocks(original, src, 0.1f); - } - - // [Fact] - public void LLM_CalcConstants() - { - ReferenceImplementations.LLM_FloatingPoint_DCT.PrintConstants(this.Output); - } - - [Theory] - [InlineData(42, 1000)] - [InlineData(1, 1000)] - [InlineData(2, 1000)] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void LLM_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - float[] sourceArray = Create8x8RandomFloatData(-range, range, seed); - - var source = Block8x8F.Load(sourceArray); - - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - - Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); - - this.CompareBlocks(expected, actual, 0.1f); - } - - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void LLM_FDCT_IsEquivalentTo_AccurateImplementation(int seed) - { - float[] floatData = Create8x8RandomFloatData(-1000, 1000); - - Block8x8F source = default; - source.LoadFrom(floatData); - - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); - actual.MultiplyInPlace(0.125f); - - this.CompareBlocks(expected, actual, 1f); - } - - [Theory] - [InlineData(42, 1000)] - [InlineData(1, 1000)] - [InlineData(2, 1000)] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - int[] intData = Create8x8RandomIntData(-range, range, seed); - float[] floatSrc = intData.ConvertAllToFloat(); - - ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); - - float[] dest = new float[64]; - - ReferenceImplementations.GT_FloatingPoint_DCT.IDCT8x8GT(floatSrc, dest); - - this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); - } + float[] floatData = Create8x8RandomFloatData(-1000, 1000); + + Block8x8F source = default; + source.LoadFrom(floatData); + + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); + actual.MultiplyInPlace(0.125f); + + this.CompareBlocks(expected, actual, 1f); + } + + [Theory] + [InlineData(42, 1000)] + [InlineData(1, 1000)] + [InlineData(2, 1000)] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void GT_IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] intData = Create8x8RandomIntData(-range, range, seed); + float[] floatSrc = intData.ConvertAllToFloat(); + + ReferenceImplementations.AccurateDCT.TransformIDCTInplace(intData); + + float[] dest = new float[64]; + + 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 54e85a42c4..b447349720 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -1,91 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public partial class ReferenceImplementationsTests { - [Trait("Format", "Jpg")] - public partial class ReferenceImplementationsTests + public class StandardIntegerDCT : JpegFixture { - public class StandardIntegerDCT : JpegFixture + public StandardIntegerDCT(ITestOutputHelper output) + : base(output) { - public StandardIntegerDCT(ITestOutputHelper output) - : base(output) - { - } + } - [Theory] - [InlineData(42, 200)] - [InlineData(1, 200)] - [InlineData(2, 200)] - public void IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) - { - int[] data = Create8x8RandomIntData(-range, range, seed); + [Theory] + [InlineData(42, 200)] + [InlineData(1, 200)] + [InlineData(2, 200)] + public void IDCT_IsEquivalentTo_AccurateImplementation(int seed, int range) + { + int[] data = Create8x8RandomIntData(-range, range, seed); - Block8x8 source = default; - source.LoadFrom(data); + Block8x8 source = default; + source.LoadFrom(data); - Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); - Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); + Block8x8 expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); + Block8x8 actual = ReferenceImplementations.StandardIntegerDCT.TransformIDCT(ref source); - this.CompareBlocks(expected, actual, 1); - } + this.CompareBlocks(expected, actual, 1); + } - [Theory] - [InlineData(42)] - [InlineData(1)] - [InlineData(2)] - public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) - { - int[] data = Create8x8RandomIntData(-1000, 1000, seed); + [Theory] + [InlineData(42)] + [InlineData(1)] + [InlineData(2)] + public void FDCT_IsEquivalentTo_AccurateImplementation(int seed) + { + int[] data = Create8x8RandomIntData(-1000, 1000, seed); - Block8x8F source = default; - source.LoadFrom(data); + Block8x8F source = default; + source.LoadFrom(data); - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - source.AddInPlace(128f); - Block8x8 temp = source.RoundAsInt16Block(); - Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); - Block8x8F actual = actual8.AsFloatBlock(); - actual.MultiplyInPlace(0.125f); + source.AddInPlace(128f); + Block8x8 temp = source.RoundAsInt16Block(); + Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); + Block8x8F actual = actual8.AsFloatBlock(); + actual.MultiplyInPlace(0.125f); - this.CompareBlocks(expected, actual, 1f); - } + this.CompareBlocks(expected, actual, 1f); + } - [Theory] - [InlineData(42, 0)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public void ForwardThenInverse(int seed, int startAt) - { - Span original = Create8x8RandomIntData(-200, 200, seed); + [Theory] + [InlineData(42, 0)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public void ForwardThenInverse(int seed, int startAt) + { + Span original = Create8x8RandomIntData(-200, 200, seed); - Span block = original.AddScalarToAllValues(128); + Span block = original.AddScalarToAllValues(128); - ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8_Inplace(block); + ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8_Inplace(block); - for (int i = 0; i < 64; i++) - { - block[i] /= 8; - } + for (int i = 0; i < 64; i++) + { + block[i] /= 8; + } - ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); + ReferenceImplementations.StandardIntegerDCT.TransformIDCTInplace(block); - for (int i = startAt; i < 64; i++) - { - float expected = original[i]; - float actual = block[i]; + for (int i = startAt; i < 64; i++) + { + float expected = original[i]; + float actual = block[i]; - Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); - } + Assert.Equal(expected, actual, new ApproximateFloatComparer(3f)); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index a1f8e2e5fa..2e97b9596b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -4,15 +4,14 @@ using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; - public partial class ReferenceImplementationsTests : JpegFixture +public partial class ReferenceImplementationsTests : JpegFixture +{ + public ReferenceImplementationsTests(ITestOutputHelper output) + : base(output) { - public ReferenceImplementationsTests(ITestOutputHelper output) - : base(output) - { - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs index 6ca3a728f5..27a2df9eaa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralConverterTests.cs @@ -2,79 +2,77 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class SpectralConverterTests { - [Trait("Format", "Jpg")] - public class SpectralConverterTests + // Test for null target size, i.e. when no scaling is needed + [Theory] + [InlineData(1, 1)] + [InlineData(800, 400)] + [InlineData(2354, 4847)] + public void CalculateResultingImageSize_Null_TargetSize(int width, int height) { - // Test for null target size, i.e. when no scaling is needed - [Theory] - [InlineData(1, 1)] - [InlineData(800, 400)] - [InlineData(2354, 4847)] - public void CalculateResultingImageSize_Null_TargetSize(int width, int height) - { - Size inputSize = new(width, height); + Size inputSize = new(width, height); - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, null, out int blockPixelSize); + Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, null, out int blockPixelSize); - Assert.Equal(expected: 8, blockPixelSize); - Assert.Equal(inputSize, outputSize); - } + Assert.Equal(expected: 8, blockPixelSize); + Assert.Equal(inputSize, outputSize); + } - // Test for 'perfect' dimensions, i.e. dimensions divisible by 8, with exact scaled size match - [Theory] - [InlineData(800, 400, 800, 400, 8)] - [InlineData(800, 400, 400, 200, 4)] - [InlineData(800, 400, 200, 100, 2)] - [InlineData(800, 400, 100, 50, 1)] - public void CalculateResultingImageSize_Perfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); + // Test for 'perfect' dimensions, i.e. dimensions divisible by 8, with exact scaled size match + [Theory] + [InlineData(800, 400, 800, 400, 8)] + [InlineData(800, 400, 400, 200, 4)] + [InlineData(800, 400, 200, 100, 2)] + [InlineData(800, 400, 100, 50, 1)] + public void CalculateResultingImageSize_Perfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) + { + Size inputSize = new(inW, inH); + Size targetSize = new(tW, tH); - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); + Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(outputSize, targetSize); - } + Assert.Equal(expectedBlockSize, blockPixelSize); + Assert.Equal(outputSize, targetSize); + } - // Test for 'imperfect' dimensions, i.e. dimensions NOT divisible by 8, with exact scaled size match - [Theory] - [InlineData(7, 14, 7, 14, 8)] - [InlineData(7, 14, 4, 7, 4)] - [InlineData(7, 14, 2, 4, 2)] - [InlineData(7, 14, 1, 2, 1)] - public void CalculateResultingImageSize_Imperfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); + // Test for 'imperfect' dimensions, i.e. dimensions NOT divisible by 8, with exact scaled size match + [Theory] + [InlineData(7, 14, 7, 14, 8)] + [InlineData(7, 14, 4, 7, 4)] + [InlineData(7, 14, 2, 4, 2)] + [InlineData(7, 14, 1, 2, 1)] + public void CalculateResultingImageSize_Imperfect_Dimensions_Exact_Match(int inW, int inH, int tW, int tH, int expectedBlockSize) + { + Size inputSize = new(inW, inH); + Size targetSize = new(tW, tH); - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); + Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(outputSize, targetSize); - } + Assert.Equal(expectedBlockSize, blockPixelSize); + Assert.Equal(outputSize, targetSize); + } - // Test for inexact target and output sizes match - [Theory] - [InlineData(7, 14, 4, 6, 4, 7, 4)] - [InlineData(7, 14, 1, 1, 1, 2, 1)] - [InlineData(800, 400, 999, 600, 800, 400, 8)] - [InlineData(800, 400, 390, 150, 400, 200, 4)] - [InlineData(804, 1198, 500, 800, 804, 1198, 8)] - public void CalculateResultingImageSize_Inexact_Target_Size(int inW, int inH, int tW, int tH, int exW, int exH, int expectedBlockSize) - { - Size inputSize = new(inW, inH); - Size targetSize = new(tW, tH); - Size expectedSize = new(exW, exH); + // Test for inexact target and output sizes match + [Theory] + [InlineData(7, 14, 4, 6, 4, 7, 4)] + [InlineData(7, 14, 1, 1, 1, 2, 1)] + [InlineData(800, 400, 999, 600, 800, 400, 8)] + [InlineData(800, 400, 390, 150, 400, 200, 4)] + [InlineData(804, 1198, 500, 800, 804, 1198, 8)] + public void CalculateResultingImageSize_Inexact_Target_Size(int inW, int inH, int tW, int tH, int exW, int exH, int expectedBlockSize) + { + Size inputSize = new(inW, inH); + Size targetSize = new(tW, tH); + Size expectedSize = new(exW, exH); - Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); + Size outputSize = SpectralConverter.CalculateResultingImageSize(inputSize, targetSize, out int blockPixelSize); - Assert.Equal(expectedBlockSize, blockPixelSize); - Assert.Equal(expectedSize, outputSize); - } + Assert.Equal(expectedBlockSize, blockPixelSize); + Assert.Equal(expectedSize, outputSize); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index db458cb7c5..58347a0724 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; @@ -11,204 +8,201 @@ 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 +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class SpectralJpegTests { - [Trait("Format", "Jpg")] - public class SpectralJpegTests - { - public SpectralJpegTests(ITestOutputHelper output) => this.Output = output; + public SpectralJpegTests(ITestOutputHelper output) => this.Output = output; - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public static readonly string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - }; + public static readonly string[] BaselineTestJpegs = + { + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; - public static readonly string[] ProgressiveTestJpegs = - { - TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, - TestImages.Jpeg.Progressive.Bad.ExifUndefType, - }; + public static readonly string[] ProgressiveTestJpegs = + { + TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, + }; - public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); + public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); - [Theory(Skip = "Debug only, enable manually!")] - [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Calculating data from ImageSharp - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - JpegDecoderOptions option = new(); + [Theory(Skip = "Debug only, enable manually!")] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Calculating data from ImageSharp + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + JpegDecoderOptions option = new(); - using var decoder = new JpegDecoderCore(option); - using var ms = new MemoryStream(sourceBytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + using var decoder = new JpegDecoderCore(option); + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - // internal scan decoder which we substitute to assert spectral correctness - var debugConverter = new DebugSpectralConverter(); - var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); + // internal scan decoder which we substitute to assert spectral correctness + var debugConverter = new DebugSpectralConverter(); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, debugConverter, cancellationToken: default); - // This would parse entire image - decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); - VerifyJpeg.SaveSpectralImage(provider, debugConverter.SpectralData); - } + // This would parse entire image + decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); + VerifyJpeg.SaveSpectralImage(provider, debugConverter.SpectralData); + } - [Theory] - [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] - public void VerifySpectralCorrectness(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] + public void VerifySpectralCorrectness(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.IsWindows) { - if (!TestEnvironment.IsWindows) - { - return; - } + return; + } - // Expected data from libjpeg - LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); + // Expected data from libjpeg + LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); - // Calculating data from ImageSharp - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - JpegDecoderOptions options = new(); + // Calculating data from ImageSharp + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + JpegDecoderOptions options = new(); - using var decoder = new JpegDecoderCore(options); - using var ms = new MemoryStream(sourceBytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + using var decoder = new JpegDecoderCore(options); + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - // internal scan decoder which we substitute to assert spectral correctness - var debugConverter = new DebugSpectralConverter(); + // internal scan decoder which we substitute to assert spectral correctness + var debugConverter = new DebugSpectralConverter(); - // This would parse entire image - decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); + // This would parse entire image + decoder.ParseStream(bufferedStream, debugConverter, cancellationToken: default); - // Actual verification - this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); - } + // Actual verification + this.VerifySpectralCorrectnessImpl(libJpegData, debugConverter.SpectralData); + } - private void VerifySpectralCorrectnessImpl( - LibJpegTools.SpectralData libJpegData, - LibJpegTools.SpectralData imageSharpData) - { - bool equality = libJpegData.Equals(imageSharpData); - this.Output.WriteLine("Spectral data equality: " + equality); + private void VerifySpectralCorrectnessImpl( + LibJpegTools.SpectralData libJpegData, + LibJpegTools.SpectralData imageSharpData) + { + bool equality = libJpegData.Equals(imageSharpData); + this.Output.WriteLine("Spectral data equality: " + equality); - int componentCount = imageSharpData.ComponentCount; - if (libJpegData.ComponentCount != componentCount) - { - throw new Exception("libJpegData.ComponentCount != componentCount"); - } + int componentCount = imageSharpData.ComponentCount; + if (libJpegData.ComponentCount != componentCount) + { + throw new Exception("libJpegData.ComponentCount != componentCount"); + } - double averageDifference = 0; - double totalDifference = 0; - double tolerance = 0; + double averageDifference = 0; + double totalDifference = 0; + double tolerance = 0; - this.Output.WriteLine("*** Differences ***"); - for (int i = 0; i < componentCount; i++) - { - LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; - LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; + this.Output.WriteLine("*** Differences ***"); + for (int i = 0; i < componentCount; i++) + { + LibJpegTools.ComponentData libJpegComponent = libJpegData.Components[i]; + LibJpegTools.ComponentData imageSharpComponent = imageSharpData.Components[i]; - (double total, double average) = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); + (double total, double average) = LibJpegTools.CalculateDifference(libJpegComponent, imageSharpComponent); - this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); - averageDifference += average; - totalDifference += total; - Size s = libJpegComponent.SpectralBlocks.Size(); - tolerance += s.Width * s.Height; - } + this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); + averageDifference += average; + totalDifference += total; + Size s = libJpegComponent.SpectralBlocks.Size(); + tolerance += s.Width * s.Height; + } - averageDifference /= componentCount; + averageDifference /= componentCount; - tolerance /= 64; // fair enough? + tolerance /= 64; // fair enough? - this.Output.WriteLine($"AVERAGE: {averageDifference}"); - this.Output.WriteLine($"TOTAL: {totalDifference}"); - this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}"); + this.Output.WriteLine($"AVERAGE: {averageDifference}"); + this.Output.WriteLine($"TOTAL: {totalDifference}"); + this.Output.WriteLine($"TOLERANCE = totalNumOfBlocks / 64 = {tolerance}"); - Assert.True(totalDifference < tolerance); - } + Assert.True(totalDifference < tolerance); + } - private class DebugSpectralConverter : SpectralConverter - where TPixel : unmanaged, IPixel - { - private JpegFrame frame; + private class DebugSpectralConverter : SpectralConverter + where TPixel : unmanaged, IPixel + { + private JpegFrame frame; - private IRawJpegData jpegData; + private IRawJpegData jpegData; - private LibJpegTools.SpectralData spectralData; + private LibJpegTools.SpectralData spectralData; - private int baselineScanRowCounter; + private int baselineScanRowCounter; - public LibJpegTools.SpectralData SpectralData + public LibJpegTools.SpectralData SpectralData + { + get { - get + // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing + // Progressive and multi-scan images must be loaded manually + if (this.frame.Progressive || !this.frame.Interleaved) { - // Due to underlying architecture, baseline interleaved jpegs would inject spectral data during parsing - // Progressive and multi-scan images must be loaded manually - if (this.frame.Progressive || !this.frame.Interleaved) + this.PrepareForDecoding(); + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) { - this.PrepareForDecoding(); - LibJpegTools.ComponentData[] components = this.spectralData.Components; - for (int i = 0; i < components.Length; i++) - { - components[i].LoadSpectral(this.frame.Components[i]); - } + components[i].LoadSpectral(this.frame.Components[i]); } - - return this.spectralData; } + + return this.spectralData; } + } - public override void ConvertStrideBaseline() + public override void ConvertStrideBaseline() + { + // This would be called only for baseline non-interleaved images + // We must copy spectral strides here + LibJpegTools.ComponentData[] components = this.spectralData.Components; + for (int i = 0; i < components.Length; i++) { - // This would be called only for baseline non-interleaved images - // We must copy spectral strides here - LibJpegTools.ComponentData[] components = this.spectralData.Components; - for (int i = 0; i < components.Length; i++) - { - components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); - } + components[i].LoadSpectralStride(this.frame.Components[i].SpectralBlocks, this.baselineScanRowCounter); + } - this.baselineScanRowCounter++; + this.baselineScanRowCounter++; - // As spectral buffers are reused for each stride decoding - we need to manually clear it like it's done in SpectralConverter - foreach (JpegComponent component in this.frame.Components) + // As spectral buffers are reused for each stride decoding - we need to manually clear it like it's done in SpectralConverter + foreach (JpegComponent component in this.frame.Components) + { + Buffer2D spectralBlocks = component.SpectralBlocks; + for (int i = 0; i < spectralBlocks.Height; i++) { - Buffer2D spectralBlocks = component.SpectralBlocks; - for (int i = 0; i < spectralBlocks.Height; i++) - { - spectralBlocks.DangerousGetRowSpan(i).Clear(); - } + spectralBlocks.DangerousGetRowSpan(i).Clear(); } } + } - public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) - { - this.frame = frame; - this.jpegData = jpegData; - } + public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) + { + this.frame = frame; + this.jpegData = jpegData; + } - public override void PrepareForDecoding() + public override void PrepareForDecoding() + { + var spectralComponents = new LibJpegTools.ComponentData[this.frame.ComponentCount]; + for (int i = 0; i < spectralComponents.Length; i++) { - var spectralComponents = new LibJpegTools.ComponentData[this.frame.ComponentCount]; - for (int i = 0; i < spectralComponents.Length; i++) - { - JpegComponent component = this.frame.Components[i]; - spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); - } - - this.spectralData = new LibJpegTools.SpectralData(spectralComponents); + JpegComponent component = this.frame.Components[i]; + spectralComponents[i] = new LibJpegTools.ComponentData(component.WidthInBlocks, component.HeightInBlocks, component.Index); } + + this.spectralData = new LibJpegTools.SpectralData(spectralComponents); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index f0e2d3ce11..25929182fb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -1,65 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class SpectralToPixelConversionTests { - [Trait("Format", "Jpg")] - public class SpectralToPixelConversionTests + public static readonly string[] BaselineTestJpegs = { - public static readonly string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - }; - - public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - - [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void Decoder_PixelBufferComparison(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Stream - byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; - using var ms = new MemoryStream(sourceBytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; - // Decoding - JpegDecoderOptions options = new(); - using var converter = new SpectralConverter(Configuration.Default); - using var decoder = new JpegDecoderCore(options); - var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); - decoder.ParseStream(bufferedStream, converter, cancellationToken: default); + public SpectralToPixelConversionTests(ITestOutputHelper output) => this.Output = output; - // Test metadata - provider.Utility.TestGroupName = nameof(JpegDecoderTests); - provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + private ITestOutputHelper Output { get; } - // Comparison - using var image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()); - using Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false); - ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); - - this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); - this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); - - // ReSharper disable once PossibleInvalidOperationException - Assert.True(report.TotalNormalizedDifference.Value < 0.005f); - } + [Theory] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void Decoder_PixelBufferComparison(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Stream + byte[] sourceBytes = TestFile.Create(provider.SourceFileOrDescription).Bytes; + using var ms = new MemoryStream(sourceBytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + + // Decoding + JpegDecoderOptions options = new(); + using var converter = new SpectralConverter(Configuration.Default); + using var decoder = new JpegDecoderCore(options); + var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); + decoder.ParseStream(bufferedStream, converter, cancellationToken: default); + + // Test metadata + provider.Utility.TestGroupName = nameof(JpegDecoderTests); + provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; + + // Comparison + using var image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata()); + using Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false); + ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); + + this.Output.WriteLine($"*** {provider.SourceFileOrDescription} ***"); + this.Output.WriteLine($"Difference: {report.DifferencePercentageString}"); + + // ReSharper disable once PossibleInvalidOperationException + Assert.True(report.TotalNormalizedDifference.Value < 0.005f); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index ffb54fb0a9..978978989e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -1,239 +1,235 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +public class JpegFixture : MeasureFixture { - public class JpegFixture : MeasureFixture + public JpegFixture(ITestOutputHelper output) + : base(output) { - public JpegFixture(ITestOutputHelper output) - : base(output) - { - } + } - // ReSharper disable once InconsistentNaming - public static float[] Create8x8FloatData() + // ReSharper disable once InconsistentNaming + public static float[] Create8x8FloatData() + { + float[] result = new float[64]; + for (int i = 0; i < 8; i++) { - float[] result = new float[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) - { - result[(i * 8) + j] = (i * 10) + j; - } + result[(i * 8) + j] = (i * 10) + j; } - - return result; } - // ReSharper disable once InconsistentNaming - public static int[] Create8x8IntData() + return result; + } + + // ReSharper disable once InconsistentNaming + public static int[] Create8x8IntData() + { + int[] result = new int[64]; + for (int i = 0; i < 8; i++) { - int[] result = new int[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) - { - result[(i * 8) + j] = (i * 10) + j; - } + result[(i * 8) + j] = (i * 10) + j; } - - return result; } - // ReSharper disable once InconsistentNaming - public static short[] Create8x8ShortData() + return result; + } + + // ReSharper disable once InconsistentNaming + public static short[] Create8x8ShortData() + { + short[] result = new short[64]; + for (int i = 0; i < 8; i++) { - short[] result = new short[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) + short val = (short)((i * 10) + j); + if ((i + j) % 2 == 0) { - short val = (short)((i * 10) + j); - if ((i + j) % 2 == 0) - { - val *= -1; - } - - result[(i * 8) + j] = val; + val *= -1; } - } - return result; + result[(i * 8) + j] = val; + } } - // ReSharper disable once InconsistentNaming - public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) + return result; + } + + // ReSharper disable once InconsistentNaming + public static int[] Create8x8RandomIntData(int minValue, int maxValue, int seed = 42) + { + var rnd = new Random(seed); + int[] result = new int[64]; + for (int i = 0; i < 8; i++) { - var rnd = new Random(seed); - int[] result = new int[64]; - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - 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; } - public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) + return result; + } + + public static float[] Create8x8RandomFloatData(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) + { + var rnd = new Random(seed); + float[] result = new float[64]; + for (int y = 0; y < yBorder; y++) { - var rnd = new Random(seed); - float[] result = new float[64]; - for (int y = 0; y < yBorder; y++) + int y8 = y * 8; + for (int x = 0; x < xBorder; x++) { - int y8 = y * 8; - for (int x = 0; x < xBorder; x++) - { - double val = rnd.NextDouble(); - val *= maxValue - minValue; - val += minValue; + double val = rnd.NextDouble(); + val *= maxValue - minValue; + val += minValue; - result[y8 + x] = (float)val; - } + result[y8 + x] = (float)val; } - - return result; } - internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) => - Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed, xBorder, yBorder)); + return result; + } + + internal static Block8x8F CreateRandomFloatBlock(float minValue, float maxValue, int seed = 42, int xBorder = 8, int yBorder = 8) => + Block8x8F.Load(Create8x8RandomFloatData(minValue, maxValue, seed, xBorder, yBorder)); - internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); + internal void Print8x8Data(T[] data) => this.Print8x8Data(new Span(data)); - internal void Print8x8Data(Span data) + internal void Print8x8Data(Span data) + { + var bld = new StringBuilder(); + for (int i = 0; i < 8; i++) { - var bld = new StringBuilder(); - for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) { - for (int j = 0; j < 8; j++) - { - bld.Append($"{data[(i * 8) + j],3} "); - } - - bld.AppendLine(); + bld.Append($"{data[(i * 8) + j],3} "); } - this.Output.WriteLine(bld.ToString()); + bld.AppendLine(); } - internal void PrintLinearData(T[] data) => this.PrintLinearData(new Span(data), data.Length); - - internal void PrintLinearData(Span data, int count = -1) - { - if (count < 0) - { - count = data.Length; - } + this.Output.WriteLine(bld.ToString()); + } - var sb = new StringBuilder(); - for (int i = 0; i < count; i++) - { - sb.Append($"{data[i],3} "); - } + internal void PrintLinearData(T[] data) => this.PrintLinearData(new Span(data), data.Length); - this.Output.WriteLine(sb.ToString()); + internal void PrintLinearData(Span data, int count = -1) + { + if (count < 0) + { + count = data.Length; } - protected void Print(string msg) + var sb = new StringBuilder(); + for (int i = 0; i < count; i++) { - Debug.WriteLine(msg); - this.Output.WriteLine(msg); + sb.Append($"{data[i],3} "); } - internal void CompareBlocks(Block8x8 a, Block8x8 b, int tolerance) => - this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f); - - internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) - => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); + this.Output.WriteLine(sb.ToString()); + } - internal void CompareBlocks(Span a, Span b, float tolerance) - { - var comparer = new ApproximateFloatComparer(tolerance); - double totalDifference = 0.0; + protected void Print(string msg) + { + Debug.WriteLine(msg); + this.Output.WriteLine(msg); + } - bool failed = false; + internal void CompareBlocks(Block8x8 a, Block8x8 b, int tolerance) => + this.CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f); - for (int i = 0; i < Block8x8F.Size; i++) - { - float expected = a[i]; - float actual = b[i]; - totalDifference += Math.Abs(expected - actual); + internal void CompareBlocks(Block8x8F a, Block8x8F b, float tolerance) + => this.CompareBlocks(a.ToArray(), b.ToArray(), tolerance); - if (!comparer.Equals(expected, actual)) - { - failed = true; - this.Output.WriteLine($"Difference too large at index {i}"); - } - } + internal void CompareBlocks(Span a, Span b, float tolerance) + { + var comparer = new ApproximateFloatComparer(tolerance); + double totalDifference = 0.0; - this.Output.WriteLine("TOTAL DIFF: " + totalDifference); - Assert.False(failed); - } + bool failed = false; - internal static bool CompareBlocks(Block8x8 a, Block8x8 b, int tolerance, out int diff) + for (int i = 0; i < Block8x8F.Size; i++) { - bool res = CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f, out float fdiff); - diff = (int)fdiff; - return res; - } + float expected = a[i]; + float actual = b[i]; + totalDifference += Math.Abs(expected - actual); - internal static bool CompareBlocks(Block8x8F a, Block8x8F b, float tolerance, out float diff) => - CompareBlocks(a.ToArray(), b.ToArray(), tolerance, out diff); + if (!comparer.Equals(expected, actual)) + { + failed = true; + this.Output.WriteLine($"Difference too large at index {i}"); + } + } - internal static bool CompareBlocks(Span a, Span b, float tolerance, out float diff) - { - var comparer = new ApproximateFloatComparer(tolerance); - bool failed = false; + this.Output.WriteLine("TOTAL DIFF: " + totalDifference); + Assert.False(failed); + } - diff = 0; + internal static bool CompareBlocks(Block8x8 a, Block8x8 b, int tolerance, out int diff) + { + bool res = CompareBlocks(a.AsFloatBlock(), b.AsFloatBlock(), tolerance + 1e-5f, out float fdiff); + diff = (int)fdiff; + return res; + } - for (int i = 0; i < 64; i++) - { - float expected = a[i]; - float actual = b[i]; - diff += Math.Abs(expected - actual); + internal static bool CompareBlocks(Block8x8F a, Block8x8F b, float tolerance, out float diff) => + CompareBlocks(a.ToArray(), b.ToArray(), tolerance, out diff); - if (!comparer.Equals(expected, actual)) - { - failed = true; - } - } + internal static bool CompareBlocks(Span a, Span b, float tolerance, out float diff) + { + var comparer = new ApproximateFloatComparer(tolerance); + bool failed = false; - return !failed; - } + diff = 0; - internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false) + for (int i = 0; i < 64; i++) { - byte[] bytes = TestFile.Create(testFileName).Bytes; - using var ms = new MemoryStream(bytes); - using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); + float expected = a[i]; + float actual = b[i]; + diff += Math.Abs(expected - actual); - JpegDecoderOptions options = new(); - var decoder = new JpegDecoderCore(options); - if (metaDataOnly) - { - decoder.Identify(bufferedStream, cancellationToken: default); - } - else + if (!comparer.Equals(expected, actual)) { - using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + failed = true; } + } + + return !failed; + } + + internal static JpegDecoderCore ParseJpegStream(string testFileName, bool metaDataOnly = false) + { + byte[] bytes = TestFile.Create(testFileName).Bytes; + using var ms = new MemoryStream(bytes); + using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); - return decoder; + JpegDecoderOptions options = new(); + var decoder = new JpegDecoderCore(options); + if (metaDataOnly) + { + decoder.Identify(bufferedStream, cancellationToken: default); } + else + { + using Image image = decoder.Decode(bufferedStream, cancellationToken: default); + } + + return decoder; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 65fc0f7071..80365b4724 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -1,231 +1,228 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class LibJpegTools { - internal static partial class LibJpegTools + /// + /// Stores spectral blocks for jpeg components. + /// + public class ComponentData : IEquatable, IJpegComponent { - /// - /// Stores spectral blocks for jpeg components. - /// - public class ComponentData : IEquatable, IJpegComponent + public ComponentData(int widthInBlocks, int heightInBlocks, int index) { - public ComponentData(int widthInBlocks, int heightInBlocks, int index) - { - this.HeightInBlocks = heightInBlocks; - this.WidthInBlocks = widthInBlocks; - this.Index = index; - this.SpectralBlocks = Configuration.Default.MemoryAllocator.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); - } + this.HeightInBlocks = heightInBlocks; + this.WidthInBlocks = widthInBlocks; + this.Index = index; + this.SpectralBlocks = Configuration.Default.MemoryAllocator.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); + } - public byte Id { get; } + public byte Id { get; } - public Size Size => new(this.WidthInBlocks, this.HeightInBlocks); + public Size Size => new(this.WidthInBlocks, this.HeightInBlocks); - public int Index { get; } + public int Index { get; } - public Size SizeInBlocks => new(this.WidthInBlocks, this.HeightInBlocks); + public Size SizeInBlocks => new(this.WidthInBlocks, this.HeightInBlocks); - public Size SamplingFactors => throw new NotSupportedException(); + public Size SamplingFactors => throw new NotSupportedException(); - public Size SubSamplingDivisors => throw new NotSupportedException(); + public Size SubSamplingDivisors => throw new NotSupportedException(); - public int HeightInBlocks { get; } + public int HeightInBlocks { get; } - public int WidthInBlocks { get; } + public int WidthInBlocks { get; } - public int QuantizationTableIndex => throw new NotSupportedException(); + public int QuantizationTableIndex => throw new NotSupportedException(); - public Buffer2D SpectralBlocks { get; private set; } + public Buffer2D SpectralBlocks { get; private set; } - public short MinVal { get; private set; } = short.MaxValue; + public short MinVal { get; private set; } = short.MaxValue; - public short MaxVal { get; private set; } = short.MinValue; + public short MaxVal { get; private set; } = short.MinValue; - public int HorizontalSamplingFactor => throw new NotImplementedException(); + public int HorizontalSamplingFactor => throw new NotImplementedException(); - public int VerticalSamplingFactor => throw new NotImplementedException(); + public int VerticalSamplingFactor => throw new NotImplementedException(); - public int DcPredictor { get; set; } + public int DcPredictor { get; set; } - public int DcTableId { get; set; } + public int DcTableId { get; set; } - public int AcTableId { get; set; } + public int AcTableId { get; set; } - internal void MakeBlock(Block8x8 block, int y, int x) - { - block.TransposeInplace(); - this.MakeBlock(block.ToArray(), y, x); - } + internal void MakeBlock(Block8x8 block, int y, int x) + { + block.TransposeInplace(); + this.MakeBlock(block.ToArray(), y, x); + } - internal void MakeBlock(short[] data, int y, int x) - { - this.MinVal = Math.Min(this.MinVal, data.Min()); - this.MaxVal = Math.Max(this.MaxVal, data.Max()); - this.SpectralBlocks[x, y] = Block8x8.Load(data); - } + internal void MakeBlock(short[] data, int y, int x) + { + this.MinVal = Math.Min(this.MinVal, data.Min()); + this.MaxVal = Math.Max(this.MaxVal, data.Max()); + this.SpectralBlocks[x, y] = Block8x8.Load(data); + } - public void LoadSpectralStride(Buffer2D data, int strideIndex) - { - int startIndex = strideIndex * data.Height; + public void LoadSpectralStride(Buffer2D data, int strideIndex) + { + int startIndex = strideIndex * data.Height; - int endIndex = Math.Min(this.HeightInBlocks, startIndex + data.Height); + int endIndex = Math.Min(this.HeightInBlocks, startIndex + data.Height); - for (int y = startIndex; y < endIndex; y++) + for (int y = startIndex; y < endIndex; y++) + { + Span blockRow = data.DangerousGetRowSpan(y - startIndex); + for (int x = 0; x < this.WidthInBlocks; x++) { - Span blockRow = data.DangerousGetRowSpan(y - startIndex); - for (int x = 0; x < this.WidthInBlocks; x++) - { - this.MakeBlock(blockRow[x], y, x); - } + this.MakeBlock(blockRow[x], y, x); } } + } - public void LoadSpectral(IJpegComponent c) + public void LoadSpectral(IJpegComponent c) + { + Buffer2D data = c.SpectralBlocks; + for (int y = 0; y < this.HeightInBlocks; y++) { - Buffer2D data = c.SpectralBlocks; - for (int y = 0; y < this.HeightInBlocks; y++) + Span blockRow = data.DangerousGetRowSpan(y); + for (int x = 0; x < this.WidthInBlocks; x++) { - Span blockRow = data.DangerousGetRowSpan(y); - for (int x = 0; x < this.WidthInBlocks; x++) - { - this.MakeBlock(blockRow[x], y, x); - } + this.MakeBlock(blockRow[x], y, x); } } + } - public static ComponentData Load(JpegComponent c, int index) - { - var result = new ComponentData( - c.WidthInBlocks, - c.HeightInBlocks, - index); + public static ComponentData Load(JpegComponent c, int index) + { + var result = new ComponentData( + c.WidthInBlocks, + c.HeightInBlocks, + index); - result.LoadSpectral(c); - return result; - } + result.LoadSpectral(c); + return result; + } - public Image CreateGrayScaleImage() - { - var result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); + public Image CreateGrayScaleImage() + { + var result = new Image(this.WidthInBlocks * 8, this.HeightInBlocks * 8); - for (int by = 0; by < this.HeightInBlocks; by++) + for (int by = 0; by < this.HeightInBlocks; by++) + { + for (int bx = 0; bx < this.WidthInBlocks; bx++) { - for (int bx = 0; bx < this.WidthInBlocks; bx++) - { - this.WriteToImage(bx, by, result); - } + this.WriteToImage(bx, by, result); } - - return result; } - internal void WriteToImage(int bx, int by, Image image) - { - Block8x8 block = this.SpectralBlocks[bx, by]; + return result; + } - for (int y = 0; y < 8; y++) + internal void WriteToImage(int bx, int by, Image image) + { + Block8x8 block = this.SpectralBlocks[bx, by]; + + for (int y = 0; y < 8; y++) + { + for (int x = 0; x < 8; x++) { - for (int x = 0; x < 8; x++) - { - float val = this.GetBlockValue(block, x, y); + float val = this.GetBlockValue(block, x, y); - var v = new Vector4(val, val, val, 1); - Rgba32 color = default; - color.FromVector4(v); + var v = new Vector4(val, val, val, 1); + Rgba32 color = default; + color.FromVector4(v); - int yy = (by * 8) + y; - int xx = (bx * 8) + x; - image[xx, yy] = color; - } + int yy = (by * 8) + y; + int xx = (bx * 8) + x; + image[xx, yy] = color; } } + } + + internal float GetBlockValue(Block8x8 block, int x, int y) + { + float d = this.MaxVal - this.MinVal; + float val = block[y, x]; + val -= this.MinVal; + val /= d; + return val; + } - internal float GetBlockValue(Block8x8 block, int x, int y) + public bool Equals(ComponentData other) + { + if (other is null) { - float d = this.MaxVal - this.MinVal; - float val = block[y, x]; - val -= this.MinVal; - val /= d; - return val; + return false; } - public bool Equals(ComponentData other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } + return true; + } - bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks - && this.WidthInBlocks == other.WidthInBlocks; - if (!ok) - { - return false; - } + bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks + && this.WidthInBlocks == other.WidthInBlocks; + if (!ok) + { + return false; + } - for (int y = 0; y < this.HeightInBlocks; y++) + for (int y = 0; y < this.HeightInBlocks; y++) + { + for (int x = 0; x < this.WidthInBlocks; x++) { - for (int x = 0; x < this.WidthInBlocks; x++) + Block8x8 a = this.SpectralBlocks[x, y]; + Block8x8 b = other.SpectralBlocks[x, y]; + if (!a.Equals(b)) { - Block8x8 a = this.SpectralBlocks[x, y]; - Block8x8 b = other.SpectralBlocks[x, y]; - if (!a.Equals(b)) - { - return false; - } + return false; } } - - return true; } - public override bool Equals(object obj) - { - if (obj is null) - { - return false; - } + return true; + } - if (object.ReferenceEquals(this, obj)) - { - return true; - } + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } - if (obj.GetType() != this.GetType()) - { - return false; - } + if (object.ReferenceEquals(this, obj)) + { + return true; + } - return this.Equals((ComponentData)obj); + if (obj.GetType() != this.GetType()) + { + return false; } - public override int GetHashCode() => HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); + return this.Equals((ComponentData)obj); + } + + public override int GetHashCode() => HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); - public ref Block8x8 GetBlockReference(int column, int row) => throw new NotImplementedException(); + public ref Block8x8 GetBlockReference(int column, int row) => throw new NotImplementedException(); - public void Init(int maxSubFactorH, int maxSubFactorV) => throw new NotImplementedException(); + public void Init(int maxSubFactorH, int maxSubFactorV) => throw new NotImplementedException(); - public void AllocateSpectral(bool fullScan) => throw new NotImplementedException(); + public void AllocateSpectral(bool fullScan) => throw new NotImplementedException(); - public void Dispose() => throw new NotImplementedException(); + public void Dispose() => throw new NotImplementedException(); - public static bool operator ==(ComponentData left, ComponentData right) => Equals(left, right); + public static bool operator ==(ComponentData left, ComponentData right) => Equals(left, right); - public static bool operator !=(ComponentData left, ComponentData right) => !Equals(left, right); - } + public static bool operator !=(ComponentData left, ComponentData right) => !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 a2e7904b58..a4bb6b7bfc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -1,152 +1,147 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class LibJpegTools { - internal static partial class LibJpegTools + /// + /// Stores spectral jpeg component data in libjpeg-compatible style. + /// + public class SpectralData : IEquatable { - /// - /// Stores spectral jpeg component data in libjpeg-compatible style. - /// - public class SpectralData : IEquatable - { - public int ComponentCount { get; private set; } + public int ComponentCount { get; private set; } - public LibJpegTools.ComponentData[] Components { get; private set; } + public LibJpegTools.ComponentData[] Components { get; private set; } - internal SpectralData(LibJpegTools.ComponentData[] components) - { - this.ComponentCount = components.Length; - this.Components = components; - } + internal SpectralData(LibJpegTools.ComponentData[] components) + { + this.ComponentCount = components.Length; + this.Components = components; + } - public Image TryCreateRGBSpectralImage() + public Image TryCreateRGBSpectralImage() + { + if (this.ComponentCount != 3) { - if (this.ComponentCount != 3) - { - return null; - } + return null; + } - LibJpegTools.ComponentData c0 = this.Components[0]; - LibJpegTools.ComponentData c1 = this.Components[1]; - LibJpegTools.ComponentData c2 = this.Components[2]; + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; - if (c0.Size != c1.Size || c1.Size != c2.Size) - { - return null; - } + if (c0.Size != c1.Size || c1.Size != c2.Size) + { + return null; + } - var result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); + var result = new Image(c0.WidthInBlocks * 8, c0.HeightInBlocks * 8); - for (int by = 0; by < c0.HeightInBlocks; by++) + for (int by = 0; by < c0.HeightInBlocks; by++) + { + for (int bx = 0; bx < c0.WidthInBlocks; bx++) { - for (int bx = 0; bx < c0.WidthInBlocks; bx++) - { - this.WriteToImage(bx, by, result); - } + this.WriteToImage(bx, by, result); } - - return result; } - internal void WriteToImage(int bx, int by, Image image) - { - LibJpegTools.ComponentData c0 = this.Components[0]; - LibJpegTools.ComponentData c1 = this.Components[1]; - LibJpegTools.ComponentData c2 = this.Components[2]; + return result; + } - Block8x8 block0 = c0.SpectralBlocks[bx, by]; - Block8x8 block1 = c1.SpectralBlocks[bx, by]; - Block8x8 block2 = c2.SpectralBlocks[bx, by]; + internal void WriteToImage(int bx, int by, Image image) + { + LibJpegTools.ComponentData c0 = this.Components[0]; + LibJpegTools.ComponentData c1 = this.Components[1]; + LibJpegTools.ComponentData c2 = this.Components[2]; - float d0 = c0.MaxVal - c0.MinVal; - float d1 = c1.MaxVal - c1.MinVal; - float d2 = c2.MaxVal - c2.MinVal; + Block8x8 block0 = c0.SpectralBlocks[bx, by]; + Block8x8 block1 = c1.SpectralBlocks[bx, by]; + Block8x8 block2 = c2.SpectralBlocks[bx, by]; - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - float val0 = c0.GetBlockValue(block0, x, y); - float val1 = c0.GetBlockValue(block1, x, y); - float val2 = c0.GetBlockValue(block2, x, y); - - var v = new Vector4(val0, val1, val2, 1); - Rgba32 color = default; - color.FromVector4(v); - - int yy = (by * 8) + y; - int xx = (bx * 8) + x; - image[xx, yy] = color; - } - } - } + float d0 = c0.MaxVal - c0.MinVal; + float d1 = c1.MaxVal - c1.MinVal; + float d2 = c2.MaxVal - c2.MinVal; - public bool Equals(SpectralData other) + for (int y = 0; y < 8; y++) { - if (other is null) + for (int x = 0; x < 8; x++) { - return false; - } + float val0 = c0.GetBlockValue(block0, x, y); + float val1 = c0.GetBlockValue(block1, x, y); + float val2 = c0.GetBlockValue(block2, x, y); - if (ReferenceEquals(this, other)) - { - return true; - } + var v = new Vector4(val0, val1, val2, 1); + Rgba32 color = default; + color.FromVector4(v); - if (this.ComponentCount != other.ComponentCount) - { - return false; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; + image[xx, yy] = color; } + } + } - for (int i = 0; i < this.ComponentCount; i++) - { - LibJpegTools.ComponentData a = this.Components[i]; - LibJpegTools.ComponentData b = other.Components[i]; - if (!a.Equals(b)) - { - return false; - } - } + public bool Equals(SpectralData other) + { + if (other is null) + { + return false; + } + if (ReferenceEquals(this, other)) + { return true; } - public override bool Equals(object obj) + if (this.ComponentCount != other.ComponentCount) { - return obj is SpectralData other && this.Equals(other); + return false; } - public override int GetHashCode() + for (int i = 0; i < this.ComponentCount; i++) { - unchecked + LibJpegTools.ComponentData a = this.Components[i]; + LibJpegTools.ComponentData b = other.Components[i]; + if (!a.Equals(b)) { - return (this.ComponentCount * 397) ^ (this.Components?[0].GetHashCode() ?? 0); + return false; } } - public static bool operator ==(SpectralData left, SpectralData right) - { - if (ReferenceEquals(left, right)) - { - return true; - } + return true; + } + + public override bool Equals(object obj) + { + return obj is SpectralData other && this.Equals(other); + } - return left.Equals(right); + public override int GetHashCode() + { + unchecked + { + return (this.ComponentCount * 397) ^ (this.Components?[0].GetHashCode() ?? 0); } + } - public static bool operator !=(SpectralData left, SpectralData right) + public static bool operator ==(SpectralData left, SpectralData right) + { + if (ReferenceEquals(left, right)) { - return !(left == right); + return true; } + + return left.Equals(right); + } + + public static bool operator !=(SpectralData left, SpectralData right) + { + return !(left == right); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index f5a0599e4b..bd1df33772 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -1,143 +1,140 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -using System.IO; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +/// +/// Utilities to read raw libjpeg data for reference conversion. +/// +internal static partial class LibJpegTools { - /// - /// Utilities to read raw libjpeg data for reference conversion. - /// - internal static partial class LibJpegTools + public static (double Total, double Average) CalculateDifference(ComponentData expected, ComponentData actual) { - public static (double Total, double Average) CalculateDifference(ComponentData expected, ComponentData actual) + BigInteger totalDiff = 0; + if (actual.WidthInBlocks < expected.WidthInBlocks) { - BigInteger totalDiff = 0; - if (actual.WidthInBlocks < expected.WidthInBlocks) - { - throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); - } + throw new Exception("actual.WidthInBlocks < expected.WidthInBlocks"); + } - if (actual.HeightInBlocks < expected.HeightInBlocks) - { - throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); - } + if (actual.HeightInBlocks < expected.HeightInBlocks) + { + throw new Exception("actual.HeightInBlocks < expected.HeightInBlocks"); + } - int w = expected.WidthInBlocks; - int h = expected.HeightInBlocks; - for (int y = 0; y < h; y++) + int w = expected.WidthInBlocks; + int h = expected.HeightInBlocks; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) { - for (int x = 0; x < w; x++) - { - Block8x8 aa = expected.SpectralBlocks[x, y]; - Block8x8 bb = actual.SpectralBlocks[x, y]; + Block8x8 aa = expected.SpectralBlocks[x, y]; + Block8x8 bb = actual.SpectralBlocks[x, y]; - long diff = Block8x8.TotalDifference(ref aa, ref bb); - totalDiff += diff; - } + long diff = Block8x8.TotalDifference(ref aa, ref bb); + totalDiff += diff; } - - int count = w * h; - double total = (double)totalDiff; - double average = (double)totalDiff / (count * Block8x8.Size); - return (total, average); } - private static string DumpToolFullPath => Path.Combine( - TestEnvironment.ToolsDirectoryFullPath, - @"jpeg\dump-jpeg-coeffs.exe"); - - /// - /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! - /// See: - /// - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md - /// - /// - public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) - { - if (!TestEnvironment.IsWindows) - { - throw new InvalidOperationException("Can't run dump-jpeg-coeffs.exe in non-Windows environment. Skip this test on Linux/Unix!"); - } + int count = w * h; + double total = (double)totalDiff; + double average = (double)totalDiff / (count * Block8x8.Size); + return (total, average); + } - string args = $@"""{sourceFile}"" ""{destFile}"""; - var process = new Process - { - StartInfo = - { - FileName = DumpToolFullPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden - } - }; - process.Start(); - process.WaitForExit(); + private static string DumpToolFullPath => Path.Combine( + TestEnvironment.ToolsDirectoryFullPath, + @"jpeg\dump-jpeg-coeffs.exe"); + + /// + /// Executes 'dump-jpeg-coeffs.exe' for the given jpeg image file, saving the libjpeg spectral data into 'destFile'. Windows only! + /// See: + /// + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md + /// + /// + public static void RunDumpJpegCoeffsTool(string sourceFile, string destFile) + { + if (!TestEnvironment.IsWindows) + { + throw new InvalidOperationException("Can't run dump-jpeg-coeffs.exe in non-Windows environment. Skip this test on Linux/Unix!"); } - /// - /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! - /// See: - /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md - /// - public static SpectralData ExtractSpectralData(string inputFile) + string args = $@"""{sourceFile}"" ""{destFile}"""; + var process = new Process { - TestFile testFile = TestFile.Create(inputFile); + StartInfo = + { + FileName = DumpToolFullPath, + Arguments = args, + WindowStyle = ProcessWindowStyle.Hidden + } + }; + process.Start(); + process.WaitForExit(); + } - string outDir = TestEnvironment.CreateOutputDirectory(".Temp", "JpegCoeffs"); - string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; - string coeffFileFullPath = Path.Combine(outDir, fn); + /// + /// Extract libjpeg from the given jpg file with 'dump-jpeg-coeffs.exe'. Windows only! + /// See: + /// https://github.com/SixLabors/Imagesharp.Tests.Images/blob/main/tools/jpeg/README.md + /// + public static SpectralData ExtractSpectralData(string inputFile) + { + TestFile testFile = TestFile.Create(inputFile); + + string outDir = TestEnvironment.CreateOutputDirectory(".Temp", "JpegCoeffs"); + string fn = $"{Path.GetFileName(inputFile)}-{new Random().Next(1000)}.dctcoeffs"; + string coeffFileFullPath = Path.Combine(outDir, fn); - try + try + { + RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + + using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) + using (var rdr = new BinaryReader(dumpStream)) { - RunDumpJpegCoeffsTool(testFile.FullPath, coeffFileFullPath); + int componentCount = rdr.ReadInt16(); + var result = new ComponentData[componentCount]; - using (var dumpStream = new FileStream(coeffFileFullPath, FileMode.Open)) - using (var rdr = new BinaryReader(dumpStream)) + for (int i = 0; i < componentCount; i++) { - int componentCount = rdr.ReadInt16(); - var result = new ComponentData[componentCount]; + int widthInBlocks = rdr.ReadInt16(); + int heightInBlocks = rdr.ReadInt16(); + var resultComponent = new ComponentData(widthInBlocks, heightInBlocks, i); + result[i] = resultComponent; + } - for (int i = 0; i < componentCount; i++) - { - int widthInBlocks = rdr.ReadInt16(); - int heightInBlocks = rdr.ReadInt16(); - var resultComponent = new ComponentData(widthInBlocks, heightInBlocks, i); - result[i] = resultComponent; - } + var buffer = new byte[64 * sizeof(short)]; - var buffer = new byte[64 * sizeof(short)]; + for (int i = 0; i < result.Length; i++) + { + ComponentData c = result[i]; - for (int i = 0; i < result.Length; i++) + for (int y = 0; y < c.HeightInBlocks; y++) { - ComponentData c = result[i]; - - for (int y = 0; y < c.HeightInBlocks; y++) + for (int x = 0; x < c.WidthInBlocks; x++) { - for (int x = 0; x < c.WidthInBlocks; x++) - { - rdr.Read(buffer, 0, buffer.Length); + rdr.Read(buffer, 0, buffer.Length); - short[] block = MemoryMarshal.Cast(buffer.AsSpan()).ToArray(); - c.MakeBlock(block, y, x); - } + short[] block = MemoryMarshal.Cast(buffer.AsSpan()).ToArray(); + c.MakeBlock(block, y, x); } } - - return new SpectralData(result); } + + return new SpectralData(result); } - finally + } + finally + { + if (File.Exists(coeffFileFullPath)) { - if (File.Exists(coeffFileFullPath)) - { - File.Delete(coeffFileFullPath); - } + File.Delete(coeffFileFullPath); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs index e9f6dbbdb9..b6e7b29a62 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -1,137 +1,135 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class ReferenceImplementations { - internal static partial class ReferenceImplementations + /// + /// True accurate FDCT/IDCT implementations. We should test everything against them! + /// Based on: + /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 + /// Claiming: + /// /* reference idct taken from "ieeetest.c" + /// * Written by Tom Lane (tgl@cs.cmu.edu). + /// * Released to public domain 11/22/93. + /// */ + /// + internal static class AccurateDCT { - /// - /// True accurate FDCT/IDCT implementations. We should test everything against them! - /// Based on: - /// https://github.com/keithw/mympeg2enc/blob/master/idct.c#L222 - /// Claiming: - /// /* reference idct taken from "ieeetest.c" - /// * Written by Tom Lane (tgl@cs.cmu.edu). - /// * Released to public domain 11/22/93. - /// */ - /// - internal static class AccurateDCT + private static readonly double[,] CosLut = InitCosLut(); + + public static Block8x8 TransformIDCT(ref Block8x8 block) { - private static readonly double[,] CosLut = InitCosLut(); + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformIDCT(ref temp); + return res0.RoundAsInt16Block(); + } - public static Block8x8 TransformIDCT(ref Block8x8 block) - { - Block8x8F temp = block.AsFloatBlock(); - Block8x8F res0 = TransformIDCT(ref temp); - return res0.RoundAsInt16Block(); - } + public static void TransformIDCTInplace(Span span) + { + var temp = default(Block8x8); + temp.LoadFrom(span); + Block8x8 result = TransformIDCT(ref temp); + result.CopyTo(span); + } - public static void TransformIDCTInplace(Span span) - { - var temp = default(Block8x8); - temp.LoadFrom(span); - Block8x8 result = TransformIDCT(ref temp); - result.CopyTo(span); - } + public static Block8x8 TransformFDCT(ref Block8x8 block) + { + Block8x8F temp = block.AsFloatBlock(); + Block8x8F res0 = TransformFDCT(ref temp); + return res0.RoundAsInt16Block(); + } - public static Block8x8 TransformFDCT(ref Block8x8 block) - { - Block8x8F temp = block.AsFloatBlock(); - Block8x8F res0 = TransformFDCT(ref temp); - return res0.RoundAsInt16Block(); - } + public static void TransformFDCTInplace(Span span) + { + var temp = default(Block8x8); + temp.LoadFrom(span); + Block8x8 result = TransformFDCT(ref temp); + result.CopyTo(span); + } - public static void TransformFDCTInplace(Span span) - { - var temp = default(Block8x8); - temp.LoadFrom(span); - Block8x8 result = TransformFDCT(ref temp); - result.CopyTo(span); - } + public static Block8x8F TransformIDCT(ref Block8x8F block) + { + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default; - public static Block8x8F TransformIDCT(ref Block8x8F block) + for (y = 0; y < 8; y++) { - int x, y, u, v; - double tmp, tmp2; - Block8x8F res = default; - - for (y = 0; y < 8; y++) + for (x = 0; x < 8; x++) { - for (x = 0; x < 8; x++) + tmp = 0.0; + for (v = 0; v < 8; v++) { - tmp = 0.0; - for (v = 0; v < 8; v++) + tmp2 = 0.0; + for (u = 0; u < 8; u++) { - tmp2 = 0.0; - for (u = 0; u < 8; u++) - { - tmp2 += block[(v * 8) + u] * CosLut[x, u]; - } - - tmp += CosLut[y, v] * tmp2; + tmp2 += block[(v * 8) + u] * CosLut[x, u]; } - res[(y * 8) + x] = (float)tmp; + tmp += CosLut[y, v] * tmp2; } - } - return res; + res[(y * 8) + x] = (float)tmp; + } } - public static Block8x8F TransformFDCT(ref Block8x8F block) - { - int x, y, u, v; - double tmp, tmp2; - Block8x8F res = default; + return res; + } + + public static Block8x8F TransformFDCT(ref Block8x8F block) + { + int x, y, u, v; + double tmp, tmp2; + Block8x8F res = default; - for (v = 0; v < 8; v++) + for (v = 0; v < 8; v++) + { + for (u = 0; u < 8; u++) { - for (u = 0; u < 8; u++) + tmp = 0.0; + for (y = 0; y < 8; y++) { - tmp = 0.0; - for (y = 0; y < 8; y++) + tmp2 = 0.0; + for (x = 0; x < 8; x++) { - tmp2 = 0.0; - for (x = 0; x < 8; x++) - { - tmp2 += block[(y * 8) + x] * CosLut[x, u]; - } - - tmp += CosLut[y, v] * tmp2; + tmp2 += block[(y * 8) + x] * CosLut[x, u]; } - res[(v * 8) + u] = (float)tmp; + tmp += CosLut[y, v] * tmp2; } - } - return res; + res[(v * 8) + u] = (float)tmp; + } } - private static double[,] InitCosLut() - { - double[,] coslu = new double[8, 8]; - int a, b; - double tmp; + return res; + } + + private static double[,] InitCosLut() + { + double[,] coslu = new double[8, 8]; + int a, b; + double tmp; - for (a = 0; a < 8; a++) + for (a = 0; a < 8; a++) + { + for (b = 0; b < 8; b++) { - for (b = 0; b < 8; b++) + tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); + if (b == 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; + tmp /= Math.Sqrt(2.0); } - } - return coslu; + 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 081deceb62..418c772da9 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,71 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class ReferenceImplementations { - internal static partial class ReferenceImplementations + /// + /// 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 { - /// - /// 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++) { - 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 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 mx09 = mx00 + mx04; - float mx0a = mx01 + mx03; - float mx0b = 1.4142135623731f * mx02; - float mx0c = mx00 - mx04; - float mx0d = mx01 - mx03; - float mx0e = 0.353553390593274f * (mx09 - mx0b); - float mx0f = 0.353553390593274f * (mx0c + mx0d); - float mx10 = 0.353553390593274f * (mx0c - mx0d); - float mx11 = 1.4142135623731f * mx06; - float mx12 = mx05 + mx07; - float mx13 = mx05 - mx07; - 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[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 = dst.Slice(8); - src = src.Slice(8); - } + 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 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 mx09 = mx00 + mx04; + float mx0a = mx01 + mx03; + float mx0b = 1.4142135623731f * mx02; + float mx0c = mx00 - mx04; + float mx0d = mx01 - mx03; + float mx0e = 0.353553390593274f * (mx09 - mx0b); + float mx0f = 0.353553390593274f * (mx0c + mx0d); + float mx10 = 0.353553390593274f * (mx0c - mx0d); + float mx11 = 1.4142135623731f * mx06; + float mx12 = mx05 + mx07; + float mx13 = mx05 - mx07; + 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[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 = dst.Slice(8); + src = src.Slice(8); } + } - public static void IDCT8x8GT(Span s, Span d) - { - Idct81d_GT(s, d); + public static void IDCT8x8GT(Span s, Span d) + { + Idct81d_GT(s, d); - Transpose8x8(d); + Transpose8x8(d); - Idct81d_GT(d, d); + Idct81d_GT(d, d); - Transpose8x8(d); - } + Transpose8x8(d); } } } 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 e8c6b0d816..9e0c62d139 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,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -9,548 +8,547 @@ using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class ReferenceImplementations { - internal static partial class ReferenceImplementations + /// + /// Contains port of non-optimized methods in: + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp + /// + /// *** Paper *** + /// paper LLM89 + /// C. Loeffler, A. Ligtenberg, and G. S. Moschytz, + /// "Practical fast 1-D DCT algorithms with 11 multiplications," + /// Proc. Int'l. Conf. on Acoustics, Speech, and Signal Processing (ICASSP89), pp. 988-991, 1989. + /// + /// The main purpose of this code is testing and documentation, it is intended to be similar to it's original counterpart. + /// DO NOT clean it! + /// DO NOT StyleCop it! + /// + internal static class LLM_FloatingPoint_DCT { - /// - /// Contains port of non-optimized methods in: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp - /// - /// *** Paper *** - /// paper LLM89 - /// C. Loeffler, A. Ligtenberg, and G. S. Moschytz, - /// "Practical fast 1-D DCT algorithms with 11 multiplications," - /// Proc. Int'l. Conf. on Acoustics, Speech, and Signal Processing (ICASSP89), pp. 988-991, 1989. - /// - /// The main purpose of this code is testing and documentation, it is intended to be similar to it's original counterpart. - /// DO NOT clean it! - /// DO NOT StyleCop it! - /// - internal static class LLM_FloatingPoint_DCT + public static Block8x8F TransformIDCT(ref Block8x8F source) { - public static Block8x8F TransformIDCT(ref Block8x8F source) - { - float[] s = new float[64]; - source.ScaledCopyTo(s); - float[] d = new float[64]; - float[] temp = new float[64]; - - IDCT2D_llm(s, d, temp); - Block8x8F result = default; - result.LoadFrom(d); - return result; - } + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + IDCT2D_llm(s, d, temp); + Block8x8F result = default; + result.LoadFrom(d); + return result; + } - public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) - { - float[] s = new float[64]; - source.ScaledCopyTo(s); - float[] d = new float[64]; - float[] temp = new float[64]; - - FDCT2D_llm(s, d, temp); - Block8x8F result = default; - result.LoadFrom(d); - return result; - } + public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) + { + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; + + FDCT2D_llm(s, d, temp); + Block8x8F result = default; + result.LoadFrom(d); + return result; + } - private static double Cos(double x) => Math.Cos(x); + private static double Cos(double x) => Math.Cos(x); - private const double M_PI = Math.PI; + private const double M_PI = Math.PI; - private static readonly double M_SQRT2 = Math.Sqrt(2); + private static readonly double M_SQRT2 = Math.Sqrt(2); - public static float[] PrintConstants(ITestOutputHelper output) + public static float[] PrintConstants(ITestOutputHelper output) + { + float[] r = new float[8]; + for (int i = 0; i < 8; i++) { - 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; + r[i] = (float)(Cos(i / 16.0 * M_PI) * M_SQRT2); + output?.WriteLine($"float r{i} = {r[i]:R}f;"); } -#pragma warning disable 219 - - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 - /// - 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; - float r3 = 1.17587554f; - float r4 = 1f; - float r5 = 0.785694957f; - float r6 = 0.5411961f; - float r7 = 0.27589938f; - - z0 = y[1] + y[7]; - z1 = y[3] + y[5]; - z2 = y[3] + y[7]; - z3 = y[1] + y[5]; - z4 = (z0 + z1) * r3; - - z0 = z0 * (-r3 + r7); - z1 = z1 * (-r3 - r1); - 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; - - 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)); - a0 = z0 + z3; - a3 = z0 - z3; - a1 = z1 + z2; - a2 = z1 - z2; - - x[0] = a0 + b0; - x[7] = a0 - b0; - x[1] = a1 + b1; - x[6] = a1 - b1; - x[2] = a2 + b2; - x[5] = a2 - b2; - x[3] = a3 + b3; - x[4] = a3 - b3; - } + return r; + } - /// - /// 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) - { - int j; +#pragma warning disable 219 - for (j = 0; j < 8; j++) - { - IDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); - } + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 + /// + 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; + float r3 = 1.17587554f; + float r4 = 1f; + float r5 = 0.785694957f; + float r6 = 0.5411961f; + float r7 = 0.27589938f; + + z0 = y[1] + y[7]; + z1 = y[3] + y[5]; + z2 = y[3] + y[7]; + z3 = y[1] + y[5]; + z4 = (z0 + z1) * r3; + + z0 = z0 * (-r3 + r7); + z1 = z1 * (-r3 - r1); + 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; + + 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)); + a0 = z0 + z3; + a3 = z0 - z3; + a1 = z1 + z2; + a2 = z1 - z2; + + x[0] = a0 + b0; + x[7] = a0 - b0; + x[1] = a1 + b1; + x[6] = a1 - b1; + x[2] = a2 + b2; + x[5] = a2 - b2; + x[3] = a3 + b3; + x[4] = a3 - b3; + } - Transpose8x8(temp, d); + /// + /// 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) + { + int j; - for (j = 0; j < 8; j++) - { - IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } + for (j = 0; j < 8; j++) + { + IDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + } - Transpose8x8(temp, d); + Transpose8x8(temp, d); - for (j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } + for (j = 0; j < 8; j++) + { + IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } - /// - /// Original: - /// - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 - /// - /// - /// Source - /// Destination - public static void FDCT2D8x4_32f(Span s, Span d) + Transpose8x8(temp, d); + + for (j = 0; j < 64; j++) { - Vector4 c0 = _mm_load_ps(s, 0); - Vector4 c1 = _mm_load_ps(s, 56); - 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; - - c1 = _mm_load_ps(s, 40); - c0 = _mm_load_ps(s, 16); - 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; - - /* - c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; - c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; - c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; - 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; c3 = t0 - t3; - c1 = t1 + t2; c2 = t1 - t2; - */ - - _mm_store_ps(d, 0, c0 + c1); - - _mm_store_ps(d, 32, c0 - c1); - - /*y[0] = c0 + c1; - y[4] = c0 - c1;*/ - - 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, 48, (w0 * c3) - (w1 * c2)); - /* - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; - */ - - w0 = new Vector4(1.175876f); - w1 = new Vector4(0.785695f); - c3 = (w0 * t4) + (w1 * t7); - c0 = (w0 * t7) - (w1 * t4); - /* - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - */ - - w0 = new Vector4(1.387040f); - w1 = new Vector4(0.275899f); - 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, 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); - _mm_store_ps(d, 56, c0 - c3); - - // y[1] = c0 + c3; y[7] = c0 - c3; - /*for(i = 0;i < 8;i++) - { - y[i] *= invsqrt2h; - }*/ + d[j] *= 0.125f; } + } - public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) + /// + /// Original: + /// + /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15 + /// + /// + /// Source + /// Destination + 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; + + c1 = _mm_load_ps(s, 48); + c0 = _mm_load_ps(s, 8); + 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; + + c0 = _mm_load_ps(s, 24); + c1 = _mm_load_ps(s, 32); + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; + + /* + c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; + c1 = x[1]; c2 = x[6]; t1 = c1 + c2; t6 = c1 - c2; + c1 = x[2]; c2 = x[5]; t2 = c1 + c2; t5 = c1 - c2; + 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; c3 = t0 - t3; + c1 = t1 + t2; c2 = t1 - t2; + */ + + _mm_store_ps(d, 0, c0 + c1); + + _mm_store_ps(d, 32, c0 - c1); + + /*y[0] = c0 + c1; + y[4] = c0 - c1;*/ + + 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, 48, (w0 * c3) - (w1 * c2)); + /* + y[2] = c2 * r[6] + c3 * r[2]; + y[6] = c3 * r[6] - c2 * r[2]; + */ + + w0 = new Vector4(1.175876f); + w1 = new Vector4(0.785695f); + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); + /* + c3 = t4 * r[3] + t7 * r[5]; + c0 = t7 * r[3] - t4 * r[5]; + */ + + w0 = new Vector4(1.387040f); + w1 = new Vector4(0.275899f); + 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, 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); + _mm_store_ps(d, 56, c0 - c3); + + // y[1] = c0 + c3; y[7] = c0 - c3; + /*for(i = 0;i < 8;i++) { - Transpose8x8(s, temp); + y[i] *= invsqrt2h; + }*/ + } - FDCT2D8x4_32f(temp, d); + public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) + { + Transpose8x8(s, temp); + + FDCT2D8x4_32f(temp, d); - FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); - Transpose8x8(d, temp); + 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); + var c = new Vector4(0.1250f); #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 + _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)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] #pragma warning disable SA1300 // Element should begin with upper-case letter - private static Vector4 _mm_load_ps(Span src, int offset) + 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]); - } + { + src = src.Slice(offset); + return new Vector4(src[0], src[1], src[2], src[3]); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [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) + 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; - dest[1] = src.Y; - dest[2] = src.Z; - dest[3] = src.W; - } + { + dest = dest.Slice(offset); + dest[0] = src.X; + dest[1] = src.Y; + dest[2] = src.Z; + dest[3] = src.W; + } - // Accurate variants of constants from: - // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c + // 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_175876 = new Vector4(1.175875602f); - private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); + private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); - private static readonly Vector4 _0_390181 = new Vector4(-0.390180644f); + private static readonly Vector4 _0_390181 = new Vector4(-0.390180644f); - private static readonly Vector4 _0_899976 = new Vector4(-0.899976223f); + private static readonly Vector4 _0_899976 = new Vector4(-0.899976223f); - private static readonly Vector4 _2_562915 = new Vector4(-2.562915447f); + private static readonly Vector4 _2_562915 = new Vector4(-2.562915447f); - private static readonly Vector4 _0_298631 = new Vector4(0.298631336f); + private static readonly Vector4 _0_298631 = new Vector4(0.298631336f); - private static readonly Vector4 _2_053120 = new Vector4(2.053119869f); + private static readonly Vector4 _2_053120 = new Vector4(2.053119869f); - private static readonly Vector4 _3_072711 = new Vector4(3.072711026f); + private static readonly Vector4 _3_072711 = new Vector4(3.072711026f); - private static readonly Vector4 _1_501321 = new Vector4(1.501321110f); + private static readonly Vector4 _1_501321 = new Vector4(1.501321110f); - private static readonly Vector4 _0_541196 = new Vector4(0.541196100f); + private static readonly Vector4 _0_541196 = new Vector4(0.541196100f); - private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); + private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); - private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); + 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) - { - /* - float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; - for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } - */ - /* - 0: 1.414214 - 1: 1.387040 - 2: 1.306563 - 3: - 4: 1.000000 - 5: 0.785695 - 6: - 7: 0.275899 - */ - - Vector4 my1 = _mm_load_ps(y, 8); - Vector4 my7 = _mm_load_ps(y, 56); - Vector4 mz0 = my1 + my7; - - Vector4 my3 = _mm_load_ps(y, 24); - Vector4 mz2 = my3 + my7; - Vector4 my5 = _mm_load_ps(y, 40); - 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]; - mz2 = (mz2 * _1_961571) + mz4; - mz3 = (mz3 * _0_390181) + mz4; - mz0 = mz0 * _0_899976; - mz1 = mz1 * _2_562915; - - /* - -0.899976 - -2.562915 - -1.961571 - -0.390181 - z0 = z0 * (-r[3] + r[7]); - z1 = z1 * (-r[3] - r[1]); - 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; - - /* - 0.298631 - 2.053120 - 3.072711 - 1.501321 - b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; - b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; - b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; - b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; - */ - - Vector4 my2 = _mm_load_ps(y, 16); - Vector4 my6 = _mm_load_ps(y, 48); - mz4 = (my2 + my6) * _0_541196; - Vector4 my0 = _mm_load_ps(y, 0); - Vector4 my4 = _mm_load_ps(y, 32); - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * _1_847759); - mz3 = mz4 + (my2 * _0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - /* - 1.847759 - 0.765367 - z4 = (y[2] + y[6]) * r[6]; - z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r[2] + r[6]); - z3 = z4 + y[2] * (r[2] - r[6]); - a0 = z0 + z3; a3 = z0 - z3; - a1 = z1 + z2; a2 = z1 - z2; - */ - - _mm_store_ps(x, 0, my0 + mb0); - - _mm_store_ps(x, 56, my0 - mb0); - - _mm_store_ps(x, 8, my1 + mb1); - - _mm_store_ps(x, 48, my1 - mb1); - - _mm_store_ps(x, 16, my2 + mb2); - - _mm_store_ps(x, 40, my2 - mb2); - - _mm_store_ps(x, 24, my3 + mb3); - - _mm_store_ps(x, 32, my3 - mb3); - /* - x[0] = a0 + b0; x[7] = a0 - b0; - x[1] = a1 + b1; x[6] = a1 - b1; - x[2] = a2 + b2; x[5] = a2 - b2; - x[3] = a3 + b3; x[4] = a3 - b3; - for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } - */ - } + /// + /// 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) + { + /* + float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; + for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + */ + /* + 0: 1.414214 + 1: 1.387040 + 2: 1.306563 + 3: + 4: 1.000000 + 5: 0.785695 + 6: + 7: 0.275899 + */ + + Vector4 my1 = _mm_load_ps(y, 8); + Vector4 my7 = _mm_load_ps(y, 56); + Vector4 mz0 = my1 + my7; + + Vector4 my3 = _mm_load_ps(y, 24); + Vector4 mz2 = my3 + my7; + Vector4 my5 = _mm_load_ps(y, 40); + 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]; + mz2 = (mz2 * _1_961571) + mz4; + mz3 = (mz3 * _0_390181) + mz4; + mz0 = mz0 * _0_899976; + mz1 = mz1 * _2_562915; + + /* + -0.899976 + -2.562915 + -1.961571 + -0.390181 + z0 = z0 * (-r[3] + r[7]); + z1 = z1 * (-r[3] - r[1]); + 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; + + /* + 0.298631 + 2.053120 + 3.072711 + 1.501321 + b3 = y[7] * (-r[1] + r[3] + r[5] - r[7]) + z0 + z2; + b2 = y[5] * ( r[1] + r[3] - r[5] + r[7]) + z1 + z3; + b1 = y[3] * ( r[1] + r[3] + r[5] - r[7]) + z1 + z2; + b0 = y[1] * ( r[1] + r[3] - r[5] - r[7]) + z0 + z3; + */ + + Vector4 my2 = _mm_load_ps(y, 16); + Vector4 my6 = _mm_load_ps(y, 48); + mz4 = (my2 + my6) * _0_541196; + Vector4 my0 = _mm_load_ps(y, 0); + Vector4 my4 = _mm_load_ps(y, 32); + mz0 = my0 + my4; + mz1 = my0 - my4; + + mz2 = mz4 + (my6 * _1_847759); + mz3 = mz4 + (my2 * _0_765367); + + my0 = mz0 + mz3; + my3 = mz0 - mz3; + my1 = mz1 + mz2; + my2 = mz1 - mz2; + /* + 1.847759 + 0.765367 + z4 = (y[2] + y[6]) * r[6]; + z0 = y[0] + y[4]; z1 = y[0] - y[4]; + z2 = z4 - y[6] * (r[2] + r[6]); + z3 = z4 + y[2] * (r[2] - r[6]); + a0 = z0 + z3; a3 = z0 - z3; + a1 = z1 + z2; a2 = z1 - z2; + */ + + _mm_store_ps(x, 0, my0 + mb0); + + _mm_store_ps(x, 56, my0 - mb0); + + _mm_store_ps(x, 8, my1 + mb1); + + _mm_store_ps(x, 48, my1 - mb1); + + _mm_store_ps(x, 16, my2 + mb2); + + _mm_store_ps(x, 40, my2 - mb2); + + _mm_store_ps(x, 24, my3 + mb3); + + _mm_store_ps(x, 32, my3 - mb3); + /* + x[0] = a0 + b0; x[7] = a0 - b0; + x[1] = a1 + b1; x[6] = a1 - b1; + x[2] = a2 + b2; x[5] = a2 - b2; + x[3] = a3 + b3; x[4] = a3 - b3; + for(i = 0;i < 8;i++){ x[i] *= 0.353554f; } + */ + } - internal static void FDCT1Dllm_32f(Span x, Span y) - { - float t0, t1, t2, t3, t4, t5, t6, t7; - float c0, c1, c2, c3; - float[] r = new float[8]; - - // 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; - r[3] = 1.175876f; - r[4] = 1.000000f; - r[5] = 0.785695f; - 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; - c1 = x[0]; - c2 = x[7]; - t0 = c1 + c2; - t7 = c1 - c2; - c1 = x[1]; - c2 = x[6]; - t1 = c1 + c2; - t6 = c1 - c2; - c1 = x[2]; - c2 = x[5]; - t2 = c1 + c2; - t5 = c1 - c2; - c1 = x[3]; - c2 = x[4]; - t3 = c1 + c2; - t4 = c1 - c2; - - c0 = t0 + t3; - c3 = t0 - t3; - c1 = t1 + t2; - c2 = t1 - t2; - - y[0] = c0 + c1; - y[4] = c0 - c1; - 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]); - - y[5] = c3 - c1; - y[3] = c0 - c2; - c0 = (c0 + c2) * invsqrt2; - c3 = (c3 + c1) * invsqrt2; - y[1] = c0 + c3; - y[7] = c0 - c3; - } + internal static void FDCT1Dllm_32f(Span x, Span y) + { + float t0, t1, t2, t3, t4, t5, t6, t7; + float c0, c1, c2, c3; + float[] r = new float[8]; + + // 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; + r[3] = 1.175876f; + r[4] = 1.000000f; + r[5] = 0.785695f; + 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; + c1 = x[0]; + c2 = x[7]; + t0 = c1 + c2; + t7 = c1 - c2; + c1 = x[1]; + c2 = x[6]; + t1 = c1 + c2; + t6 = c1 - c2; + c1 = x[2]; + c2 = x[5]; + t2 = c1 + c2; + t5 = c1 - c2; + c1 = x[3]; + c2 = x[4]; + t3 = c1 + c2; + t4 = c1 - c2; + + c0 = t0 + t3; + c3 = t0 - t3; + c1 = t1 + t2; + c2 = t1 - t2; + + y[0] = c0 + c1; + y[4] = c0 - c1; + 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]); + + y[5] = c3 - c1; + y[3] = c0 - c2; + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; + y[1] = c0 + c3; + y[7] = c0 - c3; + } - internal static void FDCT2D_llm( - Span s, - Span d, - Span temp, - bool downscaleBy8 = false, - bool subtract128FromSource = false) - { - Span sWorker = subtract128FromSource ? s.AddScalarToAllValues(-128f) : s; + internal static void FDCT2D_llm( + Span s, + Span d, + Span temp, + bool downscaleBy8 = false, + bool subtract128FromSource = false) + { + Span sWorker = subtract128FromSource ? s.AddScalarToAllValues(-128f) : s; - for (int j = 0; j < 8; j++) - { - FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); - } + for (int j = 0; j < 8; j++) + { + FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + } - Transpose8x8(temp, d); + Transpose8x8(temp, d); - for (int j = 0; j < 8; j++) - { - FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); - } + for (int j = 0; j < 8; j++) + { + FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + } - Transpose8x8(temp, d); + Transpose8x8(temp, d); - if (downscaleBy8) + if (downscaleBy8) + { + for (int j = 0; j < 64; j++) { - for (int j = 0; j < 64; j++) - { - d[j] *= 0.125f; - } + d[j] *= 0.125f; } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index 194ec98a8d..95ab076b0b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -1,367 +1,365 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static partial class ReferenceImplementations { - internal static partial class ReferenceImplementations + /// + /// TODO: produces really bad results for bigger values! + /// + /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. + /// 1. ===== Forward DCT ===== + /// **** The original golang source claims: + /// It is based on the code in jfdctint.c from the Independent JPEG Group, + /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. + /// + /// **** Could be found here as well: + /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c + /// + /// 2. ===== Inverse DCT ===== + /// + /// The golang source claims: + /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz + /// The referenced MPEG2 code claims: + /// /**********************************************************/ + /// /* inverse two dimensional DCT, Chen-Wang algorithm */ + /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ + /// /* 32-bit integer arithmetic (8 bit coefficients) */ + /// /* 11 mults, 29 adds per DCT */ + /// /* sE, 18.8.91 */ + /// /**********************************************************/ + /// /* coefficients extended to 12 bit for IEEE1180-1990 */ + /// /* compliance sE, 2.1.94 */ + /// /**********************************************************/ + /// + /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: + /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c + /// + 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; + + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) + { + var temp = new int[Block8x8.Size]; + block.CopyTo(temp); + Subtract128_TransformFDCT_Upscale8_Inplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + + // [Obsolete("Looks like this method produces really bad results for bigger values!")] + public static Block8x8 TransformIDCT(ref Block8x8 block) + { + var temp = new int[Block8x8.Size]; + block.CopyTo(temp); + TransformIDCTInplace(temp); + var result = default(Block8x8); + result.LoadFrom(temp); + return result; + } + /// - /// TODO: produces really bad results for bigger values! - /// - /// Contains the "original" golang based DCT/IDCT implementations as reference implementations. - /// 1. ===== Forward DCT ===== - /// **** The original golang source claims: - /// It is based on the code in jfdctint.c from the Independent JPEG Group, - /// found at http://www.ijg.org/files/jpegsrc.v8c.tar.gz. - /// - /// **** Could be found here as well: - /// https://github.com/mozilla/mozjpeg/blob/master/jfdctint.c - /// - /// 2. ===== Inverse DCT ===== - /// - /// The golang source claims: - /// http://standards.iso.org/ittf/PubliclyAvailableStandards/ISO_IEC_13818-4_2004_Conformance_Testing/Video/verifier/mpeg2decode_960109.tar.gz - /// The referenced MPEG2 code claims: - /// /**********************************************************/ - /// /* inverse two dimensional DCT, Chen-Wang algorithm */ - /// /* (cf. IEEE ASSP-32, pp. 803-816, Aug. 1984) */ - /// /* 32-bit integer arithmetic (8 bit coefficients) */ - /// /* 11 mults, 29 adds per DCT */ - /// /* sE, 18.8.91 */ - /// /**********************************************************/ - /// /* coefficients extended to 12 bit for IEEE1180-1990 */ - /// /* compliance sE, 2.1.94 */ - /// /**********************************************************/ - /// - /// **** The code looks pretty similar to the standard libjpeg IDCT, but without quantization: - /// https://github.com/mozilla/mozjpeg/blob/master/jidctint.c + /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. + /// Leave results scaled up by an overall factor of 8. /// - public static class StandardIntegerDCT + /// The block of coefficients. + public static void Subtract128_TransformFDCT_Upscale8_Inplace(Span block) { - 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 - /// - private const int Bits = 13; - - /// - /// The number of bits to shift by on the first pass. - /// - private const int Pass1Bits = 2; - - /// - /// The value to shift by - /// - private const int CenterJSample = 128; - - public static Block8x8 Subtract128_TransformFDCT_Upscale8(ref Block8x8 block) + // Pass 1: process rows. + for (int y = 0; y < 8; y++) { - var temp = new int[Block8x8.Size]; - block.CopyTo(temp); - Subtract128_TransformFDCT_Upscale8_Inplace(temp); - var result = default(Block8x8); - result.LoadFrom(temp); - return result; + int y8 = y * 8; + + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; + + int tmp0 = x0 + x7; + int tmp1 = x1 + x6; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + 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); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + 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; + + tmp12 += z1; + tmp13 += z1; + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); } - // [Obsolete("Looks like this method produces really bad results for bigger values!")] - public static Block8x8 TransformIDCT(ref Block8x8 block) + // Pass 2: process columns. + // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. + for (int x = 0; x < 8; x++) { - var temp = new int[Block8x8.Size]; - block.CopyTo(temp); - TransformIDCTInplace(temp); - var result = default(Block8x8); - result.LoadFrom(temp); - return result; + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; + + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + 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); + + tmp10 = tmp0 + tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp0 + tmp2; + tmp13 = tmp1 + tmp3; + 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; + + tmp12 += z1; + tmp13 += z1; + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + 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; - /// - /// Performs a forward DCT on an 8x8 block of coefficients, including a level shift. - /// Leave results scaled up by an overall factor of 8. - /// - /// The block of coefficients. - public static void Subtract128_TransformFDCT_Upscale8_Inplace(Span block) + private const int R2 = 181; // 256/sqrt(2) + + /// + /// Performs a 2-D Inverse Discrete Cosine Transformation. + /// + /// The input coefficients should already have been multiplied by the + /// appropriate quantization table. We use fixed-point computation, with the + /// number of bits for the fractional component varying over the intermediate + /// stages. + /// + /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on + /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + /// + /// The source block of coefficients + public static void TransformIDCTInplace(Span src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) { - // Pass 1: process rows. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - int x0 = block[y8]; - int x1 = block[y8 + 1]; - int x2 = block[y8 + 2]; - int x3 = block[y8 + 3]; - int x4 = block[y8 + 4]; - int x5 = block[y8 + 5]; - int x6 = block[y8 + 6]; - int x7 = block[y8 + 7]; - - int tmp0 = x0 + x7; - int tmp1 = x1 + x6; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - 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); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - 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; - - tmp12 += z1; - tmp13 += z1; - block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); - block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); - block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); - } + int y8 = y * 8; - // Pass 2: process columns. - // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. - for (int x = 0; x < 8; x++) + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) { - int tmp0 = block[x] + block[56 + x]; - int tmp1 = block[8 + x] + block[48 + x]; - int tmp2 = block[16 + x] + block[40 + x]; - int tmp3 = block[24 + x] + block[32 + x]; - - int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = block[x] - block[56 + x]; - tmp1 = block[8 + x] - block[48 + x]; - tmp2 = block[16 + x] - block[40 + x]; - tmp3 = block[24 + x] - block[32 + x]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - 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); - - tmp10 = tmp0 + tmp3; - tmp11 = tmp1 + tmp2; - tmp12 = tmp0 + tmp2; - tmp13 = tmp1 + tmp3; - 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; - - tmp12 += z1; - tmp13 += z1; - block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); - block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); - block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); - block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + 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); + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = W6 * (x3 + x2); + x2 = x1 - (W2pw6 * x2); + x3 = x1 + (W2mw6 * x3); + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = ((R2 * (x4 + x5)) + 128) >> 8; + x4 = ((R2 * (x4 - x5)) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; } - 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. - /// - /// The input coefficients should already have been multiplied by the - /// appropriate quantization table. We use fixed-point computation, with the - /// number of bits for the fractional component varying over the intermediate - /// stages. - /// - /// For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - /// discrete W transform and for the discrete Fourier transform", IEEE Trans. on - /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - /// - /// The source block of coefficients - public static void TransformIDCTInplace(Span src) + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) - { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; - } - - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - 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); - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = W6 * (x3 + x2); - x2 = x1 - (W2pw6 * x2); - x3 = x1 + (W2mw6 * x3); - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = ((R2 * (x4 + x5)) + 128) >> 8; - x4 = ((R2 * (x4 - x5)) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; - } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) - { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - 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; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = (W6 * (y3 + y2)) + 4; - y2 = (y1 - (W2pw6 * y2)) >> 3; - y3 = (y1 + (W2mw6 * y3)) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = ((R2 * (y4 + y5)) + 128) >> 8; - y4 = ((R2 * (y4 - y5)) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; - } + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + 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; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = (W6 * (y3 + y2)) + 4; + y2 = (y1 - (W2pw6 * y2)) >> 3; + y3 = (y1 + (W2mw6 * y3)) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = ((R2 * (y4 + y5)) + 128) >> 8; + y4 = ((R2 * (y4 - y5)) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + 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 3dc332787e..ffbfae8029 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -1,128 +1,125 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +/// +/// This class contains simplified (inefficient) reference implementations to produce verification data for unit tests +/// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd +/// +internal static partial class ReferenceImplementations { - /// - /// This class contains simplified (inefficient) reference implementations to produce verification data for unit tests - /// Floating point DCT code Ported from https://github.com/norishigefukushima/dct_simd - /// - internal static partial class ReferenceImplementations + public static void DequantizeBlock(ref Block8x8F block, ref Block8x8F qt, ReadOnlySpan zigzag) { - public static void DequantizeBlock(ref Block8x8F block, ref Block8x8F qt, ReadOnlySpan zigzag) + for (int i = 0; i < Block8x8F.Size; i++) { - for (int i = 0; i < Block8x8F.Size; i++) - { - int zig = zigzag[i]; - block[zig] *= qt[i]; - } + int zig = zigzag[i]; + block[zig] *= qt[i]; } + } - /// - /// Transpose 8x8 block stored linearly in a (inplace) - /// - internal static void Transpose8x8(Span data) + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + internal static void Transpose8x8(Span data) + { + for (int i = 1; i < 8; i++) { - for (int i = 1; i < 8; i++) + int i8 = i * 8; + for (int j = 0; j < i; j++) { - int i8 = i * 8; - for (int j = 0; j < i; j++) - { - float tmp = data[i8 + j]; - data[i8 + j] = data[(j * 8) + i]; - data[(j * 8) + i] = tmp; - } + float tmp = data[i8 + j]; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; } } + } - /// - /// Transpose 8x8 block stored linearly in a (inplace) - /// - internal static void Transpose8x8(Span data) + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + internal static void Transpose8x8(Span data) + { + for (int i = 1; i < 8; i++) { - for (int i = 1; i < 8; i++) + int i8 = i * 8; + for (int j = 0; j < i; j++) { - int i8 = i * 8; - for (int j = 0; j < i; j++) - { - short tmp = data[i8 + j]; - data[i8 + j] = data[(j * 8) + i]; - data[(j * 8) + i] = tmp; - } + short tmp = data[i8 + j]; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; } } + } - /// - /// Transpose 8x8 block stored linearly in a - /// - internal static void Transpose8x8(Span src, Span dest) + /// + /// Transpose 8x8 block stored linearly in a + /// + internal static void Transpose8x8(Span src, Span dest) + { + for (int i = 0; i < 8; i++) { - for (int i = 0; i < 8; i++) + int i8 = i * 8; + for (int j = 0; j < 8; j++) { - 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]; } } + } - /// - /// Copies color values from block to the destination image buffer. - /// - internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) + /// + /// 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) { - fixed (Block8x8F* p = &block) + float* b = (float*)p; + + for (int y = 0; y < 8; y++) { - float* b = (float*)p; + int y8 = y * 8; + int yStride = y * stride; - for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) { - int y8 = y * 8; - int yStride = y * stride; + float c = b[y8 + x]; - for (int x = 0; x < 8; x++) + if (c < -128) { - float c = b[y8 + x]; - - if (c < -128) - { - c = 0; - } - else if (c > 127) - { - c = 255; - } - else - { - c += 128; - } - - buffer[yStride + x] = (byte)c; + c = 0; + } + else if (c > 127) + { + c = 255; + } + else + { + c += 128; } + + buffer[yStride + x] = (byte)c; } } } + } - /// - /// Reference implementation to test . - /// - /// The input block. - /// The destination block of 16bit integers. - /// The quantization table. - /// Zig-Zag index sequence span. - public static void Quantize(ref Block8x8F src, ref Block8x8 dest, ref Block8x8F qt, ReadOnlySpan zigzag) + /// + /// Reference implementation to test . + /// + /// The input block. + /// The destination block of 16bit integers. + /// The quantization table. + /// Zig-Zag index sequence span. + public static void Quantize(ref Block8x8F src, ref Block8x8 dest, ref Block8x8F qt, ReadOnlySpan zigzag) + { + for (int i = 0; i < Block8x8F.Size; i++) { - for (int i = 0; i < Block8x8F.Size; i++) - { - int zig = zigzag[i]; - dest[i] = (short)Math.Round(src[zig] / qt[zig], MidpointRounding.AwayFromZero); - } + int zig = zigzag[i]; + dest[i] = (short)Math.Round(src[zig] / qt[zig], MidpointRounding.AwayFromZero); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs index b8963c9664..240a338f9b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/SpanExtensions.cs @@ -1,121 +1,119 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +/// +/// Span Extensions +/// +internal static class SpanExtensions { /// - /// Span Extensions + /// Save to a Vector4 /// - internal static class SpanExtensions + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) { - /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SaveTo(this Span data, ref Vector4 v) - { - v.X = data[0]; - v.Y = data[1]; - v.Z = data[2]; - v.W = data[3]; - } + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; + } - /// - /// Save to a Vector4 - /// - /// The data - /// The vector to save to - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SaveTo(this Span data, ref Vector4 v) - { - v.X = data[0]; - v.Y = data[1]; - v.Z = data[2]; - v.W = data[3]; - } + /// + /// Save to a Vector4 + /// + /// The data + /// The vector to save to + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveTo(this Span data, ref Vector4 v) + { + v.X = data[0]; + v.Y = data[1]; + v.Z = data[2]; + v.W = data[3]; + } - /// - /// Load from Vector4 - /// - /// The data - /// The vector to load from - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void LoadFrom(this Span data, ref Vector4 v) - { - data[0] = v.X; - data[1] = v.Y; - data[2] = v.Z; - data[3] = v.W; - } + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) + { + data[0] = v.X; + data[1] = v.Y; + data[2] = v.Z; + data[3] = v.W; + } - /// - /// Load from Vector4 - /// - /// The data - /// The vector to load from - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void LoadFrom(this Span data, ref Vector4 v) - { - data[0] = (int)v.X; - data[1] = (int)v.Y; - data[2] = (int)v.Z; - data[3] = (int)v.W; - } + /// + /// Load from Vector4 + /// + /// The data + /// The vector to load from + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LoadFrom(this Span data, ref Vector4 v) + { + data[0] = (int)v.X; + data[1] = (int)v.Y; + data[2] = (int)v.Z; + data[3] = (int)v.W; + } - /// - /// Converts all int values of src to float - /// - /// Source - /// A new with float values - public static float[] ConvertAllToFloat(this int[] src) + /// + /// Converts all int values of src to float + /// + /// Source + /// A new with float values + public static float[] ConvertAllToFloat(this int[] src) + { + var result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) { - var result = new float[src.Length]; - for (int i = 0; i < src.Length; i++) - { - result[i] = src[i]; - } - - return result; + result[i] = src[i]; } - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static Span AddScalarToAllValues(this Span src, float scalar) - { - var result = new float[src.Length]; - for (int i = 0; i < src.Length; i++) - { - result[i] = src[i] + scalar; - } + return result; + } - return result; + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, float scalar) + { + var result = new float[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; } - /// - /// Add a scalar to all values of src - /// - /// The source - /// The scalar value to add - /// A new instance of - public static Span AddScalarToAllValues(this Span src, int scalar) - { - var result = new int[src.Length]; - for (int i = 0; i < src.Length; i++) - { - result[i] = src[i] + scalar; - } + return result; + } - return result; + /// + /// Add a scalar to all values of src + /// + /// The source + /// The scalar value to add + /// A new instance of + public static Span AddScalarToAllValues(this Span src, int scalar) + { + var result = new int[src.Length]; + for (int i = 0; i < src.Length; i++) + { + result[i] = src[i] + scalar; } + + return result; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index 6e690ad7ae..7a31e35d0c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,76 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils +namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; + +internal static class VerifyJpeg { - internal static class VerifyJpeg + internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) { - internal static void VerifySize(IJpegComponent component, int expectedBlocksX, int expectedBlocksY) - { - Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks); - } + Assert.Equal(new Size(expectedBlocksX, expectedBlocksY), component.SizeInBlocks); + } - internal static void VerifyComponent( - IJpegComponent component, - Size expectedSizeInBlocks, - Size expectedSamplingFactors, - Size expectedSubsamplingDivisors) - { - Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks); - Assert.Equal(expectedSamplingFactors, component.SamplingFactors); - Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors); - } + internal static void VerifyComponent( + IJpegComponent component, + Size expectedSizeInBlocks, + Size expectedSamplingFactors, + Size expectedSubsamplingDivisors) + { + Assert.Equal(expectedSizeInBlocks, component.SizeInBlocks); + Assert.Equal(expectedSamplingFactors, component.SamplingFactors); + Assert.Equal(expectedSubsamplingDivisors, component.SubSamplingDivisors); + } - internal static void VerifyComponentSizes3( - IEnumerable components, - int xBc0, - int yBc0, - int xBc1, - int yBc1, - int xBc2, - int yBc2) - { - IJpegComponent[] c = components.ToArray(); - Assert.Equal(3, components.Count()); + internal static void VerifyComponentSizes3( + IEnumerable components, + int xBc0, + int yBc0, + int xBc1, + int yBc1, + int xBc2, + int yBc2) + { + IJpegComponent[] c = components.ToArray(); + Assert.Equal(3, components.Count()); - VerifySize(c[0], xBc0, yBc0); - VerifySize(c[1], xBc1, yBc1); - VerifySize(c[2], xBc2, yBc2); - } + VerifySize(c[0], xBc0, yBc0); + VerifySize(c[1], xBc1, yBc1); + VerifySize(c[2], xBc2, yBc2); + } - internal static void SaveSpectralImage( - TestImageProvider provider, - LibJpegTools.SpectralData data, - ITestOutputHelper output = null) - where TPixel : unmanaged, IPixel + internal static void SaveSpectralImage( + TestImageProvider provider, + LibJpegTools.SpectralData data, + ITestOutputHelper output = null) + where TPixel : unmanaged, IPixel + { + foreach (LibJpegTools.ComponentData comp in data.Components) { - foreach (LibJpegTools.ComponentData comp in data.Components) - { - output?.WriteLine("Min: " + comp.MinVal); - output?.WriteLine("Max: " + comp.MaxVal); + output?.WriteLine("Min: " + comp.MinVal); + output?.WriteLine("Max: " + comp.MaxVal); - using (Image image = comp.CreateGrayScaleImage()) - { - string details = $"C{comp.Index}"; - image.DebugSave(provider, details, appendPixelTypeToFileName: false); - } + using (Image image = comp.CreateGrayScaleImage()) + { + string details = $"C{comp.Index}"; + image.DebugSave(provider, details, appendPixelTypeToFileName: false); } + } - Image fullImage = data.TryCreateRGBSpectralImage(); + Image fullImage = data.TryCreateRGBSpectralImage(); - if (fullImage != null) - { - fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); - fullImage.Dispose(); - } + if (fullImage != null) + { + fullImage.DebugSave(provider, "FULL", appendPixelTypeToFileName: false); + fullImage.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs index 8e4aa71962..9723df266e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs @@ -1,52 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Jpg +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +[Trait("Format", "Jpg")] +public class ZigZagTests { - [Trait("Format", "Jpg")] - public class ZigZagTests + private static void CanHandleAllPossibleCoefficients(ReadOnlySpan order) { - private static void CanHandleAllPossibleCoefficients(ReadOnlySpan order) - { - // Mimic the behaviour of the huffman scan decoder using all possible byte values - short[] block = new short[64]; + // Mimic the behaviour of the huffman scan decoder using all possible byte values + short[] block = new short[64]; - for (int h = 0; h < 255; h++) + for (int h = 0; h < 255; h++) + { + for (int i = 1; i < 64; i++) { - for (int i = 1; i < 64; i++) - { - int s = h; - int r = s >> 4; - s &= 15; + int s = h; + int r = s >> 4; + s &= 15; - if (s != 0) + if (s != 0) + { + i += r; + block[order[i++]] = (short)s; + } + else + { + if (r == 0) { - i += r; - block[order[i++]] = (short)s; + break; } - else - { - if (r == 0) - { - break; - } - i += 16; - } + i += 16; } } } + } - [Fact] - public static void ZigZagCanHandleAllPossibleCoefficients() => - CanHandleAllPossibleCoefficients(ZigZag.ZigZagOrder); + [Fact] + public static void ZigZagCanHandleAllPossibleCoefficients() => + CanHandleAllPossibleCoefficients(ZigZag.ZigZagOrder); - [Fact] - public static void TrasposingZigZagCanHandleAllPossibleCoefficients() => - CanHandleAllPossibleCoefficients(ZigZag.TransposingOrder); - } + [Fact] + public static void TrasposingZigZagCanHandleAllPossibleCoefficients() => + CanHandleAllPossibleCoefficients(ZigZag.TransposingOrder); } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs index 4e3a8f36e5..fba734f0e7 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs @@ -1,155 +1,151 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Pbm +namespace SixLabors.ImageSharp.Tests.Formats.Pbm; + +public class ImageExtensionsTest { - public class ImageExtensionsTest + [Fact] + public void SaveAsPbm_Path() { - [Fact] - public void SaveAsPbm_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); - - using (var image = new Image(10, 10)) - { - image.SaveAsPbm(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file); } - [Fact] - public async Task SaveAsPbmAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPbmAsync(file); - } + [Fact] + public async Task SaveAsPbmAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file); } - [Fact] - public void SaveAsPbm_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsPbm(file, new PbmEncoder()); - } + [Fact] + public void SaveAsPbm_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file, new PbmEncoder()); } - [Fact] - public async Task SaveAsPbmAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPbmAsync(file, new PbmEncoder()); - } + [Fact] + public async Task SaveAsPbmAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file, new PbmEncoder()); } - [Fact] - public void SaveAsPbm_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsPbm(memoryStream); - } + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsPbm_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream); } - [Fact] - public async Task SaveAsPbmAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsPbmAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsPbmAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream); } - [Fact] - public void SaveAsPbm_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsPbm(memoryStream, new PbmEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsPbm_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream, new PbmEncoder()); } - [Fact] - public async Task SaveAsPbmAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); - } + [Fact] + public async Task SaveAsPbmAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 0d37c6b815..2e00ea8eb0 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -1,126 +1,122 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm +namespace SixLabors.ImageSharp.Tests.Formats.Pbm; + +[Trait("Format", "Pbm")] +[ValidateDisposedMemoryAllocations] +public class PbmDecoderTests { - [Trait("Format", "Pbm")] - [ValidateDisposedMemoryAllocations] - public class PbmDecoderTests + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale, PbmComponentType.Short)] + [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] + public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) { - [Theory] - [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] - [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] - [InlineData(GrayscalePlain, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscaleBinary, PbmColorType.Grayscale, PbmComponentType.Byte)] - [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale, PbmComponentType.Short)] - [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] - [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] - [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] - public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) - { - // Arrange - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - - // Act - using var image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - PbmMetadata metadata = image.Metadata.GetPbmMetadata(); - Assert.NotNull(metadata); - Assert.Equal(expectedColorType, metadata.ColorType); - Assert.Equal(expectedComponentType, metadata.ComponentType); - } - - [Theory] - [InlineData(BlackAndWhitePlain)] - [InlineData(BlackAndWhiteBinary)] - [InlineData(GrayscalePlain)] - [InlineData(GrayscalePlainMagick)] - [InlineData(GrayscaleBinary)] - [InlineData(GrayscaleBinaryWide)] - public void ImageLoadL8CanDecode(string imagePath) - { - // Arrange - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - - // Act - using var image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - } - - [Theory] - [InlineData(RgbPlain)] - [InlineData(RgbPlainMagick)] - [InlineData(RgbBinary)] - public void ImageLoadRgb24CanDecode(string imagePath) - { - // Arrange - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - - // Act - using var image = Image.Load(stream); - - // Assert - Assert.NotNull(image); - } - - [Theory] - [WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")] - [WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")] - [WithFile(GrayscalePlain, PixelTypes.L8, "pgm")] - [WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")] - [WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")] - [WithFile(GrayscaleBinaryWide, PixelTypes.L16, "pgm")] - [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] - [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] - [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] - public void DecodeReferenceImage(TestImageProvider provider, string extension) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.DebugSave(provider, extension: extension); + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedColorType, metadata.ColorType); + Assert.Equal(expectedComponentType, metadata.ComponentType); + } - bool isGrayscale = extension is "pgm" or "pbm"; - image.CompareToReferenceOutput(provider, grayscale: isGrayscale); - } + [Theory] + [InlineData(BlackAndWhitePlain)] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainMagick)] + [InlineData(GrayscaleBinary)] + [InlineData(GrayscaleBinaryWide)] + public void ImageLoadL8CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbPlainMagick)] + [InlineData(RgbBinary)] + public void ImageLoadRgb24CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); - [Theory] - [WithFile(RgbPlain, PixelTypes.Rgb24)] - public void PbmDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")] + [WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")] + [WithFile(GrayscalePlain, PixelTypes.L8, "pgm")] + [WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinaryWide, PixelTypes.L16, "pgm")] + [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] + public void DecodeReferenceImage(TestImageProvider provider, string extension) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSave(provider, extension: extension); + + bool isGrayscale = extension is "pgm" or "pbm"; + image.CompareToReferenceOutput(provider, grayscale: isGrayscale); + } + + [Theory] + [WithFile(RgbPlain, PixelTypes.Rgb24)] + public void PbmDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(new PbmDecoder(), options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Exact, - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(new PbmDecoder(), options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index 3e75d55b41..a0a4c1164f 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -1,143 +1,140 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm +namespace SixLabors.ImageSharp.Tests.Formats.Pbm; + +[Collection("RunSerial")] +[Trait("Format", "Pbm")] +public class PbmEncoderTests { - [Collection("RunSerial")] - [Trait("Format", "Pbm")] - public class PbmEncoderTests - { - public static readonly TheoryData ColorType = - new() - { - PbmColorType.BlackAndWhite, - PbmColorType.Grayscale, - PbmColorType.Rgb - }; + public static readonly TheoryData ColorType = + new() + { + PbmColorType.BlackAndWhite, + PbmColorType.Grayscale, + PbmColorType.Rgb + }; - public static readonly TheoryData PbmColorTypeFiles = - new() - { - { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, - { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, - { GrayscaleBinary, PbmColorType.Grayscale }, - { GrayscaleBinaryWide, PbmColorType.Grayscale }, - { GrayscalePlain, PbmColorType.Grayscale }, - { RgbBinary, PbmColorType.Rgb }, - { RgbPlain, PbmColorType.Rgb }, - }; - - [Theory] - [MemberData(nameof(PbmColorTypeFiles))] - public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) + public static readonly TheoryData PbmColorTypeFiles = + new() { - var options = new PbmEncoder(); + { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, + { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, + { GrayscaleBinary, PbmColorType.Grayscale }, + { GrayscaleBinaryWide, PbmColorType.Grayscale }, + { GrayscalePlain, PbmColorType.Grayscale }, + { RgbBinary, PbmColorType.Rgb }, + { RgbPlain, PbmColorType.Rgb }, + }; + + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder(); - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - PbmMetadata meta = output.Metadata.GetPbmMetadata(); - Assert.Equal(pbmColorType, meta.ColorType); - } + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); } } } + } - [Theory] - [MemberData(nameof(PbmColorTypeFiles))] - public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder() { - var options = new PbmEncoder() - { - Encoding = PbmEncoding.Plain - }; + Encoding = PbmEncoding.Plain + }; - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + + // EOF indicator for plain is a Space. + memStream.Seek(-1, SeekOrigin.End); + int lastByte = memStream.ReadByte(); + Assert.Equal(0x20, lastByte); + + memStream.Seek(0, SeekOrigin.Begin); + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - - // EOF indicator for plain is a Space. - memStream.Seek(-1, SeekOrigin.End); - int lastByte = memStream.ReadByte(); - Assert.Equal(0x20, lastByte); - - memStream.Seek(0, SeekOrigin.Begin); - using (var output = Image.Load(memStream)) - { - PbmMetadata meta = output.Metadata.GetPbmMetadata(); - Assert.Equal(pbmColorType, meta.ColorType); - } + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); } } } + } - [Theory] - [WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] - public void PbmEncoder_P1_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); - - [Theory] - [WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P4_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); - - [Theory] - [WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)] - public void PbmEncoder_P2_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); - - [Theory] - [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P5_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); - - [Theory] - [WithFile(RgbPlainMagick, PixelTypes.Rgb24)] - public void PbmEncoder_P3_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); - - [Theory] - [WithFile(RgbBinary, PixelTypes.Rgb24)] - public void PbmEncoder_P6_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); - - private static void TestPbmEncoderCore( - TestImageProvider provider, - PbmColorType colorType, - PbmEncoding encoding, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] + public void PbmEncoder_P1_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); + + [Theory] + [WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P4_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); + + [Theory] + [WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)] + public void PbmEncoder_P2_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); + + [Theory] + [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P5_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); + + [Theory] + [WithFile(RgbPlainMagick, PixelTypes.Rgb24)] + public void PbmEncoder_P3_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); + + [Theory] + [WithFile(RgbBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P6_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); + + private static void TestPbmEncoderCore( + TestImageProvider provider, + PbmColorType colorType, + PbmEncoding encoding, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var encoder = new PbmEncoder { ColorType = colorType, Encoding = encoding }; + var encoder = new PbmEncoder { ColorType = colorType, Encoding = encoding }; - using (var memStream = new MemoryStream()) + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) { - image.Save(memStream, encoder); - memStream.Position = 0; - using (var encodedImage = (Image)Image.Load(memStream)) - { - ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); - } + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); } } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs index ebaf37309a..7c57ee2fb5 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs @@ -1,86 +1,82 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Pbm; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm +namespace SixLabors.ImageSharp.Tests.Formats.Pbm; + +[Trait("Format", "Pbm")] +public class PbmMetadataTests { - [Trait("Format", "Pbm")] - public class PbmMetadataTests + [Fact] + public void CloneIsDeep() { - [Fact] - public void CloneIsDeep() - { - var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; - var clone = (PbmMetadata)meta.DeepClone(); + var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; + var clone = (PbmMetadata)meta.DeepClone(); - clone.ColorType = PbmColorType.Rgb; - clone.ComponentType = PbmComponentType.Short; + clone.ColorType = PbmColorType.Rgb; + clone.ComponentType = PbmComponentType.Short; - Assert.False(meta.ColorType.Equals(clone.ColorType)); - Assert.False(meta.ComponentType.Equals(clone.ComponentType)); - } + Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.ComponentType.Equals(clone.ComponentType)); + } - [Theory] - [InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] - [InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] - [InlineData(GrayscaleBinary, PbmEncoding.Binary)] - [InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] - [InlineData(GrayscalePlain, PbmEncoding.Plain)] - [InlineData(RgbBinary, PbmEncoding.Binary)] - [InlineData(RgbPlain, PbmEncoding.Plain)] - public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); - } + [Theory] + [InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] + [InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] + [InlineData(GrayscalePlain, PbmEncoding.Plain)] + [InlineData(RgbBinary, PbmEncoding.Binary)] + [InlineData(RgbPlain, PbmEncoding.Plain)] + public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); + } - [Theory] - [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] - [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] - [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] - [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] - [InlineData(GrayscalePlain, PbmColorType.Grayscale)] - [InlineData(RgbBinary, PbmColorType.Rgb)] - [InlineData(RgbPlain, PbmColorType.Rgb)] - public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedColorType, bitmapMetadata.ColorType); - } + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale)] + [InlineData(RgbBinary, PbmColorType.Rgb)] + [InlineData(RgbPlain, PbmColorType.Rgb)] + public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedColorType, bitmapMetadata.ColorType); + } - [Theory] - [InlineData(BlackAndWhitePlain, PbmComponentType.Bit)] - [InlineData(BlackAndWhiteBinary, PbmComponentType.Bit)] - [InlineData(GrayscaleBinary, PbmComponentType.Byte)] - [InlineData(GrayscaleBinaryWide, PbmComponentType.Short)] - [InlineData(GrayscalePlain, PbmComponentType.Byte)] - [InlineData(RgbBinary, PbmComponentType.Byte)] - [InlineData(RgbPlain, PbmComponentType.Byte)] - public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); - } + [Theory] + [InlineData(BlackAndWhitePlain, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmComponentType.Bit)] + [InlineData(GrayscaleBinary, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmComponentType.Short)] + [InlineData(GrayscalePlain, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmComponentType.Byte)] + [InlineData(RgbPlain, PbmComponentType.Byte)] + public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 628b757c18..b7ce32ed8f 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Pbm +namespace SixLabors.ImageSharp.Tests.Formats.Pbm; + +[Trait("Format", "Pbm")] +public class PbmRoundTripTests { - [Trait("Format", "Pbm")] - public class PbmRoundTripTests + [Theory] + [InlineData(BlackAndWhitePlain)] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainNormalized)] + [InlineData(GrayscalePlainMagick)] + [InlineData(GrayscaleBinary)] + public void PbmGrayscaleImageCanRoundTrip(string imagePath) { - [Theory] - [InlineData(BlackAndWhitePlain)] - [InlineData(BlackAndWhiteBinary)] - [InlineData(GrayscalePlain)] - [InlineData(GrayscalePlainNormalized)] - [InlineData(GrayscalePlainMagick)] - [InlineData(GrayscaleBinary)] - public void PbmGrayscaleImageCanRoundTrip(string imagePath) - { - // Arrange - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); - // Act - using var originalImage = Image.Load(stream); - using Image colorImage = originalImage.CloneAs(); - using Image encodedImage = this.RoundTrip(colorImage); + // Act + using var originalImage = Image.Load(stream); + using Image colorImage = originalImage.CloneAs(); + using Image encodedImage = this.RoundTrip(colorImage); - // Assert - Assert.NotNull(encodedImage); - ImageComparer.Exact.VerifySimilarity(colorImage, encodedImage); - } + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(colorImage, encodedImage); + } - [Theory] - [InlineData(RgbPlain)] - [InlineData(RgbPlainNormalized)] - [InlineData(RgbPlainMagick)] - [InlineData(RgbBinary)] - public void PbmColorImageCanRoundTrip(string imagePath) - { - // Arrange - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbPlainNormalized)] + [InlineData(RgbPlainMagick)] + [InlineData(RgbBinary)] + public void PbmColorImageCanRoundTrip(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); - // Act - using var originalImage = Image.Load(stream); - using Image encodedImage = this.RoundTrip(originalImage); + // Act + using var originalImage = Image.Load(stream); + using Image encodedImage = this.RoundTrip(originalImage); - // Assert - Assert.NotNull(encodedImage); - ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); - } + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); + } - private Image RoundTrip(Image originalImage) - where TPixel : unmanaged, IPixel - { - using var decodedStream = new MemoryStream(); - originalImage.SaveAsPbm(decodedStream); - decodedStream.Seek(0, SeekOrigin.Begin); - var encodedImage = Image.Load(decodedStream); - return encodedImage; - } + private Image RoundTrip(Image originalImage) + where TPixel : unmanaged, IPixel + { + using var decodedStream = new MemoryStream(); + originalImage.SaveAsPbm(decodedStream); + decodedStream.Seek(0, SeekOrigin.Begin); + var encodedImage = Image.Load(decodedStream); + return encodedImage; } } diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs index 9e13edeba0..1b66c4cc3c 100644 --- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs @@ -1,72 +1,69 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class Adler32Tests { - [Trait("Format", "Png")] - public class Adler32Tests - { - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default)); + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default)); - [Theory] - [InlineData(0)] - [InlineData(8)] - [InlineData(215)] - [InlineData(1024)] - [InlineData(1024 + 15)] - [InlineData(2034)] - [InlineData(4096)] - public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length); + [Theory] + [InlineData(0)] + [InlineData(8)] + [InlineData(215)] + [InlineData(1024)] + [InlineData(1024 + 15)] + [InlineData(2034)] + [InlineData(4096)] + public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length); - private static void CalculateAdlerAndCompareToReference(int length) - { - // arrange - byte[] data = GetBuffer(length); - var adler = new SharpAdler32(); - adler.Update(data); - long expected = adler.Value; + private static void CalculateAdlerAndCompareToReference(int length) + { + // arrange + byte[] data = GetBuffer(length); + var adler = new SharpAdler32(); + adler.Update(data); + long expected = adler.Value; - // act - long actual = Adler32.Calculate(data); + // act + long actual = Adler32.Calculate(data); - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - private static byte[] GetBuffer(int length) - { - byte[] data = new byte[length]; - new Random(1).NextBytes(data); + private static byte[] GetBuffer(int length) + { + byte[] data = new byte[length]; + new Random(1).NextBytes(data); - return data; - } + return data; + } - [Fact] - public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll); + [Fact] + public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll); - [Fact] - public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + [Fact] + public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - [Fact] - public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic); - private static void RunCalculateAdlerTest() + private static void RunCalculateAdlerTest() + { + int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 }; + for (int i = 0; i < testData.Length; i++) { - int[] testData = { 0, 8, 215, 1024, 1024 + 15, 2034, 4096 }; - for (int i = 0; i < testData.Length; i++) - { - CalculateAdlerAndCompareToReference(testData[i]); - } + CalculateAdlerAndCompareToReference(testData[i]); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs index fe376a800a..0dea05c531 100644 --- a/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/Crc32Tests.cs @@ -1,51 +1,48 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Compression.Zlib; -using Xunit; using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class Crc32Tests { - [Trait("Format", "Png")] - public class Crc32Tests + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void ReturnsCorrectWhenEmpty(uint input) + { + Assert.Equal(input, Crc32.Calculate(input, default)); + } + + [Theory] + [InlineData(0)] + [InlineData(8)] + [InlineData(215)] + [InlineData(1024)] + [InlineData(1024 + 15)] + [InlineData(2034)] + [InlineData(4096)] + public void MatchesReference(int length) + { + var data = GetBuffer(length); + var crc = new SharpCrc32(); + crc.Update(data); + + long expected = crc.Value; + long actual = Crc32.Calculate(data); + + Assert.Equal(expected, actual); + } + + private static byte[] GetBuffer(int length) { - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - public void ReturnsCorrectWhenEmpty(uint input) - { - Assert.Equal(input, Crc32.Calculate(input, default)); - } - - [Theory] - [InlineData(0)] - [InlineData(8)] - [InlineData(215)] - [InlineData(1024)] - [InlineData(1024 + 15)] - [InlineData(2034)] - [InlineData(4096)] - public void MatchesReference(int length) - { - var data = GetBuffer(length); - var crc = new SharpCrc32(); - crc.Update(data); - - long expected = crc.Value; - long actual = Crc32.Calculate(data); - - Assert.Equal(expected, actual); - } - - private static byte[] GetBuffer(int length) - { - var data = new byte[length]; - new Random(1).NextBytes(data); - - return data; - } + var data = new byte[length]; + new Random(1).NextBytes(data); + + return data; } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs index 1157a1e036..321250fee9 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ImageExtensionsTest.cs @@ -1,156 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class ImageExtensionsTest { - [Trait("Format", "Png")] - public class ImageExtensionsTest + [Fact] + public void SaveAsPng_Path() { - [Fact] - public void SaveAsPng_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPng_Path.png"); - - using (var image = new Image(10, 10)) - { - image.SaveAsPng(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPng_Path.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPng(file); } - [Fact] - public async Task SaveAsPngAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsPngAsync_Path.png"); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPngAsync(file); - } + [Fact] + public async Task SaveAsPngAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPngAsync_Path.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(file); } - [Fact] - public void SaveAsPng_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png"); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsPng(file, new PngEncoder()); - } + [Fact] + public void SaveAsPng_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPng_Path_Encoder.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPng(file, new PngEncoder()); } - [Fact] - public async Task SaveAsPngAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png"); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPngAsync(file, new PngEncoder()); - } + [Fact] + public async Task SaveAsPngAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPngAsync_Path_Encoder.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(file, new PngEncoder()); } - [Fact] - public void SaveAsPng_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsPng(memoryStream); - } + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsPng_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPng(memoryStream); } - [Fact] - public async Task SaveAsPngAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsPngAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsPngAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(memoryStream); } - [Fact] - public void SaveAsPng_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsPng(memoryStream, new PngEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsPng_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsPng(memoryStream, new PngEncoder()); } - [Fact] - public async Task SaveAsPngAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsPngAsync(memoryStream, new PngEncoder()); - } + [Fact] + public async Task SaveAsPngAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsPngAsync(memoryStream, new PngEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 589cd0b44b..06cb079e5b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -4,41 +4,39 @@ using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Png; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngChunkTypeTests { - [Trait("Format", "Png")] - public class PngChunkTypeTests + [Fact] + public void ChunkTypeIdsAreCorrect() { - [Fact] - public void ChunkTypeIdsAreCorrect() - { - Assert.Equal(PngChunkType.Header, GetType("IHDR")); - Assert.Equal(PngChunkType.Palette, GetType("PLTE")); - Assert.Equal(PngChunkType.Data, GetType("IDAT")); - Assert.Equal(PngChunkType.End, GetType("IEND")); - Assert.Equal(PngChunkType.Transparency, GetType("tRNS")); - Assert.Equal(PngChunkType.Text, GetType("tEXt")); - Assert.Equal(PngChunkType.InternationalText, GetType("iTXt")); - Assert.Equal(PngChunkType.CompressedText, GetType("zTXt")); - Assert.Equal(PngChunkType.Chroma, GetType("cHRM")); - Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); - Assert.Equal(PngChunkType.Physical, GetType("pHYs")); - Assert.Equal(PngChunkType.Exif, GetType("eXIf")); - Assert.Equal(PngChunkType.Time, GetType("tIME")); - Assert.Equal(PngChunkType.Background, GetType("bKGD")); - Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP")); - Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB")); - Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT")); - Assert.Equal(PngChunkType.Histogram, GetType("hIST")); - Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT")); - Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI")); - } + Assert.Equal(PngChunkType.Header, GetType("IHDR")); + Assert.Equal(PngChunkType.Palette, GetType("PLTE")); + Assert.Equal(PngChunkType.Data, GetType("IDAT")); + Assert.Equal(PngChunkType.End, GetType("IEND")); + Assert.Equal(PngChunkType.Transparency, GetType("tRNS")); + Assert.Equal(PngChunkType.Text, GetType("tEXt")); + Assert.Equal(PngChunkType.InternationalText, GetType("iTXt")); + Assert.Equal(PngChunkType.CompressedText, GetType("zTXt")); + Assert.Equal(PngChunkType.Chroma, GetType("cHRM")); + Assert.Equal(PngChunkType.Gamma, GetType("gAMA")); + Assert.Equal(PngChunkType.Physical, GetType("pHYs")); + Assert.Equal(PngChunkType.Exif, GetType("eXIf")); + Assert.Equal(PngChunkType.Time, GetType("tIME")); + Assert.Equal(PngChunkType.Background, GetType("bKGD")); + Assert.Equal(PngChunkType.EmbeddedColorProfile, GetType("iCCP")); + Assert.Equal(PngChunkType.StandardRgbColourSpace, GetType("sRGB")); + Assert.Equal(PngChunkType.SignificantBits, GetType("sBIT")); + Assert.Equal(PngChunkType.Histogram, GetType("hIST")); + Assert.Equal(PngChunkType.SuggestedPalette, GetType("sPLT")); + Assert.Equal(PngChunkType.ProprietaryApple, GetType("CgBI")); + } - private static PngChunkType GetType(string text) - { - return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); - } + private static PngChunkType GetType(string text) + { + return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs index df364ee50f..ec6de0dfab 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs @@ -3,175 +3,173 @@ using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngDecoderFilterTests { - [Trait("Format", "Png")] - public class PngDecoderFilterTests + private static void RunAverageFilterTest() + { + // arrange + byte[] scanline = + { + 3, 39, 39, 39, 0, 4, 4, 4, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 4, 4, 4, + 0, 2, 2, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 2, 2, 2, 0, + 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 254, 254, 254, + 0, 6, 6, 6, 14, 71, 71, 71, 157, 254, 254, 254, 28, 251, 251, 251, 0, 4, 4, 4, 0, 2, 2, 2, 0, 11, + 11, 11, 0, 226, 226, 226, 0, 255, 128, 234 + }; + + byte[] previousScanline = + { + 3, 74, 74, 74, 0, 73, 73, 73, 0, 73, 73, 73, 0, 74, 74, 74, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, + 72, 0, 72, 72, 72, 0, 73, 73, 73, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, 72, 0, 72, 72, 72, 0, 74, + 74, 74, 0, 72, 72, 72, 0, 73, 73, 73, 0, 75, 75, 75, 0, 73, 73, 73, 0, 74, 74, 74, 0, 72, 72, 72, 0, + 73, 73, 73, 0, 73, 73, 73, 0, 72, 72, 72, 0, 74, 74, 74, 0, 61, 61, 61, 0, 101, 101, 101, 78, 197, + 197, 197, 251, 152, 152, 152, 255, 155, 155, 155, 255, 162, 162, 162, 255, 175, 175, 175, 255, 160, + 160, 160, 255, 139, 128, 134 + }; + + byte[] expected = + { + 3, 76, 76, 76, 0, 78, 78, 78, 0, 76, 76, 76, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, 76, + 76, 0, 78, 78, 78, 0, 77, 77, 77, 0, 78, 78, 78, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, + 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 77, 77, 77, 0, 78, 78, 78, 0, 77, 77, 77, 0, 77, 77, 77, 0, + 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 73, 73, 73, 0, 73, 73, 73, 14, 158, 158, 158, 203, 175, + 175, 175, 255, 158, 158, 158, 255, 160, 160, 160, 255, 163, 163, 163, 255, 180, 180, 180, 255, 140, + 140, 140, 255, 138, 6, 115 + }; + + // act + AverageFilter.Decode(scanline, previousScanline, 4); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunUpFilterTest() { - private static void RunAverageFilterTest() + // arrange + byte[] scanline = { - // arrange - byte[] scanline = - { - 3, 39, 39, 39, 0, 4, 4, 4, 0, 1, 1, 1, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 0, 4, 4, 4, - 0, 2, 2, 2, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 2, 2, 2, 0, - 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0, 3, 3, 3, 0, 254, 254, 254, - 0, 6, 6, 6, 14, 71, 71, 71, 157, 254, 254, 254, 28, 251, 251, 251, 0, 4, 4, 4, 0, 2, 2, 2, 0, 11, - 11, 11, 0, 226, 226, 226, 0, 255, 128, 234 - }; - - byte[] previousScanline = - { - 3, 74, 74, 74, 0, 73, 73, 73, 0, 73, 73, 73, 0, 74, 74, 74, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, - 72, 0, 72, 72, 72, 0, 73, 73, 73, 0, 74, 74, 74, 0, 73, 73, 73, 0, 72, 72, 72, 0, 72, 72, 72, 0, 74, - 74, 74, 0, 72, 72, 72, 0, 73, 73, 73, 0, 75, 75, 75, 0, 73, 73, 73, 0, 74, 74, 74, 0, 72, 72, 72, 0, - 73, 73, 73, 0, 73, 73, 73, 0, 72, 72, 72, 0, 74, 74, 74, 0, 61, 61, 61, 0, 101, 101, 101, 78, 197, - 197, 197, 251, 152, 152, 152, 255, 155, 155, 155, 255, 162, 162, 162, 255, 175, 175, 175, 255, 160, - 160, 160, 255, 139, 128, 134 - }; - - byte[] expected = - { - 3, 76, 76, 76, 0, 78, 78, 78, 0, 76, 76, 76, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, 76, - 76, 0, 78, 78, 78, 0, 77, 77, 77, 0, 78, 78, 78, 0, 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 76, - 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 77, 77, 77, 0, 78, 78, 78, 0, 77, 77, 77, 0, 77, 77, 77, 0, - 76, 76, 76, 0, 77, 77, 77, 0, 77, 77, 77, 0, 73, 73, 73, 0, 73, 73, 73, 14, 158, 158, 158, 203, 175, - 175, 175, 255, 158, 158, 158, 255, 160, 160, 160, 255, 163, 163, 163, 255, 180, 180, 180, 255, 140, - 140, 140, 255, 138, 6, 115 - }; - - // act - AverageFilter.Decode(scanline, previousScanline, 4); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunUpFilterTest() + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; + + byte[] previousScanline = + { + 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, + 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, + 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 + }; + + byte[] expected = + { + 62, 126, 65, 176, 51, 183, 83, 227, 72, 248, 20, 185, 151, 46, 246, 163, 240, 81, 250, 16, 8, 214, + 134, 85, 107, 139, 90, 218, 246, 126, 144, 43, 221, 71, 45, 56, 49, 182, 240, 142, 147, 48, 178, + 119, 100, 122, 137, 166, 28, 41, 135, 81, 24, 62, 34, 62, 248, 234, 68, 166, 93, 121, 237, 200 + }; + + // act + UpFilter.Decode(scanline, previousScanline); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunSubFilterTest() + { + // arrange + byte[] scanline = { - // arrange - byte[] scanline = - { - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - }; - - byte[] previousScanline = - { - 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, - 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, - 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 - }; - - byte[] expected = - { - 62, 126, 65, 176, 51, 183, 83, 227, 72, 248, 20, 185, 151, 46, 246, 163, 240, 81, 250, 16, 8, 214, - 134, 85, 107, 139, 90, 218, 246, 126, 144, 43, 221, 71, 45, 56, 49, 182, 240, 142, 147, 48, 178, - 119, 100, 122, 137, 166, 28, 41, 135, 81, 24, 62, 34, 62, 248, 234, 68, 166, 93, 121, 237, 200 - }; - - // act - UpFilter.Decode(scanline, previousScanline); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunSubFilterTest() + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; + + byte[] expected = { - // arrange - byte[] scanline = - { - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - }; - - byte[] expected = - { - 62, 23, 186, 150, 174, 27, 135, 209, 71, 161, 37, 39, 55, 78, 228, 97, 166, 5, 49, 134, 251, 28, - 142, 82, 105, 167, 151, 102, 192, 65, 71, 156, 143, 23, 111, 167, 66, 222, 118, 130, 240, 208, 230, - 94, 133, 213, 239, 204, 236, 64, 214, 189, 249, 134, 174, 228, 179, 115, 213, 6, 174, 44, 185, 4 - }; - - // act - SubFilter.Decode(scanline, 4); - - // assert - Assert.Equal(expected, scanline); - } - - private static void RunPaethFilterTest() + 62, 23, 186, 150, 174, 27, 135, 209, 71, 161, 37, 39, 55, 78, 228, 97, 166, 5, 49, 134, 251, 28, + 142, 82, 105, 167, 151, 102, 192, 65, 71, 156, 143, 23, 111, 167, 66, 222, 118, 130, 240, 208, 230, + 94, 133, 213, 239, 204, 236, 64, 214, 189, 249, 134, 174, 228, 179, 115, 213, 6, 174, 44, 185, 4 + }; + + // act + SubFilter.Decode(scanline, 4); + + // assert + Assert.Equal(expected, scanline); + } + + private static void RunPaethFilterTest() + { + // arrange + byte[] scanline = { - // arrange - byte[] scanline = - { - 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, - 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, - 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 - }; + 62, 23, 186, 150, 174, 4, 205, 59, 153, 134, 158, 86, 240, 173, 191, 58, 111, 183, 77, 37, 85, 23, + 93, 204, 110, 139, 9, 20, 87, 154, 176, 54, 207, 214, 40, 11, 179, 199, 7, 219, 174, 242, 112, 220, + 149, 5, 9, 110, 103, 107, 231, 241, 13, 70, 216, 39, 186, 237, 39, 34, 251, 185, 228, 254 + }; - byte[] previousScanline = - { - 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, - 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, - 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 - }; + byte[] previousScanline = + { + 214, 103, 135, 26, 133, 179, 134, 168, 175, 114, 118, 99, 167, 129, 55, 105, 129, 154, 173, 235, + 179, 191, 41, 137, 253, 0, 81, 198, 159, 228, 224, 245, 14, 113, 5, 45, 126, 239, 233, 179, 229, 62, + 66, 155, 207, 117, 128, 56, 181, 190, 160, 96, 11, 248, 74, 23, 62, 253, 29, 132, 98, 192, 9, 202 + }; - byte[] expected = - { - 62, 126, 65, 176, 51, 183, 14, 235, 30, 248, 172, 254, 14, 165, 53, 56, 125, 92, 250, 16, 8, 177, - 10, 220, 118, 139, 50, 240, 205, 126, 144, 43, 221, 71, 45, 54, 144, 182, 240, 142, 147, 48, 178, - 106, 40, 122, 187, 166, 143, 41, 162, 151, 24, 111, 34, 135, 248, 92, 68, 169, 243, 21, 1, 200 - }; + byte[] expected = + { + 62, 126, 65, 176, 51, 183, 14, 235, 30, 248, 172, 254, 14, 165, 53, 56, 125, 92, 250, 16, 8, 177, + 10, 220, 118, 139, 50, 240, 205, 126, 144, 43, 221, 71, 45, 54, 144, 182, 240, 142, 147, 48, 178, + 106, 40, 122, 187, 166, 143, 41, 162, 151, 24, 111, 34, 135, 248, 92, 68, 169, 243, 21, 1, 200 + }; - // act - PaethFilter.Decode(scanline, previousScanline, 4); + // act + PaethFilter.Decode(scanline, previousScanline, 4); - // assert - Assert.Equal(expected, scanline); - } + // assert + Assert.Equal(expected, scanline); + } - [Fact] - public void AverageFilter_Works() => RunAverageFilterTest(); + [Fact] + public void AverageFilter_Works() => RunAverageFilterTest(); - [Fact] - public void UpFilter_Works() => RunUpFilterTest(); + [Fact] + public void UpFilter_Works() => RunUpFilterTest(); - [Fact] - public void SubFilter_Works() => RunSubFilterTest(); + [Fact] + public void SubFilter_Works() => RunSubFilterTest(); - [Fact] - public void PaethFilter_Works() => RunPaethFilterTest(); + [Fact] + public void PaethFilter_Works() => RunPaethFilterTest(); - [Fact] - public void AverageFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.AllowAll); + [Fact] + public void AverageFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.AllowAll); - [Fact] - public void AverageFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void AverageFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAverageFilterTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void UpFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.AllowAll); + [Fact] + public void UpFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.AllowAll); - [Fact] - public void UpFilter_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableAVX2); + [Fact] + public void UpFilter_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableAVX2); - [Fact] - public void UpFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void UpFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpFilterTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void SubFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.AllowAll); + [Fact] + public void SubFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.AllowAll); - [Fact] - public void SubFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void SubFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubFilterTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void PaethFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.AllowAll); + [Fact] + public void PaethFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.AllowAll); - [Fact] - public void PaethFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); - } + [Fact] + public void PaethFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 9de9f32ec5..5a23c60378 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -2,114 +2,110 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; -using System.IO; using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public partial class PngDecoderTests { - [Trait("Format", "Png")] - public partial class PngDecoderTests - { - // Represents ASCII string of "123456789" - private readonly byte[] check = { 49, 50, 51, 52, 53, 54, 55, 56, 57 }; - - // 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 - 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 - }; - - // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. - private static readonly byte[] Raw1X1PngIdatAndIend = - { - // IDAT - 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, - 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x01, - - // IDAT CRC - 0x5C, 0xCD, 0xFF, 0x69, - - // IEND - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, - - // IEND CRC - 0xAE, 0x42, 0x60, 0x82 - }; - - [Theory] - [InlineData((uint)PngChunkType.Header)] // IHDR - [InlineData((uint)PngChunkType.Palette)] // PLTE - /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ - public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) + // Represents ASCII string of "123456789" + private readonly byte[] check = { 49, 50, 51, 52, 53, 54, 55, 56, 57 }; + + // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. + private static readonly byte[] Raw1X1PngIhdrAndpHYs = { - string chunkName = GetChunkTypeName(chunkType); + // PNG Identifier + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, - using (var memStream = new MemoryStream()) - { - WriteHeaderChunk(memStream); - WriteChunk(memStream, chunkName); - WriteDataChunk(memStream); + // IHDR + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, + 0x00, 0x00, 0x00, - var decoder = new PngDecoder(); + // IHDR CRC + 0x90, 0x77, 0x53, 0xDE, - ImageFormatException exception = - Assert.Throws(() => decoder.Decode(DecoderOptions.Default, memStream)); + // pHYS + 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, + 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, - Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); - } - } + // pHYS CRC + 0xC7, 0x6F, 0xA8, 0x64 + }; - private static string GetChunkTypeName(uint value) + // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. + private static readonly byte[] Raw1X1PngIdatAndIend = { - byte[] data = new byte[4]; + // IDAT + 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, + 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x01, + + // IDAT CRC + 0x5C, 0xCD, 0xFF, 0x69, + + // IEND + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + + // IEND CRC + 0xAE, 0x42, 0x60, 0x82 + }; + + [Theory] + [InlineData((uint)PngChunkType.Header)] // IHDR + [InlineData((uint)PngChunkType.Palette)] // PLTE + /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ + public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) + { + string chunkName = GetChunkTypeName(chunkType); - BinaryPrimitives.WriteUInt32BigEndian(data, value); + using (var memStream = new MemoryStream()) + { + WriteHeaderChunk(memStream); + WriteChunk(memStream, chunkName); + WriteDataChunk(memStream); + + var decoder = new PngDecoder(); + + ImageFormatException exception = + Assert.Throws(() => decoder.Decode(DecoderOptions.Default, memStream)); - return Encoding.ASCII.GetString(data); + Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } + } + + private static string GetChunkTypeName(uint value) + { + byte[] data = new byte[4]; - private static void WriteHeaderChunk(MemoryStream memStream) => + BinaryPrimitives.WriteUInt32BigEndian(data, value); - // Writes a 1x1 32bit png header chunk containing a single black pixel. - memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); + return Encoding.ASCII.GetString(data); + } - private static void WriteChunk(MemoryStream memStream, string chunkName) - { - // Needs a minimum length of 9 for pHYs chunk. - memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4); - memStream.Write(Encoding.ASCII.GetBytes(chunkName), 0, 4); // 4 bytes chunk header - memStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 9); // 9 bytes of chunk data - memStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Junk Crc - } + private static void WriteHeaderChunk(MemoryStream memStream) => - private static void WriteDataChunk(MemoryStream memStream) - { - // Writes a 1x1 32bit png data chunk containing a single black pixel. - memStream.Write(Raw1X1PngIdatAndIend, 0, Raw1X1PngIdatAndIend.Length); - memStream.Position = 0; - } + // Writes a 1x1 32bit png header chunk containing a single black pixel. + memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); + + private static void WriteChunk(MemoryStream memStream, string chunkName) + { + // Needs a minimum length of 9 for pHYs chunk. + memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4); + memStream.Write(Encoding.ASCII.GetBytes(chunkName), 0, 4); // 4 bytes chunk header + memStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 9); // 9 bytes of chunk data + memStream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Junk Crc + } + + private static void WriteDataChunk(MemoryStream memStream) + { + // 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 016517844f..c83b97a88d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; @@ -13,564 +10,561 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +[ValidateDisposedMemoryAllocations] +public partial class PngDecoderTests { - [Trait("Format", "Png")] - [ValidateDisposedMemoryAllocations] - public partial class PngDecoderTests - { - private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static PngDecoder PngDecoder => new(); + private static PngDecoder PngDecoder => new(); - public static readonly string[] CommonTestImages = - { - TestImages.Png.Splash, - TestImages.Png.FilterVar, + public static readonly string[] CommonTestImages = + { + TestImages.Png.Splash, + TestImages.Png.FilterVar, - TestImages.Png.VimImage1, - TestImages.Png.VimImage2, - TestImages.Png.VersioningImage1, - TestImages.Png.VersioningImage2, + TestImages.Png.VimImage1, + TestImages.Png.VimImage2, + TestImages.Png.VersioningImage1, + TestImages.Png.VersioningImage2, - TestImages.Png.SnakeGame, + TestImages.Png.SnakeGame, - TestImages.Png.Rgb24BppTrans, + TestImages.Png.Rgb24BppTrans, - TestImages.Png.Bad.ChunkLength1, - TestImages.Png.Bad.ChunkLength2, - }; + TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.ChunkLength2, + }; - public static readonly string[] TestImagesIssue1014 = - { - TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2, - TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4, - TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6 - }; + public static readonly string[] TestImagesIssue1014 = + { + TestImages.Png.Issue1014_1, TestImages.Png.Issue1014_2, + TestImages.Png.Issue1014_3, TestImages.Png.Issue1014_4, + TestImages.Png.Issue1014_5, TestImages.Png.Issue1014_6 + }; - public static readonly string[] TestImagesIssue1177 = - { - TestImages.Png.Issue1177_1, - TestImages.Png.Issue1177_2 - }; + public static readonly string[] TestImagesIssue1177 = + { + TestImages.Png.Issue1177_1, + TestImages.Png.Issue1177_2 + }; - public static readonly string[] CorruptedTestImages = - { - TestImages.Png.Bad.CorruptedChunk, - TestImages.Png.Bad.ZlibOverflow, - TestImages.Png.Bad.ZlibOverflow2, - TestImages.Png.Bad.ZlibZtxtBadHeader, - }; + public static readonly string[] CorruptedTestImages = + { + TestImages.Png.Bad.CorruptedChunk, + TestImages.Png.Bad.ZlibOverflow, + TestImages.Png.Bad.ZlibOverflow2, + TestImages.Png.Bad.ZlibZtxtBadHeader, + }; - public static readonly TheoryData PixelFormatRange = new() - { - { TestImages.Png.Gray4Bpp, typeof(Image) }, - { TestImages.Png.L16Bit, typeof(Image) }, - { TestImages.Png.Gray1BitTrans, typeof(Image) }, - { TestImages.Png.Gray2BitTrans, typeof(Image) }, - { TestImages.Png.Gray4BitTrans, typeof(Image) }, - { TestImages.Png.GrayA8Bit, typeof(Image) }, - { TestImages.Png.GrayAlpha16Bit, typeof(Image) }, - { TestImages.Png.Palette8Bpp, typeof(Image) }, - { TestImages.Png.PalettedTwoColor, typeof(Image) }, - { TestImages.Png.Rainbow, typeof(Image) }, - { TestImages.Png.Rgb24BppTrans, typeof(Image) }, - { TestImages.Png.Kaboom, typeof(Image) }, - { TestImages.Png.Rgb48Bpp, typeof(Image) }, - { TestImages.Png.Rgb48BppTrans, typeof(Image) }, - { TestImages.Png.Rgba64Bpp, typeof(Image) }, - }; + public static readonly TheoryData PixelFormatRange = new() + { + { TestImages.Png.Gray4Bpp, typeof(Image) }, + { TestImages.Png.L16Bit, typeof(Image) }, + { TestImages.Png.Gray1BitTrans, typeof(Image) }, + { TestImages.Png.Gray2BitTrans, typeof(Image) }, + { TestImages.Png.Gray4BitTrans, typeof(Image) }, + { TestImages.Png.GrayA8Bit, typeof(Image) }, + { TestImages.Png.GrayAlpha16Bit, typeof(Image) }, + { TestImages.Png.Palette8Bpp, typeof(Image) }, + { TestImages.Png.PalettedTwoColor, typeof(Image) }, + { TestImages.Png.Rainbow, typeof(Image) }, + { TestImages.Png.Rgb24BppTrans, typeof(Image) }, + { TestImages.Png.Kaboom, typeof(Image) }, + { TestImages.Png.Rgb48Bpp, typeof(Image) }, + { TestImages.Png.Rgb48BppTrans, typeof(Image) }, + { TestImages.Png.Rgba64Bpp, typeof(Image) }, + }; + + [Theory] + [MemberData(nameof(PixelFormatRange))] + public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using var image = Image.Load(file); + Assert.IsType(type, image); + } - [Theory] - [MemberData(nameof(PixelFormatRange))] - public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); - using var image = Image.Load(file); - Assert.IsType(type, image); - } + [Theory] + [MemberData(nameof(PixelFormatRange))] + public async Task DecodeAsync_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using Image image = await Image.LoadAsync(file); + Assert.IsType(type, image); + } - [Theory] - [MemberData(nameof(PixelFormatRange))] - public async Task DecodeAsync_NonGeneric_CreatesCorrectImageType(string path, Type type) - { - string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); - using Image image = await Image.LoadAsync(file); - Assert.IsType(type, image); - } + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + public void Decode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void Decode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + public void PngDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + TargetSize = new() { Width = 150, Height = 150 } + }; - [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] - public void PngDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 } - }; + using Image image = provider.GetImage(PngDecoder, options); - using Image image = provider.GetImage(PngDecoder, options); + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.0003F), // Magick decoder shows difference on Mac + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0003F), // Magick decoder shows difference on Mac - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithAverageFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.AverageFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.AverageFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithAverageFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithSubFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithSubFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.UpFilter, PixelTypes.Rgba32)] + public void Decode_WithUpFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.UpFilter, PixelTypes.Rgba32)] - public void Decode_WithUpFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.PaethFilter3BytesPerPixel, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PaethFilter4BytesPerPixel, PixelTypes.Rgba32)] + public void Decode_WithPaethFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.PaethFilter3BytesPerPixel, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PaethFilter4BytesPerPixel, PixelTypes.Rgba32)] - public void Decode_WithPaethFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)] + public void Decode_GrayWithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.GrayA8Bit, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Gray1BitTrans, PixelTypes.Rgba32)] - public void Decode_GrayWithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Banner7Adam7InterlaceMode, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] + public void Decode_Interlaced(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner7Adam7InterlaceMode, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] - public void Decode_Interlaced(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.Indexed, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedFourColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedSixteenColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] + public void Decode_Indexed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.Indexed, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Banner8Index, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedFourColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedSixteenColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] - public void Decode_Indexed(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgb48)] + [WithFile(TestImages.Png.Rgb48BppInterlaced, PixelTypes.Rgb48)] + public void Decode_48Bpp(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgb48)] - [WithFile(TestImages.Png.Rgb48BppInterlaced, PixelTypes.Rgb48)] - public void Decode_48Bpp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.Rgba64Bpp, PixelTypes.Rgba64)] + [WithFile(TestImages.Png.Rgb48BppTrans, PixelTypes.Rgba64)] + public void Decode_64Bpp(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.Rgba64Bpp, PixelTypes.Rgba64)] - [WithFile(TestImages.Png.Rgb48BppTrans, PixelTypes.Rgba64)] - public void Decode_64Bpp(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.GrayAlpha1BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Gray4BitInterlaced, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes.Rgba32)] + public void Decoder_L8bitInterlaced(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.GrayAlpha1BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Gray4BitInterlaced, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes.Rgba32)] - public void Decoder_L8bitInterlaced(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)] + public void Decode_L16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.L16Bit, PixelTypes.Rgb48)] - public void Decode_L16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)] + [WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)] + public void Decode_GrayAlpha16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.GrayAlpha16Bit, PixelTypes.Rgba64)] - [WithFile(TestImages.Png.GrayTrns16BitInterlaced, PixelTypes.Rgba64)] - public void Decode_GrayAlpha16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.GrayA8BitInterlaced, TestPixelTypes)] + public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.GrayA8BitInterlaced, TestPixelTypes)] - public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)] + public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFileCollection(nameof(CorruptedTestImages), PixelTypes.Rgba32)] - public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [WithFile(TestImages.Png.Splash, TestPixelTypes)] + public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } - [Theory] - [WithFile(TestImages.Png.Splash, TestPixelTypes)] - public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + [Theory] + [InlineData(TestImages.Png.Bpp1, 1)] + [InlineData(TestImages.Png.Gray4Bpp, 4)] + [InlineData(TestImages.Png.Palette8Bpp, 8)] + [InlineData(TestImages.Png.Pd, 24)] + [InlineData(TestImages.Png.Blur, 32)] + [InlineData(TestImages.Png.Rgb48Bpp, 48)] + [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] + public void Identify(string imagePath, int expectedPixelSize) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); + } - [Theory] - [InlineData(TestImages.Png.Bpp1, 1)] - [InlineData(TestImages.Png.Gray4Bpp, 4)] - [InlineData(TestImages.Png.Palette8Bpp, 8)] - [InlineData(TestImages.Png.Pd, 24)] - [InlineData(TestImages.Png.Blur, 32)] - [InlineData(TestImages.Png.Rgb48Bpp, 48)] - [InlineData(TestImages.Png.Rgb48BppInterlaced, 48)] - public void Identify(string imagePath, int expectedPixelSize) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); - } + [Theory] + [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] + public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.NotNull(ex); + Assert.Contains("PNG Image does not contain a data chunk", ex.Message); + } - [Theory] - [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] - public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("PNG Image does not contain a data chunk", ex.Message); - } + [Theory] + [WithFile(TestImages.Png.Bad.MissingPaletteChunk1, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bad.MissingPaletteChunk2, PixelTypes.Rgba32)] + public void Decode_MissingPaletteChunk_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.NotNull(ex); + Assert.Contains("PNG Image does not contain a palette chunk", ex.Message); + } - [Theory] - [WithFile(TestImages.Png.Bad.MissingPaletteChunk1, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.MissingPaletteChunk2, PixelTypes.Rgba32)] - public void Decode_MissingPaletteChunk_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("PNG Image does not contain a palette chunk", ex.Message); - } + [Theory] + [WithFile(TestImages.Png.Bad.InvalidGammaChunk, PixelTypes.Rgba32)] + public void Decode_InvalidGammaChunk_Ignored(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.Null(ex); + } - [Theory] - [WithFile(TestImages.Png.Bad.InvalidGammaChunk, PixelTypes.Rgba32)] - public void Decode_InvalidGammaChunk_Ignored(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - }); - Assert.Null(ex); - } + [Theory] + [WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)] + public void Decode_InvalidBitDepth_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported bit depth", ex.Message); + } - [Theory] - [WithFile(TestImages.Png.Bad.BitDepthZero, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.BitDepthThree, PixelTypes.Rgba32)] - public void Decode_InvalidBitDepth_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("Invalid or unsupported bit depth", ex.Message); - } + [Theory] + [WithFile(TestImages.Png.Bad.ColorTypeOne, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bad.ColorTypeNine, PixelTypes.Rgba32)] + public void Decode_InvalidColorType_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.NotNull(ex); + Assert.Contains("Invalid or unsupported color type", ex.Message); + } - [Theory] - [WithFile(TestImages.Png.Bad.ColorTypeOne, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bad.ColorTypeNine, PixelTypes.Rgba32)] - public void Decode_InvalidColorType_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - }); - Assert.NotNull(ex); - Assert.Contains("Invalid or unsupported color type", ex.Message); - } + [Theory] + [WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32)] + public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + InvalidImageContentException ex = Assert.Throws( + () => + { + using Image image = provider.GetImage(PngDecoder); + }); + Assert.NotNull(ex); + Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message); + } - [Theory] - [WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32)] - public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - InvalidImageContentException ex = Assert.Throws( - () => - { - using Image image = provider.GetImage(PngDecoder); - }); - Assert.NotNull(ex); - Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message); - } + // https://github.com/SixLabors/ImageSharp/issues/1014 + [Theory] + [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] + public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + }); + Assert.Null(ex); + } - // https://github.com/SixLabors/ImageSharp/issues/1014 - [Theory] - [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] - public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + // https://github.com/SixLabors/ImageSharp/issues/1177 + [Theory] + [WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)] + public void Issue1177_CRC_Omitted(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + }); + Assert.Null(ex); + } - // https://github.com/SixLabors/ImageSharp/issues/1177 - [Theory] - [WithFileCollection(nameof(TestImagesIssue1177), PixelTypes.Rgba32)] - public void Issue1177_CRC_Omitted(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + // https://github.com/SixLabors/ImageSharp/issues/1127 + [Theory] + [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] + public void Issue1127(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + }); + Assert.Null(ex); + } - // https://github.com/SixLabors/ImageSharp/issues/1127 - [Theory] - [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] - public void Issue1127(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + // https://github.com/SixLabors/ImageSharp/issues/1047 + [Theory] + [WithFile(TestImages.Png.Bad.Issue1047_BadEndChunk, PixelTypes.Rgba32)] + public void Issue1047(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); - // https://github.com/SixLabors/ImageSharp/issues/1047 - [Theory] - [WithFile(TestImages.Png.Bad.Issue1047_BadEndChunk, PixelTypes.Rgba32)] - public void Issue1047(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } - }); - Assert.Null(ex); - } + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); + } + }); + Assert.Null(ex); + } - // https://github.com/SixLabors/ImageSharp/issues/1765 - [Theory] - [WithFile(TestImages.Png.Issue1765_Net6DeflateStreamRead, PixelTypes.Rgba32)] - public void Issue1765(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - }); - Assert.Null(ex); - } + // https://github.com/SixLabors/ImageSharp/issues/1765 + [Theory] + [WithFile(TestImages.Png.Issue1765_Net6DeflateStreamRead, PixelTypes.Rgba32)] + public void Issue1765(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + }); + Assert.Null(ex); + } - // https://github.com/SixLabors/ImageSharp/issues/2209 - [Theory] - [WithFile(TestImages.Png.Issue2209IndexedWithTransparency, PixelTypes.Rgba32)] - public void Issue2209_Decode_HasTransparencyIsTrue(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - PngMetadata metadata = image.Metadata.GetPngMetadata(); - Assert.True(metadata.HasTransparency); - } + // https://github.com/SixLabors/ImageSharp/issues/2209 + [Theory] + [WithFile(TestImages.Png.Issue2209IndexedWithTransparency, PixelTypes.Rgba32)] + public void Issue2209_Decode_HasTransparencyIsTrue(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.True(metadata.HasTransparency); + } - // https://github.com/SixLabors/ImageSharp/issues/2209 - [Theory] - [InlineData(TestImages.Png.Issue2209IndexedWithTransparency)] - public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); - Assert.True(metadata.HasTransparency); - } + // https://github.com/SixLabors/ImageSharp/issues/2209 + [Theory] + [InlineData(TestImages.Png.Issue2209IndexedWithTransparency)] + public void Issue2209_Identify_HasTransparencyIsTrue(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + PngMetadata metadata = imageInfo.Metadata.GetPngMetadata(); + Assert.True(metadata.HasTransparency); + } - // https://github.com/SixLabors/ImageSharp/issues/410 - [Theory] - [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] - public void Issue410_MalformedApplePng(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Exception ex = Record.Exception( - () => + // https://github.com/SixLabors/ImageSharp/issues/410 + [Theory] + [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] + public void Issue410_MalformedApplePng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) { - using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); - - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } - }); - Assert.NotNull(ex); - Assert.Contains("Proprietary Apple PNG detected!", ex.Message); - } + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); + } + }); + Assert.NotNull(ex); + Assert.Contains("Proprietary Apple PNG detected!", ex.Message); + } - [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); - InvalidImageContentException 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_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException 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) + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + 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(); + 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/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs index 5e408632ef..e50e40bd22 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs @@ -3,308 +3,305 @@ // Uncomment this to turn unit tests into benchmarks: // #define BENCHMARKING -using System; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngEncoderFilterTests : MeasureFixture { - [Trait("Format", "Png")] - public class PngEncoderFilterTests : MeasureFixture - { #if BENCHMARKING - public const int Times = 1000000; + public const int Times = 1000000; #else - public const int Times = 1; + public const int Times = 1; #endif - public PngEncoderFilterTests(ITestOutputHelper output) - : base(output) + public PngEncoderFilterTests(ITestOutputHelper output) + : base(output) + { + } + + public const int Size = 64; + + [Fact] + public void Average() + { + static void RunTest() { + var data = new TestData(PngFilterMethod.Average, Size); + data.TestFilter(); } - public const int Size = 64; + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + } - [Fact] - public void Average() + [Fact] + public void AverageSse2() + { + static void RunTest() { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Average, Size); - data.TestFilter(); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); + var data = new TestData(PngFilterMethod.Average, Size); + data.TestFilter(); } - [Fact] - public void AverageSse2() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Average, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); + [Fact] + public void AverageSsse3() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Average, Size); + data.TestFilter(); } - [Fact] - public void AverageSsse3() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Average, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + [Fact] + public void AverageAvx2() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Average, Size); + data.TestFilter(); } - [Fact] - public void AverageAvx2() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Average, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); + [Fact] + public void Paeth() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Paeth, Size); + data.TestFilter(); } - [Fact] - public void Paeth() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void PaethAvx2() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Paeth, Size); + data.TestFilter(); } - [Fact] - public void PaethAvx2() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); + [Fact] + public void PaethVector() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Paeth, Size); + data.TestFilter(); } - [Fact] - public void PaethVector() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Paeth, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + [Fact] + public void Up() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Up, Size); + data.TestFilter(); } - [Fact] - public void Up() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Up, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void UpAvx2() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Up, Size); + data.TestFilter(); } - [Fact] - public void UpAvx2() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Up, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); + [Fact] + public void UpVector() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Up, Size); + data.TestFilter(); } - [Fact] - public void UpVector() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Up, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + [Fact] + public void Sub() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Sub, Size); + data.TestFilter(); } - [Fact] - public void Sub() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Sub, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void SubAvx2() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Sub, Size); + data.TestFilter(); } - [Fact] - public void SubAvx2() - { - static void RunTest() - { - var data = new TestData(PngFilterMethod.Sub, Size); - data.TestFilter(); - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll); + [Fact] + public void SubVector() + { + static void RunTest() + { + var data = new TestData(PngFilterMethod.Sub, Size); + data.TestFilter(); } - [Fact] - public void SubVector() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + } + + public class TestData + { + private readonly PngFilterMethod filter; + private readonly int bpp; + private readonly byte[] previousScanline; + private readonly byte[] scanline; + private readonly byte[] expectedResult; + private readonly int expectedSum; + private readonly byte[] resultBuffer; + + public TestData(PngFilterMethod filter, int size, int bpp = 4) { - static void RunTest() + this.filter = filter; + this.bpp = bpp; + this.previousScanline = new byte[size * size * bpp]; + this.scanline = new byte[size * size * bpp]; + this.expectedResult = new byte[1 + (size * size * bpp)]; + this.resultBuffer = new byte[1 + (size * size * bpp)]; + + var rng = new Random(12345678); + byte[] tmp = new byte[6]; + for (int i = 0; i < this.previousScanline.Length; i += bpp) { - var data = new TestData(PngFilterMethod.Sub, Size); - data.TestFilter(); + rng.NextBytes(tmp); + + this.previousScanline[i + 0] = tmp[0]; + this.previousScanline[i + 1] = tmp[1]; + this.previousScanline[i + 2] = tmp[2]; + this.previousScanline[i + 3] = 255; + + this.scanline[i + 0] = tmp[3]; + this.scanline[i + 1] = tmp[4]; + this.scanline[i + 2] = tmp[5]; + this.scanline[i + 3] = 255; } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + switch (this.filter) + { + case PngFilterMethod.Sub: + ReferenceImplementations.EncodeSubFilter( + this.scanline, this.expectedResult, this.bpp, out this.expectedSum); + break; + + case PngFilterMethod.Up: + ReferenceImplementations.EncodeUpFilter( + this.previousScanline, this.scanline, this.expectedResult, out this.expectedSum); + break; + + case PngFilterMethod.Average: + ReferenceImplementations.EncodeAverageFilter( + this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); + break; + + case PngFilterMethod.Paeth: + ReferenceImplementations.EncodePaethFilter( + this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); + break; + + case PngFilterMethod.None: + case PngFilterMethod.Adaptive: + default: + throw new InvalidOperationException(); + } } - public class TestData + public void TestFilter() { - private readonly PngFilterMethod filter; - private readonly int bpp; - private readonly byte[] previousScanline; - private readonly byte[] scanline; - private readonly byte[] expectedResult; - private readonly int expectedSum; - private readonly byte[] resultBuffer; - - public TestData(PngFilterMethod filter, int size, int bpp = 4) + int sum; + switch (this.filter) { - this.filter = filter; - this.bpp = bpp; - this.previousScanline = new byte[size * size * bpp]; - this.scanline = new byte[size * size * bpp]; - this.expectedResult = new byte[1 + (size * size * bpp)]; - this.resultBuffer = new byte[1 + (size * size * bpp)]; - - var rng = new Random(12345678); - byte[] tmp = new byte[6]; - for (int i = 0; i < this.previousScanline.Length; i += bpp) - { - rng.NextBytes(tmp); - - this.previousScanline[i + 0] = tmp[0]; - this.previousScanline[i + 1] = tmp[1]; - this.previousScanline[i + 2] = tmp[2]; - this.previousScanline[i + 3] = 255; - - this.scanline[i + 0] = tmp[3]; - this.scanline[i + 1] = tmp[4]; - this.scanline[i + 2] = tmp[5]; - this.scanline[i + 3] = 255; - } - - switch (this.filter) - { - case PngFilterMethod.Sub: - ReferenceImplementations.EncodeSubFilter( - this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.Up: - ReferenceImplementations.EncodeUpFilter( - this.previousScanline, this.scanline, this.expectedResult, out this.expectedSum); - break; - - case PngFilterMethod.Average: - ReferenceImplementations.EncodeAverageFilter( - this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.Paeth: - ReferenceImplementations.EncodePaethFilter( - this.previousScanline, this.scanline, this.expectedResult, this.bpp, out this.expectedSum); - break; - - case PngFilterMethod.None: - case PngFilterMethod.Adaptive: - default: - throw new InvalidOperationException(); - } + case PngFilterMethod.Sub: + SubFilter.Encode(this.scanline, this.resultBuffer, this.bpp, out sum); + break; + + case PngFilterMethod.Up: + UpFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, out sum); + break; + + case PngFilterMethod.Average: + AverageFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum); + break; + + case PngFilterMethod.Paeth: + PaethFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum); + break; + + case PngFilterMethod.None: + case PngFilterMethod.Adaptive: + default: + throw new InvalidOperationException(); } - public void TestFilter() - { - int sum; - switch (this.filter) - { - case PngFilterMethod.Sub: - SubFilter.Encode(this.scanline, this.resultBuffer, this.bpp, out sum); - break; - - case PngFilterMethod.Up: - UpFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, out sum); - break; - - case PngFilterMethod.Average: - AverageFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum); - break; - - case PngFilterMethod.Paeth: - PaethFilter.Encode(this.previousScanline, this.scanline, this.resultBuffer, this.bpp, out sum); - break; - - case PngFilterMethod.None: - case PngFilterMethod.Adaptive: - default: - throw new InvalidOperationException(); - } - - Assert.Equal(this.expectedSum, sum); - Assert.Equal(this.expectedResult, this.resultBuffer); - } + Assert.Equal(this.expectedSum, sum); + Assert.Equal(this.expectedResult, this.resultBuffer); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs index 23fccfe931..5a9b1df2a7 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.Chunks.cs @@ -1,330 +1,324 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public partial class PngEncoderTests { - [Trait("Format", "Png")] - public partial class PngEncoderTests + [Fact] + public void HeaderChunk_ComesFirst() { - [Fact] - public void HeaderChunk_ComesFirst() - { - // arrange - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.Equal(PngChunkType.Header, type); - } + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, PngEncoder); + + // assert + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.Equal(PngChunkType.Header, type); + } - [Fact] - public void EndChunk_IsLast() + [Fact] + public void EndChunk_IsLast() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, PngEncoder); + + // assert + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + bool endChunkFound = false; + while (bytesSpan.Length > 0) { - // arrange - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool endChunkFound = false; - while (bytesSpan.Length > 0) + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(endChunkFound); + if (type == PngChunkType.End) { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(endChunkFound); - if (type == PngChunkType.End) - { - endChunkFound = true; - } - - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + endChunkFound = true; } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); } + } - [Theory] - [InlineData(PngChunkType.Gamma)] - [InlineData(PngChunkType.Chroma)] - [InlineData(PngChunkType.EmbeddedColorProfile)] - [InlineData(PngChunkType.SignificantBits)] - [InlineData(PngChunkType.StandardRgbColourSpace)] - public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj) + [Theory] + [InlineData(PngChunkType.Gamma)] + [InlineData(PngChunkType.Chroma)] + [InlineData(PngChunkType.EmbeddedColorProfile)] + [InlineData(PngChunkType.SignificantBits)] + [InlineData(PngChunkType.StandardRgbColourSpace)] + public void Chunk_ComesBeforePlteAndIDat(object chunkTypeObj) + { + // arrange + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, PngEncoder); + + // assert + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + bool palFound = false; + bool dataFound = false; + while (bytesSpan.Length > 0) { - // arrange - var chunkType = (PngChunkType)chunkTypeObj; - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool palFound = false; - bool dataFound = false; - while (bytesSpan.Length > 0) + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + if (chunkType == type) { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - if (chunkType == type) - { - Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk"); - } - - switch (type) - { - case PngChunkType.Data: - dataFound = true; - break; - case PngChunkType.Palette: - palFound = true; - break; - } - - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + Assert.False(palFound || dataFound, $"{chunkType} chunk should come before data and palette chunk"); } - } - [Theory] - [InlineData(PngChunkType.Physical)] - [InlineData(PngChunkType.SuggestedPalette)] - public void Chunk_ComesBeforeIDat(object chunkTypeObj) - { - // arrange - var chunkType = (PngChunkType)chunkTypeObj; - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, PngEncoder); - - // assert - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - bool dataFound = false; - while (bytesSpan.Length > 0) + switch (type) { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - if (chunkType == type) - { - Assert.False(dataFound, $"{chunkType} chunk should come before data chunk"); - } - - if (type == PngChunkType.Data) - { + case PngChunkType.Data: dataFound = true; - } - - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + break; + case PngChunkType.Palette: + palFound = true; + break; } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); } + } - [Fact] - public void IgnoreMetadata_WillExcludeAllAncillaryChunks() + [Theory] + [InlineData(PngChunkType.Physical)] + [InlineData(PngChunkType.SuggestedPalette)] + public void Chunk_ComesBeforeIDat(object chunkTypeObj) + { + // arrange + var chunkType = (PngChunkType)chunkTypeObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, PngEncoder); + + // assert + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + bool dataFound = false; + while (bytesSpan.Length > 0) { - // arrange - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 }; - var expectedChunkTypes = new Dictionary() - { - { PngChunkType.Header, false }, - { PngChunkType.Palette, false }, - { PngChunkType.Data, false }, - { PngChunkType.End, false } - }; - var excludedChunkTypes = new List() - { - PngChunkType.Gamma, - PngChunkType.Exif, - PngChunkType.Physical, - PngChunkType.Text, - PngChunkType.InternationalText, - PngChunkType.CompressedText, - }; - - // act - input.Save(memStream, encoder); - - // assert - Assert.True(excludedChunkTypes.Count > 0); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var type = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + if (chunkType == type) { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); - if (expectedChunkTypes.ContainsKey(chunkType)) - { - expectedChunkTypes[chunkType] = true; - } - - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + Assert.False(dataFound, $"{chunkType} chunk should come before data chunk"); } - // all expected chunk types should have been seen at least once. - foreach (PngChunkType chunkType in expectedChunkTypes.Keys) + if (type == PngChunkType.Data) { - Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); + dataFound = true; } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); } + } - [Theory] - [InlineData(PngChunkFilter.ExcludeGammaChunk)] - [InlineData(PngChunkFilter.ExcludeExifChunk)] - [InlineData(PngChunkFilter.ExcludePhysicalChunk)] - [InlineData(PngChunkFilter.ExcludeTextChunks)] - [InlineData(PngChunkFilter.ExcludeAll)] - public void ExcludeFilter_Works(object filterObj) + [Fact] + public void IgnoreMetadata_WillExcludeAllAncillaryChunks() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { IgnoreMetadata = true, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new Dictionary() { - // arrange - var chunkFilter = (PngChunkFilter)filterObj; - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 }; - var expectedChunkTypes = new Dictionary() - { - { PngChunkType.Header, false }, - { PngChunkType.Gamma, false }, - { PngChunkType.Palette, false }, - { PngChunkType.InternationalText, false }, - { PngChunkType.Text, false }, - { PngChunkType.CompressedText, false }, - { PngChunkType.Exif, false }, - { PngChunkType.Physical, false }, - { PngChunkType.Data, false }, - { PngChunkType.End, false } - }; - var excludedChunkTypes = new List(); - switch (chunkFilter) + { PngChunkType.Header, false }, + { PngChunkType.Palette, false }, + { PngChunkType.Data, false }, + { PngChunkType.End, false } + }; + var excludedChunkTypes = new List() + { + PngChunkType.Gamma, + PngChunkType.Exif, + PngChunkType.Physical, + PngChunkType.Text, + PngChunkType.InternationalText, + PngChunkType.CompressedText, + }; + + // act + input.Save(memStream, encoder); + + // assert + Assert.True(excludedChunkTypes.Count > 0); + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); + if (expectedChunkTypes.ContainsKey(chunkType)) { - case PngChunkFilter.ExcludeGammaChunk: - excludedChunkTypes.Add(PngChunkType.Gamma); - expectedChunkTypes.Remove(PngChunkType.Gamma); - break; - case PngChunkFilter.ExcludeExifChunk: - excludedChunkTypes.Add(PngChunkType.Exif); - expectedChunkTypes.Remove(PngChunkType.Exif); - break; - case PngChunkFilter.ExcludePhysicalChunk: - excludedChunkTypes.Add(PngChunkType.Physical); - expectedChunkTypes.Remove(PngChunkType.Physical); - break; - case PngChunkFilter.ExcludeTextChunks: - excludedChunkTypes.Add(PngChunkType.Text); - excludedChunkTypes.Add(PngChunkType.InternationalText); - excludedChunkTypes.Add(PngChunkType.CompressedText); - expectedChunkTypes.Remove(PngChunkType.Text); - expectedChunkTypes.Remove(PngChunkType.InternationalText); - expectedChunkTypes.Remove(PngChunkType.CompressedText); - break; - case PngChunkFilter.ExcludeAll: - excludedChunkTypes.Add(PngChunkType.Gamma); - excludedChunkTypes.Add(PngChunkType.Exif); - excludedChunkTypes.Add(PngChunkType.Physical); - excludedChunkTypes.Add(PngChunkType.Text); - excludedChunkTypes.Add(PngChunkType.InternationalText); - excludedChunkTypes.Add(PngChunkType.CompressedText); - expectedChunkTypes.Remove(PngChunkType.Gamma); - expectedChunkTypes.Remove(PngChunkType.Exif); - expectedChunkTypes.Remove(PngChunkType.Physical); - expectedChunkTypes.Remove(PngChunkType.Text); - expectedChunkTypes.Remove(PngChunkType.InternationalText); - expectedChunkTypes.Remove(PngChunkType.CompressedText); - break; + expectedChunkTypes[chunkType] = true; } - // act - input.Save(memStream, encoder); + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); + } - // assert - Assert.True(excludedChunkTypes.Count > 0); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); - if (expectedChunkTypes.ContainsKey(chunkType)) - { - expectedChunkTypes[chunkType] = true; - } - - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); - } + // all expected chunk types should have been seen at least once. + foreach (PngChunkType chunkType in expectedChunkTypes.Keys) + { + Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); + } + } + + [Theory] + [InlineData(PngChunkFilter.ExcludeGammaChunk)] + [InlineData(PngChunkFilter.ExcludeExifChunk)] + [InlineData(PngChunkFilter.ExcludePhysicalChunk)] + [InlineData(PngChunkFilter.ExcludeTextChunks)] + [InlineData(PngChunkFilter.ExcludeAll)] + public void ExcludeFilter_Works(object filterObj) + { + // arrange + var chunkFilter = (PngChunkFilter)filterObj; + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { ChunkFilter = chunkFilter, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new Dictionary() + { + { PngChunkType.Header, false }, + { PngChunkType.Gamma, false }, + { PngChunkType.Palette, false }, + { PngChunkType.InternationalText, false }, + { PngChunkType.Text, false }, + { PngChunkType.CompressedText, false }, + { PngChunkType.Exif, false }, + { PngChunkType.Physical, false }, + { PngChunkType.Data, false }, + { PngChunkType.End, false } + }; + var excludedChunkTypes = new List(); + switch (chunkFilter) + { + case PngChunkFilter.ExcludeGammaChunk: + excludedChunkTypes.Add(PngChunkType.Gamma); + expectedChunkTypes.Remove(PngChunkType.Gamma); + break; + case PngChunkFilter.ExcludeExifChunk: + excludedChunkTypes.Add(PngChunkType.Exif); + expectedChunkTypes.Remove(PngChunkType.Exif); + break; + case PngChunkFilter.ExcludePhysicalChunk: + excludedChunkTypes.Add(PngChunkType.Physical); + expectedChunkTypes.Remove(PngChunkType.Physical); + break; + case PngChunkFilter.ExcludeTextChunks: + excludedChunkTypes.Add(PngChunkType.Text); + excludedChunkTypes.Add(PngChunkType.InternationalText); + excludedChunkTypes.Add(PngChunkType.CompressedText); + expectedChunkTypes.Remove(PngChunkType.Text); + expectedChunkTypes.Remove(PngChunkType.InternationalText); + expectedChunkTypes.Remove(PngChunkType.CompressedText); + break; + case PngChunkFilter.ExcludeAll: + excludedChunkTypes.Add(PngChunkType.Gamma); + excludedChunkTypes.Add(PngChunkType.Exif); + excludedChunkTypes.Add(PngChunkType.Physical); + excludedChunkTypes.Add(PngChunkType.Text); + excludedChunkTypes.Add(PngChunkType.InternationalText); + excludedChunkTypes.Add(PngChunkType.CompressedText); + expectedChunkTypes.Remove(PngChunkType.Gamma); + expectedChunkTypes.Remove(PngChunkType.Exif); + expectedChunkTypes.Remove(PngChunkType.Physical); + expectedChunkTypes.Remove(PngChunkType.Text); + expectedChunkTypes.Remove(PngChunkType.InternationalText); + expectedChunkTypes.Remove(PngChunkType.CompressedText); + break; + } - // all expected chunk types should have been seen at least once. - foreach (PngChunkType chunkType in expectedChunkTypes.Keys) + // act + input.Save(memStream, encoder); + + // assert + Assert.True(excludedChunkTypes.Count > 0); + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.False(excludedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been excluded"); + if (expectedChunkTypes.ContainsKey(chunkType)) { - Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); + expectedChunkTypes[chunkType] = true; } + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); } - [Fact] - public void ExcludeFilter_WithNone_DoesNotExcludeChunks() + // all expected chunk types should have been seen at least once. + foreach (PngChunkType chunkType in expectedChunkTypes.Keys) { - // arrange - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 }; - var expectedChunkTypes = new List() - { - PngChunkType.Header, - PngChunkType.Gamma, - PngChunkType.EmbeddedColorProfile, - PngChunkType.Palette, - PngChunkType.InternationalText, - PngChunkType.Text, - PngChunkType.CompressedText, - PngChunkType.Exif, - PngChunkType.Physical, - PngChunkType.Data, - PngChunkType.End, - }; - - // act - input.Save(memStream, encoder); - memStream.Position = 0; - Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. - while (bytesSpan.Length > 0) - { - int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); - var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); - Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present"); + Assert.True(expectedChunkTypes[chunkType], $"We expect {chunkType} chunk to be present at least once"); + } + } - bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); - } + [Fact] + public void ExcludeFilter_WithNone_DoesNotExcludeChunks() + { + // arrange + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + var encoder = new PngEncoder() { ChunkFilter = PngChunkFilter.None, TextCompressionThreshold = 8 }; + var expectedChunkTypes = new List() + { + PngChunkType.Header, + PngChunkType.Gamma, + PngChunkType.EmbeddedColorProfile, + PngChunkType.Palette, + PngChunkType.InternationalText, + PngChunkType.Text, + PngChunkType.CompressedText, + PngChunkType.Exif, + PngChunkType.Physical, + PngChunkType.Data, + PngChunkType.End, + }; + + // act + input.Save(memStream, encoder); + memStream.Position = 0; + Span bytesSpan = memStream.ToArray().AsSpan(8); // Skip header. + while (bytesSpan.Length > 0) + { + int length = BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(0, 4)); + var chunkType = (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(bytesSpan.Slice(4, 4)); + Assert.True(expectedChunkTypes.Contains(chunkType), $"{chunkType} chunk should have been present"); + + bytesSpan = bytesSpan.Slice(4 + 4 + length + 4); } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 7211d53673..2656144d30 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -2,8 +2,6 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; @@ -11,493 +9,514 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png -{ - [Trait("Format", "Png")] - public partial class PngEncoderTests - { - private static PngEncoder PngEncoder => new(); +namespace SixLabors.ImageSharp.Tests.Formats.Png; - public static readonly TheoryData PngBitDepthFiles = - new() - { - { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, - { TestImages.Png.Bpp1, PngBitDepth.Bit1 } - }; - - public static readonly TheoryData PngTrnsFiles = - new() - { - { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, - { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, - { TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale }, - { TestImages.Png.L8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, - { TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale }, - { TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb }, - { TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb } - }; +[Trait("Format", "Png")] +public partial class PngEncoderTests +{ + private static PngEncoder PngEncoder => new(); - /// - /// All types except Palette - /// - public static readonly TheoryData PngColorTypes = new() - { - PngColorType.RgbWithAlpha, - PngColorType.Rgb, - PngColorType.Grayscale, - PngColorType.GrayscaleWithAlpha, - }; + public static readonly TheoryData PngBitDepthFiles = + new() + { + { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, + { TestImages.Png.Bpp1, PngBitDepth.Bit1 } + }; - public static readonly TheoryData PngFilterMethods = new() - { - PngFilterMethod.None, - PngFilterMethod.Sub, - PngFilterMethod.Up, - PngFilterMethod.Average, - PngFilterMethod.Paeth, - PngFilterMethod.Adaptive - }; + public static readonly TheoryData PngTrnsFiles = + new() + { + { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, + { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, + { TestImages.Png.Gray4BitTrans, PngBitDepth.Bit4, PngColorType.Grayscale }, + { TestImages.Png.L8BitTrans, PngBitDepth.Bit8, PngColorType.Grayscale }, + { TestImages.Png.GrayTrns16BitInterlaced, PngBitDepth.Bit16, PngColorType.Grayscale }, + { TestImages.Png.Rgb24BppTrans, PngBitDepth.Bit8, PngColorType.Rgb }, + { TestImages.Png.Rgb48BppTrans, PngBitDepth.Bit16, PngColorType.Rgb } + }; + + /// + /// All types except Palette + /// + public static readonly TheoryData PngColorTypes = new() + { + PngColorType.RgbWithAlpha, + PngColorType.Rgb, + PngColorType.Grayscale, + PngColorType.GrayscaleWithAlpha, + }; - /// - /// All types except Palette - /// - public static readonly TheoryData CompressionLevels - = new() - { - PngCompressionLevel.Level0, - PngCompressionLevel.Level1, - PngCompressionLevel.Level2, - PngCompressionLevel.Level3, - PngCompressionLevel.Level4, - PngCompressionLevel.Level5, - PngCompressionLevel.Level6, - PngCompressionLevel.Level7, - PngCompressionLevel.Level8, - PngCompressionLevel.Level9, - }; + public static readonly TheoryData PngFilterMethods = new() + { + PngFilterMethod.None, + PngFilterMethod.Sub, + PngFilterMethod.Up, + PngFilterMethod.Average, + PngFilterMethod.Paeth, + PngFilterMethod.Adaptive + }; + + /// + /// All types except Palette + /// + public static readonly TheoryData CompressionLevels + = new() + { + PngCompressionLevel.Level0, + PngCompressionLevel.Level1, + PngCompressionLevel.Level2, + PngCompressionLevel.Level3, + PngCompressionLevel.Level4, + PngCompressionLevel.Level5, + PngCompressionLevel.Level6, + PngCompressionLevel.Level7, + PngCompressionLevel.Level8, + PngCompressionLevel.Level9, + }; + + public static readonly TheoryData PaletteSizes = new() + { + 30, 55, 100, 201, 255 + }; - public static readonly TheoryData PaletteSizes = new() - { - 30, 55, 100, 201, 255 - }; + public static readonly TheoryData PaletteLargeOnly = new() + { + 80, 100, 120, 230 + }; - public static readonly TheoryData PaletteLargeOnly = new() - { - 80, 100, 120, 230 - }; + public static readonly PngInterlaceMode[] InterlaceMode = new[] + { + PngInterlaceMode.None, + PngInterlaceMode.Adam7 + }; - public static readonly PngInterlaceMode[] InterlaceMode = new[] - { + public static readonly TheoryData RatioFiles = + new() + { + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] + [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 : unmanaged, IPixel + => TestPngEncoderCore( + provider, + pngColorType, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, PngInterlaceMode.None, - PngInterlaceMode.Adam7 - }; + appendPngColorType: true); - public static readonly TheoryData RatioFiles = - new() + [Theory] + [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) + where TPixel : unmanaged, IPixel + { + foreach (PngInterlaceMode interlaceMode in InterlaceMode) { - { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Theory] - [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] - [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 : unmanaged, IPixel - => TestPngEncoderCore( + TestPngEncoderCore( provider, pngColorType, PngFilterMethod.Adaptive, PngBitDepth.Bit8, - PngInterlaceMode.None, + interlaceMode, + appendPixelType: true, appendPngColorType: true); + } + } - [Theory] - [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] - public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) + where TPixel : unmanaged, IPixel + { + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.RgbWithAlpha, + pngFilterMethod, + PngBitDepth.Bit8, + interlaceMode, + appendPngFilterMethod: true); + } + } + + [Theory] + [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllCompressionLevels(TestImageProvider provider, PngCompressionLevel compressionLevel) + where TPixel : unmanaged, IPixel + { + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.RgbWithAlpha, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + compressionLevel, + appendCompressionLevel: true); + } + } + + [Theory] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] + [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 : unmanaged, IPixel + { + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) + { + return; + } + + foreach (object[] filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( provider, pngColorType, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, + (PngFilterMethod)filterMethod[0], + pngBitDepth, interlaceMode, + appendPngColorType: true, appendPixelType: true, - appendPngColorType: true); + appendPngBitDepth: true); } } + } - [Theory] - [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] - public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] + public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) + where TPixel : unmanaged, IPixel + { + foreach (object[] filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( provider, - PngColorType.RgbWithAlpha, - pngFilterMethod, - PngBitDepth.Bit8, + pngColorType, + (PngFilterMethod)filterMethod[0], + pngBitDepth, interlaceMode, - appendPngFilterMethod: true); + appendPngColorType: true, + appendPixelType: true, + appendPngBitDepth: true, + optimizeMethod: PngChunkFilter.ExcludeAll); } } + } - [Theory] - [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] - public void WorksWithAllCompressionLevels(TestImageProvider provider, PngCompressionLevel compressionLevel) - where TPixel : unmanaged, IPixel + [Theory] + [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] + [WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)] + [WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)] + [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 : unmanaged, IPixel + { + using Stream stream = new MemoryStream(); + PngEncoder.Encode(provider.GetImage(), stream); + + stream.Seek(0, SeekOrigin.Begin); + + var decoder = new PngDecoder(); + + Image image = decoder.Decode(DecoderOptions.Default, stream); + + PngMetadata metadata = image.Metadata.GetPngMetadata(); + Assert.Equal(pngColorType, metadata.ColorType); + Assert.Equal(pngBitDepth, metadata.BitDepth); + } + + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] + public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) + where TPixel : unmanaged, IPixel + { + // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. + if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( + return; + } + + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( provider, - PngColorType.RgbWithAlpha, + PngColorType.Palette, PngFilterMethod.Adaptive, PngBitDepth.Bit8, interlaceMode, - compressionLevel, - appendCompressionLevel: true); - } + paletteSize: paletteSize, + appendPaletteSize: true); } + } - [Theory] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] - [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 : unmanaged, IPixel - { - // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. - if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) - { - return; - } - - foreach (object[] filterMethod in PngFilterMethods) - { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true); - } - } - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void WritesFileMarker(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + using var ms = new MemoryStream(); + image.Save(ms, PngEncoder); - [Theory] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.Palette, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit1)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit2)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit4)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb24, PngColorType.Grayscale, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgb48, PngColorType.Grayscale, PngBitDepth.Bit16)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] - public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : unmanaged, IPixel + byte[] data = ms.ToArray().Take(8).ToArray(); + byte[] expected = { - foreach (object[] filterMethod in PngFilterMethods) - { - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( - provider, - pngColorType, - (PngFilterMethod)filterMethod[0], - pngBitDepth, - interlaceMode, - appendPngColorType: true, - appendPixelType: true, - appendPngBitDepth: true, - optimizeMethod: PngChunkFilter.ExcludeAll); - } - } - } + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; - [Theory] - [WithBlankImages(1, 1, PixelTypes.A8, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Argb32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Bgr565, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Bgra4444, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Byte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.HalfSingle, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.HalfVector2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.HalfVector4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.NormalizedByte2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.NormalizedByte4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.NormalizedShort4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Rg32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Rgba1010102, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Rgba32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.RgbaVector, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithBlankImages(1, 1, PixelTypes.Short2, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Short4, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Rgb24, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Bgr24, PngColorType.Rgb, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Bgra32, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.Rgb48, PngColorType.Rgb, PngBitDepth.Bit16)] - [WithBlankImages(1, 1, PixelTypes.Rgba64, PngColorType.RgbWithAlpha, PngBitDepth.Bit16)] - [WithBlankImages(1, 1, PixelTypes.Bgra5551, PngColorType.RgbWithAlpha, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.L8, PngColorType.Grayscale, PngBitDepth.Bit8)] - [WithBlankImages(1, 1, PixelTypes.L16, PngColorType.Grayscale, PngBitDepth.Bit16)] - [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 : unmanaged, IPixel - { - using Stream stream = new MemoryStream(); - PngEncoder.Encode(provider.GetImage(), stream); + Assert.Equal(expected, data); + } - stream.Seek(0, SeekOrigin.Begin); + [Theory] + [MemberData(nameof(RatioFiles))] + public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageMetadata meta = output.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - var decoder = new PngDecoder(); + [Theory] + [MemberData(nameof(PngBitDepthFiles))] + public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) + { + var testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); - Image image = decoder.Decode(DecoderOptions.Default, stream); + memStream.Position = 0; + using var output = Image.Load(memStream); + PngMetadata meta = output.Metadata.GetPngMetadata(); - PngMetadata metadata = image.Metadata.GetPngMetadata(); - Assert.Equal(pngColorType, metadata.ColorType); - Assert.Equal(pngBitDepth, metadata.BitDepth); - } + Assert.Equal(pngBitDepth, meta.BitDepth); + } - [Theory] - [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] - public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) - where TPixel : unmanaged, IPixel + [Theory] + [InlineData(PngColorType.Palette)] + [InlineData(PngColorType.RgbWithAlpha)] + [InlineData(PngColorType.GrayscaleWithAlpha)] + public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType) + { + // arrange + var image = new Image(50, 50); + var encoder = new PngEncoder() { - // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. - if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) - { - return; - } - - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( - provider, - PngColorType.Palette, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - paletteSize: paletteSize, - appendPaletteSize: true); - } - } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void WritesFileMarker(TestImageProvider provider) - where TPixel : unmanaged, IPixel + TransparentColorMode = PngTransparentColorMode.Clear, + ColorType = colorType + }; + Rgba32 rgba32 = Color.Blue; + image.ProcessPixelRows(accessor => { - using Image image = provider.GetImage(); - using var ms = new MemoryStream(); - image.Save(ms, PngEncoder); - - byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = + for (int y = 0; y < image.Height; y++) { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }; - - Assert.Equal(expected, data); - } + System.Span rowSpan = accessor.GetRowSpan(y); - [Theory] - [MemberData(nameof(RatioFiles))] - public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - input.Save(memStream, PngEncoder); - - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageMetadata meta = output.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } + // Half of the test image should be transparent. + if (y > 25) + { + rgba32.A = 0; + } - [Theory] - [MemberData(nameof(PngBitDepthFiles))] - public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) - { - var testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - using var memStream = new MemoryStream(); - input.Save(memStream, PngEncoder); + for (int x = 0; x < image.Width; x++) + { + rowSpan[x].FromRgba32(rgba32); + } + } + }); - memStream.Position = 0; - using var output = Image.Load(memStream); - PngMetadata meta = output.Metadata.GetPngMetadata(); + // act + using var memStream = new MemoryStream(); + image.Save(memStream, encoder); - Assert.Equal(pngBitDepth, meta.BitDepth); + // assert + memStream.Position = 0; + using var actual = Image.Load(memStream); + Rgba32 expectedColor = Color.Blue; + if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha) + { + byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); + expectedColor = new Rgba32(luminance, luminance, luminance); } - [Theory] - [InlineData(PngColorType.Palette)] - [InlineData(PngColorType.RgbWithAlpha)] - [InlineData(PngColorType.GrayscaleWithAlpha)] - public void Encode_WithPngTransparentColorBehaviorClear_Works(PngColorType colorType) + actual.ProcessPixelRows(accessor => { - // arrange - var image = new Image(50, 50); - var encoder = new PngEncoder() - { - TransparentColorMode = PngTransparentColorMode.Clear, - ColorType = colorType - }; - Rgba32 rgba32 = Color.Blue; - image.ProcessPixelRows(accessor => + for (int y = 0; y < accessor.Height; y++) { - for (int y = 0; y < image.Height; y++) + System.Span rowSpan = accessor.GetRowSpan(y); + + if (y > 25) { - System.Span rowSpan = accessor.GetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) - { - rgba32.A = 0; - } - - for (int x = 0; x < image.Width; x++) - { - rowSpan[x].FromRgba32(rgba32); - } + expectedColor = Color.Transparent; } - }); - - // act - using var memStream = new MemoryStream(); - image.Save(memStream, encoder); - // assert - memStream.Position = 0; - using var actual = Image.Load(memStream); - Rgba32 expectedColor = Color.Blue; - if (colorType is PngColorType.Grayscale or PngColorType.GrayscaleWithAlpha) - { - byte luminance = ColorNumerics.Get8BitBT709Luminance(expectedColor.R, expectedColor.G, expectedColor.B); - expectedColor = new Rgba32(luminance, luminance, luminance); + for (int x = 0; x < accessor.Width; x++) + { + Assert.Equal(expectedColor, rowSpan[x]); + } } + }); + } - actual.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) + [Theory] + [MemberData(nameof(PngTrnsFiles))] + public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) + { + var testFile = TestFile.Create(imagePath); + using Image input = testFile.CreateRgba32Image(); + PngMetadata inMeta = input.Metadata.GetPngMetadata(); + Assert.True(inMeta.HasTransparency); + + using var memStream = new MemoryStream(); + input.Save(memStream, PngEncoder); + memStream.Position = 0; + using var output = Image.Load(memStream); + PngMetadata outMeta = output.Metadata.GetPngMetadata(); + Assert.True(outMeta.HasTransparency); + + switch (pngColorType) + { + case PngColorType.Grayscale: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) { - System.Span rowSpan = accessor.GetRowSpan(y); - - if (y > 25) - { - expectedColor = Color.Transparent; - } + Assert.True(outMeta.TransparentL16.HasValue); + Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); + } + else + { + Assert.True(outMeta.TransparentL8.HasValue); + Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); + } - for (int x = 0; x < accessor.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); - } + break; + case PngColorType.Rgb: + if (pngBitDepth.Equals(PngBitDepth.Bit16)) + { + Assert.True(outMeta.TransparentRgb48.HasValue); + Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48); + } + else + { + Assert.True(outMeta.TransparentRgb24.HasValue); + Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24); } - }); + + break; } + } - [Theory] - [MemberData(nameof(PngTrnsFiles))] - public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngColorType pngColorType) + [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) { - var testFile = TestFile.Create(imagePath); - using Image input = testFile.CreateRgba32Image(); - PngMetadata inMeta = input.Metadata.GetPngMetadata(); - Assert.True(inMeta.HasTransparency); - - using var memStream = new MemoryStream(); - input.Save(memStream, PngEncoder); - memStream.Position = 0; - using var output = Image.Load(memStream); - PngMetadata outMeta = output.Metadata.GetPngMetadata(); - Assert.True(outMeta.HasTransparency); - - switch (pngColorType) - { - case PngColorType.Grayscale: - if (pngBitDepth.Equals(PngBitDepth.Bit16)) - { - Assert.True(outMeta.TransparentL16.HasValue); - Assert.Equal(inMeta.TransparentL16, outMeta.TransparentL16); - } - else - { - Assert.True(outMeta.TransparentL8.HasValue); - Assert.Equal(inMeta.TransparentL8, outMeta.TransparentL8); - } - - break; - case PngColorType.Rgb: - if (pngBitDepth.Equals(PngBitDepth.Bit16)) - { - Assert.True(outMeta.TransparentRgb48.HasValue); - Assert.Equal(inMeta.TransparentRgb48, outMeta.TransparentRgb48); - } - else - { - Assert.True(outMeta.TransparentRgb24.HasValue); - Assert.Equal(inMeta.TransparentRgb24, outMeta.TransparentRgb24); - } - - break; - } + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); } + } - [Theory] - [WithTestPatternImages(587, 821, PixelTypes.Rgba32)] - [WithTestPatternImages(677, 683, PixelTypes.Rgba32)] - public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void EncodeWorksWithoutSsse3Intrinsics(TestImageProvider provider) + { + static void RunTest(string serialized) { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + TestImageProvider provider = + FeatureTestRunner.DeserializeForXunit>(serialized); + foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( @@ -511,98 +530,75 @@ public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) - { - static void RunTest(string serialized) - { - TestImageProvider provider = - FeatureTestRunner.DeserializeForXunit>(serialized); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableSSSE3, + provider); + } - foreach (PngInterlaceMode interlaceMode in InterlaceMode) - { - TestPngEncoderCore( - provider, - PngColorType.Rgb, - PngFilterMethod.Adaptive, - PngBitDepth.Bit8, - interlaceMode, - appendPixelType: true, - appendPngColorType: true); - } - } + [Fact] + public void EncodeFixesInvalidOptions() + { + // https://github.com/SixLabors/ImageSharp/issues/935 + using var ms = new MemoryStream(); + var testFile = TestFile.Create(TestImages.Png.Issue935); + using Image image = testFile.CreateRgba32Image(new PngDecoder()); - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableSSSE3, - provider); - } + image.Save(ms, new PngEncoder { ColorType = PngColorType.RgbWithAlpha }); + } - [Fact] - public void EncodeFixesInvalidOptions() + private static void TestPngEncoderCore( + TestImageProvider provider, + PngColorType pngColorType, + PngFilterMethod pngFilterMethod, + PngBitDepth bitDepth, + PngInterlaceMode interlaceMode, + PngCompressionLevel compressionLevel = PngCompressionLevel.DefaultCompression, + int paletteSize = 255, + bool appendPngColorType = false, + bool appendPngFilterMethod = false, + bool appendPixelType = false, + bool appendCompressionLevel = false, + bool appendPaletteSize = false, + bool appendPngBitDepth = false, + PngChunkFilter optimizeMethod = PngChunkFilter.None) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + var encoder = new PngEncoder { - // https://github.com/SixLabors/ImageSharp/issues/935 - using var ms = new MemoryStream(); - var testFile = TestFile.Create(TestImages.Png.Issue935); - using Image image = testFile.CreateRgba32Image(new PngDecoder()); + ColorType = pngColorType, + FilterMethod = pngFilterMethod, + CompressionLevel = compressionLevel, + BitDepth = bitDepth, + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), + InterlaceMethod = interlaceMode, + ChunkFilter = optimizeMethod, + }; - image.Save(ms, new PngEncoder { ColorType = PngColorType.RgbWithAlpha }); - } + string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; + string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; + string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; + string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; + string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; + string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty; - private static void TestPngEncoderCore( - TestImageProvider provider, - PngColorType pngColorType, - PngFilterMethod pngFilterMethod, - PngBitDepth bitDepth, - PngInterlaceMode interlaceMode, - PngCompressionLevel compressionLevel = PngCompressionLevel.DefaultCompression, - int paletteSize = 255, - bool appendPngColorType = false, - bool appendPngFilterMethod = false, - bool appendPixelType = false, - bool appendCompressionLevel = false, - bool appendPaletteSize = false, - bool appendPngBitDepth = false, - PngChunkFilter optimizeMethod = PngChunkFilter.None) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - var encoder = new PngEncoder - { - ColorType = pngColorType, - FilterMethod = pngFilterMethod, - CompressionLevel = compressionLevel, - BitDepth = bitDepth, - Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), - InterlaceMethod = interlaceMode, - ChunkFilter = optimizeMethod, - }; - - string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty; - string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty; - string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty; - string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty; - string pngBitDepthInfo = appendPngBitDepth ? bitDepth.ToString() : string.Empty; - string pngInterlaceModeInfo = interlaceMode != PngInterlaceMode.None ? $"_{interlaceMode}" : string.Empty; - - string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}"; - - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - - // 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. - using FileStream fileStream = File.OpenRead(actualOutputFile); - using Image imageSharpImage = new PngDecoder().Decode(DecoderOptions.Default, fileStream); - - fileStream.Position = 0; - - using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, fileStream, default); - ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); - } + string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}{pngInterlaceModeInfo}"; + + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); + + // 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. + using FileStream fileStream = File.OpenRead(actualOutputFile); + using Image imageSharpImage = new PngDecoder().Decode(DecoderOptions.Default, fileStream); + + fileStream.Position = 0; + + using Image referenceImage = referenceDecoder.Decode(DecoderOptions.Default, fileStream, default); + ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 74217c9821..a0d2423aab 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -1,316 +1,310 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngMetadataTests { - [Trait("Format", "Png")] - public class PngMetadataTests - { - public static readonly TheoryData RatioFiles = - new() - { - { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, - { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, - { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } - }; - - [Fact] - public void CloneIsDeep() - { - var meta = new PngMetadata - { - BitDepth = PngBitDepth.Bit16, - ColorType = PngColorType.GrayscaleWithAlpha, - InterlaceMethod = PngInterlaceMode.Adam7, - Gamma = 2, - TextData = new List { new PngTextData("name", "value", "foo", "bar") } - }; - - var clone = (PngMetadata)meta.DeepClone(); - - clone.BitDepth = PngBitDepth.Bit2; - clone.ColorType = PngColorType.Palette; - clone.InterlaceMethod = PngInterlaceMode.None; - clone.Gamma = 1; - - Assert.False(meta.BitDepth == clone.BitDepth); - Assert.False(meta.ColorType == clone.ColorType); - Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); - Assert.False(meta.Gamma.Equals(clone.Gamma)); - Assert.False(meta.TextData.Equals(clone.TextData)); - Assert.True(meta.TextData.SequenceEqual(clone.TextData)); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decoder_CanReadTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(new PngDecoder()); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encoder_PreservesTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public static readonly TheoryData RatioFiles = + new() { - var decoder = new PngDecoder(); - using Image input = provider.GetImage(decoder); - using var memoryStream = new MemoryStream(); - input.Save(memoryStream, new PngEncoder()); - - memoryStream.Position = 0; - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] - public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) - where TPixel : unmanaged, IPixel + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, + { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } + }; + + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetadata { - using Image image = provider.GetImage(new PngDecoder()); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); - Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + InterlaceMethod = PngInterlaceMode.Adam7, + Gamma = 2, + TextData = new List { new PngTextData("name", "value", "foo", "bar") } + }; + + var clone = (PngMetadata)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.InterlaceMethod = PngInterlaceMode.None; + clone.Gamma = 1; + + Assert.False(meta.BitDepth == clone.BitDepth); + Assert.False(meta.ColorType == clone.ColorType); + Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); + Assert.False(meta.Gamma.Equals(clone.Gamma)); + Assert.False(meta.TextData.Equals(clone.TextData)); + Assert.True(meta.TextData.SequenceEqual(clone.TextData)); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decoder_CanReadTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(new PngDecoder()); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encoder_PreservesTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var decoder = new PngDecoder(); + using Image input = provider.GetImage(decoder); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, new PngEncoder()); + + memoryStream.Position = 0; + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + + [Theory] + [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] + public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(new PngDecoder()); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, m => m.Value is "leading space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "trailing space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "space"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "empty"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "invalid characters"); + Assert.DoesNotContain(meta.TextData, m => m.Value is "too large"); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) + 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. + var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); + + // 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); + inputMetadata.TextData.Add(expectedTextNoneLatin); + input.Save(memoryStream, new PngEncoder { - var decoder = new PngDecoder(); - using Image input = provider.GetImage(decoder); - using var memoryStream = new MemoryStream(); - - // 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. - 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); - inputMetadata.TextData.Add(expectedTextNoneLatin); - input.Save(memoryStream, new PngEncoder - { - TextCompressionThreshold = 50 - }); - - memoryStream.Position = 0; - using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Contains(meta.TextData, m => m.Equals(expectedText)); - Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decode_ReadsExifData(TestImageProvider provider) - where TPixel : unmanaged, IPixel + TextCompressionThreshold = 50 + }); + + memoryStream.Position = 0; + using Image image = decoder.Decode(DecoderOptions.Default, memoryStream); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Contains(meta.TextData, m => m.Equals(expectedText)); + Assert.Contains(meta.TextData, m => m.Equals(expectedTextNoneLatin)); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decode_ReadsExifData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - SkipMetadata = false - }; - - using Image image = provider.GetImage(new PngDecoder(), options); - Assert.NotNull(image.Metadata.ExifProfile); - ExifProfile exif = image.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) - where TPixel : unmanaged, IPixel + SkipMetadata = false + }; + + using Image image = provider.GetImage(new PngDecoder(), options); + Assert.NotNull(image.Metadata.ExifProfile); + ExifProfile exif = image.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); + } + + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Decode_IgnoresExifData_WhenIgnoreMetadataIsTrue(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - SkipMetadata = true - }; + SkipMetadata = true + }; - PngDecoder decoder = new(); + PngDecoder decoder = new(); - using Image image = provider.GetImage(decoder, options); - Assert.Null(image.Metadata.ExifProfile); - } + using Image image = provider.GetImage(decoder, options); + Assert.Null(image.Metadata.ExifProfile); + } - [Fact] - public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() + [Fact] + public void Decode_IgnoreMetadataIsFalse_TextChunkIsRead() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - SkipMetadata = false - }; + SkipMetadata = false + }; - var testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); - using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(1, meta.TextData.Count); - Assert.Equal("Software", meta.TextData[0].Keyword); - Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); - Assert.Equal(0.4545d, meta.Gamma, precision: 4); - } + Assert.Equal(1, meta.TextData.Count); + Assert.Equal("Software", meta.TextData[0].Keyword); + Assert.Equal("paint.net 4.0.6", meta.TextData[0].Value); + Assert.Equal(0.4545d, meta.Gamma, precision: 4); + } - [Fact] - public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + [Fact] + public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - SkipMetadata = true - }; + SkipMetadata = true + }; - var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); + var testFile = TestFile.Create(TestImages.Png.PngWithMetadata); - using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); - PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.Equal(0, meta.TextData.Count); - } + using Image image = testFile.CreateRgba32Image(new PngDecoder(), options); + PngMetadata meta = image.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.Equal(0, meta.TextData.Count); + } - [Theory] - [MemberData(nameof(RatioFiles))] - public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new PngDecoder(); - using Image image = decoder.Decode(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] - public void Encode_PreservesColorProfile(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image input = provider.GetImage(new PngDecoder()); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); + [Theory] + [MemberData(nameof(RatioFiles))] + public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new PngDecoder(); + using Image image = decoder.Decode(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } - using var memStream = new MemoryStream(); - input.Save(memStream, new PngEncoder()); + [Theory] + [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] + public void Encode_PreservesColorProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image input = provider.GetImage(new PngDecoder()); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); + using var memStream = new MemoryStream(); + input.Save(memStream, new PngEncoder()); - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); - } + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); - [Theory] - [MemberData(nameof(RatioFiles))] - public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - var decoder = new PngDecoder(); - IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); - ImageMetadata meta = image.Metadata; - Assert.Equal(xResolution, meta.HorizontalResolution); - Assert.Equal(yResolution, meta.VerticalResolution); - Assert.Equal(resolutionUnit, meta.ResolutionUnits); - } - - [Theory] - [InlineData(TestImages.Png.PngWithMetadata)] - public void Identify_ReadsTextData(string imagePath) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - VerifyTextDataIsPresent(meta); - } - - [Theory] - [InlineData(TestImages.Png.PngWithMetadata)] - public void Identify_ReadsExifData(string imagePath) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - ExifProfile exif = imageInfo.Metadata.ExifProfile; - VerifyExifDataIsPresent(exif); - } - - private static void VerifyExifDataIsPresent(ExifProfile exif) - { - Assert.Equal(1, exif.Values.Count); - IExifValue software = exif.GetValue(ExifTag.Software); - Assert.NotNull(software); - Assert.Equal("ImageSharp", software.Value); - } + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); + } - private static void VerifyTextDataIsPresent(PngMetadata meta) - { - Assert.NotNull(meta); - Assert.Contains(meta.TextData, m => m.Keyword is "Comment" && m.Value is "comment"); - Assert.Contains(meta.TextData, m => m.Keyword is "Author" && m.Value is "ImageSharp"); - Assert.Contains(meta.TextData, m => m.Keyword is "Copyright" && m.Value is "ImageSharp"); - Assert.Contains(meta.TextData, m => m.Keyword is "Title" && m.Value is "unittest"); - Assert.Contains(meta.TextData, m => m.Keyword is "Description" && m.Value is "compressed-text"); - Assert.Contains(meta.TextData, m => m.Keyword is "International" && m.Value is "'e', mu'tlheghvam, ghaH yu'" && m.LanguageTag is "x-klingon" && m.TranslatedKeyword is "warning"); - Assert.Contains(meta.TextData, m => m.Keyword is "International2" && m.Value is "ИМАГЕШАРП" && m.LanguageTag is "rus"); - Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational" && m.Value is "la plume de la mante" && m.LanguageTag is "fra" && m.TranslatedKeyword is "foobar"); - Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational2" && m.Value is "這是一個考驗" && m.LanguageTag is "chinese"); - Assert.Contains(meta.TextData, m => m.Keyword is "NoLang" && m.Value is "this text chunk is missing a language tag"); - Assert.Contains(meta.TextData, m => m.Keyword is "NoTranslatedKeyword" && m.Value is "dieser chunk hat kein übersetztes Schlüßelwort"); - } - - [Theory] - [InlineData(TestImages.Png.Issue1875)] - public void Identify_ReadsLegacyExifData(string imagePath) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.NotNull(imageInfo.Metadata.ExifProfile); - - PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); - Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); - - ExifProfile exif = imageInfo.Metadata.ExifProfile; - Assert.Equal(0, exif.InvalidTags.Count); - Assert.Equal(3, exif.Values.Count); - - Assert.Equal( - "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", - exif.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal( - "Duplicated from basn3p02.png, then image metadata modified with exiv2", - exif.GetValue(ExifTag.ImageHistory).Value); - - Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); - } + [Theory] + [MemberData(nameof(RatioFiles))] + public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + var decoder = new PngDecoder(); + IImageInfo image = decoder.Identify(DecoderOptions.Default, stream); + ImageMetadata meta = image.Metadata; + Assert.Equal(xResolution, meta.HorizontalResolution); + Assert.Equal(yResolution, meta.VerticalResolution); + Assert.Equal(resolutionUnit, meta.ResolutionUnits); + } + + [Theory] + [InlineData(TestImages.Png.PngWithMetadata)] + public void Identify_ReadsTextData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + VerifyTextDataIsPresent(meta); + } + + [Theory] + [InlineData(TestImages.Png.PngWithMetadata)] + public void Identify_ReadsExifData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + ExifProfile exif = imageInfo.Metadata.ExifProfile; + VerifyExifDataIsPresent(exif); + } + + private static void VerifyExifDataIsPresent(ExifProfile exif) + { + Assert.Equal(1, exif.Values.Count); + IExifValue software = exif.GetValue(ExifTag.Software); + Assert.NotNull(software); + Assert.Equal("ImageSharp", software.Value); + } + + private static void VerifyTextDataIsPresent(PngMetadata meta) + { + Assert.NotNull(meta); + Assert.Contains(meta.TextData, m => m.Keyword is "Comment" && m.Value is "comment"); + Assert.Contains(meta.TextData, m => m.Keyword is "Author" && m.Value is "ImageSharp"); + Assert.Contains(meta.TextData, m => m.Keyword is "Copyright" && m.Value is "ImageSharp"); + Assert.Contains(meta.TextData, m => m.Keyword is "Title" && m.Value is "unittest"); + Assert.Contains(meta.TextData, m => m.Keyword is "Description" && m.Value is "compressed-text"); + Assert.Contains(meta.TextData, m => m.Keyword is "International" && m.Value is "'e', mu'tlheghvam, ghaH yu'" && m.LanguageTag is "x-klingon" && m.TranslatedKeyword is "warning"); + Assert.Contains(meta.TextData, m => m.Keyword is "International2" && m.Value is "ИМАГЕШАРП" && m.LanguageTag is "rus"); + Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational" && m.Value is "la plume de la mante" && m.LanguageTag is "fra" && m.TranslatedKeyword is "foobar"); + Assert.Contains(meta.TextData, m => m.Keyword is "CompressedInternational2" && m.Value is "這是一個考驗" && m.LanguageTag is "chinese"); + Assert.Contains(meta.TextData, m => m.Keyword is "NoLang" && m.Value is "this text chunk is missing a language tag"); + Assert.Contains(meta.TextData, m => m.Keyword is "NoTranslatedKeyword" && m.Value is "dieser chunk hat kein übersetztes Schlüßelwort"); + } + + [Theory] + [InlineData(TestImages.Png.Issue1875)] + public void Identify_ReadsLegacyExifData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); + + ExifProfile exif = imageInfo.Metadata.ExifProfile; + Assert.Equal(0, exif.InvalidTags.Count); + Assert.Equal(3, exif.Values.Count); + + Assert.Equal( + "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", + exif.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal( + "Duplicated from basn3p02.png, then image metadata modified with exiv2", + exif.GetValue(ExifTag.ImageHistory).Value); + + Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 58843227d0..8bc64362dd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngSmokeTests { - [Trait("Format", "Png")] - public class PngSmokeTests + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] + public void GeneralTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // does saving a file then reopening mean both files are identical??? + using Image image = provider.GetImage(); + using var ms = new MemoryStream(); + + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); + ImageComparer.Tolerant().VerifySimilarity(image, img2); + + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + } + + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] + public void Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] - public void GeneralTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // does saving a file then reopening mean both files are identical??? - using Image image = provider.GetImage(); - using var ms = new MemoryStream(); - - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); - ms.Position = 0; - using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); - ImageComparer.Tolerant().VerifySimilarity(image, img2); - - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - } - - [Theory] - [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] - public void Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // does saving a file then reopening mean both files are identical??? - using Image image = provider.GetImage(); - 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(ms, new PngEncoder()); - ms.Position = 0; - using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); - ImageComparer.Tolerant().VerifySimilarity(image, img2); - } + // does saving a file then reopening mean both files are identical??? + using Image image = provider.GetImage(); + 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(ms, new PngEncoder()); + ms.Position = 0; + using Image img2 = new PngDecoder().Decode(DecoderOptions.Default, ms); + ImageComparer.Tolerant().VerifySimilarity(image, img2); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs index 353250a25e..04341a2419 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngTextDataTests.cs @@ -1,77 +1,74 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Png; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +/// +/// Tests the class. +/// +[Trait("Format", "Png")] +public class PngTextDataTests { /// - /// Tests the class. + /// Tests the equality operators for inequality. /// - [Trait("Format", "Png")] - public class PngTextDataTests + [Fact] + public void AreEqual() { - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreEqual() - { - var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); - var property2 = new PngTextData("Foo", "Bar", "foo", "bar"); + var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); + var property2 = new PngTextData("Foo", "Bar", "foo", "bar"); - Assert.Equal(property1, property2); - Assert.True(property1 == property2); - } + Assert.Equal(property1, property2); + Assert.True(property1 == property2); + } - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreNotEqual() - { - var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); - var property2 = new PngTextData("Foo", "Foo", string.Empty, string.Empty); - var property3 = new PngTextData("Bar", "Bar", "unit", "test"); - var property4 = new PngTextData("Foo", null, "test", "case"); + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreNotEqual() + { + var property1 = new PngTextData("Foo", "Bar", "foo", "bar"); + var property2 = new PngTextData("Foo", "Foo", string.Empty, string.Empty); + var property3 = new PngTextData("Bar", "Bar", "unit", "test"); + var property4 = new PngTextData("Foo", null, "test", "case"); - Assert.NotEqual(property1, property2); - Assert.True(property1 != property2); + Assert.NotEqual(property1, property2); + Assert.True(property1 != property2); - Assert.NotEqual(property1, property3); - Assert.NotEqual(property1, property4); - } + Assert.NotEqual(property1, property3); + Assert.NotEqual(property1, property4); + } - /// - /// Tests whether the constructor throws an exception when the property keyword is null or empty. - /// - [Fact] - public void ConstructorThrowsWhenKeywordIsNullOrEmpty() - { - Assert.Throws(() => new PngTextData(null, "Foo", "foo", "bar")); + /// + /// Tests whether the constructor throws an exception when the property keyword is null or empty. + /// + [Fact] + public void ConstructorThrowsWhenKeywordIsNullOrEmpty() + { + Assert.Throws(() => new PngTextData(null, "Foo", "foo", "bar")); - Assert.Throws(() => new PngTextData(string.Empty, "Foo", "foo", "bar")); - } + Assert.Throws(() => new PngTextData(string.Empty, "Foo", "foo", "bar")); + } - /// - /// Tests whether the constructor correctly assigns properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var property = new PngTextData("Foo", null, "unit", "test"); - Assert.Equal("Foo", property.Keyword); - Assert.Null(property.Value); - Assert.Equal("unit", property.LanguageTag); - Assert.Equal("test", property.TranslatedKeyword); + /// + /// Tests whether the constructor correctly assigns properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var property = new PngTextData("Foo", null, "unit", "test"); + Assert.Equal("Foo", property.Keyword); + Assert.Null(property.Value); + Assert.Equal("unit", property.LanguageTag); + Assert.Equal("test", property.TranslatedKeyword); - property = new PngTextData("Foo", string.Empty, string.Empty, null); - Assert.Equal("Foo", property.Keyword); - Assert.Equal(string.Empty, property.Value); - Assert.Equal(string.Empty, property.LanguageTag); - Assert.Null(property.TranslatedKeyword); - } + property = new PngTextData("Foo", string.Empty, string.Empty, null); + Assert.Equal("Foo", property.Keyword); + Assert.Equal(string.Empty, property.Value); + Assert.Equal(string.Empty, property.LanguageTag); + Assert.Null(property.TranslatedKeyword); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs index dbc0bde0ed..d57a775876 100644 --- a/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Png/ReferenceImplementations.cs @@ -1,221 +1,219 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Png +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +/// +/// This class contains reference implementations to produce verification data for unit tests +/// +internal static partial class ReferenceImplementations { /// - /// This class contains reference implementations to produce verification data for unit tests + /// Encodes the scanline /// - internal static partial class ReferenceImplementations + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) { - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodePaethFilter(ReadOnlySpan scanline, Span previousScanline, Span result, int bytesPerPixel, out int sum) + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + + // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) + resultBaseRef = 4; + + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) - resultBaseRef = 4; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(0, above, 0)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(0, above, 0)); + sum += Numerics.Abs(unchecked((sbyte)res)); } - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Sub(x) = Raw(x) - Raw(x-bpp) - resultBaseRef = 1; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = scan; - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, xLeft); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - prev); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += Numerics.Abs(unchecked((sbyte)res)); } + } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EncodeSubFilter(ReadOnlySpan scanline, Span result, int bytesPerPixel, out int sum) + { + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + + // Sub(x) = Raw(x) - Raw(x-bpp) + resultBaseRef = 1; + + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Up(x) = Raw(x) - Prior(x) - resultBaseRef = 2; - - int x = 0; - - for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + byte scan = Unsafe.Add(ref scanBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = scan; + sum += Numerics.Abs(unchecked((sbyte)res)); } - /// - /// Encodes the scanline - /// - /// The scanline to encode - /// The previous scanline. - /// The filtered scanline result. - /// The bytes per pixel. - /// The sum of the total variance of the filtered row - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { - DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - - // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) - resultBaseRef = 3; - - int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } - - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref prevBaseRef, x); - ++x; - ref byte res = ref Unsafe.Add(ref resultBaseRef, x); - res = (byte)(scan - Average(left, above)); - sum += Numerics.Abs(unchecked((sbyte)res)); - } + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - prev); + sum += Numerics.Abs(unchecked((sbyte)res)); } + } - /// - /// Calculates the average value of two bytes - /// - /// The left byte - /// The above byte - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Average(byte left, byte above) => (left + above) >> 1; - - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbor pixel. - /// The above neighbor pixel. - /// The upper left neighbor pixel. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EncodeUpFilter(ReadOnlySpan scanline, Span previousScanline, Span result, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + + // Up(x) = Raw(x) - Prior(x) + resultBaseRef = 2; + + int x = 0; + + for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) { - int p = left + above - upperLeft; - int pa = Numerics.Abs(p - left); - int pb = Numerics.Abs(p - above); - int pc = Numerics.Abs(p - upperLeft); - - if (pa <= pb && pa <= pc) - { - return left; - } - - if (pb <= pc) - { - return above; - } - - return upperLeft; + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - above); + sum += Numerics.Abs(unchecked((sbyte)res)); } } + + /// + /// Encodes the scanline + /// + /// The scanline to encode + /// The previous scanline. + /// The filtered scanline result. + /// The bytes per pixel. + /// The sum of the total variance of the filtered row + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EncodeAverageFilter(ReadOnlySpan scanline, ReadOnlySpan previousScanline, Span result, int bytesPerPixel, out int sum) + { + DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); + DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); + + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); + sum = 0; + + // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) + resultBaseRef = 3; + + int x = 0; + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - (above >> 1)); + sum += Numerics.Abs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) + { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - Average(left, above)); + sum += Numerics.Abs(unchecked((sbyte)res)); + } + } + + /// + /// Calculates the average value of two bytes + /// + /// The left byte + /// The above byte + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Average(byte left, byte above) => (left + above) >> 1; + + /// + /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses + /// as predictor the neighboring pixel closest to the computed value. + /// + /// The left neighbor pixel. + /// The above neighbor pixel. + /// The upper left neighbor pixel. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte PaethPredictor(byte left, byte above, byte upperLeft) + { + int p = left + above - upperLeft; + int pa = Numerics.Abs(p - left); + int pb = Numerics.Abs(p - above); + int pc = Numerics.Abs(p - upperLeft); + + if (pa <= pb && pa <= pc) + { + return left; + } + + if (pb <= pc) + { + return above; + } + + return upperLeft; + } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs index 914925d407..8c0cfd5d19 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/ImageExtensionsTest.cs @@ -1,155 +1,151 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.Formats.Tga; + +public class ImageExtensionsTest { - public class ImageExtensionsTest + [Fact] + public void SaveAsTga_Path() { - [Fact] - public void SaveAsTga_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTga_Path.tga"); - - using (var image = new Image(10, 10)) - { - image.SaveAsTga(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTga_Path.tga"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTga(file); } - [Fact] - public async Task SaveAsTgaAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga"); + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTgaAsync(file); - } + [Fact] + public async Task SaveAsTgaAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTgaAsync_Path.tga"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(file); } - [Fact] - public void SaveAsTga_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga"); + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsTga(file, new TgaEncoder()); - } + [Fact] + public void SaveAsTga_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTga_Path_Encoder.tga"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTga(file, new TgaEncoder()); } - [Fact] - public async Task SaveAsTgaAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga"); + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTgaAsync(file, new TgaEncoder()); - } + [Fact] + public async Task SaveAsTgaAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTgaAsync_Path_Encoder.tga"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(file, new TgaEncoder()); } - [Fact] - public void SaveAsTga_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsTga(memoryStream); - } + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsTga_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTga(memoryStream); } - [Fact] - public async Task SaveAsTgaAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsTgaAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsTgaAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(memoryStream); } - [Fact] - public void SaveAsTga_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsTga(memoryStream, new TgaEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsTga_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTga(memoryStream, new TgaEncoder()); } - [Fact] - public async Task SaveAsTgaAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/tga", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTgaAsync(memoryStream, new TgaEncoder()); - } + [Fact] + public async Task SaveAsTgaAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsTgaAsync(memoryStream, new TgaEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tga", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tga", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 5007aa3719..b96e284b92 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tga; @@ -9,805 +8,802 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.Formats.Tga; + +[Trait("Format", "Tga")] +[ValidateDisposedMemoryAllocations] +public class TgaDecoderTests { - [Trait("Format", "Tga")] - [ValidateDisposedMemoryAllocations] - public class TgaDecoderTests - { - private static TgaDecoder TgaDecoder => new(); + private static TgaDecoder TgaDecoder => new(); - [Theory] - [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray8BitRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray8BitRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Gray16BitTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Gray16BitRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Gray16BitRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); + image.DebugSave(provider); - // Using here the reference output instead of the the reference decoder, - // because the reference decoder output seems not to be correct for 16bit gray images. - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder output seems not to be correct for 16bit gray images. + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(Bit15, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit15, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit15Rle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit15Rle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit16PalRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit16PalRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24TopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24TopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24BottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24BottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24RleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24RleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24RleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24RleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32TopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32TopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32TopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32TopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32BottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32BottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit16RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit16RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32RleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32RleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32RleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32RleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32RleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32RleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithTopLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithTopLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_Paletted_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalRleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalRleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalRleTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalRleTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteTopRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalRleBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalRleBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomLeftOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit24PalRleBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_RLE_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit24PalRleBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_RLE_WithPaletteBottomRightOrigin_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalBottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithBottomLeftOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalBottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithBottomLeftOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalBottomRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithBottomRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalBottomRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithBottomRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32PalTopRight, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithPalette_WithTopRightOrigin_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32PalTopRight, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithPalette_WithTopRightOrigin_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(NoAlphaBits16Bit, PixelTypes.Rgba32)] - [WithFile(NoAlphaBits16BitRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet_16Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(NoAlphaBits16Bit, PixelTypes.Rgba32)] + [WithFile(NoAlphaBits16BitRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet_16Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(NoAlphaBits32Bit, PixelTypes.Rgba32)] - [WithFile(NoAlphaBits32BitRle, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(NoAlphaBits32Bit, PixelTypes.Rgba32)] + [WithFile(NoAlphaBits32BitRle, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WhenAlphaBitsNotSet(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - // Using here the reference output instead of the the reference decoder, - // because the reference decoder does not ignore the alpha data here. - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + // Using here the reference output instead of the the reference decoder, + // because the reference decoder does not ignore the alpha data here. + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - // Test case for legacy format, when RLE crosses multiple lines: - // https://github.com/SixLabors/ImageSharp/pull/2172 - [Theory] - [WithFile(Github_RLE_legacy, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_LegacyFormat(TestImageProvider provider) - where TPixel : unmanaged, IPixel + // Test case for legacy format, when RLE crosses multiple lines: + // https://github.com/SixLabors/ImageSharp/pull/2172 + [Theory] + [WithFile(Github_RLE_legacy, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_LegacyFormat(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TgaDecoder)) { - using (Image image = provider.GetImage(TgaDecoder)) - { - image.DebugSave(provider); - ImageComparingUtils.CompareWithReferenceDecoder(provider, image); - } + image.DebugSave(provider); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } + } - [Theory] - [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] - public void TgaDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)] + public void TgaDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 } - }; + TargetSize = new() { Width = 150, Height = 150 } + }; - using Image image = provider.GetImage(TgaDecoder, options); + using Image image = provider.GetImage(TgaDecoder, options); - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0001F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.0001F), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); - InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); - Assert.IsType(ex.InnerException); - } + [Theory] + [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + InvalidImageContentException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); + Assert.IsType(ex.InnerException); + } - [Theory] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + [Theory] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - static void RunTest(string providerDump, string nonContiguousBuffersStr) - { - TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - using Image image = provider.GetImage(TgaDecoder); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + using Image image = provider.GetImage(TgaDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider); - } + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); } - - string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - "Disco") - .Dispose(); } + + 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 abe9e2a2d8..709a3207aa 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -1,188 +1,185 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.Formats.Tga; + +[Trait("Format", "Tga")] +public class TgaEncoderTests { - [Trait("Format", "Tga")] - public class TgaEncoderTests - { - public static readonly TheoryData BitsPerPixel = - new() - { - TgaBitsPerPixel.Pixel24, - TgaBitsPerPixel.Pixel32 - }; + public static readonly TheoryData BitsPerPixel = + new() + { + TgaBitsPerPixel.Pixel24, + TgaBitsPerPixel.Pixel32 + }; - public static readonly TheoryData TgaBitsPerPixelFiles = - new() - { - { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, - { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, - { Bit24BottomLeft, TgaBitsPerPixel.Pixel24 }, - { Bit32BottomLeft, TgaBitsPerPixel.Pixel32 }, - }; - - [Theory] - [MemberData(nameof(TgaBitsPerPixelFiles))] - public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + public static readonly TheoryData TgaBitsPerPixelFiles = + new() { - var options = new TgaEncoder(); + { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, + { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, + { Bit24BottomLeft, TgaBitsPerPixel.Pixel24 }, + { Bit32BottomLeft, TgaBitsPerPixel.Pixel32 }, + }; + + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void TgaEncoder_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder(); - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - TgaMetadata meta = output.Metadata.GetTgaMetadata(); - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } + TgaMetadata meta = output.Metadata.GetTgaMetadata(); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } } } + } - [Theory] - [MemberData(nameof(TgaBitsPerPixelFiles))] - public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + [Theory] + [MemberData(nameof(TgaBitsPerPixelFiles))] + public void TgaEncoder_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) + { + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) { - var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; - var testFile = TestFile.Create(imagePath); - using (Image input = testFile.CreateRgba32Image()) + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) { - input.Save(memStream, options); - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - TgaMetadata meta = output.Metadata.GetTgaMetadata(); - Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); - } + TgaMetadata meta = output.Metadata.GetTgaMetadata(); + Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); } } } + } - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_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 : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_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 : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] - public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) - where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - - [Theory] - [WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)] - public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvider provider, int expectedBytes) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_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 : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_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 : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)] + public void TgaEncoder_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(WhiteStripesPattern, PixelTypes.Rgba32, 2748)] + public void TgaEncoder_DoesNotAlwaysUseRunLengthPackets(TestImageProvider provider, int expectedBytes) + where TPixel : unmanaged, IPixel + { + // The test image has alternating black and white pixels, which should make using always RLE data inefficient. + using (Image image = provider.GetImage()) { - // The test image has alternating black and white pixels, which should make using always RLE data inefficient. - using (Image image = provider.GetImage()) + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + using (var memStream = new MemoryStream()) { - var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; - using (var memStream = new MemoryStream()) - { - image.Save(memStream, options); - byte[] imageBytes = memStream.ToArray(); - Assert.Equal(expectedBytes, imageBytes.Length); - } + image.Save(memStream, options); + byte[] imageBytes = memStream.ToArray(); + Assert.Equal(expectedBytes, imageBytes.Length); } } + } - // Run length encoded pixels should not exceed row boundaries. - // https://github.com/SixLabors/ImageSharp/pull/2172 - [Fact] - public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries() - { - var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; + // Run length encoded pixels should not exceed row boundaries. + // https://github.com/SixLabors/ImageSharp/pull/2172 + [Fact] + public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries() + { + var options = new TgaEncoder() { Compression = TgaCompression.RunLength }; - using (var input = new Image(30, 30)) + using (var input = new Image(30, 30)) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, options); - byte[] imageBytes = memStream.ToArray(); - Assert.Equal(138, imageBytes.Length); - } + input.Save(memStream, options); + byte[] imageBytes = memStream.ToArray(); + Assert.Equal(138, imageBytes.Length); } } + } - [Theory] - [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] - [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] - public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); - TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); - } + [Theory] + [WithFile(Bit32BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] + [WithFile(Bit24BottomLeft, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] + public void TgaEncoder_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + } - private static void TestTgaEncoderCore( - TestImageProvider provider, - TgaBitsPerPixel bitsPerPixel, - TgaCompression compression = TgaCompression.None, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel + private static void TestTgaEncoderCore( + TestImageProvider provider, + TgaBitsPerPixel bitsPerPixel, + TgaCompression compression = TgaCompression.None, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - 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()) + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) { - image.Save(memStream, encoder); - memStream.Position = 0; - using (var encodedImage = (Image)Image.Load(memStream)) - { - ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); - } + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs index 12e2926bd7..a5997cb016 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -1,54 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tga; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tga; -namespace SixLabors.ImageSharp.Tests.Formats.Tga +[Trait("Format", "Tga")] +public class TgaFileHeaderTests { - [Trait("Format", "Tga")] - public class TgaFileHeaderTests + [Theory] + [InlineData(new byte[] { 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid tga image type. + [InlineData(new byte[] { 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid colormap type. + [InlineData(new byte[] { 0, 0, 1, 5, 5, 5, 5, 5, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. + [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 0, 8, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. + [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. + [InlineData(new byte[] { 0, 0, 1, 0, 0, 6, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. + [InlineData(new byte[] { 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. + [InlineData(new byte[] { 0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10, 0, 0, 0, 20, 102, 116 })] // jp2 image header + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 0, 32, 8 })] // invalid width + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 32, 8 })] // invalid height + public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatException(byte[] data) { - [Theory] - [InlineData(new byte[] { 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid tga image type. - [InlineData(new byte[] { 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // invalid colormap type. - [InlineData(new byte[] { 0, 0, 1, 5, 5, 5, 5, 5, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 0, 8, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 0, 6, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 })] // valid colormap type (0), but colomap spec bytes should all be zero. - [InlineData(new byte[] { 0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10, 0, 0, 0, 20, 102, 116 })] // jp2 image header - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 0, 32, 8 })] // invalid width - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 32, 8 })] // invalid height - public void ImageLoad_WithNoValidTgaHeaderBytes_Throws_UnknownImageFormatException(byte[] data) - { - using var stream = new MemoryStream(data); + using var stream = new MemoryStream(data); - Assert.Throws(() => + Assert.Throws(() => + { + using (Image.Load(DecoderOptions.Default, stream, out IImageFormat _)) { - using (Image.Load(DecoderOptions.Default, stream, out IImageFormat _)) - { - } - }); - } + } + }); + } - [Theory] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 }, 250, 195, TgaBitsPerPixel.Pixel32)] - [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Pixel8)] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Pixel16)] - [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)] - public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel) - { - using var stream = new MemoryStream(data); + [Theory] + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 195, 0, 32, 8 }, 250, 195, TgaBitsPerPixel.Pixel32)] + [InlineData(new byte[] { 26, 1, 9, 0, 0, 0, 1, 16, 0, 0, 0, 0, 128, 0, 128, 0, 8, 0 }, 128, 128, TgaBitsPerPixel.Pixel8)] + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 220, 0, 16, 0 }, 220, 220, TgaBitsPerPixel.Pixel16)] + [InlineData(new byte[] { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 0, 124, 0, 24, 32 }, 124, 124, TgaBitsPerPixel.Pixel24)] + public void Identify_WithValidData_Works(byte[] data, int width, int height, TgaBitsPerPixel bitsPerPixel) + { + using var stream = new MemoryStream(data); - IImageInfo info = Image.Identify(stream); - TgaMetadata tgaData = info.Metadata.GetTgaMetadata(); - Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel); - Assert.Equal(width, info.Width); - Assert.Equal(height, info.Height); - } + IImageInfo info = Image.Identify(stream); + TgaMetadata tgaData = info.Metadata.GetTgaMetadata(); + Assert.Equal(bitsPerPixel, tgaData.BitsPerPixel); + Assert.Equal(width, info.Width); + Assert.Equal(height, info.Height); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index b3d8e7eff0..8bc3ceeb45 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -2,129 +2,124 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -using System; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Collection("RunSerial")] +[Trait("Format", "Tiff")] +public class BigTiffDecoderTests : TiffDecoderBaseTester { - [Collection("RunSerial")] - [Trait("Format", "Tiff")] - public class BigTiffDecoderTests : TiffDecoderBaseTester + [Theory] + [WithFile(BigTIFF, PixelTypes.Rgba32)] + [WithFile(BigTIFFLong, PixelTypes.Rgba32)] + [WithFile(BigTIFFLong8, PixelTypes.Rgba32)] + [WithFile(BigTIFFMotorola, PixelTypes.Rgba32)] + [WithFile(BigTIFFMotorolaLongStrips, PixelTypes.Rgba32)] + [WithFile(BigTIFFSubIFD4, PixelTypes.Rgba32)] + [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] + [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] + [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] + [WithFile(MinIsBlack, PixelTypes.Rgba32)] + [WithFile(MinIsWhite, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(BigTIFFLong8Tiles, PixelTypes.Rgba32)] + public void ThrowsNotSupported(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + + [Theory] + [WithFile(Damaged_MinIsWhite_RLE, PixelTypes.Rgba32)] + [WithFile(Damaged_MinIsBlack_RLE, PixelTypes.Rgba32)] + public void DamagedFiles(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithFile(BigTIFF, PixelTypes.Rgba32)] - [WithFile(BigTIFFLong, PixelTypes.Rgba32)] - [WithFile(BigTIFFLong8, PixelTypes.Rgba32)] - [WithFile(BigTIFFMotorola, PixelTypes.Rgba32)] - [WithFile(BigTIFFMotorolaLongStrips, PixelTypes.Rgba32)] - [WithFile(BigTIFFSubIFD4, PixelTypes.Rgba32)] - [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] - [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] - [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] - [WithFile(MinIsBlack, PixelTypes.Rgba32)] - [WithFile(MinIsWhite, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(BigTIFFLong8Tiles, PixelTypes.Rgba32)] - public void ThrowsNotSupported(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); - - [Theory] - [WithFile(Damaged_MinIsWhite_RLE, PixelTypes.Rgba32)] - [WithFile(Damaged_MinIsBlack_RLE, PixelTypes.Rgba32)] - public void DamagedFiles(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.Throws(() => TestTiffDecoder(provider)); + Assert.Throws(() => TestTiffDecoder(provider)); - using Image image = provider.GetImage(TiffDecoder); - ExifProfile exif = image.Frames.RootFrame.Metadata.ExifProfile; + using Image image = provider.GetImage(TiffDecoder); + ExifProfile exif = image.Frames.RootFrame.Metadata.ExifProfile; - // PhotometricInterpretation is required tag: https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html - Assert.Null(exif.GetValueInternal(ExifTag.PhotometricInterpretation)); - } + // PhotometricInterpretation is required tag: https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html + Assert.Null(exif.GetValueInternal(ExifTag.PhotometricInterpretation)); + } - [Theory] - [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFLong, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFLong8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFMotorola, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsWhite, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) + [Theory] + [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFLong, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFLong8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFMotorola, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsWhite, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo info = Image.Identify(stream); - - Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); - Assert.Equal(expectedWidth, info.Width); - Assert.Equal(expectedHeight, info.Height); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); - Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); - Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); - - TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffmeta); - Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType); - } + IImageInfo info = Image.Identify(stream); + + Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); + Assert.Equal(expectedWidth, info.Width); + Assert.Equal(expectedHeight, info.Height); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); + Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); + Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); + + TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffmeta); + Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType); } + } - [Theory] - [InlineData(BigTIFFLong, ImageSharp.ByteOrder.LittleEndian)] - [InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)] - public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + [Theory] + [InlineData(BigTIFFLong, ImageSharp.ByteOrder.LittleEndian)] + [InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)] + public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo info = Image.Identify(stream); + IImageInfo info = Image.Identify(stream); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); - stream.Seek(0, SeekOrigin.Begin); + stream.Seek(0, SeekOrigin.Begin); - using var img = Image.Load(stream); - Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); - } + using var img = Image.Load(stream); + Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); } + } - [Theory] - [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] - public void TiffDecoder_SubIfd8(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); + [Theory] + [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] + public void TiffDecoder_SubIfd8(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); - ExifProfile meta = image.Frames.RootFrame.Metadata.ExifProfile; + ExifProfile meta = image.Frames.RootFrame.Metadata.ExifProfile; - Assert.Equal(0, meta.InvalidTags.Count); - Assert.Equal(6, meta.Values.Count); - Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageWidth).Value); - Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageLength).Value); - Assert.Equal(64, (int)meta.GetValue(ExifTag.RowsPerStrip).Value); + Assert.Equal(0, meta.InvalidTags.Count); + Assert.Equal(6, meta.Values.Count); + Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageWidth).Value); + Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageLength).Value); + Assert.Equal(64, (int)meta.GetValue(ExifTag.RowsPerStrip).Value); - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); - Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); - } + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index 9ce45d3f36..73ce216d8d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -1,260 +1,255 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Writers; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class BigTiffMetadataTests { - [Trait("Format", "Tiff")] - public class BigTiffMetadataTests + [Fact] + public void ExifLong8() { - [Fact] - public void ExifLong8() - { - var long8 = new ExifLong8(ExifTagValue.StripByteCounts); + var long8 = new ExifLong8(ExifTagValue.StripByteCounts); - Assert.True(long8.TrySetValue(0)); - Assert.Equal(0UL, long8.GetValue()); + Assert.True(long8.TrySetValue(0)); + Assert.Equal(0UL, long8.GetValue()); - Assert.True(long8.TrySetValue(100u)); - Assert.Equal(100UL, long8.GetValue()); + Assert.True(long8.TrySetValue(100u)); + Assert.Equal(100UL, long8.GetValue()); - Assert.True(long8.TrySetValue(ulong.MaxValue)); - Assert.Equal(ulong.MaxValue, long8.GetValue()); + Assert.True(long8.TrySetValue(ulong.MaxValue)); + Assert.Equal(ulong.MaxValue, long8.GetValue()); - Assert.False(long8.TrySetValue(-65)); - Assert.Equal(ulong.MaxValue, long8.GetValue()); - } + Assert.False(long8.TrySetValue(-65)); + Assert.Equal(ulong.MaxValue, long8.GetValue()); + } - [Fact] - public void ExifSignedLong8() - { - var long8 = new ExifSignedLong8(ExifTagValue.ImageID); + [Fact] + public void ExifSignedLong8() + { + var long8 = new ExifSignedLong8(ExifTagValue.ImageID); - Assert.False(long8.TrySetValue(0)); + Assert.False(long8.TrySetValue(0)); - Assert.True(long8.TrySetValue(0L)); - Assert.Equal(0L, long8.GetValue()); + Assert.True(long8.TrySetValue(0L)); + Assert.Equal(0L, long8.GetValue()); - Assert.True(long8.TrySetValue(-100L)); - Assert.Equal(-100L, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); + Assert.True(long8.TrySetValue(-100L)); + Assert.Equal(-100L, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - Assert.True(long8.TrySetValue(long.MaxValue)); - Assert.Equal(long.MaxValue, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - } + Assert.True(long8.TrySetValue(long.MaxValue)); + Assert.Equal(long.MaxValue, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); + } - [Fact] - public void ExifLong8Array() - { - var long8 = new ExifLong8Array(ExifTagValue.StripOffsets); + [Fact] + public void ExifLong8Array() + { + var long8 = new ExifLong8Array(ExifTagValue.StripOffsets); - Assert.True(long8.TrySetValue((short)-123)); - Assert.Equal(new[] { 0UL }, long8.GetValue()); + Assert.True(long8.TrySetValue((short)-123)); + Assert.Equal(new[] { 0UL }, long8.GetValue()); - Assert.True(long8.TrySetValue((ushort)123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue((ushort)123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue((short)123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue((short)123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(123)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(123u)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123u)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(123L)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123L)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(123UL)); - Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123UL)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(new short[] { -1, 2, -3, 4 })); - Assert.Equal(new ulong[] { 0, 2UL, 0, 4UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(new short[] { -1, 2, -3, 4 })); + Assert.Equal(new ulong[] { 0, 2UL, 0, 4UL }, long8.GetValue()); - Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4 })); - Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL }, long8.GetValue()); - Assert.Equal(ExifDataType.Long, long8.DataType); + Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4 })); + Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL }, long8.GetValue()); + Assert.Equal(ExifDataType.Long, long8.DataType); - Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4, long.MaxValue })); - Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL, (ulong)long.MaxValue }, long8.GetValue()); - Assert.Equal(ExifDataType.Long8, long8.DataType); - } + Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4, long.MaxValue })); + Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL, (ulong)long.MaxValue }, long8.GetValue()); + Assert.Equal(ExifDataType.Long8, long8.DataType); + } - [Fact] - public void ExifSignedLong8Array() - { - var long8 = new ExifSignedLong8Array(ExifTagValue.StripOffsets); + [Fact] + public void ExifSignedLong8Array() + { + var long8 = new ExifSignedLong8Array(ExifTagValue.StripOffsets); - Assert.True(long8.TrySetValue(new[] { 0L })); - Assert.Equal(new[] { 0L }, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); + Assert.True(long8.TrySetValue(new[] { 0L })); + Assert.Equal(new[] { 0L }, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - Assert.True(long8.TrySetValue(new[] { -1L, 2L, long.MinValue, 4L })); - Assert.Equal(new[] { -1L, 2L, long.MinValue, 4L }, long8.GetValue()); - Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - } + Assert.True(long8.TrySetValue(new[] { -1L, 2L, long.MinValue, 4L })); + Assert.Equal(new[] { -1L, 2L, long.MinValue, 4L }, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); + } + + [Fact] + public void NotCoveredTags() + { + using var input = new Image(10, 10); - [Fact] - public void NotCoveredTags() + var testTags = new Dictionary { - using var input = new Image(10, 10); + { new ExifTag((ExifTagValue)0xdd01), (ExifDataType.SingleFloat, new float[] { 1.2f, 2.3f, 4.5f }) }, + { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.SingleFloat, 2.345f) }, + { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, + { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.DoubleFloat, 8.903) }, + { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, (sbyte)-3) }, + { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, + { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd09), (ExifDataType.SignedShort, (short)-1234) }, + { new ExifTag((ExifTagValue)0xdd10), (ExifDataType.Short, (ushort)1234) }, + }; + + // arrange + var values = new List(); + foreach (KeyValuePair tag in testTags) + { + ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); - var testTags = new Dictionary - { - { new ExifTag((ExifTagValue)0xdd01), (ExifDataType.SingleFloat, new float[] { 1.2f, 2.3f, 4.5f }) }, - { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.SingleFloat, 2.345f) }, - { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, - { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.DoubleFloat, 8.903) }, - { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, (sbyte)-3) }, - { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, - { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd09), (ExifDataType.SignedShort, (short)-1234) }, - { new ExifTag((ExifTagValue)0xdd10), (ExifDataType.Short, (ushort)1234) }, - }; - - // arrange - var values = new List(); - foreach (KeyValuePair tag in testTags) - { - ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); + Assert.True(newExifValue.TrySetValue(tag.Value.Value)); + values.Add(newExifValue); + } - Assert.True(newExifValue.TrySetValue(tag.Value.Value)); - values.Add(newExifValue); - } + input.Frames.RootFrame.Metadata.ExifProfile = new ExifProfile(values, Array.Empty()); - input.Frames.RootFrame.Metadata.ExifProfile = new ExifProfile(values, Array.Empty()); + // act + var encoder = new TiffEncoder(); + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); - // act - var encoder = new TiffEncoder(); - using var memStream = new MemoryStream(); - input.Save(memStream, encoder); + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageFrameMetadata loadedFrameMetadata = output.Frames.RootFrame.Metadata; + foreach (KeyValuePair tag in testTags) + { + IExifValue exifValue = loadedFrameMetadata.ExifProfile.GetValueInternal(tag.Key); + Assert.NotNull(exifValue); + object value = exifValue.GetValue(); - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageFrameMetadata loadedFrameMetadata = output.Frames.RootFrame.Metadata; - foreach (KeyValuePair tag in testTags) + Assert.Equal(tag.Value.DataType, exifValue.DataType); { - IExifValue exifValue = loadedFrameMetadata.ExifProfile.GetValueInternal(tag.Key); - Assert.NotNull(exifValue); - object value = exifValue.GetValue(); - - Assert.Equal(tag.Value.DataType, exifValue.DataType); - { - Assert.Equal(value, tag.Value.Value); - } + Assert.Equal(value, tag.Value.Value); } } + } - [Fact] - public void NotCoveredTags64bit() + [Fact] + public void NotCoveredTags64bit() + { + var testTags = new Dictionary { - var testTags = new Dictionary - { - { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, - { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, - //// WriteIfdTags64Bit: arrays aren't support (by our code) - ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, - ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, - }; - - var values = new List(); - foreach (KeyValuePair tag in testTags) - { - ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); + { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, + { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, + //// WriteIfdTags64Bit: arrays aren't support (by our code) + ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, + ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, + }; + + var values = new List(); + foreach (KeyValuePair tag in testTags) + { + ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); - Assert.True(newExifValue.TrySetValue(tag.Value.Value)); - values.Add(newExifValue); - } + Assert.True(newExifValue.TrySetValue(tag.Value.Value)); + values.Add(newExifValue); + } - // act - byte[] inputBytes = WriteIfdTags64Bit(values); - Configuration config = Configuration.Default; - var reader = new EntryReader( - new MemoryStream(inputBytes), - BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian, - config.MemoryAllocator); + // act + byte[] inputBytes = WriteIfdTags64Bit(values); + Configuration config = Configuration.Default; + var reader = new EntryReader( + new MemoryStream(inputBytes), + BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian, + config.MemoryAllocator); - reader.ReadTags(true, 0); + reader.ReadTags(true, 0); - List outputTags = reader.Values; + List outputTags = reader.Values; - // assert - foreach (KeyValuePair tag in testTags) - { - IExifValue exifValue = outputTags.Find(t => t.Tag == tag.Key); - Assert.NotNull(exifValue); - object value = exifValue.GetValue(); + // assert + foreach (KeyValuePair tag in testTags) + { + IExifValue exifValue = outputTags.Find(t => t.Tag == tag.Key); + Assert.NotNull(exifValue); + object value = exifValue.GetValue(); - Assert.Equal(tag.Value.DataType, exifValue.DataType); - { - Assert.Equal(value, tag.Value.Value); - } + Assert.Equal(tag.Value.DataType, exifValue.DataType); + { + Assert.Equal(value, tag.Value.Value); } } + } + + private static byte[] WriteIfdTags64Bit(List values) + { + byte[] buffer = new byte[8]; + var ms = new MemoryStream(); + var writer = new TiffStreamWriter(ms); + WriteLong8(writer, buffer, (ulong)values.Count); - private static byte[] WriteIfdTags64Bit(List values) + foreach (IExifValue entry in values) { - byte[] buffer = new byte[8]; - var ms = new MemoryStream(); - var writer = new TiffStreamWriter(ms); - WriteLong8(writer, buffer, (ulong)values.Count); + writer.Write((ushort)entry.Tag); + writer.Write((ushort)entry.DataType); + WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry)); - foreach (IExifValue entry in values) - { - writer.Write((ushort)entry.Tag); - writer.Write((ushort)entry.DataType); - WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry)); + uint length = ExifWriter.GetLength(entry); - uint length = ExifWriter.GetLength(entry); + Assert.True(length <= 8); - Assert.True(length <= 8); + if (length <= 8) + { + int sz = ExifWriter.WriteValue(entry, buffer, 0); + DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); - if (length <= 8) + // write padded + writer.BaseStream.Write(buffer.AsSpan(0, sz)); + int d = sz % 8; + if (d != 0) { - int sz = ExifWriter.WriteValue(entry, buffer, 0); - DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); - - // write padded - writer.BaseStream.Write(buffer.AsSpan(0, sz)); - int d = sz % 8; - if (d != 0) - { - writer.BaseStream.Write(new byte[d]); - } + writer.BaseStream.Write(new byte[d]); } } + } - WriteLong8(writer, buffer, 0); + WriteLong8(writer, buffer, 0); - return ms.ToArray(); - } + return ms.ToArray(); + } - private static void WriteLong8(TiffStreamWriter writer, byte[] buffer, ulong value) + private static void WriteLong8(TiffStreamWriter writer, byte[] buffer, ulong value) + { + if (TiffStreamWriter.IsLittleEndian) { - if (TiffStreamWriter.IsLittleEndian) - { - BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); - } - else - { - BinaryPrimitives.WriteUInt64BigEndian(buffer, value); - } - - writer.BaseStream.Write(buffer); + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); } + else + { + BinaryPrimitives.WriteUInt64BigEndian(buffer, value); + } + + writer.BaseStream.Write(buffer); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index 12f20cd300..1b12adac23 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -1,50 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +[Trait("Format", "Tiff")] +public class DeflateTiffCompressionTests { - [Trait("Format", "Tiff")] - public class DeflateTiffCompressionTests + [Theory] + [InlineData(new byte[] { })] + [InlineData(new byte[] { 42 })] // One byte + [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes + [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence + public void Compress_Decompress_Roundtrip_Works(byte[] data) { - [Theory] - [InlineData(new byte[] { })] - [InlineData(new byte[] { 42 })] // One byte - [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes - [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - public void Compress_Decompress_Roundtrip_Works(byte[] data) - { - using BufferedReadStream stream = CreateCompressedStream(data); - byte[] buffer = new byte[data.Length]; - - using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); + using BufferedReadStream stream = CreateCompressedStream(data); + byte[] buffer = new byte[data.Length]; - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); + using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - Assert.Equal(data, buffer); - } + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); - private static BufferedReadStream CreateCompressedStream(byte[] data) - { - Stream compressedStream = new MemoryStream(); + Assert.Equal(data, buffer); + } - using (Stream uncompressedStream = new MemoryStream(data), - deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6)) - { - uncompressedStream.CopyTo(deflateStream); - } + private static BufferedReadStream CreateCompressedStream(byte[] data) + { + Stream compressedStream = new MemoryStream(); - compressedStream.Seek(0, SeekOrigin.Begin); - return new BufferedReadStream(Configuration.Default, compressedStream); + using (Stream uncompressedStream = new MemoryStream(data), + deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6)) + { + uncompressedStream.CopyTo(deflateStream); } + + compressedStream.Seek(0, SeekOrigin.Begin); + return new BufferedReadStream(Configuration.Default, compressedStream); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs index 197beade27..635a3a33e4 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs @@ -1,62 +1,59 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; + +[Trait("Format", "Tiff")] +public class LzwTiffCompressionTests { - [Trait("Format", "Tiff")] - public class LzwTiffCompressionTests + [Theory] + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 }, new byte[] { 128, 0, 64, 66, 168, 36, 22, 12, 3, 2, 64, 64, 0, 0 })] // Repeated bytes + + public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) { - [Theory] - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 }, new byte[] { 128, 0, 64, 66, 168, 36, 22, 12, 3, 2, 64, 64, 0, 0 })] // Repeated bytes + byte[] compressedData = new byte[expectedCompressedData.Length]; + Stream streamData = CreateCompressedStream(inputData); + streamData.Read(compressedData, 0, expectedCompressedData.Length); - public void Compress_Works(byte[] inputData, byte[] expectedCompressedData) - { - byte[] compressedData = new byte[expectedCompressedData.Length]; - Stream streamData = CreateCompressedStream(inputData); - streamData.Read(compressedData, 0, expectedCompressedData.Length); + Assert.Equal(expectedCompressedData, compressedData); + } - Assert.Equal(expectedCompressedData, compressedData); - } + [Theory] + [InlineData(new byte[] { })] + [InlineData(new byte[] { 42 })] // One byte + [InlineData(new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 })] + [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes + [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes + [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence - [Theory] - [InlineData(new byte[] { })] - [InlineData(new byte[] { 42 })] // One byte - [InlineData(new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 })] - [InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes - [InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes - [InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence + public void Compress_Decompress_Roundtrip_Works(byte[] data) + { + using BufferedReadStream stream = CreateCompressedStream(data); + byte[] buffer = new byte[data.Length]; - public void Compress_Decompress_Roundtrip_Works(byte[] data) - { - using BufferedReadStream stream = CreateCompressedStream(data); - byte[] buffer = new byte[data.Length]; + using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); + decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); - using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); - decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer, default); + Assert.Equal(data, buffer); + } - Assert.Equal(data, buffer); - } + private static BufferedReadStream CreateCompressedStream(byte[] inputData) + { + Stream compressedStream = new MemoryStream(); - private static BufferedReadStream CreateCompressedStream(byte[] inputData) + using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator)) { - Stream compressedStream = new MemoryStream(); - - using (var encoder = new TiffLzwEncoder(Configuration.Default.MemoryAllocator)) - { - encoder.Encode(inputData, compressedStream); - } + encoder.Encode(inputData, compressedStream); + } - compressedStream.Seek(0, SeekOrigin.Begin); + compressedStream.Seek(0, SeekOrigin.Begin); - return new BufferedReadStream(Configuration.Default, compressedStream); - } + return new BufferedReadStream(Configuration.Default, compressedStream); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs index ca457d15e2..e79ed7ce77 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs @@ -1,30 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.IO; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +[Trait("Format", "Tiff")] +public class NoneTiffCompressionTests { - [Trait("Format", "Tiff")] - public class NoneTiffCompressionTests + [Theory] + [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })] + [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] + public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) { - [Theory] - [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })] - [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] - public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) - { - using var memoryStream = new MemoryStream(inputData); - using var stream = new BufferedReadStream(Configuration.Default, memoryStream); - byte[] buffer = new byte[expectedResult.Length]; + using var memoryStream = new MemoryStream(inputData); + using var stream = new BufferedReadStream(Configuration.Default, memoryStream); + byte[] buffer = new byte[expectedResult.Length]; - using var decompressor = new NoneTiffCompression(default, default, default); - decompressor.Decompress(stream, 0, byteCount, 1, buffer, default); + using var decompressor = new NoneTiffCompression(default, default, default); + decompressor.Decompress(stream, 0, byteCount, 1, buffer, default); - Assert.Equal(expectedResult, buffer); - } + Assert.Equal(expectedResult, buffer); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index 4ed4330ec5..c91ab0e7fe 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -1,54 +1,49 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression +[Trait("Format", "Tiff")] +public class PackBitsTiffCompressionTests { - [Trait("Format", "Tiff")] - public class PackBitsTiffCompressionTests + [Theory] + [InlineData(new byte[] { }, new byte[] { })] + [InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte + [InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes + [InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes + [InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes + [InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte + [InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes + [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample + public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) { - [Theory] - [InlineData(new byte[] { }, new byte[] { })] - [InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte - [InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes - [InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes - [InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes - [InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte - [InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes - [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample - public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) - { - using var memoryStream = new MemoryStream(inputData); - using var stream = new BufferedReadStream(Configuration.Default, memoryStream); - byte[] buffer = new byte[expectedResult.Length]; - - using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); - decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer, default); - - Assert.Equal(expectedResult, buffer); - } - - [Theory] - [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA })] // Apple PackBits sample - public void Compress_Works(byte[] inputData, byte[] expectedResult) - { - // arrange - Span input = inputData.AsSpan(); - byte[] compressed = new byte[expectedResult.Length]; - - // act - PackBitsWriter.PackBits(input, compressed); - - // assert - Assert.Equal(expectedResult, compressed); - } + using var memoryStream = new MemoryStream(inputData); + using var stream = new BufferedReadStream(Configuration.Default, memoryStream); + byte[] buffer = new byte[expectedResult.Length]; + + using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); + decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer, default); + + Assert.Equal(expectedResult, buffer); + } + + [Theory] + [InlineData(new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA })] // Apple PackBits sample + public void Compress_Works(byte[] inputData, byte[] expectedResult) + { + // arrange + Span input = inputData.AsSpan(); + byte[] compressed = new byte[expectedResult.Length]; + + // act + PackBitsWriter.PackBits(input, compressed); + + // assert + Assert.Equal(expectedResult, compressed); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs index 69c1d0a491..23806a18db 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs @@ -1,156 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class ImageExtensionsTest { - [Trait("Format", "Tiff")] - public class ImageExtensionsTest + [Fact] + public void SaveAsTiff_Path() { - [Fact] - public void SaveAsTiff_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); - - using (var image = new Image(10, 10)) - { - image.SaveAsTiff(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTiff_Path.tiff"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(file); } - [Fact] - public async Task SaveAsTiffAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); - string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTiffAsync(file); - } + [Fact] + public async Task SaveAsTiffAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(file); } - [Fact] - public void SaveAsTiff_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsTiff(file, new TiffEncoder()); - } + [Fact] + public void SaveAsTiff_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(file, new TiffEncoder()); } - [Fact] - public async Task SaveAsTiffAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTiffAsync(file, new TiffEncoder()); - } + [Fact] + public async Task SaveAsTiffAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(file, new TiffEncoder()); } - [Fact] - public void SaveAsTiff_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsTiff(memoryStream); - } + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsTiff_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(memoryStream); } - [Fact] - public async Task SaveAsTiffAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsTiffAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsTiffAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(memoryStream); } - [Fact] - public void SaveAsTiff_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsTiff(memoryStream, new TiffEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsTiff_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsTiff(memoryStream, new TiffEncoder()); } - [Fact] - public async Task SaveAsTiffAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/tiff", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); - } + [Fact] + public async Task SaveAsTiffAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsTiffAsync(memoryStream, new TiffEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/tiff", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/tiff", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs index c9854c0227..0fa7e01e8e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs @@ -1,179 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +[Trait("Format", "Tiff")] +public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase + private static readonly Rgba32 Gray000 = new(0, 0, 0, 255); + private static readonly Rgba32 Gray128 = new(128, 128, 128, 255); + private static readonly Rgba32 Gray255 = new(255, 255, 255, 255); + private static readonly Rgba32 Gray0 = new(0, 0, 0, 255); + private static readonly Rgba32 Gray8 = new(136, 136, 136, 255); + private static readonly Rgba32 GrayF = new(255, 255, 255, 255); + private static readonly Rgba32 Bit0 = new(0, 0, 0, 255); + private static readonly Rgba32 Bit1 = new(255, 255, 255, 255); + + private static readonly byte[] BilevelBytes4X4 = { - private static readonly Rgba32 Gray000 = new(0, 0, 0, 255); - private static readonly Rgba32 Gray128 = new(128, 128, 128, 255); - private static readonly Rgba32 Gray255 = new(255, 255, 255, 255); - private static readonly Rgba32 Gray0 = new(0, 0, 0, 255); - private static readonly Rgba32 Gray8 = new(136, 136, 136, 255); - private static readonly Rgba32 GrayF = new(255, 255, 255, 255); - private static readonly Rgba32 Bit0 = new(0, 0, 0, 255); - private static readonly Rgba32 Bit1 = new(255, 255, 255, 255); - - private static readonly byte[] BilevelBytes4X4 = - { - 0b01010000, - 0b11110000, - 0b01110000, - 0b10010000 - }; + 0b01010000, + 0b11110000, + 0b01110000, + 0b10010000 + }; - private static readonly Rgba32[][] BilevelResult4X4 = - { - new[] { Bit0, Bit1, Bit0, Bit1 }, - new[] { Bit1, Bit1, Bit1, Bit1 }, - new[] { Bit0, Bit1, Bit1, Bit1 }, - new[] { Bit1, Bit0, Bit0, Bit1 } - }; + private static readonly Rgba32[][] BilevelResult4X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit1 }, + new[] { Bit1, Bit0, Bit0, Bit1 } + }; - private static readonly byte[] BilevelBytes12X4 = - { - 0b01010101, 0b01010000, - 0b11111111, 0b11111111, - 0b01101001, 0b10100000, - 0b10010000, 0b01100000 - }; + private static readonly byte[] BilevelBytes12X4 = + { + 0b01010101, 0b01010000, + 0b11111111, 0b11111111, + 0b01101001, 0b10100000, + 0b10010000, 0b01100000 + }; - private static readonly Rgba32[][] BilevelResult12X4 = - { - new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, - new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, - new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, - new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } - }; + private static readonly Rgba32[][] BilevelResult12X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, + new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } + }; - private static readonly byte[] Grayscale4Bytes4X4 = - { - 0x8F, 0x0F, - 0xFF, 0xFF, - 0x08, 0x8F, - 0xF0, 0xF8 - }; + private static readonly byte[] Grayscale4Bytes4X4 = + { + 0x8F, 0x0F, + 0xFF, 0xFF, + 0x08, 0x8F, + 0xF0, 0xF8 + }; - private static readonly Rgba32[][] Grayscale4Result4X4 = - { - new[] { Gray8, GrayF, Gray0, GrayF }, - new[] { GrayF, GrayF, GrayF, GrayF }, - new[] { Gray0, Gray8, Gray8, GrayF }, - new[] { GrayF, Gray0, GrayF, Gray8 } - }; + private static readonly Rgba32[][] Grayscale4Result4X4 = + { + new[] { Gray8, GrayF, Gray0, GrayF }, + new[] { GrayF, GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8, GrayF }, + new[] { GrayF, Gray0, GrayF, Gray8 } + }; - private static readonly byte[] Grayscale4Bytes3X4 = - { - 0x8F, 0x00, - 0xFF, 0xF0, - 0x08, 0x80, - 0xF0, 0xF0 - }; + private static readonly byte[] Grayscale4Bytes3X4 = + { + 0x8F, 0x00, + 0xFF, 0xF0, + 0x08, 0x80, + 0xF0, 0xF0 + }; - private static readonly Rgba32[][] Grayscale4Result3X4 = - { - new[] { Gray8, GrayF, Gray0 }, - new[] { GrayF, GrayF, GrayF }, - new[] { Gray0, Gray8, Gray8 }, - new[] { GrayF, Gray0, GrayF } - }; + private static readonly Rgba32[][] Grayscale4Result3X4 = + { + new[] { Gray8, GrayF, Gray0 }, + new[] { GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8 }, + new[] { GrayF, Gray0, GrayF } + }; - private static readonly byte[] Grayscale8Bytes4X4 = - { - 128, 255, 000, 255, - 255, 255, 255, 255, - 000, 128, 128, 255, - 255, 000, 255, 128 - }; + private static readonly byte[] Grayscale8Bytes4X4 = + { + 128, 255, 000, 255, + 255, 255, 255, 255, + 000, 128, 128, 255, + 255, 000, 255, 128 + }; - private static readonly Rgba32[][] Grayscale8Result4X4 = - { - new[] { Gray128, Gray255, Gray000, Gray255 }, - new[] { Gray255, Gray255, Gray255, Gray255 }, - new[] { Gray000, Gray128, Gray128, Gray255 }, - new[] { Gray255, Gray000, Gray255, Gray128 } - }; + private static readonly Rgba32[][] Grayscale8Result4X4 = + { + new[] { Gray128, Gray255, Gray000, Gray255 }, + new[] { Gray255, Gray255, Gray255, Gray255 }, + new[] { Gray000, Gray128, Gray128, Gray255 }, + new[] { Gray255, Gray000, Gray255, Gray128 } + }; - public static IEnumerable BilevelData + public static IEnumerable BilevelData + { + get { - get - { - yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; - yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; - - yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; - yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; - } + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; + + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; } + } - public static IEnumerable Grayscale4_Data + public static IEnumerable Grayscale4_Data + { + get { - get - { - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; - } + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; } + } - public static IEnumerable Grayscale8_Data + public static IEnumerable Grayscale8_Data + { + get { - get - { - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; } - - [Theory] - [MemberData(nameof(BilevelData))] - [MemberData(nameof(Grayscale4_Data))] - [MemberData(nameof(Grayscale8_Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => new BlackIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(BilevelData))] - public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => new BlackIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(Grayscale4_Data))] - public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => new BlackIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height)); - - [Theory] - [MemberData(nameof(Grayscale8_Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => new BlackIsZero8TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height)); } + + [Theory] + [MemberData(nameof(BilevelData))] + [MemberData(nameof(Grayscale4_Data))] + [MemberData(nameof(Grayscale8_Data))] + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode( + expectedResult, + pixels => new BlackIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height)); + + [Theory] + [MemberData(nameof(BilevelData))] + public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode(expectedResult, pixels => new BlackIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height)); + + [Theory] + [MemberData(nameof(Grayscale4_Data))] + public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode( + expectedResult, + pixels => new BlackIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height)); + + [Theory] + [MemberData(nameof(Grayscale8_Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode(expectedResult, pixels => new BlackIsZero8TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height)); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs index 11fd712ed5..c809c6c7f9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -1,137 +1,133 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +[Trait("Format", "Tiff")] +public class PaletteTiffColorTests : PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public class PaletteTiffColorTests : PhotometricInterpretationTestBase - { - public static uint[][] Palette4ColorPalette => GeneratePalette(16); + public static uint[][] Palette4ColorPalette => GeneratePalette(16); - public static ushort[] Palette4ColorMap => GenerateColorMap(Palette4ColorPalette); + public static ushort[] Palette4ColorMap => GenerateColorMap(Palette4ColorPalette); - private static readonly byte[] Palette4Bytes4X4 = - { - 0x01, 0x23, 0x4A, 0xD2, 0x12, 0x34, 0xAB, 0xEF - }; + private static readonly byte[] Palette4Bytes4X4 = + { + 0x01, 0x23, 0x4A, 0xD2, 0x12, 0x34, 0xAB, 0xEF + }; - private static readonly Rgba32[][] Palette4Result4X4 = GenerateResult( - Palette4ColorPalette, - new[] { new[] { 0x00, 0x01, 0x02, 0x03 }, new[] { 0x04, 0x0A, 0x0D, 0x02 }, new[] { 0x01, 0x02, 0x03, 0x04 }, new[] { 0x0A, 0x0B, 0x0E, 0x0F } }); + private static readonly Rgba32[][] Palette4Result4X4 = GenerateResult( + Palette4ColorPalette, + new[] { new[] { 0x00, 0x01, 0x02, 0x03 }, new[] { 0x04, 0x0A, 0x0D, 0x02 }, new[] { 0x01, 0x02, 0x03, 0x04 }, new[] { 0x0A, 0x0B, 0x0E, 0x0F } }); - private static readonly byte[] Palette4Bytes3X4 = - { - 0x01, 0x20, - 0x4A, 0xD0, - 0x12, 0x30, - 0xAB, 0xE0 - }; + private static readonly byte[] Palette4Bytes3X4 = + { + 0x01, 0x20, + 0x4A, 0xD0, + 0x12, 0x30, + 0xAB, 0xE0 + }; - private static readonly Rgba32[][] Palette4Result3X4 = GenerateResult(Palette4ColorPalette, new[] { new[] { 0x00, 0x01, 0x02 }, new[] { 0x04, 0x0A, 0x0D }, new[] { 0x01, 0x02, 0x03 }, new[] { 0x0A, 0x0B, 0x0E } }); + private static readonly Rgba32[][] Palette4Result3X4 = GenerateResult(Palette4ColorPalette, new[] { new[] { 0x00, 0x01, 0x02 }, new[] { 0x04, 0x0A, 0x0D }, new[] { 0x01, 0x02, 0x03 }, new[] { 0x0A, 0x0B, 0x0E } }); - public static IEnumerable Palette4Data + public static IEnumerable Palette4Data + { + get { - get - { - yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Palette4Result4X4 }; - yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Offset(Palette4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 0, 4, 4, Offset(Palette4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 1, 4, 4, Offset(Palette4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 1, 4, 4, Offset(Palette4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Palette4Result3X4 }; - yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Offset(Palette4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 0, 3, 4, Offset(Palette4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 1, 3, 4, Offset(Palette4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 1, 3, 4, Offset(Palette4Result3X4, 1, 1, 6, 6) }; - } + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Palette4Result4X4 }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 0, 4, 4, Offset(Palette4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 0, 4, 4, Offset(Palette4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 0, 1, 4, 4, Offset(Palette4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Palette4Bytes4X4, 4, Palette4ColorMap, 1, 1, 4, 4, Offset(Palette4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Palette4Result3X4 }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 0, 3, 4, Offset(Palette4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 0, 3, 4, Offset(Palette4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 0, 1, 3, 4, Offset(Palette4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Palette4Bytes3X4, 4, Palette4ColorMap, 1, 1, 3, 4, Offset(Palette4Result3X4, 1, 1, 6, 6) }; } + } - public static uint[][] Palette8ColorPalette => GeneratePalette(256); + public static uint[][] Palette8ColorPalette => GeneratePalette(256); - public static ushort[] Palette8ColorMap => GenerateColorMap(Palette8ColorPalette); + public static ushort[] Palette8ColorMap => GenerateColorMap(Palette8ColorPalette); - private static readonly byte[] Palette8Bytes4X4 = - { - 000, 001, 002, 003, - 100, 110, 120, 130, - 000, 255, 128, 255, - 050, 100, 150, 200 - }; + private static readonly byte[] Palette8Bytes4X4 = + { + 000, 001, 002, 003, + 100, 110, 120, 130, + 000, 255, 128, 255, + 050, 100, 150, 200 + }; - private static readonly Rgba32[][] Palette8Result4X4 = GenerateResult(Palette8ColorPalette, new[] { new[] { 000, 001, 002, 003 }, new[] { 100, 110, 120, 130 }, new[] { 000, 255, 128, 255 }, new[] { 050, 100, 150, 200 } }); + private static readonly Rgba32[][] Palette8Result4X4 = GenerateResult(Palette8ColorPalette, new[] { new[] { 000, 001, 002, 003 }, new[] { 100, 110, 120, 130 }, new[] { 000, 255, 128, 255 }, new[] { 050, 100, 150, 200 } }); - public static IEnumerable Palette8Data + public static IEnumerable Palette8Data + { + get { - get - { - yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Palette8Result4X4 }; - yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Offset(Palette8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 0, 4, 4, Offset(Palette8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 1, 4, 4, Offset(Palette8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 1, 4, 4, Offset(Palette8Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Palette8Result4X4 }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 0, 4, 4, Offset(Palette8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 0, 4, 4, Offset(Palette8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 0, 1, 4, 4, Offset(Palette8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Palette8Bytes4X4, 8, Palette8ColorMap, 1, 1, 4, 4, Offset(Palette8Result4X4, 1, 1, 6, 6) }; } + } - [Theory] - [MemberData(nameof(Palette4Data))] - [MemberData(nameof(Palette8Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) - => AssertDecode(expectedResult, pixels => - { - new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); - }); + [Theory] + [MemberData(nameof(Palette4Data))] + [MemberData(nameof(Palette8Data))] + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) + => AssertDecode(expectedResult, pixels => + { + new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); + }); - private static uint[][] GeneratePalette(int count) + private static uint[][] GeneratePalette(int count) + { + var palette = new uint[count][]; + + for (uint i = 0; i < count; i++) { - var palette = new uint[count][]; + palette[i] = new[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u }; + } - for (uint i = 0; i < count; i++) - { - palette[i] = new[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u }; - } + return palette; + } - return palette; - } + private static ushort[] GenerateColorMap(uint[][] colorPalette) + { + int colorCount = colorPalette.Length; + var colorMap = new ushort[colorCount * 3]; - private static ushort[] GenerateColorMap(uint[][] colorPalette) + for (int i = 0; i < colorCount; i++) { - int colorCount = colorPalette.Length; - var colorMap = new ushort[colorCount * 3]; + colorMap[(colorCount * 0) + i] = (ushort)colorPalette[i][0]; + colorMap[(colorCount * 1) + i] = (ushort)colorPalette[i][1]; + colorMap[(colorCount * 2) + i] = (ushort)colorPalette[i][2]; + } - for (int i = 0; i < colorCount; i++) - { - colorMap[(colorCount * 0) + i] = (ushort)colorPalette[i][0]; - colorMap[(colorCount * 1) + i] = (ushort)colorPalette[i][1]; - colorMap[(colorCount * 2) + i] = (ushort)colorPalette[i][2]; - } + return colorMap; + } - return colorMap; - } + private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) + { + var result = new Rgba32[pixelLookup.Length][]; - private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup) + for (int y = 0; y < pixelLookup.Length; y++) { - var result = new Rgba32[pixelLookup.Length][]; + result[y] = new Rgba32[pixelLookup[y].Length]; - for (int y = 0; y < pixelLookup.Length; y++) + for (int x = 0; x < pixelLookup[y].Length; x++) { - result[y] = new Rgba32[pixelLookup[y].Length]; - - for (int x = 0; x < pixelLookup[y].Length; x++) - { - uint[] sourceColor = colorPalette[pixelLookup[y][x]]; - result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); - } + uint[] sourceColor = colorPalette[pixelLookup[y][x]]; + result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F); } - - return result; } + + return result; } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs index 5a8c4a2834..b174403839 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs @@ -1,67 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; + +[Trait("Format", "Tiff")] +public abstract class PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public abstract class PhotometricInterpretationTestBase + public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128); + + public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height) { - public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128); + int inputHeight = input.Length; + int inputWidth = input[0].Length; - public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height) - { - int inputHeight = input.Length; - int inputWidth = input[0].Length; + var output = new Rgba32[height][]; - var output = new Rgba32[height][]; + for (int y = 0; y < output.Length; y++) + { + output[y] = new Rgba32[width]; - for (int y = 0; y < output.Length; y++) + for (int x = 0; x < width; x++) { - output[y] = new Rgba32[width]; - - for (int x = 0; x < width; x++) - { - output[y][x] = DefaultColor; - } + output[y][x] = DefaultColor; } + } - for (int y = 0; y < inputHeight; y++) + for (int y = 0; y < inputHeight; y++) + { + for (int x = 0; x < inputWidth; x++) { - for (int x = 0; x < inputWidth; x++) - { - output[y + yOffset][x + xOffset] = input[y][x]; - } + output[y + yOffset][x + xOffset] = input[y][x]; } - - return output; } - internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) - { - int resultWidth = expectedResult[0].Length; - int resultHeight = expectedResult.Length; + return output; + } - using (var image = new Image(resultWidth, resultHeight)) - { - image.Mutate(x => x.BackgroundColor(DefaultColor)); - Buffer2D pixels = image.GetRootFramePixelBuffer(); + internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) + { + int resultWidth = expectedResult[0].Length; + int resultHeight = expectedResult.Length; + + using (var image = new Image(resultWidth, resultHeight)) + { + image.Mutate(x => x.BackgroundColor(DefaultColor)); + Buffer2D pixels = image.GetRootFramePixelBuffer(); - decodeAction(pixels); + decodeAction(pixels); - for (int y = 0; y < resultHeight; y++) + for (int y = 0; y < resultHeight; y++) + { + for (int x = 0; x < resultWidth; x++) { - for (int x = 0; x < resultWidth; x++) - { - Assert.True( - expectedResult[y][x] == pixels[x, y], - $"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}"); - } + Assert.True( + expectedResult[y][x] == pixels[x, y], + $"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}"); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs index 16930e7df2..d8249c3619 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs @@ -1,273 +1,268 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +[Trait("Format", "Tiff")] +public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase + private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); + private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); + private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); + private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); + + private static readonly byte[] Rgb4Bytes4X4R = { - private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); - private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); - private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); - private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); - private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); - private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); - private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); - - private static readonly byte[] Rgb4Bytes4X4R = - { - 0x0F, 0x0F, - 0xF0, 0x0F, - 0x48, 0xC4, - 0x04, 0x8C - }; + 0x0F, 0x0F, + 0xF0, 0x0F, + 0x48, 0xC4, + 0x04, 0x8C + }; - private static readonly byte[] Rgb4Bytes4X4G = - { - 0x0F, 0x0F, - 0x0F, 0x00, - 0x00, 0x08, - 0x04, 0x8C - }; + private static readonly byte[] Rgb4Bytes4X4G = + { + 0x0F, 0x0F, + 0x0F, 0x00, + 0x00, 0x08, + 0x04, 0x8C + }; - private static readonly byte[] Rgb4Bytes4X4B = - { - 0x0F, 0x0F, - 0x00, 0xFF, - 0x00, 0x0C, - 0x04, 0x8C - }; + private static readonly byte[] Rgb4Bytes4X4B = + { + 0x0F, 0x0F, + 0x00, 0xFF, + 0x00, 0x0C, + 0x04, 0x8C + }; - private static readonly byte[][] Rgb4Bytes4X4 = { Rgb4Bytes4X4R, Rgb4Bytes4X4G, Rgb4Bytes4X4B }; + private static readonly byte[][] Rgb4Bytes4X4 = { Rgb4Bytes4X4R, Rgb4Bytes4X4G, Rgb4Bytes4X4B }; - private static readonly Rgba32[][] Rgb4Result4X4 = - { - new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, - new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, - new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, - new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } - }; + private static readonly Rgba32[][] Rgb4Result4X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } + }; - private static readonly byte[] Rgb4Bytes3X4R = - { - 0x0F, 0x00, - 0xF0, 0x00, - 0x48, 0xC0, - 0x04, 0x80 - }; + private static readonly byte[] Rgb4Bytes3X4R = + { + 0x0F, 0x00, + 0xF0, 0x00, + 0x48, 0xC0, + 0x04, 0x80 + }; - private static readonly byte[] Rgb4Bytes3X4G = - { - 0x0F, 0x00, - 0x0F, 0x00, - 0x00, 0x00, - 0x04, 0x80 - }; + private static readonly byte[] Rgb4Bytes3X4G = + { + 0x0F, 0x00, + 0x0F, 0x00, + 0x00, 0x00, + 0x04, 0x80 + }; - private static readonly byte[] Rgb4Bytes3X4B = - { - 0x0F, 0x00, - 0x00, 0xF0, - 0x00, 0x00, - 0x04, 0x80 - }; + private static readonly byte[] Rgb4Bytes3X4B = + { + 0x0F, 0x00, + 0x00, 0xF0, + 0x00, 0x00, + 0x04, 0x80 + }; - private static readonly byte[][] Rgb4Bytes3X4 = { Rgb4Bytes3X4R, Rgb4Bytes3X4G, Rgb4Bytes3X4B }; + private static readonly byte[][] Rgb4Bytes3X4 = { Rgb4Bytes3X4R, Rgb4Bytes3X4G, Rgb4Bytes3X4B }; - private static readonly Rgba32[][] Rgb4Result3X4 = - { - new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, - new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, - new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, - new[] { Rgb4_000, Rgb4_444, Rgb4_888 } - }; + private static readonly Rgba32[][] Rgb4Result3X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888 } + }; - public static IEnumerable Rgb4Data + public static IEnumerable Rgb4Data + { + get { - get - { - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; } + } - private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); - private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); - private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); - private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); - private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); - private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); - private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); - - private static readonly byte[] Rgb8Bytes4X4R = - { - 000, 255, 000, 255, - 255, 000, 000, 255, - 064, 128, 192, 064, - 000, 064, 128, 192 - }; + private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); + private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); + private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); + private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); + private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); + private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); + private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); + + private static readonly byte[] Rgb8Bytes4X4R = + { + 000, 255, 000, 255, + 255, 000, 000, 255, + 064, 128, 192, 064, + 000, 064, 128, 192 + }; - private static readonly byte[] Rgb8Bytes4X4G = - { - 000, 255, 000, 255, - 000, 255, 000, 000, - 000, 000, 000, 128, - 000, 064, 128, 192 - }; + private static readonly byte[] Rgb8Bytes4X4G = + { + 000, 255, 000, 255, + 000, 255, 000, 000, + 000, 000, 000, 128, + 000, 064, 128, 192 + }; - private static readonly byte[] Rgb8Bytes4X4B = - { - 000, 255, 000, 255, - 000, 000, 255, 255, - 000, 000, 000, 192, - 000, 064, 128, 192 - }; + private static readonly byte[] Rgb8Bytes4X4B = + { + 000, 255, 000, 255, + 000, 000, 255, 255, + 000, 000, 000, 192, + 000, 064, 128, 192 + }; - private static readonly byte[][] Rgb8Bytes4X4 = - { - Rgb8Bytes4X4R, Rgb8Bytes4X4G, Rgb8Bytes4X4B - }; + private static readonly byte[][] Rgb8Bytes4X4 = + { + Rgb8Bytes4X4R, Rgb8Bytes4X4G, Rgb8Bytes4X4B + }; - private static readonly Rgba32[][] Rgb8Result4X4 = - { - new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, - new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, - new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, - new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } - }; + private static readonly Rgba32[][] Rgb8Result4X4 = + { + new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, + new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, + new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, + new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } + }; - public static IEnumerable Rgb8Data + public static IEnumerable Rgb8Data + { + get { - get - { - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; } + } - private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); - private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); - private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); - private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); - private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); - private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); - private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); - - private static readonly byte[] Rgb484Bytes4X4R = - { - 0x0F, 0x0F, - 0xF0, 0x0F, - 0x48, 0xC4, - 0x04, 0x8C - }; + private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); + private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); + private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); + private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); + + private static readonly byte[] Rgb484Bytes4X4R = + { + 0x0F, 0x0F, + 0xF0, 0x0F, + 0x48, 0xC4, + 0x04, 0x8C + }; - private static readonly byte[] Rgb484Bytes4X4G = - { - 0x00, 0xFF, 0x00, 0xFF, - 0x00, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x80, - 0x00, 0x40, 0x80, 0xC0 - }; + private static readonly byte[] Rgb484Bytes4X4G = + { + 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x40, 0x80, 0xC0 + }; - private static readonly byte[] Rgb484Bytes4X4B = - { - 0x0F, 0x0F, - 0x00, 0xFF, - 0x00, 0x0C, - 0x04, 0x8C - }; + private static readonly byte[] Rgb484Bytes4X4B = + { + 0x0F, 0x0F, + 0x00, 0xFF, + 0x00, 0x0C, + 0x04, 0x8C + }; - private static readonly Rgba32[][] Rgb484Result4X4 = - { - new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, - new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, - new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, - new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } - }; + private static readonly Rgba32[][] Rgb484Result4X4 = + { + new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, + new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, + new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, + new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } + }; - private static readonly byte[][] Rgb484Bytes4X4 = { Rgb484Bytes4X4R, Rgb484Bytes4X4G, Rgb484Bytes4X4B }; + private static readonly byte[][] Rgb484Bytes4X4 = { Rgb484Bytes4X4R, Rgb484Bytes4X4G, Rgb484Bytes4X4B }; - public static IEnumerable Rgb484_Data + public static IEnumerable Rgb484_Data + { + get { - get - { - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; } + } - [Theory] - [MemberData(nameof(Rgb4Data))] - [MemberData(nameof(Rgb8Data))] - [MemberData(nameof(Rgb484_Data))] - public void Decode_WritesPixelData( - byte[][] inputData, - TiffBitsPerSample bitsPerSample, - int left, - int top, - int width, - int height, - Rgba32[][] expectedResult) - => AssertDecode( - expectedResult, - pixels => + [Theory] + [MemberData(nameof(Rgb4Data))] + [MemberData(nameof(Rgb8Data))] + [MemberData(nameof(Rgb484_Data))] + public void Decode_WritesPixelData( + byte[][] inputData, + TiffBitsPerSample bitsPerSample, + int left, + int top, + int width, + int height, + Rgba32[][] expectedResult) + => AssertDecode( + expectedResult, + pixels => + { + var buffers = new IMemoryOwner[inputData.Length]; + for (int i = 0; i < buffers.Length; i++) { - var buffers = new IMemoryOwner[inputData.Length]; - for (int i = 0; i < buffers.Length; i++) - { - buffers[i] = Configuration.Default.MemoryAllocator.Allocate(inputData[i].Length); - ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); - } - - new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); - - foreach (IMemoryOwner buffer in buffers) - { - buffer.Dispose(); - } - }); - } + buffers[i] = Configuration.Default.MemoryAllocator.Allocate(inputData[i].Length); + ((Span)inputData[i]).CopyTo(buffers[i].GetSpan()); + } + + new RgbPlanarTiffColor(bitsPerSample).Decode(buffers, pixels, left, top, width, height); + + foreach (IMemoryOwner buffer in buffers) + { + buffer.Dispose(); + } + }); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs index 323f4f2d91..aeb135773c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs @@ -1,186 +1,182 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +[Trait("Format", "Tiff")] +public class RgbTiffColorTests : PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public class RgbTiffColorTests : PhotometricInterpretationTestBase + private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); + private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); + private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); + private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); + + private static readonly byte[] Rgb4Bytes4X4 = { - private static readonly Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255); - private static readonly Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255); - private static readonly Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255); - private static readonly Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255); - private static readonly Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255); - private static readonly Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255); - private static readonly Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255); - - private static readonly byte[] Rgb4Bytes4X4 = - { - 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, - 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F, - 0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C, - 0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC - }; + 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, + 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F, + 0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C, + 0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC + }; - private static readonly Rgba32[][] Rgb4Result4X4 = - { - new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, - new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, - new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, - new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } - }; + private static readonly Rgba32[][] Rgb4Result4X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC } + }; - private static readonly byte[] Rgb4Bytes3X4 = - { - 0x00, 0x0F, 0xFF, 0x00, 0x00, - 0xF0, 0x00, 0xF0, 0x00, 0xF0, - 0x40, 0x08, 0x00, 0xC0, 0x00, - 0x00, 0x04, 0x44, 0x88, 0x80 - }; + private static readonly byte[] Rgb4Bytes3X4 = + { + 0x00, 0x0F, 0xFF, 0x00, 0x00, + 0xF0, 0x00, 0xF0, 0x00, 0xF0, + 0x40, 0x08, 0x00, 0xC0, 0x00, + 0x00, 0x04, 0x44, 0x88, 0x80 + }; - private static readonly Rgba32[][] Rgb4Result3X4 = - { - new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, - new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, - new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, - new[] { Rgb4_000, Rgb4_444, Rgb4_888 } - }; + private static readonly Rgba32[][] Rgb4Result3X4 = + { + new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 }, + new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F }, + new[] { Rgb4_400, Rgb4_800, Rgb4_C00 }, + new[] { Rgb4_000, Rgb4_444, Rgb4_888 } + }; - public static IEnumerable Rgb4Data + public static IEnumerable Rgb4Data + { + get { - get - { - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; } + } - private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); - private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); - private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); - private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); - private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); - private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); - private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); - - private static readonly byte[] Rgb8Bytes4X4 = - { - 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255, - 255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255, - 064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192, - 000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 - }; + private static readonly Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255); + private static readonly Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255); + private static readonly Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255); + private static readonly Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255); + private static readonly Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255); + private static readonly Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255); + private static readonly Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255); + + private static readonly byte[] Rgb8Bytes4X4 = + { + 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255, + 255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255, + 064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192, + 000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 + }; - private static readonly Rgba32[][] Rgb8Result4X4 = - { - new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, - new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, - new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, - new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } - }; + private static readonly Rgba32[][] Rgb8Result4X4 = + { + new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF }, + new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F }, + new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C }, + new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC } + }; - public static IEnumerable Rgb8Data + public static IEnumerable Rgb8Data + { + get { - get - { - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; } + } - private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); - private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); - private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); - private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); - private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); - private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); - private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); - private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); - private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); - private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); - private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); - - private static readonly byte[] Rgb484Bytes4X4 = - { - 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, - 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, - 0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C, - 0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C - }; + private static readonly Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255); + private static readonly Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255); + private static readonly Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255); + private static readonly Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255); + private static readonly Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255); + private static readonly Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255); + private static readonly Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255); + private static readonly Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255); + private static readonly Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255); + private static readonly Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255); + private static readonly Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255); + + private static readonly byte[] Rgb484Bytes4X4 = + { + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, + 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F, + 0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C, + 0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C + }; - private static readonly Rgba32[][] Rgb484Result4X4 = - { - new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, - new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, - new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, - new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } - }; + private static readonly Rgba32[][] Rgb484Result4X4 = + { + new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF }, + new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F }, + new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C }, + new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC } + }; - public static IEnumerable Rgb484Data + public static IEnumerable Rgb484Data + { + get { - get - { - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; } + } - [Theory] - [MemberData(nameof(Rgb4Data))] - [MemberData(nameof(Rgb8Data))] - [MemberData(nameof(Rgb484Data))] - public void Decode_WritesPixelData(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new RgbTiffColor(bitsPerSample).Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(Rgb4Data))] + [MemberData(nameof(Rgb8Data))] + [MemberData(nameof(Rgb484Data))] + public void Decode_WritesPixelData(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new RgbTiffColor(bitsPerSample).Decode(inputData, pixels, left, top, width, height); + }); + } - [Theory] - [MemberData(nameof(Rgb8Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new Rgb888TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(Rgb8Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new Rgb888TiffColor(Configuration.Default).Decode(inputData, pixels, left, top, width, height); + }); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs index cd2048542a..0b58e3891e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs @@ -1,195 +1,191 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation +[Trait("Format", "Tiff")] +public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase { - [Trait("Format", "Tiff")] - public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase - { - private static readonly Rgba32 Gray000 = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Gray128 = new Rgba32(127, 127, 127, 255); - private static readonly Rgba32 Gray255 = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Gray0 = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Gray8 = new Rgba32(119, 119, 119, 255); - private static readonly Rgba32 GrayF = new Rgba32(0, 0, 0, 255); - private static readonly Rgba32 Bit0 = new Rgba32(255, 255, 255, 255); - private static readonly Rgba32 Bit1 = new Rgba32(0, 0, 0, 255); - - private static readonly byte[] BilevelBytes4X4 = - { - 0b01010000, - 0b11110000, - 0b01110000, - 0b10010000 - }; + private static readonly Rgba32 Gray000 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Gray128 = new Rgba32(127, 127, 127, 255); + private static readonly Rgba32 Gray255 = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Gray0 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Gray8 = new Rgba32(119, 119, 119, 255); + private static readonly Rgba32 GrayF = new Rgba32(0, 0, 0, 255); + private static readonly Rgba32 Bit0 = new Rgba32(255, 255, 255, 255); + private static readonly Rgba32 Bit1 = new Rgba32(0, 0, 0, 255); + + private static readonly byte[] BilevelBytes4X4 = + { + 0b01010000, + 0b11110000, + 0b01110000, + 0b10010000 + }; - private static readonly Rgba32[][] BilevelResult4X4 = - { - new[] { Bit0, Bit1, Bit0, Bit1 }, - new[] { Bit1, Bit1, Bit1, Bit1 }, - new[] { Bit0, Bit1, Bit1, Bit1 }, - new[] { Bit1, Bit0, Bit0, Bit1 } - }; + private static readonly Rgba32[][] BilevelResult4X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit1 }, + new[] { Bit1, Bit0, Bit0, Bit1 } + }; - private static readonly byte[] BilevelBytes12X4 = - { - 0b01010101, 0b01010000, - 0b11111111, 0b11111111, - 0b01101001, 0b10100000, - 0b10010000, 0b01100000 - }; + private static readonly byte[] BilevelBytes12X4 = + { + 0b01010101, 0b01010000, + 0b11111111, 0b11111111, + 0b01101001, 0b10100000, + 0b10010000, 0b01100000 + }; - private static readonly Rgba32[][] BilevelResult12X4 = - { - new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, - new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, - new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, - new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } - }; + private static readonly Rgba32[][] BilevelResult12X4 = + { + new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, + new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, + new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, + new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 } + }; - private static readonly byte[] Grayscale4Bytes4X4 = - { - 0x8F, 0x0F, - 0xFF, 0xFF, - 0x08, 0x8F, - 0xF0, 0xF8 - }; + private static readonly byte[] Grayscale4Bytes4X4 = + { + 0x8F, 0x0F, + 0xFF, 0xFF, + 0x08, 0x8F, + 0xF0, 0xF8 + }; - private static readonly Rgba32[][] Grayscale4Result4X4 = - { - new[] { Gray8, GrayF, Gray0, GrayF }, - new[] { GrayF, GrayF, GrayF, GrayF }, - new[] { Gray0, Gray8, Gray8, GrayF }, - new[] { GrayF, Gray0, GrayF, Gray8 } - }; + private static readonly Rgba32[][] Grayscale4Result4X4 = + { + new[] { Gray8, GrayF, Gray0, GrayF }, + new[] { GrayF, GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8, GrayF }, + new[] { GrayF, Gray0, GrayF, Gray8 } + }; - private static readonly byte[] Grayscale4Bytes3X4 = - { - 0x8F, 0x00, - 0xFF, 0xF0, - 0x08, 0x80, - 0xF0, 0xF0 - }; + private static readonly byte[] Grayscale4Bytes3X4 = + { + 0x8F, 0x00, + 0xFF, 0xF0, + 0x08, 0x80, + 0xF0, 0xF0 + }; - private static readonly Rgba32[][] Grayscale4Result3X4 = - { - new[] { Gray8, GrayF, Gray0 }, - new[] { GrayF, GrayF, GrayF }, - new[] { Gray0, Gray8, Gray8 }, - new[] { GrayF, Gray0, GrayF } - }; + private static readonly Rgba32[][] Grayscale4Result3X4 = + { + new[] { Gray8, GrayF, Gray0 }, + new[] { GrayF, GrayF, GrayF }, + new[] { Gray0, Gray8, Gray8 }, + new[] { GrayF, Gray0, GrayF } + }; - private static readonly byte[] Grayscale8Bytes4X4 = - { - 128, 255, 000, 255, - 255, 255, 255, 255, - 000, 128, 128, 255, - 255, 000, 255, 128 - }; + private static readonly byte[] Grayscale8Bytes4X4 = + { + 128, 255, 000, 255, + 255, 255, 255, 255, + 000, 128, 128, 255, + 255, 000, 255, 128 + }; - private static readonly Rgba32[][] Grayscale8Result4X4 = - { - new[] { Gray128, Gray255, Gray000, Gray255 }, - new[] { Gray255, Gray255, Gray255, Gray255 }, - new[] { Gray000, Gray128, Gray128, Gray255 }, - new[] { Gray255, Gray000, Gray255, Gray128 } - }; + private static readonly Rgba32[][] Grayscale8Result4X4 = + { + new[] { Gray128, Gray255, Gray000, Gray255 }, + new[] { Gray255, Gray255, Gray255, Gray255 }, + new[] { Gray000, Gray128, Gray128, Gray255 }, + new[] { Gray255, Gray000, Gray255, Gray128 } + }; - public static IEnumerable BilevelData - { - get - { - yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; - yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; - yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; - - yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; - yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; - yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; - } + public static IEnumerable BilevelData + { + get + { + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, BilevelResult4X4 }; + yield return new object[] { BilevelBytes4X4, 1, 0, 0, 4, 4, Offset(BilevelResult4X4, 0, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 0, 4, 4, Offset(BilevelResult4X4, 1, 0, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 0, 1, 4, 4, Offset(BilevelResult4X4, 0, 1, 6, 6) }; + yield return new object[] { BilevelBytes4X4, 1, 1, 1, 4, 4, Offset(BilevelResult4X4, 1, 1, 6, 6) }; + + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, BilevelResult12X4 }; + yield return new object[] { BilevelBytes12X4, 1, 0, 0, 12, 4, Offset(BilevelResult12X4, 0, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 0, 12, 4, Offset(BilevelResult12X4, 1, 0, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 0, 1, 12, 4, Offset(BilevelResult12X4, 0, 1, 18, 6) }; + yield return new object[] { BilevelBytes12X4, 1, 1, 1, 12, 4, Offset(BilevelResult12X4, 1, 1, 18, 6) }; } + } - public static IEnumerable Grayscale4Data - { - get - { - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; - - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; - } + public static IEnumerable Grayscale4Data + { + get + { + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Grayscale4Result4X4 }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 0, 4, 4, Offset(Grayscale4Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 0, 4, 4, Offset(Grayscale4Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 0, 1, 4, 4, Offset(Grayscale4Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes4X4, 4, 1, 1, 4, 4, Offset(Grayscale4Result4X4, 1, 1, 6, 6) }; + + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Grayscale4Result3X4 }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 0, 3, 4, Offset(Grayscale4Result3X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 0, 3, 4, Offset(Grayscale4Result3X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 0, 1, 3, 4, Offset(Grayscale4Result3X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale4Bytes3X4, 4, 1, 1, 3, 4, Offset(Grayscale4Result3X4, 1, 1, 6, 6) }; } + } - public static IEnumerable Grayscale8Data + public static IEnumerable Grayscale8Data + { + get { - get - { - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; - yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; - } + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Grayscale8Result4X4 }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 0, 4, 4, Offset(Grayscale8Result4X4, 0, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 0, 4, 4, Offset(Grayscale8Result4X4, 1, 0, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 0, 1, 4, 4, Offset(Grayscale8Result4X4, 0, 1, 6, 6) }; + yield return new object[] { Grayscale8Bytes4X4, 8, 1, 1, 4, 4, Offset(Grayscale8Result4X4, 1, 1, 6, 6) }; } + } - [Theory] - [MemberData(nameof(BilevelData))] - [MemberData(nameof(Grayscale4Data))] - [MemberData(nameof(Grayscale8Data))] - public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(BilevelData))] + [MemberData(nameof(Grayscale4Data))] + [MemberData(nameof(Grayscale8Data))] + public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZeroTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height); + }); + } - [Theory] - [MemberData(nameof(BilevelData))] - public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(BilevelData))] + public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero1TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } - [Theory] - [MemberData(nameof(Grayscale4Data))] - public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(Grayscale4Data))] + public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero4TiffColor().Decode(inputData, pixels, left, top, width, height); + }); + } - [Theory] - [MemberData(nameof(Grayscale8Data))] - public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) - { - AssertDecode(expectedResult, pixels => - { - new WhiteIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); - }); - } + [Theory] + [MemberData(nameof(Grayscale8Data))] + public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) + { + AssertDecode(expectedResult, pixels => + { + new WhiteIsZero8TiffColor().Decode(inputData, pixels, left, top, width, height); + }); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs index 5c533aef67..95f37ba407 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs @@ -8,23 +8,22 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +public abstract class TiffDecoderBaseTester { - public abstract class TiffDecoderBaseTester - { - protected static TiffDecoder TiffDecoder => new(); + protected static TiffDecoder TiffDecoder => new(); - protected static MagickReferenceDecoder ReferenceDecoder => new(); + protected static MagickReferenceDecoder ReferenceDecoder => new(); - protected static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - image.CompareToOriginal( - provider, - useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), - referenceDecoder ?? ReferenceDecoder); - } + protected static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + image.CompareToOriginal( + provider, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder ?? ReferenceDecoder); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 443e42700f..bcfd759a03 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -2,736 +2,731 @@ // Licensed under the Six Labors Split License. // ReSharper disable InconsistentNaming -using System; -using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +[ValidateDisposedMemoryAllocations] +public class TiffDecoderTests : TiffDecoderBaseTester { - [Trait("Format", "Tiff")] - [ValidateDisposedMemoryAllocations] - public class TiffDecoderTests : TiffDecoderBaseTester + public static readonly string[] MultiframeTestImages = Multiframes; + + [Theory] + [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] + public void ThrowsNotSupported(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + + [Theory] + [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] + [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Calliphora_GrayscaleUncompressed, 8, 200, 298, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] + public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) { - public static readonly string[] MultiframeTestImages = Multiframes; - - [Theory] - [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)] - [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] - [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] - public void ThrowsNotSupported(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); - - [Theory] - [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] - [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Calliphora_GrayscaleUncompressed, 8, 200, 298, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] - public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo info = Image.Identify(stream); - - Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); - Assert.Equal(expectedWidth, info.Width); - Assert.Equal(expectedHeight, info.Height); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); - Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); - Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); - } + IImageInfo info = Image.Identify(stream); + + Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); + Assert.Equal(expectedWidth, info.Width); + Assert.Equal(expectedHeight, info.Height); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); + Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); + Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); } + } - [Theory] - [InlineData(RgbLzwNoPredictorMultistrip, ImageSharp.ByteOrder.LittleEndian)] - [InlineData(RgbLzwNoPredictorMultistripMotorola, ImageSharp.ByteOrder.BigEndian)] - public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + [Theory] + [InlineData(RgbLzwNoPredictorMultistrip, ImageSharp.ByteOrder.LittleEndian)] + [InlineData(RgbLzwNoPredictorMultistripMotorola, ImageSharp.ByteOrder.BigEndian)] + public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) { - var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - IImageInfo info = Image.Identify(stream); + IImageInfo info = Image.Identify(stream); - Assert.NotNull(info.Metadata); - Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); - stream.Seek(0, SeekOrigin.Begin); + stream.Seek(0, SeekOrigin.Begin); - using var img = Image.Load(stream); - Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); - } + using var img = Image.Load(stream); + Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); } + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Uncompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)] + [WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_32Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_64Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_96Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Uncompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb888Planar6Strips, PixelTypes.Rgba32)] - [WithFile(FlowerRgb888Planar15Strips, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba8BitPlanarUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_32Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba16BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_64Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba24BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba24BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Planar_128Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(RgbPalette, PixelTypes.Rgba32)] + [WithFile(RgbPaletteDeflate, PixelTypes.Rgba32)] + [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_4Bit_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + + [Theory] + [WithFile(Flower2BitPalette, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit_WithPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); + + [Theory] + [WithFile(Flower2BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_2Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_6Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower6BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_6Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower8BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower10BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower12BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); image.DebugSave(provider); + return; } - [Theory] - [WithFile(Rgba32BitPlanarUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitPlanarUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Planar_128Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } + + [Theory] + [WithFile(Flower14BitGray, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower16BitGray, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_18Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider) + + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); image.DebugSave(provider); + return; } - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(RgbPalette, PixelTypes.Rgba32)] - [WithFile(RgbPaletteDeflate, PixelTypes.Rgba32)] - [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_4Bit_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); - - [Theory] - [WithFile(Flower2BitPalette, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_2Bit_WithPalette(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); - - [Theory] - [WithFile(Flower2BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_2Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_6Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower6BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_6Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower8BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_8Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba2BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_8Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb3Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_9Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower10BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_10Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower12BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false); - } + [Theory] + [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - [Theory] - [WithFile(Flower14BitGray, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_14Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb5Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_15Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower16BitGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGray, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayMinIsWhiteBigEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower16BitGrayPredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(Flower16BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_Gray_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba4BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_16Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FLowerRgb6Bit, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_18Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba5BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider) - - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } + [Theory] + [WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false); + [Theory] + [WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) + { + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; } - [Theory] - [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } - [Theory] - [WithFile(Rgba6BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + [Theory] + [WithFile(Flower24BitGray, PixelTypes.Rgba32)] + [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } + [Theory] + [WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)] + [WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] + [WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] + [WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)] + [WithFile(FlowerYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] + [WithFile(FlowerYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_YCbCr_24Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong + // converting the pixel data from Magick.NET to our format with YCbCr? + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false); - } + [Theory] + [WithFile(CieLab, PixelTypes.Rgba32)] + [WithFile(CieLabPlanar, PixelTypes.Rgba32)] + [WithFile(CieLabLzwPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_CieLab(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong + // converting the pixel data from Magick.NET to our format with CieLab? + using Image image = provider.GetImage(); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } - [Theory] - [WithFile(Flower24BitGray, PixelTypes.Rgba32)] - [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_24Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Flower32BitGray, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayMinIsWhite, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(FlowerYCbCr888Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Planar, PixelTypes.Rgba32)] - [WithFile(RgbYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] - [WithFile(RgbYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush2v1, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush2v2, PixelTypes.Rgba32)] - [WithFile(FlowerYCbCr888Contiguoush4v4, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_YCbCr_24Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.NET to our format with YCbCr? - using Image image = provider.GetImage(); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + [Theory] + [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] + [WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(CieLab, PixelTypes.Rgba32)] - [WithFile(CieLabPlanar, PixelTypes.Rgba32)] - [WithFile(CieLabLzwPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_CieLab(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong - // converting the pixel data from Magick.NET to our format with CieLab? - using Image image = provider.GetImage(); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); + return; } - [Theory] - [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_30Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Flower32BitGray, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayMinIsWhite, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F); + } + + [Theory] + [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(Rgba8BitUnassociatedAlpha, PixelTypes.Rgba32)] - [WithFile(Rgba8BitUnassociatedAlphaWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); image.DebugSave(provider); + return; } - [Theory] - [WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } - - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F); - } + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false); + } - [Theory] - [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_32Bit_Gray_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_42Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616ContiguousLittleEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)] + [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); image.DebugSave(provider); + return; } - [Theory] - [WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_36Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba10BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } - - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false); - } + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); + } - [Theory] - [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_42Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616PlanarLittleEndian, PixelTypes.Rgba32)] - [WithFile(Issues1716Rgb161616BitLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba12BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsMacOS) { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } - - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); + // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + return; } - [Theory] - [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_48Bit_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba14BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba14BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.IsMacOS) - { - // Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though. - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - return; - } - - // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. - TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); - } + // Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues. + TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f); + } - [Theory] - [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb242424PlanarLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_72Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232ContiguousLittleEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(FlowerRgb323232Contiguous, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232ContiguousLittleEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232Planar, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232PlanarLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(Rgba24BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba24BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] - [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Float_96Bit(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(FlowerRgbFloat323232, PixelTypes.Rgba32)] + [WithFile(FlowerRgbFloat323232LittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Float_96Bit(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(FlowerRgb323232PredictorBigEndian, PixelTypes.Rgba32)] - [WithFile(FlowerRgb323232PredictorLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_96Bit_WithPredictor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(FlowerRgb323232PredictorBigEndian, PixelTypes.Rgba32)] + [WithFile(FlowerRgb323232PredictorLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_96Bit_WithPredictor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayMinIsWhite, PixelTypes.Rgba32)] - [WithFile(Flower32BitFloatGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(Flower32BitFloatGray, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayLittleEndian, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayMinIsWhite, PixelTypes.Rgba32)] + [WithFile(Flower32BitFloatGrayMinIsWhiteLittleEndian, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Float_96Bit_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] - [WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] - [WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. - using Image image = provider.GetImage(); - image.DebugSave(provider); - } + [Theory] + [WithFile(Rgba16BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] + [WithFile(Rgba16BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_128Bit_UnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Rgba32BitUnassociatedAlphaBigEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaLittleEndian, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaBigEndianWithPredictor, PixelTypes.Rgba32)] + [WithFile(Rgba32BitUnassociatedAlphaLittleEndianWithPredictor, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_128Bit_WithUnassociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Note: because the MagickReferenceDecoder fails to load the image, we only debug save them. + using Image image = provider.GetImage(); + image.DebugSave(provider); + } - [Theory] - [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleDeflate_Predictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbDeflate_Predictor, PixelTypes.Rgba32)] - [WithFile(RgbDeflate, PixelTypes.Rgba32)] - [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32)] - [WithFile(SmallRgbDeflate, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_DeflateCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(RgbLzwPredictor, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictorSinglestripMotorola, PixelTypes.Rgba32)] - [WithFile(RgbLzwNoPredictorMultistripMotorola, PixelTypes.Rgba32)] - [WithFile(RgbLzwMultistripPredictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbPaletteLzw_Predictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_RgbLzwPredictor, PixelTypes.Rgba32)] - [WithFile(Calliphora_GrayscaleLzw_Predictor, PixelTypes.Rgba32)] - [WithFile(SmallRgbLzw, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_LzwCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(HuffmanRleAllTermCodes, PixelTypes.Rgba32)] - [WithFile(HuffmanRleAllMakeupCodes, PixelTypes.Rgba32)] - [WithFile(HuffmanRle_basi3p02, PixelTypes.Rgba32)] - [WithFile(Calliphora_HuffmanCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(CcittFax3AllTermCodes, PixelTypes.Rgba32)] - [WithFile(CcittFax3AllMakeupCodes, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax3Compressed, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax3Compressed_WithEolPadding, PixelTypes.Rgba32)] - [WithFile(Fax3Uncompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Fax4Compressed, PixelTypes.Rgba32)] - [WithFile(Fax4Compressed2, PixelTypes.Rgba32)] - [WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)] - [WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)] - [WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax4Compressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] - [WithFile(RgbPackbits, PixelTypes.Rgba32)] - [WithFile(RgbPackbitsMultistrip, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_PackBitsCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] - public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(new TiffDecoder(), options); - Assert.Equal(1, image.Frames.Count); - } + [Theory] + [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleDeflate, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleDeflate_Predictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbDeflate_Predictor, PixelTypes.Rgba32)] + [WithFile(RgbDeflate, PixelTypes.Rgba32)] + [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32)] + [WithFile(SmallRgbDeflate, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_DeflateCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(RgbLzwPredictor, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictorSinglestripMotorola, PixelTypes.Rgba32)] + [WithFile(RgbLzwNoPredictorMultistripMotorola, PixelTypes.Rgba32)] + [WithFile(RgbLzwMultistripPredictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbPaletteLzw_Predictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_RgbLzwPredictor, PixelTypes.Rgba32)] + [WithFile(Calliphora_GrayscaleLzw_Predictor, PixelTypes.Rgba32)] + [WithFile(SmallRgbLzw, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_LzwCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(HuffmanRleAllTermCodes, PixelTypes.Rgba32)] + [WithFile(HuffmanRleAllMakeupCodes, PixelTypes.Rgba32)] + [WithFile(HuffmanRle_basi3p02, PixelTypes.Rgba32)] + [WithFile(Calliphora_HuffmanCompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_HuffmanCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(CcittFax3AllTermCodes, PixelTypes.Rgba32)] + [WithFile(CcittFax3AllMakeupCodes, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax3Compressed, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax3Compressed_WithEolPadding, PixelTypes.Rgba32)] + [WithFile(Fax3Uncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Fax3Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Fax4Compressed, PixelTypes.Rgba32)] + [WithFile(Fax4Compressed2, PixelTypes.Rgba32)] + [WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)] + [WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)] + [WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Fax4Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)] + [WithFile(RgbPackbits, PixelTypes.Rgba32)] + [WithFile(RgbPackbitsMultistrip, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_PackBitsCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(MultiFrameMipMap, PixelTypes.Rgba32)] + public void CanDecodeJustOneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new TiffDecoder(), options); + Assert.Equal(1, image.Frames.Count); + } - [Theory] - [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] - [WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)] - [WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)] - [WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)] - [WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)] - [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] - [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] - [WithFile(Issues2123, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); - - [Theory] - [WithFile(WebpCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_WebpCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(RgbJpegCompressed, PixelTypes.Rgba32)] + [WithFile(RgbJpegCompressed2, PixelTypes.Rgba32)] + [WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)] + [WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)] + [WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)] + [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] + [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] + [WithFile(Issues2123, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); + + [Theory] + [WithFile(WebpCompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_WebpCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsWindows) { - if (TestEnvironment.IsWindows) - { - TestTiffDecoder(provider, useExactComparer: false); - } + TestTiffDecoder(provider, useExactComparer: false); } + } - // https://github.com/SixLabors/ImageSharp/issues/1891 - [Theory] - [WithFile(Issues1891, PixelTypes.Rgba32)] - public void TiffDecoder_ThrowsException_WithTooManyDirectories(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws( - () => + // https://github.com/SixLabors/ImageSharp/issues/1891 + [Theory] + [WithFile(Issues1891, PixelTypes.Rgba32)] + public void TiffDecoder_ThrowsException_WithTooManyDirectories(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws( + () => + { + using (provider.GetImage(TiffDecoder)) { - using (provider.GetImage(TiffDecoder)) - { - } - }); - - // https://github.com/SixLabors/ImageSharp/issues/2149 - [Theory] - [WithFile(Issues2149, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_Fax4CompressedWithStrips(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); - - [Theory] - [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] - public void DecodeMultiframe(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - Assert.True(image.Frames.Count > 1); + } + }); + + // https://github.com/SixLabors/ImageSharp/issues/2149 + [Theory] + [WithFile(Issues2149, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Fax4CompressedWithStrips(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] + public void DecodeMultiframe(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + Assert.True(image.Frames.Count > 1); - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); - image.DebugSaveMultiFrame(provider); - image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); - } + image.DebugSaveMultiFrame(provider); + image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); + } - [Theory] - [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] - public void TiffDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Rgba3BitUnassociatedAlpha, PixelTypes.Rgba32)] + public void TiffDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - TargetSize = new() { Width = 150, Height = 150 } - }; + TargetSize = new() { Width = 150, Height = 150 } + }; - using Image image = provider.GetImage(TiffDecoder, options); + using Image image = provider.GetImage(TiffDecoder, options); - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.Exact, - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs index 16723c295e..594cee2e99 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderBaseTester.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; @@ -10,104 +9,101 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +[Trait("Format", "Tiff")] +public abstract class TiffEncoderBaseTester { - [Trait("Format", "Tiff")] - public abstract class TiffEncoderBaseTester - { - protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); + protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(); - protected static void TestStripLength( - TestImageProvider provider, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, IPixel - { - // arrange - var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image input = provider.GetImage(); - using var memStream = new MemoryStream(); - TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata(); - TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None; + protected static void TestStripLength( + TestImageProvider provider, + TiffPhotometricInterpretation photometricInterpretation, + TiffCompression compression, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + TiffFrameMetadata inputMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata(); + TiffCompression inputCompression = inputMeta.Compression ?? TiffCompression.None; - // act - input.Save(memStream, tiffEncoder); + // act + input.Save(memStream, tiffEncoder); - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; - TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - ImageFrame rootFrame = output.Frames.RootFrame; + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ExifProfile exifProfileOutput = output.Frames.RootFrame.Metadata.ExifProfile; + TiffFrameMetadata outputMeta = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + ImageFrame rootFrame = output.Frames.RootFrame; - Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - Assert.True(output.Height > (int)rowsPerStrip); - Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1); - Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value; - Assert.NotNull(stripByteCounts); - Assert.True(stripByteCounts.Length > 1); - Assert.NotNull(outputMeta.BitsPerPixel); + Number rowsPerStrip = exifProfileOutput.GetValue(ExifTag.RowsPerStrip) != null ? exifProfileOutput.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + Assert.True(output.Height > (int)rowsPerStrip); + Assert.True(exifProfileOutput.GetValue(ExifTag.StripOffsets)?.Value.Length > 1); + Number[] stripByteCounts = exifProfileOutput.GetValue(ExifTag.StripByteCounts)?.Value; + Assert.NotNull(stripByteCounts); + Assert.True(stripByteCounts.Length > 1); + Assert.NotNull(outputMeta.BitsPerPixel); - foreach (Number sz in stripByteCounts) - { - Assert.True((uint)sz <= TiffConstants.DefaultStripSize); - } + foreach (Number sz in stripByteCounts) + { + Assert.True((uint)sz <= TiffConstants.DefaultStripSize); + } - // For uncompressed more accurate test. - if (compression == TiffCompression.None) + // For uncompressed more accurate test. + if (compression == TiffCompression.None) + { + for (int i = 0; i < stripByteCounts.Length - 1; i++) { - for (int i = 0; i < stripByteCounts.Length - 1; i++) - { - // The difference must be less than one row. - int stripBytes = (int)stripByteCounts[i]; - int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width; + // The difference must be less than one row. + int stripBytes = (int)stripByteCounts[i]; + int widthBytes = ((int)outputMeta.BitsPerPixel + 7) / 8 * rootFrame.Width; - Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes); - } + Assert.True((TiffConstants.DefaultStripSize - stripBytes) < widthBytes); } - - // Compare with reference. - TestTiffEncoderCore( - provider, - inputMeta.BitsPerPixel, - photometricInterpretation, - inputCompression, - useExactComparer: useExactComparer, - compareTolerance: compareTolerance); } - protected static void TestTiffEncoderCore( - TestImageProvider provider, - TiffBitsPerPixel? bitsPerPixel, - TiffPhotometricInterpretation photometricInterpretation, - TiffCompression compression = TiffCompression.None, - TiffPredictor predictor = TiffPredictor.None, - bool useExactComparer = true, - float compareTolerance = 0.001f, - IImageDecoder imageDecoder = null) - where TPixel : unmanaged, IPixel + // Compare with reference. + TestTiffEncoderCore( + provider, + inputMeta.BitsPerPixel, + photometricInterpretation, + inputCompression, + useExactComparer: useExactComparer, + compareTolerance: compareTolerance); + } + + protected static void TestTiffEncoderCore( + TestImageProvider provider, + TiffBitsPerPixel? bitsPerPixel, + TiffPhotometricInterpretation photometricInterpretation, + TiffCompression compression = TiffCompression.None, + TiffPredictor predictor = TiffPredictor.None, + bool useExactComparer = true, + float compareTolerance = 0.001f, + IImageDecoder imageDecoder = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + var encoder = new TiffEncoder { - using Image image = provider.GetImage(); - var encoder = new TiffEncoder - { - PhotometricInterpretation = photometricInterpretation, - BitsPerPixel = bitsPerPixel, - Compression = compression, - HorizontalPredictor = predictor - }; + PhotometricInterpretation = photometricInterpretation, + BitsPerPixel = bitsPerPixel, + Compression = compression, + HorizontalPredictor = predictor + }; - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), - referenceDecoder: imageDecoder ?? ReferenceDecoder); - } + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder: imageDecoder ?? ReferenceDecoder); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index 2d594319e6..95ed17f1c7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -1,47 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Writers; using SixLabors.ImageSharp.Memory; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +[Trait("Format", "Tiff")] +public class TiffEncoderHeaderTests { - [Trait("Format", "Tiff")] - public class TiffEncoderHeaderTests + private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create(); + private static readonly Configuration Configuration = Configuration.Default; + private static readonly ITiffEncoderOptions Options = new TiffEncoder(); + + [Fact] + public void WriteHeader_WritesValidHeader() { - private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create(); - private static readonly Configuration Configuration = Configuration.Default; - private static readonly ITiffEncoderOptions Options = new TiffEncoder(); + using var stream = new MemoryStream(); + var encoder = new TiffEncoderCore(Options, MemoryAllocator); - [Fact] - public void WriteHeader_WritesValidHeader() + using (var writer = new TiffStreamWriter(stream)) { - using var stream = new MemoryStream(); - var encoder = new TiffEncoderCore(Options, MemoryAllocator); + long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); + } - using (var writer = new TiffStreamWriter(stream)) - { - long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); - } + Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); + } - Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray()); - } + [Fact] + public void WriteHeader_ReturnsFirstIfdMarker() + { + using var stream = new MemoryStream(); + var encoder = new TiffEncoderCore(Options, MemoryAllocator); - [Fact] - public void WriteHeader_ReturnsFirstIfdMarker() + using (var writer = new TiffStreamWriter(stream)) { - using var stream = new MemoryStream(); - var encoder = new TiffEncoderCore(Options, MemoryAllocator); - - using (var writer = new TiffStreamWriter(stream)) - { - long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); - Assert.Equal(4, firstIfdMarker); - } + long firstIfdMarker = TiffEncoderCore.WriteHeader(writer); + Assert.Equal(4, firstIfdMarker); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs index 809ae0d00c..86f45a3a33 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMultiframeTests.cs @@ -1,178 +1,175 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class TiffEncoderMultiframeTests : TiffEncoderBaseTester { - [Trait("Format", "Tiff")] - public class TiffEncoderMultiframeTests : TiffEncoderBaseTester - { - [Theory] - [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] - [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); - - [Theory] - [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_WithPreview(TestImageProvider provider) + [Theory] + [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - [Theory] - [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)] - [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Convert(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb); + [Theory] + [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] + [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_NotSupport(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb)); + + [Theory] + [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_WithPreview(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgb24)] + [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_Convert(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit48, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_RemoveFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Assert.True(image.Frames.Count > 1); - [Theory] - [WithFile(MultiframeLzwPredictor, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_RemoveFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - Assert.True(image.Frames.Count > 1); - - image.Frames.RemoveFrame(0); - - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; - var encoder = new TiffEncoder - { - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Deflate - }; - - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); - } + image.Frames.RemoveFrame(0); - [Theory] - [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_AddFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel + TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; + var encoder = new TiffEncoder { - using Image image = provider.GetImage(); - Assert.Equal(1, image.Frames.Count); + PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, + BitsPerPixel = bitsPerPixel, + Compression = TiffCompression.Deflate + }; + + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + ImageComparer.Exact); + } - using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + [Theory] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_AddFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + Assert.Equal(1, image.Frames.Count); - using var image2 = new Image(image.Width, image.Height, Color.Yellow.ToRgba32()); + using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); - image.Frames.AddFrame(image1.Frames.RootFrame); - image.Frames.AddFrame(image2.Frames.RootFrame); + using var image2 = new Image(image.Width, image.Height, Color.Yellow.ToRgba32()); - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; - var encoder = new TiffEncoder - { - PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Deflate - }; + image.Frames.AddFrame(image1.Frames.RootFrame); + image.Frames.AddFrame(image2.Frames.RootFrame); - using (var ms = new System.IO.MemoryStream()) - { - image.Save(ms, encoder); + TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit24; + var encoder = new TiffEncoder + { + PhotometricInterpretation = TiffPhotometricInterpretation.Rgb, + BitsPerPixel = bitsPerPixel, + Compression = TiffCompression.Deflate + }; - ms.Position = 0; - using var output = Image.Load(ms); + using (var ms = new System.IO.MemoryStream()) + { + image.Save(ms, encoder); - Assert.Equal(3, output.Frames.Count); + ms.Position = 0; + using var output = Image.Load(ms); - ImageFrame frame1 = output.Frames[1]; - ImageFrame frame2 = output.Frames[2]; + Assert.Equal(3, output.Frames.Count); - Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]); - Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]); + ImageFrame frame1 = output.Frames[1]; + ImageFrame frame2 = output.Frames[2]; - Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); + Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]); + Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]); - Assert.Equal(TiffPhotometricInterpretation.Rgb, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.Rgb, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); - } + Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); + Assert.Equal(TiffCompression.Deflate, frame1.Metadata.GetTiffMetadata().Compression); - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); + Assert.Equal(TiffPhotometricInterpretation.Rgb, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); + Assert.Equal(TiffPhotometricInterpretation.Rgb, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); } - [Theory] - [WithBlankImages(100, 100, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeMultiframe_Create(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + ImageComparer.Exact); + } - using var image0 = new Image(image.Width, image.Height, Color.Red.ToRgba32()); + [Theory] + [WithBlankImages(100, 100, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeMultiframe_Create(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); + using var image0 = new Image(image.Width, image.Height, Color.Red.ToRgba32()); - using var image2 = new Image(image.Width, image.Height, Color.Yellow.ToRgba32()); + using var image1 = new Image(image.Width, image.Height, Color.Green.ToRgba32()); - image.Frames.AddFrame(image0.Frames.RootFrame); - image.Frames.AddFrame(image1.Frames.RootFrame); - image.Frames.AddFrame(image2.Frames.RootFrame); - image.Frames.RemoveFrame(0); + using var image2 = new Image(image.Width, image.Height, Color.Yellow.ToRgba32()); - TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit8; - var encoder = new TiffEncoder - { - PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor, - BitsPerPixel = bitsPerPixel, - Compression = TiffCompression.Lzw - }; + image.Frames.AddFrame(image0.Frames.RootFrame); + image.Frames.AddFrame(image1.Frames.RootFrame); + image.Frames.AddFrame(image2.Frames.RootFrame); + image.Frames.RemoveFrame(0); - using (var ms = new System.IO.MemoryStream()) - { - image.Save(ms, encoder); + TiffBitsPerPixel bitsPerPixel = TiffBitsPerPixel.Bit8; + var encoder = new TiffEncoder + { + PhotometricInterpretation = TiffPhotometricInterpretation.PaletteColor, + BitsPerPixel = bitsPerPixel, + Compression = TiffCompression.Lzw + }; - ms.Position = 0; - using var output = Image.Load(ms); + using (var ms = new System.IO.MemoryStream()) + { + image.Save(ms, encoder); - Assert.Equal(3, output.Frames.Count); + ms.Position = 0; + using var output = Image.Load(ms); - ImageFrame frame0 = output.Frames[0]; - ImageFrame frame1 = output.Frames[1]; - ImageFrame frame2 = output.Frames[2]; + Assert.Equal(3, output.Frames.Count); - Assert.Equal(Color.Red.ToRgba32(), frame0[10, 10]); - Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]); - Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]); + ImageFrame frame0 = output.Frames[0]; + ImageFrame frame1 = output.Frames[1]; + ImageFrame frame2 = output.Frames[2]; - Assert.Equal(TiffCompression.Lzw, frame0.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); - Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); + Assert.Equal(Color.Red.ToRgba32(), frame0[10, 10]); + Assert.Equal(Color.Green.ToRgba32(), frame1[10, 10]); + Assert.Equal(Color.Yellow.ToRgba32(), frame2[10, 10]); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame0.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); - } + Assert.Equal(TiffCompression.Lzw, frame0.Metadata.GetTiffMetadata().Compression); + Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); + Assert.Equal(TiffCompression.Lzw, frame1.Metadata.GetTiffMetadata().Compression); - image.VerifyEncoder( - provider, - "tiff", - bitsPerPixel, - encoder, - ImageComparer.Exact); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame0.Metadata.GetTiffMetadata().PhotometricInterpretation); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame1.Metadata.GetTiffMetadata().PhotometricInterpretation); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame2.Metadata.GetTiffMetadata().PhotometricInterpretation); } + + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + ImageComparer.Exact); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 155f2a9f8b..eb421cf0ff 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -1,490 +1,486 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.PixelFormats; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class TiffEncoderTests : TiffEncoderBaseTester { - [Trait("Format", "Tiff")] - public class TiffEncoderTests : TiffEncoderBaseTester + [Theory] + [InlineData(null, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)] + [InlineData(TiffPhotometricInterpretation.WhiteIsZero, TiffBitsPerPixel.Bit8)] + //// Unsupported TiffPhotometricInterpretation should default to 24 bits + [InlineData(TiffPhotometricInterpretation.CieLab, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.ColorFilterArray, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.ItuLab, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.LinearRaw, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.Separated, TiffBitsPerPixel.Bit24)] + [InlineData(TiffPhotometricInterpretation.TransparencyMask, TiffBitsPerPixel.Bit24)] + public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel) + { + // arrange + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.None, frameMetaData.Compression); + } + + [Theory] + [InlineData(TiffBitsPerPixel.Bit24)] + [InlineData(TiffBitsPerPixel.Bit8)] + [InlineData(TiffBitsPerPixel.Bit4)] + [InlineData(TiffBitsPerPixel.Bit1)] + public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel) + { + // arrange + var tiffEncoder = new TiffEncoder { BitsPerPixel = bitsPerPixel }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(bitsPerPixel, frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.None, frameMetaData.Compression); + } + + [Theory] + [InlineData(TiffBitsPerPixel.Bit48)] + [InlineData(TiffBitsPerPixel.Bit42)] + [InlineData(TiffBitsPerPixel.Bit36)] + [InlineData(TiffBitsPerPixel.Bit30)] + [InlineData(TiffBitsPerPixel.Bit12)] + [InlineData(TiffBitsPerPixel.Bit10)] + [InlineData(TiffBitsPerPixel.Bit6)] + public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel) + { + // arrange + var tiffEncoder = new TiffEncoder { BitsPerPixel = bitsPerPixel }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); + } + + [Theory] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Ccitt1D)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup4Fax)] + public void EncoderOptions_WithInvalidCompressionAndPixelTypeCombination_DefaultsToRgb(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + { + // arrange + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); + } + + [Theory] + [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] + [InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] + [InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup4Fax)] + [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.Jpeg)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] + [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] + public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( + TiffPhotometricInterpretation? photometricInterpretation, + TiffCompression compression, + TiffBitsPerPixel expectedBitsPerPixel, + TiffCompression expectedCompression) + { + // arrange + var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel); + Assert.Equal(expectedCompression, rootFrameMetaData.Compression); + } + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit1)] + [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit24)] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit4)] + [WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] + public void TiffEncoder_PreservesBitsPerPixel(TestImageProvider provider, TiffBitsPerPixel expectedBitsPerPixel) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + } + + [Fact] + public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8() + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = new Image(10, 10); + using var memStream = new MemoryStream(); + TiffBitsPerPixel expectedBitsPerPixel = TiffBitsPerPixel.Bit8; + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)] + [WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)] + public void TiffEncoder_PreservesCompression(TestImageProvider provider, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel { - [Theory] - [InlineData(null, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffBitsPerPixel.Bit8)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffBitsPerPixel.Bit8)] - [InlineData(TiffPhotometricInterpretation.WhiteIsZero, TiffBitsPerPixel.Bit8)] - //// Unsupported TiffPhotometricInterpretation should default to 24 bits - [InlineData(TiffPhotometricInterpretation.CieLab, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.ColorFilterArray, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.ItuLab, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.LinearRaw, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.Separated, TiffBitsPerPixel.Bit24)] - [InlineData(TiffPhotometricInterpretation.TransparencyMask, TiffBitsPerPixel.Bit24)] - public void EncoderOptions_SetPhotometricInterpretation_Works(TiffPhotometricInterpretation? photometricInterpretation, TiffBitsPerPixel expectedBitsPerPixel) - { - // arrange - var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.None, frameMetaData.Compression); - } - - [Theory] - [InlineData(TiffBitsPerPixel.Bit24)] - [InlineData(TiffBitsPerPixel.Bit8)] - [InlineData(TiffBitsPerPixel.Bit4)] - [InlineData(TiffBitsPerPixel.Bit1)] - public void EncoderOptions_SetBitPerPixel_Works(TiffBitsPerPixel bitsPerPixel) - { - // arrange - var tiffEncoder = new TiffEncoder { BitsPerPixel = bitsPerPixel }; - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(bitsPerPixel, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.None, frameMetaData.Compression); - } - - [Theory] - [InlineData(TiffBitsPerPixel.Bit48)] - [InlineData(TiffBitsPerPixel.Bit42)] - [InlineData(TiffBitsPerPixel.Bit36)] - [InlineData(TiffBitsPerPixel.Bit30)] - [InlineData(TiffBitsPerPixel.Bit12)] - [InlineData(TiffBitsPerPixel.Bit10)] - [InlineData(TiffBitsPerPixel.Bit6)] - public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel) - { - // arrange - var tiffEncoder = new TiffEncoder { BitsPerPixel = bitsPerPixel }; - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); - } - - [Theory] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Ccitt1D)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup4Fax)] - public void EncoderOptions_WithInvalidCompressionAndPixelTypeCombination_DefaultsToRgb(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - { - // arrange - var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel); - } - - [Theory] - [InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffBitsPerPixel.Bit8, TiffCompression.Deflate)] - [InlineData(null, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits, TiffBitsPerPixel.Bit24, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits, TiffBitsPerPixel.Bit8, TiffCompression.PackBits)] - [InlineData(null, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffBitsPerPixel.Bit24, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffBitsPerPixel.Bit8, TiffCompression.Lzw)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup3Fax)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax, TiffBitsPerPixel.Bit1, TiffCompression.CcittGroup4Fax)] - [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.Jpeg)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] - [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] - public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( - TiffPhotometricInterpretation? photometricInterpretation, - TiffCompression compression, - TiffBitsPerPixel expectedBitsPerPixel, - TiffCompression expectedCompression) - { - // arrange - var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression }; - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata rootFrameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, rootFrameMetaData.BitsPerPixel); - Assert.Equal(expectedCompression, rootFrameMetaData.Compression); - } - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit1)] - [WithFile(GrayscaleUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit24)] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit4)] - [WithFile(RgbPalette, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32, TiffBitsPerPixel.Bit8)] - public void TiffEncoder_PreservesBitsPerPixel(TestImageProvider provider, TiffBitsPerPixel expectedBitsPerPixel) - where TPixel : unmanaged, IPixel - { - // arrange - var tiffEncoder = new TiffEncoder(); - using Image input = provider.GetImage(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); - } - - [Fact] - public void TiffEncoder_PreservesBitsPerPixel_WhenInputIsL8() - { - // arrange - var tiffEncoder = new TiffEncoder(); - using Image input = new Image(10, 10); - using var memStream = new MemoryStream(); - TiffBitsPerPixel expectedBitsPerPixel = TiffBitsPerPixel.Bit8; - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedBitsPerPixel, frameMetaData.BitsPerPixel); - } - - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.None)] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, TiffCompression.Lzw)] - [WithFile(RgbDeflate, PixelTypes.Rgba32, TiffCompression.Deflate)] - [WithFile(RgbPackbits, PixelTypes.Rgba32, TiffCompression.PackBits)] - public void TiffEncoder_PreservesCompression(TestImageProvider provider, TiffCompression expectedCompression) - where TPixel : unmanaged, IPixel - { - // arrange - var tiffEncoder = new TiffEncoder(); - using Image input = provider.GetImage(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - Assert.Equal(expectedCompression, output.Frames.RootFrame.Metadata.GetTiffMetadata().Compression); - } - - [Theory] - [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, null)] - [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] - [WithFile(RgbDeflate, PixelTypes.Rgba32, null)] - [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] - public void TiffEncoder_PreservesPredictor(TestImageProvider provider, TiffPredictor? expectedPredictor) - where TPixel : unmanaged, IPixel - { - // arrange - var tiffEncoder = new TiffEncoder(); - using Image input = provider.GetImage(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, tiffEncoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata frameMetadata = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(expectedPredictor, frameMetadata.Predictor); - } - - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] - [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] - public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffCompression compression, TiffCompression expectedCompression) - where TPixel : unmanaged, IPixel - { - // arrange - var encoder = new TiffEncoder() { Compression = compression, BitsPerPixel = TiffBitsPerPixel.Bit1 }; - using Image input = provider.GetImage(); - using var memStream = new MemoryStream(); - - // act - input.Save(memStream, encoder); - - // assert - memStream.Position = 0; - using var output = Image.Load(memStream); - TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - Assert.Equal(TiffBitsPerPixel.Bit1, frameMetaData.BitsPerPixel); - Assert.Equal(expectedCompression, frameMetaData.Compression); - } - - // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. - [Theory] - [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] - public void TiffEncoder_EncodePlanar_AndReload_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, imageDecoder: new TiffDecoder()); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeRgb_WithJpegCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); - - [Theory] - [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] - [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithDeflateCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithDeflateCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Deflate); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.PackBits); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup3Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup4Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Ccitt1D); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] - public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_BlackIsZero_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D); - - [Theory] - [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] - [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] - public void TiffEncoder_StripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - TestStripLength(provider, photometricInterpretation, compression); - - [Theory] - [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] - public void TiffEncoder_StripLength_WithPalette(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - TestStripLength(provider, photometricInterpretation, compression, false, 0.01f); - - [Theory] - [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] - public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) - where TPixel : unmanaged, IPixel => - //// CcittGroup3Fax compressed data length can be larger than the original length. - Assert.Throws(() => TestStripLength(provider, photometricInterpretation, compression)); - - [Theory] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)] - [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)] - public void TiffEncode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - using Image image = provider.GetImage(); - - var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; - image.DebugSave(provider, encoder); - } + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + Assert.Equal(expectedCompression, output.Frames.RootFrame.Metadata.GetTiffMetadata().Compression); + } + + [Theory] + [WithFile(RgbLzwNoPredictor, PixelTypes.Rgba32, null)] + [WithFile(RgbLzwPredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + [WithFile(RgbDeflate, PixelTypes.Rgba32, null)] + [WithFile(RgbDeflatePredictor, PixelTypes.Rgba32, TiffPredictor.Horizontal)] + public void TiffEncoder_PreservesPredictor(TestImageProvider provider, TiffPredictor? expectedPredictor) + where TPixel : unmanaged, IPixel + { + // arrange + var tiffEncoder = new TiffEncoder(); + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, tiffEncoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetadata = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(expectedPredictor, frameMetadata.Predictor); + } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffCompression.Ccitt1D, TiffCompression.Ccitt1D)] + public void TiffEncoder_EncodesWithCorrectBiColorModeCompression(TestImageProvider provider, TiffCompression compression, TiffCompression expectedCompression) + where TPixel : unmanaged, IPixel + { + // arrange + var encoder = new TiffEncoder() { Compression = compression, BitsPerPixel = TiffBitsPerPixel.Bit1 }; + using Image input = provider.GetImage(); + using var memStream = new MemoryStream(); + + // act + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffBitsPerPixel.Bit1, frameMetaData.BitsPerPixel); + Assert.Equal(expectedCompression, frameMetaData.Compression); + } + + // This makes sure, that when decoding a planar tiff, the planar configuration is not carried over to the encoded image. + [Theory] + [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] + public void TiffEncoder_EncodePlanar_AndReload_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, imageDecoder: new TiffDecoder()); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Lzw, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeRgb_WithJpegCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Lzw, TiffPredictor.Horizontal); + + [Theory] + [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeGray_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPalette, PixelTypes.Rgba32)] + [WithFile(Flower4BitPaletteGray, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffPhotometricInterpretation.PaletteColor, useExactComparer: false, compareTolerance: 0.003f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithPackBitsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.PackBits, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithDeflateCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithDeflateCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Deflate, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeColorPalette_WithLzwCompressionAndPredictor_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw, TiffPredictor.Horizontal, useExactComparer: false, compareTolerance: 0.001f); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithDeflateCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithDeflateCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Deflate); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithPackBitsCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.PackBits); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup3Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.CcittGroup4Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_WhiteIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.WhiteIsZero, TiffCompression.Ccitt1D); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)] + public void TiffEncoder_EncodeBiColor_WithModifiedHuffmanCompression_BlackIsZero_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D); + + [Theory] + [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] + [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + [WithFile(RgbUncompressed, PixelTypes.Rgb48, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] + public void TiffEncoder_StripLength(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + TestStripLength(provider, photometricInterpretation, compression); + + [Theory] + [WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)] + public void TiffEncoder_StripLength_WithPalette(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + TestStripLength(provider, photometricInterpretation, compression, false, 0.01f); + + [Theory] + [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] + public void TiffEncoder_StripLength_OutOfBounds(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) + where TPixel : unmanaged, IPixel => + //// CcittGroup3Fax compressed data length can be larger than the original length. + Assert.Throws(() => TestStripLength(provider, photometricInterpretation, compression)); + + [Theory] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)] + [WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)] + public void TiffEncode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TiffPhotometricInterpretation photometricInterpretation) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + using Image image = provider.GetImage(); + + var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation }; + image.DebugSave(provider, encoder); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs index 1e445bfd7e..2f5132ff8c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs @@ -2,23 +2,21 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Tiff; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class TiffFormatTests { - [Trait("Format", "Tiff")] - public class TiffFormatTests + [Fact] + public void FormatProperties_AreAsExpected() { - [Fact] - public void FormatProperties_AreAsExpected() - { - TiffFormat tiffFormat = TiffFormat.Instance; + TiffFormat tiffFormat = TiffFormat.Instance; - Assert.Equal("TIFF", tiffFormat.Name); - Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); - Assert.Contains("image/tiff", tiffFormat.MimeTypes); - Assert.Contains("tif", tiffFormat.FileExtensions); - Assert.Contains("tiff", tiffFormat.FileExtensions); - } + Assert.Equal("TIFF", tiffFormat.Name); + Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); + Assert.Contains("image/tiff", tiffFormat.MimeTypes); + Assert.Contains("tif", tiffFormat.FileExtensions); + Assert.Contains("tiff", tiffFormat.FileExtensions); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index c30a7b6c6f..8f4345b2e8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Tiff; @@ -13,294 +10,291 @@ using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; - -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff +namespace SixLabors.ImageSharp.Tests.Formats.Tiff; + +[Trait("Format", "Tiff")] +public class TiffMetadataTests { - [Trait("Format", "Tiff")] - public class TiffMetadataTests + private static TiffDecoder TiffDecoder => new(); + + private class NumberComparer : IEqualityComparer { - private static TiffDecoder TiffDecoder => new(); + public bool Equals(Number x, Number y) => x.Equals(y); - private class NumberComparer : IEqualityComparer + public int GetHashCode(Number obj) => obj.GetHashCode(); + } + + [Fact] + public void TiffMetadata_CloneIsDeep() + { + var meta = new TiffMetadata { - public bool Equals(Number x, Number y) => x.Equals(y); + ByteOrder = ByteOrder.BigEndian, + }; - public int GetHashCode(Number obj) => obj.GetHashCode(); - } + var clone = (TiffMetadata)meta.DeepClone(); - [Fact] - public void TiffMetadata_CloneIsDeep() - { - var meta = new TiffMetadata - { - ByteOrder = ByteOrder.BigEndian, - }; + clone.ByteOrder = ByteOrder.LittleEndian; - var clone = (TiffMetadata)meta.DeepClone(); + Assert.False(meta.ByteOrder == clone.ByteOrder); + } - clone.ByteOrder = ByteOrder.LittleEndian; + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); + var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); + VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); + + var clone = (TiffFrameMetadata)meta.DeepClone(); + + clone.BitsPerPixel = TiffBitsPerPixel.Bit8; + clone.Compression = TiffCompression.None; + clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; + clone.Predictor = TiffPredictor.Horizontal; + + Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); + Assert.False(meta.Compression == clone.Compression); + Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); + Assert.False(meta.Predictor == clone.Predictor); + } - Assert.False(meta.ByteOrder == clone.ByteOrder); - } + private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) + { + Assert.NotNull(frameMetaData); + Assert.NotNull(frameMetaData.BitsPerPixel); + Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); + Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); + } - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); - VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); - - var clone = (TiffFrameMetadata)meta.DeepClone(); - - clone.BitsPerPixel = TiffBitsPerPixel.Bit8; - clone.Compression = TiffCompression.None; - clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; - clone.Predictor = TiffPredictor.Horizontal; - - Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); - Assert.False(meta.Compression == clone.Compression); - Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); - Assert.False(meta.Predictor == clone.Predictor); - } + [Theory] + [InlineData(Calliphora_BiColorUncompressed, 1)] + [InlineData(GrayscaleUncompressed, 8)] + [InlineData(RgbUncompressed, 24)] + public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); - private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) - { - Assert.NotNull(frameMetaData); - Assert.NotNull(frameMetaData.BitsPerPixel); - Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); - Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); - } + IImageInfo imageInfo = Image.Identify(stream); - [Theory] - [InlineData(Calliphora_BiColorUncompressed, 1)] - [InlineData(GrayscaleUncompressed, 8)] - [InlineData(RgbUncompressed, 24)] - public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); + Assert.NotNull(imageInfo); + TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetadata); + Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); + } - IImageInfo imageInfo = Image.Identify(stream); + [Theory] + [InlineData(GrayscaleUncompressed, ByteOrder.BigEndian)] + [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] + public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); - Assert.NotNull(imageInfo); - TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetadata); - Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); - } + IImageInfo imageInfo = Image.Identify(stream); - [Theory] - [InlineData(GrayscaleUncompressed, ByteOrder.BigEndian)] - [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] - public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); + Assert.NotNull(imageInfo); + TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetadata); + Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); + } - IImageInfo imageInfo = Image.Identify(stream); + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] + [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] + public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(new TiffDecoder(), options); + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - Assert.NotNull(imageInfo); - TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetadata); - Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); + Assert.NotNull(meta); + if (ignoreMetadata) + { + Assert.Null(rootFrameMetaData.XmpProfile); + Assert.Null(rootFrameMetaData.ExifProfile); } - - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] - [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] - public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel + else { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(new TiffDecoder(), options); - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; - - Assert.NotNull(meta); - if (ignoreMetadata) - { - Assert.Null(rootFrameMetaData.XmpProfile); - Assert.Null(rootFrameMetaData.ExifProfile); - } - else - { - Assert.NotNull(rootFrameMetaData.XmpProfile); - Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); - Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); - } + Assert.NotNull(rootFrameMetaData.XmpProfile); + Assert.NotNull(rootFrameMetaData.ExifProfile); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); + Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } + } - [Theory] - [WithFile(InvalidIptcData, PixelTypes.Rgba32)] - public void CanDecodeImage_WithIptcDataAsLong(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); + [Theory] + [WithFile(InvalidIptcData, PixelTypes.Rgba32)] + public void CanDecodeImage_WithIptcDataAsLong(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); - IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; - Assert.NotNull(iptcProfile); - IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); - Assert.NotNull(byline); - Assert.Equal("Studio Mantyniemi", byline.Value); - } + IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; + Assert.NotNull(iptcProfile); + IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); + Assert.NotNull(byline); + Assert.Equal("Studio Mantyniemi", byline.Value); + } - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void BaselineTags(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - ImageFrame rootFrame = image.Frames.RootFrame; - Assert.Equal(32, rootFrame.Width); - Assert.Equal(32, rootFrame.Height); - Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); - - ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; - TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); - Assert.NotNull(exifProfile); - - // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData - // and removed from the profile on decode. - Assert.Equal(26, exifProfile.Values.Count); - Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); - Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); - Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); - Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); - Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); - Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); - Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); - Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); - var expectedResolution = new Rational(10000, 1000, simplify: false); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); - Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); - Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); - Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); - Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); - Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); - Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); - Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); - ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; - Assert.NotNull(colorMap); - Assert.Equal(48, colorMap.Length); - Assert.Equal(10537, colorMap[0]); - Assert.Equal(14392, colorMap[1]); - Assert.Equal(58596, colorMap[46]); - Assert.Equal(3855, colorMap[47]); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); - Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); - - ImageMetadata imageMetaData = image.Metadata; - Assert.NotNull(imageMetaData); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); - Assert.Equal(10, imageMetaData.HorizontalResolution); - Assert.Equal(10, imageMetaData.VerticalResolution); - - TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); - Assert.NotNull(tiffMetaData); - Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); - } + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void BaselineTags(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + ImageFrame rootFrame = image.Frames.RootFrame; + Assert.Equal(32, rootFrame.Width); + Assert.Equal(32, rootFrame.Height); + Assert.NotNull(rootFrame.Metadata.XmpProfile); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); + + ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; + TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); + Assert.NotNull(exifProfile); + + // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData + // and removed from the profile on decode. + Assert.Equal(26, exifProfile.Values.Count); + Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); + Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); + Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); + Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); + Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); + Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); + Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); + Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); + var expectedResolution = new Rational(10000, 1000, simplify: false); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); + Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); + Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); + Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); + Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); + Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); + Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); + Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); + ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; + Assert.NotNull(colorMap); + Assert.Equal(48, colorMap.Length); + Assert.Equal(10537, colorMap[0]); + Assert.Equal(14392, colorMap[1]); + Assert.Equal(58596, colorMap[46]); + Assert.Equal(3855, colorMap[47]); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); + Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); + + ImageMetadata imageMetaData = image.Metadata; + Assert.NotNull(imageMetaData); + Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); + Assert.Equal(10, imageMetaData.HorizontalResolution); + Assert.Equal(10, imageMetaData.VerticalResolution); + + TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffMetaData); + Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); + } - [Theory] - [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] - public void SubfileType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - Assert.NotNull(meta); + [Theory] + [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] + public void SubfileType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + TiffMetadata meta = image.Metadata.GetTiffMetadata(); + Assert.NotNull(meta); - Assert.Equal(2, image.Frames.Count); + Assert.Equal(2, image.Frames.Count); - ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[0].Width); - Assert.Equal(255, image.Frames[0].Height); + ExifProfile frame0Exif = image.Frames[0].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.FullImage, (TiffNewSubfileType)frame0Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[0].Width); + Assert.Equal(255, image.Frames[0].Height); - ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; - Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); - Assert.Equal(255, image.Frames[1].Width); - Assert.Equal(255, image.Frames[1].Height); - } + ExifProfile frame1Exif = image.Frames[1].Metadata.ExifProfile; + Assert.Equal(TiffNewSubfileType.Preview, (TiffNewSubfileType)frame1Exif.GetValue(ExifTag.SubfileType).Value); + Assert.Equal(255, image.Frames[1].Width); + Assert.Equal(255, image.Frames[1].Height); + } - [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32)] - public void Encode_PreservesMetadata(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Load Tiff image - DecoderOptions options = new() { SkipMetadata = false }; - using Image image = provider.GetImage(new TiffDecoder(), options); - - ImageMetadata inputMetaData = image.Metadata; - ImageFrame rootFrameInput = image.Frames.RootFrame; - TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); - XmpProfile xmpProfileInput = rootFrameInput.Metadata.XmpProfile; - ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; - - Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); - Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); - - // Save to Tiff - var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; - using var ms = new MemoryStream(); - image.Save(ms, tiffEncoder); - - // Assert - ms.Position = 0; - using var encodedImage = Image.Load(ms); - - ImageMetadata encodedImageMetaData = encodedImage.Metadata; - ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; - TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); - ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; - XmpProfile encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; - - Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); - Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); - - Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); - Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); - Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); - - Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); - Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); - - PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); - PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); - Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); - Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - - Assert.NotNull(xmpProfileInput); - Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); - - Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); - Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", exifProfileInput.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Авторские права", exifProfileInput.GetValue(ExifTag.Copyright).Value); - - Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); - - // Note that the encoded profile has PlanarConfiguration explicitly set, which is missing in the original image profile. - Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); - Assert.Equal(exifProfileInput.Values.Count + 1, encodedImageExifProfile.Values.Count); - } + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void Encode_PreservesMetadata(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Load Tiff image + DecoderOptions options = new() { SkipMetadata = false }; + using Image image = provider.GetImage(new TiffDecoder(), options); + + ImageMetadata inputMetaData = image.Metadata; + ImageFrame rootFrameInput = image.Frames.RootFrame; + TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); + XmpProfile xmpProfileInput = rootFrameInput.Metadata.XmpProfile; + ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; + + Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); + Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); + + // Save to Tiff + var tiffEncoder = new TiffEncoder() { PhotometricInterpretation = TiffPhotometricInterpretation.Rgb }; + using var ms = new MemoryStream(); + image.Save(ms, tiffEncoder); + + // Assert + ms.Position = 0; + using var encodedImage = Image.Load(ms); + + ImageMetadata encodedImageMetaData = encodedImage.Metadata; + ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; + TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); + ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; + XmpProfile encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; + + Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); + Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); + + Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); + Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); + Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); + + Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); + Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); + + PixelResolutionUnit resolutionUnitInput = UnitConverter.ExifProfileToResolutionUnit(exifProfileInput); + PixelResolutionUnit resolutionUnitEncoded = UnitConverter.ExifProfileToResolutionUnit(encodedImageExifProfile); + Assert.Equal(resolutionUnitInput, resolutionUnitEncoded); + Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + + Assert.NotNull(xmpProfileInput); + Assert.NotNull(encodedImageXmpProfile); + Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); + + Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); + Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", exifProfileInput.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Авторские права", exifProfileInput.GetValue(ExifTag.Copyright).Value); + + Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); + + // Note that the encoded profile has PlanarConfiguration explicitly set, which is missing in the original image profile. + Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); + Assert.Equal(exifProfileInput.Values.Count + 1, encodedImageExifProfile.Values.Count); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs index 253381985e..f6a1257f47 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs @@ -1,118 +1,114 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Writers; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils; -namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils +[Trait("Format", "Tiff")] +public class TiffWriterTests { - [Trait("Format", "Tiff")] - public class TiffWriterTests + [Fact] + public void IsLittleEndian_IsTrueOnWindows() { - [Fact] - public void IsLittleEndian_IsTrueOnWindows() - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - Assert.True(TiffStreamWriter.IsLittleEndian); - } + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + Assert.True(TiffStreamWriter.IsLittleEndian); + } - [Theory] - [InlineData(new byte[] { }, 0)] - [InlineData(new byte[] { 42 }, 1)] - [InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] - public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.Write(data); - Assert.Equal(writer.Position, expectedResult); - } + [Theory] + [InlineData(new byte[] { }, 0)] + [InlineData(new byte[] { 42 }, 1)] + [InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)] + public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult) + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(data); + Assert.Equal(writer.Position, expectedResult); + } - [Fact] - public void Write_WritesByte() - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.Write(42); + [Fact] + public void Write_WritesByte() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(42); - Assert.Equal(new byte[] { 42 }, stream.ToArray()); - } + Assert.Equal(new byte[] { 42 }, stream.ToArray()); + } - [Fact] - public void Write_WritesByteArray() - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.Write(new byte[] { 2, 4, 6, 8 }); + [Fact] + public void Write_WritesByteArray() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(new byte[] { 2, 4, 6, 8 }); - Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); - } + Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray()); + } - [Fact] - public void Write_WritesUInt16() - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.Write(1234); + [Fact] + public void Write_WritesUInt16() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(1234); - Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); - } + Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray()); + } - [Fact] - public void Write_WritesUInt32() - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.Write(12345678U); + [Fact] + public void Write_WritesUInt32() + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.Write(12345678U); - Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); - } + Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray()); + } - [Theory] - [InlineData(new byte[] { }, new byte[] { })] - [InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] - [InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] - [InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] - [InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] - [InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12, 0, 0 })] + [Theory] + [InlineData(new byte[] { }, new byte[] { })] + [InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })] + [InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })] + [InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })] + [InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })] + [InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12, 0, 0 })] - public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) - { - using var stream = new MemoryStream(); - using var writer = new TiffStreamWriter(stream); - writer.WritePadded(bytes); + public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult) + { + using var stream = new MemoryStream(); + using var writer = new TiffStreamWriter(stream); + writer.WritePadded(bytes); - Assert.Equal(expectedResult, stream.ToArray()); - } + Assert.Equal(expectedResult, stream.ToArray()); + } + + [Fact] + public void WriteMarker_WritesToPlacedPosition() + { + using var stream = new MemoryStream(); - [Fact] - public void WriteMarker_WritesToPlacedPosition() + using (var writer = new TiffStreamWriter(stream)) { - using var stream = new MemoryStream(); + writer.Write(0x11111111); + long marker = writer.PlaceMarker(); + writer.Write(0x33333333); - using (var writer = new TiffStreamWriter(stream)) - { - writer.Write(0x11111111); - long marker = writer.PlaceMarker(); - writer.Write(0x33333333); - - writer.WriteMarker(marker, 0x12345678); - - writer.Write(0x44444444); - } - - Assert.Equal( - new byte[] - { - 0x11, 0x11, 0x11, 0x11, - 0x78, 0x56, 0x34, 0x12, - 0x33, 0x33, 0x33, 0x33, - 0x44, 0x44, 0x44, 0x44 - }, - stream.ToArray()); + writer.WriteMarker(marker, 0x12345678); + + writer.Write(0x44444444); } + + Assert.Equal( + new byte[] + { + 0x11, 0x11, 0x11, 0x11, + 0x78, 0x56, 0x34, 0x12, + 0x33, 0x33, 0x33, 0x33, + 0x44, 0x44, 0x44, 0x44 + }, + stream.ToArray()); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs index 013674cfed..c5e8c975f1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs @@ -3,87 +3,85 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class ColorSpaceTransformUtilsTests { - [Trait("Format", "Webp")] - public class ColorSpaceTransformUtilsTests + private static void RunCollectColorBlueTransformsTest() { - private static void RunCollectColorBlueTransformsTest() - { - uint[] pixelData = - { - 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, - 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 - }; - - int[] expectedOutput = - { - 31, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - - int[] histo = new int[256]; - ColorSpaceTransformUtils.CollectColorBlueTransforms(pixelData, 0, 32, 1, 0, 0, histo); - - Assert.Equal(expectedOutput, histo); - } - - private static void RunCollectColorRedTransformsTest() + uint[] pixelData = { - uint[] pixelData = - { - 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, - 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 - }; + 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, + 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 + }; - int[] expectedOutput = - { - 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; + int[] expectedOutput = + { + 31, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + int[] histo = new int[256]; + ColorSpaceTransformUtils.CollectColorBlueTransforms(pixelData, 0, 32, 1, 0, 0, histo); + + Assert.Equal(expectedOutput, histo); + } - int[] histo = new int[256]; - ColorSpaceTransformUtils.CollectColorRedTransforms(pixelData, 0, 32, 1, 0, histo); + private static void RunCollectColorRedTransformsTest() + { + uint[] pixelData = + { + 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, + 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 + }; - Assert.Equal(expectedOutput, histo); - } + int[] expectedOutput = + { + 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 + }; + + int[] histo = new int[256]; + ColorSpaceTransformUtils.CollectColorRedTransforms(pixelData, 0, 32, 1, 0, histo); + + Assert.Equal(expectedOutput, histo); + } - [Fact] - public void CollectColorBlueTransforms_Works() => RunCollectColorBlueTransformsTest(); + [Fact] + public void CollectColorBlueTransforms_Works() => RunCollectColorBlueTransformsTest(); - [Fact] - public void CollectColorRedTransforms_Works() => RunCollectColorRedTransformsTest(); + [Fact] + public void CollectColorRedTransforms_Works() => RunCollectColorRedTransformsTest(); - [Fact] - public void CollectColorBlueTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.AllowAll); + [Fact] + public void CollectColorBlueTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.AllowAll); - [Fact] - public void CollectColorBlueTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableSSE41); + [Fact] + public void CollectColorBlueTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableSSE41); - [Fact] - public void CollectColorBlueTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableAVX2); + [Fact] + public void CollectColorBlueTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableAVX2); - [Fact] - public void CollectColorRedTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.AllowAll); + [Fact] + public void CollectColorRedTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.AllowAll); - [Fact] - public void CollectColorRedTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableSSE41); + [Fact] + public void CollectColorRedTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableSSE41); - [Fact] - public void CollectColorRedTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableAVX2); - } + [Fact] + public void CollectColorRedTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableAVX2); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs index 7071f8edbc..11c4bb62e7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/DominantCostRangeTests.cs @@ -2,78 +2,76 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Webp.Lossless; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class DominantCostRangeTests { - [Trait("Format", "Webp")] - public class DominantCostRangeTests + [Fact] + public void DominantCost_Constructor() { - [Fact] - public void DominantCost_Constructor() - { - var dominantCostRange = new DominantCostRange(); - Assert.Equal(0, dominantCostRange.LiteralMax); - Assert.Equal(double.MaxValue, dominantCostRange.LiteralMin); - Assert.Equal(0, dominantCostRange.RedMax); - Assert.Equal(double.MaxValue, dominantCostRange.RedMin); - Assert.Equal(0, dominantCostRange.BlueMax); - Assert.Equal(double.MaxValue, dominantCostRange.BlueMin); - } + var dominantCostRange = new DominantCostRange(); + Assert.Equal(0, dominantCostRange.LiteralMax); + Assert.Equal(double.MaxValue, dominantCostRange.LiteralMin); + Assert.Equal(0, dominantCostRange.RedMax); + Assert.Equal(double.MaxValue, dominantCostRange.RedMin); + Assert.Equal(0, dominantCostRange.BlueMax); + Assert.Equal(double.MaxValue, dominantCostRange.BlueMin); + } - [Fact] - public void UpdateDominantCostRange_Works() + [Fact] + public void UpdateDominantCostRange_Works() + { + // arrange + var dominantCostRange = new DominantCostRange(); + var histogram = new Vp8LHistogram(10) { - // arrange - var dominantCostRange = new DominantCostRange(); - var histogram = new Vp8LHistogram(10) - { - LiteralCost = 1.0d, - RedCost = 2.0d, - BlueCost = 3.0d - }; + LiteralCost = 1.0d, + RedCost = 2.0d, + BlueCost = 3.0d + }; - // act - dominantCostRange.UpdateDominantCostRange(histogram); + // act + dominantCostRange.UpdateDominantCostRange(histogram); - // assert - Assert.Equal(1.0d, dominantCostRange.LiteralMax); - Assert.Equal(1.0d, dominantCostRange.LiteralMin); - Assert.Equal(2.0d, dominantCostRange.RedMax); - Assert.Equal(2.0d, dominantCostRange.RedMin); - Assert.Equal(3.0d, dominantCostRange.BlueMax); - Assert.Equal(3.0d, dominantCostRange.BlueMin); - } + // assert + Assert.Equal(1.0d, dominantCostRange.LiteralMax); + Assert.Equal(1.0d, dominantCostRange.LiteralMin); + Assert.Equal(2.0d, dominantCostRange.RedMax); + Assert.Equal(2.0d, dominantCostRange.RedMin); + Assert.Equal(3.0d, dominantCostRange.BlueMax); + Assert.Equal(3.0d, dominantCostRange.BlueMin); + } - [Theory] - [InlineData(3, 19)] - [InlineData(4, 34)] - public void GetHistoBinIndex_Works(int partitions, int expectedIndex) + [Theory] + [InlineData(3, 19)] + [InlineData(4, 34)] + public void GetHistoBinIndex_Works(int partitions, int expectedIndex) + { + // arrange + var dominantCostRange = new DominantCostRange() + { + BlueMax = 253.4625, + BlueMin = 109.0, + LiteralMax = 285.0, + LiteralMin = 133.0, + RedMax = 191.0, + RedMin = 109.0 + }; + var histogram = new Vp8LHistogram(6) { - // arrange - var dominantCostRange = new DominantCostRange() - { - BlueMax = 253.4625, - BlueMin = 109.0, - LiteralMax = 285.0, - LiteralMin = 133.0, - RedMax = 191.0, - RedMin = 109.0 - }; - var histogram = new Vp8LHistogram(6) - { - LiteralCost = 247.0d, - RedCost = 112.0d, - BlueCost = 202.0d, - BitCost = 733.0d - }; - dominantCostRange.UpdateDominantCostRange(histogram); + LiteralCost = 247.0d, + RedCost = 112.0d, + BlueCost = 202.0d, + BitCost = 733.0d + }; + dominantCostRange.UpdateDominantCostRange(histogram); - // act - int binIndex = dominantCostRange.GetHistoBinIndex(histogram, partitions); + // act + int binIndex = dominantCostRange.GetHistoBinIndex(histogram, partitions); - // assert - Assert.Equal(expectedIndex, binIndex); - } + // assert + Assert.Equal(expectedIndex, binIndex); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs index 951d936078..737b023f1f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ImageExtensionsTests.cs @@ -1,156 +1,152 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class ImageExtensionsTests { - [Trait("Format", "Webp")] - public class ImageExtensionsTests + [Fact] + public void SaveAsWebp_Path() { - [Fact] - public void SaveAsWebp_Path() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); - string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); - - using (var image = new Image(10, 10)) - { - image.SaveAsWebp(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); + string file = Path.Combine(dir, "SaveAsWebp_Path.webp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsWebp(file); } - [Fact] - public async Task SaveAsWebpAsync_Path() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); - string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsWebpAsync(file); - } + [Fact] + public async Task SaveAsWebpAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTests)); + string file = Path.Combine(dir, "SaveAsWebpAsync_Path.webp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsWebpAsync(file); } - [Fact] - public void SaveAsWebp_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - image.SaveAsWebp(file, new WebpEncoder()); - } + [Fact] + public void SaveAsWebp_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsWebp_Path_Encoder.webp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsWebp(file, new WebpEncoder()); } - [Fact] - public async Task SaveAsWebpAsync_Path_Encoder() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); - string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsWebpAsync(file, new WebpEncoder()); - } + [Fact] + public async Task SaveAsWebpAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsWebpAsync_Path_Encoder.webp"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsWebpAsync(file, new WebpEncoder()); } - [Fact] - public void SaveAsWebp_Stream() + using (Image.Load(file, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); - - using (var image = new Image(10, 10)) - { - image.SaveAsWebp(memoryStream); - } + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsWebp_Stream() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsWebp(memoryStream); } - [Fact] - public async Task SaveAsWebpAsync_StreamAsync() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - await image.SaveAsWebpAsync(memoryStream); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public async Task SaveAsWebpAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsWebpAsync(memoryStream); } - [Fact] - public void SaveAsWebp_Stream_Encoder() - { - using var memoryStream = new MemoryStream(); + memoryStream.Position = 0; - using (var image = new Image(10, 10)) - { - image.SaveAsWebp(memoryStream, new WebpEncoder()); - } + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - memoryStream.Position = 0; + [Fact] + public void SaveAsWebp_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.SaveAsWebp(memoryStream, new WebpEncoder()); } - [Fact] - public async Task SaveAsWebpAsync_Stream_Encoder() + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) { - using var memoryStream = new MemoryStream(); + Assert.Equal("image/webp", mime.DefaultMimeType); + } + } - using (var image = new Image(10, 10)) - { - await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); - } + [Fact] + public async Task SaveAsWebpAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); - memoryStream.Position = 0; + using (var image = new Image(10, 10)) + { + await image.SaveAsWebpAsync(memoryStream, new WebpEncoder()); + } - using (Image.Load(memoryStream, out IImageFormat mime)) - { - Assert.Equal("image/webp", mime.DefaultMimeType); - } + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/webp", mime.DefaultMimeType); } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index eb7b4dc64b..1607e907bc 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -3,289 +3,287 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class LosslessUtilsTests { - [Trait("Format", "Webp")] - public class LosslessUtilsTests + private static void RunCombinedShannonEntropyTest() { - private static void RunCombinedShannonEntropyTest() - { - int[] x = { 3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 }; - int[] y = { 11, 12, 8, 3, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 }; - const float expected = 884.7585f; + int[] x = { 3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 }; + int[] y = { 11, 12, 8, 3, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 }; + const float expected = 884.7585f; - float actual = LosslessUtils.CombinedShannonEntropy(x, y); + float actual = LosslessUtils.CombinedShannonEntropy(x, y); - Assert.Equal(expected, actual, 5); - } + Assert.Equal(expected, actual, 5); + } - private static void RunSubtractGreenTest() + private static void RunSubtractGreenTest() + { + uint[] pixelData = { - uint[] pixelData = - { - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - }; - - uint[] expectedOutput = - { - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - }; - - LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + uint[] expectedOutput = + { + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + LosslessUtils.SubtractGreenFromBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } - private static void RunAddGreenToBlueAndRedTest() + private static void RunAddGreenToBlueAndRedTest() + { + uint[] pixelData = { - uint[] pixelData = - { - 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, - 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, - 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, - 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, - 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, - 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, - 4285163259, 4285228287, 4284901886 - }; - - uint[] expectedOutput = - { - 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, - 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, - 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, - 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, - 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, - 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, - 4291847777, 4291781731, 4291783015 - }; - - LosslessUtils.AddGreenToBlueAndRed(pixelData); - - Assert.Equal(expectedOutput, pixelData); - } + 4284188659, 4284254193, 4284318702, 4284187883, 4284318441, 4284383470, 4284318700, 4284124392, + 4283799012, 4283864546, 4284581610, 4285163264, 4284891926, 4284497945, 4284761620, 4284893965, + 4284828428, 4284959500, 4284959755, 4284828426, 4284960520, 4285289733, 4285159937, 4285292030, + 4285358077, 4285228030, 4284966398, 4285097213, 4285227773, 4285096956, 4285097470, 4285228540, + 4285163516, 4285425149, 4285294332, 4285228540, 4285228543, 4285163261, 4285163516, 4285032701, + 4284835841, 4284835584, 4284966140, 4285228029, 4284770300, 4285097214, 4285293819, 4285228795, + 4285163259, 4285228287, 4284901886 + }; + + uint[] expectedOutput = + { + 4293035898, 4293101432, 4292903793, 4292838511, 4292837995, 4292771950, 4292903791, 4293299316, + 4293563769, 4293629303, 4293363312, 4291913575, 4289282905, 4288692313, 4289349210, 4289809240, + 4289743703, 4289874775, 4289940567, 4289743701, 4290137943, 4290860378, 4291058267, 4291386715, + 4291583836, 4291715937, 4291585379, 4291650657, 4291650143, 4291584863, 4291716451, 4291847521, + 4291913571, 4292044130, 4291978850, 4291847521, 4291847524, 4291847779, 4291913571, 4291848293, + 4291651689, 4291585895, 4291519584, 4291715936, 4291520355, 4291650658, 4291847263, 4291913313, + 4291847777, 4291781731, 4291783015 + }; + + LosslessUtils.AddGreenToBlueAndRed(pixelData); + + Assert.Equal(expectedOutput, pixelData); + } - private static void RunTransformColorTest() + private static void RunTransformColorTest() + { + uint[] pixelData = { - uint[] pixelData = - { - 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, - 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, - 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, - 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, - 392450, 196861, 16712192, 16711680, 130564, 16451071 - }; - - var m = new Vp8LMultipliers() - { - GreenToBlue = 240, - GreenToRed = 232, - RedToBlue = 0 - }; + 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, + 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, + 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, + 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, + 392450, 196861, 16712192, 16711680, 130564, 16451071 + }; + + var m = new Vp8LMultipliers() + { + GreenToBlue = 240, + GreenToRed = 232, + RedToBlue = 0 + }; - uint[] expectedOutput = - { - 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, - 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, - 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, - 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, - 16711680, 65027, 16712962 - }; + uint[] expectedOutput = + { + 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, + 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, + 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, + 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, + 16711680, 65027, 16712962 + }; - LosslessUtils.TransformColor(m, pixelData, pixelData.Length); + LosslessUtils.TransformColor(m, pixelData, pixelData.Length); - Assert.Equal(expectedOutput, pixelData); - } + Assert.Equal(expectedOutput, pixelData); + } - private static void RunTransformColorInverseTest() + private static void RunTransformColorInverseTest() + { + uint[] pixelData = { - uint[] pixelData = - { - 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, - 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, - 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, - 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, - 16711680, 65027, 16712962 - }; - - var m = new Vp8LMultipliers() - { - GreenToBlue = 240, - GreenToRed = 232, - RedToBlue = 0 - }; + 100279, 65790, 16710907, 16712190, 130813, 65028, 131840, 264449, 133377, 65790, 61697, 15917319, + 14801924, 16317698, 591614, 394748, 16711935, 131072, 65792, 16711679, 328704, 656896, 132607, + 328703, 197120, 66563, 16646657, 196607, 130815, 16711936, 131587, 131326, 66049, 261632, 16711936, + 16776960, 3, 511, 65792, 16711938, 16580612, 65535, 65019, 327425, 16516097, 261377, 196861, 66049, + 16711680, 65027, 16712962 + }; + + var m = new Vp8LMultipliers() + { + GreenToBlue = 240, + GreenToRed = 232, + RedToBlue = 0 + }; - uint[] expectedOutput = - { - 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, - 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, - 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, - 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, - 392450, 196861, 16712192, 16711680, 130564, 16451071 - }; + uint[] expectedOutput = + { + 5998579, 65790, 130301, 16646653, 196350, 130565, 16712702, 16583164, 16452092, 65790, 782600, + 647446, 16571414, 16448771, 263931, 132601, 16711935, 131072, 511, 16711679, 132350, 329469, + 16647676, 132093, 66303, 16647169, 16515584, 196607, 196096, 16646655, 514, 131326, 16712192, + 327169, 16646655, 16776960, 3, 16712190, 511, 16646401, 16580612, 65535, 196092, 327425, 16319743, + 392450, 196861, 16712192, 16711680, 130564, 16451071 + }; - LosslessUtils.TransformColorInverse(m, pixelData); + LosslessUtils.TransformColorInverse(m, pixelData); - Assert.Equal(expectedOutput, pixelData); - } + Assert.Equal(expectedOutput, pixelData); + } - private static void RunPredictor11Test() + private static void RunPredictor11Test() + { + // arrange + uint[] topData = { 4278258949, 4278258949 }; + const uint left = 4294839812; + short[] scratch = new short[8]; + const uint expectedResult = 4294839812; + + // act + unsafe { - // arrange - uint[] topData = { 4278258949, 4278258949 }; - const uint left = 4294839812; - short[] scratch = new short[8]; - const uint expectedResult = 4294839812; - - // act - unsafe + fixed (uint* top = &topData[1]) { - fixed (uint* top = &topData[1]) - { - uint actual = LosslessUtils.Predictor11(left, top, scratch); + uint actual = LosslessUtils.Predictor11(left, top, scratch); - // assert - Assert.Equal(expectedResult, actual); - } + // assert + Assert.Equal(expectedResult, actual); } } + } - private static void RunPredictor12Test() - { - // arrange - uint[] topData = { 4294844413, 4294779388 }; - const uint left = 4294844413; - const uint expectedResult = 4294779388; + private static void RunPredictor12Test() + { + // arrange + uint[] topData = { 4294844413, 4294779388 }; + const uint left = 4294844413; + const uint expectedResult = 4294779388; - // act - unsafe + // act + unsafe + { + fixed (uint* top = &topData[1]) { - fixed (uint* top = &topData[1]) - { - uint actual = LosslessUtils.Predictor12(left, top); + uint actual = LosslessUtils.Predictor12(left, top); - // assert - Assert.Equal(expectedResult, actual); - } + // assert + Assert.Equal(expectedResult, actual); } } + } - private static void RunPredictor13Test() - { - // arrange - uint[] topData = { 4278193922, 4278193666 }; - const uint left = 4278193410; - const uint expectedResult = 4278193154; + private static void RunPredictor13Test() + { + // arrange + uint[] topData = { 4278193922, 4278193666 }; + const uint left = 4278193410; + const uint expectedResult = 4278193154; - // act - unsafe + // act + unsafe + { + fixed (uint* top = &topData[1]) { - fixed (uint* top = &topData[1]) - { - uint actual = LosslessUtils.Predictor13(left, top); + uint actual = LosslessUtils.Predictor13(left, top); - // assert - Assert.Equal(expectedResult, actual); - } + // assert + Assert.Equal(expectedResult, actual); } } + } - [Fact] - public void CombinedShannonEntropy_Works() => RunCombinedShannonEntropyTest(); + [Fact] + public void CombinedShannonEntropy_Works() => RunCombinedShannonEntropyTest(); - [Fact] - public void Predictor11_Works() => RunPredictor11Test(); + [Fact] + public void Predictor11_Works() => RunPredictor11Test(); - [Fact] - public void Predictor12_Works() => RunPredictor12Test(); + [Fact] + public void Predictor12_Works() => RunPredictor12Test(); - [Fact] - public void Predictor13_Works() => RunPredictor13Test(); + [Fact] + public void Predictor13_Works() => RunPredictor13Test(); - [Fact] - public void SubtractGreen_Works() => RunSubtractGreenTest(); + [Fact] + public void SubtractGreen_Works() => RunSubtractGreenTest(); - [Fact] - public void AddGreenToBlueAndRed_Works() => RunAddGreenToBlueAndRedTest(); + [Fact] + public void AddGreenToBlueAndRed_Works() => RunAddGreenToBlueAndRedTest(); - [Fact] - public void TransformColor_Works() => RunTransformColorTest(); + [Fact] + public void TransformColor_Works() => RunTransformColorTest(); - [Fact] - public void TransformColorInverse_Works() => RunTransformColorInverseTest(); + [Fact] + public void TransformColorInverse_Works() => RunTransformColorInverseTest(); - [Fact] - public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll); + [Fact] + public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll); - [Fact] - public void CombinedShannonEntropy_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.DisableAVX2); + [Fact] + public void CombinedShannonEntropy_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.DisableAVX2); - [Fact] - public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); + [Fact] + public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); - [Fact] - public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2); - [Fact] - public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll); + [Fact] + public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll); - [Fact] - public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2); - [Fact] - public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll); + [Fact] + public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll); - [Fact] - public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2); - [Fact] - public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); + [Fact] + public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); - [Fact] - public void SubtractGreen_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2); + [Fact] + public void SubtractGreen_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2); - [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); + [Fact] + public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); - [Fact] - public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); + [Fact] + public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); - [Fact] - public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2); + [Fact] + public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2); - [Fact] - public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + [Fact] + public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); - [Fact] - public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); + [Fact] + public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); - [Fact] - public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); + [Fact] + public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); - [Fact] - public void TransformColor_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableAVX2); + [Fact] + public void TransformColor_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableAVX2); - [Fact] - public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); + [Fact] + public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); - [Fact] - public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); + [Fact] + public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); - [Fact] - public void TransformColorInverse_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableAVX2); - } + [Fact] + public void TransformColorInverse_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableAVX2); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 419d6e2665..69b503b5e5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -1,355 +1,352 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class LossyUtilsTests { - [Trait("Format", "Webp")] - public class LossyUtilsTests + private static void RunTransformTwoTest() { - private static void RunTransformTwoTest() + // arrange + short[] src = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = { - // arrange - short[] src = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - byte[] dst = - { - 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, - 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, - 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, - 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, - 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, - 0, 0, 0, 0, 0, 0, 0, 0 - }; - byte[] expected = - { - 105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, - 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169, - 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, - 105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, - 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169, - 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0 - }; - int[] scratch = new int[16]; - - // act - LossyUtils.TransformTwo(src, dst, scratch); - - // assert - Assert.True(expected.SequenceEqual(dst)); - } - - private static void RunTransformOneTest() + 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, + 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, + 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, + 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, + 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, + 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + byte[] expected = { - // arrange - short[] src = { -176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - byte[] dst = - { - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, - 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129 - }; - byte[] expected = - { - 111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, - 0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101, - 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 129 - }; - int[] scratch = new int[16]; - - // act - LossyUtils.TransformOne(src, dst, scratch); - - // assert - Assert.True(expected.SequenceEqual(dst)); - } - - private static void RunVp8Sse16X16Test() + 105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, + 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169, + 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, + 105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, + 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169, + 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformTwo(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + + private static void RunTransformOneTest() + { + // arrange + short[] src = { -176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = { - // arrange - byte[] a = - { - 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, - 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, - 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, - 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, - 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, - 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, - 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, - 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, - 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, - 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, - 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, - 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, - 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, - 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, - 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, - 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, - 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, - 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, - 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - }; - - byte[] b = - { - 150, 150, 150, 150, 146, 149, 152, 154, 164, 166, 154, 132, 99, 92, 106, 112, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, - 161, 164, 151, 130, 93, 86, 100, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, 93, 86, 100, 106, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, - 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 148, 148, 148, 148, 149, 158, 162, 159, 155, 155, 153, 129, - 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 154, 154, 154, 156, 161, 159, 152, - 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, 94, 87, 101, 106, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 153, 157, 162, - 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 152, 156, 158, 157, 140, 137, 145, 159, 155, 160, 150, 131, - 89, 88, 102, 101, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 153, 161, 160, 149, 118, 128, 147, 162, 155, 160, 150, 131, 86, 85, 99, 98, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 165, 161, 144, 96, 128, 154, 159, 155, - 160, 150, 131, 83, 82, 97, 96, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 161, 160, 149, 105, 78, 127, 156, 170, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 160, 160, 133, 85, 81, 129, 155, - 167, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 156, 147, 109, 76, 85, 130, 153, 163, 156, 156, 154, 130, 81, 77, 95, 102, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 128, 87, 83, - 88, 132, 152, 159, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204 - }; - - int expected = 2063; - - // act - int actual = LossyUtils.Vp8_Sse16X16(a, b); - - // assert - Assert.Equal(expected, actual); - } - - private static void RunVp8Sse16X8Test() + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + byte[] expected = { - // arrange - byte[] a = - { - 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, - 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, - 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, - 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, - 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, - 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, - 154, 154, 151, 132, 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, - 171, 178, 172, 176, 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, - 102, 100, 107, 100, 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, - 160, 162, 161, 153, 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, - 171, 179, 178, 172, 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, - 86, 86, 102, 105, 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, - 154, 152, 158, 163, 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105 - }; - - byte[] b = - { - 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, - 146, 149, 152, 154, 161, 164, 151, 130, 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, - 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, - 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, - 150, 150, 150, 150, 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 103, 103, 103, 103, - 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 148, 148, 148, 148, 149, 158, 162, 159, - 155, 155, 153, 129, 94, 87, 101, 106, 102, 100, 100, 102, 100, 101, 120, 122, 170, 176, 176, 170, - 174, 180, 171, 177, 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, - 102, 105, 105, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, 154, 154, 154, 154, - 156, 161, 159, 152, 155, 155, 153, 129, 94, 87, 101, 106, 102, 112, 112, 102, 100, 101, 120, 122, - 170, 176, 176, 170, 174, 180, 171, 177, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, - 94, 87, 101, 106, 102, 117, 117, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, - 152, 153, 157, 162, 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104 - }; - - int expected = 749; - - // act - int actual = LossyUtils.Vp8_Sse16X8(a, b); - - // assert - Assert.Equal(expected, actual); - } - - private static void RunVp8Sse4X4Test() + 111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformOne(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + + private static void RunVp8Sse16X16Test() + { + // arrange + byte[] a = { - // arrange - byte[] a = - { - 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, - 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, - 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, - 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, - 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128 - }; - - byte[] b = - { - 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, - 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 - }; - - int expected = 27; - - // act - int actual = LossyUtils.Vp8_Sse4X4(a, b); - - // assert - Assert.Equal(expected, actual); - } - - private static void RunMean16x4Test() + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + + byte[] b = { - // arrange - byte[] input = - { - 154, 145, 102, 115, 127, 129, 126, 125, 126, 120, 133, 152, 157, 153, 119, 94, 104, 116, 111, 113, - 113, 109, 105, 124, 173, 175, 177, 170, 175, 172, 166, 164, 151, 141, 99, 114, 125, 126, 135, 150, - 133, 115, 127, 149, 141, 168, 100, 54, 110, 117, 115, 116, 119, 115, 117, 130, 174, 174, 174, 157, - 146, 171, 166, 158, 117, 140, 96, 111, 119, 119, 136, 171, 188, 134, 121, 126, 136, 119, 59, 77, - 109, 115, 113, 120, 120, 117, 128, 115, 174, 173, 173, 161, 152, 148, 153, 162, 105, 140, 96, 114, - 115, 122, 141, 173, 190, 190, 142, 106, 151, 78, 66, 141, 110, 117, 123, 136, 118, 124, 127, 114, - 173, 175, 166, 155, 155, 159, 159, 158 - }; - uint[] dc = new uint[4]; - uint[] expectedDc = { 1940, 2139, 2252, 1813 }; - - // act - LossyUtils.Mean16x4(input, dc); - - // assert - Assert.True(dc.SequenceEqual(expectedDc)); - } - - private static void RunHadamardTransformTest() + 150, 150, 150, 150, 146, 149, 152, 154, 164, 166, 154, 132, 99, 92, 106, 112, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, + 161, 164, 151, 130, 93, 86, 100, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, 93, 86, 100, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, + 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 148, 148, 148, 148, 149, 158, 162, 159, 155, 155, 153, 129, + 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 154, 154, 154, 156, 161, 159, 152, + 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, 94, 87, 101, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 153, 157, 162, + 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 152, 156, 158, 157, 140, 137, 145, 159, 155, 160, 150, 131, + 89, 88, 102, 101, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 153, 161, 160, 149, 118, 128, 147, 162, 155, 160, 150, 131, 86, 85, 99, 98, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 165, 161, 144, 96, 128, 154, 159, 155, + 160, 150, 131, 83, 82, 97, 96, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 161, 160, 149, 105, 78, 127, 156, 170, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 160, 160, 133, 85, 81, 129, 155, + 167, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 156, 147, 109, 76, 85, 130, 153, 163, 156, 156, 154, 130, 81, 77, 95, 102, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 128, 87, 83, + 88, 132, 152, 159, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204 + }; + + int expected = 2063; + + // act + int actual = LossyUtils.Vp8_Sse16X16(a, b); + + // assert + Assert.Equal(expected, actual); + } + + private static void RunVp8Sse16X8Test() + { + // arrange + byte[] a = { - // arrange - byte[] a = - { - 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, - 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, - 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, - 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, - 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27 - }; + 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, + 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, + 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, + 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, + 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, + 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, + 154, 154, 151, 132, 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, + 171, 178, 172, 176, 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, + 102, 100, 107, 100, 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, + 160, 162, 161, 153, 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, + 171, 179, 178, 172, 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, + 86, 86, 102, 105, 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, + 154, 152, 158, 163, 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105 + }; + + byte[] b = + { + 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, + 146, 149, 152, 154, 161, 164, 151, 130, 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, + 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, + 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, + 150, 150, 150, 150, 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 103, 103, 103, 103, + 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 148, 148, 148, 148, 149, 158, 162, 159, + 155, 155, 153, 129, 94, 87, 101, 106, 102, 100, 100, 102, 100, 101, 120, 122, 170, 176, 176, 170, + 174, 180, 171, 177, 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, + 102, 105, 105, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, 154, 154, 154, 154, + 156, 161, 159, 152, 155, 155, 153, 129, 94, 87, 101, 106, 102, 112, 112, 102, 100, 101, 120, 122, + 170, 176, 176, 170, 174, 180, 171, 177, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, + 94, 87, 101, 106, 102, 117, 117, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, + 152, 153, 157, 162, 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104 + }; + + int expected = 749; + + // act + int actual = LossyUtils.Vp8_Sse16X8(a, b); + + // assert + Assert.Equal(expected, actual); + } - byte[] b = - { - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 - }; + private static void RunVp8Sse4X4Test() + { + // arrange + byte[] a = + { + 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, + 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, + 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, + 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, + 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128 + }; + + byte[] b = + { + 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, + 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; - ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; - int expected = 2; + int expected = 27; - // act - int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]); + // act + int actual = LossyUtils.Vp8_Sse4X4(a, b); - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } + + private static void RunMean16x4Test() + { + // arrange + byte[] input = + { + 154, 145, 102, 115, 127, 129, 126, 125, 126, 120, 133, 152, 157, 153, 119, 94, 104, 116, 111, 113, + 113, 109, 105, 124, 173, 175, 177, 170, 175, 172, 166, 164, 151, 141, 99, 114, 125, 126, 135, 150, + 133, 115, 127, 149, 141, 168, 100, 54, 110, 117, 115, 116, 119, 115, 117, 130, 174, 174, 174, 157, + 146, 171, 166, 158, 117, 140, 96, 111, 119, 119, 136, 171, 188, 134, 121, 126, 136, 119, 59, 77, + 109, 115, 113, 120, 120, 117, 128, 115, 174, 173, 173, 161, 152, 148, 153, 162, 105, 140, 96, 114, + 115, 122, 141, 173, 190, 190, 142, 106, 151, 78, 66, 141, 110, 117, 123, 136, 118, 124, 127, 114, + 173, 175, 166, 155, 155, 159, 159, 158 + }; + uint[] dc = new uint[4]; + uint[] expectedDc = { 1940, 2139, 2252, 1813 }; + + // act + LossyUtils.Mean16x4(input, dc); + + // assert + Assert.True(dc.SequenceEqual(expectedDc)); + } + + private static void RunHadamardTransformTest() + { + // arrange + byte[] a = + { + 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, + 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 29, 29, 28, + 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 27, 27, 26, + 26, 26, 26, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, 129, 128, + 128, 128, 128, 128, 128, 128, 128, 28, 27, 27, 26, 26, 27, 27, 28, 27, 28, 28, 29, 29, 28, 28, 27 + }; + + byte[] b = + { + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 + }; - [Fact] - public void RunTransformTwo_Works() => RunTransformTwoTest(); + ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; + int expected = 2; - [Fact] - public void RunTransformOne_Works() => RunTransformOneTest(); + // act + int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]); - [Fact] - public void Vp8Sse16X16_Works() => RunVp8Sse16X16Test(); + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void Vp8Sse16X8_Works() => RunVp8Sse16X8Test(); + [Fact] + public void RunTransformTwo_Works() => RunTransformTwoTest(); - [Fact] - public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); + [Fact] + public void RunTransformOne_Works() => RunTransformOneTest(); - [Fact] - public void Mean16x4_Works() => RunMean16x4Test(); + [Fact] + public void Vp8Sse16X16_Works() => RunVp8Sse16X16Test(); - [Fact] - public void HadamardTransform_Works() => RunHadamardTransformTest(); + [Fact] + public void Vp8Sse16X8_Works() => RunVp8Sse16X8Test(); - [Fact] - public void TransformTwo_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll); + [Fact] + public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); - [Fact] - public void TransformTwo_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void Mean16x4_Works() => RunMean16x4Test(); - [Fact] - public void TransformOne_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll); + [Fact] + public void HadamardTransform_Works() => RunHadamardTransformTest(); - [Fact] - public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void TransformTwo_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll); - [Fact] - public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); + [Fact] + public void TransformTwo_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); + [Fact] + public void TransformOne_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll); - [Fact] - public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); + [Fact] + public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); + [Fact] + public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); - [Fact] - public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); - [Fact] - public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); + [Fact] + public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); - [Fact] - public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); + [Fact] + public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); - [Fact] - public void Vp8Sse4X4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); - [Fact] - public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); + [Fact] + public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); - [Fact] - public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); + [Fact] + public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); - [Fact] - public void Mean16x4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void Vp8Sse4X4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2); - [Fact] - public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); + [Fact] + public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); - [Fact] - public void HadamardTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic); - } + [Fact] + public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); + + [Fact] + public void Mean16x4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void HadamardTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void HadamardTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunHadamardTransformTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index d6c2f415e2..f0961de6bb 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -1,164 +1,160 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class PredictorEncoderTests { - [Trait("Format", "Webp")] - public class PredictorEncoderTests - { - [Fact] - public static void ColorSpaceTransform_WithBikeImage_ProducesExpectedData() - => RunColorSpaceTransformTestWithBikeImage(); + [Fact] + public static void ColorSpaceTransform_WithBikeImage_ProducesExpectedData() + => RunColorSpaceTransformTestWithBikeImage(); - [Fact] - public static void ColorSpaceTransform_WithPeakImage_ProducesExpectedData() - => RunColorSpaceTransformTestWithPeakImage(); + [Fact] + public static void ColorSpaceTransform_WithPeakImage_ProducesExpectedData() + => RunColorSpaceTransformTestWithPeakImage(); - [Fact] - public void ColorSpaceTransform_WithPeakImage_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); + [Fact] + public void ColorSpaceTransform_WithPeakImage_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.AllowAll); - [Fact] - public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); + [Fact] + public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); + [Fact] + public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.AllowAll); - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); + [Fact] + public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); - [Fact] - public void ColorSpaceTransform_WithBikeImage_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableAVX2); + [Fact] + public void ColorSpaceTransform_WithBikeImage_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableAVX2); - // Test image: Input\Webp\peak.png - private static void RunColorSpaceTransformTestWithPeakImage() + // Test image: Input\Webp\peak.png + private static void RunColorSpaceTransformTestWithPeakImage() + { + // arrange + uint[] expectedData = { - // arrange - uint[] expectedData = - { - 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, - 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, - 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, - 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, - 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, - 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, - 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, - 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, - 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, - 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, - 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, - 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, - 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, - 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, - 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, - 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, - 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, - 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, - 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, - 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, - 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, - 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, - 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, - 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, - 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, - 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 - }; - - // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Peak)); - using var image = Image.Load(imgBytes); - uint[] bgra = ToBgra(image); - - const int colorTransformBits = 3; - int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - uint[] transformData = new uint[transformWidth * transformHeight]; - int[] scratch = new int[256]; - - // act - PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); - - // assert - Assert.Equal(expectedData, transformData); - } - - private static void RunColorSpaceTransformTestWithBikeImage() + 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, + 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, + 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, + 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, + 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, + 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, + 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, + 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, + 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, + 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, + 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, + 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, + 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, + 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, + 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, + 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, + 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, + 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, + 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, + 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, + 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, + 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, + 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, + 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, + 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, + 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 + }; + + // Convert image pixels to bgra array. + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Peak)); + using var image = Image.Load(imgBytes); + uint[] bgra = ToBgra(image); + + const int colorTransformBits = 3; + int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); + uint[] transformData = new uint[transformWidth * transformHeight]; + int[] scratch = new int[256]; + + // act + PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); + + // assert + Assert.Equal(expectedData, transformData); + } + + private static void RunColorSpaceTransformTestWithBikeImage() + { + // arrange + uint[] expectedData = { - // arrange - uint[] expectedData = - { - 4278714368, 4278192876, 4278198304, 4278198304, 4278190304, 4278190080, 4278190080, 4278198272, - 4278197760, 4278198816, 4278197794, 4278197774, 4278190080, 4278190080, 4278198816, 4278197281, - 4278197280, 4278197792, 4278200353, 4278191343, 4278190304, 4294713873, 4278198784, 4294844416, - 4278201578, 4278200044, 4278191343, 4278190288, 4294705200, 4294717139, 4278203628, 4278201064, - 4278201586, 4278197792, 4279240909 - }; - - // Convert image pixels to bgra array. - byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); - using var image = Image.Load(imgBytes); - uint[] bgra = ToBgra(image); - - const int colorTransformBits = 4; - int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); - int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); - uint[] transformData = new uint[transformWidth * transformHeight]; - int[] scratch = new int[256]; - - // act - PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); - - // assert - Assert.Equal(expectedData, transformData); - } - - private static uint[] ToBgra(Image image) - where TPixel : unmanaged, IPixel + 4278714368, 4278192876, 4278198304, 4278198304, 4278190304, 4278190080, 4278190080, 4278198272, + 4278197760, 4278198816, 4278197794, 4278197774, 4278190080, 4278190080, 4278198816, 4278197281, + 4278197280, 4278197792, 4278200353, 4278191343, 4278190304, 4294713873, 4278198784, 4294844416, + 4278201578, 4278200044, 4278191343, 4278190288, 4294705200, 4294717139, 4278203628, 4278201064, + 4278201586, 4278197792, 4279240909 + }; + + // Convert image pixels to bgra array. + byte[] imgBytes = File.ReadAllBytes(TestImageFullPath(TestImages.Webp.Lossy.BikeSmall)); + using var image = Image.Load(imgBytes); + uint[] bgra = ToBgra(image); + + const int colorTransformBits = 4; + int transformWidth = LosslessUtils.SubSampleSize(image.Width, colorTransformBits); + int transformHeight = LosslessUtils.SubSampleSize(image.Height, colorTransformBits); + uint[] transformData = new uint[transformWidth * transformHeight]; + int[] scratch = new int[256]; + + // act + PredictorEncoder.ColorSpaceTransform(image.Width, image.Height, colorTransformBits, 75, bgra, transformData, scratch); + + // assert + Assert.Equal(expectedData, transformData); + } + + private static uint[] ToBgra(Image image) + where TPixel : unmanaged, IPixel + { + uint[] bgra = new uint[image.Width * image.Height]; + image.ProcessPixelRows(accessor => { - uint[] bgra = new uint[image.Width * image.Height]; - image.ProcessPixelRows(accessor => + int idx = 0; + for (int y = 0; y < accessor.Height; y++) { - int idx = 0; - for (int y = 0; y < accessor.Height; y++) + Span rowSpan = accessor.GetRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) { - Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) - { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; - } + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; } - }); + } + }); - return bgra; - } + return bgra; + } - private static Bgra32 ToBgra32(TPixel color) - where TPixel : unmanaged, IPixel - { - Rgba32 rgba = default; - color.ToRgba32(ref rgba); - var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); - return bgra; - } - - private static string TestImageFullPath(string path) - => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + private static Bgra32 ToBgra32(TPixel color) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + var bgra = new Bgra32(rgba.R, rgba.G, rgba.B, rgba.A); + return bgra; } + + private static string TestImageFullPath(string path) + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 0e4630fdb7..59691204be 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class QuantEncTests { - [Trait("Format", "Webp")] - public class QuantEncTests + private static unsafe void RunQuantizeBlockTest() { - private static unsafe void RunQuantizeBlockTest() + // arrange + short[] input = { 378, 777, -851, 888, 259, 148, 0, -111, -185, -185, -74, -37, 148, 74, 111, 74 }; + short[] output = new short[16]; + ushort[] q = { 42, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37 }; + ushort[] iq = { 3120, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542 }; + uint[] bias = { 49152, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296 }; + uint[] zthresh = { 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21 }; + short[] expectedOutput = { 9, 21, 7, -5, 4, -23, 24, 0, -5, 4, 2, -2, -3, -1, 3, 2 }; + int expectedResult = 1; + Vp8Matrix vp8Matrix = default; + for (int i = 0; i < 16; i++) { - // arrange - short[] input = { 378, 777, -851, 888, 259, 148, 0, -111, -185, -185, -74, -37, 148, 74, 111, 74 }; - short[] output = new short[16]; - ushort[] q = { 42, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37 }; - ushort[] iq = { 3120, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542, 3542 }; - uint[] bias = { 49152, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296, 55296 }; - uint[] zthresh = { 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21 }; - short[] expectedOutput = { 9, 21, 7, -5, 4, -23, 24, 0, -5, 4, 2, -2, -3, -1, 3, 2 }; - int expectedResult = 1; - Vp8Matrix vp8Matrix = default; - for (int i = 0; i < 16; i++) - { - vp8Matrix.Q[i] = q[i]; - vp8Matrix.IQ[i] = iq[i]; - vp8Matrix.Bias[i] = bias[i]; - vp8Matrix.ZThresh[i] = zthresh[i]; - } - - // act - int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix); - - // assert - Assert.True(output.SequenceEqual(expectedOutput)); - Assert.Equal(expectedResult, actualResult); + vp8Matrix.Q[i] = q[i]; + vp8Matrix.IQ[i] = iq[i]; + vp8Matrix.Bias[i] = bias[i]; + vp8Matrix.ZThresh[i] = zthresh[i]; } - [Fact] - public void QuantizeBlock_Works() => RunQuantizeBlockTest(); + // act + int actualResult = QuantEnc.QuantizeBlock(input, output, ref vp8Matrix); - [Fact] - public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); + // assert + Assert.True(output.SequenceEqual(expectedOutput)); + Assert.Equal(expectedResult, actualResult); + } - [Fact] - public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + [Fact] + public void QuantizeBlock_Works() => RunQuantizeBlockTest(); - [Fact] - public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); - } + [Fact] + public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); + + [Fact] + public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 2923d1aaa6..dab0716927 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -1,165 +1,162 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class Vp8EncodingTests { - [Trait("Format", "Webp")] - public class Vp8EncodingTests + private static void RunFTransform2Test() + { + // arrange + byte[] src = { 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175 }; + byte[] reference = { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 }; + short[] actualOutput1 = new short[16]; + short[] actualOutput2 = new short[16]; + short[] expectedOutput1 = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + short[] expectedOutput2 = { 192, -34, 10, 1, -11, 8, 10, -7, 6, 3, -8, 4, 5, -3, -2, 6 }; + + // act + Vp8Encoding.FTransform2(src, reference, actualOutput1, actualOutput2, new int[16]); + + // assert + Assert.True(expectedOutput1.SequenceEqual(actualOutput1)); + Assert.True(expectedOutput2.SequenceEqual(actualOutput2)); + } + + private static void RunFTransformTest() + { + // arrange + byte[] src = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175 + }; + byte[] reference = + { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129 + }; + short[] actualOutput = new short[16]; + short[] expectedOutput = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + + // act + Vp8Encoding.FTransform(src, reference, actualOutput, new int[16]); + + // assert + Assert.True(expectedOutput.SequenceEqual(actualOutput)); + } + + private static void RunOneInverseTransformTest() { - private static void RunFTransform2Test() + // arrange + byte[] reference = { - // arrange - byte[] src = { 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175 }; - byte[] reference = { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 }; - short[] actualOutput1 = new short[16]; - short[] actualOutput2 = new short[16]; - short[] expectedOutput1 = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; - short[] expectedOutput2 = { 192, -34, 10, 1, -11, 8, 10, -7, 6, 3, -8, 4, 5, -3, -2, 6 }; - - // act - Vp8Encoding.FTransform2(src, reference, actualOutput1, actualOutput2, new int[16]); - - // assert - Assert.True(expectedOutput1.SequenceEqual(actualOutput1)); - Assert.True(expectedOutput2.SequenceEqual(actualOutput2)); - } - - private static void RunFTransformTest() + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129 + }; + short[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = new byte[128]; + byte[] expected = { - // arrange - byte[] src = - { - 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, - 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, - 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, - 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, - 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, - 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, - 170, 170, 169, 171, 171, 179, 173, 175 - }; - byte[] reference = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - }; - short[] actualOutput = new short[16]; - short[] expectedOutput = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; - - // act - Vp8Encoding.FTransform(src, reference, actualOutput, new int[16]); - - // assert - Assert.True(expectedOutput.SequenceEqual(actualOutput)); - } - - private static void RunOneInverseTransformTest() + 161, 160, 149, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 160, 160, 133, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 156, 147, 109, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 152, 128, 87, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + Vp8Encoding.ITransformOne(reference, input, dst, scratch); + + // assert + Assert.True(dst.SequenceEqual(expected)); + } + + private static void RunTwoInverseTransformTest() + { + // arrange + byte[] reference = { - // arrange - byte[] reference = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - }; - short[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - byte[] dst = new byte[128]; - byte[] expected = - { - 161, 160, 149, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 160, 160, 133, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 156, 147, 109, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 152, 128, 87, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0 - }; - int[] scratch = new int[16]; - - // act - Vp8Encoding.ITransformOne(reference, input, dst, scratch); - - // assert - Assert.True(dst.SequenceEqual(expected)); - } - - private static void RunTwoInverseTransformTest() + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129 + }; + short[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = new byte[128]; + byte[] expected = { - // arrange - byte[] reference = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129 - }; - short[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - byte[] dst = new byte[128]; - byte[] expected = - { - 161, 160, 149, 105, 78, 127, 156, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 160, 160, 133, 85, 81, 129, 155, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 156, 147, 109, 76, 85, 130, 153, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 128, 87, 83, 88, 132, 152, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - int[] scratch = new int[16]; - - // act - Vp8Encoding.ITransformTwo(reference, input, dst, scratch); - - // assert - Assert.True(dst.SequenceEqual(expected)); - } - - [Fact] - public void FTransform2_Works() => RunFTransform2Test(); - - [Fact] - public void FTransform_Works() => RunFTransformTest(); - - [Fact] - public void OneInverseTransform_Works() => RunOneInverseTransformTest(); - - [Fact] - public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); - - [Fact] - public void FTransform2_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.AllowAll); - - [Fact] - public void FTransform2_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void FTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void FTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void OneInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); - - [Fact] - public void TwoInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.AllowAll); - - [Fact] - public void TwoInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); + 161, 160, 149, 105, 78, 127, 156, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 160, 160, 133, 85, 81, 129, 155, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 156, 147, 109, 76, 85, 130, 153, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 128, 87, 83, 88, 132, 152, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + Vp8Encoding.ITransformTwo(reference, input, dst, scratch); + + // assert + Assert.True(dst.SequenceEqual(expected)); } + + [Fact] + public void FTransform2_Works() => RunFTransform2Test(); + + [Fact] + public void FTransform_Works() => RunFTransformTest(); + + [Fact] + public void OneInverseTransform_Works() => RunOneInverseTransformTest(); + + [Fact] + public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); + + [Fact] + public void FTransform2_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform2_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void FTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void OneInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void TwoInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void TwoInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs index 053f8304df..a18eff73ce 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -1,224 +1,221 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class Vp8HistogramTests { - [Trait("Format", "Webp")] - public class Vp8HistogramTests + public static IEnumerable Data { - public static IEnumerable Data + get { - get + var result = new List(); + result.Add(new object[] { - var result = new List(); - result.Add(new object[] + new byte[] { - new byte[] - { - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204 - }, - new byte[] - { - 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, - 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, 127, 128, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 128, 127, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, - 129, 128, 129, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, - 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 129, 129, - 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - } - }); - return result; - } - } - - private static void RunCollectHistogramTest() - { - // arrange - var histogram = new Vp8Histogram(); - - byte[] reference = - { - 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, - 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, - 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, - 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, - 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, - 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, - 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, - 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, - 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, - 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, - 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, - 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, - 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, - 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, - 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, - 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, - 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, - 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, - 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - }; - byte[] pred = - { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 129, 129 - }; - int expectedAlpha = 146; - - // act - histogram.CollectHistogram(reference, pred, 0, 10); - int actualAlpha = histogram.GetAlpha(); - - // assert - Assert.Equal(expectedAlpha, actualAlpha); + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204 + }, + new byte[] + { + 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, + 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, 127, 128, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 128, 127, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, + 129, 128, 129, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, + 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 129, 129, + 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + } + }); + return result; } + } - [Fact] - public void RunCollectHistogramTest_Works() => RunCollectHistogramTest(); + private static void RunCollectHistogramTest() + { + // arrange + var histogram = new Vp8Histogram(); - [Fact] - public void GetAlpha_WithEmptyHistogram_Works() + byte[] reference = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + byte[] pred = { - // arrange - var histogram = new Vp8Histogram(); + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129 + }; + int expectedAlpha = 146; + + // act + histogram.CollectHistogram(reference, pred, 0, 10); + int actualAlpha = histogram.GetAlpha(); + + // assert + Assert.Equal(expectedAlpha, actualAlpha); + } - // act - int alpha = histogram.GetAlpha(); + [Fact] + public void RunCollectHistogramTest_Works() => RunCollectHistogramTest(); - // assert - Assert.Equal(0, alpha); - } + [Fact] + public void GetAlpha_WithEmptyHistogram_Works() + { + // arrange + var histogram = new Vp8Histogram(); - [Theory] - [MemberData(nameof(Data))] - public void GetAlpha_Works(byte[] reference, byte[] pred) - { - // arrange - var histogram = new Vp8Histogram(); - histogram.CollectHistogram(reference, pred, 0, 1); + // act + int alpha = histogram.GetAlpha(); - // act - int alpha = histogram.GetAlpha(); + // assert + Assert.Equal(0, alpha); + } - // assert - Assert.Equal(1054, alpha); - } + [Theory] + [MemberData(nameof(Data))] + public void GetAlpha_Works(byte[] reference, byte[] pred) + { + // arrange + var histogram = new Vp8Histogram(); + histogram.CollectHistogram(reference, pred, 0, 1); - [Theory] - [MemberData(nameof(Data))] - public void Merge_Works(byte[] reference, byte[] pred) - { - // arrange - var histogram1 = new Vp8Histogram(); - histogram1.CollectHistogram(reference, pred, 0, 1); - var histogram2 = new Vp8Histogram(); - histogram1.Merge(histogram2); + // act + int alpha = histogram.GetAlpha(); - // act - int alpha = histogram2.GetAlpha(); + // assert + Assert.Equal(1054, alpha); + } - // assert - Assert.Equal(1054, alpha); - } + [Theory] + [MemberData(nameof(Data))] + public void Merge_Works(byte[] reference, byte[] pred) + { + // arrange + var histogram1 = new Vp8Histogram(); + histogram1.CollectHistogram(reference, pred, 0, 1); + var histogram2 = new Vp8Histogram(); + histogram1.Merge(histogram2); - [Fact] - public void CollectHistogramTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.AllowAll); + // act + int alpha = histogram2.GetAlpha(); - [Fact] - public void CollectHistogramTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.DisableHWIntrinsic); + // assert + Assert.Equal(1054, alpha); } + + [Fact] + public void CollectHistogramTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.AllowAll); + + [Fact] + public void CollectHistogramTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs index 4aa3c1a5b5..39c3c89550 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs @@ -1,108 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class Vp8LHistogramTests { - [Trait("Format", "Webp")] - public class Vp8LHistogramTests + private static void RunAddVectorTest() { - private static void RunAddVectorTest() + // arrange + uint[] pixelData = { - // arrange - uint[] pixelData = - { - 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, - 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, - 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, - 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, - 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, - 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, - 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, - 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, - 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, - 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, - 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, - 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, - 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, - 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, - 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, - 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, - 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, - 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, - 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, - 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, - 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, - 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, - 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, - 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, - 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, - 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, - 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 - }; + 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, + 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, + 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, + 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, + 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, + 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, + 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, + 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, + 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, + 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, + 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, + 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, + 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, + 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, + 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, + 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, + 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, + 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, + 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, + 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, + 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, + 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, + 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, + 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, + 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, + 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 + }; - uint[] literals = - { - 198, 0, 14, 0, 46, 0, 22, 0, 36, 0, 24, 0, 12, 0, 10, 0, 10, 0, 2, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, - 10, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 2, 0, 2, 0, 0, 0, 8, 0, 2, 0, 38, 0, 4 - }; + uint[] literals = + { + 198, 0, 14, 0, 46, 0, 22, 0, 36, 0, 24, 0, 12, 0, 10, 0, 10, 0, 2, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 10, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 2, 0, 2, 0, 0, 0, 8, 0, 2, 0, 38, 0, 4 + }; - uint[] expectedLiterals = new uint[1305]; + uint[] expectedLiterals = new uint[1305]; - // All remaining values are expected to be zero. - literals.AsSpan().CopyTo(expectedLiterals); + // All remaining values are expected to be zero. + literals.AsSpan().CopyTo(expectedLiterals); - var backwardRefs = new Vp8LBackwardRefs(pixelData.Length); - for (int i = 0; i < pixelData.Length; i++) + var backwardRefs = new Vp8LBackwardRefs(pixelData.Length); + for (int i = 0; i < pixelData.Length; i++) + { + backwardRefs.Add(new PixOrCopy() { - backwardRefs.Add(new PixOrCopy() - { - BgraOrDistance = pixelData[i], - Len = 1, - Mode = PixOrCopyMode.Literal - }); - } + BgraOrDistance = pixelData[i], + Len = 1, + Mode = PixOrCopyMode.Literal + }); + } - var histogram0 = new Vp8LHistogram(backwardRefs, 3); - var histogram1 = new Vp8LHistogram(backwardRefs, 3); - for (int i = 0; i < 5; i++) - { - histogram0.IsUsed[i] = true; - histogram1.IsUsed[i] = true; - } + var histogram0 = new Vp8LHistogram(backwardRefs, 3); + var histogram1 = new Vp8LHistogram(backwardRefs, 3); + for (int i = 0; i < 5; i++) + { + histogram0.IsUsed[i] = true; + histogram1.IsUsed[i] = true; + } - var output = new Vp8LHistogram(3); + var output = new Vp8LHistogram(3); - // act - histogram0.Add(histogram1, output); + // act + histogram0.Add(histogram1, output); - // assert - Assert.True(output.Literal.SequenceEqual(expectedLiterals)); - } + // assert + Assert.True(output.Literal.SequenceEqual(expectedLiterals)); + } - [Fact] - public void AddVector_Works() => RunAddVectorTest(); + [Fact] + public void AddVector_Works() => RunAddVectorTest(); - [Fact] - public void AddVector_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.AllowAll); + [Fact] + public void AddVector_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.AllowAll); - [Fact] - public void AddVector_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.DisableAVX2); - } + [Fact] + public void AddVector_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.DisableAVX2); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs index 8368b12b14..0b85ececb9 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ModeScoreTests.cs @@ -2,93 +2,91 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Webp.Lossy; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class Vp8ModeScoreTests { - [Trait("Format", "Webp")] - public class Vp8ModeScoreTests + [Fact] + public void InitScore_Works() { - [Fact] - public void InitScore_Works() - { - var score = new Vp8ModeScore(); - score.InitScore(); - Assert.Equal(0, score.D); - Assert.Equal(0, score.SD); - Assert.Equal(0, score.R); - Assert.Equal(0, score.H); - Assert.Equal(0u, score.Nz); - Assert.Equal(Vp8ModeScore.MaxCost, score.Score); - } + var score = new Vp8ModeScore(); + score.InitScore(); + Assert.Equal(0, score.D); + Assert.Equal(0, score.SD); + Assert.Equal(0, score.R); + Assert.Equal(0, score.H); + Assert.Equal(0u, score.Nz); + Assert.Equal(Vp8ModeScore.MaxCost, score.Score); + } - [Fact] - public void CopyScore_Works() + [Fact] + public void CopyScore_Works() + { + // arrange + var score1 = new Vp8ModeScore { - // arrange - var score1 = new Vp8ModeScore - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; - var score2 = new Vp8ModeScore(); - score2.InitScore(); + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; + var score2 = new Vp8ModeScore(); + score2.InitScore(); - // act - score2.CopyScore(score1); + // act + score2.CopyScore(score1); - // assert - Assert.Equal(score1.D, score2.D); - Assert.Equal(score1.SD, score2.SD); - Assert.Equal(score1.R, score2.R); - Assert.Equal(score1.H, score2.H); - Assert.Equal(score1.Nz, score2.Nz); - Assert.Equal(score1.Score, score2.Score); - } + // assert + Assert.Equal(score1.D, score2.D); + Assert.Equal(score1.SD, score2.SD); + Assert.Equal(score1.R, score2.R); + Assert.Equal(score1.H, score2.H); + Assert.Equal(score1.Nz, score2.Nz); + Assert.Equal(score1.Score, score2.Score); + } - [Fact] - public void AddScore_Works() + [Fact] + public void AddScore_Works() + { + // arrange + var score1 = new Vp8ModeScore + { + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; + var score2 = new Vp8ModeScore { - // arrange - var score1 = new Vp8ModeScore - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; - var score2 = new Vp8ModeScore - { - Score = 123, - Nz = 1, - D = 2, - H = 3, - ModeI16 = 4, - ModeUv = 5, - R = 6, - SD = 7 - }; + Score = 123, + Nz = 1, + D = 2, + H = 3, + ModeI16 = 4, + ModeUv = 5, + R = 6, + SD = 7 + }; - // act - score2.AddScore(score1); + // act + score2.AddScore(score1); - // assert - Assert.Equal(4, score2.D); - Assert.Equal(14, score2.SD); - Assert.Equal(12, score2.R); - Assert.Equal(6, score2.H); - Assert.Equal(1u, score2.Nz); - Assert.Equal(246, score2.Score); - } + // assert + Assert.Equal(4, score2.D); + Assert.Equal(14, score2.SD); + Assert.Equal(12, score2.R); + Assert.Equal(6, score2.H); + Assert.Equal(1u, score2.Nz); + Assert.Equal(246, score2.Score); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs index 5819320e82..1f9136e9ab 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs @@ -3,33 +3,31 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class Vp8ResidualTests { - [Trait("Format", "Webp")] - public class Vp8ResidualTests + private static void RunSetCoeffsTest() { - private static void RunSetCoeffsTest() - { - // arrange - var residual = new Vp8Residual(); - short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 }; + // arrange + var residual = new Vp8Residual(); + short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 }; - // act - residual.SetCoeffs(coeffs); + // act + residual.SetCoeffs(coeffs); - // assert - Assert.Equal(9, residual.Last); - } + // assert + Assert.Equal(9, residual.Last); + } - [Fact] - public void RunSetCoeffsTest_Works() => RunSetCoeffsTest(); + [Fact] + public void RunSetCoeffsTest_Works() => RunSetCoeffsTest(); - [Fact] - public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll); + [Fact] + public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll); - [Fact] - public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic); - } + [Fact] + public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs index 1346964ec4..a3fe028db5 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs @@ -1,210 +1,207 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class WebpCommonUtilsTests { - [Trait("Format", "Webp")] - public class WebpCommonUtilsTests - { - [Fact] - public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); + [Fact] + public void CheckNonOpaque_WithOpaquePixels_Works() => RunCheckNoneOpaqueWithOpaquePixelsTest(); - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_Works() => RunCheckNoneOpaqueWithNoneOpaquePixelsTest(); - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.AllowAll); - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); - [Fact] - public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); + [Fact] + public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableAVX2); - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithHardwareIntrinsics_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.AllowAll); - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); - [Fact] - public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); + [Fact] + public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableAVX2); - private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() + private static void RunCheckNoneOpaqueWithNoneOpaquePixelsTest() + { + // arrange + byte[] rowBytes = { - // arrange - byte[] rowBytes = - { - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 10, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 0, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 0, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 100, - 171, 165, 151, 0, - 209, 208, 210, 100, - 174, 183, 189, 255, - 148, 158, 158, 255, - }; - Span row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) - { - // act - noneOpaque = WebpCommonUtils.CheckNonOpaque(row); - - // assert - Assert.True(noneOpaque); - } - - // One last test with the complete row. + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 10, + 171, 165, 151, 255, + 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 10, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 209, 208, 210, 0, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 0, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 100, + 171, 165, 151, 0, + 209, 208, 210, 100, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + + // assert Assert.True(noneOpaque); } - private static void RunCheckNoneOpaqueWithOpaquePixelsTest() + // One last test with the complete row. + noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + Assert.True(noneOpaque); + } + + private static void RunCheckNoneOpaqueWithOpaquePixelsTest() + { + // arrange + byte[] rowBytes = { - // arrange - byte[] rowBytes = - { - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 122, 120, 101, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - 148, 158, 158, 255, - 171, 165, 151, 255, - 209, 208, 210, 255, - 174, 183, 189, 255, - 148, 158, 158, 255, - }; - Span row = MemoryMarshal.Cast(rowBytes); - - bool noneOpaque; - for (int length = 8; length < row.Length; length += 8) - { - // act - noneOpaque = WebpCommonUtils.CheckNonOpaque(row[..length]); - - // assert - Assert.False(noneOpaque); - } - - // One last test with the complete row. - noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 122, 120, 101, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + 148, 158, 158, 255, + 171, 165, 151, 255, + 209, 208, 210, 255, + 174, 183, 189, 255, + 148, 158, 158, 255, + }; + Span row = MemoryMarshal.Cast(rowBytes); + + bool noneOpaque; + for (int length = 8; length < row.Length; length += 8) + { + // act + noneOpaque = WebpCommonUtils.CheckNonOpaque(row[..length]); + + // assert Assert.False(noneOpaque); } + + // One last test with the complete row. + noneOpaque = WebpCommonUtils.CheckNonOpaque(row); + Assert.False(noneOpaque); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 3f000467d0..f5fd98f458 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -1,448 +1,443 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Webp; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +[ValidateDisposedMemoryAllocations] +public class WebpDecoderTests { - [Trait("Format", "Webp")] - [ValidateDisposedMemoryAllocations] - public class WebpDecoderTests + private static WebpDecoder WebpDecoder => new(); + + private static MagickReferenceDecoder ReferenceDecoder => new(); + + private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter); + + private static string TestImageLossyVerticalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedVerticalFilter); + + private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); + + private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); + + [Theory] + [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] + [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] + [InlineData(Lossless.NoTransform2, 128, 128, 32)] + [InlineData(Lossy.Alpha1, 1000, 307, 32)] + [InlineData(Lossy.Alpha2, 1000, 307, 32)] + [InlineData(Lossy.BikeWithExif, 250, 195, 24)] + public void Identify_DetectsCorrectDimensionsAndBitDepth( + string imagePath, + int expectedWidth, + int expectedHeight, + int expectedBitsPerPixel) { - private static WebpDecoder WebpDecoder => new(); + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); + } - private static MagickReferenceDecoder ReferenceDecoder => new(); + [Theory] + [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter03, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter); + [Theory] + [WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SimpleFilter05, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static string TestImageLossyVerticalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedVerticalFilter); + [Theory] + [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] + [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter08, PixelTypes.Rgba32)] + [WithFile(Lossy.ComplexFilter09, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); + [Theory] + [WithFile(Lossy.Small01, PixelTypes.Rgba32)] + [WithFile(Lossy.Small02, PixelTypes.Rgba32)] + [WithFile(Lossy.Small03, PixelTypes.Rgba32)] + [WithFile(Lossy.Small04, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_VerySmall(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); + [Theory] + [WithFile(Lossy.SegmentationNoFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter05, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationNoFilter06, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter01, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter02, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter03, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter04, PixelTypes.Rgba32)] + [WithFile(Lossy.SegmentationComplexFilter05, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithPartitions(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - [Theory] - [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] - [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] - [InlineData(Lossless.NoTransform2, 128, 128, 32)] - [InlineData(Lossy.Alpha1, 1000, 307, 32)] - [InlineData(Lossy.Alpha2, 1000, 307, 32)] - [InlineData(Lossy.BikeWithExif, 250, 195, 24)] - public void Identify_DetectsCorrectDimensionsAndBitDepth( - string imagePath, - int expectedWidth, - int expectedHeight, - int expectedBitsPerPixel) - { - var testFile = TestFile.Create(imagePath); - using var stream = new MemoryStream(testFile.Bytes, false); - IImageInfo imageInfo = Image.Identify(stream); - Assert.NotNull(imageInfo); - Assert.Equal(expectedWidth, imageInfo.Width); - Assert.Equal(expectedHeight, imageInfo.Height); - Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); - } - - [Theory] - [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.NoFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter03, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithoutFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.SimpleFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SimpleFilter05, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSimpleFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.IccpComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.VeryShort, PixelTypes.Rgba32)] - [WithFile(Lossy.BikeComplexFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter07, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter08, PixelTypes.Rgba32)] - [WithFile(Lossy.ComplexFilter09, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithComplexFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Small01, PixelTypes.Rgba32)] - [WithFile(Lossy.Small02, PixelTypes.Rgba32)] - [WithFile(Lossy.Small03, PixelTypes.Rgba32)] - [WithFile(Lossy.Small04, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_VerySmall(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.SegmentationNoFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter05, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationNoFilter06, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter01, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter02, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter03, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter04, PixelTypes.Rgba32)] - [WithFile(Lossy.SegmentationComplexFilter05, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithPartitions(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Partitions01, PixelTypes.Rgba32)] - [WithFile(Lossy.Partitions02, PixelTypes.Rgba32)] - [WithFile(Lossy.Partitions03, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSegmentation(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.Sharpness01, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness02, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness03, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness04, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness05, PixelTypes.Rgba32)] - [WithFile(Lossy.Sharpness06, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] - [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaThinkingSmiley, PixelTypes.Rgba32)] - [WithFile(Lossy.AlphaSticker, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.Alpha, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithAlpha(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.GreenTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] - [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] - - // TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work. - // [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithSubtractGreenTransform( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.ColorIndexTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform2, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] - [WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.CrossColorTransform1, PixelTypes.Rgba32)] - [WithFile(Lossless.CrossColorTransform2, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.TwoTransforms1, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms2, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms3, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms4, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms5, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms6, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms8, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms9, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms10, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] - [WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.ThreeTransforms1, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms2, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms3, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms4, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] - [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] - [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossless_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); - - Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); - Assert.Equal(12, image.Frames.Count); - } - - [Theory] - [WithFile(Lossy.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); - WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); - - image.DebugSaveMultiFrame(provider); - image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); - - Assert.Equal(0, webpMetaData.AnimationLoopCount); - Assert.Equal(150U, frameMetaData.FrameDuration); - Assert.Equal(12, image.Frames.Count); - } - - [Theory] - [WithFile(Lossless.Animated, PixelTypes.Rgba32)] - public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - DecoderOptions options = new() { MaxFrames = 1 }; - using Image image = provider.GetImage(new WebpDecoder(), options); - Assert.Equal(1, image.Frames.Count); - } - - [Theory] - [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] - [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] - [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Lossless_WithIssues(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Just make sure no exception is thrown. The reference decoder fails to load the image. - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - } - - [Theory] - [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] - public void WebpDecoder_Decode_Resize(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.Partitions01, PixelTypes.Rgba32)] + [WithFile(Lossy.Partitions02, PixelTypes.Rgba32)] + [WithFile(Lossy.Partitions03, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSegmentation(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossy.Sharpness01, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness02, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness03, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness04, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness05, PixelTypes.Rgba32)] + [WithFile(Lossy.Sharpness06, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithSharpnessLevel(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossy.AlphaNoCompression, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionNoFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedNoFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaNoCompressionGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedHorizontalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedVerticalFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaCompressedGradientFilter, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha2, PixelTypes.Rgba32)] + [WithFile(Lossy.Alpha3, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaThinkingSmiley, PixelTypes.Rgba32)] + [WithFile(Lossy.AlphaSticker, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossy_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.Alpha, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.NoTransform2, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithoutTransforms(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.GreenTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform3, PixelTypes.Rgba32)] + [WithFile(Lossless.GreenTransform4, PixelTypes.Rgba32)] + + // TODO: Reference decoder throws here MagickCorruptImageErrorException, webpinfo also indicates an error here, but decoding the image seems to work. + // [WithFile(Lossless.GreenTransform5, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithSubtractGreenTransform( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.ColorIndexTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform2, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform3, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform4, PixelTypes.Rgba32)] + [WithFile(Lossless.ColorIndexTransform5, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithColorIndexTransform(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.PredictorTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.PredictorTransform2, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithPredictorTransform(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.CrossColorTransform1, PixelTypes.Rgba32)] + [WithFile(Lossless.CrossColorTransform2, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithCrossColorTransform(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.TwoTransforms1, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms2, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms3, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms4, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms5, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms6, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms7, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms8, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms9, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms10, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms11, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms12, PixelTypes.Rgba32)] + [WithFile(Lossless.TwoTransforms13, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithTwoTransforms(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.ThreeTransforms1, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms2, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms3, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms4, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms5, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms6, PixelTypes.Rgba32)] + [WithFile(Lossless.ThreeTransforms7, PixelTypes.Rgba32)] + [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithThreeTransforms(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.Animated, PixelTypes.Rgba32)] + public void Decode_AnimatedLossless_VerifyAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(12, image.Frames.Count); + } + + [Theory] + [WithFile(Lossy.Animated, PixelTypes.Rgba32)] + public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.Frames.RootFrame.Metadata.GetWebpMetadata(); + + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Tolerant(0.04f)); + + Assert.Equal(0, webpMetaData.AnimationLoopCount); + Assert.Equal(150U, frameMetaData.FrameDuration); + Assert.Equal(12, image.Frames.Count); + } + + [Theory] + [WithFile(Lossless.Animated, PixelTypes.Rgba32)] + public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(new WebpDecoder(), options); + Assert.Equal(1, image.Frames.Count); + } + + [Theory] + [WithFile(Lossless.LossLessCorruptImage1, PixelTypes.Rgba32)] + [WithFile(Lossless.LossLessCorruptImage2, PixelTypes.Rgba32)] + [WithFile(Lossless.LossLessCorruptImage4, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Lossless_WithIssues(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Just make sure no exception is thrown. The reference decoder fails to load the image. + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + } + + [Theory] + [WithFile(Lossless.BikeThreeTransforms, PixelTypes.Rgba32)] + public void WebpDecoder_Decode_Resize(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { - DecoderOptions options = new() + TargetSize = new() { Width = 150, Height = 150 } + }; + + using Image image = provider.GetImage(WebpDecoder, options); + + FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; + + image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.0007F), + provider, + testOutputDetails: details, + appendPixelTypeToFileName: false); + } + + // https://github.com/SixLabors/ImageSharp/issues/1594 + [Theory] + [WithFile(Lossy.Issue1594, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue1594(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + + [Theory] + [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] + public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + Assert.Throws( + () => { - TargetSize = new() { Width = 150, Height = 150 } - }; - - using Image image = provider.GetImage(WebpDecoder, options); - - FormattableString details = $"{options.TargetSize.Value.Width}_{options.TargetSize.Value.Height}"; - - image.DebugSave(provider, testOutputDetails: details, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0007F), - provider, - testOutputDetails: details, - appendPixelTypeToFileName: false); - } - - // https://github.com/SixLabors/ImageSharp/issues/1594 - [Theory] - [WithFile(Lossy.Issue1594, PixelTypes.Rgba32)] - public void WebpDecoder_CanDecode_Issue1594(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } - - [Theory] - [WithFile(Lossless.LossLessCorruptImage3, PixelTypes.Rgba32)] - public void WebpDecoder_ThrowImageFormatException_OnInvalidImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - Assert.Throws( - () => + using (provider.GetImage(WebpDecoder)) { - using (provider.GetImage(WebpDecoder)) - { - } - }); + } + }); - private static void RunDecodeLossyWithHorizontalFilter() - { - var provider = TestImageProvider.File(TestImageLossyHorizontalFilterPath); - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + private static void RunDecodeLossyWithHorizontalFilter() + { + var provider = TestImageProvider.File(TestImageLossyHorizontalFilterPath); + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static void RunDecodeLossyWithVerticalFilter() - { - var provider = TestImageProvider.File(TestImageLossyVerticalFilterPath); - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + private static void RunDecodeLossyWithVerticalFilter() + { + var provider = TestImageProvider.File(TestImageLossyVerticalFilterPath); + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static void RunDecodeLossyWithSimpleFilterTest() - { - var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + private static void RunDecodeLossyWithSimpleFilterTest() + { + var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - private static void RunDecodeLossyWithComplexFilterTest() - { - var provider = TestImageProvider.File(TestImageLossyComplexFilterPath); - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + private static void RunDecodeLossyWithComplexFilterTest() + { + var provider = TestImageProvider.File(TestImageLossyComplexFilterPath); + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - [Fact] - public void DecodeLossyWithHorizontalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithHorizontalFilter, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void DecodeLossyWithHorizontalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithHorizontalFilter, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void DecodeLossyWithVerticalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithVerticalFilter, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void DecodeLossyWithVerticalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithVerticalFilter, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); - [Fact] - public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); - } + [Fact] + public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index f16f50b989..9796d89833 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -1,347 +1,344 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Webp; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class WebpEncoderTests { - [Trait("Format", "Webp")] - public class WebpEncoderTests + private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.NoFilter06); + + [Theory] + [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // If its not a webp input image, it should default to lossy. + [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] + [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] + public void Encode_PreserveRatio(TestImageProvider provider, WebpFileFormatType expectedFormat) + where TPixel : unmanaged, IPixel { - private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.NoFilter06); - - [Theory] - [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] // If its not a webp input image, it should default to lossy. - [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] - [WithFile(Lossy.BikeWithExif, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] - public void Encode_PreserveRatio(TestImageProvider provider, WebpFileFormatType expectedFormat) - where TPixel : unmanaged, IPixel - { - var options = new WebpEncoder(); - using Image input = provider.GetImage(); - using var memoryStream = new MemoryStream(); - input.Save(memoryStream, options); + var options = new WebpEncoder(); + using Image input = provider.GetImage(); + using var memoryStream = new MemoryStream(); + input.Save(memoryStream, options); - memoryStream.Position = 0; - using var output = Image.Load(memoryStream); + memoryStream.Position = 0; + using var output = Image.Load(memoryStream); - ImageMetadata meta = output.Metadata; - WebpMetadata webpMetaData = meta.GetWebpMetadata(); - Assert.Equal(expectedFormat, webpMetaData.FileFormat); - } + ImageMetadata meta = output.Metadata; + WebpMetadata webpMetaData = meta.GetWebpMetadata(); + Assert.Equal(expectedFormat, webpMetaData.FileFormat); + } - [Theory] - [WithFile(Flag, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] - public void Encode_Lossless_WithPalette_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Flag, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.PalettedTwoColor, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Paletted256Colors, PixelTypes.Rgba32)] + public void Encode_Lossless_WithPalette_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Quality = 100, - Method = WebpEncodingMethod.BestQuality - }; - - using Image image = provider.GetImage(); - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } + FileFormat = WebpFileFormatType.Lossless, + Quality = 100, + Method = WebpEncodingMethod.BestQuality + }; + + using Image image = provider.GetImage(); + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] - public void Encode_Lossless_WithDifferentQuality_Works(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 100)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 80)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, 20)] + public void Encode_Lossless_WithDifferentQuality_Works(TestImageProvider provider, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossless", "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } + FileFormat = WebpFileFormatType.Lossless, + Quality = quality + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossless", "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] + public void Encode_Lossless_WithDifferentMethodAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Method = method, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossless", "_m", method, "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } + FileFormat = WebpFileFormatType.Lossless, + Method = method, + Quality = quality + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossless", "_m", method, "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] - public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] + public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Method = WebpEncodingMethod.BestQuality - }; + FileFormat = WebpFileFormatType.Lossless, + Method = WebpEncodingMethod.BestQuality + }; - using Image image = provider.GetImage(); - using var memoryStream = new MemoryStream(); - image.Save(memoryStream, encoder); + using Image image = provider.GetImage(); + using var memoryStream = new MemoryStream(); + image.Save(memoryStream, encoder); - Assert.Equal(memoryStream.Length, expectedBytes); - } + Assert.Equal(memoryStream.Length, expectedBytes); + } - [Theory] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] - [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 40)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 20)] - [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 10)] - [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] - public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] + [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 40)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 20)] + [WithFile(RgbTestPattern80x80, PixelTypes.Rgba32, 10)] + [WithFile(RgbTestPattern63x63, PixelTypes.Rgba32, 40)] + public void Encode_Lossless_WithNearLosslessFlag_Works(TestImageProvider provider, int nearLosslessQuality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - NearLossless = true, - NearLosslessQuality = nearLosslessQuality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality)); - } + FileFormat = WebpFileFormatType.Lossless, + NearLossless = true, + NearLosslessQuality = nearLosslessQuality + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("nearlossless", "_q", nearLosslessQuality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(nearLosslessQuality)); + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] - [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] - public void Encode_Lossless_WithPreserveTransparentColor_Works(TestImageProvider provider, WebpEncodingMethod method) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6)] + [WithFile(Lossy.Alpha1, PixelTypes.Rgba32, 4)] + public void Encode_Lossless_WithPreserveTransparentColor_Works(TestImageProvider provider, WebpEncodingMethod method) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossless, - Method = method, - TransparentColorMode = WebpTransparentColorMode.Preserve - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossless", "_m", method); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); - } + FileFormat = WebpFileFormatType.Lossless, + Method = method, + TransparentColorMode = WebpTransparentColorMode.Preserve + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossless", "_m", method); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); + } - [Fact] - public void Encode_Lossless_OneByOnePixel_Works() + [Fact] + public void Encode_Lossless_OneByOnePixel_Works() + { + // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. + using var image = new Image(1, 1); + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; + using (var memStream = new MemoryStream()) { - // Just make sure, encoding 1 pixel by 1 pixel does not throw an exception. - using var image = new Image(1, 1); - var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; - using (var memStream = new MemoryStream()) - { - image.SaveAsWebp(memStream, encoder); - } + image.SaveAsWebp(memStream, encoder); } + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 20)] - public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 20)] + public void Encode_Lossy_WithDifferentQuality_Works(TestImageProvider provider, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossy", "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); - } + FileFormat = WebpFileFormatType.Lossy, + Quality = quality + }; - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] - public void Encode_Lossy_WithDifferentFilterStrength_Works(TestImageProvider provider, int filterStrength) - where TPixel : unmanaged, IPixel - { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - FilterStrength = filterStrength - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossy", "_f", filterStrength); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); - } + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] - public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works(TestImageProvider provider, int snsStrength) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] + public void Encode_Lossy_WithDifferentFilterStrength_Works(TestImageProvider provider, int filterStrength) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - SpatialNoiseShaping = snsStrength - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossy", "_sns", snsStrength); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); - } + FileFormat = WebpFileFormatType.Lossy, + FilterStrength = filterStrength + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_f", filterStrength); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); + } - [Theory] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] - [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] - public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 80)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 50)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 30)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 10)] + public void Encode_Lossy_WithDifferentSpatialNoiseShapingStrength_Works(TestImageProvider provider, int snsStrength) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - Method = method, - Quality = quality - }; - - using Image image = provider.GetImage(); - string testOutputDetails = string.Concat("lossy", "_m", method, "_q", quality); - image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); - } + FileFormat = WebpFileFormatType.Lossy, + SpatialNoiseShaping = snsStrength + }; - [Theory] - [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, false, 64020)] - [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, true, 16200)] - public void Encode_Lossy_WithAlpha_Works(TestImageProvider provider, bool compressed, int expectedFileSize) - where TPixel : unmanaged, IPixel + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_sns", snsStrength); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(75)); + } + + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 75)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 0, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 1, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 2, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 3, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 4, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 5, 100)] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 6, 100)] + public void Encode_Lossy_WithDifferentMethodsAndQuality_Works(TestImageProvider provider, WebpEncodingMethod method, int quality) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - var encoder = new WebpEncoder() - { - FileFormat = WebpFileFormatType.Lossy, - UseAlphaCompression = compressed - }; - - using Image image = provider.GetImage(); - string encodedFile = image.VerifyEncoder( - provider, - "webp", - $"with_alpha_compressed_{compressed}", - encoder, - ImageComparer.Tolerant(0.04f), - referenceDecoder: new MagickReferenceDecoder()); - - int encodedBytes = File.ReadAllBytes(encodedFile).Length; - Assert.True(encodedBytes <= expectedFileSize); - } + FileFormat = WebpFileFormatType.Lossy, + Method = method, + Quality = quality + }; + + using Image image = provider.GetImage(); + string testOutputDetails = string.Concat("lossy", "_m", method, "_q", quality); + image.VerifyEncoder(provider, "webp", testOutputDetails, encoder, customComparer: GetComparer(quality)); + } - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] - public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, false, 64020)] + [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32, true, 16200)] + public void Encode_Lossy_WithAlpha_Works(TestImageProvider provider, bool compressed, int expectedFileSize) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() { - using Image image = provider.GetImage(); + FileFormat = WebpFileFormatType.Lossy, + UseAlphaCompression = compressed + }; + + using Image image = provider.GetImage(); + string encodedFile = image.VerifyEncoder( + provider, + "webp", + $"with_alpha_compressed_{compressed}", + encoder, + ImageComparer.Tolerant(0.04f), + referenceDecoder: new MagickReferenceDecoder()); + + int encodedBytes = File.ReadAllBytes(encodedFile).Length; + Assert.True(encodedBytes <= expectedFileSize); + } - var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder); - } + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] + public void Encode_Lossless_WorksWithTestPattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - [Theory] - [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] - [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] - public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } - var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); - } + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + [WithFile(TestPatternOpaqueSmall, PixelTypes.Rgba32)] + public void Encode_Lossy_WorksWithTestPattern(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - public static void RunEncodeLossy_WithPeakImage() - { - var provider = TestImageProvider.File(TestImageLossyFullPath); - using Image image = provider.GetImage(); + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); + } - var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; - image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); - } + public static void RunEncodeLossy_WithPeakImage() + { + var provider = TestImageProvider.File(TestImageLossyFullPath); + using Image image = provider.GetImage(); - [Fact] - public void RunEncodeLossy_WithPeakImage_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.AllowAll); + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); + } - [Fact] - public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void RunEncodeLossy_WithPeakImage_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.AllowAll); - private static ImageComparer GetComparer(int quality) - { - float tolerance = 0.01f; // ~1.0% + [Fact] + public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); - if (quality < 30) - { - tolerance = 0.02f; // ~2.0% - } + private static ImageComparer GetComparer(int quality) + { + float tolerance = 0.01f; // ~1.0% - return ImageComparer.Tolerant(tolerance); + if (quality < 30) + { + tolerance = 0.02f; // ~2.0% } + + return ImageComparer.Tolerant(tolerance); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 1288538723..566b7e4dd2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -1,209 +1,204 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class WebpMetaDataTests { - [Trait("Format", "Webp")] - public class WebpMetaDataTests - { - private static WebpDecoder WebpDecoder => new(); + private static WebpDecoder WebpDecoder => new(); - [Theory] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32, true)] + public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLossyImage(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); + if (ignoreMetadata) { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - } - else - { - ExifProfile exifProfile = image.Metadata.ExifProfile; - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); - } + Assert.Null(image.Metadata.ExifProfile); } - - [Theory] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel + else { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.ExifProfile); - } - else - { - ExifProfile exifProfile = image.Metadata.ExifProfile; - Assert.NotNull(exifProfile); - Assert.NotEmpty(exifProfile.Values); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Make) && m.GetValue().Equals("Canon")); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Model) && m.GetValue().Equals("Canon PowerShot S40")); - Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); - } + ExifProfile exifProfile = image.Metadata.ExifProfile; + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); } + } - [Theory] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, true)] - public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32, true)] + public void IgnoreMetadata_ControlsWhetherExifIsParsed_WithLosslessImage(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); + if (ignoreMetadata) { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = provider.GetImage(WebpDecoder, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.IccProfile); - } - else - { - Assert.NotNull(image.Metadata.IccProfile); - Assert.NotEmpty(image.Metadata.IccProfile.Entries); - } + Assert.Null(image.Metadata.ExifProfile); } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, true)] - public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata) - where TPixel : unmanaged, IPixel + else { - DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; - using Image image = await provider.GetImageAsync(WebpDecoder, options); - if (ignoreMetadata) - { - Assert.Null(image.Metadata.XmpProfile); - } - else - { - Assert.NotNull(image.Metadata.XmpProfile); - Assert.NotEmpty(image.Metadata.XmpProfile.Data); - } + ExifProfile exifProfile = image.Metadata.ExifProfile; + Assert.NotNull(exifProfile); + Assert.NotEmpty(exifProfile.Values); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Make) && m.GetValue().Equals("Canon")); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Model) && m.GetValue().Equals("Canon PowerShot S40")); + Assert.Contains(exifProfile.Values, m => m.Tag.Equals(ExifTag.Software) && m.GetValue().Equals("GIMP 2.10.2")); } + } - [Theory] - [InlineData(WebpFileFormatType.Lossy)] - [InlineData(WebpFileFormatType.Lossless)] - public void Encode_WritesExifWithPadding(WebpFileFormatType fileFormatType) + [Theory] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossless.WithIccp, PixelTypes.Rgba32, true)] + public void IgnoreMetadata_ControlsWhetherIccpIsParsed(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = provider.GetImage(WebpDecoder, options); + if (ignoreMetadata) { - // arrange - using var input = new Image(25, 25); - using var memoryStream = new MemoryStream(); - var expectedExif = new ExifProfile(); - string expectedSoftware = "ImageSharp"; - expectedExif.SetValue(ExifTag.Software, expectedSoftware); - input.Metadata.ExifProfile = expectedExif; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = fileFormatType }); - memoryStream.Position = 0; - - // assert - using var image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); - Assert.Equal(expectedSoftware, actualExif.GetValue(ExifTag.Software).Value); + Assert.Null(image.Metadata.IccProfile); } - - [Theory] - [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32)] - public void EncodeLossyWebp_PreservesExif(TestImageProvider provider) - where TPixel : unmanaged, IPixel + else { - // arrange - using Image input = provider.GetImage(WebpDecoder); - using var memoryStream = new MemoryStream(); - ExifProfile expectedExif = input.Metadata.ExifProfile; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }); - memoryStream.Position = 0; - - // assert - using var image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + Assert.NotNull(image.Metadata.IccProfile); + Assert.NotEmpty(image.Metadata.IccProfile.Entries); } + } - [Theory] - [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32)] - public void EncodeLosslessWebp_PreservesExif(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, true)] + public async Task IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { SkipMetadata = ignoreMetadata }; + using Image image = await provider.GetImageAsync(WebpDecoder, options); + if (ignoreMetadata) { - // arrange - using Image input = provider.GetImage(WebpDecoder); - using var memoryStream = new MemoryStream(); - ExifProfile expectedExif = input.Metadata.ExifProfile; - - // act - input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }); - memoryStream.Position = 0; - - // assert - using var image = Image.Load(memoryStream); - ExifProfile actualExif = image.Metadata.ExifProfile; - Assert.NotNull(actualExif); - Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + Assert.Null(image.Metadata.XmpProfile); } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] - [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] - public void Encode_PreservesColorProfile(TestImageProvider provider, WebpFileFormatType fileFormat) - where TPixel : unmanaged, IPixel + else { - using Image input = provider.GetImage(WebpDecoder); - ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; - byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - - using var memStream = new MemoryStream(); - input.Save(memStream, new WebpEncoder() - { - FileFormat = fileFormat - }); - - memStream.Position = 0; - using var output = Image.Load(memStream); - ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; - byte[] actualProfileBytes = actualProfile.ToByteArray(); - - Assert.NotNull(actualProfile); - Assert.Equal(expectedProfileBytes, actualProfileBytes); + Assert.NotNull(image.Metadata.XmpProfile); + Assert.NotEmpty(image.Metadata.XmpProfile.Data); } + } + + [Theory] + [InlineData(WebpFileFormatType.Lossy)] + [InlineData(WebpFileFormatType.Lossless)] + public void Encode_WritesExifWithPadding(WebpFileFormatType fileFormatType) + { + // arrange + using var input = new Image(25, 25); + using var memoryStream = new MemoryStream(); + var expectedExif = new ExifProfile(); + string expectedSoftware = "ImageSharp"; + expectedExif.SetValue(ExifTag.Software, expectedSoftware); + input.Metadata.ExifProfile = expectedExif; + + // act + input.Save(memoryStream, new WebpEncoder() { FileFormat = fileFormatType }); + memoryStream.Position = 0; + + // assert + using var image = Image.Load(memoryStream); + ExifProfile actualExif = image.Metadata.ExifProfile; + Assert.NotNull(actualExif); + Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + Assert.Equal(expectedSoftware, actualExif.GetValue(ExifTag.Software).Value); + } + + [Theory] + [WithFile(TestImages.Webp.Lossy.BikeWithExif, PixelTypes.Rgba32)] + public void EncodeLossyWebp_PreservesExif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(WebpDecoder); + using var memoryStream = new MemoryStream(); + ExifProfile expectedExif = input.Metadata.ExifProfile; + + // act + input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }); + memoryStream.Position = 0; + + // assert + using var image = Image.Load(memoryStream); + ExifProfile actualExif = image.Metadata.ExifProfile; + Assert.NotNull(actualExif); + Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + } + + [Theory] + [WithFile(TestImages.Webp.Lossless.WithExif, PixelTypes.Rgba32)] + public void EncodeLosslessWebp_PreservesExif(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image input = provider.GetImage(WebpDecoder); + using var memoryStream = new MemoryStream(); + ExifProfile expectedExif = input.Metadata.ExifProfile; + + // act + input.Save(memoryStream, new WebpEncoder() { FileFormat = WebpFileFormatType.Lossless }); + memoryStream.Position = 0; + + // assert + using var image = Image.Load(memoryStream); + ExifProfile actualExif = image.Metadata.ExifProfile; + Assert.NotNull(actualExif); + Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); + } + + [Theory] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] + [WithFile(TestImages.Webp.Lossy.WithIccp, PixelTypes.Rgba32, WebpFileFormatType.Lossy)] + public void Encode_PreservesColorProfile(TestImageProvider provider, WebpFileFormatType fileFormat) + where TPixel : unmanaged, IPixel + { + using Image input = provider.GetImage(WebpDecoder); + ImageSharp.Metadata.Profiles.Icc.IccProfile expectedProfile = input.Metadata.IccProfile; + byte[] expectedProfileBytes = expectedProfile.ToByteArray(); - [Theory] - [WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)] - public void WebpDecoder_IgnoresInvalidExifChunk(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using var memStream = new MemoryStream(); + input.Save(memStream, new WebpEncoder() { - Exception ex = Record.Exception(() => - { - using Image image = provider.GetImage(); - }); - Assert.Null(ex); - } + FileFormat = fileFormat + }); + + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageSharp.Metadata.Profiles.Icc.IccProfile actualProfile = output.Metadata.IccProfile; + byte[] actualProfileBytes = actualProfile.ToByteArray(); + + Assert.NotNull(actualProfile); + Assert.Equal(expectedProfileBytes, actualProfileBytes); + } + + [Theory] + [WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)] + public void WebpDecoder_IgnoresInvalidExifChunk(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(); + }); + Assert.Null(ex); } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 718dd920fe..d28b2ee40d 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp.Lossy; @@ -10,256 +8,254 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Webp +namespace SixLabors.ImageSharp.Tests.Formats.Webp; + +[Trait("Format", "Webp")] +public class YuvConversionTests { - [Trait("Format", "Webp")] - public class YuvConversionTests - { - private static WebpDecoder WebpDecoder => new(); + private static WebpDecoder WebpDecoder => new(); - private static MagickReferenceDecoder ReferenceDecoder => new(); + private static MagickReferenceDecoder ReferenceDecoder => new(); - private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Webp.Lossy.NoFilter06); + private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Webp.Lossy.NoFilter06); - public static void RunUpSampleYuvToRgbTest() - { - var provider = TestImageProvider.File(TestImageLossyFullPath); - using Image image = provider.GetImage(WebpDecoder); - image.DebugSave(provider); - image.CompareToOriginal(provider, ReferenceDecoder); - } + public static void RunUpSampleYuvToRgbTest() + { + var provider = TestImageProvider.File(TestImageLossyFullPath); + using Image image = provider.GetImage(WebpDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } - [Fact] - public void UpSampleYuvToRgb_Works() => RunUpSampleYuvToRgbTest(); + [Fact] + public void UpSampleYuvToRgb_Works() => RunUpSampleYuvToRgbTest(); - [Fact] - public void UpSampleYuvToRgb_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.AllowAll); + [Fact] + public void UpSampleYuvToRgb_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.AllowAll); - [Fact] - public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableSSE2); + [Fact] + public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableSSE2); - [Theory] - [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] + public void ConvertRgbToYuv_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + Configuration config = image.GetConfiguration(); + MemoryAllocator memoryAllocator = config.MemoryAllocator; + int pixels = image.Width * image.Height; + int uvWidth = (image.Width + 1) >> 1; + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + Span y = yBuffer.GetSpan(); + Span u = uBuffer.GetSpan(); + Span v = vBuffer.GetSpan(); + byte[] expectedY = + { + 82, 82, 82, 82, 128, 135, 134, 129, 167, 179, 176, 172, 192, 201, 200, 204, 188, 172, 175, 177, 168, + 151, 154, 154, 153, 152, 151, 151, 152, 160, 160, 160, 160, 82, 82, 82, 82, 140, 137, 135, 116, 174, + 183, 176, 162, 196, 199, 199, 210, 188, 166, 176, 181, 170, 145, 155, 154, 153, 154, 151, 151, 150, + 162, 159, 160, 160, 82, 82, 83, 82, 142, 139, 137, 117, 176, 184, 177, 164, 195, 199, 198, 210, 188, + 165, 175, 180, 169, 145, 155, 154, 153, 154, 152, 151, 150, 163, 160, 160, 160, 82, 82, 82, 82, 124, + 122, 120, 101, 161, 171, 165, 151, 197, 209, 208, 210, 197, 174, 183, 189, 175, 148, 158, 158, 155, + 151, 148, 148, 147, 159, 156, 156, 159, 128, 140, 142, 124, 189, 185, 183, 167, 201, 199, 198, 209, + 179, 165, 171, 179, 160, 145, 151, 152, 151, 154, 152, 151, 153, 164, 160, 160, 160, 170, 170, 170, + 169, 135, 137, 139, 122, 185, 182, 180, 165, 201, 200, 199, 210, 180, 166, 173, 180, 162, 145, 153, + 153, 151, 154, 151, 150, 152, 164, 160, 159, 159, 170, 170, 170, 170, 134, 135, 137, 120, 184, 180, + 177, 164, 200, 198, 196, 210, 181, 167, 174, 181, 163, 146, 155, 155, 153, 154, 152, 150, 152, 163, + 160, 159, 159, 167, 167, 167, 168, 129, 116, 117, 101, 167, 166, 164, 149, 205, 210, 209, 210, 191, + 177, 184, 191, 170, 149, 158, 159, 153, 151, 148, 146, 148, 159, 155, 155, 155, 170, 169, 170, 170, + 167, 174, 175, 161, 201, 201, 200, 204, 178, 173, 174, 185, 159, 148, 155, 158, 152, 152, 151, 150, + 153, 162, 159, 158, 160, 170, 169, 169, 168, 109, 122, 120, 129, 179, 183, 184, 171, 199, 200, 198, + 210, 172, 166, 170, 179, 155, 145, 150, 152, 149, 155, 152, 150, 155, 164, 161, 159, 162, 170, 170, + 170, 170, 92, 111, 109, 115, 176, 176, 177, 165, 198, 198, 196, 209, 174, 170, 173, 183, 159, 148, + 155, 156, 152, 154, 152, 150, 154, 163, 160, 158, 159, 166, 166, 168, 169, 98, 117, 116, 117, 172, + 162, 164, 152, 209, 210, 210, 210, 184, 179, 183, 192, 164, 151, 157, 159, 150, 150, 148, 146, 150, + 159, 155, 154, 157, 170, 169, 170, 170, 117, 136, 134, 123, 192, 196, 196, 197, 179, 180, 180, 191, + 159, 155, 159, 164, 153, 151, 152, 150, 154, 160, 157, 155, 160, 170, 166, 167, 165, 120, 134, 135, + 139, 69, 87, 86, 90, 201, 199, 199, 208, 165, 166, 167, 177, 148, 145, 148, 151, 150, 155, 153, 150, + 157, 165, 162, 159, 165, 170, 169, 170, 166, 84, 107, 108, 111, 49, 66, 64, 71, 200, 199, 198, 208, + 171, 173, 174, 184, 155, 150, 155, 157, 152, 153, 153, 149, 156, 163, 160, 157, 162, 167, 165, 169, + 167, 97, 121, 121, 125, 60, 77, 75, 76, 204, 210, 210, 210, 179, 180, 181, 191, 158, 152, 156, 159, + 150, 150, 149, 146, 152, 159, 156, 153, 160, 170, 169, 170, 170, 112, 135, 136, 138, 71, 88, 86, 79, + 188, 188, 188, 197, 160, 162, 163, 170, 152, 150, 152, 151, 154, 157, 156, 152, 160, 167, 164, 164, + 161, 135, 146, 150, 143, 77, 98, 99, 103, 51, 62, 60, 62, 172, 166, 165, 174, 145, 145, 145, 150, + 152, 155, 154, 150, 160, 165, 163, 159, 168, 170, 168, 170, 151, 80, 104, 109, 101, 44, 63, 63, 66, + 55, 52, 53, 51, 175, 176, 175, 183, 151, 153, 155, 158, 151, 152, 152, 148, 157, 161, 160, 156, 164, + 168, 165, 169, 156, 100, 122, 126, 118, 60, 79, 79, 81, 54, 52, 52, 51, 177, 181, 180, 188, 153, + 153, 155, 159, 149, 150, 150, 146, 155, 159, 157, 153, 164, 170, 169, 170, 170, 109, 131, 136, 127, + 66, 86, 86, 87, 46, 43, 43, 47, 168, 170, 169, 175, 151, 151, 152, 153, 153, 155, 154, 150, 160, + 164, 162, 160, 161, 151, 157, 165, 144, 88, 109, 114, 105, 55, 69, 68, 67, 62, 56, 56, 59, 151, 145, + 145, 148, 154, 154, 154, 150, 162, 164, 163, 159, 170, 170, 167, 170, 135, 80, 100, 110, 89, 41, 61, + 64, 59, 56, 53, 50, 50, 94, 85, 86, 79, 154, 155, 155, 158, 152, 152, 152, 148, 159, 161, 160, 155, + 166, 169, 165, 169, 146, 104, 122, 131, 110, 61, 80, 83, 75, 53, 53, 48, 47, 84, 74, 75, 75, 154, + 154, 154, 158, 151, 150, 150, 146, 158, 159, 158, 154, 167, 170, 169, 170, 153, 108, 127, 136, 113, + 63, 83, 87, 78, 48, 46, 43, 41, 81, 71, 72, 74, 153, 153, 153, 155, 153, 152, 152, 148, 160, 161, + 159, 157, 165, 165, 166, 170, 143, 101, 118, 127, 104, 60, 75, 78, 70, 56, 51, 48, 46, 85, 76, 77, + 81, 152, 154, 154, 151, 164, 164, 163, 159, 170, 170, 167, 170, 121, 84, 98, 114, 78, 44, 60, 68, + 52, 56, 53, 48, 56, 96, 85, 85, 83, 107, 105, 106, 100, 151, 151, 152, 148, 160, 160, 160, 155, 169, + 170, 166, 169, 134, 108, 121, 135, 98, 63, 79, 87, 69, 53, 53, 46, 50, 85, 73, 73, 71, 104, 95, 96, + 97, 151, 151, 151, 148, 160, 159, 159, 155, 169, 170, 170, 170, 137, 108, 121, 136, 99, 63, 78, 87, + 67, 51, 48, 43, 48, 85, 73, 72, 71, 105, 96, 97, 98, 152, 150, 150, 147, 160, 159, 159, 155, 169, + 170, 169, 170, 140, 111, 125, 139, 102, 67, 81, 87, 67, 50, 47, 41, 46, 83, 71, 71, 70, 103, 96, 96, + 98, 160, 162, 163, 159, 170, 170, 167, 170, 109, 91, 98, 117, 70, 49, 60, 72, 49, 55, 54, 46, 62, + 95, 84, 81, 85, 107, 104, 105, 103, 96, 98, 97, 100, 160, 159, 160, 156, 170, 170, 167, 169, 122, + 111, 118, 136, 87, 66, 77, 88, 62, 52, 53, 43, 56, 85, 74, 71, 76, 105, 95, 96, 96, 98, 100, 100, + 100, 160, 160, 160, 156, 170, 170, 167, 170, 120, 109, 116, 134, 86, 64, 75, 86, 60, 53, 51, 43, 56, + 86, 75, 72, 77, 106, 96, 97, 96, 97, 100, 100, 100, 160, 160, 160, 159, 169, 170, 168, 170, 129, + 115, 117, 123, 90, 71, 76, 79, 62, 51, 51, 47, 59, 79, 75, 74, 81, 100, 97, 98, 98, 100, 100, 100, + 100 + }; + byte[] expectedU = { - // arrange - using Image image = provider.GetImage(); - Configuration config = image.GetConfiguration(); - MemoryAllocator memoryAllocator = config.MemoryAllocator; - int pixels = image.Width * image.Height; - int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - Span y = yBuffer.GetSpan(); - Span u = uBuffer.GetSpan(); - Span v = vBuffer.GetSpan(); - byte[] expectedY = - { - 82, 82, 82, 82, 128, 135, 134, 129, 167, 179, 176, 172, 192, 201, 200, 204, 188, 172, 175, 177, 168, - 151, 154, 154, 153, 152, 151, 151, 152, 160, 160, 160, 160, 82, 82, 82, 82, 140, 137, 135, 116, 174, - 183, 176, 162, 196, 199, 199, 210, 188, 166, 176, 181, 170, 145, 155, 154, 153, 154, 151, 151, 150, - 162, 159, 160, 160, 82, 82, 83, 82, 142, 139, 137, 117, 176, 184, 177, 164, 195, 199, 198, 210, 188, - 165, 175, 180, 169, 145, 155, 154, 153, 154, 152, 151, 150, 163, 160, 160, 160, 82, 82, 82, 82, 124, - 122, 120, 101, 161, 171, 165, 151, 197, 209, 208, 210, 197, 174, 183, 189, 175, 148, 158, 158, 155, - 151, 148, 148, 147, 159, 156, 156, 159, 128, 140, 142, 124, 189, 185, 183, 167, 201, 199, 198, 209, - 179, 165, 171, 179, 160, 145, 151, 152, 151, 154, 152, 151, 153, 164, 160, 160, 160, 170, 170, 170, - 169, 135, 137, 139, 122, 185, 182, 180, 165, 201, 200, 199, 210, 180, 166, 173, 180, 162, 145, 153, - 153, 151, 154, 151, 150, 152, 164, 160, 159, 159, 170, 170, 170, 170, 134, 135, 137, 120, 184, 180, - 177, 164, 200, 198, 196, 210, 181, 167, 174, 181, 163, 146, 155, 155, 153, 154, 152, 150, 152, 163, - 160, 159, 159, 167, 167, 167, 168, 129, 116, 117, 101, 167, 166, 164, 149, 205, 210, 209, 210, 191, - 177, 184, 191, 170, 149, 158, 159, 153, 151, 148, 146, 148, 159, 155, 155, 155, 170, 169, 170, 170, - 167, 174, 175, 161, 201, 201, 200, 204, 178, 173, 174, 185, 159, 148, 155, 158, 152, 152, 151, 150, - 153, 162, 159, 158, 160, 170, 169, 169, 168, 109, 122, 120, 129, 179, 183, 184, 171, 199, 200, 198, - 210, 172, 166, 170, 179, 155, 145, 150, 152, 149, 155, 152, 150, 155, 164, 161, 159, 162, 170, 170, - 170, 170, 92, 111, 109, 115, 176, 176, 177, 165, 198, 198, 196, 209, 174, 170, 173, 183, 159, 148, - 155, 156, 152, 154, 152, 150, 154, 163, 160, 158, 159, 166, 166, 168, 169, 98, 117, 116, 117, 172, - 162, 164, 152, 209, 210, 210, 210, 184, 179, 183, 192, 164, 151, 157, 159, 150, 150, 148, 146, 150, - 159, 155, 154, 157, 170, 169, 170, 170, 117, 136, 134, 123, 192, 196, 196, 197, 179, 180, 180, 191, - 159, 155, 159, 164, 153, 151, 152, 150, 154, 160, 157, 155, 160, 170, 166, 167, 165, 120, 134, 135, - 139, 69, 87, 86, 90, 201, 199, 199, 208, 165, 166, 167, 177, 148, 145, 148, 151, 150, 155, 153, 150, - 157, 165, 162, 159, 165, 170, 169, 170, 166, 84, 107, 108, 111, 49, 66, 64, 71, 200, 199, 198, 208, - 171, 173, 174, 184, 155, 150, 155, 157, 152, 153, 153, 149, 156, 163, 160, 157, 162, 167, 165, 169, - 167, 97, 121, 121, 125, 60, 77, 75, 76, 204, 210, 210, 210, 179, 180, 181, 191, 158, 152, 156, 159, - 150, 150, 149, 146, 152, 159, 156, 153, 160, 170, 169, 170, 170, 112, 135, 136, 138, 71, 88, 86, 79, - 188, 188, 188, 197, 160, 162, 163, 170, 152, 150, 152, 151, 154, 157, 156, 152, 160, 167, 164, 164, - 161, 135, 146, 150, 143, 77, 98, 99, 103, 51, 62, 60, 62, 172, 166, 165, 174, 145, 145, 145, 150, - 152, 155, 154, 150, 160, 165, 163, 159, 168, 170, 168, 170, 151, 80, 104, 109, 101, 44, 63, 63, 66, - 55, 52, 53, 51, 175, 176, 175, 183, 151, 153, 155, 158, 151, 152, 152, 148, 157, 161, 160, 156, 164, - 168, 165, 169, 156, 100, 122, 126, 118, 60, 79, 79, 81, 54, 52, 52, 51, 177, 181, 180, 188, 153, - 153, 155, 159, 149, 150, 150, 146, 155, 159, 157, 153, 164, 170, 169, 170, 170, 109, 131, 136, 127, - 66, 86, 86, 87, 46, 43, 43, 47, 168, 170, 169, 175, 151, 151, 152, 153, 153, 155, 154, 150, 160, - 164, 162, 160, 161, 151, 157, 165, 144, 88, 109, 114, 105, 55, 69, 68, 67, 62, 56, 56, 59, 151, 145, - 145, 148, 154, 154, 154, 150, 162, 164, 163, 159, 170, 170, 167, 170, 135, 80, 100, 110, 89, 41, 61, - 64, 59, 56, 53, 50, 50, 94, 85, 86, 79, 154, 155, 155, 158, 152, 152, 152, 148, 159, 161, 160, 155, - 166, 169, 165, 169, 146, 104, 122, 131, 110, 61, 80, 83, 75, 53, 53, 48, 47, 84, 74, 75, 75, 154, - 154, 154, 158, 151, 150, 150, 146, 158, 159, 158, 154, 167, 170, 169, 170, 153, 108, 127, 136, 113, - 63, 83, 87, 78, 48, 46, 43, 41, 81, 71, 72, 74, 153, 153, 153, 155, 153, 152, 152, 148, 160, 161, - 159, 157, 165, 165, 166, 170, 143, 101, 118, 127, 104, 60, 75, 78, 70, 56, 51, 48, 46, 85, 76, 77, - 81, 152, 154, 154, 151, 164, 164, 163, 159, 170, 170, 167, 170, 121, 84, 98, 114, 78, 44, 60, 68, - 52, 56, 53, 48, 56, 96, 85, 85, 83, 107, 105, 106, 100, 151, 151, 152, 148, 160, 160, 160, 155, 169, - 170, 166, 169, 134, 108, 121, 135, 98, 63, 79, 87, 69, 53, 53, 46, 50, 85, 73, 73, 71, 104, 95, 96, - 97, 151, 151, 151, 148, 160, 159, 159, 155, 169, 170, 170, 170, 137, 108, 121, 136, 99, 63, 78, 87, - 67, 51, 48, 43, 48, 85, 73, 72, 71, 105, 96, 97, 98, 152, 150, 150, 147, 160, 159, 159, 155, 169, - 170, 169, 170, 140, 111, 125, 139, 102, 67, 81, 87, 67, 50, 47, 41, 46, 83, 71, 71, 70, 103, 96, 96, - 98, 160, 162, 163, 159, 170, 170, 167, 170, 109, 91, 98, 117, 70, 49, 60, 72, 49, 55, 54, 46, 62, - 95, 84, 81, 85, 107, 104, 105, 103, 96, 98, 97, 100, 160, 159, 160, 156, 170, 170, 167, 169, 122, - 111, 118, 136, 87, 66, 77, 88, 62, 52, 53, 43, 56, 85, 74, 71, 76, 105, 95, 96, 96, 98, 100, 100, - 100, 160, 160, 160, 156, 170, 170, 167, 170, 120, 109, 116, 134, 86, 64, 75, 86, 60, 53, 51, 43, 56, - 86, 75, 72, 77, 106, 96, 97, 96, 97, 100, 100, 100, 160, 160, 160, 159, 169, 170, 168, 170, 129, - 115, 117, 123, 90, 71, 76, 79, 62, 51, 51, 47, 59, 79, 75, 74, 81, 100, 97, 98, 98, 100, 100, 100, - 100 - }; - byte[] expectedU = - { - 90, 90, 59, 63, 36, 38, 23, 20, 34, 35, 47, 48, 70, 82, 104, 121, 121, 90, 90, 61, 69, 37, 42, 22, - 18, 33, 32, 47, 47, 67, 75, 97, 113, 120, 59, 61, 30, 37, 22, 20, 38, 36, 50, 50, 78, 83, 113, 122, - 142, 166, 164, 63, 69, 37, 43, 20, 18, 34, 32, 48, 47, 70, 73, 102, 110, 136, 166, 166, 36, 37, 22, - 20, 38, 35, 50, 49, 80, 80, 116, 119, 145, 165, 185, 197, 193, 38, 42, 20, 18, 35, 32, 48, 47, 72, - 72, 106, 108, 142, 165, 184, 191, 194, 23, 22, 38, 34, 50, 48, 81, 77, 117, 115, 150, 160, 184, 194, - 212, 220, 217, 20, 18, 36, 32, 49, 47, 76, 71, 111, 108, 148, 164, 185, 190, 208, 217, 219, 34, 33, - 50, 48, 80, 73, 116, 111, 150, 154, 184, 190, 213, 217, 226, 232, 232, 35, 32, 49, 47, 80, 72, 115, - 107, 154, 164, 187, 189, 211, 216, 228, 237, 235, 47, 46, 77, 70, 115, 106, 149, 148, 184, 187, 213, - 214, 226, 230, 227, 223, 224, 48, 47, 83, 73, 119, 108, 159, 164, 190, 189, 214, 216, 229, 236, 229, - 222, 220, 70, 67, 113, 101, 145, 142, 184, 185, 213, 211, 226, 229, 226, 226, 218, 211, 212, 82, 75, - 122, 110, 165, 165, 193, 190, 217, 216, 231, 236, 226, 222, 214, 208, 207, 104, 97, 142, 136, 186, - 184, 212, 208, 227, 228, 227, 229, 218, 214, 196, 185, 188, 121, 113, 166, 166, 197, 191, 220, 217, - 232, 237, 223, 222, 211, 208, 185, 173, 172, 121, 120, 164, 166, 193, 194, 217, 219, 232, 235, 224, - 220, 212, 207, 188, 172, 172 - }; - byte[] expectedV = - { - 240, 240, 201, 206, 172, 174, 136, 136, 92, 90, 55, 50, 37, 30, 26, 23, 23, 240, 240, 204, 213, 173, - 179, 141, 141, 96, 98, 56, 54, 38, 31, 27, 25, 23, 201, 204, 164, 172, 129, 135, 82, 87, 46, 47, 33, - 29, 25, 23, 20, 16, 16, 206, 213, 172, 180, 137, 141, 93, 99, 54, 54, 36, 31, 26, 25, 21, 17, 16, - 172, 173, 129, 138, 81, 89, 45, 49, 32, 30, 24, 24, 19, 16, 42, 55, 51, 174, 179, 136, 141, 89, 99, - 51, 55, 35, 31, 26, 25, 21, 17, 39, 48, 52, 136, 141, 82, 92, 45, 51, 31, 32, 24, 24, 19, 17, 43, - 51, 74, 85, 81, 136, 141, 87, 99, 49, 55, 32, 32, 25, 25, 20, 17, 41, 46, 69, 81, 83, 92, 96, 46, - 53, 32, 34, 24, 25, 18, 18, 44, 47, 76, 81, 103, 117, 116, 90, 97, 48, 54, 30, 31, 24, 25, 18, 17, - 43, 46, 74, 80, 103, 118, 122, 55, 57, 33, 36, 24, 26, 19, 20, 44, 43, 76, 77, 102, 111, 138, 159, - 157, 50, 54, 30, 31, 24, 25, 17, 17, 47, 46, 77, 79, 106, 118, 143, 164, 168, 37, 38, 25, 26, 19, - 21, 43, 41, 75, 73, 103, 106, 138, 152, 174, 195, 194, 30, 31, 23, 25, 16, 17, 51, 46, 81, 79, 111, - 118, 151, 164, 188, 205, 206, 26, 27, 20, 21, 43, 39, 74, 69, 103, 102, 138, 143, 174, 188, 204, - 216, 218, 23, 25, 16, 17, 55, 48, 85, 81, 117, 118, 159, 164, 195, 205, 216, 227, 227, 23, 23, 16, - 16, 51, 52, 81, 83, 116, 122, 157, 168, 194, 206, 218, 227, 227 - }; + 90, 90, 59, 63, 36, 38, 23, 20, 34, 35, 47, 48, 70, 82, 104, 121, 121, 90, 90, 61, 69, 37, 42, 22, + 18, 33, 32, 47, 47, 67, 75, 97, 113, 120, 59, 61, 30, 37, 22, 20, 38, 36, 50, 50, 78, 83, 113, 122, + 142, 166, 164, 63, 69, 37, 43, 20, 18, 34, 32, 48, 47, 70, 73, 102, 110, 136, 166, 166, 36, 37, 22, + 20, 38, 35, 50, 49, 80, 80, 116, 119, 145, 165, 185, 197, 193, 38, 42, 20, 18, 35, 32, 48, 47, 72, + 72, 106, 108, 142, 165, 184, 191, 194, 23, 22, 38, 34, 50, 48, 81, 77, 117, 115, 150, 160, 184, 194, + 212, 220, 217, 20, 18, 36, 32, 49, 47, 76, 71, 111, 108, 148, 164, 185, 190, 208, 217, 219, 34, 33, + 50, 48, 80, 73, 116, 111, 150, 154, 184, 190, 213, 217, 226, 232, 232, 35, 32, 49, 47, 80, 72, 115, + 107, 154, 164, 187, 189, 211, 216, 228, 237, 235, 47, 46, 77, 70, 115, 106, 149, 148, 184, 187, 213, + 214, 226, 230, 227, 223, 224, 48, 47, 83, 73, 119, 108, 159, 164, 190, 189, 214, 216, 229, 236, 229, + 222, 220, 70, 67, 113, 101, 145, 142, 184, 185, 213, 211, 226, 229, 226, 226, 218, 211, 212, 82, 75, + 122, 110, 165, 165, 193, 190, 217, 216, 231, 236, 226, 222, 214, 208, 207, 104, 97, 142, 136, 186, + 184, 212, 208, 227, 228, 227, 229, 218, 214, 196, 185, 188, 121, 113, 166, 166, 197, 191, 220, 217, + 232, 237, 223, 222, 211, 208, 185, 173, 172, 121, 120, 164, 166, 193, 194, 217, 219, 232, 235, 224, + 220, 212, 207, 188, 172, 172 + }; + byte[] expectedV = + { + 240, 240, 201, 206, 172, 174, 136, 136, 92, 90, 55, 50, 37, 30, 26, 23, 23, 240, 240, 204, 213, 173, + 179, 141, 141, 96, 98, 56, 54, 38, 31, 27, 25, 23, 201, 204, 164, 172, 129, 135, 82, 87, 46, 47, 33, + 29, 25, 23, 20, 16, 16, 206, 213, 172, 180, 137, 141, 93, 99, 54, 54, 36, 31, 26, 25, 21, 17, 16, + 172, 173, 129, 138, 81, 89, 45, 49, 32, 30, 24, 24, 19, 16, 42, 55, 51, 174, 179, 136, 141, 89, 99, + 51, 55, 35, 31, 26, 25, 21, 17, 39, 48, 52, 136, 141, 82, 92, 45, 51, 31, 32, 24, 24, 19, 17, 43, + 51, 74, 85, 81, 136, 141, 87, 99, 49, 55, 32, 32, 25, 25, 20, 17, 41, 46, 69, 81, 83, 92, 96, 46, + 53, 32, 34, 24, 25, 18, 18, 44, 47, 76, 81, 103, 117, 116, 90, 97, 48, 54, 30, 31, 24, 25, 18, 17, + 43, 46, 74, 80, 103, 118, 122, 55, 57, 33, 36, 24, 26, 19, 20, 44, 43, 76, 77, 102, 111, 138, 159, + 157, 50, 54, 30, 31, 24, 25, 17, 17, 47, 46, 77, 79, 106, 118, 143, 164, 168, 37, 38, 25, 26, 19, + 21, 43, 41, 75, 73, 103, 106, 138, 152, 174, 195, 194, 30, 31, 23, 25, 16, 17, 51, 46, 81, 79, 111, + 118, 151, 164, 188, 205, 206, 26, 27, 20, 21, 43, 39, 74, 69, 103, 102, 138, 143, 174, 188, 204, + 216, 218, 23, 25, 16, 17, 55, 48, 85, 81, 117, 118, 159, 164, 195, 205, 216, 227, 227, 23, 23, 16, + 16, 51, 52, 81, 83, 116, 122, 157, 168, 194, 206, 218, 227, 227 + }; - // act - YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); + // act + YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); - // assert - Assert.True(expectedY.AsSpan().SequenceEqual(y)); - Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); - Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); - } + // assert + Assert.True(expectedY.AsSpan().SequenceEqual(y)); + Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); + Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); + } - [Theory] - [WithFile(TestImages.Png.TestPattern31x31HalfTransparent, PixelTypes.Rgba32)] - public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // arrange - using Image image = provider.GetImage(); - Configuration config = image.GetConfiguration(); - MemoryAllocator memoryAllocator = config.MemoryAllocator; - int pixels = image.Width * image.Height; - int uvWidth = (image.Width + 1) >> 1; - using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + [Theory] + [WithFile(TestImages.Png.TestPattern31x31HalfTransparent, PixelTypes.Rgba32)] + public void ConvertRgbToYuv_WithAlpha_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + Configuration config = image.GetConfiguration(); + MemoryAllocator memoryAllocator = config.MemoryAllocator; + int pixels = image.Width * image.Height; + int uvWidth = (image.Width + 1) >> 1; + using System.Buffers.IMemoryOwner yBuffer = memoryAllocator.Allocate(pixels, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner uBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); + using System.Buffers.IMemoryOwner vBuffer = memoryAllocator.Allocate(uvWidth * image.Height, AllocationOptions.Clean); - Span y = yBuffer.GetSpan(); - Span u = uBuffer.GetSpan(); - Span v = vBuffer.GetSpan(); - byte[] expectedY = - { - 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, - 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, - 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, - 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, - 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, - 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, - 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, - 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, - 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, - 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 81, 158, 170, 118, 130, 182, 65, 142, 220, 103, 155, - 167, 115, 127, 204, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 145, 157, 106, - 118, 170, 118, 130, 207, 90, 142, 154, 103, 114, 192, 115, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 145, 93, 105, 157, 105, 117, 195, 78, 130, 142, 90, 102, 179, 102, 114, 192, - 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 92, 170, 93, 105, 182, 65, 142, 129, - 77, 155, 167, 90, 102, 179, 62, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 157, - 80, 92, 170, 52, 130, 117, 65, 142, 154, 102, 89, 166, 49, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, - 82, 82, 82, 82, 82, 82, 145, 197, 80, 157, 169, 117, 104, 181, 130, 142, 90, 77, 154, 37, 114, 191, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 209, 67, 144, 156, 105, 117, 169, 117, - 129, 206, 64, 141, 153, 102, 179, 166, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, - 55, 132, 144, 92, 169, 156, 104, 116, 194, 77, 129, 141, 89, 166, 178, 101, 81, 81, 81, 81, 81, 81, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 80, 157, 144, 92, 104, 181, 64, 116, 193, 76, 154, - 166, 89, 101, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 67, 144, 156, 79, 91, - 169, 52, 104, 181, 64, 141, 153, 76, 88, 165, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, - 81, 81, 183, 132, 144, 196, 79, 156, 39, 116, 168, 51, 129, 141, 89, 76, 153, 101, 81, 81, 81, 81, - 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 183, 66, 143, 155, 104, 156, 168, 116, 128, - 205, 63, 140, 218, 101, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 119, 196, 54, - 131, 208, 91, 143, 155, 103, 115, 193, 51, 128, 205, 88, 165, 41, 41, 41, 41, 41, 41, 41, 41, 41, - 41, 41, 41, 41, 41, 41, 183, 41, 118, 196, 79, 156, 143, 91, 103, 180, 63, 115, 193, 75, 153, 165, - 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 29, 106, 183, 66, 143, 130, 78, 90, 168, - 116, 103, 180, 63, 140, 152, 75, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 93, - 171, 53, 131, 118, 66, 78, 155, 103, 90, 167, 50, 128, 140, 63, 75 - }; - byte[] expectedU = - { - 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, - 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, - 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, - 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, - 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 139, - 229, 146, 204, 132, 199, 131, 204, 135, 90, 90, 90, 90, 90, 90, 90, 100, 161, 92, 116, 141, 99, 155, - 113, 97, 90, 90, 90, 90, 90, 90, 90, 173, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, - 96, 96, 148, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 108, 134, 130, 112, - 149, 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 107, 164, 117, 149, 127, 128, 166, 107, 129, - 159, 159, 159, 159, 159, 159, 159, 161, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, - 240, 240, 240, 137, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 109, - 150, 108, 140, 161, 80, 157, 162, 128 - }; - byte[] expectedV = - { - 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, - 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, - 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, - 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, - 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 188, - 109, 172, 108, 164, 119, 152, 92, 189, 240, 240, 240, 240, 240, 240, 240, 104, 121, 131, 142, 135, - 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 122, 136, 148, 137, 113, 157, 155, 121, 130, - 155, 155, 155, 155, 155, 155, 155, 109, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, - 81, 81, 143, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 63, 147, 133, 119, - 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 135, 109, 129, 122, 124, 107, 108, 128, - 138, 110, 110, 110, 110, 110, 110, 110, 117, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, - 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85 - }; + Span y = yBuffer.GetSpan(); + Span u = uBuffer.GetSpan(); + Span v = vBuffer.GetSpan(); + byte[] expectedY = + { + 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, + 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, + 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 235, 235, 235, 235, 235, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, + 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, + 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, + 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, + 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, + 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, 16, 16, 16, 16, 152, 41, 41, 152, 152, + 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 16, 16, 16, 16, 16, 235, 235, 235, 235, 235, 16, + 16, 16, 16, 16, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 152, 41, 41, 152, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 81, 158, 170, 118, 130, 182, 65, 142, 220, 103, 155, + 167, 115, 127, 204, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 145, 157, 106, + 118, 170, 118, 130, 207, 90, 142, 154, 103, 114, 192, 115, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 145, 93, 105, 157, 105, 117, 195, 78, 130, 142, 90, 102, 179, 102, 114, 192, + 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 92, 170, 93, 105, 182, 65, 142, 129, + 77, 155, 167, 90, 102, 179, 62, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 80, 157, + 80, 92, 170, 52, 130, 117, 65, 142, 154, 102, 89, 166, 49, 127, 82, 82, 82, 82, 82, 82, 82, 82, 82, + 82, 82, 82, 82, 82, 82, 145, 197, 80, 157, 169, 117, 104, 181, 130, 142, 90, 77, 154, 37, 114, 191, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 209, 67, 144, 156, 105, 117, 169, 117, + 129, 206, 64, 141, 153, 102, 179, 166, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 55, 132, 144, 92, 169, 156, 104, 116, 194, 77, 129, 141, 89, 166, 178, 101, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 80, 157, 144, 92, 104, 181, 64, 116, 193, 76, 154, + 166, 89, 101, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 67, 144, 156, 79, 91, + 169, 52, 104, 181, 64, 141, 153, 76, 88, 165, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 183, 132, 144, 196, 79, 156, 39, 116, 168, 51, 129, 141, 89, 76, 153, 101, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 119, 131, 183, 66, 143, 155, 104, 156, 168, 116, 128, + 205, 63, 140, 218, 101, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 119, 196, 54, + 131, 208, 91, 143, 155, 103, 115, 193, 51, 128, 205, 88, 165, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 183, 41, 118, 196, 79, 156, 143, 91, 103, 180, 63, 115, 193, 75, 153, 165, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 29, 106, 183, 66, 143, 130, 78, 90, 168, + 116, 103, 180, 63, 140, 152, 75, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 93, + 171, 53, 131, 118, 66, 78, 155, 103, 90, 167, 50, 128, 140, 63, 75 + }; + byte[] expectedU = + { + 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, + 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, + 240, 139, 240, 139, 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, + 128, 128, 128, 128, 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 128, 128, 128, 128, + 128, 128, 128, 139, 240, 139, 240, 139, 240, 139, 240, 139, 112, 112, 108, 106, 106, 112, 112, 139, + 229, 146, 204, 132, 199, 131, 204, 135, 90, 90, 90, 90, 90, 90, 90, 100, 161, 92, 116, 141, 99, 155, + 113, 97, 90, 90, 90, 90, 90, 90, 90, 173, 145, 114, 173, 122, 133, 127, 96, 170, 96, 96, 96, 96, 96, + 96, 96, 148, 98, 134, 122, 113, 139, 93, 169, 85, 91, 91, 91, 91, 91, 91, 91, 108, 134, 130, 112, + 149, 105, 139, 146, 110, 91, 91, 91, 91, 91, 91, 91, 107, 164, 117, 149, 127, 128, 166, 107, 129, + 159, 159, 159, 159, 159, 159, 159, 161, 112, 113, 138, 87, 143, 112, 88, 161, 240, 240, 240, 240, + 240, 240, 240, 137, 110, 162, 110, 140, 158, 104, 159, 137, 240, 240, 240, 240, 240, 240, 240, 109, + 150, 108, 140, 161, 80, 157, 162, 128 + }; + byte[] expectedV = + { + 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, + 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, + 110, 189, 110, 189, 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, + 128, 128, 128, 128, 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 128, 128, 128, 128, + 128, 128, 128, 189, 110, 189, 110, 189, 110, 189, 110, 189, 175, 175, 186, 193, 193, 175, 175, 188, + 109, 172, 108, 164, 119, 152, 92, 189, 240, 240, 240, 240, 240, 240, 240, 104, 121, 131, 142, 135, + 109, 92, 146, 115, 240, 240, 240, 240, 240, 240, 240, 122, 136, 148, 137, 113, 157, 155, 121, 130, + 155, 155, 155, 155, 155, 155, 155, 109, 135, 96, 88, 142, 136, 105, 138, 116, 81, 81, 81, 81, 81, + 81, 81, 143, 104, 148, 150, 111, 138, 128, 116, 141, 81, 81, 81, 81, 81, 81, 81, 63, 147, 133, 119, + 141, 165, 126, 147, 173, 101, 101, 101, 101, 101, 101, 101, 135, 109, 129, 122, 124, 107, 108, 128, + 138, 110, 110, 110, 110, 110, 110, 110, 117, 137, 151, 127, 114, 131, 139, 142, 120, 110, 110, 110, + 110, 110, 110, 110, 142, 156, 119, 137, 167, 141, 151, 66, 85 + }; - // act - YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); + // act + YuvConversion.ConvertRgbToYuv(image, config, memoryAllocator, y, u, v); - // assert - Assert.True(expectedY.AsSpan().SequenceEqual(y)); - Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); - Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); - } + // assert + Assert.True(expectedY.AsSpan().SequenceEqual(y)); + Assert.True(expectedU.AsSpan().SequenceEqual(u[..expectedU.Length])); + Assert.True(expectedV.AsSpan().SequenceEqual(v[..expectedV.Length])); } } diff --git a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs index 52509a4e4e..d663a803b3 100644 --- a/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicOptionsDefaultsExtensionsTests.cs @@ -1,187 +1,183 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Threading.Tasks; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.Processing; -using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class GraphicOptionsDefaultsExtensionsTests { - public class GraphicOptionsDefaultsExtensionsTests + [Fact] + public void SetDefaultOptionsOnProcessingContext() { - [Fact] - public void SetDefaultOptionsOnProcessingContext() - { - var option = new GraphicsOptions(); - var config = new Configuration(); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + var option = new GraphicsOptions(); + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); - context.SetGraphicsOptions(option); + context.SetGraphicsOptions(option); - // sets the prop on the processing context not on the configuration - Assert.Equal(option, context.Properties[typeof(GraphicsOptions)]); - Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); - } + // sets the prop on the processing context not on the configuration + Assert.Equal(option, context.Properties[typeof(GraphicsOptions)]); + Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); + } - [Fact] - public void UpdateDefaultOptionsOnProcessingContext_AlwaysNewInstance() + [Fact] + public void UpdateDefaultOptionsOnProcessingContext_AlwaysNewInstance() + { + var option = new GraphicsOptions() { - var option = new GraphicsOptions() - { - BlendPercentage = 0.9f - }; - var config = new Configuration(); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); - context.SetGraphicsOptions(option); - - context.SetGraphicsOptions(o => - { - Assert.Equal(0.9f, o.BlendPercentage); // has origional values - o.BlendPercentage = 0.4f; - }); - - var returnedOption = context.GetGraphicsOptions(); - Assert.Equal(0.4f, returnedOption.BlendPercentage); - Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated - } - - [Fact] - public void SetDefaultOptionsOnConfiguration() + BlendPercentage = 0.9f + }; + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.SetGraphicsOptions(option); + + context.SetGraphicsOptions(o => { - var option = new GraphicsOptions(); - var config = new Configuration(); + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); - config.SetGraphicsOptions(option); + var returnedOption = context.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } + + [Fact] + public void SetDefaultOptionsOnConfiguration() + { + var option = new GraphicsOptions(); + var config = new Configuration(); - Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]); - } + config.SetGraphicsOptions(option); + + Assert.Equal(option, config.Properties[typeof(GraphicsOptions)]); + } - [Fact] - public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance() + [Fact] + public void UpdateDefaultOptionsOnConfiguration_AlwaysNewInstance() + { + var option = new GraphicsOptions() { - var option = new GraphicsOptions() - { - BlendPercentage = 0.9f - }; - var config = new Configuration(); - config.SetGraphicsOptions(option); - - config.SetGraphicsOptions(o => - { - Assert.Equal(0.9f, o.BlendPercentage); // has origional values - o.BlendPercentage = 0.4f; - }); - - var returnedOption = config.GetGraphicsOptions(); - Assert.Equal(0.4f, returnedOption.BlendPercentage); - Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated - } - - [Fact] - public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance() + BlendPercentage = 0.9f + }; + var config = new Configuration(); + config.SetGraphicsOptions(option); + + config.SetGraphicsOptions(o => { - var config = new Configuration(); + Assert.Equal(0.9f, o.BlendPercentage); // has origional values + o.BlendPercentage = 0.4f; + }); - var options = config.GetGraphicsOptions(); - Assert.NotNull(options); - config.SetGraphicsOptions((GraphicsOptions)null); + var returnedOption = config.GetGraphicsOptions(); + Assert.Equal(0.4f, returnedOption.BlendPercentage); + Assert.Equal(0.9f, option.BlendPercentage); // hasn't been mutated + } - var options2 = config.GetGraphicsOptions(); - Assert.NotNull(options2); + [Fact] + public void GetDefaultOptionsFromConfiguration_SettingNullThenReturnsNewInstance() + { + var config = new Configuration(); - // we set it to null should now be a new instance - Assert.NotEqual(options, options2); - } + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + config.SetGraphicsOptions((GraphicsOptions)null); - [Fact] - public void GetDefaultOptionsFromConfiguration_IgnoreIncorectlyTypesDictionEntry() - { - var config = new Configuration(); + var options2 = config.GetGraphicsOptions(); + Assert.NotNull(options2); - config.Properties[typeof(GraphicsOptions)] = "wronge type"; - var options = config.GetGraphicsOptions(); - Assert.NotNull(options); - Assert.IsType(options); - } + // we set it to null should now be a new instance + Assert.NotEqual(options, options2); + } - [Fact] - public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance() - { - var config = new Configuration(); + [Fact] + public void GetDefaultOptionsFromConfiguration_IgnoreIncorectlyTypesDictionEntry() + { + var config = new Configuration(); - Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); - var options = config.GetGraphicsOptions(); - Assert.NotNull(options); - } + config.Properties[typeof(GraphicsOptions)] = "wronge type"; + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + Assert.IsType(options); + } - [Fact] - public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue() - { - var config = new Configuration(); + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsInstance() + { + var config = new Configuration(); - var options = config.GetGraphicsOptions(); - var options2 = config.GetGraphicsOptions(); - Assert.Equal(options, options2); - } + Assert.DoesNotContain(typeof(GraphicsOptions), config.Properties.Keys); + var options = config.GetGraphicsOptions(); + Assert.NotNull(options); + } - [Fact] - public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance() - { - var config = new Configuration(); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + [Fact] + public void GetDefaultOptionsFromConfiguration_AlwaysReturnsSameValue() + { + var config = new Configuration(); - var ctxOptions = context.GetGraphicsOptions(); - Assert.NotNull(ctxOptions); - } + var options = config.GetGraphicsOptions(); + var options2 = config.GetGraphicsOptions(); + Assert.Equal(options, options2); + } - [Fact] - public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull() - { - var config = new Configuration(); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstance() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); - context.SetGraphicsOptions((GraphicsOptions)null); - var ctxOptions = context.GetGraphicsOptions(); - Assert.NotNull(ctxOptions); - } + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } - [Fact] - public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance() - { - var option = new GraphicsOptions(); - var config = new Configuration(); - config.SetGraphicsOptions(option); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + [Fact] + public void GetDefaultOptionsFromProcessingContext_AlwaysReturnsInstanceEvenIfSetToNull() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); - var ctxOptions = context.GetGraphicsOptions(); - Assert.Equal(option, ctxOptions); - } + context.SetGraphicsOptions((GraphicsOptions)null); + var ctxOptions = context.GetGraphicsOptions(); + Assert.NotNull(ctxOptions); + } - [Fact] - public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry() - { - var config = new Configuration(); - var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); - context.Properties[typeof(GraphicsOptions)] = "wronge type"; - var options = context.GetGraphicsOptions(); - Assert.NotNull(options); - Assert.IsType(options); - } - - [Theory] - [WithBlankImages(100, 100, PixelTypes.Rgba32)] - public void CanGetGraphicsOptionsMultiThreaded(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Fact] + public void GetDefaultOptionsFromProcessingContext_FallbackToConfigsInstance() + { + var option = new GraphicsOptions(); + var config = new Configuration(); + config.SetGraphicsOptions(option); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + + var ctxOptions = context.GetGraphicsOptions(); + Assert.Equal(option, ctxOptions); + } + + [Fact] + public void GetDefaultOptionsFromProcessingContext_IgnoreIncorectlyTypesDictionEntry() + { + var config = new Configuration(); + var context = new FakeImageOperationsProvider.FakeImageOperations(config, null, true); + context.Properties[typeof(GraphicsOptions)] = "wronge type"; + var options = context.GetGraphicsOptions(); + Assert.NotNull(options); + Assert.IsType(options); + } + + [Theory] + [WithBlankImages(100, 100, PixelTypes.Rgba32)] + public void CanGetGraphicsOptionsMultiThreaded(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Could not get fake operations to trigger #1230 so using a real image. + Parallel.For(0, 10, _ => { - // Could not get fake operations to trigger #1230 so using a real image. - Parallel.For(0, 10, _ => - { - using Image image = provider.GetImage(); - image.Mutate(x => x.BackgroundColor(Color.White)); - }); - } + using Image image = provider.GetImage(); + image.Mutate(x => x.BackgroundColor(Color.White)); + }); } } diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index 73fcfce35b..3531599866 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -3,88 +3,86 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class GraphicsOptionsTests { - public class GraphicsOptionsTests - { - private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); - private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions(); - private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); + private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions(); + private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); - [Fact] - public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + [Fact] + public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); - [Fact] - public void DefaultGraphicsOptionsAntialias() - { - Assert.True(this.newGraphicsOptions.Antialias); - Assert.True(this.cloneGraphicsOptions.Antialias); - } + [Fact] + public void DefaultGraphicsOptionsAntialias() + { + Assert.True(this.newGraphicsOptions.Antialias); + Assert.True(this.cloneGraphicsOptions.Antialias); + } - [Fact] - public void DefaultGraphicsOptionsAntialiasSuppixelDepth() - { - const int Expected = 16; - Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); - Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); - } + [Fact] + public void DefaultGraphicsOptionsAntialiasSuppixelDepth() + { + const int Expected = 16; + Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); + Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); + } - [Fact] - public void DefaultGraphicsOptionsBlendPercentage() - { - const float Expected = 1F; - Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); - Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); - } + [Fact] + public void DefaultGraphicsOptionsBlendPercentage() + { + const float Expected = 1F; + Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); + Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); + } - [Fact] - public void DefaultGraphicsOptionsColorBlendingMode() - { - const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; - Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); - } + [Fact] + public void DefaultGraphicsOptionsColorBlendingMode() + { + const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; + Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); + Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); + } - [Fact] - public void DefaultGraphicsOptionsAlphaCompositionMode() - { - const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; - Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); - } + [Fact] + public void DefaultGraphicsOptionsAlphaCompositionMode() + { + const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; + Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); + Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); + } - [Fact] - public void NonDefaultClone() + [Fact] + public void NonDefaultClone() + { + var expected = new GraphicsOptions { - var expected = new GraphicsOptions - { - AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, - Antialias = false, - AntialiasSubpixelDepth = 23, - BlendPercentage = .25F, - ColorBlendingMode = PixelColorBlendingMode.HardLight, - }; + AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, + Antialias = false, + AntialiasSubpixelDepth = 23, + BlendPercentage = .25F, + ColorBlendingMode = PixelColorBlendingMode.HardLight, + }; - GraphicsOptions actual = expected.DeepClone(); + GraphicsOptions actual = expected.DeepClone(); - Assert.Equal(expected, actual, GraphicsOptionsComparer); - } + Assert.Equal(expected, actual, GraphicsOptionsComparer); + } - [Fact] - public void CloneIsDeep() - { - var expected = new GraphicsOptions(); - GraphicsOptions actual = expected.DeepClone(); + [Fact] + public void CloneIsDeep() + { + var expected = new GraphicsOptions(); + GraphicsOptions actual = expected.DeepClone(); - actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; - actual.Antialias = false; - actual.AntialiasSubpixelDepth = 23; - actual.BlendPercentage = .25F; - actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; + actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; + actual.Antialias = false; + actual.AntialiasSubpixelDepth = 23; + actual.BlendPercentage = .25F; + actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; - Assert.NotEqual(expected, actual, GraphicsOptionsComparer); - } + Assert.NotEqual(expected, actual, GraphicsOptionsComparer); } } diff --git a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs index 929be9f2b9..4c06d0cd55 100644 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs @@ -2,29 +2,27 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class ColorNumericsTests { - public class ColorNumericsTests + [Theory] + [InlineData(0.2f, 0.7f, 0.1f, 256, 140)] + [InlineData(0.5f, 0.5f, 0.5f, 256, 128)] + [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)] + [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)] + public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected) { - [Theory] - [InlineData(0.2f, 0.7f, 0.1f, 256, 140)] - [InlineData(0.5f, 0.5f, 0.5f, 256, 128)] - [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)] - [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)] - public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected) - { - // arrange - var vector = new Vector4(x, y, z, 0.0f); - - // act - int actual = ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + // arrange + var vector = new Vector4(x, y, z, 0.0f); - // assert - Assert.Equal(expected, actual); - } + // act + int actual = ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); - // TODO: We need to test all ColorNumerics methods! + // assert + Assert.Equal(expected, actual); } + + // TODO: We need to test all ColorNumerics methods! } diff --git a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs index 9287fb87f4..75f988a4cb 100644 --- a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs @@ -1,309 +1,305 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class NumericsTests { - public class NumericsTests + private delegate void SpanAction(Span span, TArg arg, TArg1 arg1); + + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + public void Modulo2(int x) { - private delegate void SpanAction(Span span, TArg arg, TArg1 arg1); + int actual = Numerics.Modulo2(x); + Assert.Equal(x % 2, actual); + } - private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + public void Modulo4(int x) + { + int actual = Numerics.Modulo4(x); + Assert.Equal(x % 4, actual); + } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - public void Modulo2(int x) - { - int actual = Numerics.Modulo2(x); - Assert.Equal(x % 2, actual); - } + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(100)] + [InlineData(123)] + [InlineData(53436353)] + [InlineData(975)] + public void Modulo8(int x) + { + int actual = Numerics.Modulo8(x); + Assert.Equal(x % 8, actual); + } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - public void Modulo4(int x) - { - int actual = Numerics.Modulo4(x); - Assert.Equal(x % 4, actual); - } + [Theory] + [InlineData(0, 2)] + [InlineData(1, 2)] + [InlineData(2, 2)] + [InlineData(0, 4)] + [InlineData(3, 4)] + [InlineData(5, 4)] + [InlineData(5, 8)] + [InlineData(8, 8)] + [InlineData(8, 16)] + [InlineData(15, 16)] + [InlineData(17, 16)] + [InlineData(17, 32)] + [InlineData(31, 32)] + [InlineData(32, 32)] + [InlineData(33, 32)] + public void Modulo2P(int x, int m) + { + int actual = Numerics.ModuloP2(x, m); + Assert.Equal(x % m, actual); + } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - [InlineData(6)] - [InlineData(7)] - [InlineData(8)] - [InlineData(100)] - [InlineData(123)] - [InlineData(53436353)] - [InlineData(975)] - public void Modulo8(int x) - { - int actual = Numerics.Modulo8(x); - Assert.Equal(x % 8, actual); - } + [Theory] + [InlineData(-5)] + [InlineData(-17)] + [InlineData(-12856)] + [InlineData(-32)] + [InlineData(-7425)] + [InlineData(5)] + [InlineData(17)] + [InlineData(12856)] + [InlineData(32)] + [InlineData(7425)] + public void Abs(int x) + { + int expected = Math.Abs(x); + Assert.Equal(expected, Numerics.Abs(x)); + } - [Theory] - [InlineData(0, 2)] - [InlineData(1, 2)] - [InlineData(2, 2)] - [InlineData(0, 4)] - [InlineData(3, 4)] - [InlineData(5, 4)] - [InlineData(5, 8)] - [InlineData(8, 8)] - [InlineData(8, 16)] - [InlineData(15, 16)] - [InlineData(17, 16)] - [InlineData(17, 32)] - [InlineData(31, 32)] - [InlineData(32, 32)] - [InlineData(33, 32)] - public void Modulo2P(int x, int m) - { - int actual = Numerics.ModuloP2(x, m); - Assert.Equal(x % m, actual); - } + [Theory] + [InlineData(-5)] + [InlineData(-17)] + [InlineData(-12856)] + [InlineData(-32)] + [InlineData(-7425)] + [InlineData(5)] + [InlineData(17)] + [InlineData(12856)] + [InlineData(32)] + [InlineData(7425)] + public void Pow2(float x) + { + float expected = (float)Math.Pow(x, 2); + Assert.Equal(expected, Numerics.Pow2(x)); + } - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(-7425)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - [InlineData(7425)] - public void Abs(int x) - { - int expected = Math.Abs(x); - Assert.Equal(expected, Numerics.Abs(x)); - } + [Theory] + [InlineData(-5)] + [InlineData(-17)] + [InlineData(-12856)] + [InlineData(-32)] + [InlineData(5)] + [InlineData(17)] + [InlineData(12856)] + [InlineData(32)] + public void Pow3(float x) + { + float expected = (float)Math.Pow(x, 3); + Assert.Equal(expected, Numerics.Pow3(x)); + } - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(-7425)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - [InlineData(7425)] - public void Pow2(float x) - { - float expected = (float)Math.Pow(x, 2); - Assert.Equal(expected, Numerics.Pow2(x)); - } + [Theory] + [InlineData(1, 1, 1)] + [InlineData(1, 42, 1)] + [InlineData(10, 8, 2)] + [InlineData(12, 18, 6)] + [InlineData(4536, 1000, 8)] + [InlineData(1600, 1024, 64)] + public void GreatestCommonDivisor(int a, int b, int expected) + { + int actual = Numerics.GreatestCommonDivisor(a, b); + Assert.Equal(expected, actual); + } - [Theory] - [InlineData(-5)] - [InlineData(-17)] - [InlineData(-12856)] - [InlineData(-32)] - [InlineData(5)] - [InlineData(17)] - [InlineData(12856)] - [InlineData(32)] - public void Pow3(float x) - { - float expected = (float)Math.Pow(x, 3); - Assert.Equal(expected, Numerics.Pow3(x)); - } + [Theory] + [InlineData(1, 1, 1)] + [InlineData(1, 42, 42)] + [InlineData(3, 4, 12)] + [InlineData(6, 4, 12)] + [InlineData(1600, 1024, 25600)] + [InlineData(3264, 100, 81600)] + public void LeastCommonMultiple(int a, int b, int expected) + { + int actual = Numerics.LeastCommonMultiple(a, b); + Assert.Equal(expected, actual); + } - [Theory] - [InlineData(1, 1, 1)] - [InlineData(1, 42, 1)] - [InlineData(10, 8, 2)] - [InlineData(12, 18, 6)] - [InlineData(4536, 1000, 8)] - [InlineData(1600, 1024, 64)] - public void GreatestCommonDivisor(int a, int b, int expected) + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + [InlineData(63)] + public void PremultiplyVectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { - int actual = Numerics.GreatestCommonDivisor(a, b); - Assert.Equal(expected, actual); - } + Numerics.Premultiply(ref v); + return v; + }).ToArray(); - [Theory] - [InlineData(1, 1, 1)] - [InlineData(1, 42, 42)] - [InlineData(3, 4, 12)] - [InlineData(6, 4, 12)] - [InlineData(1600, 1024, 25600)] - [InlineData(3264, 100, 81600)] - public void LeastCommonMultiple(int a, int b, int expected) - { - int actual = Numerics.LeastCommonMultiple(a, b); - Assert.Equal(expected, actual); - } + Numerics.Premultiply(source); + + Assert.Equal(expected, source, this.approximateFloatComparer); + } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] - public void PremultiplyVectorSpan(int length) + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + [InlineData(63)] + public void UnPremultiplyVectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => - { - Numerics.Premultiply(ref v); - return v; - }).ToArray(); + Numerics.UnPremultiply(ref v); + return v; + }).ToArray(); - Numerics.Premultiply(source); + Numerics.UnPremultiply(source); - Assert.Equal(expected, source, this.approximateFloatComparer); - } + Assert.Equal(expected, source, this.approximateFloatComparer); + } - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] - public void UnPremultiplyVectorSpan(int length) - { - var rnd = new Random(42); - Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => - { - Numerics.UnPremultiply(ref v); - return v; - }).ToArray(); + [Theory] + [InlineData(64, 36, 96)] + [InlineData(128, 16, 196)] + [InlineData(567, 18, 142)] + [InlineData(1024, 0, 255)] + public void ClampByte(int length, byte min, byte max) + { + TestClampSpan( + length, + min, + max, + (s, m1, m2) => Numerics.Clamp(s, m1, m2), + (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + } - Numerics.UnPremultiply(source); + [Theory] + [InlineData(64, 36, 96)] + [InlineData(128, 16, 196)] + [InlineData(567, 18, 142)] + [InlineData(1024, 0, 255)] + public void ClampInt(int length, int min, int max) + { + TestClampSpan( + length, + min, + max, + (s, m1, m2) => Numerics.Clamp(s, m1, m2), + (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + } - Assert.Equal(expected, source, this.approximateFloatComparer); - } + [Theory] + [InlineData(64, 36, 96)] + [InlineData(128, 16, 196)] + [InlineData(567, 18, 142)] + [InlineData(1024, 0, 255)] + public void ClampUInt(int length, uint min, uint max) + { + TestClampSpan( + length, + min, + max, + (s, m1, m2) => Numerics.Clamp(s, m1, m2), + (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + } - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampByte(int length, byte min, byte max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } + [Theory] + [InlineData(64, 36, 96)] + [InlineData(128, 16, 196)] + [InlineData(567, 18, 142)] + [InlineData(1024, 0, 255)] + public void ClampFloat(int length, float min, float max) + { + TestClampSpan( + length, + min, + max, + (s, m1, m2) => Numerics.Clamp(s, m1, m2), + (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + } - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampInt(int length, int min, int max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } + [Theory] + [InlineData(64, 36, 96)] + [InlineData(128, 16, 196)] + [InlineData(567, 18, 142)] + [InlineData(1024, 0, 255)] + public void ClampDouble(int length, double min, double max) + { + TestClampSpan( + length, + min, + max, + (s, m1, m2) => Numerics.Clamp(s, m1, m2), + (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + } - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampUInt(int length, uint min, uint max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } + private static void TestClampSpan( + int length, + T min, + T max, + SpanAction clampAction, + Func refClampFunc) + where T : unmanaged, IComparable + { + Span actual = new T[length]; - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampFloat(int length, float min, float max) + var r = new Random(); + for (int i = 0; i < length; i++) { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); + actual[i] = (T)Convert.ChangeType(r.Next(byte.MinValue, byte.MaxValue), typeof(T)); } - [Theory] - [InlineData(64, 36, 96)] - [InlineData(128, 16, 196)] - [InlineData(567, 18, 142)] - [InlineData(1024, 0, 255)] - public void ClampDouble(int length, double min, double max) - { - TestClampSpan( - length, - min, - max, - (s, m1, m2) => Numerics.Clamp(s, m1, m2), - (v, m1, m2) => Numerics.Clamp(v, m1, m2)); - } + Span expected = new T[length]; + actual.CopyTo(expected); - private static void TestClampSpan( - int length, - T min, - T max, - SpanAction clampAction, - Func refClampFunc) - where T : unmanaged, IComparable + for (int i = 0; i < expected.Length; i++) { - Span actual = new T[length]; - - var r = new Random(); - for (int i = 0; i < length; i++) - { - actual[i] = (T)Convert.ChangeType(r.Next(byte.MinValue, byte.MaxValue), typeof(T)); - } - - Span expected = new T[length]; - actual.CopyTo(expected); - - for (int i = 0; i < expected.Length; i++) - { - ref T v = ref expected[i]; - v = refClampFunc(v, min, max); - } + ref T v = ref expected[i]; + v = refClampFunc(v, min, max); + } - clampAction(actual, min, max); + clampAction(actual, min, max); - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); } } } diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs index a6d5d4a8dd..c13a30052c 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs @@ -1,40 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class ParallelExecutionSettingsTests { - public class ParallelExecutionSettingsTests + [Theory] + [InlineData(-3, true)] + [InlineData(-2, true)] + [InlineData(-1, false)] + [InlineData(0, true)] + [InlineData(1, false)] + [InlineData(5, false)] + public void Constructor_MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) { - [Theory] - [InlineData(-3, true)] - [InlineData(-2, true)] - [InlineData(-1, false)] - [InlineData(0, true)] - [InlineData(1, false)] - [InlineData(5, false)] - public void Constructor_MaxDegreeOfParallelism_CompatibleWith_ParallelOptions(int maxDegreeOfParallelism, bool throws) + if (throws) + { + Assert.Throws( + () => + { + _ = new ParallelExecutionSettings( + maxDegreeOfParallelism, + Configuration.Default.MemoryAllocator); + }); + } + else { - if (throws) - { - Assert.Throws( - () => - { - _ = new ParallelExecutionSettings( - maxDegreeOfParallelism, - Configuration.Default.MemoryAllocator); - }); - } - else - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - Configuration.Default.MemoryAllocator); - Assert.Equal(maxDegreeOfParallelism, parallelSettings.MaxDegreeOfParallelism); - } + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + Configuration.Default.MemoryAllocator); + Assert.Equal(maxDegreeOfParallelism, parallelSettings.MaxDegreeOfParallelism); } } } diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 3c85e0e2c0..e77d8fee42 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -1,435 +1,430 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class ParallelRowIteratorTests { - public class ParallelRowIteratorTests - { - public delegate void RowIntervalAction(RowInterval rows, Span span); + public delegate void RowIntervalAction(RowInterval rows, Span span); - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper output; - public ParallelRowIteratorTests(ITestOutputHelper output) - { - this.output = output; - } + public ParallelRowIteratorTests(ITestOutputHelper output) + { + this.output = output; + } - /// - /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength - /// - 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))] - public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) + /// + /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength + /// + public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = + new TheoryData { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); + { 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))] + public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); - var rectangle = new Rectangle(0, minY, 10, maxY - minY); + var rectangle = new Rectangle(0, minY, 10, maxY - minY); - int actualNumberOfSteps = 0; + int actualNumberOfSteps = 0; - void RowAction(RowInterval 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); + } - var operation = new TestRowIntervalOperation(RowAction); + var operation = new TestRowIntervalOperation(RowAction); - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); - } + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); - var rectangle = new Rectangle(0, minY, 10, maxY - minY); + 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]; + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + var actualData = new int[maxY]; - void RowAction(RowInterval rows) + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } + actualData[y] = y; } - - var operation = new TestRowIntervalOperation(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); - - Assert.Equal(expectedData, actualData); } - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); + var operation = new TestRowIntervalOperation(RowAction); - var rectangle = new Rectangle(0, minY, 10, maxY - minY); + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); - int actualNumberOfSteps = 0; + Assert.Equal(expectedData, actualData); + } - void RowAction(RowInterval rows, Span buffer) - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + var rectangle = new Rectangle(0, minY, 10, maxY - minY); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } + int actualNumberOfSteps = 0; - var operation = new TestRowIntervalOperation(RowAction); + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); } - [Theory] - [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( - int maxDegreeOfParallelism, - int minY, - int maxY, - int expectedStepLength, - int expectedLastStepLength, - int expectedNumberOfSteps) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - 1, - Configuration.Default.MemoryAllocator); + var operation = new TestRowIntervalOperation(RowAction); - var rectangle = new Rectangle(0, minY, 10, maxY - minY); + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); - var actualData = new int[maxY]; + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } - void RowAction(RowInterval rows, Span buffer) - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - } + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength, + int expectedNumberOfSteps) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); - var operation = new TestRowIntervalOperation(RowAction); + var rectangle = new Rectangle(0, minY, 10, maxY - minY); - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + var actualData = new int[maxY]; - Assert.Equal(expectedData, actualData); + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } } - 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 }, - }; - - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRows_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); + var operation = new TestRowIntervalOperation(RowAction); - var rectangle = new Rectangle(0, 0, width, height); + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); - int actualNumberOfSteps = 0; + Assert.Equal(expectedData, actualData); + } - void RowAction(RowInterval rows) - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); + 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 }, + }; + + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRows_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + var rectangle = new Rectangle(0, 0, width, height); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } + int actualNumberOfSteps = 0; - var operation = new TestRowIntervalOperation(RowAction); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - ParallelRowIterator.IterateRowIntervals( - rectangle, - in parallelSettings, - in operation); + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); } - [Theory] - [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] - public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( - int maxDegreeOfParallelism, - int minimumPixelsProcessedPerTask, - int width, - int height, - int expectedNumberOfSteps, - int expectedStepLength, - int expectedLastStepLength) - { - var parallelSettings = new ParallelExecutionSettings( - maxDegreeOfParallelism, - minimumPixelsProcessedPerTask, - Configuration.Default.MemoryAllocator); + var operation = new TestRowIntervalOperation(RowAction); - var rectangle = new Rectangle(0, 0, width, height); + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); - int actualNumberOfSteps = 0; + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } - void RowAction(RowInterval rows, Span buffer) - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); + [Theory] + [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + int width, + int height, + int expectedNumberOfSteps, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + minimumPixelsProcessedPerTask, + Configuration.Default.MemoryAllocator); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + var rectangle = new Rectangle(0, 0, width, height); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - } + int actualNumberOfSteps = 0; - var operation = new TestRowIntervalOperation(RowAction); + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - ParallelRowIterator.IterateRowIntervals, Vector4>( - rectangle, - in parallelSettings, - in operation); + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); } - 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 }, - }; - - [Theory] - [MemberData(nameof(IterateRectangularBuffer_Data))] - public void IterateRectangularBuffer( - int maxDegreeOfParallelism, - int bufferWidth, - int bufferHeight, - int rectX, - int rectY, - int rectWidth, - int rectHeight) - { - MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; + var operation = new TestRowIntervalOperation(RowAction); - using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - { - var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); - void FillRow(int y, Buffer2D buffer) - { - for (int x = rect.Left; x < rect.Right; x++) - { - buffer[x, y] = new Point(x, y); - } - } + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } - // Fill Expected data: - for (int y = rectY; y < rect.Bottom; y++) - { - FillRow(y, expected); - } + 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 }, + }; + + [Theory] + [MemberData(nameof(IterateRectangularBuffer_Data))] + public void IterateRectangularBuffer( + int maxDegreeOfParallelism, + int bufferWidth, + int bufferHeight, + int rectX, + int rectY, + int rectWidth, + int rectHeight) + { + MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; - // Fill actual data using IterateRows: - var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + { + var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); - void RowAction(RowInterval rows) + void FillRow(int y, Buffer2D buffer) + { + for (int x = rect.Left; x < rect.Right; x++) { - this.output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } + buffer[x, y] = new Point(x, y); } - - var operation = new TestRowIntervalOperation(RowAction); - - ParallelRowIterator.IterateRowIntervals( - rect, - settings, - in operation); - - // Assert: - TestImageExtensions.CompareBuffers(expected, actual); } - } - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsRequiresValidRectangle(int width, int height) - { - var parallelSettings = default(ParallelExecutionSettings); + // Fill Expected data: + for (int y = rectY; y < rect.Bottom; y++) + { + FillRow(y, expected); + } - var rect = new Rectangle(0, 0, width, height); + // Fill actual data using IterateRows: + var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); 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); - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); + ParallelRowIterator.IterateRowIntervals( + rect, + settings, + in operation); - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + // Assert: + TestImageExtensions.CompareBuffers(expected, actual); } + } - [Theory] - [InlineData(0, 10)] - [InlineData(10, 0)] - [InlineData(-10, 10)] - [InlineData(10, -10)] - public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsRequiresValidRectangle(int width, int height) + { + var parallelSettings = default(ParallelExecutionSettings); + + var rect = new Rectangle(0, 0, width, height); + + void RowAction(RowInterval rows) { - var parallelSettings = default(ParallelExecutionSettings); + } - var rect = new Rectangle(0, 0, width, height); + var operation = new TestRowIntervalOperation(RowAction); - void RowAction(RowInterval rows, Span memory) - { - } + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); + + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } - var operation = new TestRowIntervalOperation(RowAction); + [Theory] + [InlineData(0, 10)] + [InlineData(10, 0)] + [InlineData(-10, 10)] + [InlineData(10, -10)] + public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) + { + var parallelSettings = default(ParallelExecutionSettings); - ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); + var rect = new Rectangle(0, 0, width, height); - Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + void RowAction(RowInterval rows, Span memory) + { } - private readonly struct TestRowIntervalOperation : IRowIntervalOperation - { - private readonly Action action; + var operation = new TestRowIntervalOperation(RowAction); - public TestRowIntervalOperation(Action action) - => this.action = action; + ArgumentOutOfRangeException ex = Assert.Throws( + () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); - public void Invoke(in RowInterval rows) => this.action(rows); - } + Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); + } - private readonly struct TestRowIntervalOperation : IRowIntervalOperation - where TBuffer : unmanaged - { - private readonly RowIntervalAction action; + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; - public TestRowIntervalOperation(RowIntervalAction action) - => this.action = action; + public TestRowIntervalOperation(Action action) + => this.action = action; - public void Invoke(in RowInterval rows, Span span) - => this.action(rows, span); - } + 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 787a75d61f..95f1d4e289 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -1,62 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class RowIntervalTests { - public class RowIntervalTests + [Fact] + public void Slice1() + { + var rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(5); + + Assert.Equal(15, sliced.Min); + Assert.Equal(20, sliced.Max); + } + + [Fact] + public void Slice2() + { + var rowInterval = new RowInterval(10, 20); + RowInterval sliced = rowInterval.Slice(3, 5); + + Assert.Equal(13, sliced.Min); + Assert.Equal(18, sliced.Max); + } + + [Fact] + public void Equality_WhenTrue() + { + var a = new RowInterval(42, 123); + var b = new RowInterval(42, 123); + + Assert.True(a.Equals(b)); + Assert.True(a.Equals((object)b)); + Assert.True(a == b); + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() { - [Fact] - public void Slice1() - { - var rowInterval = new RowInterval(10, 20); - RowInterval sliced = rowInterval.Slice(5); - - Assert.Equal(15, sliced.Min); - Assert.Equal(20, sliced.Max); - } - - [Fact] - public void Slice2() - { - var rowInterval = new RowInterval(10, 20); - RowInterval sliced = rowInterval.Slice(3, 5); - - Assert.Equal(13, sliced.Min); - Assert.Equal(18, sliced.Max); - } - - [Fact] - public void Equality_WhenTrue() - { - var a = new RowInterval(42, 123); - var b = new RowInterval(42, 123); - - Assert.True(a.Equals(b)); - Assert.True(a.Equals((object)b)); - Assert.True(a == b); - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - } - - [Fact] - public void Equality_WhenFalse() - { - var a = new RowInterval(42, 123); - var b = new RowInterval(42, 125); - var c = new RowInterval(40, 123); - - Assert.False(a.Equals(b)); - Assert.False(c.Equals(a)); - Assert.False(b.Equals(c)); - - Assert.False(a.Equals((object)b)); - Assert.False(a.Equals(null)); - Assert.False(a == b); - Assert.True(a != c); - } + var a = new RowInterval(42, 123); + var b = new RowInterval(42, 125); + var c = new RowInterval(40, 123); + + Assert.False(a.Equals(b)); + Assert.False(c.Equals(a)); + Assert.False(b.Equals(c)); + + Assert.False(a.Equals((object)b)); + Assert.False(a.Equals(null)); + Assert.False(a == b); + Assert.True(a != c); } } diff --git a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs index 8b8119121d..64f79840ca 100644 --- a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs +++ b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs @@ -1,167 +1,164 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class TolerantMathTests { - public class TolerantMathTests + private readonly TolerantMath tolerantMath = new TolerantMath(0.1); + + [Theory] + [InlineData(0)] + [InlineData(0.01)] + [InlineData(-0.05)] + public void IsZero_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsZero(a)); + } + + [Theory] + [InlineData(0.11)] + [InlineData(-0.101)] + [InlineData(42)] + public void IsZero_WhenFalse(double a) + { + Assert.False(this.tolerantMath.IsZero(a)); + } + + [Theory] + [InlineData(0.11)] + [InlineData(100)] + public void IsPositive_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsPositive(a)); + } + + [Theory] + [InlineData(0.09)] + [InlineData(-0.1)] + [InlineData(-1000)] + public void IsPositive_WhenFalse(double a) { - private readonly TolerantMath tolerantMath = new TolerantMath(0.1); - - [Theory] - [InlineData(0)] - [InlineData(0.01)] - [InlineData(-0.05)] - public void IsZero_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsZero(a)); - } - - [Theory] - [InlineData(0.11)] - [InlineData(-0.101)] - [InlineData(42)] - public void IsZero_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsZero(a)); - } - - [Theory] - [InlineData(0.11)] - [InlineData(100)] - public void IsPositive_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsPositive(a)); - } - - [Theory] - [InlineData(0.09)] - [InlineData(-0.1)] - [InlineData(-1000)] - public void IsPositive_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsPositive(a)); - } - - [Theory] - [InlineData(-0.11)] - [InlineData(-100)] - public void IsNegative_WhenTrue(double a) - { - Assert.True(this.tolerantMath.IsNegative(a)); - } - - [Theory] - [InlineData(-0.09)] - [InlineData(0.1)] - [InlineData(1000)] - public void IsNegative_WhenFalse(double a) - { - Assert.False(this.tolerantMath.IsNegative(a)); - } - - [Theory] - [InlineData(4.2, 4.2)] - [InlineData(4.2, 4.25)] - [InlineData(-Math.PI, -Math.PI + 0.05)] - [InlineData(999999.2, 999999.25)] - public void AreEqual_WhenTrue(double a, double b) - { - Assert.True(this.tolerantMath.AreEqual(a, b)); - } - - [Theory] - [InlineData(1, 2)] - [InlineData(-1000000, -1000000.2)] - public void AreEqual_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.AreEqual(a, b)); - } - - [Theory] - [InlineData(2, 1.8)] - [InlineData(-20, -20.2)] - [InlineData(0.1, -0.1)] - [InlineData(100, 10)] - public void IsGreater_IsLess_WhenTrue(double a, double b) - { - Assert.True(this.tolerantMath.IsGreater(a, b)); - Assert.True(this.tolerantMath.IsLess(b, a)); - } - - [Theory] - [InlineData(2, 1.95)] - [InlineData(-20, -20.02)] - [InlineData(0.01, -0.01)] - [InlineData(999999, 999999.09)] - public void IsGreater_IsLess_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.IsGreater(a, b)); - Assert.False(this.tolerantMath.IsLess(b, a)); - } - - [Theory] - [InlineData(3, 2)] - [InlineData(3, 2.99)] - [InlineData(2.99, 3)] - [InlineData(-5, -6)] - [InlineData(-5, -5.05)] - [InlineData(-5.05, -5)] - public void IsGreaterOrEqual_IsLessOrEqual_WhenTrue(double a, double b) - { - Assert.True(this.tolerantMath.IsGreaterOrEqual(a, b)); - Assert.True(this.tolerantMath.IsLessOrEqual(b, a)); - } - - [Theory] - [InlineData(2, 3)] - [InlineData(2.89, 3)] - [InlineData(-3, -2.89)] - public void IsGreaterOrEqual_IsLessOrEqual_WhenFalse(double a, double b) - { - Assert.False(this.tolerantMath.IsGreaterOrEqual(a, b)); - Assert.False(this.tolerantMath.IsLessOrEqual(b, a)); - } - - [Theory] - [InlineData(3.5, 4.0)] - [InlineData(3.89, 4.0)] - [InlineData(4.09, 4.0)] - [InlineData(4.11, 5.0)] - [InlineData(0.11, 1)] - [InlineData(0.05, 0)] - [InlineData(-0.5, 0)] - [InlineData(-0.95, -1)] - [InlineData(-1.05, -1)] - [InlineData(-1.5, -1)] - public void Ceiling(double value, double expected) - { - double actual = this.tolerantMath.Ceiling(value); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(1, 1)] - [InlineData(0.99, 1)] - [InlineData(0.5, 0)] - [InlineData(0.01, 0)] - [InlineData(-0.09, 0)] - [InlineData(-0.11, -1)] - [InlineData(-100.11, -101)] - [InlineData(-100.09, -100)] - public void Floor(double value, double expected) - { - double plz1 = Math.IEEERemainder(1.1, 1); - double plz2 = Math.IEEERemainder(0.9, 1); - - double plz3 = Math.IEEERemainder(-1.1, 1); - double plz4 = Math.IEEERemainder(-0.9, 1); - - double actual = this.tolerantMath.Floor(value); - Assert.Equal(expected, actual); - } + Assert.False(this.tolerantMath.IsPositive(a)); + } + + [Theory] + [InlineData(-0.11)] + [InlineData(-100)] + public void IsNegative_WhenTrue(double a) + { + Assert.True(this.tolerantMath.IsNegative(a)); + } + + [Theory] + [InlineData(-0.09)] + [InlineData(0.1)] + [InlineData(1000)] + public void IsNegative_WhenFalse(double a) + { + Assert.False(this.tolerantMath.IsNegative(a)); + } + + [Theory] + [InlineData(4.2, 4.2)] + [InlineData(4.2, 4.25)] + [InlineData(-Math.PI, -Math.PI + 0.05)] + [InlineData(999999.2, 999999.25)] + public void AreEqual_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.AreEqual(a, b)); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(-1000000, -1000000.2)] + public void AreEqual_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.AreEqual(a, b)); + } + + [Theory] + [InlineData(2, 1.8)] + [InlineData(-20, -20.2)] + [InlineData(0.1, -0.1)] + [InlineData(100, 10)] + public void IsGreater_IsLess_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.IsGreater(a, b)); + Assert.True(this.tolerantMath.IsLess(b, a)); + } + + [Theory] + [InlineData(2, 1.95)] + [InlineData(-20, -20.02)] + [InlineData(0.01, -0.01)] + [InlineData(999999, 999999.09)] + public void IsGreater_IsLess_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.IsGreater(a, b)); + Assert.False(this.tolerantMath.IsLess(b, a)); + } + + [Theory] + [InlineData(3, 2)] + [InlineData(3, 2.99)] + [InlineData(2.99, 3)] + [InlineData(-5, -6)] + [InlineData(-5, -5.05)] + [InlineData(-5.05, -5)] + public void IsGreaterOrEqual_IsLessOrEqual_WhenTrue(double a, double b) + { + Assert.True(this.tolerantMath.IsGreaterOrEqual(a, b)); + Assert.True(this.tolerantMath.IsLessOrEqual(b, a)); + } + + [Theory] + [InlineData(2, 3)] + [InlineData(2.89, 3)] + [InlineData(-3, -2.89)] + public void IsGreaterOrEqual_IsLessOrEqual_WhenFalse(double a, double b) + { + Assert.False(this.tolerantMath.IsGreaterOrEqual(a, b)); + Assert.False(this.tolerantMath.IsLessOrEqual(b, a)); + } + + [Theory] + [InlineData(3.5, 4.0)] + [InlineData(3.89, 4.0)] + [InlineData(4.09, 4.0)] + [InlineData(4.11, 5.0)] + [InlineData(0.11, 1)] + [InlineData(0.05, 0)] + [InlineData(-0.5, 0)] + [InlineData(-0.95, -1)] + [InlineData(-1.05, -1)] + [InlineData(-1.5, -1)] + public void Ceiling(double value, double expected) + { + double actual = this.tolerantMath.Ceiling(value); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(0.99, 1)] + [InlineData(0.5, 0)] + [InlineData(0.01, 0)] + [InlineData(-0.09, 0)] + [InlineData(-0.11, -1)] + [InlineData(-100.11, -101)] + [InlineData(-100.09, -100)] + public void Floor(double value, double expected) + { + double plz1 = Math.IEEERemainder(1.1, 1); + double plz2 = Math.IEEERemainder(0.9, 1); + + double plz3 = Math.IEEERemainder(-1.1, 1); + double plz4 = Math.IEEERemainder(-0.9, 1); + + double actual = this.tolerantMath.Floor(value); + Assert.Equal(expected, actual); } } diff --git a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs index cb262e624e..ba9bd1cc5f 100644 --- a/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/UnitConverterHelperTests.cs @@ -2,40 +2,38 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Common.Helpers; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Helpers +namespace SixLabors.ImageSharp.Tests.Helpers; + +public class UnitConverterHelperTests { - public class UnitConverterHelperTests + [Fact] + public void InchToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.InchToMeter(expected); + actual = UnitConverter.MeterToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void InchToFromCm() { - [Fact] - public void InchToFromMeter() - { - const double expected = 96D; - double actual = UnitConverter.InchToMeter(expected); - actual = UnitConverter.MeterToInch(actual); - - Assert.Equal(expected, actual, 15); - } - - [Fact] - public void InchToFromCm() - { - const double expected = 96D; - double actual = UnitConverter.InchToCm(expected); - actual = UnitConverter.CmToInch(actual); - - Assert.Equal(expected, actual, 15); - } - - [Fact] - public void CmToFromMeter() - { - const double expected = 96D; - double actual = UnitConverter.CmToMeter(expected); - actual = UnitConverter.MeterToCm(actual); - - Assert.Equal(expected, actual, 15); - } + const double expected = 96D; + double actual = UnitConverter.InchToCm(expected); + actual = UnitConverter.CmToInch(actual); + + Assert.Equal(expected, actual, 15); + } + + [Fact] + public void CmToFromMeter() + { + const double expected = 96D; + double actual = UnitConverter.CmToMeter(expected); + actual = UnitConverter.MeterToCm(actual); + + Assert.Equal(expected, actual, 15); } } diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs index 55f6224f98..6a9c344860 100644 --- a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs @@ -1,382 +1,378 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.IO; -using Xunit; -namespace SixLabors.ImageSharp.Tests.IO +namespace SixLabors.ImageSharp.Tests.IO; + +public class BufferedReadStreamTests { - public class BufferedReadStreamTests + private readonly Configuration configuration; + + public BufferedReadStreamTests() { - private readonly Configuration configuration; + this.configuration = Configuration.CreateDefaultInstance(); + } - public BufferedReadStreamTests() + public static readonly TheoryData BufferSizes = + new TheoryData() { - this.configuration = Configuration.CreateDefaultInstance(); - } - - public static readonly TheoryData BufferSizes = - new TheoryData() - { - 1, 2, 4, 8, - 16, 97, 503, - 719, 1024, - 8096, 64768 - }; - - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSingleByteFromOrigin(int bufferSize) + 1, 2, 4, 8, + 16, 97, 503, + 719, 1024, + 8096, 64768 + }; + + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadSingleByteFromOrigin(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - Assert.Equal(expected[0], reader.ReadByte()); + Assert.Equal(expected[0], reader.ReadByte()); - // We've read a whole chunk but increment by 1 in our reader. - Assert.True(stream.Position >= bufferSize); - Assert.Equal(1, reader.Position); - } - - // Position of the stream should be reset on disposal. - Assert.Equal(1, stream.Position); + // We've read a whole chunk but increment by 1 in our reader. + Assert.True(stream.Position >= bufferSize); + Assert.Equal(1, reader.Position); } + + // Position of the stream should be reset on disposal. + Assert.Equal(1, stream.Position); } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSingleByteFromOffset(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadSingleByteFromOffset(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + byte[] expected = stream.ToArray(); + int offset = expected.Length / 2; + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - int offset = expected.Length / 2; - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - reader.Position = offset; + reader.Position = offset; - Assert.Equal(expected[offset], reader.ReadByte()); - - // We've read a whole chunk but increment by 1 in our reader. - Assert.Equal(bufferSize + offset, stream.Position); - Assert.Equal(offset + 1, reader.Position); - } + Assert.Equal(expected[offset], reader.ReadByte()); - Assert.Equal(offset + 1, stream.Position); + // We've read a whole chunk but increment by 1 in our reader. + Assert.Equal(bufferSize + offset, stream.Position); + Assert.Equal(offset + 1, reader.Position); } + + Assert.Equal(offset + 1, stream.Position); } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentSingleByteCorrectly(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadSubsequentSingleByteCorrectly(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + byte[] expected = stream.ToArray(); + int i; + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - int i; - using (var reader = new BufferedReadStream(this.configuration, stream)) + for (i = 0; i < expected.Length; i++) { - for (i = 0; i < expected.Length; i++) + Assert.Equal(expected[i], reader.ReadByte()); + Assert.Equal(i + 1, reader.Position); + + if (i < bufferSize) + { + Assert.Equal(stream.Position, bufferSize); + } + else if (i >= bufferSize && i < bufferSize * 2) + { + // We should have advanced to the second chunk now. + Assert.Equal(stream.Position, bufferSize * 2); + } + else { - Assert.Equal(expected[i], reader.ReadByte()); - Assert.Equal(i + 1, reader.Position); - - if (i < bufferSize) - { - Assert.Equal(stream.Position, bufferSize); - } - else if (i >= bufferSize && i < bufferSize * 2) - { - // We should have advanced to the second chunk now. - Assert.Equal(stream.Position, bufferSize * 2); - } - else - { - // We should have advanced to the third chunk now. - Assert.Equal(stream.Position, bufferSize * 3); - } + // We should have advanced to the third chunk now. + Assert.Equal(stream.Position, bufferSize * 3); } } - - Assert.Equal(i, stream.Position); } + + Assert.Equal(i, stream.Position); } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadMultipleBytesFromOrigin(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadMultipleBytesFromOrigin(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + var buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - var buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - Assert.Equal(2, reader.Read(buffer, 0, 2)); - Assert.Equal(expected[0], buffer[0]); - Assert.Equal(expected[1], buffer[1]); + Assert.Equal(2, reader.Read(buffer, 0, 2)); + Assert.Equal(expected[0], buffer[0]); + Assert.Equal(expected[1], buffer[1]); - // We've read a whole chunk but increment by the buffer length in our reader. - Assert.True(stream.Position >= bufferSize); - Assert.Equal(buffer.Length, reader.Position); - } + // We've read a whole chunk but increment by the buffer length in our reader. + Assert.True(stream.Position >= bufferSize); + Assert.Equal(buffer.Length, reader.Position); } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentMultipleByteCorrectly(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadSubsequentMultipleByteCorrectly(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + const int increment = 2; + var buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - const int increment = 2; - var buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) + for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) { - for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) + // Check values are correct. + Assert.Equal(increment, reader.Read(buffer, 0, increment)); + Assert.Equal(expected[o], buffer[0]); + Assert.Equal(expected[o + 1], buffer[1]); + Assert.Equal(o + increment, reader.Position); + + // These tests ensure that we are correctly reading + // our buffer in chunks of the given size. + int offset = i * increment; + + // First chunk. + if (offset < bufferSize) { - // Check values are correct. - Assert.Equal(increment, reader.Read(buffer, 0, increment)); - Assert.Equal(expected[o], buffer[0]); - Assert.Equal(expected[o + 1], buffer[1]); - Assert.Equal(o + increment, reader.Position); - - // These tests ensure that we are correctly reading - // our buffer in chunks of the given size. - int offset = i * increment; - - // First chunk. - if (offset < bufferSize) - { - // We've read an entire chunk once and are - // now reading from that chunk. - Assert.True(stream.Position >= bufferSize); - continue; - } - - // Second chunk - if (offset < bufferSize * 2) - { - Assert.True(stream.Position > bufferSize); - - // Odd buffer size with even increments can - // jump to the third chunk on final read. - Assert.True(stream.Position <= bufferSize * 3); - continue; - } - - // Third chunk - Assert.True(stream.Position > bufferSize * 2); + // We've read an entire chunk once and are + // now reading from that chunk. + Assert.True(stream.Position >= bufferSize); + continue; } + + // Second chunk + if (offset < bufferSize * 2) + { + Assert.True(stream.Position > bufferSize); + + // Odd buffer size with even increments can + // jump to the third chunk on final read. + Assert.True(stream.Position <= bufferSize * 3); + continue; + } + + // Third chunk + Assert.True(stream.Position > bufferSize * 2); } } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanReadSubsequentMultipleByteSpanCorrectly(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + const int increment = 2; + Span buffer = new byte[2]; + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - const int increment = 2; - Span buffer = new byte[2]; - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) + for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) { - for (int i = 0, o = 0; i < expected.Length / increment; i++, o += increment) + // Check values are correct. + Assert.Equal(increment, reader.Read(buffer, 0, increment)); + Assert.Equal(expected[o], buffer[0]); + Assert.Equal(expected[o + 1], buffer[1]); + Assert.Equal(o + increment, reader.Position); + + // These tests ensure that we are correctly reading + // our buffer in chunks of the given size. + int offset = i * increment; + + // First chunk. + if (offset < bufferSize) + { + // We've read an entire chunk once and are + // now reading from that chunk. + Assert.True(stream.Position >= bufferSize); + continue; + } + + // Second chunk + if (offset < bufferSize * 2) { - // Check values are correct. - Assert.Equal(increment, reader.Read(buffer, 0, increment)); - Assert.Equal(expected[o], buffer[0]); - Assert.Equal(expected[o + 1], buffer[1]); - Assert.Equal(o + increment, reader.Position); - - // These tests ensure that we are correctly reading - // our buffer in chunks of the given size. - int offset = i * increment; - - // First chunk. - if (offset < bufferSize) - { - // We've read an entire chunk once and are - // now reading from that chunk. - Assert.True(stream.Position >= bufferSize); - continue; - } - - // Second chunk - if (offset < bufferSize * 2) - { - Assert.True(stream.Position > bufferSize); - - // Odd buffer size with even increments can - // jump to the third chunk on final read. - Assert.True(stream.Position <= bufferSize * 3); - continue; - } - - // Third chunk - Assert.True(stream.Position > bufferSize * 2); + Assert.True(stream.Position > bufferSize); + + // Odd buffer size with even increments can + // jump to the third chunk on final read. + Assert.True(stream.Position <= bufferSize * 3); + continue; } + + // Third chunk + Assert.True(stream.Position > bufferSize * 2); } } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSkip(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanSkip(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 4)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 4)) + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - int skip = 1; - int plusOne = 1; - int skip2 = bufferSize; + int skip = 1; + int plusOne = 1; + int skip2 = bufferSize; - // Skip - reader.Skip(skip); - Assert.Equal(skip, reader.Position); - Assert.Equal(stream.Position, reader.Position); + // Skip + reader.Skip(skip); + Assert.Equal(skip, reader.Position); + Assert.Equal(stream.Position, reader.Position); - // Read - Assert.Equal(expected[skip], reader.ReadByte()); + // Read + Assert.Equal(expected[skip], reader.ReadByte()); - // Skip Again - reader.Skip(skip2); + // Skip Again + reader.Skip(skip2); - // First Skip + First Read + Second Skip - int position = skip + plusOne + skip2; + // First Skip + First Read + Second Skip + int position = skip + plusOne + skip2; - Assert.Equal(position, reader.Position); - Assert.Equal(stream.Position, reader.Position); - Assert.Equal(expected[position], reader.ReadByte()); - } + Assert.Equal(position, reader.Position); + Assert.Equal(stream.Position, reader.Position); + Assert.Equal(expected[position], reader.ReadByte()); } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamReadsSmallStream(int bufferSize) - { - this.configuration.StreamProcessingBufferSize = bufferSize; + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamReadsSmallStream(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; - // Create a stream smaller than the default buffer length - using (MemoryStream stream = this.CreateTestStream(Math.Max(1, bufferSize / 4))) + // Create a stream smaller than the default buffer length + using (MemoryStream stream = this.CreateTestStream(Math.Max(1, bufferSize / 4))) + { + byte[] expected = stream.ToArray(); + int offset = expected.Length / 2; + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - int offset = expected.Length / 2; - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - reader.Position = offset; + reader.Position = offset; - Assert.Equal(expected[offset], reader.ReadByte()); + Assert.Equal(expected[offset], reader.ReadByte()); - // We've read a whole length of the stream but increment by 1 in our reader. - Assert.Equal(Math.Max(1, bufferSize / 4), stream.Position); - Assert.Equal(offset + 1, reader.Position); - } - - Assert.Equal(offset + 1, stream.Position); + // We've read a whole length of the stream but increment by 1 in our reader. + Assert.Equal(Math.Max(1, bufferSize / 4), stream.Position); + Assert.Equal(offset + 1, reader.Position); } + + Assert.Equal(offset + 1, stream.Position); } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamReadsCanReadAllAsSingleByteFromOrigin(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamReadsCanReadAllAsSingleByteFromOrigin(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 3)) + byte[] expected = stream.ToArray(); + using (var reader = new BufferedReadStream(this.configuration, stream)) { - byte[] expected = stream.ToArray(); - using (var reader = new BufferedReadStream(this.configuration, stream)) + for (int i = 0; i < expected.Length; i++) { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], reader.ReadByte()); - } + Assert.Equal(expected[i], reader.ReadByte()); } } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamThrowsOnNegativePosition(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamThrowsOnNegativePosition(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize)) + using (var reader = new BufferedReadStream(this.configuration, stream)) { - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - Assert.Throws(() => reader.Position = -stream.Length); - } + Assert.Throws(() => reader.Position = -stream.Length); } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSetPositionToEnd(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanSetPositionToEnd(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) + using (var reader = new BufferedReadStream(this.configuration, stream)) { - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - reader.Position = reader.Length; - } + reader.Position = reader.Length; } } + } - [Theory] - [MemberData(nameof(BufferSizes))] - public void BufferedStreamCanSetPositionPastTheEnd(int bufferSize) + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanSetPositionPastTheEnd(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) { - this.configuration.StreamProcessingBufferSize = bufferSize; - using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) + using (var reader = new BufferedReadStream(this.configuration, stream)) { - using (var reader = new BufferedReadStream(this.configuration, stream)) - { - reader.Position = reader.Length + 1; - Assert.Equal(stream.Length + 1, stream.Position); - } + reader.Position = reader.Length + 1; + Assert.Equal(stream.Length + 1, stream.Position); } } + } - private MemoryStream CreateTestStream(int length) - { - var buffer = new byte[length]; - var random = new Random(); - random.NextBytes(buffer); + private MemoryStream CreateTestStream(int length) + { + var buffer = new byte[length]; + var random = new Random(); + random.NextBytes(buffer); - return new EvilStream(buffer); - } + return new EvilStream(buffer); + } - // Simulates a stream that can only return 1 byte at a time per read instruction. - // See https://github.com/SixLabors/ImageSharp/issues/1268 - private class EvilStream : MemoryStream + // Simulates a stream that can only return 1 byte at a time per read instruction. + // See https://github.com/SixLabors/ImageSharp/issues/1268 + private class EvilStream : MemoryStream + { + public EvilStream(byte[] buffer) + : base(buffer) { - public EvilStream(byte[] buffer) - : base(buffer) - { - } + } - public override int Read(byte[] buffer, int offset, int count) - { - return base.Read(buffer, offset, 1); - } + public override int Read(byte[] buffer, int offset, int count) + { + return base.Read(buffer, offset, 1); } } } diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 7353ea0d6c..1803cfddb9 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -1,379 +1,373 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.IO +namespace SixLabors.ImageSharp.Tests.IO; + +/// +/// Tests for the class. +/// +public class ChunkedMemoryStreamTests { /// - /// Tests for the class. + /// The default length in bytes of each buffer chunk when allocating large buffers. /// - public class ChunkedMemoryStreamTests - { - /// - /// The default length in bytes of each buffer chunk when allocating large buffers. - /// - private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb + private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb - /// - /// The default length in bytes of each buffer chunk when allocating small buffers. - /// - private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb + /// + /// The default length in bytes of each buffer chunk when allocating small buffers. + /// + private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb - private readonly MemoryAllocator allocator; + private readonly MemoryAllocator allocator; - public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; + public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; - [Fact] - public void MemoryStream_GetPositionTest_Negative() + [Fact] + public void MemoryStream_GetPositionTest_Negative() + { + using var ms = new ChunkedMemoryStream(this.allocator); + long iCurrentPos = ms.Position; + for (int i = -1; i > -6; i--) { - using var ms = new ChunkedMemoryStream(this.allocator); - long iCurrentPos = ms.Position; - for (int i = -1; i > -6; i--) - { - Assert.Throws(() => ms.Position = i); - Assert.Equal(ms.Position, iCurrentPos); - } + Assert.Throws(() => ms.Position = i); + Assert.Equal(ms.Position, iCurrentPos); } + } - [Fact] - public void MemoryStream_ReadTest_Negative() - { - var ms2 = new ChunkedMemoryStream(this.allocator); + [Fact] + public void MemoryStream_ReadTest_Negative() + { + var ms2 = new ChunkedMemoryStream(this.allocator); - Assert.Throws(() => ms2.Read(null, 0, 0)); - Assert.Throws(() => ms2.Read(new byte[] { 1 }, -1, 0)); - Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, -1)); - Assert.Throws(() => ms2.Read(new byte[] { 1 }, 2, 0)); - Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 2)); + Assert.Throws(() => ms2.Read(null, 0, 0)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, -1, 0)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, -1)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 2, 0)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 2)); - ms2.Dispose(); + ms2.Dispose(); - Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 1)); - } + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 1)); + } + + [Theory] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] + public void MemoryStream_ReadByteTest(int length) + { + using MemoryStream ms = this.CreateTestStream(length); + using var cms = new ChunkedMemoryStream(this.allocator); - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - public void MemoryStream_ReadByteTest(int length) + ms.CopyTo(cms); + cms.Position = 0; + byte[] expected = ms.ToArray(); + + for (int i = 0; i < expected.Length; i++) { - using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); + Assert.Equal(expected[i], cms.ReadByte()); + } + } - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); + [Theory] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] + public void MemoryStream_ReadByteBufferTest(int length) + { + using MemoryStream ms = this.CreateTestStream(length); + using var cms = new ChunkedMemoryStream(this.allocator); + + ms.CopyTo(cms); + cms.Position = 0; + byte[] expected = ms.ToArray(); + byte[] buffer = new byte[2]; + for (int i = 0; i < expected.Length; i += 2) + { + cms.Read(buffer); + Assert.Equal(expected[i], buffer[0]); + Assert.Equal(expected[i + 1], buffer[1]); + } + } - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], cms.ReadByte()); - } + [Theory] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] + public void MemoryStream_ReadByteBufferSpanTest(int length) + { + using MemoryStream ms = this.CreateTestStream(length); + using var cms = new ChunkedMemoryStream(this.allocator); + + ms.CopyTo(cms); + cms.Position = 0; + byte[] expected = ms.ToArray(); + Span buffer = new byte[2]; + for (int i = 0; i < expected.Length; i += 2) + { + cms.Read(buffer); + Assert.Equal(expected[i], buffer[0]); + Assert.Equal(expected[i + 1], buffer[1]); } + } - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - public void MemoryStream_ReadByteBufferTest(int length) + [Fact] + public void MemoryStream_WriteToTests() + { + using (var ms2 = new ChunkedMemoryStream(this.allocator)) { - using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); - - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); - byte[] buffer = new byte[2]; - for (int i = 0; i < expected.Length; i += 2) + byte[] bytArrRet; + byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + + // [] Write to memoryStream, check the memoryStream + ms2.Write(bytArr, 0, bytArr.Length); + + using var readonlyStream = new ChunkedMemoryStream(this.allocator); + ms2.WriteTo(readonlyStream); + readonlyStream.Flush(); + readonlyStream.Position = 0; + bytArrRet = new byte[(int)readonlyStream.Length]; + readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); + for (int i = 0; i < bytArr.Length; i++) { - cms.Read(buffer); - Assert.Equal(expected[i], buffer[0]); - Assert.Equal(expected[i + 1], buffer[1]); + Assert.Equal(bytArr[i], bytArrRet[i]); } } - [Theory] - [InlineData(DefaultSmallChunkSize)] - [InlineData((int)(DefaultSmallChunkSize * 1.5))] - [InlineData(DefaultSmallChunkSize * 4)] - [InlineData((int)(DefaultSmallChunkSize * 5.5))] - [InlineData(DefaultSmallChunkSize * 16)] - public void MemoryStream_ReadByteBufferSpanTest(int length) + // [] Write to memoryStream, check the memoryStream + using (var ms2 = new ChunkedMemoryStream(this.allocator)) + using (var ms3 = new ChunkedMemoryStream(this.allocator)) { - using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); - - ms.CopyTo(cms); - cms.Position = 0; - byte[] expected = ms.ToArray(); - Span buffer = new byte[2]; - for (int i = 0; i < expected.Length; i += 2) + byte[] bytArrRet; + byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + + ms2.Write(bytArr, 0, bytArr.Length); + ms2.WriteTo(ms3); + ms3.Position = 0; + bytArrRet = new byte[(int)ms3.Length]; + ms3.Read(bytArrRet, 0, (int)ms3.Length); + for (int i = 0; i < bytArr.Length; i++) { - cms.Read(buffer); - Assert.Equal(expected[i], buffer[0]); - Assert.Equal(expected[i + 1], buffer[1]); + Assert.Equal(bytArr[i], bytArrRet[i]); } } + } - [Fact] - public void MemoryStream_WriteToTests() + [Fact] + public void MemoryStream_WriteToSpanTests() + { + using (var ms2 = new ChunkedMemoryStream(this.allocator)) { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - { - byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - - // [] Write to memoryStream, check the memoryStream - ms2.Write(bytArr, 0, bytArr.Length); - - using var readonlyStream = new ChunkedMemoryStream(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } - } + Span bytArrRet; + Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; // [] Write to memoryStream, check the memoryStream - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - using (var ms3 = new ChunkedMemoryStream(this.allocator)) + ms2.Write(bytArr, 0, bytArr.Length); + + using var readonlyStream = new ChunkedMemoryStream(this.allocator); + ms2.WriteTo(readonlyStream); + readonlyStream.Flush(); + readonlyStream.Position = 0; + bytArrRet = new byte[(int)readonlyStream.Length]; + readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); + for (int i = 0; i < bytArr.Length; i++) { - byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - - ms2.Write(bytArr, 0, bytArr.Length); - ms2.WriteTo(ms3); - ms3.Position = 0; - bytArrRet = new byte[(int)ms3.Length]; - ms3.Read(bytArrRet, 0, (int)ms3.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } + Assert.Equal(bytArr[i], bytArrRet[i]); } } - [Fact] - public void MemoryStream_WriteToSpanTests() + // [] Write to memoryStream, check the memoryStream + using (var ms2 = new ChunkedMemoryStream(this.allocator)) + using (var ms3 = new ChunkedMemoryStream(this.allocator)) { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) + Span bytArrRet; + Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + + ms2.Write(bytArr, 0, bytArr.Length); + ms2.WriteTo(ms3); + ms3.Position = 0; + bytArrRet = new byte[(int)ms3.Length]; + ms3.Read(bytArrRet, 0, (int)ms3.Length); + for (int i = 0; i < bytArr.Length; i++) { - Span bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - - // [] Write to memoryStream, check the memoryStream - ms2.Write(bytArr, 0, bytArr.Length); - - using var readonlyStream = new ChunkedMemoryStream(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } + Assert.Equal(bytArr[i], bytArrRet[i]); } + } + } - // [] Write to memoryStream, check the memoryStream - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - using (var ms3 = new ChunkedMemoryStream(this.allocator)) + [Fact] + public void MemoryStream_WriteByteTests() + { + using (var ms2 = new ChunkedMemoryStream(this.allocator)) + { + byte[] bytArrRet; + byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + + for (int i = 0; i < bytArr.Length; i++) { - Span bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - - ms2.Write(bytArr, 0, bytArr.Length); - ms2.WriteTo(ms3); - ms3.Position = 0; - bytArrRet = new byte[(int)ms3.Length]; - ms3.Read(bytArrRet, 0, (int)ms3.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } + ms2.WriteByte(bytArr[i]); } - } - [Fact] - public void MemoryStream_WriteByteTests() - { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) + using var readonlyStream = new ChunkedMemoryStream(this.allocator); + ms2.WriteTo(readonlyStream); + readonlyStream.Flush(); + readonlyStream.Position = 0; + bytArrRet = new byte[(int)readonlyStream.Length]; + readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); + for (int i = 0; i < bytArr.Length; i++) { - byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - - for (int i = 0; i < bytArr.Length; i++) - { - ms2.WriteByte(bytArr[i]); - } - - using var readonlyStream = new ChunkedMemoryStream(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } + Assert.Equal(bytArr[i], bytArrRet[i]); } } + } - [Fact] - public void MemoryStream_WriteToTests_Negative() - { - using var ms2 = new ChunkedMemoryStream(this.allocator); - Assert.Throws(() => ms2.WriteTo(null)); + [Fact] + public void MemoryStream_WriteToTests_Negative() + { + using var ms2 = new ChunkedMemoryStream(this.allocator); + Assert.Throws(() => ms2.WriteTo(null)); - ms2.Write(new byte[] { 1 }, 0, 1); - var readonlyStream = new MemoryStream(new byte[1028], false); - Assert.Throws(() => ms2.WriteTo(readonlyStream)); + ms2.Write(new byte[] { 1 }, 0, 1); + var readonlyStream = new MemoryStream(new byte[1028], false); + Assert.Throws(() => ms2.WriteTo(readonlyStream)); - readonlyStream.Dispose(); + readonlyStream.Dispose(); - // [] Pass in a closed stream - Assert.Throws(() => ms2.WriteTo(readonlyStream)); - } + // [] Pass in a closed stream + Assert.Throws(() => ms2.WriteTo(readonlyStream)); + } - [Fact] - public void MemoryStream_CopyTo_Invalid() + [Fact] + public void MemoryStream_CopyTo_Invalid() + { + ChunkedMemoryStream memoryStream; + const string bufferSize = nameof(bufferSize); + using (memoryStream = new ChunkedMemoryStream(this.allocator)) { - ChunkedMemoryStream memoryStream; - const string bufferSize = nameof(bufferSize); - using (memoryStream = new ChunkedMemoryStream(this.allocator)) - { - const string destination = nameof(destination); - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null)); + const string destination = nameof(destination); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null)); - // Validate the destination parameter first. - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); - Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); + // Validate the destination parameter first. + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: 0)); + Assert.Throws(destination, () => memoryStream.CopyTo(destination: null, bufferSize: -1)); - // Then bufferSize. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); - } - - // After the Stream is disposed, we should fail on all CopyTos. - Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. + // Then bufferSize. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // 0-length buffer doesn't make sense. Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); + } - ChunkedMemoryStream disposedStream = memoryStream; + // After the Stream is disposed, we should fail on all CopyTos. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: 0)); // Not before bufferSize is validated. + Assert.Throws(bufferSize, () => memoryStream.CopyTo(Stream.Null, bufferSize: -1)); - // We should throw first for the source being disposed... - Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); + ChunkedMemoryStream disposedStream = memoryStream; - // Then for the destination being disposed. - memoryStream = new ChunkedMemoryStream(this.allocator); - Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); - memoryStream.Dispose(); - } + // We should throw first for the source being disposed... + Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); - [Theory] - [MemberData(nameof(CopyToData))] - public void CopyTo(Stream source, byte[] expected) - { - using var destination = new ChunkedMemoryStream(this.allocator); - source.CopyTo(destination); - Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end. - Assert.Equal(expected, destination.ToArray()); - } + // Then for the destination being disposed. + memoryStream = new ChunkedMemoryStream(this.allocator); + Assert.Throws(() => memoryStream.CopyTo(disposedStream, 1)); + memoryStream.Dispose(); + } - public static IEnumerable GetAllTestImages() - { - IEnumerable allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories) - .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase)); + [Theory] + [MemberData(nameof(CopyToData))] + public void CopyTo(Stream source, byte[] expected) + { + using var destination = new ChunkedMemoryStream(this.allocator); + source.CopyTo(destination); + Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end. + Assert.Equal(expected, destination.ToArray()); + } - var result = new List(); - foreach (string path in allImageFiles) - { - result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length)); - } + public static IEnumerable GetAllTestImages() + { + IEnumerable allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories) + .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase)); - return result; + var result = new List(); + foreach (string path in allImageFiles) + { + result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length)); } - public static IEnumerable AllTestImages = GetAllTestImages(); + return result; + } + + public static IEnumerable AllTestImages = GetAllTestImages(); - [Theory] - [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] - public void DecoderIntegrationTest(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] + public void DecoderIntegrationTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) { - if (!TestEnvironment.Is64BitProcess) - { - return; - } + return; + } - Image expected; - try - { - expected = provider.GetImage(); - } - catch - { - // The image is invalid - return; - } + Image expected; + try + { + expected = provider.GetImage(); + } + catch + { + // The image is invalid + return; + } - string fullPath = Path.Combine( - TestEnvironment.InputImagesDirectoryFullPath, - ((TestImageProvider.FileProvider)provider).FilePath); + string fullPath = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + ((TestImageProvider.FileProvider)provider).FilePath); - using FileStream fs = File.OpenRead(fullPath); - using var nonSeekableStream = new NonSeekableStream(fs); + using FileStream fs = File.OpenRead(fullPath); + using var nonSeekableStream = new NonSeekableStream(fs); - var actual = Image.Load(nonSeekableStream); + var actual = Image.Load(nonSeekableStream); - ImageComparer.Exact.VerifySimilarity(expected, actual); - } + ImageComparer.Exact.VerifySimilarity(expected, actual); + } - public static IEnumerable CopyToData() - { - // Stream is positioned @ beginning of data - byte[] data1 = new byte[] { 1, 2, 3 }; - var stream1 = new MemoryStream(data1); + public static IEnumerable CopyToData() + { + // Stream is positioned @ beginning of data + byte[] data1 = new byte[] { 1, 2, 3 }; + var stream1 = new MemoryStream(data1); - yield return new object[] { stream1, data1 }; + yield return new object[] { stream1, data1 }; - // Stream is positioned in the middle of data - byte[] data2 = new byte[] { 0xff, 0xf3, 0xf0 }; - var stream2 = new MemoryStream(data2) { Position = 1 }; + // Stream is positioned in the middle of data + byte[] data2 = new byte[] { 0xff, 0xf3, 0xf0 }; + var stream2 = new MemoryStream(data2) { Position = 1 }; - yield return new object[] { stream2, new byte[] { 0xf3, 0xf0 } }; + yield return new object[] { stream2, new byte[] { 0xf3, 0xf0 } }; - // Stream is positioned after end of data - byte[] data3 = data2; - var stream3 = new MemoryStream(data3) { Position = data3.Length + 1 }; + // Stream is positioned after end of data + byte[] data3 = data2; + var stream3 = new MemoryStream(data3) { Position = data3.Length + 1 }; - yield return new object[] { stream3, Array.Empty() }; - } + yield return new object[] { stream3, Array.Empty() }; + } - private MemoryStream CreateTestStream(int length) - { - byte[] buffer = new byte[length]; - var random = new Random(); - random.NextBytes(buffer); + private MemoryStream CreateTestStream(int length) + { + byte[] buffer = new byte[length]; + var random = new Random(); + random.NextBytes(buffer); - return new MemoryStream(buffer); - } + return new MemoryStream(buffer); } } diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs index 89fc1aa893..3d512b7d27 100644 --- a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs +++ b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs @@ -1,50 +1,46 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.IO; -using Xunit; -namespace SixLabors.ImageSharp.Tests.IO +namespace SixLabors.ImageSharp.Tests.IO; + +public class LocalFileSystemTests { - public class LocalFileSystemTests + [Fact] + public void OpenRead() { - [Fact] - public void OpenRead() - { - string path = Path.GetTempFileName(); - string testData = Guid.NewGuid().ToString(); - File.WriteAllText(path, testData); + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + File.WriteAllText(path, testData); - var fs = new LocalFileSystem(); + var fs = new LocalFileSystem(); - using (var r = new StreamReader(fs.OpenRead(path))) - { - string data = r.ReadToEnd(); - - Assert.Equal(testData, data); - } + using (var r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); - File.Delete(path); + Assert.Equal(testData, data); } - [Fact] - public void Create() - { - string path = Path.GetTempFileName(); - string testData = Guid.NewGuid().ToString(); - var fs = new LocalFileSystem(); - - using (var r = new StreamWriter(fs.Create(path))) - { - r.Write(testData); - } + File.Delete(path); + } - string data = File.ReadAllText(path); - Assert.Equal(testData, data); + [Fact] + public void Create() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + var fs = new LocalFileSystem(); - File.Delete(path); + using (var r = new StreamWriter(fs.Create(path))) + { + r.Write(testData); } + + string data = File.ReadAllText(path); + Assert.Equal(testData, data); + + File.Delete(path); } } diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 6ee20bdb27..25674e6a8d 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -1,166 +1,162 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class ImageCloneTests { - public class ImageCloneTests + [Fact] + public void CloneAs_WhenDisposed_Throws() { - [Fact] - public void CloneAs_WhenDisposed_Throws() - { - var image = new Image(5, 5); - image.Dispose(); + var image = new Image(5, 5); + image.Dispose(); - Assert.Throws(() => image.CloneAs()); - } + Assert.Throws(() => image.CloneAs()); + } - [Fact] - public void Clone_WhenDisposed_Throws() - { - var image = new Image(5, 5); - image.Dispose(); + [Fact] + public void Clone_WhenDisposed_Throws() + { + var image = new Image(5, 5); + image.Dispose(); - Assert.Throws(() => image.Clone()); - } + Assert.Throws(() => image.Clone()); + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToBgra32(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToBgra32(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + { + for (int y = 0; y < imageAccessor.Height; y++) { - for (int y = 0; y < imageAccessor.Height; y++) + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < imageAccessor.Width; x++) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < imageAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Bgra32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - Assert.Equal(expected.A, actual.A); - } + Rgba32 expected = row[x]; + Bgra32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); } - }); - } + } + }); + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToAbgr32(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToAbgr32(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + { + for (int y = 0; y < imageAccessor.Height; y++) { - for (int y = 0; y < imageAccessor.Height; y++) + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < cloneAccessor.Width; x++) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Abgr32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - } + Rgba32 expected = row[x]; + Abgr32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); } - }); - } + } + }); + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToBgr24(TestImageProvider provider) - { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToBgr24(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + { + for (int y = 0; y < imageAccessor.Height; y++) { - for (int y = 0; y < imageAccessor.Height; y++) + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < cloneAccessor.Width; x++) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Bgr24 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - } + Rgba32 expected = row[x]; + Bgr24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); } - }); - } + } + }); + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToArgb32(TestImageProvider provider) + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToArgb32(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + for (int y = 0; y < imageAccessor.Height; y++) { - for (int y = 0; y < imageAccessor.Height; y++) + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < cloneAccessor.Width; x++) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < cloneAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Argb32 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - Assert.Equal(expected.A, actual.A); - } + Rgba32 expected = row[x]; + Argb32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); } - }); - } + } + }); + } - [Theory] - [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] - public void CloneAs_ToRgb24(TestImageProvider provider) + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToRgb24(TestImageProvider provider) + { + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - using Image image = provider.GetImage(); - using Image clone = image.CloneAs(); - image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => + for (int y = 0; y < imageAccessor.Height; y++) { - for (int y = 0; y < imageAccessor.Height; y++) + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); + + for (int x = 0; x < imageAccessor.Width; x++) { - Span row = imageAccessor.GetRowSpan(y); - Span rowClone = cloneAccessor.GetRowSpan(y); - - for (int x = 0; x < imageAccessor.Width; x++) - { - Rgba32 expected = row[x]; - Rgb24 actual = rowClone[x]; - - Assert.Equal(expected.R, actual.R); - Assert.Equal(expected.G, actual.G); - Assert.Equal(expected.B, actual.B); - } + Rgba32 expected = row[x]; + Rgb24 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); } - }); - } + } + }); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index c5793224a9..28c4f686b0 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -1,406 +1,401 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class ImageFrameCollectionTests { - public abstract partial class ImageFrameCollectionTests + [GroupOutput("ImageFramesCollectionTests")] + public class Generic : ImageFrameCollectionTests { - [GroupOutput("ImageFramesCollectionTests")] - public class Generic : ImageFrameCollectionTests + [Fact] + public void Constructor_ShouldCreateOneFrame() { - [Fact] - public void Constructor_ShouldCreateOneFrame() - { - Assert.Equal(1, this.Collection.Count); - } + Assert.Equal(1, this.Collection.Count); + } - [Fact] - public void AddNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using var frame = new ImageFrame(Configuration.Default, 1, 1); - using ImageFrame addedFrame = this.Collection.AddFrame(frame); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } + [Fact] + public void AddNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + using var frame = new ImageFrame(Configuration.Default, 1, 1); + using ImageFrame addedFrame = this.Collection.AddFrame(frame); + }); - [Fact] - public void AddNewFrame_Frame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame((ImageFrame)null); - }); + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } - Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); - } + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + using ImageFrame addedFrame = this.Collection.AddFrame((ImageFrame)null); + }); - [Fact] - public void AddNewFrame_PixelBuffer_DataMustNotBeNull() - { - Rgba32[] data = null; + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); + } - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame(data); - }); + [Fact] + public void AddNewFrame_PixelBuffer_DataMustNotBeNull() + { + Rgba32[] data = null; - Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); - } + ArgumentNullException ex = Assert.Throws( + () => + { + using ImageFrame addedFrame = this.Collection.AddFrame(data); + }); - [Fact] - public void AddNewFrame_PixelBuffer_BufferIncorrectSize() - { - ArgumentOutOfRangeException ex = Assert.Throws( - () => - { - using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); - }); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); + } - Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); - } + [Fact] + public void AddNewFrame_PixelBuffer_BufferIncorrectSize() + { + ArgumentOutOfRangeException ex = Assert.Throws( + () => + { + using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); + }); - [Fact] - public void InsertNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using var frame = new ImageFrame(Configuration.Default, 1, 1); - using ImageFrame insertedFrame = this.Collection.InsertFrame(1, frame); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } + Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); + } - [Fact] - public void InsertNewFrame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - using ImageFrame insertedFrame = this.Collection.InsertFrame(1, null); - }); + [Fact] + public void InsertNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + using var frame = new ImageFrame(Configuration.Default, 1, 1); + using ImageFrame insertedFrame = this.Collection.InsertFrame(1, frame); + }); - Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); - } + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } - [Fact] - public void Constructor_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 1, 1); - new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); - }); - - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } + [Fact] + public void InsertNewFrame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + using ImageFrame insertedFrame = this.Collection.InsertFrame(1, null); + }); - [Fact] - public void RemoveAtFrame_ThrowIfRemovingLastFrame() - { - using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame }); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); + } - InvalidOperationException ex = Assert.Throws( - () => collection.RemoveFrame(0)); - Assert.Equal("Cannot remove last frame.", ex.Message); - } + [Fact] + public void Constructor_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 1, 1); + new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); + }); + + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } - [Fact] - public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); - - collection.RemoveFrame(0); - Assert.Equal(1, collection.Count); - } + [Fact] + public void RemoveAtFrame_ThrowIfRemovingLastFrame() + { + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame }); + + InvalidOperationException ex = Assert.Throws( + () => collection.RemoveFrame(0)); + Assert.Equal("Cannot remove last frame.", ex.Message); + } - [Fact] - public void RootFrameIsFrameAtIndexZero() - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); + [Fact] + public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); + + collection.RemoveFrame(0); + Assert.Equal(1, collection.Count); + } - Assert.Equal(collection.RootFrame, collection[0]); - } + [Fact] + public void RootFrameIsFrameAtIndexZero() + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); - [Fact] - public void ConstructorPopulatesFrames() - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); + Assert.Equal(collection.RootFrame, collection[0]); + } - Assert.Equal(2, collection.Count); - } + [Fact] + public void ConstructorPopulatesFrames() + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); - [Fact] - public void DisposeClearsCollection() - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); + Assert.Equal(2, collection.Count); + } - collection.Dispose(); + [Fact] + public void DisposeClearsCollection() + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); - Assert.Equal(0, collection.Count); - } + collection.Dispose(); - [Fact] - public void Dispose_DisposesAllInnerFrames() - { - using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); - using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); - var collection = new ImageFrameCollection( - this.Image, - new[] { imageFrame1, imageFrame2 }); - - IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); - collection.Dispose(); - - Assert.All( - framesSnapShot, - f => - { - // The pixel source of the frame is null after its been disposed. - Assert.Null(f.PixelBuffer); - }); - } + Assert.Equal(0, collection.Count); + } - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void CloneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image img = provider.GetImage()) + [Fact] + public void Dispose_DisposesAllInnerFrames() + { + using var imageFrame1 = new ImageFrame(Configuration.Default, 10, 10); + using var imageFrame2 = new ImageFrame(Configuration.Default, 10, 10); + var collection = new ImageFrameCollection( + this.Image, + new[] { imageFrame1, imageFrame2 }); + + IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); + collection.Dispose(); + + Assert.All( + framesSnapShot, + f => { - using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); - using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway - using (Image cloned = img.Frames.CloneFrame(0)) - { - Assert.Equal(2, img.Frames.Count); - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - - cloned.ComparePixelBufferTo(imgMem); - } - } - } + // The pixel source of the frame is null after its been disposed. + Assert.Null(f.PixelBuffer); + }); + } - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void ExtractFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void CloneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) { - using (Image img = provider.GetImage()) + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); // add a frame anyway + using (Image cloned = img.Frames.CloneFrame(0)) { - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMemory)); - TPixel[] sourcePixelData = imgMemory.ToArray(); - - using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); - using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); - using (Image cloned = img.Frames.ExportFrame(0)) - { - Assert.Equal(1, img.Frames.Count); - cloned.ComparePixelBufferTo(sourcePixelData.AsSpan()); - } + Assert.Equal(2, img.Frames.Count); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + + cloned.ComparePixelBufferTo(imgMem); } } + } - [Fact] - public void CreateFrame_Default() + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) { - using (this.Image.Frames.CreateFrame()) + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMemory)); + TPixel[] sourcePixelData = imgMemory.ToArray(); + + using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); + using (Image cloned = img.Frames.ExportFrame(0)) { - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); + Assert.Equal(1, img.Frames.Count); + cloned.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } + } - [Fact] - public void CreateFrame_CustomFillColor() + [Fact] + public void CreateFrame_Default() + { + using (this.Image.Frames.CreateFrame()) { - using (this.Image.Frames.CreateFrame(Color.HotPink)) - { - Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); - } + Assert.Equal(2, this.Image.Frames.Count); + this.Image.Frames[1].ComparePixelBufferTo(default(Rgba32)); } + } - [Fact] - public void AddFrameFromPixelData() + [Fact] + public void CreateFrame_CustomFillColor() + { + using (this.Image.Frames.CreateFrame(Color.HotPink)) { - Assert.True(this.Image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - Rgba32[] pixelData = imgMem.ToArray(); - using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); Assert.Equal(2, this.Image.Frames.Count); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); } + } - [Fact] - public void AddFrame_clones_sourceFrame() - { - using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); + [Fact] + public void AddFrameFromPixelData() + { + Assert.True(this.Image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + Rgba32[] pixelData = imgMem.ToArray(); + using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); + Assert.Equal(2, this.Image.Frames.Count); + } - Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); - addedFrame.ComparePixelBufferTo(otherFrameMem.Span); - Assert.NotEqual(otherFrame, addedFrame); - } + [Fact] + public void AddFrame_clones_sourceFrame() + { + using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); - [Fact] - public void InsertFrame_clones_sourceFrame() - { - using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); - using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); + Assert.NotEqual(otherFrame, addedFrame); + } - Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); - addedFrame.ComparePixelBufferTo(otherFrameMem.Span); - Assert.NotEqual(otherFrame, addedFrame); - } + [Fact] + public void InsertFrame_clones_sourceFrame() + { + using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); + using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); - [Fact] - public void MoveFrame_LeavesFrameInCorrectLocation() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); + Assert.NotEqual(otherFrame, addedFrame); + } - ImageFrame frame = this.Image.Frames[4]; - this.Image.Frames.MoveFrame(4, 7); - int newIndex = this.Image.Frames.IndexOf(frame); - Assert.Equal(7, newIndex); + [Fact] + public void MoveFrame_LeavesFrameInCorrectLocation() + { + for (int i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void IndexOf_ReturnsCorrectIndex() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + ImageFrame frame = this.Image.Frames[4]; + this.Image.Frames.MoveFrame(4, 7); + int newIndex = this.Image.Frames.IndexOf(frame); + Assert.Equal(7, newIndex); + } - ImageFrame frame = this.Image.Frames[4]; - int index = this.Image.Frames.IndexOf(frame); - Assert.Equal(4, index); + [Fact] + public void IndexOf_ReturnsCorrectIndex() + { + for (int i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void Contains_TrueIfMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + ImageFrame frame = this.Image.Frames[4]; + int index = this.Image.Frames.IndexOf(frame); + Assert.Equal(4, index); + } - ImageFrame frame = this.Image.Frames[4]; - Assert.True(this.Image.Frames.Contains(frame)); + [Fact] + public void Contains_TrueIfMember() + { + for (int i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void Contains_FalseIfNonMember() - { - for (int i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + ImageFrame frame = this.Image.Frames[4]; + Assert.True(this.Image.Frames.Contains(frame)); + } - using var frame = new ImageFrame(Configuration.Default, 10, 10); - Assert.False(this.Image.Frames.Contains(frame)); + [Fact] + public void Contains_FalseIfNonMember() + { + for (int i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void PreferContiguousImageBuffers_True_AppliedToAllFrames() + using var frame = new ImageFrame(Configuration.Default, 10, 10); + Assert.False(this.Image.Frames.Contains(frame)); + } + + [Fact] + public void PreferContiguousImageBuffers_True_AppliedToAllFrames() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 100, 100); + image.Frames.CreateFrame(); + image.Frames.InsertFrame(0, image.Frames[0]); + image.Frames.CreateFrame(Color.Red); + + Assert.Equal(4, image.Frames.Count); + IEnumerable> frames = image.Frames; + foreach (ImageFrame frame in frames) { - Configuration configuration = Configuration.Default.Clone(); - configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; - configuration.PreferContiguousImageBuffers = true; - - using var image = new Image(configuration, 100, 100); - image.Frames.CreateFrame(); - image.Frames.InsertFrame(0, image.Frames[0]); - image.Frames.CreateFrame(Color.Red); - - Assert.Equal(4, image.Frames.Count); - IEnumerable> frames = image.Frames; - foreach (ImageFrame frame in frames) - { - Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory _)); - } + Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory _)); } + } - [Fact] - public void DisposeCall_NoThrowIfCalledMultiple() - { - var image = new Image(Configuration.Default, 10, 10); - var frameCollection = image.Frames as ImageFrameCollection; + [Fact] + public void DisposeCall_NoThrowIfCalledMultiple() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; - image.Dispose(); // this should invalidate underlying collection as well - frameCollection.Dispose(); - } + image.Dispose(); // this should invalidate underlying collection as well + frameCollection.Dispose(); + } - [Fact] - public void PublicProperties_ThrowIfDisposed() - { - var image = new Image(Configuration.Default, 10, 10); - var frameCollection = image.Frames as ImageFrameCollection; + [Fact] + public void PublicProperties_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; - image.Dispose(); // this should invalidate underlying collection as well + image.Dispose(); // this should invalidate underlying collection as well - Assert.Throws(() => { var prop = frameCollection.RootFrame; }); - } + Assert.Throws(() => { var prop = frameCollection.RootFrame; }); + } - [Fact] - public void PublicMethods_ThrowIfDisposed() - { - var image = new Image(Configuration.Default, 10, 10); - var frameCollection = image.Frames as ImageFrameCollection; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { var res = frameCollection.AddFrame(default); }); - Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); - Assert.Throws(() => { var res = frameCollection.Contains(default); }); - Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); - Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); - Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); - Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); - Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); - Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); - Assert.Throws(() => { frameCollection.RemoveFrame(default); }); - Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); - } + [Fact] + public void PublicMethods_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames as ImageFrameCollection; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var res = frameCollection.AddFrame(default); }); + Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); + Assert.Throws(() => { var res = frameCollection.Contains(default); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); + Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); + Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); + Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); + Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); + Assert.Throws(() => { frameCollection.RemoveFrame(default); }); + Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index 46297bd6d9..6e394d5925 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -1,357 +1,352 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public abstract partial class ImageFrameCollectionTests { - public abstract partial class ImageFrameCollectionTests + [GroupOutput("ImageFramesCollectionTests")] + public class NonGeneric : ImageFrameCollectionTests { - [GroupOutput("ImageFramesCollectionTests")] - public class NonGeneric : ImageFrameCollectionTests - { - private new Image Image => base.Image; + private new Image Image => base.Image; - private new ImageFrameCollection Collection => base.Collection; + private new ImageFrameCollection Collection => base.Collection; - [Fact] - public void AddFrame_OfDifferentPixelType() + [Fact] + public void AddFrame_OfDifferentPixelType() + { + using (var sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + Color.Blue)) { - using (var sourceImage = new Image( - this.Image.GetConfiguration(), - this.Image.Width, - this.Image.Height, - Color.Blue)) - { - this.Collection.AddFrame(sourceImage.Frames.RootFrame); - } + this.Collection.AddFrame(sourceImage.Frames.RootFrame); + } - Rgba32[] expectedAllBlue = - Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); + Rgba32[] expectedAllBlue = + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); - Assert.Equal(2, this.Collection.Count); - var actualFrame = (ImageFrame)this.Collection[1]; + Assert.Equal(2, this.Collection.Count); + var actualFrame = (ImageFrame)this.Collection[1]; - actualFrame.ComparePixelBufferTo(expectedAllBlue); - } + actualFrame.ComparePixelBufferTo(expectedAllBlue); + } - [Fact] - public void InsertFrame_OfDifferentPixelType() + [Fact] + public void InsertFrame_OfDifferentPixelType() + { + using (var sourceImage = new Image( + this.Image.GetConfiguration(), + this.Image.Width, + this.Image.Height, + Color.Blue)) { - using (var sourceImage = new Image( - this.Image.GetConfiguration(), - this.Image.Width, - this.Image.Height, - Color.Blue)) - { - this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); - } + this.Collection.InsertFrame(0, sourceImage.Frames.RootFrame); + } - Rgba32[] expectedAllBlue = - Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); + Rgba32[] expectedAllBlue = + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); - Assert.Equal(2, this.Collection.Count); - var actualFrame = (ImageFrame)this.Collection[0]; + Assert.Equal(2, this.Collection.Count); + var actualFrame = (ImageFrame)this.Collection[0]; - actualFrame.ComparePixelBufferTo(expectedAllBlue); - } + actualFrame.ComparePixelBufferTo(expectedAllBlue); + } - [Fact] - public void Constructor_ShouldCreateOneFrame() - { - Assert.Equal(1, this.Collection.Count); - } + [Fact] + public void Constructor_ShouldCreateOneFrame() + { + Assert.Equal(1, this.Collection.Count); + } - [Fact] - public void AddNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); - }); + [Fact] + public void AddNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 1, 1)); + }); - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } - [Fact] - public void AddNewFrame_Frame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - this.Collection.AddFrame(null); - }); + [Fact] + public void AddNewFrame_Frame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.AddFrame(null); + }); - Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); - } + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); + } - [Fact] - public void InsertNewFrame_FramesMustHaveSameSize() - { - ArgumentException ex = Assert.Throws( - () => - { - this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); - }); + [Fact] + public void InsertNewFrame_FramesMustHaveSameSize() + { + ArgumentException ex = Assert.Throws( + () => + { + this.Collection.InsertFrame(1, new ImageFrame(Configuration.Default, 1, 1)); + }); - Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); - } + Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); + } - [Fact] - public void InsertNewFrame_FramesNotBeNull() - { - ArgumentNullException ex = Assert.Throws( - () => - { - this.Collection.InsertFrame(1, null); - }); + [Fact] + public void InsertNewFrame_FramesNotBeNull() + { + ArgumentNullException ex = Assert.Throws( + () => + { + this.Collection.InsertFrame(1, null); + }); - Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); - } + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); + } - [Fact] - public void RemoveAtFrame_ThrowIfRemovingLastFrame() - { - InvalidOperationException ex = Assert.Throws( - () => - { - this.Collection.RemoveFrame(0); - }); - Assert.Equal("Cannot remove last frame.", ex.Message); - } + [Fact] + public void RemoveAtFrame_ThrowIfRemovingLastFrame() + { + InvalidOperationException ex = Assert.Throws( + () => + { + this.Collection.RemoveFrame(0); + }); + Assert.Equal("Cannot remove last frame.", ex.Message); + } - [Fact] - public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() - { - this.Collection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + [Fact] + public void RemoveAtFrame_CanRemoveFrameZeroIfMultipleFramesExist() + { + this.Collection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); - this.Collection.RemoveFrame(0); - Assert.Equal(1, this.Collection.Count); - } + this.Collection.RemoveFrame(0); + Assert.Equal(1, this.Collection.Count); + } - [Fact] - public void RootFrameIsFrameAtIndexZero() - { - Assert.Equal(this.Collection.RootFrame, this.Collection[0]); - } + [Fact] + public void RootFrameIsFrameAtIndexZero() + { + Assert.Equal(this.Collection.RootFrame, this.Collection[0]); + } - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void CloneFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void CloneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) { - using (Image img = provider.GetImage()) - { - ImageFrameCollection nonGenericFrameCollection = img.Frames; + ImageFrameCollection nonGenericFrameCollection = img.Frames; - nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway - using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) - { - Assert.Equal(2, img.Frames.Count); + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); // add a frame anyway + using (Image cloned = nonGenericFrameCollection.CloneFrame(0)) + { + Assert.Equal(2, img.Frames.Count); - var expectedClone = (Image)cloned; + var expectedClone = (Image)cloned; - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - expectedClone.ComparePixelBufferTo(imgMem); - } + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + expectedClone.ComparePixelBufferTo(imgMem); } } + } - [Theory] - [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] - public void ExtractFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] + public void ExtractFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image img = provider.GetImage()) { - using (Image img = provider.GetImage()) - { - Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - TPixel[] sourcePixelData = imgMem.ToArray(); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + TPixel[] sourcePixelData = imgMem.ToArray(); - ImageFrameCollection nonGenericFrameCollection = img.Frames; + ImageFrameCollection nonGenericFrameCollection = img.Frames; - nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); - using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) - { - Assert.Equal(1, img.Frames.Count); + nonGenericFrameCollection.AddFrame(new ImageFrame(Configuration.Default, 10, 10)); + using (Image cloned = nonGenericFrameCollection.ExportFrame(0)) + { + Assert.Equal(1, img.Frames.Count); - var expectedClone = (Image)cloned; - expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); - } + var expectedClone = (Image)cloned; + expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } + } - [Fact] - public void CreateFrame_Default() - { - this.Image.Frames.CreateFrame(); + [Fact] + public void CreateFrame_Default() + { + this.Image.Frames.CreateFrame(); - Assert.Equal(2, this.Image.Frames.Count); + Assert.Equal(2, this.Image.Frames.Count); - var frame = (ImageFrame)this.Image.Frames[1]; + var frame = (ImageFrame)this.Image.Frames[1]; - frame.ComparePixelBufferTo(default(Rgba32)); - } + frame.ComparePixelBufferTo(default(Rgba32)); + } - [Fact] - public void CreateFrame_CustomFillColor() - { - this.Image.Frames.CreateFrame(Color.HotPink); + [Fact] + public void CreateFrame_CustomFillColor() + { + this.Image.Frames.CreateFrame(Color.HotPink); - Assert.Equal(2, this.Image.Frames.Count); + Assert.Equal(2, this.Image.Frames.Count); - var frame = (ImageFrame)this.Image.Frames[1]; + var frame = (ImageFrame)this.Image.Frames[1]; - frame.ComparePixelBufferTo(Color.HotPink); - } + frame.ComparePixelBufferTo(Color.HotPink); + } - [Fact] - public void MoveFrame_LeavesFrameInCorrectLocation() + [Fact] + public void MoveFrame_LeavesFrameInCorrectLocation() + { + for (var i = 0; i < 9; i++) { - for (var i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } - - var frame = this.Image.Frames[4]; - this.Image.Frames.MoveFrame(4, 7); - var newIndex = this.Image.Frames.IndexOf(frame); - Assert.Equal(7, newIndex); + this.Image.Frames.CreateFrame(); } - [Fact] - public void IndexOf_ReturnsCorrectIndex() - { - for (var i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + var frame = this.Image.Frames[4]; + this.Image.Frames.MoveFrame(4, 7); + var newIndex = this.Image.Frames.IndexOf(frame); + Assert.Equal(7, newIndex); + } - var frame = this.Image.Frames[4]; - var index = this.Image.Frames.IndexOf(frame); - Assert.Equal(4, index); + [Fact] + public void IndexOf_ReturnsCorrectIndex() + { + for (var i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void Contains_TrueIfMember() - { - for (var i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + var frame = this.Image.Frames[4]; + var index = this.Image.Frames.IndexOf(frame); + Assert.Equal(4, index); + } - var frame = this.Image.Frames[4]; - Assert.True(this.Image.Frames.Contains(frame)); + [Fact] + public void Contains_TrueIfMember() + { + for (var i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void Contains_FalseIfNonMember() - { - for (var i = 0; i < 9; i++) - { - this.Image.Frames.CreateFrame(); - } + var frame = this.Image.Frames[4]; + Assert.True(this.Image.Frames.Contains(frame)); + } - var frame = new ImageFrame(Configuration.Default, 10, 10); - Assert.False(this.Image.Frames.Contains(frame)); + [Fact] + public void Contains_FalseIfNonMember() + { + for (var i = 0; i < 9; i++) + { + this.Image.Frames.CreateFrame(); } - [Fact] - public void PublicProperties_ThrowIfDisposed() - { - var image = new Image(Configuration.Default, 10, 10); - var frameCollection = image.Frames; + var frame = new ImageFrame(Configuration.Default, 10, 10); + Assert.False(this.Image.Frames.Contains(frame)); + } - image.Dispose(); // this should invalidate underlying collection as well + [Fact] + public void PublicProperties_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames; - Assert.Throws(() => { var prop = frameCollection.RootFrame; }); - } + image.Dispose(); // this should invalidate underlying collection as well - [Fact] - public void PublicMethods_ThrowIfDisposed() - { - var image = new Image(Configuration.Default, 10, 10); - var frameCollection = image.Frames; - var rgba32Array = new Rgba32[0]; - - image.Dispose(); // this should invalidate underlying collection as well - - Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); - Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array); }); - Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); - Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); - Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); - Assert.Throws(() => { var res = frameCollection.Contains(default); }); - Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); - Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); - Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); - Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); - Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); - Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); - Assert.Throws(() => { frameCollection.RemoveFrame(default); }); - Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); - } + Assert.Throws(() => { var prop = frameCollection.RootFrame; }); + } + + [Fact] + public void PublicMethods_ThrowIfDisposed() + { + var image = new Image(Configuration.Default, 10, 10); + var frameCollection = image.Frames; + var rgba32Array = new Rgba32[0]; + + image.Dispose(); // this should invalidate underlying collection as well + + Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); + Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array); }); + Assert.Throws(() => { var res = frameCollection.AddFrame((ImageFrame)null); }); + Assert.Throws(() => { var res = frameCollection.AddFrame(rgba32Array.AsSpan()); }); + Assert.Throws(() => { var res = frameCollection.CloneFrame(default); }); + Assert.Throws(() => { var res = frameCollection.Contains(default); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(); }); + Assert.Throws(() => { var res = frameCollection.CreateFrame(default); }); + Assert.Throws(() => { var res = frameCollection.ExportFrame(default); }); + Assert.Throws(() => { var res = frameCollection.GetEnumerator(); }); + Assert.Throws(() => { var prop = frameCollection.IndexOf(default); }); + Assert.Throws(() => { var prop = frameCollection.InsertFrame(default, default); }); + Assert.Throws(() => { frameCollection.RemoveFrame(default); }); + Assert.Throws(() => { frameCollection.MoveFrame(default, default); }); + } - /// - /// 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 : unmanaged, IPixel + /// + /// 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 : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (var dest = new Image(source.GetConfiguration(), source.Width, source.Height)) { - 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); + ImportFrameAs(source.Frames, dest.Frames, 3); + ImportFrameAs(source.Frames, dest.Frames, 4); + + // Drop the original empty root frame: + dest.Frames.RemoveFrame(0); + + dest.DebugSave(provider, appendSourceFileOrDescription: false, extension: "gif"); + dest.CompareToOriginal(provider); + + for (int i = 0; i < 5; i++) { - // Giphy.gif has 5 frames - ImportFrameAs(source.Frames, dest.Frames, 0); - ImportFrameAs(source.Frames, dest.Frames, 1); - ImportFrameAs(source.Frames, dest.Frames, 2); - ImportFrameAs(source.Frames, dest.Frames, 3); - ImportFrameAs(source.Frames, dest.Frames, 4); - - // Drop the original empty root frame: - dest.Frames.RemoveFrame(0); - - dest.DebugSave(provider, appendSourceFileOrDescription: false, extension: "gif"); - dest.CompareToOriginal(provider); - - for (int i = 0; i < 5; i++) - { - CompareGifMetadata(source.Frames[i], dest.Frames[i]); - } + CompareGifMetadata(source.Frames[i], dest.Frames[i]); } } + } - private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) - where TPixel : unmanaged, IPixel + private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) + where TPixel : unmanaged, IPixel + { + using (Image temp = source.CloneFrame(index)) { - using (Image temp = source.CloneFrame(index)) + using (Image temp2 = temp.CloneAs()) { - using (Image temp2 = temp.CloneAs()) - { - destination.AddFrame(temp2.Frames.RootFrame); - } + destination.AddFrame(temp2.Frames.RootFrame); } } + } - 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(); + 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(); - Assert.Equal(aData.DisposalMethod, bData.DisposalMethod); - Assert.Equal(aData.FrameDelay, bData.FrameDelay); - Assert.Equal(aData.ColorTableLength, bData.ColorTableLength); - } + Assert.Equal(aData.DisposalMethod, bData.DisposalMethod); + Assert.Equal(aData.FrameDelay, bData.FrameDelay); + Assert.Equal(aData.ColorTableLength, bData.ColorTableLength); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs index 3efef246b3..14c38d1f70 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs @@ -1,30 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class ImageFrameCollectionTests : IDisposable { - public abstract partial class ImageFrameCollectionTests : IDisposable - { - protected Image Image { get; } + protected Image Image { get; } - protected ImageFrameCollection Collection { get; } + protected ImageFrameCollection Collection { get; } - public ImageFrameCollectionTests() - { - // Needed to get English exception messages, which are checked in several tests. - System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + public ImageFrameCollectionTests() + { + // Needed to get English exception messages, which are checked in several tests. + System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); - this.Image = new Image(10, 10); - this.Collection = new ImageFrameCollection(this.Image, 10, 10, default(Rgba32)); - } + this.Image = new Image(10, 10); + this.Collection = new ImageFrameCollection(this.Image, 10, 10, default(Rgba32)); + } - public void Dispose() - { - this.Image.Dispose(); - this.Collection.Dispose(); - } + public void Dispose() + { + this.Image.Dispose(); + this.Collection.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index d6f877f3d3..3b9779ea42 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -1,193 +1,189 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class ImageFrameTests { - public class ImageFrameTests + public class Indexer { - public class Indexer + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + var allocator = new TestMemoryAllocator(); + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + this.configuration.MemoryAllocator = allocator; + } - private void LimitBufferCapacity(int bufferCapacityInBytes) + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) { - var allocator = new TestMemoryAllocator(); - allocator.BufferCapacityInBytes = bufferCapacityInBytes; - this.configuration.MemoryAllocator = allocator; + this.LimitBufferCapacity(100); } - [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); + } - 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); } - 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); + } - 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); } - [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); + } - 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); } - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeY(bool enforceDisco, int y) + 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); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } + this.LimitBufferCapacity(20); + } - 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); + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); } - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) { - if (disco) - { - this.LimitBufferCapacity(20); - } + image.Frames.RootFrame.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.Frames.RootFrame.CopyPixelDataTo(destination); + } - using var image = new Image(this.configuration, 10, 10); - if (disco) - { - Assert.True(image.GetPixelMemoryGroup().Count > 1); - } + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); - byte[] expected = TestUtils.FillImageWithRandomBytes(image); - byte[] actual = new byte[expected.Length]; + Assert.ThrowsAny(() => + { if (byteSpan) { - image.Frames.RootFrame.CopyPixelDataTo(actual); + image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); } else { - Span destination = MemoryMarshal.Cast(actual); - image.Frames.RootFrame.CopyPixelDataTo(destination); + image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); } - - Assert.True(expected.AsSpan().SequenceEqual(actual)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) - { - using var image = new Image(this.configuration, 10, 10); - - Assert.ThrowsAny(() => - { - if (byteSpan) - { - image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); - } - else - { - image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); - } - }); - } + }); } + } - public class ProcessPixelRows : ProcessPixelRowsTestBase + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.Frames.RootFrame.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows( + image2.Frames.RootFrame, + image3.Frames.RootFrame, + processPixels); + + [Fact] + public void NullReference_Throws() { - protected override void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) => - image.Frames.RootFrame.ProcessPixelRows(processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) => - image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) => - image1.Frames.RootFrame.ProcessPixelRows( - image2.Frames.RootFrame, - image3.Frames.RootFrame, - processPixels); - - [Fact] - public void NullReference_Throws() - { - using var img = new Image(1, 1); - ImageFrame frame = img.Frames.RootFrame; + using var img = new Image(1, 1); + ImageFrame frame = img.Frames.RootFrame; - Assert.Throws(() => frame.ProcessPixelRows(null)); + Assert.Throws(() => frame.ProcessPixelRows(null)); - Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); - Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); - Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); - } + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index ce929a12fa..978e0921ce 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -3,56 +3,54 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class ImageRotationTests { - public class ImageRotationTests + [Fact] + public void RotateImageByMinus90Degrees() { - [Fact] - public void RotateImageByMinus90Degrees() - { - (Size original, Size rotated) = Rotate(-90); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } + (Size original, Size rotated) = Rotate(-90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } - [Fact] - public void RotateImageBy90Degrees() - { - (Size original, Size rotated) = Rotate(90); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } + [Fact] + public void RotateImageBy90Degrees() + { + (Size original, Size rotated) = Rotate(90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } - [Fact] - public void RotateImageBy180Degrees() - { - (Size original, Size rotated) = Rotate(180); - Assert.Equal(original, rotated); - } + [Fact] + public void RotateImageBy180Degrees() + { + (Size original, Size rotated) = Rotate(180); + Assert.Equal(original, rotated); + } - [Fact] - public void RotateImageBy270Degrees() - { - (Size original, Size rotated) = Rotate(270); - Assert.Equal(new Size(original.Height, original.Width), rotated); - } + [Fact] + public void RotateImageBy270Degrees() + { + (Size original, Size rotated) = Rotate(270); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } - [Fact] - public void RotateImageBy360Degrees() - { - (Size original, Size rotated) = Rotate(360); - Assert.Equal(original, rotated); - } + [Fact] + public void RotateImageBy360Degrees() + { + (Size original, Size rotated) = Rotate(360); + Assert.Equal(original, rotated); + } - private static (Size Original, Size Rotated) Rotate(int angle) + private static (Size Original, Size Rotated) Rotate(int angle) + { + var file = TestFile.Create(TestImages.Bmp.Car); + using (var image = Image.Load(file.FullPath)) { - var file = TestFile.Create(TestImages.Bmp.Car); - using (var image = Image.Load(file.FullPath)) - { - Size original = image.Size(); - image.Mutate(x => x.Rotate(angle)); - return (original, image.Size()); - } + Size original = image.Size(); + image.Mutate(x => x.Rotate(angle)); + return (original, image.Size()); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 908cd56ae1..a3f03bed5a 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,100 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the class. +/// +public class ImageSaveTests : IDisposable { - /// - /// Tests the class. - /// - public class ImageSaveTests : IDisposable + private readonly Image image; + private readonly Mock fileSystem; + private readonly Mock encoder; + private readonly Mock encoderNotInFormat; + private IImageFormatDetector localMimeTypeDetector; + private Mock localImageFormat; + + public ImageSaveTests() { - private readonly Image image; - private readonly Mock fileSystem; - private readonly Mock encoder; - private readonly Mock encoderNotInFormat; - private IImageFormatDetector localMimeTypeDetector; - private Mock localImageFormat; - - public ImageSaveTests() - { - this.localImageFormat = new Mock(); - this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); - - this.encoder = new Mock(); - - this.encoderNotInFormat = new Mock(); - - this.fileSystem = new Mock(); - var config = new Configuration - { - FileSystem = this.fileSystem.Object - }; - config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); - config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); - this.image = new Image(config, 1, 1); - } - - [Fact] - public void SavePath() - { - var stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); - this.image.Save("path.png"); + this.localImageFormat = new Mock(); + this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.image, stream)); - } + this.encoder = new Mock(); - [Fact] - public void SavePathWithEncoder() + this.encoderNotInFormat = new Mock(); + + this.fileSystem = new Mock(); + var config = new Configuration { - var stream = new MemoryStream(); - this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + FileSystem = this.fileSystem.Object + }; + config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); + this.image = new Image(config, 1, 1); + } - this.image.Save("path.jpg", this.encoderNotInFormat.Object); + [Fact] + public void SavePath() + { + var stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); + this.image.Save("path.png"); - this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); - } + this.encoder.Verify(x => x.Encode(this.image, stream)); + } - [Fact] - public void ToBase64String() - { - string str = this.image.ToBase64String(this.localImageFormat.Object); + [Fact] + public void SavePathWithEncoder() + { + var stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.encoder.Verify(x => x.Encode(this.image, It.IsAny())); - } + this.image.Save("path.jpg", this.encoderNotInFormat.Object); - [Fact] - public void SaveStreamWithMime() - { - var stream = new MemoryStream(); - this.image.Save(stream, this.localImageFormat.Object); + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); + } - this.encoder.Verify(x => x.Encode(this.image, stream)); - } + [Fact] + public void ToBase64String() + { + string str = this.image.ToBase64String(this.localImageFormat.Object); - [Fact] - public void SaveStreamWithEncoder() - { - var stream = new MemoryStream(); + this.encoder.Verify(x => x.Encode(this.image, It.IsAny())); + } - this.image.Save(stream, this.encoderNotInFormat.Object); + [Fact] + public void SaveStreamWithMime() + { + var stream = new MemoryStream(); + this.image.Save(stream, this.localImageFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); - } + this.encoder.Verify(x => x.Encode(this.image, stream)); + } - public void Dispose() - { - this.image.Dispose(); - } + [Fact] + public void SaveStreamWithEncoder() + { + var stream = new MemoryStream(); + + this.image.Save(stream, this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); + } + + public void Dispose() + { + this.image.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs index d1848281f7..14d65fbb96 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -1,167 +1,162 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class Decode_Cancellation : ImageLoadTestBase { - public class Decode_Cancellation : ImageLoadTestBase - { - private bool isTestStreamSeekable; - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0); - private readonly SemaphoreSlim continueSemaphore = new(0); - private readonly CancellationTokenSource cts = new(); + private bool isTestStreamSeekable; + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new(0); + private readonly SemaphoreSlim continueSemaphore = new(0); + private readonly CancellationTokenSource cts = new(); - public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128; - - [Theory] - [InlineData(false)] - [InlineData(true)] - public Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) - { - this.isTestStreamSeekable = isInputStreamSeekable; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + public Decode_Cancellation() => this.TopLevelConfiguration.StreamProcessingBufferSize = 128; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); - } + [Theory] + [InlineData(false)] + [InlineData(true)] + public Task LoadAsync_Specific_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Theory] - [InlineData(false)] - [InlineData(true)] - public Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) + DecoderOptions options = new() { - this.isTestStreamSeekable = isInputStreamSeekable; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); - } + [Theory] + [InlineData(false)] + [InlineData(true)] + public Task LoadAsync_Agnostic_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Fact] - public Task LoadAsync_Agnostic_Path() + DecoderOptions options = new() { - this.isTestStreamSeekable = true; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.DataStream, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); - } + [Fact] + public Task LoadAsync_Agnostic_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Fact] - public Task LoadAsync_Specific_Path() + DecoderOptions options = new() { - this.isTestStreamSeekable = true; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); - } + [Fact] + public Task LoadAsync_Specific_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Theory] - [InlineData(false)] - [InlineData(true)] - public Task IdentifyAsync_Stream(bool isInputStreamSeekable) + DecoderOptions options = new() { - this.isTestStreamSeekable = isInputStreamSeekable; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.LoadAsync(options, this.MockFilePath, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.DataStream, this.cts.Token)); - } + [Theory] + [InlineData(false)] + [InlineData(true)] + public Task IdentifyAsync_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Fact] - public Task IdentifyAsync_CustomConfiguration_Path() + DecoderOptions options = new() { - this.isTestStreamSeekable = true; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.DataStream, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.MockFilePath, this.cts.Token)); - } + [Fact] + public Task IdentifyAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Theory] - [InlineData(false)] - [InlineData(true)] - public Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + DecoderOptions options = new() { - this.isTestStreamSeekable = isInputStreamSeekable; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.IdentifyAsync(options, this.MockFilePath, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.DataStream, this.cts.Token)); - } + [Theory] + [InlineData(false)] + [InlineData(true)] + public Task IdentifyWithFormatAsync_CustomConfiguration_Stream(bool isInputStreamSeekable) + { + this.isTestStreamSeekable = isInputStreamSeekable; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Fact] - public Task IdentifyWithFormatAsync_CustomConfiguration_Path() + DecoderOptions options = new() { - this.isTestStreamSeekable = true; - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.DataStream, this.cts.Token)); + } - return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.MockFilePath, this.cts.Token)); - } + [Fact] + public Task IdentifyWithFormatAsync_CustomConfiguration_Path() + { + this.isTestStreamSeekable = true; + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); - [Fact] - public Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + DecoderOptions options = new() { - _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + Configuration = this.TopLevelConfiguration + }; - return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); - } + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(options, this.MockFilePath, this.cts.Token)); + } - private async Task DoCancel() - { - // wait until we reach the middle of the steam - await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + [Fact] + public Task IdentifyWithFormatAsync_DefaultConfiguration_Stream() + { + _ = Task.Factory.StartNew(this.DoCancel, TaskCreationOptions.LongRunning); + + return Assert.ThrowsAsync(() => Image.IdentifyWithFormatAsync(this.DataStream, this.cts.Token)); + } - // set the cancellation - this.cts.Cancel(); + private async Task DoCancel() + { + // wait until we reach the middle of the steam + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); - // continue processing the stream - this.continueSemaphore.Release(); - } + // set the cancellation + this.cts.Cancel(); - protected override Stream CreateStream() => this.TestFormat.CreateAsyncSemaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable); + // continue processing the stream + this.continueSemaphore.Release(); } + + protected override Stream CreateStream() => this.TestFormat.CreateAsyncSemaphoreStream(this.notifyWaitPositionReachedSemaphore, this.continueSemaphore, this.isTestStreamSeekable); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index dab6000f3c..271a4a08e8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -1,141 +1,136 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + /// + /// Tests the class. + /// + public class DetectFormat : ImageLoadTestBase { - /// - /// Tests the class. - /// - public class DetectFormat : ImageLoadTestBase - { - private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); + private ReadOnlySpan ActualImageSpan => this.ActualImageBytes.AsSpan(); - private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; - private static readonly IImageFormat ExpectedGlobalFormat = - Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); - [Fact] - public void FromBytes_GlobalConfiguration() - { - IImageFormat type = Image.DetectFormat(this.ActualImageSpan); + [Fact] + public void FromBytes_GlobalConfiguration() + { + IImageFormat type = Image.DetectFormat(this.ActualImageSpan); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromBytes_CustomConfiguration() + [Fact] + public void FromBytes_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageFormat type = Image.DetectFormat(options, this.ByteArray); + IImageFormat type = Image.DetectFormat(options, this.ByteArray); - Assert.Equal(this.LocalImageFormat, type); - } + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void FromFileSystemPath_GlobalConfiguration() - { - IImageFormat type = Image.DetectFormat(ActualImagePath); - Assert.Equal(ExpectedGlobalFormat, type); - } + [Fact] + public void FromFileSystemPath_GlobalConfiguration() + { + IImageFormat type = Image.DetectFormat(ActualImagePath); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromFileSystemPath_CustomConfiguration() + [Fact] + public void FromFileSystemPath_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageFormat type = Image.DetectFormat(options, this.MockFilePath); - Assert.Equal(this.LocalImageFormat, type); - } + IImageFormat type = Image.DetectFormat(options, this.MockFilePath); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void FromStream_GlobalConfiguration() + [Fact] + public void FromStream_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) { - using (var stream = new MemoryStream(this.ActualImageBytes)) - { - IImageFormat type = Image.DetectFormat(stream); - Assert.Equal(ExpectedGlobalFormat, type); - } + IImageFormat type = Image.DetectFormat(stream); + Assert.Equal(ExpectedGlobalFormat, type); } + } - [Fact] - public void FromStream_CustomConfiguration() + [Fact] + public void FromStream_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageFormat type = Image.DetectFormat(options, this.DataStream); - Assert.Equal(this.LocalImageFormat, type); - } + IImageFormat type = Image.DetectFormat(options, this.DataStream); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void WhenNoMatchingFormatFound_ReturnsNull() + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = new() - }; + Configuration = new() + }; - IImageFormat type = Image.DetectFormat(options, this.DataStream); - Assert.Null(type); - } + IImageFormat type = Image.DetectFormat(options, this.DataStream); + Assert.Null(type); + } - [Fact] - public async Task FromStreamAsync_GlobalConfiguration() + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) { - using (var stream = new MemoryStream(this.ActualImageBytes)) - { - IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); - Assert.Equal(ExpectedGlobalFormat, type); - } + IImageFormat type = await Image.DetectFormatAsync(new AsyncStreamWrapper(stream, () => false)); + Assert.Equal(ExpectedGlobalFormat, type); } + } - [Fact] - public async Task FromStreamAsync_CustomConfiguration() + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); - Assert.Equal(this.LocalImageFormat, type); - } + IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = new() - }; + Configuration = new() + }; - IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); - Assert.Null(type); - } + IImageFormat type = await Image.DetectFormatAsync(options, new AsyncStreamWrapper(this.DataStream, () => false)); + Assert.Null(type); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index e7f5b52ac5..bec58f56bd 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -1,333 +1,329 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using System.IO.Compression; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + /// + /// Tests the class. + /// + public class Identify : ImageLoadTestBase { - /// - /// Tests the class. - /// - public class Identify : ImageLoadTestBase - { - private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); - private static readonly Size ExpectedImageSize = new(108, 202); + private static readonly Size ExpectedImageSize = new(108, 202); - private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + private static byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; - private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; + private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; - private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; - private static readonly IImageFormat ExpectedGlobalFormat = - Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); - [Fact] - public void FromBytes_GlobalConfiguration() - { - IImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type); + [Fact] + public void FromBytes_GlobalConfiguration() + { + IImageInfo info = Image.Identify(ActualImageBytes, out IImageFormat type); - Assert.Equal(ExpectedImageSize, info.Size()); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.Equal(ExpectedImageSize, info.Size()); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromBytes_CustomConfiguration() + [Fact] + public void FromBytes_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type); + IImageInfo info = Image.Identify(options, this.ByteArray, out IImageFormat type); - Assert.Equal(this.LocalImageInfo, info); - Assert.Equal(this.LocalImageFormat, type); - } + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void FromFileSystemPath_GlobalConfiguration() - { - IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); + [Fact] + public void FromFileSystemPath_GlobalConfiguration() + { + IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromFileSystemPath_CustomConfiguration() + [Fact] + public void FromFileSystemPath_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); + IImageInfo info = Image.Identify(options, this.MockFilePath, out IImageFormat type); - Assert.Equal(this.LocalImageInfo, info); - Assert.Equal(this.LocalImageFormat, type); - } + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void FromStream_GlobalConfiguration() - { - using var stream = new MemoryStream(ActualImageBytes); - IImageInfo info = Image.Identify(stream, out IImageFormat type); + [Fact] + public void FromStream_GlobalConfiguration() + { + using var stream = new MemoryStream(ActualImageBytes); + IImageInfo info = Image.Identify(stream, out IImageFormat type); - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromStream_GlobalConfiguration_NoFormat() - { - using var stream = new MemoryStream(ActualImageBytes); - IImageInfo info = Image.Identify(stream); + [Fact] + public void FromStream_GlobalConfiguration_NoFormat() + { + using var stream = new MemoryStream(ActualImageBytes); + IImageInfo info = Image.Identify(stream); - Assert.NotNull(info); - } + Assert.NotNull(info); + } - [Fact] - public void FromNonSeekableStream_GlobalConfiguration() - { - using var stream = new MemoryStream(ActualImageBytes); - using var nonSeekableStream = new NonSeekableStream(stream); + [Fact] + public void FromNonSeekableStream_GlobalConfiguration() + { + using var stream = new MemoryStream(ActualImageBytes); + using var nonSeekableStream = new NonSeekableStream(stream); - IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type); + IImageInfo info = Image.Identify(nonSeekableStream, out IImageFormat type); - Assert.NotNull(info); - Assert.Equal(ExpectedGlobalFormat, type); - } + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } - [Fact] - public void FromNonSeekableStream_GlobalConfiguration_NoFormat() - { - using var stream = new MemoryStream(ActualImageBytes); - using var nonSeekableStream = new NonSeekableStream(stream); + [Fact] + public void FromNonSeekableStream_GlobalConfiguration_NoFormat() + { + using var stream = new MemoryStream(ActualImageBytes); + using var nonSeekableStream = new NonSeekableStream(stream); - IImageInfo info = Image.Identify(nonSeekableStream); + IImageInfo info = Image.Identify(nonSeekableStream); - Assert.NotNull(info); - } + Assert.NotNull(info); + } - [Fact] - public void FromStream_CustomConfiguration() + [Fact] + public void FromStream_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); + IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); - Assert.Equal(this.LocalImageInfo, info); - Assert.Equal(this.LocalImageFormat, type); - } + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } - [Fact] - public void FromStream_CustomConfiguration_NoFormat() + [Fact] + public void FromStream_CustomConfiguration_NoFormat() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - IImageInfo info = Image.Identify(options, this.DataStream); + IImageInfo info = Image.Identify(options, this.DataStream); - Assert.Equal(this.LocalImageInfo, info); - } + Assert.Equal(this.LocalImageInfo, info); + } - [Fact] - public void WhenNoMatchingFormatFound_ReturnsNull() + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = new() - }; + Configuration = new() + }; - IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); + IImageInfo info = Image.Identify(options, this.DataStream, out IImageFormat type); - Assert.Null(info); - Assert.Null(type); - } + Assert.Null(info); + Assert.Null(type); + } - [Fact] - public void FromStream_ZeroLength_ReturnsNull() - { - // https://github.com/SixLabors/ImageSharp/issues/1903 - using var zipFile = new ZipArchive(new MemoryStream( - new byte[] - { - 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, - 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, - 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, - 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, - 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, - 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, - 0x00, 0x00, 0x00, 0x00 - })); - using Stream stream = zipFile.Entries[0].Open(); - IImageInfo info = Image.Identify(stream); - Assert.Null(info); - } - - [Fact] - public async Task FromStreamAsync_GlobalConfiguration_NoFormat() - { - using var stream = new MemoryStream(ActualImageBytes); - var asyncStream = new AsyncStreamWrapper(stream, () => false); - IImageInfo info = await Image.IdentifyAsync(asyncStream); + [Fact] + public void FromStream_ZeroLength_ReturnsNull() + { + // https://github.com/SixLabors/ImageSharp/issues/1903 + using var zipFile = new ZipArchive(new MemoryStream( + new byte[] + { + 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, + 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, + 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, + 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, + 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, + 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, + 0x00, 0x00, 0x00, 0x00 + })); + using Stream stream = zipFile.Entries[0].Open(); + IImageInfo info = Image.Identify(stream); + Assert.Null(info); + } - Assert.NotNull(info); - } + [Fact] + public async Task FromStreamAsync_GlobalConfiguration_NoFormat() + { + using var stream = new MemoryStream(ActualImageBytes); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + IImageInfo info = await Image.IdentifyAsync(asyncStream); - [Fact] - public async Task FromStreamAsync_GlobalConfiguration() - { - using var stream = new MemoryStream(ActualImageBytes); - var asyncStream = new AsyncStreamWrapper(stream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); + Assert.NotNull(info); + } - Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); - Assert.Equal(ExpectedGlobalFormat, res.Format); - } + [Fact] + public async Task FromStreamAsync_GlobalConfiguration() + { + using var stream = new MemoryStream(ActualImageBytes); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); - [Fact] - public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat() - { - using var stream = new MemoryStream(ActualImageBytes); - using var nonSeekableStream = new NonSeekableStream(stream); + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } - var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); - IImageInfo info = await Image.IdentifyAsync(asyncStream); + [Fact] + public async Task FromNonSeekableStreamAsync_GlobalConfiguration_NoFormat() + { + using var stream = new MemoryStream(ActualImageBytes); + using var nonSeekableStream = new NonSeekableStream(stream); - Assert.NotNull(info); - } + var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); + IImageInfo info = await Image.IdentifyAsync(asyncStream); - [Fact] - public async Task FromNonSeekableStreamAsync_GlobalConfiguration() - { - using var stream = new MemoryStream(ActualImageBytes); - using var nonSeekableStream = new NonSeekableStream(stream); + Assert.NotNull(info); + } - var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); + [Fact] + public async Task FromNonSeekableStreamAsync_GlobalConfiguration() + { + using var stream = new MemoryStream(ActualImageBytes); + using var nonSeekableStream = new NonSeekableStream(stream); - Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); - Assert.Equal(ExpectedGlobalFormat, res.Format); - } + var asyncStream = new AsyncStreamWrapper(nonSeekableStream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(asyncStream); - [Fact] - public async Task FromStreamAsync_ZeroLength_ReturnsNull() - { - // https://github.com/SixLabors/ImageSharp/issues/1903 - using var zipFile = new ZipArchive(new MemoryStream( - new byte[] - { - 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, - 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, - 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, - 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, - 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, - 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, - 0x00, 0x00, 0x00, 0x00 - })); - using Stream stream = zipFile.Entries[0].Open(); - IImageInfo info = await Image.IdentifyAsync(stream); - Assert.Null(info); - } - - [Fact] - public async Task FromPathAsync_CustomConfiguration() - { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } - IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); - Assert.Equal(this.LocalImageInfo, info); - } + [Fact] + public async Task FromStreamAsync_ZeroLength_ReturnsNull() + { + // https://github.com/SixLabors/ImageSharp/issues/1903 + using var zipFile = new ZipArchive(new MemoryStream( + new byte[] + { + 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, + 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, + 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, + 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, + 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, + 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, + 0x00, 0x00, 0x00, 0x00 + })); + using Stream stream = zipFile.Entries[0].Open(); + IImageInfo info = await Image.IdentifyAsync(stream); + Assert.Null(info); + } - [Fact] - public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() + [Fact] + public async Task FromPathAsync_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, this.MockFilePath); - Assert.NotNull(info.ImageInfo); - Assert.Equal(this.LocalImageFormat, info.Format); - } + IImageInfo info = await Image.IdentifyAsync(options, this.MockFilePath); + Assert.Equal(this.LocalImageInfo, info); + } - [Fact] - public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_CustomConfiguration() + { + DecoderOptions options = new() { - (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); + Configuration = this.LocalConfiguration + }; - Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); - Assert.Equal(ExpectedGlobalFormat, res.Format); - } + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, this.MockFilePath); + Assert.NotNull(info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } - [Fact] - public async Task FromPathAsync_GlobalConfiguration() - { - IImageInfo info = await Image.IdentifyAsync(ActualImagePath); + [Fact] + public async Task IdentifyWithFormatAsync_FromPath_GlobalConfiguration() + { + (IImageInfo ImageInfo, IImageFormat Format) res = await Image.IdentifyWithFormatAsync(ActualImagePath); + + Assert.Equal(ExpectedImageSize, res.ImageInfo.Size()); + Assert.Equal(ExpectedGlobalFormat, res.Format); + } - Assert.Equal(ExpectedImageSize, info.Size()); - } + [Fact] + public async Task FromPathAsync_GlobalConfiguration() + { + IImageInfo info = await Image.IdentifyAsync(ActualImagePath); - [Fact] - public async Task FromStreamAsync_CustomConfiguration() + Assert.Equal(ExpectedImageSize, info.Size()); + } + + [Fact] + public async Task FromStreamAsync_CustomConfiguration() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.LocalConfiguration - }; + Configuration = this.LocalConfiguration + }; - var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); - Assert.Equal(this.LocalImageInfo, info.ImageInfo); - Assert.Equal(this.LocalImageFormat, info.Format); - } + Assert.Equal(this.LocalImageInfo, info.ImageInfo); + Assert.Equal(this.LocalImageFormat, info.Format); + } - [Fact] - public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + [Fact] + public async Task WhenNoMatchingFormatFoundAsync_ReturnsNull() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = new() - }; + Configuration = new() + }; - var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); - (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); + var asyncStream = new AsyncStreamWrapper(this.DataStream, () => false); + (IImageInfo ImageInfo, IImageFormat Format) info = await Image.IdentifyWithFormatAsync(options, asyncStream); - Assert.Null(info.ImageInfo); - } + Assert.Null(info.ImageInfo); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index 1cfc744d00..cace719bd9 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -1,119 +1,115 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public abstract class ImageLoadTestBase : IDisposable { - public abstract class ImageLoadTestBase : IDisposable - { - private Lazy dataStreamLazy; - - protected Image localStreamReturnImageRgba32; - - protected Image localStreamReturnImageAgnostic; - - protected Mock localDecoder; - - protected IImageFormatDetector localMimeTypeDetector; - - protected Mock localImageFormatMock; - - protected Mock localImageInfoMock; - - protected readonly string MockFilePath = Guid.NewGuid().ToString(); + private Lazy dataStreamLazy; - internal readonly Mock LocalFileSystemMock = new Mock(); + protected Image localStreamReturnImageRgba32; - protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); + protected Image localStreamReturnImageAgnostic; - public Configuration LocalConfiguration { get; } + protected Mock localDecoder; - public TestFormat TestFormat { get; } = new TestFormat(); + protected IImageFormatDetector localMimeTypeDetector; - /// - /// Gets the top-level configuration in the context of this test case. - /// It has registered. - /// - public Configuration TopLevelConfiguration { get; } + protected Mock localImageFormatMock; - public byte[] Marker { get; } + protected Mock localImageInfoMock; - public Stream DataStream => this.dataStreamLazy.Value; + protected readonly string MockFilePath = Guid.NewGuid().ToString(); - public byte[] DecodedData { get; private set; } + internal readonly Mock LocalFileSystemMock = new Mock(); - protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); + protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); - protected ImageLoadTestBase() - { - this.localStreamReturnImageRgba32 = new Image(1, 1); - this.localStreamReturnImageAgnostic = new Image(1, 1); + public Configuration LocalConfiguration { get; } - this.localImageInfoMock = new Mock(); - this.localImageFormatMock = new Mock(); + public TestFormat TestFormat { get; } = new TestFormat(); - var detector = new Mock(); - detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(this.localImageInfoMock.Object); + /// + /// Gets the top-level configuration in the context of this test case. + /// It has registered. + /// + public Configuration TopLevelConfiguration { get; } - this.localDecoder = detector.As(); - this.localDecoder - .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s, ct) => - { - using var ms = new MemoryStream(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(this.localStreamReturnImageRgba32); + public byte[] Marker { get; } - this.localDecoder - .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((c, s, ct) => - { - using var ms = new MemoryStream(); - s.CopyTo(ms); - this.DecodedData = ms.ToArray(); - }) - .Returns(this.localStreamReturnImageAgnostic); + public Stream DataStream => this.dataStreamLazy.Value; - this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + public byte[] DecodedData { get; private set; } - this.LocalConfiguration = new Configuration(); - this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); - this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); + protected byte[] ByteArray => ((MemoryStream)this.DataStream).ToArray(); - this.TopLevelConfiguration = new Configuration(this.TestFormat); - - this.Marker = Guid.NewGuid().ToByteArray(); - - this.dataStreamLazy = new Lazy(this.CreateStream); - Stream StreamFactory() => this.DataStream; - - this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory); - this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory); - this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; - this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; - } - - public void Dispose() - { - // Clean up the global object; - this.localStreamReturnImageRgba32?.Dispose(); - this.localStreamReturnImageAgnostic?.Dispose(); - } + protected ImageLoadTestBase() + { + this.localStreamReturnImageRgba32 = new Image(1, 1); + this.localStreamReturnImageAgnostic = new Image(1, 1); + + this.localImageInfoMock = new Mock(); + this.localImageFormatMock = new Mock(); + + var detector = new Mock(); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(this.localImageInfoMock.Object); + + this.localDecoder = detector.As(); + this.localDecoder + .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => + { + using var ms = new MemoryStream(); + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); + }) + .Returns(this.localStreamReturnImageRgba32); + + this.localDecoder + .Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((c, s, ct) => + { + using var ms = new MemoryStream(); + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); + }) + .Returns(this.localStreamReturnImageAgnostic); + + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); + + this.LocalConfiguration = new Configuration(); + this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); + + this.TopLevelConfiguration = new Configuration(this.TestFormat); + + this.Marker = Guid.NewGuid().ToByteArray(); + + this.dataStreamLazy = new Lazy(this.CreateStream); + Stream StreamFactory() => this.DataStream; + + this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(StreamFactory); + this.topLevelFileSystem.AddFile(this.MockFilePath, StreamFactory); + this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; + this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; + } - protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker); + public void Dispose() + { + // Clean up the global object; + this.localStreamReturnImageRgba32?.Dispose(); + this.localStreamReturnImageAgnostic?.Dispose(); } + + protected virtual Stream CreateStream() => this.TestFormat.CreateStream(this.Marker); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 5b794928e4..238096be49 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -1,60 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class LoadPixelData { - public class LoadPixelData + [Theory] + [InlineData(false)] + [InlineData(true)] + [ValidateDisposedMemoryAllocations] + public void FromPixels(bool useSpan) { - [Theory] - [InlineData(false)] - [InlineData(true)] - [ValidateDisposedMemoryAllocations] - public void FromPixels(bool useSpan) - { - Rgba32[] data = { Color.Black, Color.White, Color.White, Color.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(Color.Black, (Color)img[0, 0]); - Assert.Equal(Color.White, (Color)img[0, 1]); + using (Image img = useSpan + ? Image.LoadPixelData(data.AsSpan(), 2, 2) + : Image.LoadPixelData(data, 2, 2)) + { + Assert.NotNull(img); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Color.White, (Color)img[1, 0]); - Assert.Equal(Color.Black, (Color)img[1, 1]); - } + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void FromBytes(bool useSpan) - { - byte[] data = - { - 0, 0, 0, 255, // 0,0 - 255, 255, 255, 255, // 0,1 - 255, 255, 255, 255, // 1,0 - 0, 0, 0, 255, // 1,1 - }; - using (Image img = useSpan - ? Image.LoadPixelData(data.AsSpan(), 2, 2) - : Image.LoadPixelData(data, 2, 2)) + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FromBytes(bool useSpan) + { + byte[] data = { - Assert.NotNull(img); - Assert.Equal(Color.Black, (Color)img[0, 0]); - Assert.Equal(Color.White, (Color)img[0, 1]); + 0, 0, 0, 255, // 0,0 + 255, 255, 255, 255, // 0,1 + 255, 255, 255, 255, // 1,0 + 0, 0, 0, 255, // 1,1 + }; + using (Image img = useSpan + ? Image.LoadPixelData(data.AsSpan(), 2, 2) + : Image.LoadPixelData(data, 2, 2)) + { + Assert.NotNull(img); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Color.White, (Color)img[1, 0]); - Assert.Equal(Color.Black, (Color)img[1, 1]); - } + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index df68eda0fa..c8723e1f10 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -1,106 +1,102 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FileSystemPath_PassLocalConfiguration : ImageLoadTestBase { - public class Load_FileSystemPath_PassLocalConfiguration : ImageLoadTestBase + [Fact] + public void Configuration_Path_Specific() { - [Fact] - public void Configuration_Path_Specific() + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.MockFilePath); + var img = Image.Load(options, this.MockFilePath); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Path_Agnostic() + [Fact] + public void Configuration_Path_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.MockFilePath); + var img = Image.Load(options, this.MockFilePath); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Path_OutFormat_Specific() + [Fact] + public void Configuration_Path_OutFormat_Specific() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.MockFilePath, out IImageFormat format); + var img = Image.Load(options, this.MockFilePath, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Path_OutFormat_Agnostic() + [Fact] + public void Configuration_Path_OutFormat_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.MockFilePath, out IImageFormat format); + var img = Image.Load(options, this.MockFilePath, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void WhenFileNotFound_Throws() - => Assert.Throws( - () => + [Fact] + public void WhenFileNotFound_Throws() + => Assert.Throws( + () => + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - - Image.Load(options, Guid.NewGuid().ToString()); - }); - - [Fact] - public void WhenPathIsNull_Throws() - => Assert.Throws( - () => + Configuration = this.TopLevelConfiguration + }; + + Image.Load(options, Guid.NewGuid().ToString()); + }); + + [Fact] + public void WhenPathIsNull_Throws() + => Assert.Throws( + () => + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; - Image.Load(options, (string)null); - }); - } + Configuration = this.TopLevelConfiguration + }; + Image.Load(options, (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 1ce8fd4eba..67fec5e497 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -1,91 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FileSystemPath_UseDefaultConfiguration { - public class Load_FileSystemPath_UseDefaultConfiguration - { - private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); + private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); - private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); + private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); - [Fact] - public void Path_Specific() - { - using var img = Image.Load(this.Path); - VerifyDecodedImage(img); - } + [Fact] + public void Path_Specific() + { + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); + } - [Fact] - public void Path_Agnostic() - { - using var img = Image.Load(this.Path); - VerifyDecodedImage(img); - } + [Fact] + public void Path_Agnostic() + { + using var img = Image.Load(this.Path); + VerifyDecodedImage(img); + } - [Fact] - public async Task Path_Agnostic_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } + [Fact] + public async Task Path_Agnostic_Async() + { + using Image img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } - [Fact] - public async Task Path_Specific_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } + [Fact] + public async Task Path_Specific_Async() + { + using Image img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } - [Fact] - public async Task Path_Agnostic_Configuration_Async() - { - using Image img = await Image.LoadAsync(this.Path); - VerifyDecodedImage(img); - } + [Fact] + public async Task Path_Agnostic_Configuration_Async() + { + using Image img = await Image.LoadAsync(this.Path); + VerifyDecodedImage(img); + } - [Fact] - public void Path_OutFormat_Specific() - { - using var img = Image.Load(this.Path, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } + [Fact] + public void Path_OutFormat_Specific() + { + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } - [Fact] - public void Path_OutFormat_Agnostic() - { - using var img = Image.Load(this.Path, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } + [Fact] + public void Path_OutFormat_Agnostic() + { + using var img = Image.Load(this.Path, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } - [Fact] - public void WhenFileNotFound_Throws() - => Assert.Throws(() => Image.Load(Guid.NewGuid().ToString())); + [Fact] + public void WhenFileNotFound_Throws() + => Assert.Throws(() => Image.Load(Guid.NewGuid().ToString())); - [Fact] - public void WhenPathIsNull_Throws() - => Assert.Throws(() => Image.Load((string)null)); + [Fact] + public void WhenPathIsNull_Throws() + => Assert.Throws(() => Image.Load((string)null)); - [Fact] - public Task Async_WhenFileNotFound_Throws() - => Assert.ThrowsAsync(() => Image.LoadAsync(Guid.NewGuid().ToString())); + [Fact] + public Task Async_WhenFileNotFound_Throws() + => Assert.ThrowsAsync(() => Image.LoadAsync(Guid.NewGuid().ToString())); - [Fact] - public Task Async_WhenPathIsNull_Throws() - => Assert.ThrowsAsync(() => Image.LoadAsync((string)null)); - } + [Fact] + public Task Async_WhenPathIsNull_Throws() + => Assert.ThrowsAsync(() => Image.LoadAsync((string)null)); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs index d4e8adf114..7946bdbed9 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -1,83 +1,79 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase { - public class Load_FromBytes_PassLocalConfiguration : ImageLoadTestBase - { - private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); + private ReadOnlySpan ByteSpan => this.ByteArray.AsSpan(); - [Fact] - public void Configuration_Bytes_Specific() + [Fact] + public void Configuration_Bytes_Specific() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.ByteSpan); + var img = Image.Load(options, this.ByteSpan); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Bytes_Agnostic() + [Fact] + public void Configuration_Bytes_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.ByteSpan); + var img = Image.Load(options, this.ByteSpan); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Bytes_OutFormat_Specific() + [Fact] + public void Configuration_Bytes_OutFormat_Specific() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.ByteSpan, out IImageFormat format); + var img = Image.Load(options, this.ByteSpan, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Bytes_OutFormat_Agnostic() + [Fact] + public void Configuration_Bytes_OutFormat_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.ByteSpan, out IImageFormat format); + var img = Image.Load(options, this.ByteSpan, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index 2b57f9aa13..6ea469e65c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -1,54 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FromBytes_UseGlobalConfiguration { - public class Load_FromBytes_UseGlobalConfiguration + private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; + + private static Span ByteSpan => new(ByteArray); + + private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); + + [Fact] + public void Bytes_Specific() + { + using var img = Image.Load(ByteSpan); + VerifyDecodedImage(img); + } + + [Fact] + public void Bytes_Agnostic() + { + using var img = Image.Load(ByteSpan); + VerifyDecodedImage(img); + } + + [Fact] + public void Bytes_OutFormat_Specific() + { + using var img = Image.Load(ByteSpan, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } + + [Fact] + public void Bytes_OutFormat_Agnostic() { - private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - - private static Span ByteSpan => new(ByteArray); - - private static void VerifyDecodedImage(Image img) => Assert.Equal(new Size(127, 64), img.Size()); - - [Fact] - public void Bytes_Specific() - { - using var img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - } - - [Fact] - public void Bytes_Agnostic() - { - using var img = Image.Load(ByteSpan); - VerifyDecodedImage(img); - } - - [Fact] - public void Bytes_OutFormat_Specific() - { - using var img = Image.Load(ByteSpan, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } - - [Fact] - public void Bytes_OutFormat_Agnostic() - { - using var img = Image.Load(ByteSpan, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } + using var img = Image.Load(ByteSpan, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs index 8931ab46c3..46e6dfac59 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_PassLocalConfiguration.cs @@ -1,113 +1,109 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FromStream_PassLocalConfiguration : ImageLoadTestBase { - public class Load_FromStream_PassLocalConfiguration : ImageLoadTestBase + [Fact] + public void Configuration_Stream_Specific() { - [Fact] - public void Configuration_Stream_Specific() + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.DataStream); + var img = Image.Load(options, this.DataStream); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.Sample(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.Sample(), img); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Stream_Agnostic() + [Fact] + public void Configuration_Stream_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.DataStream); + var img = Image.Load(options, this.DataStream); - Assert.NotNull(img); - Assert.Equal(this.TestFormat.SampleAgnostic(), img); + Assert.NotNull(img); + Assert.Equal(this.TestFormat.SampleAgnostic(), img); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void NonSeekableStream() + [Fact] + public void NonSeekableStream() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var stream = new NonSeekableStream(this.DataStream); - var img = Image.Load(options, stream); + var stream = new NonSeekableStream(this.DataStream); + var img = Image.Load(options, stream); - Assert.NotNull(img); + Assert.NotNull(img); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public async Task NonSeekableStreamAsync() + [Fact] + public async Task NonSeekableStreamAsync() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var stream = new NonSeekableStream(this.DataStream); - Image img = await Image.LoadAsync(options, stream); + var stream = new NonSeekableStream(this.DataStream); + Image img = await Image.LoadAsync(options, stream); - Assert.NotNull(img); + Assert.NotNull(img); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Stream_OutFormat_Specific() + [Fact] + public void Configuration_Stream_OutFormat_Specific() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.DataStream, out IImageFormat format); + var img = Image.Load(options, this.DataStream, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifySpecificDecodeCall(this.Marker, this.TopLevelConfiguration); + } - [Fact] - public void Configuration_Stream_OutFormat_Agnostic() + [Fact] + public void Configuration_Stream_OutFormat_Agnostic() + { + DecoderOptions options = new() { - DecoderOptions options = new() - { - Configuration = this.TopLevelConfiguration - }; + Configuration = this.TopLevelConfiguration + }; - var img = Image.Load(options, this.DataStream, out IImageFormat format); + var img = Image.Load(options, this.DataStream, out IImageFormat format); - Assert.NotNull(img); - Assert.Equal(this.TestFormat, format); + Assert.NotNull(img); + Assert.Equal(this.TestFormat, format); - this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); - } + this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs index 7ad660c68c..a051176a77 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs @@ -1,42 +1,37 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public partial class ImageTests { - public partial class ImageTests + public class Load_FromStream_Throws : IDisposable { - public class Load_FromStream_Throws : IDisposable - { - private static readonly byte[] Data = new byte[] { 0x01 }; + private static readonly byte[] Data = new byte[] { 0x01 }; - private MemoryStream Stream { get; } = new MemoryStream(Data); + private MemoryStream Stream { get; } = new MemoryStream(Data); - [Fact] - public void Image_Load_Throws_UnknownImageFormatException() - => Assert.Throws(() => + [Fact] + public void Image_Load_Throws_UnknownImageFormatException() + => Assert.Throws(() => + { + using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) { - using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) - { - } - }); + } + }); - [Fact] - public void Image_Load_T_Throws_UnknownImageFormatException() - => Assert.Throws(() => + [Fact] + public void Image_Load_T_Throws_UnknownImageFormatException() + => Assert.Throws(() => + { + using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) { - using (Image.Load(DecoderOptions.Default, this.Stream, out IImageFormat format)) - { - } - }); + } + }); - public void Dispose() => this.Stream?.Dispose(); - } + public void Dispose() => this.Stream?.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs index aacc0cee9a..839749c5d6 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -1,109 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class Load_FromStream_UseDefaultConfiguration : IDisposable { - public class Load_FromStream_UseDefaultConfiguration : IDisposable - { - private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; + private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - private MemoryStream BaseStream { get; } + private MemoryStream BaseStream { get; } - private AsyncStreamWrapper Stream { get; } + private AsyncStreamWrapper Stream { get; } - private bool AllowSynchronousIO { get; set; } = true; + private bool AllowSynchronousIO { get; set; } = true; - public Load_FromStream_UseDefaultConfiguration() - { - this.BaseStream = new MemoryStream(Data); - this.Stream = new AsyncStreamWrapper(this.BaseStream, () => this.AllowSynchronousIO); - } + public Load_FromStream_UseDefaultConfiguration() + { + this.BaseStream = new MemoryStream(Data); + this.Stream = new AsyncStreamWrapper(this.BaseStream, () => this.AllowSynchronousIO); + } - private static void VerifyDecodedImage(Image img) - => Assert.Equal(new Size(127, 64), img.Size()); + private static void VerifyDecodedImage(Image img) + => Assert.Equal(new Size(127, 64), img.Size()); - [Fact] - public void Stream_Specific() - { - using var img = Image.Load(this.Stream); - VerifyDecodedImage(img); - } + [Fact] + public void Stream_Specific() + { + using var img = Image.Load(this.Stream); + VerifyDecodedImage(img); + } - [Fact] - public void Stream_Agnostic() - { - using var img = Image.Load(this.Stream); - VerifyDecodedImage(img); - } + [Fact] + public void Stream_Agnostic() + { + using var img = Image.Load(this.Stream); + VerifyDecodedImage(img); + } - [Fact] - public void Stream_OutFormat_Specific() - { - using var img = Image.Load(this.Stream, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } + [Fact] + public void Stream_OutFormat_Specific() + { + using var img = Image.Load(this.Stream, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } - [Fact] - public void Stream_OutFormat_Agnostic() - { - using var img = Image.Load(this.Stream, out IImageFormat format); - VerifyDecodedImage(img); - Assert.IsType(format); - } + [Fact] + public void Stream_OutFormat_Agnostic() + { + using var img = Image.Load(this.Stream, out IImageFormat format); + VerifyDecodedImage(img); + Assert.IsType(format); + } - [Fact] - public async Task Async_Stream_OutFormat_Agnostic() + [Fact] + public async Task Async_Stream_OutFormat_Agnostic() + { + this.AllowSynchronousIO = false; + (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) { - this.AllowSynchronousIO = false; - (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); - using (formattedImage.Image) - { - VerifyDecodedImage(formattedImage.Image); - Assert.IsType(formattedImage.Format); - } + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); } + } - [Fact] - public async Task Async_Stream_Specific() - { - this.AllowSynchronousIO = false; - using Image img = await Image.LoadAsync(this.Stream); - VerifyDecodedImage(img); - } + [Fact] + public async Task Async_Stream_Specific() + { + this.AllowSynchronousIO = false; + using Image img = await Image.LoadAsync(this.Stream); + VerifyDecodedImage(img); + } - [Fact] - public async Task Async_Stream_Agnostic() - { - this.AllowSynchronousIO = false; - using Image img = await Image.LoadAsync(this.Stream); - VerifyDecodedImage(img); - } + [Fact] + public async Task Async_Stream_Agnostic() + { + this.AllowSynchronousIO = false; + using Image img = await Image.LoadAsync(this.Stream); + VerifyDecodedImage(img); + } - [Fact] - public async Task Async_Stream_OutFormat_Specific() + [Fact] + public async Task Async_Stream_OutFormat_Specific() + { + this.AllowSynchronousIO = false; + (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); + using (formattedImage.Image) { - this.AllowSynchronousIO = false; - (Image Image, IImageFormat Format) formattedImage = await Image.LoadWithFormatAsync(this.Stream); - using (formattedImage.Image) - { - VerifyDecodedImage(formattedImage.Image); - Assert.IsType(formattedImage.Format); - } + VerifyDecodedImage(formattedImage.Image); + Assert.IsType(formattedImage.Format); } - - public void Dispose() => this.BaseStream?.Dispose(); } + + public void Dispose() => this.BaseStream?.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index 88fee00686..88a6b5890b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -1,81 +1,77 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class Save { - public class Save + [Fact] + public void DetectedEncoding() { - [Fact] - public void DetectedEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "DetectedEncoding.png"); - - using (var image = new Image(10, 10)) - { - image.Save(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "DetectedEncoding.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + image.Save(file); } - [Fact] - public void WhenExtensionIsUnknown_Throws() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public void WhenExtensionIsUnknown_Throws() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); - Assert.Throws( - () => + Assert.Throws( + () => + { + using (var image = new Image(10, 10)) { - using (var image = new Image(10, 10)) - { - image.Save(file); - } - }); - } + image.Save(file); + } + }); + } - [Fact] - public void SetEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "SetEncoding.dat"); + [Fact] + public void SetEncoding() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "SetEncoding.dat"); - using (var image = new Image(10, 10)) - { - image.Save(file, new PngEncoder()); - } + using (var image = new Image(10, 10)) + { + image.Save(file, new PngEncoder()); + } - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); } + } - [Fact] - public void ThrowsWhenDisposed() + [Fact] + public void ThrowsWhenDisposed() + { + using var image = new Image(5, 5); + image.Dispose(); + IImageEncoder encoder = Mock.Of(); + using (var stream = new MemoryStream()) { - using var image = new Image(5, 5); - image.Dispose(); - IImageEncoder encoder = Mock.Of(); - using (var stream = new MemoryStream()) - { - Assert.Throws(() => image.Save(stream, encoder)); - } + Assert.Throws(() => image.Save(stream, encoder)); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index d4b90671ad..1ceadb964d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -1,157 +1,151 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using Moq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class SaveAsync { - public class SaveAsync + [Fact] + public async Task DetectedEncoding() { - [Fact] - public async Task DetectedEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "DetectedEncodingAsync.png"); - - using (var image = new Image(10, 10)) - { - await image.SaveAsync(file); - } + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "DetectedEncodingAsync.png"); - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file); } - [Fact] - public async Task WhenExtensionIsUnknown_Throws() + using (Image.Load(file, out IImageFormat mime)) { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); + Assert.Equal("image/png", mime.DefaultMimeType); + } + } + + [Fact] + public async Task WhenExtensionIsUnknown_Throws() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "UnknownExtensionsEncoding_Throws.tmp"); - await Assert.ThrowsAsync( - async () => + await Assert.ThrowsAsync( + async () => + { + using (var image = new Image(10, 10)) { - using (var image = new Image(10, 10)) - { - await image.SaveAsync(file); - } - }); - } + await image.SaveAsync(file); + } + }); + } - [Fact] - public async Task SetEncoding() - { - string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); - string file = Path.Combine(dir, "SetEncoding.dat"); + [Fact] + public async Task SetEncoding() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageTests)); + string file = Path.Combine(dir, "SetEncoding.dat"); - using (var image = new Image(10, 10)) - { - await image.SaveAsync(file, new PngEncoder()); - } + using (var image = new Image(10, 10)) + { + await image.SaveAsync(file, new PngEncoder()); + } - using (Image.Load(file, out IImageFormat mime)) - { - Assert.Equal("image/png", mime.DefaultMimeType); - } + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/png", mime.DefaultMimeType); } + } - [Theory] - [InlineData("test.pbm", "image/x-portable-pixmap")] - [InlineData("test.png", "image/png")] - [InlineData("test.tga", "image/tga")] - [InlineData("test.bmp", "image/bmp")] - [InlineData("test.jpg", "image/jpeg")] - [InlineData("test.gif", "image/gif")] - public async Task SaveStreamWithMime(string filename, string mimeType) + [Theory] + [InlineData("test.pbm", "image/x-portable-pixmap")] + [InlineData("test.png", "image/png")] + [InlineData("test.tga", "image/tga")] + [InlineData("test.bmp", "image/bmp")] + [InlineData("test.jpg", "image/jpeg")] + [InlineData("test.gif", "image/gif")] + public async Task SaveStreamWithMime(string filename, string mimeType) + { + using (var image = new Image(5, 5)) { - using (var image = new Image(5, 5)) - { - string ext = Path.GetExtension(filename); - IImageFormat format = image.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); - Assert.Equal(mimeType, format.DefaultMimeType); + string ext = Path.GetExtension(filename); + IImageFormat format = image.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); + Assert.Equal(mimeType, format.DefaultMimeType); - using (var stream = new MemoryStream()) - { - var asyncStream = new AsyncStreamWrapper(stream, () => false); - await image.SaveAsync(asyncStream, format); + using (var stream = new MemoryStream()) + { + var asyncStream = new AsyncStreamWrapper(stream, () => false); + await image.SaveAsync(asyncStream, format); - stream.Position = 0; + stream.Position = 0; - (Image Image, IImageFormat Format) imf = await Image.LoadWithFormatAsync(stream); + (Image Image, IImageFormat Format) imf = await Image.LoadWithFormatAsync(stream); - Assert.Equal(format, imf.Format); - Assert.Equal(mimeType, imf.Format.DefaultMimeType); + Assert.Equal(format, imf.Format); + Assert.Equal(mimeType, imf.Format.DefaultMimeType); - imf.Image.Dispose(); - } + imf.Image.Dispose(); } } + } - [Fact] - public async Task ThrowsWhenDisposed() + [Fact] + public async Task ThrowsWhenDisposed() + { + var image = new Image(5, 5); + image.Dispose(); + IImageEncoder encoder = Mock.Of(); + using (var stream = new MemoryStream()) { - var image = new Image(5, 5); - image.Dispose(); - IImageEncoder encoder = Mock.Of(); - using (var stream = new MemoryStream()) - { - await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); - } + await Assert.ThrowsAsync(async () => await image.SaveAsync(stream, encoder)); } + } - [Theory] - [InlineData("test.pbm")] - [InlineData("test.png")] - [InlineData("test.tga")] - [InlineData("test.bmp")] - [InlineData("test.jpg")] - [InlineData("test.gif")] - public async Task SaveAsync_NeverCallsSyncMethods(string filename) + [Theory] + [InlineData("test.pbm")] + [InlineData("test.png")] + [InlineData("test.tga")] + [InlineData("test.bmp")] + [InlineData("test.jpg")] + [InlineData("test.gif")] + public async Task SaveAsync_NeverCallsSyncMethods(string filename) + { + using (var image = new Image(5, 5)) { - using (var image = new Image(5, 5)) + IImageEncoder encoder = image.DetectEncoder(filename); + using (var stream = new MemoryStream()) { - IImageEncoder encoder = image.DetectEncoder(filename); - using (var stream = new MemoryStream()) - { - var asyncStream = new AsyncStreamWrapper(stream, () => false); - await image.SaveAsync(asyncStream, encoder); - } + var asyncStream = new AsyncStreamWrapper(stream, () => false); + await image.SaveAsync(asyncStream, encoder); } } + } - [Fact] - public async Task SaveAsync_WithNonSeekableStream_IsCancellable() + [Fact] + public async Task SaveAsync_WithNonSeekableStream_IsCancellable() + { + using var image = new Image(4000, 4000); + var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; + using var stream = new MemoryStream(); + var asyncStream = new AsyncStreamWrapper(stream, () => false); + var cts = new CancellationTokenSource(); + + var pausedStream = new PausedStream(asyncStream); + pausedStream.OnWaiting(s => { - using var image = new Image(4000, 4000); - var encoder = new PngEncoder() { CompressionLevel = PngCompressionLevel.BestCompression }; - using var stream = new MemoryStream(); - var asyncStream = new AsyncStreamWrapper(stream, () => false); - var cts = new CancellationTokenSource(); - - var pausedStream = new PausedStream(asyncStream); - pausedStream.OnWaiting(s => - { - cts.Cancel(); - pausedStream.Release(); - }); + cts.Cancel(); + pausedStream.Release(); + }); - await Assert.ThrowsAsync(async () => await image.SaveAsync(pausedStream, encoder, cts.Token)); - } + await Assert.ThrowsAsync(async () => await image.SaveAsync(pausedStream, encoder, cts.Token)); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 19abf9e28c..1b2c20548b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -1,11 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; -using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; @@ -13,200 +11,266 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public partial class ImageTests { - public partial class ImageTests + public class WrapMemory { - public class WrapMemory + /// + /// A exposing the locked pixel memory of a instance. + /// TODO: This should be an example in https://github.com/SixLabors/Samples + /// + public class BitmapMemoryManager : MemoryManager { - /// - /// A exposing the locked pixel memory of a instance. - /// TODO: This should be an example in https://github.com/SixLabors/Samples - /// - public class BitmapMemoryManager : MemoryManager - { - private readonly Bitmap bitmap; + private readonly Bitmap bitmap; - private readonly BitmapData bmpData; + private readonly BitmapData bmpData; - private readonly int length; + private readonly int length; - public BitmapMemoryManager(Bitmap bitmap) + public BitmapMemoryManager(Bitmap bitmap) + { + if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { - if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) - { - throw new ArgumentException("bitmap.PixelFormat != PixelFormat.Format32bppArgb", nameof(bitmap)); - } - - this.bitmap = bitmap; - var rectangle = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height); - this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); - this.length = bitmap.Width * bitmap.Height; + throw new ArgumentException("bitmap.PixelFormat != PixelFormat.Format32bppArgb", nameof(bitmap)); } - public bool IsDisposed { get; private set; } - - protected override void Dispose(bool disposing) - { - if (this.IsDisposed) - { - return; - } - - if (disposing) - { - this.bitmap.UnlockBits(this.bmpData); - } + this.bitmap = bitmap; + var rectangle = new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height); + this.bmpData = bitmap.LockBits(rectangle, ImageLockMode.ReadWrite, bitmap.PixelFormat); + this.length = bitmap.Width * bitmap.Height; + } - this.IsDisposed = true; - } + public bool IsDisposed { get; private set; } - public override unsafe Span GetSpan() + protected override void Dispose(bool disposing) + { + if (this.IsDisposed) { - void* ptr = (void*)this.bmpData.Scan0; - return new Span(ptr, this.length); + return; } - public override unsafe MemoryHandle Pin(int elementIndex = 0) + if (disposing) { - void* ptr = (void*)this.bmpData.Scan0; - return new MemoryHandle(ptr, pinnable: this); + this.bitmap.UnlockBits(this.bmpData); } - public override void Unpin() - { - } + this.IsDisposed = true; } - public sealed class CastMemoryManager : MemoryManager - where TFrom : unmanaged - where TTo : unmanaged + public override unsafe Span GetSpan() { - private readonly Memory memory; + void* ptr = (void*)this.bmpData.Scan0; + return new Span(ptr, this.length); + } - public CastMemoryManager(Memory memory) - { - this.memory = memory; - } + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + void* ptr = (void*)this.bmpData.Scan0; + return new MemoryHandle(ptr, pinnable: this); + } - /// - protected override void Dispose(bool disposing) - { - } + public override void Unpin() + { + } + } - /// - public override Span GetSpan() - { - return MemoryMarshal.Cast(this.memory.Span); - } + public sealed class CastMemoryManager : MemoryManager + where TFrom : unmanaged + where TTo : unmanaged + { + private readonly Memory memory; - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - int byteOffset = elementIndex * Unsafe.SizeOf(); - int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf(), out int remainder); + public CastMemoryManager(Memory memory) + { + this.memory = memory; + } - if (remainder != 0) - { - ThrowHelper.ThrowArgumentException("The input index doesn't result in an aligned item access", nameof(elementIndex)); - } + /// + protected override void Dispose(bool disposing) + { + } - return this.memory.Slice(shiftedOffset).Pin(); - } + /// + public override Span GetSpan() + { + return MemoryMarshal.Cast(this.memory.Span); + } - /// - public override void Unpin() + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + int byteOffset = elementIndex * Unsafe.SizeOf(); + int shiftedOffset = Math.DivRem(byteOffset, Unsafe.SizeOf(), out int remainder); + + if (remainder != 0) { + ThrowHelper.ThrowArgumentException("The input index doesn't result in an aligned item access", nameof(elementIndex)); } + + return this.memory.Slice(shiftedOffset).Pin(); } - [Fact] - public void WrapMemory_CreatedImageIsCorrect() + /// + public override void Unpin() { - var cfg = Configuration.CreateDefaultInstance(); - var metaData = new ImageMetadata(); + } + } - var array = new Rgba32[25]; - var memory = new Memory(array); + [Fact] + public void WrapMemory_CreatedImageIsCorrect() + { + var cfg = Configuration.CreateDefaultInstance(); + var metaData = new ImageMetadata(); - using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - ref Rgba32 pixel0 = ref imageMem.Span[0]; - Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); + var array = new Rgba32[25]; + var memory = new Memory(array); - Assert.Equal(cfg, image.GetConfiguration()); - Assert.Equal(metaData, image.Metadata); - } + using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) + { + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; + Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); + + Assert.Equal(cfg, image.GetConfiguration()); + Assert.Equal(metaData, image.Metadata); } + } - [Fact] - public void WrapSystemDrawingBitmap_WhenObserved() + [Fact] + public void WrapSystemDrawingBitmap_WhenObserved() + { + if (ShouldSkipBitmapTest) { - if (ShouldSkipBitmapTest) - { - return; - } + return; + } - using (var bmp = new Bitmap(51, 23)) + using (var bmp = new Bitmap(51, 23)) + { + using (var memoryManager = new BitmapMemoryManager(bmp)) { - using (var memoryManager = new BitmapMemoryManager(bmp)) + Memory memory = memoryManager.Memory; + Bgra32 bg = Color.Red; + Bgra32 fg = Color.Green; + + using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Memory memory = memoryManager.Memory; - Bgra32 bg = Color.Red; - Bgra32 fg = Color.Green; + Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); + image.GetPixelMemoryGroup().Fill(bg); - using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) + image.ProcessPixelRows(accessor => { - Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - image.GetPixelMemoryGroup().Fill(bg); - - image.ProcessPixelRows(accessor => + for (var i = 10; i < 20; i++) { - for (var i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); - } - - Assert.False(memoryManager.IsDisposed); - } - - if (!Directory.Exists(TestEnvironment.ActualOutputDirectoryFullPath)) - { - Directory.CreateDirectory(TestEnvironment.ActualOutputDirectoryFullPath); + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); + Assert.False(memoryManager.IsDisposed); + } - bmp.Save(fn, ImageFormat.Bmp); + if (!Directory.Exists(TestEnvironment.ActualOutputDirectoryFullPath)) + { + Directory.CreateDirectory(TestEnvironment.ActualOutputDirectoryFullPath); } + + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); + + bmp.Save(fn, ImageFormat.Bmp); + } + } + + [Fact] + public void WrapSystemDrawingBitmap_WhenOwned() + { + if (ShouldSkipBitmapTest) + { + return; } - [Fact] - public void WrapSystemDrawingBitmap_WhenOwned() + using (var bmp = new Bitmap(51, 23)) { - if (ShouldSkipBitmapTest) + var memoryManager = new BitmapMemoryManager(bmp); + Bgra32 bg = Color.Red; + Bgra32 fg = Color.Green; + + using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - return; + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); + image.GetPixelMemoryGroup().Fill(bg); + image.ProcessPixelRows(accessor => + { + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } - using (var bmp = new Bitmap(51, 23)) + Assert.True(memoryManager.IsDisposed); + + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenOwned)}.bmp"); + + bmp.Save(fn, ImageFormat.Bmp); + } + } + + [Fact] + public void WrapMemory_FromBytes_CreatedImageIsCorrect() + { + var cfg = Configuration.CreateDefaultInstance(); + var metaData = new ImageMetadata(); + + var array = new byte[25 * Unsafe.SizeOf()]; + var memory = new Memory(array); + + using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) + { + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; + Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0)); + + Assert.Equal(cfg, image.GetConfiguration()); + Assert.Equal(metaData, image.Metadata); + } + } + + [Fact] + public void WrapSystemDrawingBitmap_FromBytes_WhenObserved() + { + if (ShouldSkipBitmapTest) + { + return; + } + + using (var bmp = new Bitmap(51, 23)) + { + using (var memoryManager = new BitmapMemoryManager(bmp)) { - var memoryManager = new BitmapMemoryManager(bmp); + Memory pixelMemory = memoryManager.Memory; + Memory byteMemory = new CastMemoryManager(pixelMemory).Memory; Bgra32 bg = Color.Red; Bgra32 fg = Color.Green; - using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) + using (var image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); + Span pixelSpan = pixelMemory.Span; + Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; + + // We can't compare the two Memory instances directly as they wrap different memory managers. + // To check that the underlying data matches, we can just manually check their lenth, and the + // fact that a reference to the first pixel in both spans is actually the same memory location. + Assert.Equal(pixelSpan.Length, imageSpan.Length); + Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); + image.GetPixelMemoryGroup().Fill(bg); image.ProcessPixelRows(accessor => { @@ -217,61 +281,65 @@ public void WrapSystemDrawingBitmap_WhenOwned() }); } - Assert.True(memoryManager.IsDisposed); + Assert.False(memoryManager.IsDisposed); + } - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenOwned)}.bmp"); + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - bmp.Save(fn, ImageFormat.Bmp); - } + bmp.Save(fn, ImageFormat.Bmp); } + } - [Fact] - public void WrapMemory_FromBytes_CreatedImageIsCorrect() - { - var cfg = Configuration.CreateDefaultInstance(); - var metaData = new ImageMetadata(); + [Fact] + public unsafe void WrapMemory_FromPointer_CreatedImageIsCorrect() + { + var cfg = Configuration.CreateDefaultInstance(); + var metaData = new ImageMetadata(); - var array = new byte[25 * Unsafe.SizeOf()]; - var memory = new Memory(array); + var array = new Rgba32[25]; - using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) + fixed (void* ptr = array) + { + using (var image = Image.WrapMemory(cfg, ptr, 5, 5, metaData)) { Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - ref Rgba32 pixel0 = ref imageMem.Span[0]; - Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0)); + Span imageSpan = imageMem.Span; + ref Rgba32 pixel0 = ref imageSpan[0]; + Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); + ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; + Assert.True(Unsafe.AreSame(ref array[array.Length - 1], ref pixel_1)); Assert.Equal(cfg, image.GetConfiguration()); Assert.Equal(metaData, image.Metadata); } } + } - [Fact] - public void WrapSystemDrawingBitmap_FromBytes_WhenObserved() + [Fact] + public unsafe void WrapSystemDrawingBitmap_FromPointer() + { + if (ShouldSkipBitmapTest) { - if (ShouldSkipBitmapTest) - { - return; - } + return; + } - using (var bmp = new Bitmap(51, 23)) + using (var bmp = new Bitmap(51, 23)) + { + using (var memoryManager = new BitmapMemoryManager(bmp)) { - using (var memoryManager = new BitmapMemoryManager(bmp)) - { - Memory pixelMemory = memoryManager.Memory; - Memory byteMemory = new CastMemoryManager(pixelMemory).Memory; - Bgra32 bg = Color.Red; - Bgra32 fg = Color.Green; + Memory pixelMemory = memoryManager.Memory; + Bgra32 bg = Color.Red; + Bgra32 fg = Color.Green; - using (var image = Image.WrapMemory(byteMemory, bmp.Width, bmp.Height)) + fixed (void* p = pixelMemory.Span) + { + using (var image = Image.WrapMemory(p, bmp.Width, bmp.Height)) { Span pixelSpan = pixelMemory.Span; Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; - // We can't compare the two Memory instances directly as they wrap different memory managers. - // To check that the underlying data matches, we can just manually check their lenth, and the - // fact that a reference to the first pixel in both spans is actually the same memory location. Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); @@ -287,255 +355,182 @@ public void WrapSystemDrawingBitmap_FromBytes_WhenObserved() Assert.False(memoryManager.IsDisposed); } - - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - - bmp.Save(fn, ImageFormat.Bmp); } - } - [Fact] - public unsafe void WrapMemory_FromPointer_CreatedImageIsCorrect() - { - var cfg = Configuration.CreateDefaultInstance(); - var metaData = new ImageMetadata(); + string fn = System.IO.Path.Combine( + TestEnvironment.ActualOutputDirectoryFullPath, + $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - var array = new Rgba32[25]; - - fixed (void* ptr = array) - { - using (var image = Image.WrapMemory(cfg, ptr, 5, 5, metaData)) - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Span imageSpan = imageMem.Span; - ref Rgba32 pixel0 = ref imageSpan[0]; - Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); - ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; - Assert.True(Unsafe.AreSame(ref array[array.Length - 1], ref pixel_1)); - - Assert.Equal(cfg, image.GetConfiguration()); - Assert.Equal(metaData, image.Metadata); - } - } + bmp.Save(fn, ImageFormat.Bmp); } + } - [Fact] - public unsafe void WrapSystemDrawingBitmap_FromPointer() - { - if (ShouldSkipBitmapTest) - { - return; - } - - using (var bmp = new Bitmap(51, 23)) - { - using (var memoryManager = new BitmapMemoryManager(bmp)) - { - Memory pixelMemory = memoryManager.Memory; - Bgra32 bg = Color.Red; - Bgra32 fg = Color.Green; - - fixed (void* p = pixelMemory.Span) - { - using (var image = Image.WrapMemory(p, bmp.Width, bmp.Height)) - { - Span pixelSpan = pixelMemory.Span; - Span imageSpan = image.GetRootFramePixelBuffer().DangerousGetSingleMemory().Span; - - Assert.Equal(pixelSpan.Length, imageSpan.Length); - Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); - - image.GetPixelMemoryGroup().Fill(bg); - image.ProcessPixelRows(accessor => - { - for (var i = 10; i < 20; i++) - { - accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); - } - }); - } - - Assert.False(memoryManager.IsDisposed); - } - } - - string fn = System.IO.Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); - - bmp.Save(fn, ImageFormat.Bmp); - } - } + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(1023, 32, 32)] + public void WrapMemory_MemoryOfT_InvalidSize(int size, int height, int width) + { + var array = new Rgba32[size]; + var memory = new Memory(array); - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - public void WrapMemory_MemoryOfT_InvalidSize(int size, int height, int width) - { - var array = new Rgba32[size]; - var memory = new Memory(array); + Assert.Throws(() => Image.WrapMemory(memory, height, width)); + } - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } + [Theory] + [InlineData(25, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1024, 32, 32)] + [InlineData(2048, 32, 32)] + public void WrapMemory_MemoryOfT_ValidSize(int size, int height, int width) + { + var array = new Rgba32[size]; + var memory = new Memory(array); - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_MemoryOfT_ValidSize(int size, int height, int width) - { - var array = new Rgba32[size]; - var memory = new Memory(array); + Image.WrapMemory(memory, height, width); + } - Image.WrapMemory(memory, height, width); - } + private class TestMemoryOwner : IMemoryOwner + { + public bool Disposed { get; private set; } - private class TestMemoryOwner : IMemoryOwner - { - public bool Disposed { get; private set; } + public Memory Memory { get; set; } - public Memory Memory { get; set; } + public void Dispose() => this.Disposed = true; + } - public void Dispose() => this.Disposed = true; - } + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(1023, 32, 32)] + public void WrapMemory_IMemoryOwnerOfT_InvalidSize(int size, int height, int width) + { + var array = new Rgba32[size]; + var memory = new TestMemoryOwner { Memory = array }; - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - public void WrapMemory_IMemoryOwnerOfT_InvalidSize(int size, int height, int width) - { - var array = new Rgba32[size]; - var memory = new TestMemoryOwner { Memory = array }; + Assert.Throws(() => Image.WrapMemory(memory, height, width)); + } - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } + [Theory] + [InlineData(25, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1024, 32, 32)] + [InlineData(2048, 32, 32)] + public void WrapMemory_IMemoryOwnerOfT_ValidSize(int size, int height, int width) + { + var array = new Rgba32[size]; + var memory = new TestMemoryOwner { Memory = array }; - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_IMemoryOwnerOfT_ValidSize(int size, int height, int width) + using (var img = Image.WrapMemory(memory, width, height)) { - var array = new Rgba32[size]; - var memory = new TestMemoryOwner { Memory = array }; + Assert.Equal(width, img.Width); + Assert.Equal(height, img.Height); - using (var img = Image.WrapMemory(memory, width, height)) + img.ProcessPixelRows(accessor => { - Assert.Equal(width, img.Width); - Assert.Equal(height, img.Height); - - img.ProcessPixelRows(accessor => + for (int i = 0; i < height; ++i) { - for (int i = 0; i < height; ++i) - { - var arrayIndex = width * i; + var arrayIndex = width * i; - Span rowSpan = accessor.GetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref array[arrayIndex]; + Span rowSpan = accessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref array[arrayIndex]; - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } - }); - } - - Assert.True(memory.Disposed); + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - public void WrapMemory_IMemoryOwnerOfByte_InvalidSize(int size, int height, int width) - { - var array = new byte[size * Unsafe.SizeOf()]; - var memory = new TestMemoryOwner { Memory = array }; + Assert.True(memory.Disposed); + } - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(1023, 32, 32)] + public void WrapMemory_IMemoryOwnerOfByte_InvalidSize(int size, int height, int width) + { + var array = new byte[size * Unsafe.SizeOf()]; + var memory = new TestMemoryOwner { Memory = array }; + + Assert.Throws(() => Image.WrapMemory(memory, height, width)); + } + + [Theory] + [InlineData(25, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1024, 32, 32)] + [InlineData(2048, 32, 32)] + public void WrapMemory_IMemoryOwnerOfByte_ValidSize(int size, int height, int width) + { + var pixelSize = Unsafe.SizeOf(); + var array = new byte[size * pixelSize]; + var memory = new TestMemoryOwner { Memory = array }; - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_IMemoryOwnerOfByte_ValidSize(int size, int height, int width) + using (var img = Image.WrapMemory(memory, width, height)) { - var pixelSize = Unsafe.SizeOf(); - var array = new byte[size * pixelSize]; - var memory = new TestMemoryOwner { Memory = array }; + Assert.Equal(width, img.Width); + Assert.Equal(height, img.Height); - using (var img = Image.WrapMemory(memory, width, height)) + img.ProcessPixelRows(acccessor => { - Assert.Equal(width, img.Width); - Assert.Equal(height, img.Height); - - img.ProcessPixelRows(acccessor => + for (int i = 0; i < height; ++i) { - for (int i = 0; i < height; ++i) - { - var arrayIndex = pixelSize * width * i; + var arrayIndex = pixelSize * width * i; - Span rowSpan = acccessor.GetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); + Span rowSpan = acccessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } - }); - } - - Assert.True(memory.Disposed); + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(1023, 32, 32)] - public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width) - { - var array = new byte[size * Unsafe.SizeOf()]; - var memory = new Memory(array); + Assert.True(memory.Disposed); + } - Assert.Throws(() => Image.WrapMemory(memory, height, width)); - } + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(1023, 32, 32)] + public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width) + { + var array = new byte[size * Unsafe.SizeOf()]; + var memory = new Memory(array); - [Theory] - [InlineData(25, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1024, 32, 32)] - [InlineData(2048, 32, 32)] - public void WrapMemory_MemoryOfByte_ValidSize(int size, int height, int width) - { - var array = new byte[size * Unsafe.SizeOf()]; - var memory = new Memory(array); + Assert.Throws(() => Image.WrapMemory(memory, height, width)); + } - Image.WrapMemory(memory, height, width); - } + [Theory] + [InlineData(25, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1024, 32, 32)] + [InlineData(2048, 32, 32)] + public void WrapMemory_MemoryOfByte_ValidSize(int size, int height, int width) + { + var array = new byte[size * Unsafe.SizeOf()]; + var memory = new Memory(array); - [Theory] - [InlineData(0, 5, 5)] - [InlineData(20, 5, 5)] - [InlineData(26, 5, 5)] - [InlineData(2, 1, 1)] - [InlineData(1023, 32, 32)] - public unsafe void WrapMemory_Pointer_Null(int size, int height, int width) - { - Assert.Throws(() => Image.WrapMemory((void*)null, height, width)); - } + Image.WrapMemory(memory, height, width); + } - private static bool ShouldSkipBitmapTest => - !TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"); + [Theory] + [InlineData(0, 5, 5)] + [InlineData(20, 5, 5)] + [InlineData(26, 5, 5)] + [InlineData(2, 1, 1)] + [InlineData(1023, 32, 32)] + public unsafe void WrapMemory_Pointer_Null(int size, int height, int width) + { + Assert.Throws(() => Image.WrapMemory((void*)null, height, width)); } + + private static bool ShouldSkipBitmapTest => + !TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1"); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 9e3b79647c..02ccfb713b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,361 +1,355 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the class. +/// +public partial class ImageTests { - /// - /// Tests the class. - /// - public partial class ImageTests + public class Constructor { - public class Constructor + [Fact] + public void Width_Height() { - [Fact] - public void Width_Height() + using (var image = new Image(11, 23)) { - using (var image = new Image(11, 23)) - { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(default(Rgba32)); + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); + image.ComparePixelBufferTo(default(Rgba32)); - Assert.Equal(Configuration.Default, image.GetConfiguration()); - } + Assert.Equal(Configuration.Default, image.GetConfiguration()); } + } - [Fact] - public void Configuration_Width_Height() - { - Configuration configuration = Configuration.Default.Clone(); + [Fact] + public void Configuration_Width_Height() + { + Configuration configuration = Configuration.Default.Clone(); - using (var image = new Image(configuration, 11, 23)) - { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(default(Rgba32)); + using (var image = new Image(configuration, 11, 23)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); + image.ComparePixelBufferTo(default(Rgba32)); - Assert.Equal(configuration, image.GetConfiguration()); - } + Assert.Equal(configuration, image.GetConfiguration()); } + } - [Fact] - public void Configuration_Width_Height_BackgroundColor() - { - Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Color.Aquamarine; + [Fact] + public void Configuration_Width_Height_BackgroundColor() + { + Configuration configuration = Configuration.Default.Clone(); + Rgba32 color = Color.Aquamarine; - using (var image = new Image(configuration, 11, 23, color)) - { - Assert.Equal(11, image.Width); - Assert.Equal(23, image.Height); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - Assert.Equal(11 * 23, imageMem.Length); - image.ComparePixelBufferTo(color); + using (var image = new Image(configuration, 11, 23, color)) + { + Assert.Equal(11, image.Width); + Assert.Equal(23, image.Height); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); + image.ComparePixelBufferTo(color); - Assert.Equal(configuration, image.GetConfiguration()); - } + Assert.Equal(configuration, image.GetConfiguration()); } + } - [Fact] - public void CreateUninitialized() - { - Configuration configuration = Configuration.Default.Clone(); + [Fact] + public void CreateUninitialized() + { + Configuration configuration = Configuration.Default.Clone(); - byte dirtyValue = 123; - configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); - var metadata = new ImageMetadata(); + byte dirtyValue = 123; + configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); + var metadata = new ImageMetadata(); - using (var image = Image.CreateUninitialized(configuration, 21, 22, metadata)) - { - Assert.Equal(21, image.Width); - Assert.Equal(22, image.Height); - Assert.Same(configuration, image.GetConfiguration()); - Assert.Same(metadata, image.Metadata); + using (var image = Image.CreateUninitialized(configuration, 21, 22, metadata)) + { + Assert.Equal(21, image.Width); + Assert.Equal(22, image.Height); + Assert.Same(configuration, image.GetConfiguration()); + Assert.Same(metadata, image.Metadata); - Assert.Equal(dirtyValue, image[5, 5].PackedValue); - } + Assert.Equal(dirtyValue, image[5, 5].PackedValue); } } + } - public class Indexer - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - private void LimitBufferCapacity(int bufferCapacityInBytes) => - this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; + private void LimitBufferCapacity(int bufferCapacityInBytes) => + this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; - [Theory] - [InlineData(false)] - [InlineData(true)] - public void GetSet(bool enforceDisco) + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (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); + this.LimitBufferCapacity(100); } - 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); + 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); + } - using var image = new Image(this.configuration, 10, 10); - ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); - Assert.Equal("x", ex.ParamName); + 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); } - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeX(bool enforceDisco, int x) + 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) { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } + 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); + } - 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); } - [Theory] - [MemberData(nameof(OutOfRangeData))] - public void Set_OutOfRangeY(bool enforceDisco, int y) + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) { - if (enforceDisco) - { - this.LimitBufferCapacity(100); - } + this.LimitBufferCapacity(20); + } - using var image = new Image(this.configuration, 10, 10); - ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); - Assert.Equal("y", ex.ParamName); + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); } - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) { - if (disco) - { - this.LimitBufferCapacity(20); - } + image.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.CopyPixelDataTo(destination); + } - using var image = new Image(this.configuration, 10, 10); - if (disco) - { - Assert.True(image.GetPixelMemoryGroup().Count > 1); - } + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } - byte[] expected = TestUtils.FillImageWithRandomBytes(image); - byte[] actual = new byte[expected.Length]; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { if (byteSpan) { - image.CopyPixelDataTo(actual); + image.CopyPixelDataTo(new byte[199]); } else { - Span destination = MemoryMarshal.Cast(actual); - image.CopyPixelDataTo(destination); + image.CopyPixelDataTo(new La16[99]); } - - Assert.True(expected.AsSpan().SequenceEqual(actual)); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) - { - using var image = new Image(this.configuration, 10, 10); - - Assert.ThrowsAny(() => - { - if (byteSpan) - { - image.CopyPixelDataTo(new byte[199]); - } - else - { - image.CopyPixelDataTo(new La16[99]); - } - }); - } + }); } + } - public class ProcessPixelRows : ProcessPixelRowsTestBase + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, image3, processPixels); + + [Fact] + public void NullReference_Throws() { - protected override void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) => - image.ProcessPixelRows(processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) => - image1.ProcessPixelRows(image2, processPixels); - - protected override void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) => - image1.ProcessPixelRows(image2, image3, processPixels); - - [Fact] - public void NullReference_Throws() - { - using var img = new Image(1, 1); + using var img = new Image(1, 1); - Assert.Throws(() => img.ProcessPixelRows(null)); + Assert.Throws(() => img.ProcessPixelRows(null)); - Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, img, null)); + Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); - Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - } + Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); } + } - public class Dispose - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + public class Dispose + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - public void MultipleDisposeCalls() - { - var image = new Image(this.configuration, 10, 10); - image.Dispose(); - image.Dispose(); - } + public void MultipleDisposeCalls() + { + var image = new Image(this.configuration, 10, 10); + image.Dispose(); + image.Dispose(); + } - [Fact] - public void NonPrivateProperties_ObjectDisposedException() - { - var image = new Image(this.configuration, 10, 10); - var genericImage = (Image)image; + [Fact] + public void NonPrivateProperties_ObjectDisposedException() + { + var image = new Image(this.configuration, 10, 10); + var genericImage = (Image)image; - image.Dispose(); + image.Dispose(); - // Image - Assert.Throws(() => { var prop = image.Frames; }); + // Image + Assert.Throws(() => { var prop = image.Frames; }); - // Image - Assert.Throws(() => { var prop = genericImage.Frames; }); - } + // Image + Assert.Throws(() => { var prop = genericImage.Frames; }); + } - [Fact] - public void Save_ObjectDisposedException() - { - using var stream = new MemoryStream(); - var image = new Image(this.configuration, 10, 10); - var encoder = new JpegEncoder(); + [Fact] + public void Save_ObjectDisposedException() + { + using var stream = new MemoryStream(); + var image = new Image(this.configuration, 10, 10); + var encoder = new JpegEncoder(); - image.Dispose(); + image.Dispose(); - // Image - Assert.Throws(() => image.Save(stream, encoder)); - } + // Image + Assert.Throws(() => image.Save(stream, encoder)); + } - [Fact] - public void AcceptVisitor_ObjectDisposedException() - { - // This test technically should exist but it's impossible to write proper test case without reflection: - // All visitor types are private and can't be created without context of some save/processing operation - // Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway - return; - } + [Fact] + public void AcceptVisitor_ObjectDisposedException() + { + // This test technically should exist but it's impossible to write proper test case without reflection: + // All visitor types are private and can't be created without context of some save/processing operation + // Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway + return; + } - [Fact] - public void NonPrivateMethods_ObjectDisposedException() - { - var image = new Image(this.configuration, 10, 10); - var genericImage = (Image)image; + [Fact] + public void NonPrivateMethods_ObjectDisposedException() + { + var image = new Image(this.configuration, 10, 10); + var genericImage = (Image)image; - image.Dispose(); + image.Dispose(); - // Image - Assert.Throws(() => { var res = image.Clone(this.configuration); }); - Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); - Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); + // Image + Assert.Throws(() => { var res = image.Clone(this.configuration); }); + Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); + Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); - // Image - Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); - } + // Image + Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); } + } - public class DetectEncoder + public class DetectEncoder + { + [Fact] + public void KnownExtension_ReturnsEncoder() { - [Fact] - public void KnownExtension_ReturnsEncoder() - { - using var image = new Image(1, 1); - IImageEncoder encoder = image.DetectEncoder("dummy.png"); - Assert.NotNull(encoder); - Assert.IsType(encoder); - } + using var image = new Image(1, 1); + IImageEncoder encoder = image.DetectEncoder("dummy.png"); + Assert.NotNull(encoder); + Assert.IsType(encoder); + } - [Fact] - public void UnknownExtension_ThrowsNotSupportedException() - { - using var image = new Image(1, 1); - Assert.Throws(() => image.DetectEncoder("dummy.yolo")); - } + [Fact] + public void UnknownExtension_ThrowsNotSupportedException() + { + using var image = new Image(1, 1); + Assert.Throws(() => image.DetectEncoder("dummy.yolo")); + } - [Fact] - public void NoDetectorRegisteredForKnownExtension_ThrowsNotSupportedException() - { - var configuration = new Configuration(); - var format = new TestFormat(); - configuration.ImageFormatsManager.AddImageFormat(format); - configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); + [Fact] + public void NoDetectorRegisteredForKnownExtension_ThrowsNotSupportedException() + { + var configuration = new Configuration(); + var format = new TestFormat(); + configuration.ImageFormatsManager.AddImageFormat(format); + configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); - using var image = new Image(configuration, 1, 1); - Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); - } + using var image = new Image(configuration, 1, 1); + Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 6d4198c595..b65719228c 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -1,91 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class LargeImageIntegrationTests { - public class LargeImageIntegrationTests + [Theory(Skip = "For local testing only.")] + [WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] + public void CreateAndResize(TestImageProvider provider) { - [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); - } + using Image image = provider.GetImage(); + image.Mutate(c => c.Resize(1000, 1000)); + image.DebugSave(provider); + } - [Fact] - public void PreferContiguousImageBuffers_CreateImage_BufferIsContiguous() - { - // Run remotely to avoid large allocation in the test process: - RemoteExecutor.Invoke(RunTest).Dispose(); + [Fact] + public void PreferContiguousImageBuffers_CreateImage_BufferIsContiguous() + { + // Run remotely to avoid large allocation in the test process: + RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; + static void RunTest() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; - using var image = new Image(configuration, 2048, 2048); - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory mem)); - Assert.Equal(2048 * 2048, mem.Length); - } + using var image = new Image(configuration, 2048, 2048); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.Equal(2048 * 2048, mem.Length); } + } - [Theory] - [InlineData("bmp")] - [InlineData("png")] - [InlineData("jpeg")] - [InlineData("gif")] - [InlineData("tiff")] - [InlineData("webp")] - public void PreferContiguousImageBuffers_LoadImage_BufferIsContiguous(string formatOuter) + [Theory] + [InlineData("bmp")] + [InlineData("png")] + [InlineData("jpeg")] + [InlineData("gif")] + [InlineData("tiff")] + [InlineData("webp")] + public void PreferContiguousImageBuffers_LoadImage_BufferIsContiguous(string formatOuter) + { + // Run remotely to avoid large allocation in the test process: + RemoteExecutor.Invoke(RunTest, formatOuter).Dispose(); + + static void RunTest(string formatInner) { - // Run remotely to avoid large allocation in the test process: - RemoteExecutor.Invoke(RunTest, formatOuter).Dispose(); + using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations(); - static void RunTest(string formatInner) + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; + IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( + configuration.ImageFormatsManager.FindFormatByFileExtension(formatInner)); + string dir = TestEnvironment.CreateOutputDirectory(".Temp"); + string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}"); + using (Image temp = new(2048, 2048)) { - using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations(); - - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( - configuration.ImageFormatsManager.FindFormatByFileExtension(formatInner)); - string dir = TestEnvironment.CreateOutputDirectory(".Temp"); - string path = Path.Combine(dir, $"{Guid.NewGuid()}.{formatInner}"); - using (Image temp = new(2048, 2048)) - { - temp.Save(path, encoder); - } + temp.Save(path, encoder); + } - DecoderOptions options = new() - { - Configuration = configuration - }; + DecoderOptions options = new() + { + Configuration = configuration + }; - using var image = Image.Load(options, path); - File.Delete(path); - Assert.Equal(1, image.GetPixelMemoryGroup().Count); - } + using var image = Image.Load(options, path); + File.Delete(path); + Assert.Equal(1, image.GetPixelMemoryGroup().Count); } + } - [Theory] - [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] - public void DangerousTryGetSinglePixelMemory_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) - { - provider.LimitAllocatorBufferCapacity().InPixels(10); - using Image image = provider.GetImage(); - Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory mem)); - Assert.False(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory _)); - } + [Theory] + [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] + public void DangerousTryGetSinglePixelMemory_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) + { + provider.LimitAllocatorBufferCapacity().InPixels(10); + using Image image = provider.GetImage(); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.False(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory _)); } } diff --git a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs index e35f05f364..d0576c828b 100644 --- a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs +++ b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs @@ -1,28 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Formats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. +/// +public class MockImageFormatDetector : IImageFormatDetector { - /// - /// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. - /// - public class MockImageFormatDetector : IImageFormatDetector - { - private IImageFormat localImageFormatMock; + private IImageFormat localImageFormatMock; - public MockImageFormatDetector(IImageFormat imageFormat) - { - this.localImageFormatMock = imageFormat; - } + public MockImageFormatDetector(IImageFormat imageFormat) + { + this.localImageFormatMock = imageFormat; + } - public int HeaderSize => 1; + public int HeaderSize => 1; - public IImageFormat DetectFormat(ReadOnlySpan header) - { - return this.localImageFormatMock; - } + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.localImageFormatMock; } } diff --git a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs index 469c7304d7..e9b64d3b2c 100644 --- a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs +++ b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs @@ -1,43 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +internal class NonSeekableStream : Stream { - internal class NonSeekableStream : Stream - { - private readonly Stream dataStream; + private readonly Stream dataStream; - public NonSeekableStream(Stream dataStream) - => this.dataStream = dataStream; + public NonSeekableStream(Stream dataStream) + => this.dataStream = dataStream; - public override bool CanRead => this.dataStream.CanRead; + public override bool CanRead => this.dataStream.CanRead; - public override bool CanSeek => false; + public override bool CanSeek => false; - public override bool CanWrite => false; + public override bool CanWrite => false; - public override long Length => throw new NotSupportedException(); + public override long Length => throw new NotSupportedException(); - public override long Position - { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } - public override void Flush() => this.dataStream.Flush(); + public override void Flush() => this.dataStream.Flush(); - public override int Read(byte[] buffer, int offset, int count) => this.dataStream.Read(buffer, offset, count); + public override int Read(byte[] buffer, int offset, int count) => this.dataStream.Read(buffer, offset, count); - public override long Seek(long offset, SeekOrigin origin) - => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) + => throw new NotSupportedException(); - public override void SetLength(long value) - => throw new NotSupportedException(); + public override void SetLength(long value) + => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) - => throw new NotImplementedException(); - } + public override void Write(byte[] buffer, int offset, int count) + => throw new NotImplementedException(); } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index 97f6a02b0d..27cbe1a7ea 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -1,323 +1,319 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract class ProcessPixelRowsTestBase { - public abstract class ProcessPixelRowsTestBase + protected abstract void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + [Fact] + public void PixelAccessorDimensionsAreCorrect() { - protected abstract void ProcessPixelRowsImpl( - Image image, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - protected abstract void ProcessPixelRowsImpl( - Image image1, - Image image2, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - protected abstract void ProcessPixelRowsImpl( - Image image1, - Image image2, - Image image3, - PixelAccessorAction processPixels) - where TPixel : unmanaged, IPixel; - - [Fact] - public void PixelAccessorDimensionsAreCorrect() + using var image = new Image(123, 456); + this.ProcessPixelRowsImpl(image, accessor => { - using var image = new Image(123, 456); - this.ProcessPixelRowsImpl(image, accessor => - { - Assert.Equal(123, accessor.Width); - Assert.Equal(456, accessor.Height); - }); - } + Assert.Equal(123, accessor.Width); + Assert.Equal(456, accessor.Height); + }); + } - [Fact] - public void WriteImagePixels_SingleImage() + [Fact] + public void WriteImagePixels_SingleImage() + { + using var image = new Image(256, 256); + this.ProcessPixelRowsImpl(image, accessor => { - using var image = new Image(256, 256); - this.ProcessPixelRowsImpl(image, accessor => + for (int y = 0; y < accessor.Height; y++) { - for (int y = 0; y < accessor.Height; y++) + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new L16((ushort)(x * y)); - } + row[x] = new L16((ushort)(x * y)); } - }); + } + }); - Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * y, actual); - } + int actual = row[x].PackedValue; + Assert.Equal(x * y, actual); } } + } - [Fact] - public void WriteImagePixels_MultiImage2() + [Fact] + public void WriteImagePixels_MultiImage2() + { + using var img1 = new Image(256, 256); + Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) { - using var img1 = new Image(256, 256); - Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } + row[x] = new L16((ushort)(x * y)); } + } - using var img2 = new Image(256, 256); + using var img2 = new Image(256, 256); - this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => + this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => + { + for (int y = 0; y < accessor1.Height; y++) { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - row1.CopyTo(row2); - } - }); + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + row1.CopyTo(row2); + } + }); - buffer = img2.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) + buffer = img2.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) { - Span row = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual); - } + int actual = row[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual); } } + } - [Fact] - public void WriteImagePixels_MultiImage3() + [Fact] + public void WriteImagePixels_MultiImage3() + { + using var img1 = new Image(256, 256); + Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) { - using var img1 = new Image(256, 256); - Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) + Span row = buffer2.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) { - Span row = buffer2.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } + row[x] = new L16((ushort)(x * y)); } + } - using var img2 = new Image(256, 256); - using var img3 = new Image(256, 256); + using var img2 = new Image(256, 256); + using var img3 = new Image(256, 256); - this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => + this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => + { + for (int y = 0; y < accessor1.Height; y++) { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - Span row3 = accessor3.GetRowSpan(y); - row1.CopyTo(row2); - row1.CopyTo(row3); - } - }); + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + Span row3 = accessor3.GetRowSpan(y); + row1.CopyTo(row2); + row1.CopyTo(row3); + } + }); - buffer2 = img2.Frames.RootFrame.PixelBuffer; - Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) + buffer2 = img2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row2 = buffer2.DangerousGetRowSpan(y); + Span row3 = buffer3.DangerousGetRowSpan(y); + for (int x = 0; x < 256; x++) { - Span row2 = buffer2.DangerousGetRowSpan(y); - Span row3 = buffer3.DangerousGetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual2 = row2[x].PackedValue; - int actual3 = row3[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual2); - Assert.Equal(x * y, actual3); - } + int actual2 = row2[x].PackedValue; + int actual3 = row3[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual2); + Assert.Equal(x * y, actual3); } } + } - [Fact] - public void Disposed_ThrowsObjectDisposedException() - { - using var nonDisposed = new Image(1, 1); - var disposed = new Image(1, 1); - disposed.Dispose(); + [Fact] + public void Disposed_ThrowsObjectDisposedException() + { + using var nonDisposed = new Image(1, 1); + var disposed = new Image(1, 1); + disposed.Dispose(); - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, _ => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, _ => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { })); - Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); - } + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers1(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers1(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName, string throwExceptionStr) - { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer = UnmanagedBuffer.Allocate(100); - var allocator = new MockUnmanagedMemoryAllocator(buffer); - Configuration.Default.MemoryAllocator = allocator; + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer); + Configuration.Default.MemoryAllocator = allocator; - var image = new Image(10, 10); + var image = new Image(10, 10); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - try + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => { - GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + ((IDisposable)buffer).Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) { - ((IDisposable)buffer).Dispose(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + throw new NonFatalException(); + } + }); } + catch (NonFatalException) + { + } + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers2(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers2(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName, string throwExceptionStr) - { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer1 = UnmanagedBuffer.Allocate(100); - var buffer2 = UnmanagedBuffer.Allocate(100); - var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); - Configuration.Default.MemoryAllocator = allocator; + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); + Configuration.Default.MemoryAllocator = allocator; - var image1 = new Image(10, 10); - var image2 = new Image(10, 10); + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); - Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - try + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => { - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) { - ((IDisposable)buffer1).Dispose(); - ((IDisposable)buffer2).Dispose(); - Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { } + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void RetainsUnmangedBuffers3(bool throwException) - { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers3(bool throwException) + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName, string throwExceptionStr) + static void RunTest(string testTypeName, string throwExceptionStr) + { + bool throwExceptionInner = bool.Parse(throwExceptionStr); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); + var buffer3 = UnmanagedBuffer.Allocate(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + var image3 = new Image(10, 10); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + try { - bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer1 = UnmanagedBuffer.Allocate(100); - var buffer2 = UnmanagedBuffer.Allocate(100); - var buffer3 = UnmanagedBuffer.Allocate(100); - var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); - Configuration.Default.MemoryAllocator = allocator; - - var image1 = new Image(10, 10); - var image2 = new Image(10, 10); - var image3 = new Image(10, 10); - - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - try + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => { - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); + ((IDisposable)buffer3).Dispose(); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) { - ((IDisposable)buffer1).Dispose(); - ((IDisposable)buffer2).Dispose(); - ((IDisposable)buffer3).Dispose(); - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - if (throwExceptionInner) - { - throw new NonFatalException(); - } - }); - } - catch (NonFatalException) - { - } - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { } - } - private static ProcessPixelRowsTestBase GetTest(string testTypeName) - { - Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); - return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - private class NonFatalException : Exception - { - } + private static ProcessPixelRowsTestBase GetTest(string testTypeName) + { + Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); + return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); + } - private class MockUnmanagedMemoryAllocator : MemoryAllocator - where T1 : struct - { - private Stack> buffers = new(); + private class NonFatalException : Exception + { + } + + private class MockUnmanagedMemoryAllocator : MemoryAllocator + where T1 : struct + { + private Stack> buffers = new(); - public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) + public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) + { + foreach (UnmanagedBuffer buffer in buffers) { - foreach (UnmanagedBuffer buffer in buffers) - { - this.buffers.Push(buffer); - } + this.buffers.Push(buffer); } + } - protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => - this.buffers.Pop() as IMemoryOwner; - } + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => + this.buffers.Pop() as IMemoryOwner; } } diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index b0d940e50c..48b982e1ad 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -4,30 +4,27 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public class ImageInfoTests { - public class ImageInfoTests + [Fact] + public void ImageInfoInitializesCorrectly() { - [Fact] - public void ImageInfoInitializesCorrectly() - { - const int Width = 50; - const int Height = 60; - var size = new Size(Width, Height); - var rectangle = new Rectangle(0, 0, Width, Height); - var pixelType = new PixelTypeInfo(8); - var meta = new ImageMetadata(); + const int Width = 50; + const int Height = 60; + var size = new Size(Width, Height); + var rectangle = new Rectangle(0, 0, Width, Height); + var pixelType = new PixelTypeInfo(8); + var meta = new ImageMetadata(); - var info = new ImageInfo(pixelType, Width, Height, meta); + var info = new ImageInfo(pixelType, Width, Height, meta); - Assert.Equal(pixelType, info.PixelType); - Assert.Equal(Width, info.Width); - Assert.Equal(Height, info.Height); - Assert.Equal(size, info.Size()); - Assert.Equal(rectangle, info.Bounds()); - Assert.Equal(meta, info.Metadata); - } + Assert.Equal(pixelType, info.PixelType); + Assert.Equal(Width, info.Width); + Assert.Equal(Height, info.Height); + Assert.Equal(size, info.Size()); + Assert.Equal(rectangle, info.Bounds()); + Assert.Equal(meta, info.Metadata); } } diff --git a/tests/ImageSharp.Tests/Issues/Issue594.cs b/tests/ImageSharp.Tests/Issues/Issue594.cs index fdcb771782..51f1ef7c67 100644 --- a/tests/ImageSharp.Tests/Issues/Issue594.cs +++ b/tests/ImageSharp.Tests/Issues/Issue594.cs @@ -1,266 +1,263 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Issues +namespace SixLabors.ImageSharp.Tests.Issues; + +public class Issue594 { - public class Issue594 + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void NormalizedByte4() + { + // Test 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 + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4())); + + // Test ToScaledVector4. + Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); + Assert.Equal(0, scaled.X); + Assert.Equal(0, scaled.Y); + Assert.Equal(0, scaled.Z); + Assert.Equal(0, scaled.W); + + // Test FromScaledVector4. + var pixel = default(NormalizedByte4); + pixel.FromScaledVector4(scaled); + Assert.Equal(0x81818181, pixel.PackedValue); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xA740DA0D, new NormalizedByte4(x, y, z, w).PackedValue); + var n = default(NormalizedByte4); + n.FromRgba32(new Rgba32(141, 90, 192, 39)); + Assert.Equal(0xA740DA0D, n.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); + + // 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).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).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)); + } + + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void NormalizedShort4() + { + // Test 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 + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4())); + Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4())); + Assert.True(Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4())); + + // Test ToScaledVector4. + Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); + Assert.Equal(1, scaled.X); + Assert.Equal(1, scaled.Y); + Assert.Equal(1, scaled.Z); + Assert.Equal(1, scaled.W); + + // Test FromScaledVector4. + var pixel = default(NormalizedShort4); + pixel.FromScaledVector4(scaled); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).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); + + // 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).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).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)); + + // 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)); + } + + // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue + // see https://github.com/SixLabors/ImageSharp/issues/594 + [Fact(Skip = "Skipped because of issue #594")] + public void Short4() + { + // Test the limits. + 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. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); + Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); + + // Test ToScaledVector4. + Vector4 scaled = new Short4(Vector4.One * 0x7FFF).ToScaledVector4(); + Assert.Equal(1, scaled.X); + Assert.Equal(1, scaled.Y); + Assert.Equal(1, scaled.Z); + Assert.Equal(1, scaled.W); + + // Test FromScaledVector4. + var pixel = default(Short4); + pixel.FromScaledVector4(scaled); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); + + // Test clamping. + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -1234567.0f).ToVector4()); + + // Test Ordering + float x = 0.1f; + float y = -0.3f; + float z = 0.5f; + float w = -0.7f; + Assert.Equal(18446462598732840960, new Short4(x, y, z, w).PackedValue); + + x = 11547; + y = 12653; + z = 29623; + w = 193; + 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); + + // 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).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).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)); + + // 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)); + } + + // Comparison helpers with small tolerance to allow for floating point rounding during computations. + public static bool Equal(float a, float b) + { + return Math.Abs(a - b) < 1e-5; + } + + public static bool Equal(Vector4 a, Vector4 b) { - // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue - // see https://github.com/SixLabors/ImageSharp/issues/594 - [Fact(Skip = "Skipped because of issue #594")] - public void NormalizedByte4() - { - // Test 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 - Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4())); - - // Test ToScaledVector4. - Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); - Assert.Equal(0, scaled.X); - Assert.Equal(0, scaled.Y); - Assert.Equal(0, scaled.Z); - Assert.Equal(0, scaled.W); - - // Test FromScaledVector4. - var pixel = default(NormalizedByte4); - pixel.FromScaledVector4(scaled); - Assert.Equal(0x81818181, pixel.PackedValue); - - // Test Ordering - float x = 0.1f; - float y = -0.3f; - float z = 0.5f; - float w = -0.7f; - Assert.Equal(0xA740DA0D, new NormalizedByte4(x, y, z, w).PackedValue); - var n = default(NormalizedByte4); - n.FromRgba32(new Rgba32(141, 90, 192, 39)); - Assert.Equal(0xA740DA0D, n.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); - - // 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).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).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)); - } - - // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue - // see https://github.com/SixLabors/ImageSharp/issues/594 - [Fact(Skip = "Skipped because of issue #594")] - public void NormalizedShort4() - { - // Test 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 - Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4())); - Assert.True(Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4())); - Assert.True(Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4())); - - // Test ToScaledVector4. - Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); - Assert.Equal(1, scaled.X); - Assert.Equal(1, scaled.Y); - Assert.Equal(1, scaled.Z); - Assert.Equal(1, scaled.W); - - // Test FromScaledVector4. - var pixel = default(NormalizedShort4); - pixel.FromScaledVector4(scaled); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); - - // Test Ordering - float x = 0.1f; - float y = -0.3f; - float z = 0.5f; - float w = -0.7f; - Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).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); - - // 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).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).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)); - - // 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)); - } - - // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue - // see https://github.com/SixLabors/ImageSharp/issues/594 - [Fact(Skip = "Skipped because of issue #594")] - public void Short4() - { - // Test the limits. - 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. - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); - Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); - - // Test ToScaledVector4. - Vector4 scaled = new Short4(Vector4.One * 0x7FFF).ToScaledVector4(); - Assert.Equal(1, scaled.X); - Assert.Equal(1, scaled.Y); - Assert.Equal(1, scaled.Z); - Assert.Equal(1, scaled.W); - - // Test FromScaledVector4. - var pixel = default(Short4); - pixel.FromScaledVector4(scaled); - Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); - - // Test clamping. - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -1234567.0f).ToVector4()); - - // Test Ordering - float x = 0.1f; - float y = -0.3f; - float z = 0.5f; - float w = -0.7f; - Assert.Equal(18446462598732840960, new Short4(x, y, z, w).PackedValue); - - x = 11547; - y = 12653; - z = 29623; - w = 193; - 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); - - // 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).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).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)); - - // 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)); - } - - // Comparison helpers with small tolerance to allow for floating point rounding during computations. - public static bool Equal(float a, float b) - { - return Math.Abs(a - b) < 1e-5; - } - - public static bool Equal(Vector4 a, Vector4 b) - { - return Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z) && Equal(a.W, b.W); - } + return Equal(a.X, b.X) && Equal(a.Y, b.Y) && Equal(a.Z, b.Z) && Equal(a.W, b.W); } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs index ba8b663ff3..50e1bee7ff 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs @@ -1,25 +1,23 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +internal static class BufferExtensions { - internal static class BufferExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetSpan(this IMemoryOwner buffer) - => buffer.Memory.Span; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetSpan(this IMemoryOwner buffer) + => buffer.Memory.Span; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Length(this IMemoryOwner buffer) - => buffer.GetSpan().Length; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IMemoryOwner buffer) + => buffer.GetSpan().Length; - public static ref T GetReference(this IMemoryOwner buffer) - where T : struct => - ref MemoryMarshal.GetReference(buffer.GetSpan()); - } + public static ref T GetReference(this IMemoryOwner buffer) + where T : struct => + ref MemoryMarshal.GetReference(buffer.GetSpan()); } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index b5233b5a94..33950c4697 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -1,281 +1,278 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -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.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +/// +/// Inherit this class to test an implementation (provided by ). +/// +public abstract class BufferTestSuite { - /// - /// Inherit this class to test an implementation (provided by ). - /// - public abstract class BufferTestSuite + protected BufferTestSuite(MemoryAllocator memoryAllocator) { - protected BufferTestSuite(MemoryAllocator memoryAllocator) - { - this.MemoryAllocator = memoryAllocator; - } - - protected MemoryAllocator MemoryAllocator { get; } - - public struct CustomStruct : IEquatable - { - public long A; - - public byte B; - - public float C; + this.MemoryAllocator = memoryAllocator; + } - public CustomStruct(long a, byte b, float c) - { - this.A = a; - this.B = b; - this.C = c; - } + protected MemoryAllocator MemoryAllocator { get; } - public bool Equals(CustomStruct other) - { - return this.A == other.A && this.B == other.B && this.C.Equals(other.C); - } - - public override bool Equals(object obj) - { - return obj is CustomStruct other && this.Equals(other); - } + public struct CustomStruct : IEquatable + { + public long A; - public override int GetHashCode() - { - unchecked - { - int hashCode = this.A.GetHashCode(); - hashCode = (hashCode * 397) ^ this.B.GetHashCode(); - hashCode = (hashCode * 397) ^ this.C.GetHashCode(); - return hashCode; - } - } - } + public byte B; - public static readonly TheoryData LengthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + public float C; - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_byte(int desiredLength) + public CustomStruct(long a, byte b, float c) { - this.TestHasCorrectLength(desiredLength); + this.A = a; + this.B = b; + this.C = c; } - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_float(int desiredLength) + public bool Equals(CustomStruct other) { - this.TestHasCorrectLength(desiredLength); + return this.A == other.A && this.B == other.B && this.C.Equals(other.C); } - [Theory] - [MemberData(nameof(LengthValues))] - public void HasCorrectLength_CustomStruct(int desiredLength) + public override bool Equals(object obj) { - this.TestHasCorrectLength(desiredLength); + return obj is CustomStruct other && this.Equals(other); } - private void TestHasCorrectLength(int desiredLength) - where T : struct + public override int GetHashCode() { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) + unchecked { - Assert.Equal(desiredLength, buffer.GetSpan().Length); + int hashCode = this.A.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.C.GetHashCode(); + return hashCode; } } + } - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_byte(int desiredLength) - { - this.TestCanAllocateCleanBuffer(desiredLength); - } + public static readonly TheoryData LengthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_double(int desiredLength) - { - this.TestCanAllocateCleanBuffer(desiredLength); - } + [Theory] + [MemberData(nameof(LengthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } - [Theory] - [MemberData(nameof(LengthValues))] - public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + [Theory] + [MemberData(nameof(LengthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LengthValues))] + public void HasCorrectLength_CustomStruct(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + private void TestHasCorrectLength(int desiredLength) + where T : struct + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { - this.TestCanAllocateCleanBuffer(desiredLength); + Assert.Equal(desiredLength, buffer.GetSpan().Length); } + } - private void TestCanAllocateCleanBuffer(int desiredLength) - where T : struct, IEquatable - { - ReadOnlySpan expected = new T[desiredLength]; + [Theory] + [MemberData(nameof(LengthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } - for (int i = 0; i < 10; i++) - { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean)) - { - Assert.True(buffer.GetSpan().SequenceEqual(expected)); - } - } - } + [Theory] + [MemberData(nameof(LengthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } - [Theory] - [MemberData(nameof(LengthValues))] - public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) - { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength); - } + [Theory] + [MemberData(nameof(LengthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } - [Theory] - [MemberData(nameof(LengthValues))] - public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) - { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength); - } + private void TestCanAllocateCleanBuffer(int desiredLength) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; - private void TestSpanPropertyIsAlwaysTheSame(int desiredLength) - where T : struct + for (int i = 0; i < 10; i++) { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean)) { - ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); - ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); - ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan()); - - Assert.True(Unsafe.AreSame(ref a, ref b)); - Assert.True(Unsafe.AreSame(ref b, ref c)); + Assert.True(buffer.GetSpan().SequenceEqual(expected)); } } + } - [Theory] - [MemberData(nameof(LengthValues))] - public void WriteAndReadElements_float(int desiredLength) - { - this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); - } + [Theory] + [MemberData(nameof(LengthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } - [Theory] - [MemberData(nameof(LengthValues))] - public void WriteAndReadElements_byte(int desiredLength) + [Theory] + [MemberData(nameof(LengthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength) + where T : struct + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None)) { - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1)); + ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref T c = ref MemoryMarshal.GetReference(buffer.GetSpan()); + + Assert.True(Unsafe.AreSame(ref a, ref b)); + Assert.True(Unsafe.AreSame(ref b, ref c)); } + } - private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue) - where T : struct + [Theory] + [MemberData(nameof(LengthValues))] + public void WriteAndReadElements_float(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LengthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1)); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue) + where T : struct + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) + var expectedVals = new T[buffer.Length()]; + + for (int i = 0; i < buffer.Length(); i++) { - var expectedVals = new T[buffer.Length()]; - - for (int i = 0; i < buffer.Length(); i++) - { - Span span = buffer.GetSpan(); - expectedVals[i] = getExpectedValue(i); - span[i] = expectedVals[i]; - } - - for (int i = 0; i < buffer.Length(); i++) - { - Span span = buffer.GetSpan(); - Assert.Equal(expectedVals[i], span[i]); - } + Span span = buffer.GetSpan(); + expectedVals[i] = getExpectedValue(i); + span[i] = expectedVals[i]; } - } - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.GetSpan(); + Assert.Equal(expectedVals[i], span[i]); + } } + } - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); - } + [Theory] + [MemberData(nameof(LengthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } - [Theory] - [MemberData(nameof(LengthValues))] - public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) - { - this.TestIndexOutOfRangeShouldThrow(desiredLength); - } + [Theory] + [MemberData(nameof(LengthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } - private T TestIndexOutOfRangeShouldThrow(int desiredLength) - where T : struct, IEquatable - { - var dummy = default(T); + [Theory] + [MemberData(nameof(LengthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) - { - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength]; - }); - - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength + 1]; - }); - - Assert.ThrowsAny( - () => - { - Span span = buffer.GetSpan(); - dummy = span[desiredLength + 42]; - }); - } + private T TestIndexOutOfRangeShouldThrow(int desiredLength) + where T : struct, IEquatable + { + var dummy = default(T); - return dummy; + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) + { + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength + 1]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.GetSpan(); + dummy = span[desiredLength + 42]; + }); } - [Fact] - public void GetMemory_ReturnsValidMemory() + return dummy; + } + + [Fact] + public void GetMemory_ReturnsValidMemory() + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) - { - Span span0 = buffer.GetSpan(); - span0[10].A = 30; - Memory memory = buffer.Memory; + Span span0 = buffer.GetSpan(); + span0[10].A = 30; + Memory memory = buffer.Memory; - Assert.Equal(42, memory.Length); - Span span1 = memory.Span; + Assert.Equal(42, memory.Length); + Span span1 = memory.Span; - Assert.Equal(42, span1.Length); - Assert.Equal(30, span1[10].A); - } + Assert.Equal(42, span1.Length); + Assert.Equal(30, span1[10].A); } + } - [Fact] - public unsafe void GetMemory_ResultIsPinnable() + [Fact] + public unsafe void GetMemory_ResultIsPinnable() + { + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) { - using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(42)) - { - Span span0 = buffer.GetSpan(); - span0[10] = 30; + Span span0 = buffer.GetSpan(); + span0[10] = 30; - Memory memory = buffer.Memory; + Memory memory = buffer.Memory; - using (MemoryHandle h = memory.Pin()) - { - int* ptr = (int*)h.Pointer; - Assert.Equal(30, ptr[10]); - } + using (MemoryHandle h = memory.Pin()) + { + int* ptr = (int*)h.Pointer; + Assert.Equal(30, ptr[10]); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs index b2074eda0d..1876d26479 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/MemoryDiagnosticsTests.cs @@ -1,121 +1,116 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Diagnostics; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class MemoryDiagnosticsTests { - public class MemoryDiagnosticsTests - { - private const int OneMb = 1 << 20; + private const int OneMb = 1 << 20; - private static MemoryAllocator Allocator => Configuration.Default.MemoryAllocator; + private static MemoryAllocator Allocator => Configuration.Default.MemoryAllocator; + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PerfectCleanup_NoLeaksReported(bool isGroupOuter) + { + RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString()).Dispose(); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void PerfectCleanup_NoLeaksReported(bool isGroupOuter) + static void RunTest(string isGroupInner) { - RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString()).Dispose(); + bool isGroup = bool.Parse(isGroupInner); + int leakCounter = 0; + MemoryDiagnostics.UndisposedAllocation += _ => Interlocked.Increment(ref leakCounter); - static void RunTest(string isGroupInner) - { - bool isGroup = bool.Parse(isGroupInner); - int leakCounter = 0; - MemoryDiagnostics.UndisposedAllocation += _ => Interlocked.Increment(ref leakCounter); + List buffers = new(); - List buffers = new(); + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + for (int length = 1024; length <= 64 * OneMb; length *= 2) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + IDisposable buffer = isGroup ? + Allocator.AllocateGroup(length, 1024) : + Allocator.Allocate(length); + buffers.Add(buffer); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter > cntBefore); + } - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - for (int length = 1024; length <= 64 * OneMb; length *= 2) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - IDisposable buffer = isGroup ? - Allocator.AllocateGroup(length, 1024) : - Allocator.Allocate(length); - buffers.Add(buffer); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter > cntBefore); - } - - foreach (IDisposable buffer in buffers) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - buffer.Dispose(); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter < cntBefore); - } - - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - Assert.Equal(0, leakCounter); + foreach (IDisposable buffer in buffers) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + buffer.Dispose(); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter < cntBefore); } + + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + Assert.Equal(0, leakCounter); } + } - [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void MissingCleanup_LeaksAreReported(bool isGroupOuter, bool subscribeLeakHandleOuter) - { - RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString(), subscribeLeakHandleOuter.ToString()).Dispose(); + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void MissingCleanup_LeaksAreReported(bool isGroupOuter, bool subscribeLeakHandleOuter) + { + RemoteExecutor.Invoke(RunTest, isGroupOuter.ToString(), subscribeLeakHandleOuter.ToString()).Dispose(); - static void RunTest(string isGroupInner, string subscribeLeakHandleInner) + static void RunTest(string isGroupInner, string subscribeLeakHandleInner) + { + bool isGroup = bool.Parse(isGroupInner); + bool subscribeLeakHandle = bool.Parse(subscribeLeakHandleInner); + int leakCounter = 0; + bool stackTraceOk = true; + if (subscribeLeakHandle) { - bool isGroup = bool.Parse(isGroupInner); - bool subscribeLeakHandle = bool.Parse(subscribeLeakHandleInner); - int leakCounter = 0; - bool stackTraceOk = true; - if (subscribeLeakHandle) + MemoryDiagnostics.UndisposedAllocation += stackTrace => { - MemoryDiagnostics.UndisposedAllocation += stackTrace => - { - Interlocked.Increment(ref leakCounter); - stackTraceOk &= stackTrace.Contains(nameof(RunTest)) && stackTrace.Contains(nameof(AllocateAndForget)); - Assert.Contains(nameof(AllocateAndForget), stackTrace); - }; - } - - Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); - for (int length = 1024; length <= 64 * OneMb; length *= 2) - { - long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; - AllocateAndForget(length, isGroup); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; - Assert.True(cntAfter > cntBefore); - } - - if (subscribeLeakHandle) - { - // Make sure at least some of the leak callbacks have time to complete on the ThreadPool - Thread.Sleep(200); - Assert.True(leakCounter > 3, $"leakCounter did not count enough leaks ({leakCounter} only)"); - } + Interlocked.Increment(ref leakCounter); + stackTraceOk &= stackTrace.Contains(nameof(RunTest)) && stackTrace.Contains(nameof(AllocateAndForget)); + Assert.Contains(nameof(AllocateAndForget), stackTrace); + }; + } - Assert.True(stackTraceOk); + Assert.Equal(0, MemoryDiagnostics.TotalUndisposedAllocationCount); + for (int length = 1024; length <= 64 * OneMb; length *= 2) + { + long cntBefore = MemoryDiagnostics.TotalUndisposedAllocationCount; + AllocateAndForget(length, isGroup); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + long cntAfter = MemoryDiagnostics.TotalUndisposedAllocationCount; + Assert.True(cntAfter > cntBefore); } - [MethodImpl(MethodImplOptions.NoInlining)] - static void AllocateAndForget(int length, bool isGroup) + if (subscribeLeakHandle) { - if (isGroup) - { - _ = Allocator.AllocateGroup(length, 1024); - } - else - { - _ = Allocator.Allocate(length); - } + // Make sure at least some of the leak callbacks have time to complete on the ThreadPool + Thread.Sleep(200); + Assert.True(leakCounter > 3, $"leakCounter did not count enough leaks ({leakCounter} only)"); + } + + Assert.True(stackTraceOk); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AllocateAndForget(int length, bool isGroup) + { + if (isGroup) + { + _ = Allocator.AllocateGroup(length, 1024); + } + else + { + _ = Allocator.Allocate(length); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs index 0171c8c4f8..517eda7076 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs @@ -1,126 +1,123 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class RefCountedLifetimeGuardTests { - public class RefCountedLifetimeGuardTests + [Theory] + [InlineData(1)] + [InlineData(3)] + public void Dispose_ResultsInSingleRelease(int disposeCount) { - [Theory] - [InlineData(1)] - [InlineData(3)] - public void Dispose_ResultsInSingleRelease(int disposeCount) + var guard = new MockLifetimeGuard(); + Assert.Equal(0, guard.ReleaseInvocationCount); + + for (int i = 0; i < disposeCount; i++) { - var guard = new MockLifetimeGuard(); - Assert.Equal(0, guard.ReleaseInvocationCount); + guard.Dispose(); + } - for (int i = 0; i < disposeCount; i++) - { - guard.Dispose(); - } + Assert.Equal(1, guard.ReleaseInvocationCount); + } - Assert.Equal(1, guard.ReleaseInvocationCount); - } + [Fact] + public void Finalize_ResultsInSingleRelease() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [Fact] - public void Finalize_ResultsInSingleRelease() + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); - LeakGuard(false); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); - } + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); + LeakGuard(false); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); } + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + public void AddRef_PreventsReleaseOnDispose(int addRefCount) + { + var guard = new MockLifetimeGuard(); - [Theory] - [InlineData(1)] - [InlineData(3)] - public void AddRef_PreventsReleaseOnDispose(int addRefCount) + for (int i = 0; i < addRefCount; i++) { - var guard = new MockLifetimeGuard(); + guard.AddRef(); + } - for (int i = 0; i < addRefCount; i++) - { - guard.AddRef(); - } + guard.Dispose(); - guard.Dispose(); + for (int i = 0; i < addRefCount; i++) + { + Assert.Equal(0, guard.ReleaseInvocationCount); + guard.ReleaseRef(); + } - for (int i = 0; i < addRefCount; i++) - { - Assert.Equal(0, guard.ReleaseInvocationCount); - guard.ReleaseRef(); - } + Assert.Equal(1, guard.ReleaseInvocationCount); + } - Assert.Equal(1, guard.ReleaseInvocationCount); - } + [Fact] + public void AddRef_PreventsReleaseOnFinalize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [Fact] - public void AddRef_PreventsReleaseOnFinalize() + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - LeakGuard(true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); - } + LeakGuard(true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); } + } - [Fact] - public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() - { - var guard = new MockLifetimeGuard(); - guard.Dispose(); - guard.AddRef(); - guard.ReleaseRef(); + [Fact] + public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() + { + var guard = new MockLifetimeGuard(); + guard.Dispose(); + guard.AddRef(); + guard.ReleaseRef(); - Assert.Equal(1, guard.ReleaseInvocationCount); - } + Assert.Equal(1, guard.ReleaseInvocationCount); + } - [Fact] - public void UnmanagedBufferLifetimeGuard_Handle_IsReturnedByRef() - { - var h = UnmanagedMemoryHandle.Allocate(10); - using var guard = new UnmanagedBufferLifetimeGuard.FreeHandle(h); - Assert.True(guard.Handle.IsValid); - guard.Handle.Free(); - Assert.False(guard.Handle.IsValid); - } + [Fact] + public void UnmanagedBufferLifetimeGuard_Handle_IsReturnedByRef() + { + var h = UnmanagedMemoryHandle.Allocate(10); + using var guard = new UnmanagedBufferLifetimeGuard.FreeHandle(h); + Assert.True(guard.Handle.IsValid); + guard.Handle.Free(); + Assert.False(guard.Handle.IsValid); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void LeakGuard(bool addRef) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LeakGuard(bool addRef) + { + var guard = new MockLifetimeGuard(); + if (addRef) { - var guard = new MockLifetimeGuard(); - if (addRef) - { - guard.AddRef(); - } + guard.AddRef(); } + } - private class MockLifetimeGuard : RefCountedMemoryLifetimeGuard - { - public int ReleaseInvocationCount { get; private set; } + private class MockLifetimeGuard : RefCountedMemoryLifetimeGuard + { + public int ReleaseInvocationCount { get; private set; } - public static int GlobalReleaseInvocationCount { get; private set; } + public static int GlobalReleaseInvocationCount { get; private set; } - protected override void Release() - { - this.ReleaseInvocationCount++; - GlobalReleaseInvocationCount++; - } + protected override void Release() + { + this.ReleaseInvocationCount++; + GlobalReleaseInvocationCount++; } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs index 3a3fa70161..a956190cd2 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs @@ -1,60 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Linq; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class SharedArrayPoolBufferTests { - public class SharedArrayPoolBufferTests + [Fact] + public void AllocatesArrayPoolArray() { - [Fact] - public void AllocatesArrayPoolArray() - { - RemoteExecutor.Invoke(RunTest).Dispose(); + RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() + static void RunTest() + { + using (var buffer = new SharedArrayPoolBuffer(900)) { - using (var buffer = new SharedArrayPoolBuffer(900)) - { - Assert.Equal(900, buffer.GetSpan().Length); - buffer.GetSpan().Fill(42); - } + Assert.Equal(900, buffer.GetSpan().Length); + buffer.GetSpan().Fill(42); + } - byte[] array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + byte[] array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); - Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); - } + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); } + } - [Fact] - public void OutstandingReferences_RetainArrays() - { - RemoteExecutor.Invoke(RunTest).Dispose(); + [Fact] + public void OutstandingReferences_RetainArrays() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - var buffer = new SharedArrayPoolBuffer(900); - Span span = buffer.GetSpan(); - - buffer.AddRef(); - ((IDisposable)buffer).Dispose(); - span.Fill(42); - byte[] array = ArrayPool.Shared.Rent(900); - Assert.NotEqual(42, array[0]); - ArrayPool.Shared.Return(array); - - buffer.ReleaseRef(); - array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); - Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); - ArrayPool.Shared.Return(array); - } + static void RunTest() + { + var buffer = new SharedArrayPoolBuffer(900); + Span span = buffer.GetSpan(); + + buffer.AddRef(); + ((IDisposable)buffer).Dispose(); + span.Fill(42); + byte[] array = ArrayPool.Shared.Rent(900); + Assert.NotEqual(42, array[0]); + ArrayPool.Shared.Return(array); + + buffer.ReleaseRef(); + array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); + ArrayPool.Shared.Return(array); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 2c854eecce..780ba7f20e 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class SimpleGcMemoryAllocatorTests { - public class SimpleGcMemoryAllocatorTests + public class BufferTests : BufferTestSuite { - public class BufferTests : BufferTestSuite + public BufferTests() + : base(new SimpleGcMemoryAllocator()) { - public BufferTests() - : base(new SimpleGcMemoryAllocator()) - { - } } + } - protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator(); + protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator(); - [Theory] - [InlineData(-1)] - public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.Allocate(length)); - Assert.Equal("length", ex.ParamName); - } + [Theory] + [InlineData(-1)] + public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + { + ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.Allocate(length)); + Assert.Equal("length", ex.ParamName); + } - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + [Fact] + public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + { + SimpleGcMemoryAllocator allocator = this.MemoryAllocator; + using IMemoryOwner memoryOwner = allocator.Allocate(100); + + using (MemoryHandle pin = memoryOwner.Memory.Pin()) { - SimpleGcMemoryAllocator allocator = this.MemoryAllocator; - using IMemoryOwner memoryOwner = allocator.Allocate(100); - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); } - [StructLayout(LayoutKind.Explicit, Size = 512)] - private struct BigStruct + using (MemoryHandle pin = memoryOwner.Memory.Pin()) { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); } } + + [StructLayout(LayoutKind.Explicit, Size = 512)] + private struct BigStruct + { + } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 5b9d430af0..2f47e6ff3a 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -1,162 +1,157 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public partial class UniformUnmanagedMemoryPoolTests { - public partial class UniformUnmanagedMemoryPoolTests + [Collection(nameof(NonParallelTests))] + public class Trim { - [Collection(nameof(NonParallelTests))] - public class Trim + [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] + public class NonParallelTests + { + } + + [Fact] + public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() { - [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] - public class NonParallelTests + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 5_000 }; + var pool = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + + UnmanagedMemoryHandle[] a = pool.Rent(64); + UnmanagedMemoryHandle[] b = pool.Rent(64); + pool.Return(a); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + Thread.Sleep(15_000); + + // We expect at least 2 Trim actions, first trim 32, then 16 arrays. + // 128 - 32 - 16 = 80 + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 80, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); + pool.Return(b); } + } + + // Complicated Xunit ceremony to disable parallel execution of an individual test, + // MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed, + // which is strongly timing-sensitive, and might be flaky under high load. + [CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)] + public class NonParallelCollection + { + } + + [Collection(nameof(NonParallelCollection))] + public class NonParallel + { + public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - [Fact] - public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() + // TODO: Investigate failures on macOS. All handles are released after GC. + // (It seems to happen more consistently on .NET 6.) + [ConditionalFact(nameof(IsNotMacOS))] + public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() { - var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 5_000 }; - var pool = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; + var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); + Thread.Sleep(8_000); // Let some callbacks fire already + var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; + var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); + + pool1.Return(pool1.Rent(64)); + pool2.Return(pool2.Rent(64)); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - UnmanagedMemoryHandle[] a = pool.Rent(64); - UnmanagedMemoryHandle[] b = pool.Rent(64); - pool.Return(a); + // This exercises pool weak reference list trimming: + LeakPoolInstance(); + GC.Collect(); + GC.WaitForPendingFinalizers(); Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(15_000); - // We expect at least 2 Trim actions, first trim 32, then 16 arrays. - // 128 - 32 - 16 = 80 + Thread.Sleep(15_000); Assert.True( - UnmanagedMemoryHandle.TotalOutstandingHandles <= 80, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); - pool.Return(b); + UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); + GC.KeepAlive(pool1); + GC.KeepAlive(pool2); } - } - - // Complicated Xunit ceremony to disable parallel execution of an individual test, - // MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed, - // which is strongly timing-sensitive, and might be flaky under high load. - [CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)] - public class NonParallelCollection - { - } - - [Collection(nameof(NonParallelCollection))] - public class NonParallel - { - public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - // TODO: Investigate failures on macOS. All handles are released after GC. - // (It seems to happen more consistently on .NET 6.) - [ConditionalFact(nameof(IsNotMacOS))] - public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance() { - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; - var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); - Thread.Sleep(8_000); // Let some callbacks fire already - var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; - var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); - - pool1.Return(pool1.Rent(64)); - pool2.Return(pool2.Rent(64)); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - - // This exercises pool weak reference list trimming: - LeakPoolInstance(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - - Thread.Sleep(15_000); - Assert.True( - UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); - GC.KeepAlive(pool1); - GC.KeepAlive(pool2); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void LeakPoolInstance() - { - var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; - _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); - } + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; + _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); } } + } + + public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; + private static readonly List PressureArrays = new(); - public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; - private static readonly List PressureArrays = new(); + [ConditionalFact(nameof(Is32BitProcess))] + public static void GC_Collect_OnHighLoad_TrimsEntirePool() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [ConditionalFact(nameof(Is32BitProcess))] - public static void GC_Collect_OnHighLoad_TrimsEntirePool() + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); + Assert.False(Environment.Is64BitProcess); + const int oneMb = 1 << 20; - static void RunTest() - { - Assert.False(Environment.Is64BitProcess); - const int oneMb = 1 << 20; + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f }; - var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f }; + GCMemoryInfo memInfo = GC.GetGCMemoryInfo(); + int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / oneMb); + highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold); - GCMemoryInfo memInfo = GC.GetGCMemoryInfo(); - int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / oneMb); - highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold); + var pool = new UniformUnmanagedMemoryPool(oneMb, 16, trimSettings); + pool.Return(pool.Rent(16)); + Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles); - var pool = new UniformUnmanagedMemoryPool(oneMb, 16, trimSettings); - pool.Return(pool.Rent(16)); - Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles); + for (int i = 0; i < highLoadThreshold; i++) + { + byte[] array = new byte[oneMb]; + TouchPage(array); + PressureArrays.Add(array); + } - for (int i = 0; i < highLoadThreshold; i++) - { - byte[] array = new byte[oneMb]; - TouchPage(array); - PressureArrays.Add(array); - } + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + // Prevent eager collection of the pool: + GC.KeepAlive(pool); - // Prevent eager collection of the pool: - GC.KeepAlive(pool); + static void TouchPage(byte[] b) + { + uint size = (uint)b.Length; + const uint pageSize = 4096; + uint numPages = size / pageSize; - static void TouchPage(byte[] b) + for (uint i = 0; i < numPages; i++) { - uint size = (uint)b.Length; - const uint pageSize = 4096; - uint numPages = size / pageSize; - - for (uint i = 0; i < numPages; i++) - { - b[i * pageSize] = (byte)(i % 256); - } + b[i * pageSize] = (byte)(i % 256); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index d767d12515..69fc1a5f7d 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -1,383 +1,375 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public partial class UniformUnmanagedMemoryPoolTests { - public partial class UniformUnmanagedMemoryPoolTests - { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper output; - public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) => this.output = output; + public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) => this.output = output; - private class CleanupUtil : IDisposable - { - private readonly UniformUnmanagedMemoryPool pool; - private readonly List handlesToDestroy = new(); - private readonly List ptrsToDestroy = new(); + private class CleanupUtil : IDisposable + { + private readonly UniformUnmanagedMemoryPool pool; + private readonly List handlesToDestroy = new(); + private readonly List ptrsToDestroy = new(); - public CleanupUtil(UniformUnmanagedMemoryPool pool) - { - this.pool = pool; - } + public CleanupUtil(UniformUnmanagedMemoryPool pool) + { + this.pool = pool; + } - public void Register(UnmanagedMemoryHandle handle) => this.handlesToDestroy.Add(handle); + public void Register(UnmanagedMemoryHandle handle) => this.handlesToDestroy.Add(handle); - public void Register(IEnumerable handles) => this.handlesToDestroy.AddRange(handles); + public void Register(IEnumerable handles) => this.handlesToDestroy.AddRange(handles); - public void Register(IntPtr memoryPtr) => this.ptrsToDestroy.Add(memoryPtr); + public void Register(IntPtr memoryPtr) => this.ptrsToDestroy.Add(memoryPtr); - public void Register(IEnumerable memoryPtrs) => this.ptrsToDestroy.AddRange(memoryPtrs); + public void Register(IEnumerable memoryPtrs) => this.ptrsToDestroy.AddRange(memoryPtrs); - public void Dispose() + public void Dispose() + { + foreach (UnmanagedMemoryHandle handle in this.handlesToDestroy) { - foreach (UnmanagedMemoryHandle handle in this.handlesToDestroy) - { - handle.Free(); - } + handle.Free(); + } - this.pool.Release(); + this.pool.Release(); - foreach (IntPtr ptr in this.ptrsToDestroy) - { - Marshal.FreeHGlobal(ptr); - } + foreach (IntPtr ptr in this.ptrsToDestroy) + { + Marshal.FreeHGlobal(ptr); } } + } - [Theory] - [InlineData(3, 11)] - [InlineData(7, 4)] - public void Constructor_InitializesProperties(int arrayLength, int capacity) + [Theory] + [InlineData(3, 11)] + [InlineData(7, 4)] + public void Constructor_InitializesProperties(int arrayLength, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(arrayLength, capacity); + Assert.Equal(arrayLength, pool.BufferLength); + Assert.Equal(capacity, pool.Capacity); + } + + [Theory] + [InlineData(1, 3)] + [InlineData(8, 10)] + public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(length, capacity); + using var cleanup = new CleanupUtil(pool); + + for (int i = 0; i < capacity; i++) { - var pool = new UniformUnmanagedMemoryPool(arrayLength, capacity); - Assert.Equal(arrayLength, pool.BufferLength); - Assert.Equal(capacity, pool.Capacity); + UnmanagedMemoryHandle h = pool.Rent(); + CheckBuffer(length, pool, h); + cleanup.Register(h); } + } - [Theory] - [InlineData(1, 3)] - [InlineData(8, 10)] - public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) - { - var pool = new UniformUnmanagedMemoryPool(length, capacity); - using var cleanup = new CleanupUtil(pool); + [Fact] + public void Return_DoesNotDeallocateMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - for (int i = 0; i < capacity; i++) - { - UnmanagedMemoryHandle h = pool.Rent(); - CheckBuffer(length, pool, h); - cleanup.Register(h); - } + static void RunTest() + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Return(a); + pool.Return(b); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Fact] - public void Return_DoesNotDeallocateMemory() - { - RemoteExecutor.Invoke(RunTest).Dispose(); + private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) + { + Assert.False(h.IsInvalid); + Span span = GetSpan(h, pool.BufferLength); + span.Fill(123); - static void RunTest() - { - var pool = new UniformUnmanagedMemoryPool(16, 16); - UnmanagedMemoryHandle a = pool.Rent(); - UnmanagedMemoryHandle[] b = pool.Rent(2); + byte[] expected = new byte[length]; + expected.AsSpan().Fill(123); + Assert.True(span.SequenceEqual(expected)); + } - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - pool.Return(a); - pool.Return(b); - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - } + private static unsafe Span GetSpan(UnmanagedMemoryHandle h, int length) => new Span(h.Pointer, length); - private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) - { - Assert.False(h.IsInvalid); - Span span = GetSpan(h, pool.BufferLength); - span.Fill(123); + [Theory] + [InlineData(1, 1)] + [InlineData(1, 5)] + [InlineData(42, 7)] + [InlineData(5, 10)] + public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) + { + var pool = new UniformUnmanagedMemoryPool(length, 10); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); + cleanup.Register(handles); - byte[] expected = new byte[length]; - expected.AsSpan().Fill(123); - Assert.True(span.SequenceEqual(expected)); + Assert.NotNull(handles); + Assert.Equal(bufferCount, handles.Length); + + foreach (UnmanagedMemoryHandle h in handles) + { + CheckBuffer(length, pool, h); } + } - private static unsafe Span GetSpan(UnmanagedMemoryHandle h, int length) => new Span(h.Pointer, length); + [Fact] + public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() + { + var pool = new UniformUnmanagedMemoryPool(128, 10); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] a = pool.Rent(2); + cleanup.Register(a); + UnmanagedMemoryHandle b = pool.Rent(); + cleanup.Register(b); + + Assert.NotEqual(a[0].Handle, a[1].Handle); + Assert.NotEqual(a[0].Handle, b.Handle); + Assert.NotEqual(a[1].Handle, b.Handle); + } - [Theory] - [InlineData(1, 1)] - [InlineData(1, 5)] - [InlineData(42, 7)] - [InlineData(5, 10)] - public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) - { - var pool = new UniformUnmanagedMemoryPool(length, 10); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); - cleanup.Register(handles); + [Theory] + [InlineData(4, 2, 10)] + [InlineData(5, 1, 6)] + [InlineData(12, 4, 12)] + public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + var allHandles = new HashSet(); + var handleUnits = new List(); + UnmanagedMemoryHandle[] handles; + for (int i = 0; i < totalCount; i += rentUnit) + { + handles = pool.Rent(rentUnit); Assert.NotNull(handles); - Assert.Equal(bufferCount, handles.Length); - - foreach (UnmanagedMemoryHandle h in handles) + handleUnits.Add(handles); + foreach (UnmanagedMemoryHandle array in handles) { - CheckBuffer(length, pool, h); + allHandles.Add(array); } - } - [Fact] - public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() - { - var pool = new UniformUnmanagedMemoryPool(128, 10); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle[] a = pool.Rent(2); - cleanup.Register(a); - UnmanagedMemoryHandle b = pool.Rent(); - cleanup.Register(b); - - Assert.NotEqual(a[0].Handle, a[1].Handle); - Assert.NotEqual(a[0].Handle, b.Handle); - Assert.NotEqual(a[1].Handle, b.Handle); + // Allocate some memory, so potential new pool allocation wouldn't allocated the same memory: + cleanup.Register(Marshal.AllocHGlobal(128)); } - [Theory] - [InlineData(4, 2, 10)] - [InlineData(5, 1, 6)] - [InlineData(12, 4, 12)] - public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) + foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) { - var pool = new UniformUnmanagedMemoryPool(128, capacity); - using var cleanup = new CleanupUtil(pool); - var allHandles = new HashSet(); - var handleUnits = new List(); - - UnmanagedMemoryHandle[] handles; - for (int i = 0; i < totalCount; i += rentUnit) + if (arrayUnit.Length == 1) { - handles = pool.Rent(rentUnit); - Assert.NotNull(handles); - handleUnits.Add(handles); - foreach (UnmanagedMemoryHandle array in handles) - { - allHandles.Add(array); - } - - // Allocate some memory, so potential new pool allocation wouldn't allocated the same memory: - cleanup.Register(Marshal.AllocHGlobal(128)); + // Test single-array return: + pool.Return(arrayUnit.Single()); } - - foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) + else { - if (arrayUnit.Length == 1) - { - // Test single-array return: - pool.Return(arrayUnit.Single()); - } - else - { - pool.Return(arrayUnit); - } + pool.Return(arrayUnit); } + } - handles = pool.Rent(totalCount); - - Assert.NotNull(handles); - - foreach (UnmanagedMemoryHandle array in handles) - { - Assert.Contains(array, allHandles); - } + handles = pool.Rent(totalCount); - cleanup.Register(allHandles); - } + Assert.NotNull(handles); - [Fact] - public void Rent_SingleBuffer_OverCapacity_ReturnsInvalidBuffer() + foreach (UnmanagedMemoryHandle array in handles) { - var pool = new UniformUnmanagedMemoryPool(7, 1000); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle[] initial = pool.Rent(1000); - Assert.NotNull(initial); - cleanup.Register(initial); - UnmanagedMemoryHandle b1 = pool.Rent(); - Assert.True(b1.IsInvalid); + Assert.Contains(array, allHandles); } - [Theory] - [InlineData(0, 6, 5)] - [InlineData(5, 1, 5)] - [InlineData(4, 7, 10)] - public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) - { - var pool = new UniformUnmanagedMemoryPool(128, capacity); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle[] initial = pool.Rent(initialRent); - Assert.NotNull(initial); - cleanup.Register(initial); - UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); - Assert.Null(b1); - } + cleanup.Register(allHandles); + } - [Theory] - [InlineData(0, 5, 5)] - [InlineData(5, 1, 6)] - [InlineData(4, 7, 11)] - [InlineData(3, 3, 7)] - public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) - { - var pool = new UniformUnmanagedMemoryPool(128, capacity); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle[] b0 = pool.Rent(initialRent); - Assert.NotNull(b0); - cleanup.Register(b0); - UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); - Assert.NotNull(b1); - cleanup.Register(b1); - } + [Fact] + public void Rent_SingleBuffer_OverCapacity_ReturnsInvalidBuffer() + { + var pool = new UniformUnmanagedMemoryPool(7, 1000); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(1000); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle b1 = pool.Rent(); + Assert.True(b1.IsInvalid); + } - public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; + [Theory] + [InlineData(0, 6, 5)] + [InlineData(5, 1, 5)] + [InlineData(4, 7, 10)] + public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(initialRent); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.Null(b1); + } - // TODO: Investigate macOS failures - [ConditionalTheory(nameof(IsNotMacOS))] - [InlineData(false)] - [InlineData(true)] - public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) - { - RemoteExecutor.Invoke(RunTest, multiple.ToString()).Dispose(); + [Theory] + [InlineData(0, 5, 5)] + [InlineData(5, 1, 6)] + [InlineData(4, 7, 11)] + [InlineData(3, 3, 7)] + public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] b0 = pool.Rent(initialRent); + Assert.NotNull(b0); + cleanup.Register(b0); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.NotNull(b1); + cleanup.Register(b1); + } - static void RunTest(string multipleInner) - { - var pool = new UniformUnmanagedMemoryPool(16, 16); - using var cleanup = new CleanupUtil(pool); - UnmanagedMemoryHandle b0 = pool.Rent(); - IntPtr h0 = b0.Handle; - UnmanagedMemoryHandle b1 = pool.Rent(); - IntPtr h1 = b1.Handle; - pool.Return(b0); - pool.Return(b1); - pool.Release(); - - // Do some unmanaged allocations to make sure new pool buffers are different: - IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); - cleanup.Register(dummy); - - if (bool.Parse(multipleInner)) - { - UnmanagedMemoryHandle b = pool.Rent(); - cleanup.Register(b); - Assert.NotEqual(h0, b.Handle); - Assert.NotEqual(h1, b.Handle); - } - else - { - UnmanagedMemoryHandle[] b = pool.Rent(2); - cleanup.Register(b); - Assert.NotEqual(h0, b[0].Handle); - Assert.NotEqual(h1, b[0].Handle); - Assert.NotEqual(h0, b[1].Handle); - Assert.NotEqual(h1, b[1].Handle); - } - } - } + public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS; - [Fact] - public void Release_ShouldFreeRetainedMemory() + // TODO: Investigate macOS failures + [ConditionalTheory(nameof(IsNotMacOS))] + [InlineData(false)] + [InlineData(true)] + public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) + { + RemoteExecutor.Invoke(RunTest, multiple.ToString()).Dispose(); + + static void RunTest(string multipleInner) { - RemoteExecutor.Invoke(RunTest).Dispose(); + var pool = new UniformUnmanagedMemoryPool(16, 16); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle b0 = pool.Rent(); + IntPtr h0 = b0.Handle; + UnmanagedMemoryHandle b1 = pool.Rent(); + IntPtr h1 = b1.Handle; + pool.Return(b0); + pool.Return(b1); + pool.Release(); - static void RunTest() + // Do some unmanaged allocations to make sure new pool buffers are different: + IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); + cleanup.Register(dummy); + + if (bool.Parse(multipleInner)) + { + UnmanagedMemoryHandle b = pool.Rent(); + cleanup.Register(b); + Assert.NotEqual(h0, b.Handle); + Assert.NotEqual(h1, b.Handle); + } + else { - var pool = new UniformUnmanagedMemoryPool(16, 16); - UnmanagedMemoryHandle a = pool.Rent(); UnmanagedMemoryHandle[] b = pool.Rent(2); - pool.Return(a); - pool.Return(b); - - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - pool.Release(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + cleanup.Register(b); + Assert.NotEqual(h0, b[0].Handle); + Assert.NotEqual(h1, b[0].Handle); + Assert.NotEqual(h0, b[1].Handle); + Assert.NotEqual(h1, b[1].Handle); } } + } + + [Fact] + public void Release_ShouldFreeRetainedMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [Fact] - public void RentReturn_IsThreadSafe() + static void RunTest() { - int count = Environment.ProcessorCount * 200; - var pool = new UniformUnmanagedMemoryPool(8, count); - using var cleanup = new CleanupUtil(pool); - var rnd = new Random(0); + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + pool.Return(a); + pool.Return(b); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Release(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RentReturn_IsThreadSafe() + { + int count = Environment.ProcessorCount * 200; + var pool = new UniformUnmanagedMemoryPool(8, count); + using var cleanup = new CleanupUtil(pool); + var rnd = new Random(0); - Parallel.For(0, Environment.ProcessorCount, (int i) => + Parallel.For(0, Environment.ProcessorCount, (int i) => + { + var allHandles = new List(); + int pauseAt = rnd.Next(100); + for (int j = 0; j < 100; j++) { - var allHandles = new List(); - int pauseAt = rnd.Next(100); - for (int j = 0; j < 100; j++) - { - UnmanagedMemoryHandle[] data = pool.Rent(2); + UnmanagedMemoryHandle[] data = pool.Rent(2); - GetSpan(data[0], pool.BufferLength).Fill((byte)i); - GetSpan(data[1], pool.BufferLength).Fill((byte)i); - allHandles.Add(data[0]); - allHandles.Add(data[1]); + GetSpan(data[0], pool.BufferLength).Fill((byte)i); + GetSpan(data[1], pool.BufferLength).Fill((byte)i); + allHandles.Add(data[0]); + allHandles.Add(data[1]); - if (j == pauseAt) - { - Thread.Sleep(15); - } + if (j == pauseAt) + { + Thread.Sleep(15); } + } - Span expected = new byte[8]; - expected.Fill((byte)i); + Span expected = new byte[8]; + expected.Fill((byte)i); - foreach (UnmanagedMemoryHandle h in allHandles) - { - Assert.True(expected.SequenceEqual(GetSpan(h, pool.BufferLength))); - pool.Return(new[] { h }); - } - }); - } + foreach (UnmanagedMemoryHandle h in allHandles) + { + Assert.True(expected.SequenceEqual(GetSpan(h, pool.BufferLength))); + pool.Return(new[] { h }); + } + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void LeakPool_FinalizerShouldFreeRetainedHandles(bool withGuardedBuffers) + { + RemoteExecutor.Invoke(RunTest, withGuardedBuffers.ToString()).Dispose(); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void LeakPool_FinalizerShouldFreeRetainedHandles(bool withGuardedBuffers) + static void RunTest(string withGuardedBuffersInner) { - RemoteExecutor.Invoke(RunTest, withGuardedBuffers.ToString()).Dispose(); + LeakPoolInstance(bool.Parse(withGuardedBuffersInner)); + Assert.Equal(20, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } - static void RunTest(string withGuardedBuffersInner) + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance(bool withGuardedBuffers) + { + var pool = new UniformUnmanagedMemoryPool(16, 128); + if (withGuardedBuffers) { - LeakPoolInstance(bool.Parse(withGuardedBuffersInner)); - Assert.Equal(20, UnmanagedMemoryHandle.TotalOutstandingHandles); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + UnmanagedMemoryHandle h = pool.Rent(); + _ = pool.CreateGuardedBuffer(h, 10, false); + UnmanagedMemoryHandle[] g = pool.Rent(19); + _ = pool.CreateGroupLifetimeGuard(g); } - - [MethodImpl(MethodImplOptions.NoInlining)] - static void LeakPoolInstance(bool withGuardedBuffers) + else { - var pool = new UniformUnmanagedMemoryPool(16, 128); - if (withGuardedBuffers) - { - UnmanagedMemoryHandle h = pool.Rent(); - _ = pool.CreateGuardedBuffer(h, 10, false); - UnmanagedMemoryHandle[] g = pool.Rent(19); - _ = pool.CreateGroupLifetimeGuard(g); - } - else - { - pool.Return(pool.Rent(20)); - } + pool.Return(pool.Rent(20)); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 2cd1c696da..bd7c4f2bdb 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -1,396 +1,391 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class UniformUnmanagedPoolMemoryAllocatorTests { - public class UniformUnmanagedPoolMemoryAllocatorTests + public class BufferTests1 : BufferTestSuite { - public class BufferTests1 : BufferTestSuite + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 1024, + poolBufferSizeInBytes: 2048, + maxPoolSizeInBytes: 2048 * 4, + unmanagedBufferSizeInBytes: 4096); + + public BufferTests1() + : base(CreateMemoryAllocator()) { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedArrayPoolThresholdInBytes: 1024, - poolBufferSizeInBytes: 2048, - maxPoolSizeInBytes: 2048 * 4, - unmanagedBufferSizeInBytes: 4096); - - public BufferTests1() - : base(CreateMemoryAllocator()) - { - } } + } - public class BufferTests2 : BufferTestSuite + public class BufferTests2 : BufferTestSuite + { + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 512, + poolBufferSizeInBytes: 1024, + maxPoolSizeInBytes: 1024 * 4, + unmanagedBufferSizeInBytes: 2048); + + public BufferTests2() + : base(CreateMemoryAllocator()) { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedArrayPoolThresholdInBytes: 512, - poolBufferSizeInBytes: 1024, - maxPoolSizeInBytes: 1024 * 4, - unmanagedBufferSizeInBytes: 2048); - - public BufferTests2() - : base(CreateMemoryAllocator()) - { - } } + } - public static TheoryData AllocateData = - new TheoryData() - { - { default(S4), 16, 256, 256, 1024, 64, 64, 1, -1, 64 }, - { default(S4), 16, 256, 256, 1024, 256, 256, 1, -1, 256 }, - { default(S4), 16, 256, 256, 1024, 250, 256, 1, -1, 250 }, - { default(S4), 16, 256, 256, 1024, 248, 250, 1, -1, 248 }, - { default(S4), 16, 1024, 2048, 4096, 512, 256, 2, 256, 256 }, - { default(S4), 16, 1024, 1024, 1024, 511, 256, 2, 256, 255 }, - }; - - [Theory] - [MemberData(nameof(AllocateData))] - public void AllocateGroup_BufferSizesAreCorrect( - T dummy, - int sharedArrayPoolThresholdInBytes, - int maxContiguousPoolBufferInBytes, - int maxPoolSizeInBytes, - int maxCapacityOfUnmanagedBuffers, - long allocationLengthInElements, - int bufferAlignmentInElements, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct + public static TheoryData AllocateData = + new TheoryData() { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedArrayPoolThresholdInBytes, - maxContiguousPoolBufferInBytes, - maxPoolSizeInBytes, - maxCapacityOfUnmanagedBuffers); - - using MemoryGroup g = allocator.AllocateGroup(allocationLengthInElements, bufferAlignmentInElements); - MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup( - expectedNumberOfBuffers, - expectedBufferSize, - expectedSizeOfLastBuffer, - g); - } + { default(S4), 16, 256, 256, 1024, 64, 64, 1, -1, 64 }, + { default(S4), 16, 256, 256, 1024, 256, 256, 1, -1, 256 }, + { default(S4), 16, 256, 256, 1024, 250, 256, 1, -1, 250 }, + { default(S4), 16, 256, 256, 1024, 248, 250, 1, -1, 248 }, + { default(S4), 16, 1024, 2048, 4096, 512, 256, 2, 256, 256 }, + { default(S4), 16, 1024, 1024, 1024, 511, 256, 2, 256, 255 }, + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void AllocateGroup_BufferSizesAreCorrect( + T dummy, + int sharedArrayPoolThresholdInBytes, + int maxContiguousPoolBufferInBytes, + int maxPoolSizeInBytes, + int maxCapacityOfUnmanagedBuffers, + long allocationLengthInElements, + int bufferAlignmentInElements, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes, + maxContiguousPoolBufferInBytes, + maxPoolSizeInBytes, + maxCapacityOfUnmanagedBuffers); + + using MemoryGroup g = allocator.AllocateGroup(allocationLengthInElements, bufferAlignmentInElements); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup( + expectedNumberOfBuffers, + expectedBufferSize, + expectedSizeOfLastBuffer, + g); + } - [Fact] - public void AllocateGroup_MultipleTimes_ExceedPoolLimit() + [Fact] + public void AllocateGroup_MultipleTimes_ExceedPoolLimit() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + 64, + 128, + 1024, + 1024); + + var groups = new List>(); + for (int i = 0; i < 16; i++) { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( - 64, - 128, - 1024, - 1024); - - var groups = new List>(); - for (int i = 0; i < 16; i++) - { - int lengthInElements = 128 / Unsafe.SizeOf(); - MemoryGroup g = allocator.AllocateGroup(lengthInElements, 32); - MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup(1, -1, lengthInElements, g); - groups.Add(g); - } - - foreach (MemoryGroup g in groups) - { - g.Dispose(); - } + int lengthInElements = 128 / Unsafe.SizeOf(); + MemoryGroup g = allocator.AllocateGroup(lengthInElements, 32); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup(1, -1, lengthInElements, g); + groups.Add(g); } - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + foreach (MemoryGroup g in groups) { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null); - using IMemoryOwner memoryOwner = allocator.Allocate(100); + g.Dispose(); + } + } - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } + [Fact] + public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null); + using IMemoryOwner memoryOwner = allocator.Allocate(100); - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } + using (MemoryHandle pin = memoryOwner.Memory.Pin()) + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); } - [Fact] - public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() + using (MemoryHandle pin = memoryOwner.Memory.Pin()) { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - var allocator = MemoryAllocator.Create(); - long sixteenMegabytes = 16 * (1 << 20); - - // Should allocate 4 times 4MB discontiguos blocks: - MemoryGroup g = allocator.AllocateGroup(sixteenMegabytes, 1024); - Assert.Equal(4, g.Count); - } + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); } + } + + [Fact] + public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - [Fact] - public void MemoryAllocator_Create_LimitPoolSize() + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); + var allocator = MemoryAllocator.Create(); + long sixteenMegabytes = 16 * (1 << 20); - static void RunTest() - { - var allocator = MemoryAllocator.Create(new MemoryAllocatorOptions() - { - MaximumPoolSizeMegabytes = 8 - }); - - MemoryGroup g0 = allocator.AllocateGroup(B(8), 1024); - MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); - ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); - ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); - g0.Dispose(); - g1.Dispose(); - - // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); - IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); - - using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); - using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); - ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); - ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); - - Assert.True(Unsafe.AreSame(ref r0, ref r2)); - Assert.False(Unsafe.AreSame(ref r1, ref r3)); - - Marshal.FreeHGlobal(dummy1); - Marshal.FreeHGlobal(dummy2); - } - - static long B(int value) => value << 20; + // Should allocate 4 times 4MB discontiguos blocks: + MemoryGroup g = allocator.AllocateGroup(sixteenMegabytes, 1024); + Assert.Equal(4, g.Count); } + } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void BufferDisposal_ReturnsToPool(bool shared) - { - RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + [Fact] + public void MemoryAllocator_Create_LimitPoolSize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest(string sharedStr) + static void RunTest() + { + var allocator = MemoryAllocator.Create(new MemoryAllocatorOptions() { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); - IMemoryOwner buffer0 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); - buffer0.GetSpan()[0] = 42; - buffer0.Dispose(); - using IMemoryOwner buffer1 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); - Assert.Equal(42, buffer1.GetSpan()[0]); - } + MaximumPoolSizeMegabytes = 8 + }); + + MemoryGroup g0 = allocator.AllocateGroup(B(8), 1024); + MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); + ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); + ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); + g0.Dispose(); + g1.Dispose(); + + // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: + IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + + using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); + using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); + ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); + ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); + + Assert.True(Unsafe.AreSame(ref r0, ref r2)); + Assert.False(Unsafe.AreSame(ref r1, ref r3)); + + Marshal.FreeHGlobal(dummy1); + Marshal.FreeHGlobal(dummy2); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void MemoryGroupDisposal_ReturnsToPool(bool shared) + static long B(int value) => value << 20; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void BufferDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) { - RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + IMemoryOwner buffer0 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + buffer0.GetSpan()[0] = 42; + buffer0.Dispose(); + using IMemoryOwner buffer1 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + Assert.Equal(42, buffer1.GetSpan()[0]); + } + } - static void RunTest(string sharedStr) - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); - MemoryGroup g0 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); - g0.Single().Span[0] = 42; - g0.Dispose(); - using MemoryGroup g1 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); - Assert.Equal(42, g1.Single().Span[0]); - } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void MemoryGroupDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + MemoryGroup g0 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + g0.Single().Span[0] = 42; + g0.Dispose(); + using MemoryGroup g1 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + Assert.Equal(42, g1.Single().Span[0]); } + } - [Fact] - public void ReleaseRetainedResources_ShouldFreePooledMemory() + [Fact] + public void ReleaseRetainedResources_ShouldFreePooledMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); - MemoryGroup g = allocator.AllocateGroup(2048, 128); - g.Dispose(); - Assert.Equal(4, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + g.Dispose(); + Assert.Equal(4, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Fact] - public void ReleaseRetainedResources_DoesNotFreeOutstandingBuffers() + [Fact] + public void ReleaseRetainedResources_DoesNotFreeOutstandingBuffers() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); - IMemoryOwner b = allocator.Allocate(256); - MemoryGroup g = allocator.AllocateGroup(2048, 128); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - b.Dispose(); - g.Dispose(); - Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); - allocator.ReleaseRetainedResources(); - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + IMemoryOwner b = allocator.Allocate(256); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + b.Dispose(); + g.Dispose(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Theory] - [InlineData(300)] // Group of single SharedArrayPoolBuffer - [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer - [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers - public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) + [Theory] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer + [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers + public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) + { + if (TestEnvironment.IsMacOS) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } + // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); - static void RunTest(string lengthStr) - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - - AllocateGroupAndForget(allocator, lengthInner); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - AllocateGroupAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); - Assert.Equal(42, g.First().Span[0]); - } + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateGroupAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateGroupAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); + Assert.Equal(42, g.First().Span[0]); } + } - private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + MemoryGroup g = allocator.AllocateGroup(length, 100); + if (check) { - MemoryGroup g = allocator.AllocateGroup(length, 100); - if (check) - { - Assert.Equal(42, g.First().Span[0]); - } + Assert.Equal(42, g.First().Span[0]); + } - g.First().Span[0] = 42; + g.First().Span[0] = 42; - if (length < 512) - { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. - MemoryGroup g1 = allocator.AllocateGroup(length, 100); - g1.First().Span[0] = 42; - } + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + MemoryGroup g1 = allocator.AllocateGroup(length, 100); + g1.First().Span[0] = 42; } + } - [Theory] - [InlineData(300)] // Group of single SharedArrayPoolBuffer - [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer - public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) + [Theory] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer + public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) + { + if (TestEnvironment.IsMacOS) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } + // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); - static void RunTest(string lengthStr) - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - - AllocateSingleAndForget(allocator, lengthInner); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - AllocateSingleAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using IMemoryOwner g = allocator.Allocate(lengthInner); - Assert.Equal(42, g.GetSpan()[0]); - GC.KeepAlive(allocator); - } + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateSingleAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateSingleAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using IMemoryOwner g = allocator.Allocate(lengthInner); + Assert.Equal(42, g.GetSpan()[0]); + GC.KeepAlive(allocator); } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + IMemoryOwner g = allocator.Allocate(length); + if (check) { - IMemoryOwner g = allocator.Allocate(length); - if (check) - { - Assert.Equal(42, g.GetSpan()[0]); - } + Assert.Equal(42, g.GetSpan()[0]); + } - g.GetSpan()[0] = 42; + g.GetSpan()[0] = 42; - if (length < 512) - { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. - IMemoryOwner g1 = allocator.Allocate(length); - g1.GetSpan()[0] = 42; - } + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + IMemoryOwner g1 = allocator.Allocate(length); + g1.GetSpan()[0] = 42; } + } - [Fact] - public void Issue2001_NegativeMemoryReportedByGc() - { - RemoteExecutor.Invoke(RunTest).Dispose(); + [Fact] + public void Issue2001_NegativeMemoryReportedByGc() + { + RemoteExecutor.Invoke(RunTest).Dispose(); - static void RunTest() - { - // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466 - UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176; - _ = MemoryAllocator.Create(); - } + static void RunTest() + { + // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466 + UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176; + _ = MemoryAllocator.Create(); } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs index c20bef655a..d0a5cfa9a7 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs @@ -1,93 +1,89 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class UnmanagedBufferTests { - public class UnmanagedBufferTests + public class AllocatorBufferTests : BufferTestSuite { - public class AllocatorBufferTests : BufferTestSuite + public AllocatorBufferTests() + : base(new UnmanagedMemoryAllocator(1024 * 64)) { - public AllocatorBufferTests() - : base(new UnmanagedMemoryAllocator(1024 * 64)) - { - } } + } - [Fact] - public void Allocate_CreatesValidBuffer() - { - using var buffer = UnmanagedBuffer.Allocate(10); - Span span = buffer.GetSpan(); - Assert.Equal(10, span.Length); - span[9] = 123; - Assert.Equal(123, span[9]); - } + [Fact] + public void Allocate_CreatesValidBuffer() + { + using var buffer = UnmanagedBuffer.Allocate(10); + Span span = buffer.GetSpan(); + Assert.Equal(10, span.Length); + span[9] = 123; + Assert.Equal(123, span[9]); + } - [Fact] - public unsafe void Dispose_DoesNotReleaseOutstandingReferences() + [Fact] + public unsafe void Dispose_DoesNotReleaseOutstandingReferences() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() { - RemoteExecutor.Invoke(RunTest).Dispose(); + var buffer = UnmanagedBuffer.Allocate(10); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + Span span = buffer.GetSpan(); - static void RunTest() + // Pin should AddRef + using (MemoryHandle h = buffer.Pin()) { - var buffer = UnmanagedBuffer.Allocate(10); + int* ptr = (int*)h.Pointer; + ((IDisposable)buffer).Dispose(); Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - Span span = buffer.GetSpan(); + ptr[3] = 13; + Assert.Equal(13, span[3]); + } // Unpin should ReleaseRef - // Pin should AddRef - using (MemoryHandle h = buffer.Pin()) - { - int* ptr = (int*)h.Pointer; - ((IDisposable)buffer).Dispose(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - ptr[3] = 13; - Assert.Equal(13, span[3]); - } // Unpin should ReleaseRef - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } + } - [Theory] - [InlineData(2)] - [InlineData(12)] - public void BufferFinalization_TracksAllocations(int count) - { - RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + [Theory] + [InlineData(2)] + [InlineData(12)] + public void BufferFinalization_TracksAllocations(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); - static void RunTest(string countStr) - { - int countInner = int.Parse(countStr); - List> l = FillList(countInner); + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + List> l = FillList(countInner); - l.RemoveRange(0, l.Count / 2); + l.RemoveRange(0, l.Count / 2); - GC.Collect(); - GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); - Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements - Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); - } + Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements + Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); + } - static List> FillList(int countInner) + static List> FillList(int countInner) + { + var l = new List>(); + for (int i = 0; i < countInner; i++) { - var l = new List>(); - for (int i = 0; i < countInner; i++) - { - var h = UnmanagedBuffer.Allocate(42); - l.Add(h); - } - - return l; + var h = UnmanagedBuffer.Allocate(42); + l.Add(h); } + + return l; } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs index c753aeb47a..7a0736b545 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -1,100 +1,96 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.Allocators +namespace SixLabors.ImageSharp.Tests.Memory.Allocators; + +public class UnmanagedMemoryHandleTests { - public class UnmanagedMemoryHandleTests + [Fact] + public unsafe void Allocate_AllocatesReadWriteMemory() { - [Fact] - public unsafe void Allocate_AllocatesReadWriteMemory() + var h = UnmanagedMemoryHandle.Allocate(128); + Assert.False(h.IsInvalid); + Assert.True(h.IsValid); + byte* ptr = (byte*)h.Handle; + for (int i = 0; i < 128; i++) { - var h = UnmanagedMemoryHandle.Allocate(128); - Assert.False(h.IsInvalid); - Assert.True(h.IsValid); - byte* ptr = (byte*)h.Handle; - for (int i = 0; i < 128; i++) - { - ptr[i] = (byte)i; - } - - for (int i = 0; i < 128; i++) - { - Assert.Equal((byte)i, ptr[i]); - } - - h.Free(); + ptr[i] = (byte)i; } - [Fact] - public void Free_ClosesHandle() + for (int i = 0; i < 128; i++) { - var h = UnmanagedMemoryHandle.Allocate(128); - h.Free(); - Assert.True(h.IsInvalid); - Assert.Equal(IntPtr.Zero, h.Handle); + Assert.Equal((byte)i, ptr[i]); } - [Theory] - [InlineData(1)] - [InlineData(13)] - public void Create_Free_AllocationsAreTracked(int count) - { - RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + h.Free(); + } + + [Fact] + public void Free_ClosesHandle() + { + var h = UnmanagedMemoryHandle.Allocate(128); + h.Free(); + Assert.True(h.IsInvalid); + Assert.Equal(IntPtr.Zero, h.Handle); + } - static void RunTest(string countStr) + [Theory] + [InlineData(1)] + [InlineData(13)] + public void Create_Free_AllocationsAreTracked(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + var l = new List(); + for (int i = 0; i < countInner; i++) { - int countInner = int.Parse(countStr); - var l = new List(); - for (int i = 0; i < countInner; i++) - { - Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); - var h = UnmanagedMemoryHandle.Allocate(42); - Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); - l.Add(h); - } + Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); + var h = UnmanagedMemoryHandle.Allocate(42); + Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); + l.Add(h); + } - for (int i = 0; i < countInner; i++) - { - Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); - l[i].Free(); - Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); - } + for (int i = 0; i < countInner; i++) + { + Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); + l[i].Free(); + Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); } } + } - [Fact] - public void Equality_WhenTrue() - { - var h1 = UnmanagedMemoryHandle.Allocate(10); - UnmanagedMemoryHandle h2 = h1; + [Fact] + public void Equality_WhenTrue() + { + var h1 = UnmanagedMemoryHandle.Allocate(10); + UnmanagedMemoryHandle h2 = h1; - Assert.True(h1.Equals(h2)); - Assert.True(h2.Equals(h1)); - Assert.True(h1 == h2); - Assert.False(h1 != h2); - Assert.True(h1.GetHashCode() == h2.GetHashCode()); - h1.Free(); - } + Assert.True(h1.Equals(h2)); + Assert.True(h2.Equals(h1)); + Assert.True(h1 == h2); + Assert.False(h1 != h2); + Assert.True(h1.GetHashCode() == h2.GetHashCode()); + h1.Free(); + } - [Fact] - public void Equality_WhenFalse() - { - var h1 = UnmanagedMemoryHandle.Allocate(10); - var h2 = UnmanagedMemoryHandle.Allocate(10); + [Fact] + public void Equality_WhenFalse() + { + var h1 = UnmanagedMemoryHandle.Allocate(10); + var h2 = UnmanagedMemoryHandle.Allocate(10); - Assert.False(h1.Equals(h2)); - Assert.False(h2.Equals(h1)); - Assert.False(h1 == h2); - Assert.True(h1 != h2); + Assert.False(h1.Equals(h2)); + Assert.False(h2.Equals(h1)); + Assert.False(h1 == h2); + Assert.True(h1 != h2); - h1.Free(); - h2.Free(); - } + h1.Free(); + h2.Free(); } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs index 7e80e81755..88c05fdd0e 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs @@ -1,160 +1,156 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests { - public partial class Buffer2DTests + public class SwapOrCopyContent { - public class SwapOrCopyContent - { - private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); - [Fact] - public void SwapOrCopyContent_WhenBothAllocated() + [Fact] + public void SwapOrCopyContent_WhenBothAllocated() + { + using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) + using (Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) { - using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) - using (Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) - { - a[1, 3] = 666; - b[1, 3] = 444; + a[1, 3] = 666; + b[1, 3] = 444; - Memory aa = a.FastMemoryGroup.Single(); - Memory bb = b.FastMemoryGroup.Single(); + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); - Buffer2D.SwapOrCopyContent(a, b); + Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.FastMemoryGroup.Single()); - Assert.Equal(aa, b.FastMemoryGroup.Single()); + 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(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]); - } + 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.DangerousGetRowSpan(0)[0]; - int actual2 = dest.DangerousGetRowSpan(0)[0]; - int actual3 = dest.GetSafeRowMemory(0).Span[0]; - int actual5 = dest[0, 0]; - - Assert.Equal(1, actual1); - Assert.Equal(1, actual2); - Assert.Equal(1, actual3); - Assert.Equal(1, actual5); - } + [Fact] + public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() + { + using var destData = MemoryGroup.Wrap(new int[100]); + using var dest = new Buffer2D(destData, 10, 10); - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() + using (Buffer2D source = this.memoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; - using Buffer2D a = this.memoryAllocator.Allocate2D(48, 2); - using Buffer2D b = this.memoryAllocator.Allocate2D(50, 2); - - Memory a0 = a.FastMemoryGroup[0]; - Memory a1 = a.FastMemoryGroup[1]; - Memory b0 = b.FastMemoryGroup[0]; - Memory b1 = b.FastMemoryGroup[1]; - - bool swap = Buffer2D.SwapOrCopyContent(a, b); - Assert.True(swap); - - Assert.Equal(b0, a.FastMemoryGroup[0]); - Assert.Equal(b1, a.FastMemoryGroup[1]); - Assert.Equal(a0, b.FastMemoryGroup[0]); - Assert.Equal(a1, b.FastMemoryGroup[1]); - Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); + source[0, 0] = 1; + dest[0, 0] = 2; + + Buffer2D.SwapOrCopyContent(dest, source); } - [Fact] - public void WhenBothAreMemoryOwners_ShouldReplaceViews() - { - using Buffer2D a = this.memoryAllocator.Allocate2D(100, 1); - using Buffer2D b = this.memoryAllocator.Allocate2D(100, 2); + int actual1 = dest.DangerousGetRowSpan(0)[0]; + int actual2 = dest.DangerousGetRowSpan(0)[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual5 = dest[0, 0]; - a.FastMemoryGroup[0].Span[42] = 1; - b.FastMemoryGroup[0].Span[33] = 2; - MemoryGroupView aView0 = (MemoryGroupView)a.MemoryGroup; - MemoryGroupView bView0 = (MemoryGroupView)b.MemoryGroup; + Assert.Equal(1, actual1); + Assert.Equal(1, actual2); + Assert.Equal(1, actual3); + Assert.Equal(1, actual5); + } - Buffer2D.SwapOrCopyContent(a, b); - Assert.False(aView0.IsValid); - Assert.False(bView0.IsValid); - Assert.ThrowsAny(() => _ = aView0[0].Span); - Assert.ThrowsAny(() => _ = bView0[0].Span); - - Assert.True(a.MemoryGroup.IsValid); - Assert.True(b.MemoryGroup.IsValid); - Assert.Equal(2, a.MemoryGroup[0].Span[33]); - Assert.Equal(1, b.MemoryGroup[0].Span[42]); - } + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; + using Buffer2D a = this.memoryAllocator.Allocate2D(48, 2); + using Buffer2D b = this.memoryAllocator.Allocate2D(50, 2); + + Memory a0 = a.FastMemoryGroup[0]; + Memory a1 = a.FastMemoryGroup[1]; + Memory b0 = b.FastMemoryGroup[0]; + Memory b1 = b.FastMemoryGroup[1]; + + bool swap = Buffer2D.SwapOrCopyContent(a, b); + Assert.True(swap); + + Assert.Equal(b0, a.FastMemoryGroup[0]); + Assert.Equal(b1, a.FastMemoryGroup[1]); + Assert.Equal(a0, b.FastMemoryGroup[0]); + Assert.Equal(a1, b.FastMemoryGroup[1]); + Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); + } - [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); + [Fact] + public void WhenBothAreMemoryOwners_ShouldReplaceViews() + { + using Buffer2D a = this.memoryAllocator.Allocate2D(100, 1); + using Buffer2D b = this.memoryAllocator.Allocate2D(100, 2); + + a.FastMemoryGroup[0].Span[42] = 1; + b.FastMemoryGroup[0].Span[33] = 2; + MemoryGroupView aView0 = (MemoryGroupView)a.MemoryGroup; + MemoryGroupView bView0 = (MemoryGroupView)b.MemoryGroup; + + Buffer2D.SwapOrCopyContent(a, b); + Assert.False(aView0.IsValid); + Assert.False(bView0.IsValid); + Assert.ThrowsAny(() => _ = aView0[0].Span); + Assert.ThrowsAny(() => _ = bView0[0].Span); + + Assert.True(a.MemoryGroup.IsValid); + Assert.True(b.MemoryGroup.IsValid); + Assert.Equal(2, a.MemoryGroup[0].Span[33]); + Assert.Equal(1, b.MemoryGroup[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 = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); + using var destOwner = new TestMemoryManager(data); + using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); - using Buffer2D source = this.memoryAllocator.Allocate2D(21, 1); + using Buffer2D source = this.memoryAllocator.Allocate2D(21, 1); - source.FastMemoryGroup[0].Span[10] = color; + source.FastMemoryGroup[0].Span[10] = color; - // Act: - bool swap = Buffer2D.SwapOrCopyContent(dest, source); + // Act: + bool swap = Buffer2D.SwapOrCopyContent(dest, source); - // Assert: - Assert.False(swap); - Assert.Equal(color, dest.MemoryGroup[0].Span[10]); - Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[0]); - } + // Assert: + Assert.False(swap); + Assert.Equal(color, dest.MemoryGroup[0].Span[10]); + Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[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); + [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); - using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); + using var destOwner = new TestMemoryManager(data); + using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); - using Buffer2D source = this.memoryAllocator.Allocate2D(22, 1); + using Buffer2D source = this.memoryAllocator.Allocate2D(22, 1); - source.FastMemoryGroup[0].Span[10] = color; + source.FastMemoryGroup[0].Span[10] = color; - // Act: - Assert.ThrowsAny(() => Buffer2D.SwapOrCopyContent(dest, source)); + // Act: + Assert.ThrowsAny(() => Buffer2D.SwapOrCopyContent(dest, source)); - Assert.Equal(color, source.MemoryGroup[0].Span[10]); - Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); - } + Assert.Equal(color, source.MemoryGroup[0].Span[10]); + Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index c8d30a1ee6..5364de0652 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -1,346 +1,340 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests { - public partial class Buffer2DTests + // ReSharper disable once ClassNeverInstantiated.Local + private class Assert : Xunit.Assert { - // ReSharper disable once ClassNeverInstantiated.Local - private class Assert : Xunit.Assert + public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) + where T : struct { - 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 buffer.Span[bufferOffset]; + ref T actual = ref MemoryMarshal.GetReference(span); + ref T expected = ref buffer.Span[bufferOffset]; - True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); - } + True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } + } - private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - - private const int Big = 99999; + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - [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; + private const int Big = 99999; - 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(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; - [Theory] - [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) + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, 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(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); - } + 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(false)] - [InlineData(true)] - public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) + [Theory] + [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)) { - this.MemoryAllocator.BufferCapacityInBytes = 10_000; - - using Buffer2D buffer = useSizeOverload ? - this.MemoryAllocator.Allocate2D( - new Size(200, 200), - preferContiguosImageBuffers: true) : - this.MemoryAllocator.Allocate2D( - 200, - 200, - preferContiguosImageBuffers: true); - Assert.Equal(1, buffer.FastMemoryGroup.Count); - Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); + Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); } + } - [Theory] - [InlineData(50, 10, 20, 4)] - public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) + { + this.MemoryAllocator.BufferCapacityInBytes = 10_000; + + using Buffer2D buffer = useSizeOverload ? + this.MemoryAllocator.Allocate2D( + new Size(200, 200), + preferContiguosImageBuffers: true) : + this.MemoryAllocator.Allocate2D( + 200, + 200, + preferContiguosImageBuffers: true); + Assert.Equal(1, buffer.FastMemoryGroup.Count); + Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); + } - using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); - MemoryGroup memoryGroup = buffer.FastMemoryGroup; - int expectedAlignment = width * alignmentMultiplier; + [Theory] + [InlineData(50, 10, 20, 4)] + public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - Assert.Equal(expectedAlignment, memoryGroup.BufferLength); - } + using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); + MemoryGroup memoryGroup = buffer.FastMemoryGroup; + int expectedAlignment = width * alignmentMultiplier; - [Fact] - public void CreateClean() + Assert.Equal(expectedAlignment, memoryGroup.BufferLength); + } + + [Fact] + public void CreateClean() + { + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) + Span span = buffer.DangerousGetSingleSpan(); + for (int j = 0; j < span.Length; j++) { - Span span = buffer.DangerousGetSingleSpan(); - for (int j = 0; j < span.Length; j++) - { - Assert.Equal(0, span[j]); - } + Assert.Equal(0, span[j]); } } + } - [Theory] - [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 DangerousGetRowSpan_TestAllocator(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + [Theory] + [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 DangerousGetRowSpan_TestAllocator(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.DangerousGetRowSpan(y); + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + Span span = buffer.DangerousGetRowSpan(y); - Assert.Equal(width, span.Length); + Assert.Equal(width, span.Length); - int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); - Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); - } + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } + } - [Theory] - [InlineData(100, 5)] // Within shared pool - [InlineData(77, 11)] // Within shared pool - [InlineData(100, 19)] // Single unmanaged pooled buffer - [InlineData(103, 17)] // Single unmanaged pooled buffer - [InlineData(100, 22)] // 2 unmanaged pooled buffers - [InlineData(100, 99)] // 9 unmanaged pooled buffers - [InlineData(100, 120)] // 2 unpooled buffers - public unsafe void DangerousGetRowSpan_UnmanagedAllocator(int width, int height) - { - const int sharedPoolThreshold = 1_000; - const int poolBufferSize = 2_000; - const int maxPoolSize = 10_000; - const int unpooledBufferSize = 8_000; - - int elementSize = sizeof(TestStructs.Foo); - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( - sharedPoolThreshold * elementSize, - poolBufferSize * elementSize, - maxPoolSize * elementSize, - unpooledBufferSize * elementSize); + [Theory] + [InlineData(100, 5)] // Within shared pool + [InlineData(77, 11)] // Within shared pool + [InlineData(100, 19)] // Single unmanaged pooled buffer + [InlineData(103, 17)] // Single unmanaged pooled buffer + [InlineData(100, 22)] // 2 unmanaged pooled buffers + [InlineData(100, 99)] // 9 unmanaged pooled buffers + [InlineData(100, 120)] // 2 unpooled buffers + public unsafe void DangerousGetRowSpan_UnmanagedAllocator(int width, int height) + { + const int sharedPoolThreshold = 1_000; + const int poolBufferSize = 2_000; + const int maxPoolSize = 10_000; + const int unpooledBufferSize = 8_000; - using Buffer2D buffer = allocator.Allocate2D(width, height); + int elementSize = sizeof(TestStructs.Foo); + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedPoolThreshold * elementSize, + poolBufferSize * elementSize, + maxPoolSize * elementSize, + unpooledBufferSize * elementSize); - var rnd = new Random(42); + using Buffer2D buffer = allocator.Allocate2D(width, height); - for (int y = 0; y < buffer.Height; y++) - { - Span span = buffer.DangerousGetRowSpan(y); - for (int x = 0; x < span.Length; x++) - { - ref TestStructs.Foo e = ref span[x]; - e.A = rnd.Next(); - e.B = rnd.NextDouble(); - } - } + var rnd = new Random(42); - // Re-seed - rnd = new Random(42); - for (int y = 0; y < buffer.Height; y++) + for (int y = 0; y < buffer.Height; y++) + { + Span span = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < span.Length; x++) { - Span span = buffer.GetSafeRowMemory(y).Span; - for (int x = 0; x < span.Length; x++) - { - ref TestStructs.Foo e = ref span[x]; - Assert.True(rnd.Next() == e.A, $"Mismatch @ y={y} x={x}"); - Assert.True(rnd.NextDouble() == e.B, $"Mismatch @ y={y} x={x}"); - } + ref TestStructs.Foo e = ref span[x]; + e.A = rnd.Next(); + e.B = rnd.NextDouble(); } } - [Theory] - [InlineData(10, 0, 0, 0)] - [InlineData(10, 0, 2, 0)] - [InlineData(10, 1, 2, 0)] - [InlineData(10, 1, 3, 0)] - [InlineData(10, 1, 5, -1)] - [InlineData(10, 2, 2, -1)] - [InlineData(10, 3, 2, 1)] - [InlineData(10, 4, 2, -1)] - [InlineData(30, 3, 2, 0)] - [InlineData(30, 4, 1, -1)] - public void TryGetPaddedRowSpanY(int bufferCapacity, int y, int padding, int expectedBufferIndex) + // Re-seed + rnd = new Random(42); + for (int y = 0; y < buffer.Height; y++) { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; - using Buffer2D buffer = this.MemoryAllocator.Allocate2D(3, 5); - - bool expectSuccess = expectedBufferIndex >= 0; - bool success = buffer.DangerousTryGetPaddedRowSpan(y, padding, out Span paddedSpan); - Xunit.Assert.Equal(expectSuccess, success); - if (success) + Span span = buffer.GetSafeRowMemory(y).Span; + for (int x = 0; x < span.Length; x++) { - int expectedSubBufferOffset = (3 * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); - Assert.SpanPointsTo(paddedSpan, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); + ref TestStructs.Foo e = ref span[x]; + Assert.True(rnd.Next() == e.A, $"Mismatch @ y={y} x={x}"); + Assert.True(rnd.NextDouble() == e.B, $"Mismatch @ y={y} x={x}"); } } + } - 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] - [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); + [Theory] + [InlineData(10, 0, 0, 0)] + [InlineData(10, 0, 2, 0)] + [InlineData(10, 1, 2, 0)] + [InlineData(10, 1, 3, 0)] + [InlineData(10, 1, 5, -1)] + [InlineData(10, 2, 2, -1)] + [InlineData(10, 3, 2, 1)] + [InlineData(10, 4, 2, -1)] + [InlineData(30, 3, 2, 0)] + [InlineData(30, 4, 1, -1)] + public void TryGetPaddedRowSpanY(int bufferCapacity, int y, int padding, int expectedBufferIndex) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(3, 5); - Exception ex = Assert.ThrowsAny(() => buffer.DangerousGetRowSpan(y)); - Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + bool expectSuccess = expectedBufferIndex >= 0; + bool success = buffer.DangerousTryGetPaddedRowSpan(y, padding, out Span paddedSpan); + Xunit.Assert.Equal(expectSuccess, success); + if (success) + { + int expectedSubBufferOffset = (3 * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(paddedSpan, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } + } - 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); + 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] + [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[x, y]++); - Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); - } + Exception ex = Assert.ThrowsAny(() => buffer.DangerousGetRowSpan(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; + 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); - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; - int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); + Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } - Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); + [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; - ref TestStructs.Foo actual = ref buffer[x, y]; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); - ref TestStructs.Foo expected = ref span[x]; + Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); - Assert.True(Unsafe.AreSame(ref expected, ref actual)); - } + ref TestStructs.Foo actual = ref buffer[x, y]; + + ref TestStructs.Foo expected = ref span[x]; + + Assert.True(Unsafe.AreSame(ref expected, ref actual)); } + } - [Theory] - [InlineData(100, 20, 0, 90, 10)] - [InlineData(100, 3, 0, 50, 50)] - [InlineData(123, 23, 10, 80, 13)] - [InlineData(10, 1, 3, 6, 3)] - [InlineData(2, 2, 0, 1, 1)] - [InlineData(5, 1, 1, 3, 2)] - public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount) + [Theory] + [InlineData(100, 20, 0, 90, 10)] + [InlineData(100, 3, 0, 50, 50)] + [InlineData(123, 23, 10, 80, 13)] + [InlineData(10, 1, 3, 6, 3)] + [InlineData(2, 2, 0, 1, 1)] + [InlineData(5, 1, 1, 3, 2)] + public void CopyColumns(int width, int height, int startIndex, int destIndex, int columnCount) + { + var rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - var rnd = new Random(123); - using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) - { - rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.DangerousCopyColumns(startIndex, destIndex, columnCount); + b.DangerousCopyColumns(startIndex, destIndex, columnCount); - for (int y = 0; y < b.Height; y++) - { - Span row = b.DangerousGetRowSpan(y); + for (int y = 0; y < b.Height; y++) + { + Span row = b.DangerousGetRowSpan(y); - Span s = row.Slice(startIndex, columnCount); - Span d = row.Slice(destIndex, columnCount); + Span s = row.Slice(startIndex, columnCount); + Span d = row.Slice(destIndex, columnCount); - Xunit.Assert.True(s.SequenceEqual(d)); - } + Xunit.Assert.True(s.SequenceEqual(d)); } } + } - [Fact] - public void CopyColumns_InvokeMultipleTimes() + [Fact] + public void CopyColumns_InvokeMultipleTimes() + { + var rnd = new Random(123); + using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - var rnd = new Random(123); - using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) - { - rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); + rnd.RandomFill(b.DangerousGetSingleSpan(), 0, 1); - b.DangerousCopyColumns(0, 50, 22); - b.DangerousCopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); + b.DangerousCopyColumns(0, 50, 22); - for (int y = 0; y < b.Height; y++) - { - Span row = b.DangerousGetRowSpan(y); + for (int y = 0; y < b.Height; y++) + { + Span row = b.DangerousGetRowSpan(y); - Span s = row.Slice(0, 22); - Span d = row.Slice(50, 22); + Span s = row.Slice(0, 22); + Span d = row.Slice(50, 22); - Xunit.Assert.True(s.SequenceEqual(d)); - } + Xunit.Assert.True(s.SequenceEqual(d)); } } + } - [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; + [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); + Buffer2D.SwapOrCopyContent(buffer1, buffer2); - Assert.False(mgBefore.IsValid); - Assert.NotSame(mgBefore, buffer1.MemoryGroup); - } + 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 514b2d0713..46907b9c0e 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -1,159 +1,156 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory +namespace SixLabors.ImageSharp.Tests.Memory; + +public class BufferAreaTests { - public class BufferAreaTests - { - private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); - [Fact] - public void Construct() - { - using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); - var rectangle = new Rectangle(3, 2, 5, 6); - var area = new Buffer2DRegion(buffer, rectangle); + [Fact] + public void Construct() + { + using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new Buffer2DRegion(buffer, rectangle); - Assert.Equal(buffer, area.Buffer); - Assert.Equal(rectangle, area.Rectangle); - } + Assert.Equal(buffer, area.Buffer); + Assert.Equal(rectangle, area.Rectangle); + } - private Buffer2D CreateTestBuffer(int w, int h) + private Buffer2D CreateTestBuffer(int w, int h) + { + Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); + for (int y = 0; y < h; y++) { - Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); - for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) { - for (int x = 0; x < w; x++) - { - buffer[x, y] = (y * 100) + x; - } + buffer[x, y] = (y * 100) + x; } - - return buffer; } - [Theory] - [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) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - var r = new Rectangle(rx, ry, 5, 6); + return buffer; + } - Buffer2DRegion region = buffer.GetRegion(r); + [Theory] + [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) + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, 5, 6); - int value = region[x, y]; - int expected = ((ry + y) * 100) + rx + x; - Assert.Equal(expected, value); - } + Buffer2DRegion region = buffer.GetRegion(r); - [Theory] - [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) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + int value = region[x, y]; + int expected = ((ry + y) * 100) + rx + x; + Assert.Equal(expected, value); + } + + [Theory] + [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) + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - var r = new Rectangle(rx, ry, w, h); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, w, h); - Buffer2DRegion region = buffer.GetRegion(r); + Buffer2DRegion region = buffer.GetRegion(r); - Span span = region.DangerousGetRowSpan(y); + Span span = region.DangerousGetRowSpan(y); - Assert.Equal(w, span.Length); + Assert.Equal(w, span.Length); - for (int i = 0; i < w; i++) - { - int expected = ((ry + y) * 100) + rx + i; - int value = span[i]; + for (int i = 0; i < w; i++) + { + int expected = ((ry + y) * 100) + rx + i; + int value = span[i]; - Assert.Equal(expected, value); - } + Assert.Equal(expected, value); } + } - [Fact] - public void GetSubArea() - { - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); + [Fact] + public void GetSubArea() + { + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); - Buffer2DRegion area1 = area0.GetSubRegion(4, 4, 5, 5); + Buffer2DRegion area1 = area0.GetSubRegion(4, 4, 5, 5); - var expectedRect = new Rectangle(10, 12, 5, 5); + var expectedRect = new Rectangle(10, 12, 5, 5); - Assert.Equal(buffer, area1.Buffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(buffer, area1.Buffer); + 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]); + } - [Theory] - [InlineData(1000)] - [InlineData(40)] - public void GetReferenceToOrigin(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void GetReferenceToOrigin(int bufferCapacity) + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion area0 = buffer.GetRegion(6, 8, 10, 10); - ref int r = ref area0.GetReferenceToOrigin(); + ref int r = ref area0.GetReferenceToOrigin(); - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + int expected = buffer[6, 8]; + Assert.Equal(expected, r); + } - [Theory] - [InlineData(1000)] - [InlineData(70)] - public void Clear_FullArea(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + [Theory] + [InlineData(1000)] + [InlineData(70)] + public void Clear_FullArea(int bufferCapacity) + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(22, 13); - var emptyRow = new int[22]; - buffer.GetRegion().Clear(); + using Buffer2D buffer = this.CreateTestBuffer(22, 13); + var emptyRow = new int[22]; + buffer.GetRegion().Clear(); - for (int y = 0; y < 13; y++) - { - Span row = buffer.DangerousGetRowSpan(y); - Assert.True(row.SequenceEqual(emptyRow)); - } + for (int y = 0; y < 13; y++) + { + Span row = buffer.DangerousGetRowSpan(y); + Assert.True(row.SequenceEqual(emptyRow)); } + } - [Theory] - [InlineData(1000)] - [InlineData(40)] - public void Clear_SubArea(int bufferCapacity) - { - this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void Clear_SubArea(int bufferCapacity) + { + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - using Buffer2D buffer = this.CreateTestBuffer(20, 30); - Buffer2DRegion region = buffer.GetRegion(5, 5, 10, 10); - region.Clear(); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + Buffer2DRegion region = buffer.GetRegion(5, 5, 10, 10); + region.Clear(); - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); - for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) - { - Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); - Assert.True(span.SequenceEqual(new int[region.Width])); - } + for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) + { + Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Assert.True(span.SequenceEqual(new int[region.Width])); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index 88ed1fb464..878084674a 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -1,120 +1,118 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public struct MemoryGroupIndex : IEquatable { - public struct MemoryGroupIndex : IEquatable - { - public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); + 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 override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); - public int BufferLength { get; } + public int BufferLength { get; } - public int BufferIndex { get; } + public int BufferIndex { get; } - public int ElementIndex { get; } + public int ElementIndex { get; } - public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) - { - this.BufferLength = bufferLength; - this.BufferIndex = bufferIndex; - this.ElementIndex = elementIndex; - } + 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 static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) + public bool Equals(MemoryGroupIndex other) + { + if (this.BufferLength != other.BufferLength) { - int nextElementIndex = idx.ElementIndex + val; - return new MemoryGroupIndex( - idx.BufferLength, - idx.BufferIndex + (nextElementIndex / idx.BufferLength), - nextElementIndex % idx.BufferLength); + throw new InvalidOperationException(); } - public bool Equals(MemoryGroupIndex other) - { - if (this.BufferLength != other.BufferLength) - { - throw new InvalidOperationException(); - } + return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; + } - 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) => !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(); + } - public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) + if (a.BufferIndex < b.BufferIndex) { - 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; + return true; } - public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) + if (a.BufferIndex == b.BufferIndex) { - 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; + return a.ElementIndex < b.ElementIndex; } + + return false; } - internal static class MemoryGroupIndexExtensions + public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) { - public static T GetElementAt(this IMemoryGroup group, MemoryGroupIndex idx) - where T : struct + if (a.BufferLength != b.BufferLength) { - return group[idx.BufferIndex].Span[idx.ElementIndex]; + throw new InvalidOperationException(); } - public static void SetElementAt(this IMemoryGroup group, MemoryGroupIndex idx, T value) - where T : struct + if (a.BufferIndex > b.BufferIndex) { - group[idx.BufferIndex].Span[idx.ElementIndex] = value; + return true; } - public static MemoryGroupIndex MinIndex(this IMemoryGroup group) - where T : struct + if (a.BufferIndex == b.BufferIndex) { - return new MemoryGroupIndex(group.BufferLength, 0, 0); + return a.ElementIndex > b.ElementIndex; } - 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); - } + 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 index c7027d68cd..a49ab77781 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -1,67 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit; +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +public class MemoryGroupIndexTests { - public class MemoryGroupIndexTests + [Fact] + public void Equal() { - [Fact] - public void Equal() - { - var a = new MemoryGroupIndex(10, 1, 3); - var b = new MemoryGroupIndex(10, 1, 3); + 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); - } + 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); + [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); - } + 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); + [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); - } + 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() + { + 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; + [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); - } + 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 index 38b00538be..936e482cb3 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -1,236 +1,229 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; -using Xunit; -using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public partial class MemoryGroupTests { - public partial class MemoryGroupTests + public class Allocate : MemoryGroupTestsBase { - public class Allocate : MemoryGroupTestsBase - { #pragma warning disable SA1509 - public static TheoryData AllocateData = new() - { - { 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(S4), 1024, 20, 800, 4, 240, 80 }, - - { 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 Allocate_FromMemoryAllocator_BufferSizesAreCorrect( - T dummy, - int bufferCapacity, - int bufferAlignment, - long totalLength, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct + public static TheoryData AllocateData = new() { - this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + { 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(S4), 1024, 20, 800, 4, 240, 80 }, + + { 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 Allocate_FromMemoryAllocator_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); + // Act: + using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); - // Assert: - ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); - } + // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + } - [Theory] - [MemberData(nameof(AllocateData))] - public void Allocate_FromPool_BufferSizesAreCorrect( - T dummy, - int bufferCapacity, - int bufferAlignment, - long totalLength, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct + [Theory] + [MemberData(nameof(AllocateData))] + public void Allocate_FromPool_BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + if (totalLength == 0) { - if (totalLength == 0) - { - // Invalid case for UniformByteArrayPool allocations - return; - } + // Invalid case for UniformByteArrayPool allocations + return; + } - var pool = new UniformUnmanagedMemoryPool(bufferCapacity, expectedNumberOfBuffers); + var pool = new UniformUnmanagedMemoryPool(bufferCapacity, expectedNumberOfBuffers); - // Act: - Assert.True(MemoryGroup.TryAllocate(pool, totalLength, bufferAlignment, AllocationOptions.None, out MemoryGroup g)); + // Act: + Assert.True(MemoryGroup.TryAllocate(pool, totalLength, bufferAlignment, AllocationOptions.None, out MemoryGroup g)); - // Assert: - ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); - g.Dispose(); - } + // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + g.Dispose(); + } - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) + { + var pool = new UniformUnmanagedMemoryPool(10, 5); + UnmanagedMemoryHandle[] buffers = pool.Rent(5); + foreach (UnmanagedMemoryHandle b in buffers) { - var pool = new UniformUnmanagedMemoryPool(10, 5); - UnmanagedMemoryHandle[] buffers = pool.Rent(5); - foreach (UnmanagedMemoryHandle b in buffers) - { - new Span(b.Pointer, pool.BufferLength).Fill(42); - } - - pool.Return(buffers); + new Span(b.Pointer, pool.BufferLength).Fill(42); + } - Assert.True(MemoryGroup.TryAllocate(pool, 50, 10, options, out MemoryGroup g)); - Span expected = stackalloc byte[10]; - expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); - foreach (Memory memory in g) - { - Assert.True(expected.SequenceEqual(memory.Span)); - } + pool.Return(buffers); - g.Dispose(); + Assert.True(MemoryGroup.TryAllocate(pool, 50, 10, options, out MemoryGroup g)); + Span expected = stackalloc byte[10]; + expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); + foreach (Memory memory in g) + { + Assert.True(expected.SequenceEqual(memory.Span)); } - [Theory] - [InlineData(64, 4, 60, 240, true)] - [InlineData(64, 4, 60, 244, false)] - public void Allocate_FromPool_AroundLimit( - int bufferCapacityBytes, - int poolCapacity, - int alignmentBytes, - int requestBytes, - bool shouldSucceed) - { - var pool = new UniformUnmanagedMemoryPool(bufferCapacityBytes, poolCapacity); - int alignmentElements = alignmentBytes / Unsafe.SizeOf(); - int requestElements = requestBytes / Unsafe.SizeOf(); + g.Dispose(); + } - Assert.Equal(shouldSucceed, MemoryGroup.TryAllocate(pool, requestElements, alignmentElements, AllocationOptions.None, out MemoryGroup g)); - if (shouldSucceed) - { - Assert.NotNull(g); - } - else - { - Assert.Null(g); - } + [Theory] + [InlineData(64, 4, 60, 240, true)] + [InlineData(64, 4, 60, 244, false)] + public void Allocate_FromPool_AroundLimit( + int bufferCapacityBytes, + int poolCapacity, + int alignmentBytes, + int requestBytes, + bool shouldSucceed) + { + var pool = new UniformUnmanagedMemoryPool(bufferCapacityBytes, poolCapacity); + int alignmentElements = alignmentBytes / Unsafe.SizeOf(); + int requestElements = requestBytes / Unsafe.SizeOf(); - g?.Dispose(); + Assert.Equal(shouldSucceed, MemoryGroup.TryAllocate(pool, requestElements, alignmentElements, AllocationOptions.None, out MemoryGroup g)); + if (shouldSucceed) + { + Assert.NotNull(g); } - - internal static void ValidateAllocateMemoryGroup( - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer, - MemoryGroup g) - where T : struct + else { - Assert.Equal(expectedNumberOfBuffers, g.Count); - - if (expectedBufferSize >= 0) - { - Assert.Equal(expectedBufferSize, g.BufferLength); - } + Assert.Null(g); + } - if (g.Count == 0) - { - return; - } + g?.Dispose(); + } - for (int i = 0; i < g.Count - 1; i++) - { - Assert.Equal(g[i].Length, expectedBufferSize); - } + internal static void ValidateAllocateMemoryGroup( + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer, + MemoryGroup g) + where T : struct + { + Assert.Equal(expectedNumberOfBuffers, g.Count); - Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); + if (expectedBufferSize >= 0) + { + Assert.Equal(expectedBufferSize, g.BufferLength); } - [Fact] - public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + if (g.Count == 0) { - this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 + return; + } - Assert.Throws(() => - { - MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); - }); + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBufferSize); } - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 + + Assert.Throws(() => { - this.MemoryAllocator.BufferCapacityInBytes = 200; - this.MemoryAllocator.EnableNonThreadSafeLogging(); + MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); + }); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) + { + this.MemoryAllocator.BufferCapacityInBytes = 200; + this.MemoryAllocator.EnableNonThreadSafeLogging(); + + HashSet bufferHashes; - 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); - int expectedBlockCount = 5; - using (var g = MemoryGroup.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) + for (int i = 0; i < expectedBlockCount; i++) { - 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(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))); } + + Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); + Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); } } +} - [StructLayout(LayoutKind.Sequential, Size = 5)] - internal struct S5 - { - public override string ToString() => "S5"; - } +[StructLayout(LayoutKind.Sequential, Size = 5)] +internal struct S5 +{ + public override string ToString() => "S5"; +} - [StructLayout(LayoutKind.Sequential, Size = 4)] - internal struct S4 - { - public override string ToString() => "S4"; - } +[StructLayout(LayoutKind.Sequential, Size = 4)] +internal struct S4 +{ + public override string ToString() => "S4"; } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs index a721891c57..23aaf3c559 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -1,111 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public partial class MemoryGroupTests { - public partial class MemoryGroupTests + public class CopyTo : MemoryGroupTestsBase { - public class CopyTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; + 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); + [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); + 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); + 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}"); - } + 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); + [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)); - } + 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(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); - [Theory] - [InlineData(20, 7, 19)] - [InlineData(2, 1, 1)] - public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) + int expected = 1; + foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - var trg = new int[spanLength]; - Assert.ThrowsAny(() => src.CopyTo(trg)); + 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) + [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++) { - 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)); - } + src[i] = i + 1; } - [Theory] - [InlineData(10, 3, 11)] - [InlineData(0, 3, 1)] - public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); + src.AsSpan().CopyTo(trg); + + int position = 0; + for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) { - var src = new int[spanLength]; - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + 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.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs index ca57b9db9f..cb297cebff 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs @@ -1,84 +1,81 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public partial class MemoryGroupTests { - public partial class MemoryGroupTests + public class View : MemoryGroupTestsBase { - public class View : MemoryGroupTestsBase + [Fact] + public void RefersToOwnerGroupContent() { - [Fact] - public void RefersToOwnerGroupContent() - { - using MemoryGroup group = this.CreateTestGroup(240, 80, true); + 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) + 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) { - Span span = memory.Span; - foreach (int t in span) - { - Assert.Equal(cnt, t); - cnt++; - } + Assert.Equal(cnt, t); + cnt++; } } + } - [Fact] - public void IsInvalidatedOnOwnerGroupDispose() + [Fact] + public void IsInvalidatedOnOwnerGroupDispose() + { + MemoryGroupView view; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) { - MemoryGroupView view; - using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) - { - view = group.View; - } - - Assert.False(view.IsValid); + view = group.View; + } - Assert.ThrowsAny(() => - { - _ = view.Count; - }); + Assert.False(view.IsValid); - Assert.ThrowsAny(() => - { - _ = view.BufferLength; - }); + Assert.ThrowsAny(() => + { + _ = view.Count; + }); - Assert.ThrowsAny(() => - { - _ = view.TotalLength; - }); + Assert.ThrowsAny(() => + { + _ = view.BufferLength; + }); - Assert.ThrowsAny(() => - { - _ = view[0]; - }); - } + Assert.ThrowsAny(() => + { + _ = view.TotalLength; + }); - [Fact] - public void WhenInvalid_CanNotUseMemberMemory() + Assert.ThrowsAny(() => { - Memory memory; - using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) - { - memory = group.View[0]; - } + _ = view[0]; + }); + } - Assert.ThrowsAny(() => - { - _ = memory.Span; - }); + [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 index ed5952c67e..316150c305 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -1,241 +1,235 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public partial class MemoryGroupTests : MemoryGroupTestsBase { - public partial class MemoryGroupTests : MemoryGroupTestsBase + [Fact] + public void IsValid_TrueAfterCreation() { - [Fact] - public void IsValid_TrueAfterCreation() - { - using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); - Assert.True(g.IsValid); - } + Assert.True(g.IsValid); + } - [Fact] - public void IsValid_FalseAfterDisposal() - { - using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); - g.Dispose(); - Assert.False(g.IsValid); - } + 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 + private static readonly TheoryData CopyAndTransformData = + new TheoryData() { - 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); + { 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 }, + }; - src.TransformTo(trg, MultiplyAllBy2); + public class TransformTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; - 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); + [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); - Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } + src.TransformTo(trg, MultiplyAllBy2); - [Fact] - public void WhenTargetBufferTooShort_Throws() + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); - Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); + Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); } } - [Theory] - [InlineData(100, 5)] - [InlineData(100, 101)] - public void TransformInplace(int totalLength, int bufferLength) + [Fact] + public void WhenTargetBufferTooShort_Throws() { using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - 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++; - } + Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); } + } - [Fact] - public void Wrap() + [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[] 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); + int val = src.GetElementAt(i); + Assert.Equal(expected: cnt * 2, val); + cnt++; + } + } - using var group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); + [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); - Assert.Equal(3, group.Count); - Assert.Equal(4, group.BufferLength); - Assert.Equal(10, group.TotalLength); + using var group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); - Assert.True(group[0].Span.SequenceEqual(data0)); - Assert.True(group[1].Span.SequenceEqual(data1)); - Assert.True(group[2].Span.SequenceEqual(data2)); + Assert.Equal(3, group.Count); + Assert.Equal(4, group.BufferLength); + Assert.Equal(10, group.TotalLength); - int cnt = 0; - int[][] allData = { data0, data1, data2 }; - foreach (Memory memory in group) - { - Assert.True(memory.Span.SequenceEqual(allData[cnt])); - cnt++; - } - } + 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() + int cnt = 0; + int[][] allData = { data0, data1, data2 }; + foreach (Memory memory in group) { - { 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 }, - }; + Assert.True(memory.Span.SequenceEqual(allData[cnt])); + cnt++; + } + } - [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); + 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.GetBoundedMemorySlice(start, length); + Memory slice = group.GetBoundedMemorySlice(start, length); - Assert.Equal(length, slice.Length); + Assert.Equal(length, slice.Length); - int expected = (int)start + 1; - foreach (int val in slice.Span) - { - Assert.Equal(expected, val); - expected++; - } + 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, -1, 91 }, - { 300, 100, 110, 91 }, - { 42, 7, 0, 8 }, - { 42, 7, 1, 7 }, - { 42, 7, 1, 30 }, - }; + public static TheoryData GetBoundedSlice_ErrorData = new TheoryData() + { + { 300, 100, -1, 91 }, + { 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.GetBoundedMemorySlice(start, length)); + } - [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.GetBoundedMemorySlice(start, length)); - } + [Fact] + public void FillWithFastEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); - [Fact] - public void FillWithFastEnumerator() + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + foreach (Memory memory in group) { - 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)); - } + Assert.True(memory.Span.SequenceEqual(expectedRow)); } + } - [Fact] - public void FillWithSlowGenericEnumerator() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Fill(42); + [Fact] + public void FillWithSlowGenericEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); - IReadOnlyList> groupAsList = group; - foreach (Memory memory in groupAsList) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + IReadOnlyList> groupAsList = group; + foreach (Memory memory in groupAsList) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); } + } - [Fact] - public void FillWithSlowEnumerator() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Fill(42); + [Fact] + public void FillWithSlowEnumerator() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); - IEnumerable groupAsList = group; - foreach (Memory memory in groupAsList) - { - Assert.True(memory.Span.SequenceEqual(expectedRow)); - } + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + IEnumerable groupAsList = group; + foreach (Memory memory in groupAsList) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); } + } - [Fact] - public void Clear() - { - using MemoryGroup group = this.CreateTestGroup(100, 10, true); - group.Clear(); + [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)); - } + var expectedRow = new int[10]; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); } + } - private static void MultiplyAllBy2(ReadOnlySpan source, Span target) + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) + { + Assert.Equal(source.Length, target.Length); + for (int k = 0; k < source.Length; k++) { - Assert.Equal(source.Length, target.Length); - for (int k = 0; k < source.Length; k++) - { - target[k] = source[k] * 2; - } + target[k] = source[k] * 2; } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs index 376afdf711..8fe6ef3ddb 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -3,33 +3,32 @@ using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; + +public abstract class MemoryGroupTestsBase { - 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) { - internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); - /// - /// Create a group, either uninitialized or filled with incrementing numbers starting with 1. - /// - internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + if (!fillSequence) { - 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; } + + 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/TestStructs.cs b/tests/ImageSharp.Tests/Memory/TestStructs.cs index 5940c44ac6..ccbee5bd51 100644 --- a/tests/ImageSharp.Tests/Memory/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -1,95 +1,91 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Memory; -namespace SixLabors.ImageSharp.Tests.Memory +public static class TestStructs { - public static class TestStructs + public struct Foo : IEquatable { - public struct Foo : IEquatable - { - public int A; + public int A; - public double B; + public double B; - public Foo(int a, double b) - { - this.A = a; - this.B = b; - } + public Foo(int a, double b) + { + this.A = a; + this.B = b; + } - internal static Foo[] CreateArray(int size) + internal static Foo[] CreateArray(int size) + { + var result = new Foo[size]; + for (int i = 0; i < size; i++) { - var result = new Foo[size]; - for (int i = 0; i < size; i++) - { - result[i] = new Foo(i + 1, i + 1); - } - - return result; + result[i] = new Foo(i + 1, i + 1); } - public override bool Equals(object obj) => obj is Foo foo && this.Equals(foo); - - public bool Equals(Foo other) => this.A.Equals(other.A) && this.B.Equals(other.B); + 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(); - return hashCode; - } + public override bool Equals(object obj) => obj is Foo foo && this.Equals(foo); - public override string ToString() => $"({this.A},{this.B})"; - } + public bool Equals(Foo other) => this.A.Equals(other.A) && this.B.Equals(other.B); - /// - /// sizeof(AlignedFoo) == sizeof(long) - /// - public unsafe struct AlignedFoo : IEquatable + public override int GetHashCode() { - public int A; + int hashCode = -1817952719; + hashCode = (hashCode * -1521134295) + base.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); + return hashCode; + } - public int B; + public override string ToString() => $"({this.A},{this.B})"; + } - static AlignedFoo() - { - Assert.Equal(sizeof(AlignedFoo), sizeof(long)); - } + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public unsafe struct AlignedFoo : IEquatable + { + public int A; - public AlignedFoo(int a, int b) - { - this.A = a; - this.B = b; - } + public int B; - public override bool Equals(object obj) => obj is AlignedFoo foo && this.Equals(foo); + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } - public bool Equals(AlignedFoo other) => this.A.Equals(other.A) && this.B.Equals(other.B); + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } - internal static AlignedFoo[] CreateArray(int size) - { - var result = new AlignedFoo[size]; - for (int i = 0; i < size; i++) - { - result[i] = new AlignedFoo(i + 1, i + 1); - } + public override bool Equals(object obj) => obj is AlignedFoo foo && this.Equals(foo); - return result; - } + public bool Equals(AlignedFoo other) => this.A.Equals(other.A) && this.B.Equals(other.B); - public override int GetHashCode() + internal static AlignedFoo[] CreateArray(int size) + { + var result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) { - int hashCode = -1817952719; - hashCode = (hashCode * -1521134295) + base.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); - hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); - return hashCode; + 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(); + return hashCode; } } } diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs index 87a3d63b85..252784a7c5 100644 --- a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs +++ b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs @@ -1,76 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Threading; using SixLabors.ImageSharp.Diagnostics; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public static class MemoryAllocatorValidator { - public static class MemoryAllocatorValidator - { - private static readonly AsyncLocal LocalInstance = new(); + private static readonly AsyncLocal LocalInstance = new(); - public static bool MonitoringAllocations => LocalInstance.Value != null; + public static bool MonitoringAllocations => LocalInstance.Value != null; - static MemoryAllocatorValidator() - { - MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated; - MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased; - } + static MemoryAllocatorValidator() + { + MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated; + MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased; + } - private static void MemoryDiagnostics_MemoryReleased() + private static void MemoryDiagnostics_MemoryReleased() + { + TestMemoryDiagnostics backing = LocalInstance.Value; + if (backing != null) { - TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalRemainingAllocated--; - } + backing.TotalRemainingAllocated--; } + } - private static void MemoryDiagnostics_MemoryAllocated() + private static void MemoryDiagnostics_MemoryAllocated() + { + TestMemoryDiagnostics backing = LocalInstance.Value; + if (backing != null) { - TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalAllocated++; - backing.TotalRemainingAllocated++; - } + backing.TotalAllocated++; + backing.TotalRemainingAllocated++; } + } - public static TestMemoryDiagnostics MonitorAllocations() - { - var diag = new TestMemoryDiagnostics(); - LocalInstance.Value = diag; - return diag; - } + public static TestMemoryDiagnostics MonitorAllocations() + { + var diag = new TestMemoryDiagnostics(); + LocalInstance.Value = diag; + return diag; + } - public static void StopMonitoringAllocations() => LocalInstance.Value = null; + public static void StopMonitoringAllocations() => LocalInstance.Value = null; - public static void ValidateAllocations(int expectedAllocationCount = 0) - => LocalInstance.Value?.Validate(expectedAllocationCount); + public static void ValidateAllocations(int expectedAllocationCount = 0) + => LocalInstance.Value?.Validate(expectedAllocationCount); - public class TestMemoryDiagnostics : IDisposable - { - public int TotalAllocated { get; set; } + public class TestMemoryDiagnostics : IDisposable + { + public int TotalAllocated { get; set; } - public int TotalRemainingAllocated { get; set; } + public int TotalRemainingAllocated { get; set; } - public void Validate(int expectedAllocationCount) - { - int count = this.TotalRemainingAllocated; - bool pass = expectedAllocationCount == count; - Assert.True(pass, $"Expected {expectedAllocationCount} undisposed buffers but found {count}"); - } + public void Validate(int expectedAllocationCount) + { + int count = this.TotalRemainingAllocated; + bool pass = expectedAllocationCount == count; + Assert.True(pass, $"Expected {expectedAllocationCount} undisposed buffers but found {count}"); + } - public void Dispose() + public void Dispose() + { + this.Validate(0); + if (LocalInstance.Value == this) { - this.Validate(0); - if (LocalInstance.Value == this) - { - StopMonitoringAllocations(); - } + StopMonitoringAllocations(); } } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 6fb63ba529..e9c61db6fc 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -5,78 +5,76 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using Xunit; using ExifProfile = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifProfile; using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; -namespace SixLabors.ImageSharp.Tests.Metadata +namespace SixLabors.ImageSharp.Tests.Metadata; + +/// +/// Tests the class. +/// +public class ImageFrameMetadataTests { - /// - /// Tests the class. - /// - public class ImageFrameMetadataTests + [Fact] + public void ConstructorImageFrameMetadata() { - [Fact] - public void ConstructorImageFrameMetadata() - { - const int frameDelay = 42; - const int colorTableLength = 128; - const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; + const int frameDelay = 42; + const int colorTableLength = 128; + const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; - var metaData = new ImageFrameMetadata(); - GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata(); - gifFrameMetadata.FrameDelay = frameDelay; - gifFrameMetadata.ColorTableLength = colorTableLength; - gifFrameMetadata.DisposalMethod = disposalMethod; + var metaData = new ImageFrameMetadata(); + GifFrameMetadata gifFrameMetadata = metaData.GetGifMetadata(); + gifFrameMetadata.FrameDelay = frameDelay; + gifFrameMetadata.ColorTableLength = colorTableLength; + gifFrameMetadata.DisposalMethod = disposalMethod; - var clone = new ImageFrameMetadata(metaData); - GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); + var clone = new ImageFrameMetadata(metaData); + GifFrameMetadata cloneGifFrameMetadata = clone.GetGifMetadata(); - Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay); - Assert.Equal(colorTableLength, cloneGifFrameMetadata.ColorTableLength); - Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod); - } + Assert.Equal(frameDelay, cloneGifFrameMetadata.FrameDelay); + Assert.Equal(colorTableLength, cloneGifFrameMetadata.ColorTableLength); + Assert.Equal(disposalMethod, cloneGifFrameMetadata.DisposalMethod); + } - [Fact] - public void CloneIsDeep() + [Fact] + public void CloneIsDeep() + { + // arrange + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.Software, "UnitTest"); + exifProfile.SetValue(ExifTag.Artist, "UnitTest"); + var xmpProfile = new XmpProfile(new byte[0]); + var iccProfile = new IccProfile() { - // arrange - var exifProfile = new ExifProfile(); - exifProfile.SetValue(ExifTag.Software, "UnitTest"); - exifProfile.SetValue(ExifTag.Artist, "UnitTest"); - var xmpProfile = new XmpProfile(new byte[0]); - var iccProfile = new IccProfile() - { - Header = new IccProfileHeader() - { - CmmType = "Unittest" - } - }; - var iptcProfile = new ImageSharp.Metadata.Profiles.Iptc.IptcProfile(); - var metaData = new ImageFrameMetadata() + Header = new IccProfileHeader() { - XmpProfile = xmpProfile, - ExifProfile = exifProfile, - IccProfile = iccProfile, - IptcProfile = iptcProfile - }; + CmmType = "Unittest" + } + }; + var iptcProfile = new ImageSharp.Metadata.Profiles.Iptc.IptcProfile(); + var metaData = new ImageFrameMetadata() + { + XmpProfile = xmpProfile, + ExifProfile = exifProfile, + IccProfile = iccProfile, + IptcProfile = iptcProfile + }; - // act - ImageFrameMetadata clone = metaData.DeepClone(); + // act + ImageFrameMetadata clone = metaData.DeepClone(); - // assert - Assert.NotNull(clone); - Assert.NotNull(clone.ExifProfile); - Assert.NotNull(clone.XmpProfile); - Assert.NotNull(clone.IccProfile); - Assert.NotNull(clone.IptcProfile); - Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); - Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); - Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); - Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); - Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); - Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); - } + // assert + Assert.NotNull(clone); + Assert.NotNull(clone.ExifProfile); + Assert.NotNull(clone.XmpProfile); + Assert.NotNull(clone.IccProfile); + Assert.NotNull(clone.IptcProfile); + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); + Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); + Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); + Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); + Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); + Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs index 5d1c2cea38..9b1e42d016 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageMetadataTests.cs @@ -5,102 +5,99 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Metadata; -namespace SixLabors.ImageSharp.Tests.Metadata +/// +/// Tests the class. +/// +public class ImageMetadataTests { - /// - /// Tests the class. - /// - public class ImageMetadataTests + [Fact] + public void ConstructorImageMetadata() { - [Fact] - public void ConstructorImageMetadata() - { - var metaData = new ImageMetadata(); + var metaData = new ImageMetadata(); - var exifProfile = new ExifProfile(); + var exifProfile = new ExifProfile(); - metaData.ExifProfile = exifProfile; - metaData.HorizontalResolution = 4; - metaData.VerticalResolution = 2; + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; - ImageMetadata clone = metaData.DeepClone(); + ImageMetadata clone = metaData.DeepClone(); - Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); - Assert.Equal(4, clone.HorizontalResolution); - Assert.Equal(2, clone.VerticalResolution); - } + Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); + Assert.Equal(4, clone.HorizontalResolution); + Assert.Equal(2, clone.VerticalResolution); + } - [Fact] - public void CloneIsDeep() + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageMetadata { - var metaData = new ImageMetadata - { - ExifProfile = new ExifProfile(), - HorizontalResolution = 4, - VerticalResolution = 2 - }; - - ImageMetadata clone = metaData.DeepClone(); - clone.HorizontalResolution = 2; - clone.VerticalResolution = 4; - - Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); - Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); - Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); - } + ExifProfile = new ExifProfile(), + HorizontalResolution = 4, + VerticalResolution = 2 + }; + + ImageMetadata clone = metaData.DeepClone(); + clone.HorizontalResolution = 2; + clone.VerticalResolution = 4; + + Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); + Assert.False(metaData.HorizontalResolution.Equals(clone.HorizontalResolution)); + Assert.False(metaData.VerticalResolution.Equals(clone.VerticalResolution)); + } - [Fact] - public void HorizontalResolution() - { - var metaData = new ImageMetadata(); - Assert.Equal(96, metaData.HorizontalResolution); + [Fact] + public void HorizontalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution = 0; - Assert.Equal(96, metaData.HorizontalResolution); + metaData.HorizontalResolution = 0; + Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution = -1; - Assert.Equal(96, metaData.HorizontalResolution); + metaData.HorizontalResolution = -1; + Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution = 1; - Assert.Equal(1, metaData.HorizontalResolution); - } + metaData.HorizontalResolution = 1; + Assert.Equal(1, metaData.HorizontalResolution); + } - [Fact] - public void VerticalResolution() - { - var metaData = new ImageMetadata(); - Assert.Equal(96, metaData.VerticalResolution); + [Fact] + public void VerticalResolution() + { + var metaData = new ImageMetadata(); + Assert.Equal(96, metaData.VerticalResolution); - metaData.VerticalResolution = 0; - Assert.Equal(96, metaData.VerticalResolution); + metaData.VerticalResolution = 0; + Assert.Equal(96, metaData.VerticalResolution); - metaData.VerticalResolution = -1; - Assert.Equal(96, metaData.VerticalResolution); + metaData.VerticalResolution = -1; + Assert.Equal(96, metaData.VerticalResolution); - metaData.VerticalResolution = 1; - Assert.Equal(1, metaData.VerticalResolution); - } + metaData.VerticalResolution = 1; + Assert.Equal(1, metaData.VerticalResolution); + } - [Fact] - public void SyncProfiles() - { - var exifProfile = new ExifProfile(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); + [Fact] + public void SyncProfiles() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - using (var image = new Image(1, 1)) - { - image.Metadata.ExifProfile = exifProfile; - image.Metadata.HorizontalResolution = 400; - image.Metadata.VerticalResolution = 500; + using (var image = new Image(1, 1)) + { + image.Metadata.ExifProfile = exifProfile; + image.Metadata.HorizontalResolution = 400; + image.Metadata.VerticalResolution = 500; - image.Metadata.SyncProfiles(); + image.Metadata.SyncProfiles(); - Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - } + Assert.Equal(400, image.Metadata.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(500, image.Metadata.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index a101e6e706..26f777ae72 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -1,606 +1,599 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif +[Trait("Profile", "Exif")] +public class ExifProfileTests { - [Trait("Profile", "Exif")] - public class ExifProfileTests + public enum TestImageWriteFormat { - public enum TestImageWriteFormat - { - /// - /// Writes a jpg file. - /// - Jpeg, - - /// - /// Writes a png file. - /// - Png, - - /// - /// Writes a lossless webp file. - /// - WebpLossless, - - /// - /// Writes a lossy webp file. - /// - WebpLossy - } - - private static readonly Dictionary TestProfileValues = new() - { - { ExifTag.Software, "Software" }, - { ExifTag.Copyright, "Copyright" }, - { ExifTag.Orientation, (ushort)5 }, - { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, - { ExifTag.ImageDescription, "ImageDescription" }, - { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, - { ExifTag.Model, "Model" }, - { ExifTag.XPAuthor, "The XPAuthor text" }, - { ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.Unicode, "The Unicode text") }, - { ExifTag.GPSAreaInformation, new EncodedString("Default constructor text (GPSAreaInformation)") }, - }; + /// + /// Writes a jpg file. + /// + Jpeg, + + /// + /// Writes a png file. + /// + Png, + + /// + /// Writes a lossless webp file. + /// + WebpLossless, + + /// + /// Writes a lossy webp file. + /// + WebpLossy + } - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void Constructor(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); + private static readonly Dictionary TestProfileValues = new() + { + { ExifTag.Software, "Software" }, + { ExifTag.Copyright, "Copyright" }, + { ExifTag.Orientation, (ushort)5 }, + { ExifTag.ShutterSpeedValue, new SignedRational(75.55) }, + { ExifTag.ImageDescription, "ImageDescription" }, + { ExifTag.ExposureTime, new Rational(1.0 / 1600.0) }, + { ExifTag.Model, "Model" }, + { ExifTag.XPAuthor, "The XPAuthor text" }, + { ExifTag.UserComment, new EncodedString(EncodedString.CharacterCode.Unicode, "The Unicode text") }, + { ExifTag.GPSAreaInformation, new EncodedString("Default constructor text (GPSAreaInformation)") }, + }; + + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] + public void Constructor(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).CreateRgba32Image(); - Assert.Null(image.Metadata.ExifProfile); + Assert.Null(image.Metadata.ExifProfile); - const string expected = "Dirk Lemstra"; - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, expected); + const string expected = "Dirk Lemstra"; + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, expected); - image = WriteAndRead(image, imageFormat); + image = WriteAndRead(image, imageFormat); - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(1, image.Metadata.ExifProfile.Values.Count); + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(1, image.Metadata.ExifProfile.Values.Count); - IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Copyright); + IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.Copyright); - Assert.NotNull(value); - Assert.Equal(expected, value.Value); - image.Dispose(); - } - - [Fact] - public void ConstructorEmpty() - { - new ExifProfile(null); - new ExifProfile(Array.Empty()); - } + Assert.NotNull(value); + Assert.Equal(expected, value.Value); + image.Dispose(); + } - [Fact] - public void EmptyWriter() - { - var profile = new ExifProfile() { Parts = ExifParts.GpsTags }; - profile.SetValue(ExifTag.Copyright, "Copyright text"); + [Fact] + public void ConstructorEmpty() + { + new ExifProfile(null); + new ExifProfile(Array.Empty()); + } - byte[] bytes = profile.ToByteArray(); + [Fact] + public void EmptyWriter() + { + var profile = new ExifProfile() { Parts = ExifParts.GpsTags }; + profile.SetValue(ExifTag.Copyright, "Copyright text"); - Assert.NotNull(bytes); - Assert.Empty(bytes); - } + byte[] bytes = profile.ToByteArray(); - [Fact] - public void ConstructorCopy() - { - Assert.Throws(() => ((ExifProfile)null).DeepClone()); + Assert.NotNull(bytes); + Assert.Empty(bytes); + } - ExifProfile profile = GetExifProfile(); + [Fact] + public void ConstructorCopy() + { + Assert.Throws(() => ((ExifProfile)null).DeepClone()); - ExifProfile clone = profile.DeepClone(); - TestProfile(clone); + ExifProfile profile = GetExifProfile(); - profile.SetValue(ExifTag.ColorSpace, (ushort)2); + ExifProfile clone = profile.DeepClone(); + TestProfile(clone); - clone = profile.DeepClone(); - TestProfile(clone); - } + profile.SetValue(ExifTag.ColorSpace, (ushort)2); - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WriteFraction(TestImageWriteFormat imageFormat) - { - using var memStream = new MemoryStream(); - double exposureTime = 1.0 / 1600; + clone = profile.DeepClone(); + TestProfile(clone); + } - ExifProfile profile = GetExifProfile(); + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] + public void WriteFraction(TestImageWriteFormat imageFormat) + { + using var memStream = new MemoryStream(); + double exposureTime = 1.0 / 1600; - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); + ExifProfile profile = GetExifProfile(); - var image = new Image(1, 1); - image.Metadata.ExifProfile = profile; + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime)); - image = WriteAndRead(image, imageFormat); + var image = new Image(1, 1); + image.Metadata.ExifProfile = profile; - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + image = WriteAndRead(image, imageFormat); - IExifValue value = profile.GetValue(ExifTag.ExposureTime); - Assert.NotNull(value); - Assert.NotEqual(exposureTime, value.Value.ToDouble()); + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - memStream.Position = 0; - profile = GetExifProfile(); + IExifValue value = profile.GetValue(ExifTag.ExposureTime); + Assert.NotNull(value); + Assert.NotEqual(exposureTime, value.Value.ToDouble()); - profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); - image.Metadata.ExifProfile = profile; + memStream.Position = 0; + profile = GetExifProfile(); - image = WriteAndRead(image, imageFormat); + profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true)); + image.Metadata.ExifProfile = profile; - profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + image = WriteAndRead(image, imageFormat); - value = profile.GetValue(ExifTag.ExposureTime); - Assert.Equal(exposureTime, value.Value.ToDouble()); + profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - image.Dispose(); - } + value = profile.GetValue(ExifTag.ExposureTime); + Assert.Equal(exposureTime, value.Value.ToDouble()); - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void ReadWriteInfinity(TestImageWriteFormat imageFormat) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); + image.Dispose(); + } - image = WriteAndReadJpeg(image); - IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] + public void ReadWriteInfinity(TestImageWriteFormat imageFormat) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity)); - image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); + image = WriteAndReadJpeg(image); + IExifValue value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value); - image = WriteAndRead(image, imageFormat); - value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); - Assert.NotNull(value); - Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); + image.Metadata.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity)); - image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); + image = WriteAndRead(image, imageFormat); + value = image.Metadata.ExifProfile.GetValue(ExifTag.ExposureBiasValue); + Assert.NotNull(value); + Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value); - image = WriteAndRead(image, imageFormat); - IExifValue value2 = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); - Assert.NotNull(value2); - Assert.Equal(new Rational(double.PositiveInfinity), value2.Value); + image.Metadata.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity)); - image.Dispose(); - } + image = WriteAndRead(image, imageFormat); + IExifValue value2 = image.Metadata.ExifProfile.GetValue(ExifTag.FlashEnergy); + Assert.NotNull(value2); + Assert.Equal(new Rational(double.PositiveInfinity), value2.Value); - [Theory] - /* The original exif profile has 19 values, the written profile should be 3 less. - 1 x due to setting of null "ReferenceBlackWhite" value. - 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere - strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) - https://exiftool.org/TagNames/EXIF.html */ - [InlineData(TestImageWriteFormat.Jpeg, 18)] - [InlineData(TestImageWriteFormat.Png, 18)] - [InlineData(TestImageWriteFormat.WebpLossless, 18)] - public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) - { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + image.Dispose(); + } - IExifValue software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - Assert.Equal("ImageSharp", software.Value); + [Theory] + /* The original exif profile has 19 values, the written profile should be 3 less. + 1 x due to setting of null "ReferenceBlackWhite" value. + 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere + strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) + https://exiftool.org/TagNames/EXIF.html */ + [InlineData(TestImageWriteFormat.Jpeg, 18)] + [InlineData(TestImageWriteFormat.Png, 18)] + [InlineData(TestImageWriteFormat.WebpLossless, 18)] + public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) + { + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); - // ExifString can set integer values. - Assert.True(software.TrySetValue(15)); - Assert.False(software.TrySetValue(15F)); + IExifValue software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + Assert.Equal("ImageSharp", software.Value); - image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); + // ExifString can set integer values. + Assert.True(software.TrySetValue(15)); + Assert.False(software.TrySetValue(15F)); - IExifValue shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + image.Metadata.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55)); - Assert.Equal(new SignedRational(7555, 100), shutterSpeed.Value); - Assert.False(shutterSpeed.TrySetValue(75)); + IExifValue shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); + Assert.Equal(new SignedRational(7555, 100), shutterSpeed.Value); + Assert.False(shutterSpeed.TrySetValue(75)); - // We also need to change this value because this overrides XResolution when the image is written. - image.Metadata.HorizontalResolution = 150.0; + image.Metadata.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0)); - IExifValue xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(150, 1), xResolution.Value); + // We also need to change this value because this overrides XResolution when the image is written. + image.Metadata.HorizontalResolution = 150.0; - Assert.False(xResolution.TrySetValue("ImageSharp")); + IExifValue xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(150, 1), xResolution.Value); - image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); + Assert.False(xResolution.TrySetValue("ImageSharp")); - IExifValue referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - Assert.Null(referenceBlackWhite.Value); + image.Metadata.ExifProfile.SetValue(ExifTag.ReferenceBlackWhite, null); - var expectedLatitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; - image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, expectedLatitude); + IExifValue referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(referenceBlackWhite.Value); - IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - Assert.Equal(expectedLatitude, latitude.Value); + var expectedLatitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) }; + image.Metadata.ExifProfile.SetValue(ExifTag.GPSLatitude, expectedLatitude); - // todo: duplicate tags - Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); + IExifValue latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); - image = WriteAndRead(image, imageFormat); + // todo: duplicate tags + Assert.Equal(2, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); + image = WriteAndRead(image, imageFormat); - Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count); + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(0, image.Metadata.ExifProfile.Values.Count(v => (ushort)v.Tag == 59932)); - software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); - Assert.Equal("15", software.Value); + Assert.Equal(expectedProfileValueCount, image.Metadata.ExifProfile.Values.Count); - shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); - Assert.Equal(new SignedRational(75.55), shutterSpeed.Value); + software = image.Metadata.ExifProfile.GetValue(ExifTag.Software); + Assert.Equal("15", software.Value); - xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(150.0), xResolution.Value); + shutterSpeed = image.Metadata.ExifProfile.GetValue(ExifTag.ShutterSpeedValue); + Assert.Equal(new SignedRational(75.55), shutterSpeed.Value); - referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); - Assert.Null(referenceBlackWhite); + xResolution = image.Metadata.ExifProfile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(150.0), xResolution.Value); - latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); - Assert.Equal(expectedLatitude, latitude.Value); + referenceBlackWhite = image.Metadata.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite); + Assert.Null(referenceBlackWhite); - image.Dispose(); - } + latitude = image.Metadata.ExifProfile.GetValue(ExifTag.GPSLatitude); + Assert.Equal(expectedLatitude, latitude.Value); - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat) - { - // Arrange - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; - - // Act - image = WriteAndRead(image, imageFormat); + image.Dispose(); + } - // Assert - Assert.NotNull(image.Metadata.ExifProfile); - Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); - foreach (IExifValue exifProfileValue in image.Metadata.ExifProfile.Values) - { - Assert.True(ExifTags.GetPart(exifProfileValue.Tag) == ExifParts.ExifTags); - } + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] + public void WriteOnlyExifTags_Works(TestImageWriteFormat imageFormat) + { + // Arrange + Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + image.Metadata.ExifProfile.Parts = ExifParts.ExifTags; - image.Dispose(); - } + // Act + image = WriteAndRead(image, imageFormat); - [Fact] - public void RemoveEntry_Works() + // Assert + Assert.NotNull(image.Metadata.ExifProfile); + Assert.Equal(7, image.Metadata.ExifProfile.Values.Count); + foreach (IExifValue exifProfileValue in image.Metadata.ExifProfile.Values) { - // Arrange - using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); - int profileCount = image.Metadata.ExifProfile.Values.Count; - - // Assert - Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); - Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); - Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count); + Assert.True(ExifTags.GetPart(exifProfileValue.Tag) == ExifParts.ExifTags); } - [Fact] - public void Syncs() - { - var exifProfile = new ExifProfile(); - exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); - exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - - var metaData = new ImageMetadata - { - ExifProfile = exifProfile, - HorizontalResolution = 200, - VerticalResolution = 300 - }; - - metaData.HorizontalResolution = 100; - - Assert.Equal(200, 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(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()); + image.Dispose(); + } - exifProfile.Sync(metaData); + [Fact] + public void RemoveEntry_Works() + { + // Arrange + using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + int profileCount = image.Metadata.ExifProfile.Values.Count; + + // Assert + Assert.NotNull(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.True(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.False(image.Metadata.ExifProfile.RemoveValue(ExifTag.ColorSpace)); + Assert.Null(image.Metadata.ExifProfile.GetValue(ExifTag.ColorSpace)); + Assert.Equal(profileCount - 1, image.Metadata.ExifProfile.Values.Count); + } - Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); - Assert.Equal(150, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - } + [Fact] + public void Syncs() + { + var exifProfile = new ExifProfile(); + exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); + exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - [Fact] - public void Values() + var metaData = new ImageMetadata { - ExifProfile profile = GetExifProfile(); - - TestProfile(profile); + ExifProfile = exifProfile, + HorizontalResolution = 200, + VerticalResolution = 300 + }; - using Image thumbnail = profile.CreateThumbnail(); - Assert.NotNull(thumbnail); - Assert.Equal(256, thumbnail.Width); - Assert.Equal(170, thumbnail.Height); + metaData.HorizontalResolution = 100; - using Image genericThumbnail = profile.CreateThumbnail(); - Assert.NotNull(genericThumbnail); - Assert.Equal(256, genericThumbnail.Width); - Assert.Equal(170, genericThumbnail.Height); - } + Assert.Equal(200, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - [Fact] - public void ReadWriteLargeProfileJpg() - { - ExifTag[] tags = { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; - foreach (ExifTag tag in tags) - { - // Arrange - var junk = new StringBuilder(); - for (int i = 0; i < 65600; i++) - { - junk.Append('a'); - } - - var image = new Image(100, 100); - ExifProfile expectedProfile = CreateExifProfile(); - var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); - expectedProfile.SetValue(tag, junk.ToString()); - image.Metadata.ExifProfile = expectedProfile; - - // Act - Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); - - // Assert - ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actualProfile); - - foreach (ExifTag expectedProfileTag in expectedProfileTags) - { - IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); - IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); - Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); - } - - IExifValue expected = expectedProfile.GetValue(tag); - IExifValue actual = actualProfile.GetValue(tag); - Assert.Equal(expected, actual); - } - } + exifProfile.Sync(metaData); - [Fact] - public void ExifTypeUndefined() - { - // This image contains an 802 byte EXIF profile. - // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) - using Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); - Assert.NotNull(image); + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + metaData.VerticalResolution = 150; - foreach (ExifValue value in profile.Values) - { - if (value.DataType == ExifDataType.Undefined) - { - Assert.True(value.IsArray); - Assert.Equal(4U, 4 * ExifDataTypes.GetSize(value.DataType)); - } - } - } + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - [Fact] - public void TestArrayValueWithUnspecifiedSize() - { - // This images contains array in the exif profile that has zero components. - using Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); + exifProfile.Sync(metaData); - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(150, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); + } - // Force parsing of the profile. - Assert.Equal(25, profile.Values.Count); + [Fact] + public void Values() + { + ExifProfile profile = GetExifProfile(); - // todo: duplicate tags (from root container and subIfd) - Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); + TestProfile(profile); - byte[] bytes = profile.ToByteArray(); - Assert.Equal(531, bytes.Length); + using Image thumbnail = profile.CreateThumbnail(); + Assert.NotNull(thumbnail); + Assert.Equal(256, thumbnail.Width); + Assert.Equal(170, thumbnail.Height); - var profile2 = new ExifProfile(bytes); - Assert.Equal(25, profile2.Values.Count); - } + using Image genericThumbnail = profile.CreateThumbnail(); + Assert.NotNull(genericThumbnail); + Assert.Equal(256, genericThumbnail.Width); + Assert.Equal(170, genericThumbnail.Height); + } - [Theory] - [InlineData(TestImageWriteFormat.Jpeg)] - [InlineData(TestImageWriteFormat.Png)] - [InlineData(TestImageWriteFormat.WebpLossless)] - [InlineData(TestImageWriteFormat.WebpLossy)] - public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + [Fact] + public void ReadWriteLargeProfileJpg() + { + ExifTag[] tags = { ExifTag.Software, ExifTag.Copyright, ExifTag.Model, ExifTag.ImageDescription }; + foreach (ExifTag tag in tags) { // Arrange - var image = new Image(1, 1); - image.Metadata.ExifProfile = CreateExifProfile(); - - // Act - using Image reloadedImage = WriteAndRead(image, imageFormat); - - // Assert - ExifProfile actual = reloadedImage.Metadata.ExifProfile; - Assert.NotNull(actual); - foreach (KeyValuePair expectedProfileValue in TestProfileValues) + var junk = new StringBuilder(); + for (int i = 0; i < 65600; i++) { - IExifValue actualProfileValue = actual.GetValueInternal(expectedProfileValue.Key); - Assert.NotNull(actualProfileValue); - Assert.Equal(expectedProfileValue.Value, actualProfileValue.GetValue()); + junk.Append('a'); } - } - [Fact] - public void ProfileToByteArray() - { - // Arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); + var image = new Image(100, 100); ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + expectedProfile.SetValue(tag, junk.ToString()); + image.Metadata.ExifProfile = expectedProfile; // Act - byte[] actualBytes = expectedProfile.ToByteArray(); - var actualProfile = new ExifProfile(actualBytes); + Image reloadedImage = WriteAndRead(image, TestImageWriteFormat.Jpeg); // Assert - Assert.NotNull(actualBytes); - Assert.NotEmpty(actualBytes); - Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); + ExifProfile actualProfile = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actualProfile); + foreach (ExifTag expectedProfileTag in expectedProfileTags) { IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); } + + IExifValue expected = expectedProfile.GetValue(tag); + IExifValue actual = actualProfile.GetValue(tag); + Assert.Equal(expected, actual); } + } - private static ExifProfile CreateExifProfile() - { - var profile = new ExifProfile(); + [Fact] + public void ExifTypeUndefined() + { + // This image contains an 802 byte EXIF profile. + // It has a tag with an index offset of 18,481,152 bytes (overrunning the data) + using Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateRgba32Image(); + Assert.NotNull(image); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - foreach (KeyValuePair exifProfileValue in TestProfileValues) + foreach (ExifValue value in profile.Values) + { + if (value.DataType == ExifDataType.Undefined) { - profile.SetValueInternal(exifProfileValue.Key, exifProfileValue.Value); + Assert.True(value.IsArray); + Assert.Equal(4U, 4 * ExifDataTypes.GetSize(value.DataType)); } - - return profile; } + } - [Fact] - public void IfdStructure() - { - var exif = new ExifProfile(); - exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); + [Fact] + public void TestArrayValueWithUnspecifiedSize() + { + // This images contains array in the exif profile that has zero components. + using Image image = TestFile.Create(TestImages.Jpeg.Issues.InvalidCast520).CreateRgba32Image(); - Span actualBytes = exif.ToByteArray(); + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); - // Assert - int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; - Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4))); + // Force parsing of the profile. + Assert.Equal(25, profile.Values.Count); - int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; - Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(nextIfdPointerOffset, 4))); - } + // todo: duplicate tags (from root container and subIfd) + Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); - internal static ExifProfile GetExifProfile() - { - using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + byte[] bytes = profile.ToByteArray(); + Assert.Equal(531, bytes.Length); + + var profile2 = new ExifProfile(bytes); + Assert.Equal(25, profile2.Values.Count); + } - ExifProfile profile = image.Metadata.ExifProfile; - Assert.NotNull(profile); + [Theory] + [InlineData(TestImageWriteFormat.Jpeg)] + [InlineData(TestImageWriteFormat.Png)] + [InlineData(TestImageWriteFormat.WebpLossless)] + [InlineData(TestImageWriteFormat.WebpLossy)] + public void WritingImagePreservesExifProfile(TestImageWriteFormat imageFormat) + { + // Arrange + var image = new Image(1, 1); + image.Metadata.ExifProfile = CreateExifProfile(); - return profile; - } + // Act + using Image reloadedImage = WriteAndRead(image, imageFormat); - private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) + // Assert + ExifProfile actual = reloadedImage.Metadata.ExifProfile; + Assert.NotNull(actual); + foreach (KeyValuePair expectedProfileValue in TestProfileValues) { - switch (imageFormat) - { - case TestImageWriteFormat.Jpeg: - return WriteAndReadJpeg(image); - case TestImageWriteFormat.Png: - return WriteAndReadPng(image); - case TestImageWriteFormat.WebpLossless: - return WriteAndReadWebp(image, WebpFileFormatType.Lossless); - case TestImageWriteFormat.WebpLossy: - return WriteAndReadWebp(image, WebpFileFormatType.Lossy); - default: - throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); - } + IExifValue actualProfileValue = actual.GetValueInternal(expectedProfileValue.Key); + Assert.NotNull(actualProfileValue); + Assert.Equal(expectedProfileValue.Value, actualProfileValue.GetValue()); } + } - private static Image WriteAndReadJpeg(Image image) + [Fact] + public void ProfileToByteArray() + { + // Arrange + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); + ExifProfile expectedProfile = CreateExifProfile(); + var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + + // Act + byte[] actualBytes = expectedProfile.ToByteArray(); + var actualProfile = new ExifProfile(actualBytes); + + // Assert + Assert.NotNull(actualBytes); + Assert.NotEmpty(actualBytes); + Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); + foreach (ExifTag expectedProfileTag in expectedProfileTags) { - using var memStream = new MemoryStream(); - image.SaveAsJpeg(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); + IExifValue actualProfileValue = actualProfile.GetValueInternal(expectedProfileTag); + IExifValue expectedProfileValue = expectedProfile.GetValueInternal(expectedProfileTag); + Assert.Equal(expectedProfileValue.GetValue(), actualProfileValue.GetValue()); } + } - private static Image WriteAndReadPng(Image image) - { - using var memStream = new MemoryStream(); - image.SaveAsPng(memStream); - image.Dispose(); + private static ExifProfile CreateExifProfile() + { + var profile = new ExifProfile(); - memStream.Position = 0; - return Image.Load(memStream); + foreach (KeyValuePair exifProfileValue in TestProfileValues) + { + profile.SetValueInternal(exifProfileValue.Key, exifProfileValue.Value); } - private static Image WriteAndReadWebp(Image image, WebpFileFormatType fileFormat) - { - using var memStream = new MemoryStream(); - image.SaveAsWebp(memStream, new WebpEncoder() { FileFormat = fileFormat }); - image.Dispose(); + return profile; + } - memStream.Position = 0; - return Image.Load(memStream); - } + [Fact] + public void IfdStructure() + { + var exif = new ExifProfile(); + exif.SetValue(ExifTag.XPAuthor, "Dan Petitt"); + + Span actualBytes = exif.ToByteArray(); + + // Assert + int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; + Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4))); - private static void TestProfile(ExifProfile profile) + int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; + Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(nextIfdPointerOffset, 4))); + } + + internal static ExifProfile GetExifProfile() + { + using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); + + ExifProfile profile = image.Metadata.ExifProfile; + Assert.NotNull(profile); + + return profile; + } + + private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) + { + switch (imageFormat) { - Assert.NotNull(profile); + case TestImageWriteFormat.Jpeg: + return WriteAndReadJpeg(image); + case TestImageWriteFormat.Png: + return WriteAndReadPng(image); + case TestImageWriteFormat.WebpLossless: + return WriteAndReadWebp(image, WebpFileFormatType.Lossless); + case TestImageWriteFormat.WebpLossy: + return WriteAndReadWebp(image, WebpFileFormatType.Lossy); + default: + throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); + } + } - // todo: duplicate tags - Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932)); + private static Image WriteAndReadJpeg(Image image) + { + using var memStream = new MemoryStream(); + image.SaveAsJpeg(memStream); + image.Dispose(); - Assert.Equal(18, profile.Values.Count); + memStream.Position = 0; + return Image.Load(memStream); + } - foreach (IExifValue value in profile.Values) - { - Assert.NotNull(value.GetValue()); - } + private static Image WriteAndReadPng(Image image) + { + using var memStream = new MemoryStream(); + image.SaveAsPng(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + + private static Image WriteAndReadWebp(Image image, WebpFileFormatType fileFormat) + { + using var memStream = new MemoryStream(); + image.SaveAsWebp(memStream, new WebpEncoder() { FileFormat = fileFormat }); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + + private static void TestProfile(ExifProfile profile) + { + Assert.NotNull(profile); - IExifValue software = profile.GetValue(ExifTag.Software); - Assert.Equal("Windows Photo Editor 10.0.10011.16384", software.Value); + // todo: duplicate tags + Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932)); - IExifValue xResolution = profile.GetValue(ExifTag.XResolution); - Assert.Equal(new Rational(300.0), xResolution.Value); + Assert.Equal(18, profile.Values.Count); - IExifValue xDimension = profile.GetValue(ExifTag.PixelXDimension); - Assert.Equal(2338U, xDimension.Value); + foreach (IExifValue value in profile.Values) + { + Assert.NotNull(value.GetValue()); } + + IExifValue software = profile.GetValue(ExifTag.Software); + Assert.Equal("Windows Photo Editor 10.0.10011.16384", software.Value); + + IExifValue xResolution = profile.GetValue(ExifTag.XResolution); + Assert.Equal(new Rational(300.0), xResolution.Value); + + IExifValue xDimension = profile.GetValue(ExifTag.PixelXDimension); + Assert.Equal(2338U, xDimension.Value); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs index 479a719dff..8fe19b37de 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs @@ -1,34 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; + +[Trait("Profile", "Exif")] +public class ExifReaderTests { - [Trait("Profile", "Exif")] - public class ExifReaderTests + [Fact] + public void Read_DataIsEmpty_ReturnsEmptyCollection() { - [Fact] - public void Read_DataIsEmpty_ReturnsEmptyCollection() - { - var reader = new ExifReader(Array.Empty()); + var reader = new ExifReader(Array.Empty()); - IList result = reader.ReadValues(); + IList result = reader.ReadValues(); - Assert.Equal(0, result.Count); - } + Assert.Equal(0, result.Count); + } - [Fact] - public void Read_DataIsMinimal_ReturnsEmptyCollection() - { - var reader = new ExifReader(new byte[] { 69, 120, 105, 102, 0, 0 }); + [Fact] + public void Read_DataIsMinimal_ReturnsEmptyCollection() + { + var reader = new ExifReader(new byte[] { 69, 120, 105, 102, 0, 0 }); - IList result = reader.ReadValues(); + IList result = reader.ReadValues(); - Assert.Equal(0, result.Count); - } + Assert.Equal(0, result.Count); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index 08b8fab35f..d2198f9cae 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -2,37 +2,35 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; + +[Trait("Profile", "Exif")] +public class ExifTagDescriptionAttributeTests { - [Trait("Profile", "Exif")] - public class ExifTagDescriptionAttributeTests + [Fact] + public void TestExifTag() { - [Fact] - public void TestExifTag() - { - var exifProfile = new ExifProfile(); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); - IExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("None", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Inches", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("Centimeter", value.ToString()); - - exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); - value = exifProfile.GetValue(ExifTag.ResolutionUnit); - Assert.Equal("4", value.ToString()); - - exifProfile.SetValue(ExifTag.ImageWidth, 123U); - value = exifProfile.GetValue(ExifTag.ImageWidth); - Assert.Equal("123", value.ToString()); - } + var exifProfile = new ExifProfile(); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); + IExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("None", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)2); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Inches", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)3); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("Centimeter", value.ToString()); + + exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)4); + value = exifProfile.GetValue(ExifTag.ResolutionUnit); + Assert.Equal("4", value.ToString()); + + exifProfile.SetValue(ExifTag.ImageWidth, 123U); + value = exifProfile.GetValue(ExifTag.ImageWidth); + Assert.Equal("123", value.ToString()); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs index 63ed58ecaf..548329250e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifValueTests.cs @@ -3,52 +3,50 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif; + +[Trait("Profile", "Exif")] +public class ExifValueTests { - [Trait("Profile", "Exif")] - public class ExifValueTests - { - private readonly ExifProfile profile; + private readonly ExifProfile profile; - public ExifValueTests() + public ExifValueTests() + { + using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) { - using (Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image()) - { - this.profile = image.Metadata.ExifProfile; - } + this.profile = image.Metadata.ExifProfile; } + } - private IExifValue GetExifValue() - { - Assert.NotNull(this.profile); + private IExifValue GetExifValue() + { + Assert.NotNull(this.profile); - return this.profile.GetValue(ExifTag.Software); - } + return this.profile.GetValue(ExifTag.Software); + } - [Fact] - public void IEquatable() - { - IExifValue first = this.GetExifValue(); - IExifValue second = this.GetExifValue(); + [Fact] + public void IEquatable() + { + IExifValue first = this.GetExifValue(); + IExifValue second = this.GetExifValue(); - Assert.True(first == second); - Assert.True(first.Equals(second)); - } + Assert.True(first == second); + Assert.True(first.Equals(second)); + } - [Fact] - public void Properties() - { - IExifValue value = this.GetExifValue(); + [Fact] + public void Properties() + { + IExifValue value = this.GetExifValue(); - Assert.Equal(ExifDataType.Ascii, value.DataType); - Assert.Equal(ExifTag.Software, value.Tag); - Assert.False(value.IsArray); + Assert.Equal(ExifDataType.Ascii, value.DataType); + Assert.Equal(ExifTag.Software, value.Tag); + Assert.False(value.IsArray); - const string expected = "Windows Photo Editor 10.0.10011.16384"; - Assert.Equal(expected, value.ToString()); - Assert.Equal(expected, value.Value); - } + const string expected = "Windows Photo Editor 10.0.10011.16384"; + Assert.Equal(expected, value.ToString()); + Assert.Equal(expected, value.Value); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index ac5907547c..1c53bc9dd1 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -1,648 +1,645 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Text; using SixLabors.ImageSharp.Metadata.Profiles.Exif; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values; + +[Trait("Profile", "Exif")] +public class ExifValuesTests { - [Trait("Profile", "Exif")] - public class ExifValuesTests + public static TheoryData ByteTags => new TheoryData { - public static TheoryData ByteTags => new TheoryData - { - { ExifTag.FaxProfile }, - { ExifTag.ModeNumber }, - { ExifTag.GPSAltitudeRef } - }; + { ExifTag.FaxProfile }, + { ExifTag.ModeNumber }, + { ExifTag.GPSAltitudeRef } + }; - public static TheoryData ByteArrayTags => new TheoryData - { - { ExifTag.ClipPath }, - { ExifTag.VersionYear }, - { ExifTag.XMP }, - { ExifTag.CFAPattern2 }, - { ExifTag.TIFFEPStandardID }, - { ExifTag.GPSVersionID }, - }; - - public static TheoryData DoubleArrayTags => new TheoryData - { - { ExifTag.PixelScale }, - { ExifTag.IntergraphMatrix }, - { ExifTag.ModelTiePoint }, - { ExifTag.ModelTransform } - }; + public static TheoryData ByteArrayTags => new TheoryData + { + { ExifTag.ClipPath }, + { ExifTag.VersionYear }, + { ExifTag.XMP }, + { ExifTag.CFAPattern2 }, + { ExifTag.TIFFEPStandardID }, + { ExifTag.GPSVersionID }, + }; + + public static TheoryData DoubleArrayTags => new TheoryData + { + { ExifTag.PixelScale }, + { ExifTag.IntergraphMatrix }, + { ExifTag.ModelTiePoint }, + { ExifTag.ModelTransform } + }; - public static TheoryData LongTags => new TheoryData - { - { ExifTag.SubfileType }, - { ExifTag.SubIFDOffset }, - { ExifTag.GPSIFDOffset }, - { ExifTag.T4Options }, - { ExifTag.T6Options }, - { ExifTag.XClipPathUnits }, - { ExifTag.YClipPathUnits }, - { ExifTag.ProfileType }, - { ExifTag.CodingMethods }, - { ExifTag.T82ptions }, - { ExifTag.JPEGInterchangeFormat }, - { ExifTag.JPEGInterchangeFormatLength }, - { ExifTag.MDFileTag }, - { ExifTag.StandardOutputSensitivity }, - { ExifTag.RecommendedExposureIndex }, - { ExifTag.ISOSpeed }, - { ExifTag.ISOSpeedLatitudeyyy }, - { ExifTag.ISOSpeedLatitudezzz }, - { ExifTag.FaxRecvParams }, - { ExifTag.FaxRecvTime }, - { ExifTag.ImageNumber }, - }; - - public static TheoryData LongArrayTags => new TheoryData - { - { ExifTag.FreeOffsets }, - { ExifTag.FreeByteCounts }, - { ExifTag.ColorResponseUnit }, - { ExifTag.TileOffsets }, - { ExifTag.SMinSampleValue }, - { ExifTag.SMaxSampleValue }, - { ExifTag.JPEGQTables }, - { ExifTag.JPEGDCTables }, - { ExifTag.JPEGACTables }, - { ExifTag.StripRowCounts }, - { ExifTag.IntergraphRegisters }, - { ExifTag.TimeZoneOffset } - }; - - public static TheoryData NumberTags => new TheoryData - { - { ExifTag.ImageWidth }, - { ExifTag.ImageLength }, - { ExifTag.TileWidth }, - { ExifTag.TileLength }, - { ExifTag.BadFaxLines }, - { ExifTag.ConsecutiveBadFaxLines }, - { ExifTag.PixelXDimension }, - { ExifTag.PixelYDimension } - }; - - public static TheoryData NumberArrayTags => new TheoryData - { - { ExifTag.StripOffsets }, - { ExifTag.StripByteCounts }, - { ExifTag.TileByteCounts }, - { ExifTag.ImageLayer } - }; + public static TheoryData LongTags => new TheoryData + { + { ExifTag.SubfileType }, + { ExifTag.SubIFDOffset }, + { ExifTag.GPSIFDOffset }, + { ExifTag.T4Options }, + { ExifTag.T6Options }, + { ExifTag.XClipPathUnits }, + { ExifTag.YClipPathUnits }, + { ExifTag.ProfileType }, + { ExifTag.CodingMethods }, + { ExifTag.T82ptions }, + { ExifTag.JPEGInterchangeFormat }, + { ExifTag.JPEGInterchangeFormatLength }, + { ExifTag.MDFileTag }, + { ExifTag.StandardOutputSensitivity }, + { ExifTag.RecommendedExposureIndex }, + { ExifTag.ISOSpeed }, + { ExifTag.ISOSpeedLatitudeyyy }, + { ExifTag.ISOSpeedLatitudezzz }, + { ExifTag.FaxRecvParams }, + { ExifTag.FaxRecvTime }, + { ExifTag.ImageNumber }, + }; + + public static TheoryData LongArrayTags => new TheoryData + { + { ExifTag.FreeOffsets }, + { ExifTag.FreeByteCounts }, + { ExifTag.ColorResponseUnit }, + { ExifTag.TileOffsets }, + { ExifTag.SMinSampleValue }, + { ExifTag.SMaxSampleValue }, + { ExifTag.JPEGQTables }, + { ExifTag.JPEGDCTables }, + { ExifTag.JPEGACTables }, + { ExifTag.StripRowCounts }, + { ExifTag.IntergraphRegisters }, + { ExifTag.TimeZoneOffset } + }; + + public static TheoryData NumberTags => new TheoryData + { + { ExifTag.ImageWidth }, + { ExifTag.ImageLength }, + { ExifTag.TileWidth }, + { ExifTag.TileLength }, + { ExifTag.BadFaxLines }, + { ExifTag.ConsecutiveBadFaxLines }, + { ExifTag.PixelXDimension }, + { ExifTag.PixelYDimension } + }; + + public static TheoryData NumberArrayTags => new TheoryData + { + { ExifTag.StripOffsets }, + { ExifTag.StripByteCounts }, + { ExifTag.TileByteCounts }, + { ExifTag.ImageLayer } + }; - public static TheoryData RationalTags => new TheoryData - { - { ExifTag.XPosition }, - { ExifTag.YPosition }, - { ExifTag.XResolution }, - { ExifTag.YResolution }, - { ExifTag.BatteryLevel }, - { ExifTag.ExposureTime }, - { ExifTag.FNumber }, - { ExifTag.MDScalePixel }, - { ExifTag.CompressedBitsPerPixel }, - { ExifTag.ApertureValue }, - { ExifTag.MaxApertureValue }, - { ExifTag.SubjectDistance }, - { ExifTag.FocalLength }, - { ExifTag.FlashEnergy2 }, - { ExifTag.FocalPlaneXResolution2 }, - { ExifTag.FocalPlaneYResolution2 }, - { ExifTag.ExposureIndex2 }, - { ExifTag.Humidity }, - { ExifTag.Pressure }, - { ExifTag.Acceleration }, - { ExifTag.FlashEnergy }, - { ExifTag.FocalPlaneXResolution }, - { ExifTag.FocalPlaneYResolution }, - { ExifTag.ExposureIndex }, - { ExifTag.DigitalZoomRatio }, - { ExifTag.GPSAltitude }, - { ExifTag.GPSDOP }, - { ExifTag.GPSSpeed }, - { ExifTag.GPSTrack }, - { ExifTag.GPSImgDirection }, - { ExifTag.GPSDestBearing }, - { ExifTag.GPSDestDistance }, - }; - - public static TheoryData RationalArrayTags => new TheoryData - { - { ExifTag.WhitePoint }, - { ExifTag.PrimaryChromaticities }, - { ExifTag.YCbCrCoefficients }, - { ExifTag.ReferenceBlackWhite }, - { ExifTag.GPSLatitude }, - { ExifTag.GPSLongitude }, - { ExifTag.GPSTimestamp }, - { ExifTag.GPSDestLatitude }, - { ExifTag.GPSDestLongitude }, - { ExifTag.LensSpecification } - }; - - public static TheoryData ShortTags => new TheoryData - { - { ExifTag.OldSubfileType }, - { ExifTag.Compression }, - { ExifTag.PhotometricInterpretation }, - { ExifTag.Thresholding }, - { ExifTag.CellWidth }, - { ExifTag.CellLength }, - { ExifTag.FillOrder }, - { ExifTag.Orientation }, - { ExifTag.SamplesPerPixel }, - { ExifTag.PlanarConfiguration }, - { ExifTag.Predictor }, - { ExifTag.GrayResponseUnit }, - { ExifTag.ResolutionUnit }, - { ExifTag.CleanFaxData }, - { ExifTag.InkSet }, - { ExifTag.NumberOfInks }, - { ExifTag.DotRange }, - { ExifTag.Indexed }, - { ExifTag.OPIProxy }, - { ExifTag.JPEGProc }, - { ExifTag.JPEGRestartInterval }, - { ExifTag.YCbCrPositioning }, - { ExifTag.Rating }, - { ExifTag.RatingPercent }, - { ExifTag.ExposureProgram }, - { ExifTag.Interlace }, - { ExifTag.SelfTimerMode }, - { ExifTag.SensitivityType }, - { ExifTag.MeteringMode }, - { ExifTag.LightSource }, - { ExifTag.FocalPlaneResolutionUnit2 }, - { ExifTag.SensingMethod2 }, - { ExifTag.Flash }, - { ExifTag.ColorSpace }, - { ExifTag.FocalPlaneResolutionUnit }, - { ExifTag.SensingMethod }, - { ExifTag.CustomRendered }, - { ExifTag.ExposureMode }, - { ExifTag.WhiteBalance }, - { ExifTag.FocalLengthIn35mmFilm }, - { ExifTag.SceneCaptureType }, - { ExifTag.GainControl }, - { ExifTag.Contrast }, - { ExifTag.Saturation }, - { ExifTag.Sharpness }, - { ExifTag.SubjectDistanceRange }, - { ExifTag.GPSDifferential } - }; - - public static TheoryData ShortArrayTags => new TheoryData - { - { ExifTag.BitsPerSample }, - { ExifTag.MinSampleValue }, - { ExifTag.MaxSampleValue }, - { ExifTag.GrayResponseCurve }, - { ExifTag.ColorMap }, - { ExifTag.ExtraSamples }, - { ExifTag.PageNumber }, - { ExifTag.TransferFunction }, - { ExifTag.HalftoneHints }, - { ExifTag.SampleFormat }, - { ExifTag.TransferRange }, - { ExifTag.DefaultImageColor }, - { ExifTag.JPEGLosslessPredictors }, - { ExifTag.JPEGPointTransforms }, - { ExifTag.YCbCrSubsampling }, - { ExifTag.CFARepeatPatternDim }, - { ExifTag.IntergraphPacketData }, - { ExifTag.ISOSpeedRatings }, - { ExifTag.SubjectArea }, - { ExifTag.SubjectLocation } - }; - - public static TheoryData SignedRationalTags => new TheoryData - { - { ExifTag.ShutterSpeedValue }, - { ExifTag.BrightnessValue }, - { ExifTag.ExposureBiasValue }, - { ExifTag.AmbientTemperature }, - { ExifTag.WaterDepth }, - { ExifTag.CameraElevationAngle } - }; - - public static TheoryData SignedRationalArrayTags => new TheoryData - { - { ExifTag.Decode } - }; + public static TheoryData RationalTags => new TheoryData + { + { ExifTag.XPosition }, + { ExifTag.YPosition }, + { ExifTag.XResolution }, + { ExifTag.YResolution }, + { ExifTag.BatteryLevel }, + { ExifTag.ExposureTime }, + { ExifTag.FNumber }, + { ExifTag.MDScalePixel }, + { ExifTag.CompressedBitsPerPixel }, + { ExifTag.ApertureValue }, + { ExifTag.MaxApertureValue }, + { ExifTag.SubjectDistance }, + { ExifTag.FocalLength }, + { ExifTag.FlashEnergy2 }, + { ExifTag.FocalPlaneXResolution2 }, + { ExifTag.FocalPlaneYResolution2 }, + { ExifTag.ExposureIndex2 }, + { ExifTag.Humidity }, + { ExifTag.Pressure }, + { ExifTag.Acceleration }, + { ExifTag.FlashEnergy }, + { ExifTag.FocalPlaneXResolution }, + { ExifTag.FocalPlaneYResolution }, + { ExifTag.ExposureIndex }, + { ExifTag.DigitalZoomRatio }, + { ExifTag.GPSAltitude }, + { ExifTag.GPSDOP }, + { ExifTag.GPSSpeed }, + { ExifTag.GPSTrack }, + { ExifTag.GPSImgDirection }, + { ExifTag.GPSDestBearing }, + { ExifTag.GPSDestDistance }, + }; + + public static TheoryData RationalArrayTags => new TheoryData + { + { ExifTag.WhitePoint }, + { ExifTag.PrimaryChromaticities }, + { ExifTag.YCbCrCoefficients }, + { ExifTag.ReferenceBlackWhite }, + { ExifTag.GPSLatitude }, + { ExifTag.GPSLongitude }, + { ExifTag.GPSTimestamp }, + { ExifTag.GPSDestLatitude }, + { ExifTag.GPSDestLongitude }, + { ExifTag.LensSpecification } + }; + + public static TheoryData ShortTags => new TheoryData + { + { ExifTag.OldSubfileType }, + { ExifTag.Compression }, + { ExifTag.PhotometricInterpretation }, + { ExifTag.Thresholding }, + { ExifTag.CellWidth }, + { ExifTag.CellLength }, + { ExifTag.FillOrder }, + { ExifTag.Orientation }, + { ExifTag.SamplesPerPixel }, + { ExifTag.PlanarConfiguration }, + { ExifTag.Predictor }, + { ExifTag.GrayResponseUnit }, + { ExifTag.ResolutionUnit }, + { ExifTag.CleanFaxData }, + { ExifTag.InkSet }, + { ExifTag.NumberOfInks }, + { ExifTag.DotRange }, + { ExifTag.Indexed }, + { ExifTag.OPIProxy }, + { ExifTag.JPEGProc }, + { ExifTag.JPEGRestartInterval }, + { ExifTag.YCbCrPositioning }, + { ExifTag.Rating }, + { ExifTag.RatingPercent }, + { ExifTag.ExposureProgram }, + { ExifTag.Interlace }, + { ExifTag.SelfTimerMode }, + { ExifTag.SensitivityType }, + { ExifTag.MeteringMode }, + { ExifTag.LightSource }, + { ExifTag.FocalPlaneResolutionUnit2 }, + { ExifTag.SensingMethod2 }, + { ExifTag.Flash }, + { ExifTag.ColorSpace }, + { ExifTag.FocalPlaneResolutionUnit }, + { ExifTag.SensingMethod }, + { ExifTag.CustomRendered }, + { ExifTag.ExposureMode }, + { ExifTag.WhiteBalance }, + { ExifTag.FocalLengthIn35mmFilm }, + { ExifTag.SceneCaptureType }, + { ExifTag.GainControl }, + { ExifTag.Contrast }, + { ExifTag.Saturation }, + { ExifTag.Sharpness }, + { ExifTag.SubjectDistanceRange }, + { ExifTag.GPSDifferential } + }; + + public static TheoryData ShortArrayTags => new TheoryData + { + { ExifTag.BitsPerSample }, + { ExifTag.MinSampleValue }, + { ExifTag.MaxSampleValue }, + { ExifTag.GrayResponseCurve }, + { ExifTag.ColorMap }, + { ExifTag.ExtraSamples }, + { ExifTag.PageNumber }, + { ExifTag.TransferFunction }, + { ExifTag.HalftoneHints }, + { ExifTag.SampleFormat }, + { ExifTag.TransferRange }, + { ExifTag.DefaultImageColor }, + { ExifTag.JPEGLosslessPredictors }, + { ExifTag.JPEGPointTransforms }, + { ExifTag.YCbCrSubsampling }, + { ExifTag.CFARepeatPatternDim }, + { ExifTag.IntergraphPacketData }, + { ExifTag.ISOSpeedRatings }, + { ExifTag.SubjectArea }, + { ExifTag.SubjectLocation } + }; + + public static TheoryData SignedRationalTags => new TheoryData + { + { ExifTag.ShutterSpeedValue }, + { ExifTag.BrightnessValue }, + { ExifTag.ExposureBiasValue }, + { ExifTag.AmbientTemperature }, + { ExifTag.WaterDepth }, + { ExifTag.CameraElevationAngle } + }; + + public static TheoryData SignedRationalArrayTags => new TheoryData + { + { ExifTag.Decode } + }; - public static TheoryData StringTags => new TheoryData - { - { ExifTag.ImageDescription }, - { ExifTag.Make }, - { ExifTag.Model }, - { ExifTag.Software }, - { ExifTag.DateTime }, - { ExifTag.Artist }, - { ExifTag.HostComputer }, - { ExifTag.Copyright }, - { ExifTag.DocumentName }, - { ExifTag.PageName }, - { ExifTag.InkNames }, - { ExifTag.TargetPrinter }, - { ExifTag.ImageID }, - { ExifTag.MDLabName }, - { ExifTag.MDSampleInfo }, - { ExifTag.MDPrepDate }, - { ExifTag.MDPrepTime }, - { ExifTag.MDFileUnits }, - { ExifTag.SEMInfo }, - { ExifTag.SpectralSensitivity }, - { ExifTag.DateTimeOriginal }, - { ExifTag.DateTimeDigitized }, - { ExifTag.SubsecTime }, - { ExifTag.SubsecTimeOriginal }, - { ExifTag.SubsecTimeDigitized }, - { ExifTag.RelatedSoundFile }, - { ExifTag.FaxSubaddress }, - { ExifTag.OffsetTime }, - { ExifTag.OffsetTimeOriginal }, - { ExifTag.OffsetTimeDigitized }, - { ExifTag.SecurityClassification }, - { ExifTag.ImageHistory }, - { ExifTag.ImageUniqueID }, - { ExifTag.OwnerName }, - { ExifTag.SerialNumber }, - { ExifTag.LensMake }, - { ExifTag.LensModel }, - { ExifTag.LensSerialNumber }, - { ExifTag.GDALMetadata }, - { ExifTag.GDALNoData }, - { ExifTag.GPSLatitudeRef }, - { ExifTag.GPSLongitudeRef }, - { ExifTag.GPSSatellites }, - { ExifTag.GPSStatus }, - { ExifTag.GPSMeasureMode }, - { ExifTag.GPSSpeedRef }, - { ExifTag.GPSTrackRef }, - { ExifTag.GPSImgDirectionRef }, - { ExifTag.GPSMapDatum }, - { ExifTag.GPSDestLatitudeRef }, - { ExifTag.GPSDestLongitudeRef }, - { ExifTag.GPSDestBearingRef }, - { ExifTag.GPSDestDistanceRef }, - { ExifTag.GPSDateStamp }, - }; - - public static TheoryData UndefinedTags => new TheoryData - { - { ExifTag.FileSource }, - { ExifTag.SceneType } - }; + public static TheoryData StringTags => new TheoryData + { + { ExifTag.ImageDescription }, + { ExifTag.Make }, + { ExifTag.Model }, + { ExifTag.Software }, + { ExifTag.DateTime }, + { ExifTag.Artist }, + { ExifTag.HostComputer }, + { ExifTag.Copyright }, + { ExifTag.DocumentName }, + { ExifTag.PageName }, + { ExifTag.InkNames }, + { ExifTag.TargetPrinter }, + { ExifTag.ImageID }, + { ExifTag.MDLabName }, + { ExifTag.MDSampleInfo }, + { ExifTag.MDPrepDate }, + { ExifTag.MDPrepTime }, + { ExifTag.MDFileUnits }, + { ExifTag.SEMInfo }, + { ExifTag.SpectralSensitivity }, + { ExifTag.DateTimeOriginal }, + { ExifTag.DateTimeDigitized }, + { ExifTag.SubsecTime }, + { ExifTag.SubsecTimeOriginal }, + { ExifTag.SubsecTimeDigitized }, + { ExifTag.RelatedSoundFile }, + { ExifTag.FaxSubaddress }, + { ExifTag.OffsetTime }, + { ExifTag.OffsetTimeOriginal }, + { ExifTag.OffsetTimeDigitized }, + { ExifTag.SecurityClassification }, + { ExifTag.ImageHistory }, + { ExifTag.ImageUniqueID }, + { ExifTag.OwnerName }, + { ExifTag.SerialNumber }, + { ExifTag.LensMake }, + { ExifTag.LensModel }, + { ExifTag.LensSerialNumber }, + { ExifTag.GDALMetadata }, + { ExifTag.GDALNoData }, + { ExifTag.GPSLatitudeRef }, + { ExifTag.GPSLongitudeRef }, + { ExifTag.GPSSatellites }, + { ExifTag.GPSStatus }, + { ExifTag.GPSMeasureMode }, + { ExifTag.GPSSpeedRef }, + { ExifTag.GPSTrackRef }, + { ExifTag.GPSImgDirectionRef }, + { ExifTag.GPSMapDatum }, + { ExifTag.GPSDestLatitudeRef }, + { ExifTag.GPSDestLongitudeRef }, + { ExifTag.GPSDestBearingRef }, + { ExifTag.GPSDestDistanceRef }, + { ExifTag.GPSDateStamp }, + }; + + public static TheoryData UndefinedTags => new TheoryData + { + { ExifTag.FileSource }, + { ExifTag.SceneType } + }; - public static TheoryData UndefinedArrayTags => new TheoryData - { - { ExifTag.JPEGTables }, - { ExifTag.OECF }, - { ExifTag.ExifVersion }, - { ExifTag.ComponentsConfiguration }, - { ExifTag.MakerNote }, - { ExifTag.FlashpixVersion }, - { ExifTag.SpatialFrequencyResponse }, - { ExifTag.SpatialFrequencyResponse2 }, - { ExifTag.Noise }, - { ExifTag.CFAPattern }, - { ExifTag.DeviceSettingDescription }, - { ExifTag.ImageSourceData }, - }; - - public static TheoryData EncodedStringTags => new TheoryData - { - { ExifTag.UserComment }, - { ExifTag.GPSProcessingMethod }, - { ExifTag.GPSAreaInformation } - }; + public static TheoryData UndefinedArrayTags => new TheoryData + { + { ExifTag.JPEGTables }, + { ExifTag.OECF }, + { ExifTag.ExifVersion }, + { ExifTag.ComponentsConfiguration }, + { ExifTag.MakerNote }, + { ExifTag.FlashpixVersion }, + { ExifTag.SpatialFrequencyResponse }, + { ExifTag.SpatialFrequencyResponse2 }, + { ExifTag.Noise }, + { ExifTag.CFAPattern }, + { ExifTag.DeviceSettingDescription }, + { ExifTag.ImageSourceData }, + }; + + public static TheoryData EncodedStringTags => new TheoryData + { + { ExifTag.UserComment }, + { ExifTag.GPSProcessingMethod }, + { ExifTag.GPSAreaInformation } + }; - public static TheoryData Ucs2StringTags => new TheoryData - { - { ExifTag.XPTitle }, - { ExifTag.XPComment }, - { ExifTag.XPAuthor }, - { ExifTag.XPKeywords }, - { ExifTag.XPSubject }, - }; - - [Theory] - [MemberData(nameof(ByteTags))] - public void ExifByteTests(ExifTag tag) - { - const byte expected = byte.MaxValue; - ExifValue value = ExifValues.Create(tag); + public static TheoryData Ucs2StringTags => new TheoryData + { + { ExifTag.XPTitle }, + { ExifTag.XPComment }, + { ExifTag.XPAuthor }, + { ExifTag.XPKeywords }, + { ExifTag.XPSubject }, + }; + + [Theory] + [MemberData(nameof(ByteTags))] + public void ExifByteTests(ExifTag tag) + { + const byte expected = byte.MaxValue; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifByte)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(ByteArrayTags))] - public void ExifByteArrayTests(ExifTag tag) - { - byte[] expected = new[] { byte.MaxValue }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(ByteArrayTags))] + public void ExifByteArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifByteArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifByteArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(DoubleArrayTags))] - public void ExifDoubleArrayTests(ExifTag tag) - { - double[] expected = new[] { double.MaxValue }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(DoubleArrayTags))] + public void ExifDoubleArrayTests(ExifTag tag) + { + double[] expected = new[] { double.MaxValue }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifDoubleArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifDoubleArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(LongTags))] - public void ExifLongTests(ExifTag tag) - { - const uint expected = uint.MaxValue; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(LongTags))] + public void ExifLongTests(ExifTag tag) + { + const uint expected = uint.MaxValue; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifLong)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifLong)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(LongArrayTags))] - public void ExifLongArrayTests(ExifTag tag) - { - uint[] expected = new[] { uint.MaxValue }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(LongArrayTags))] + public void ExifLongArrayTests(ExifTag tag) + { + uint[] expected = new[] { uint.MaxValue }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifLongArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifLongArray)value; + Assert.Equal(expected, typed.Value); + } - [Fact] - public void NumberTests() - { - Number value1 = ushort.MaxValue; - Number value2 = ushort.MaxValue; - Assert.True(value1 == value2); + [Fact] + public void NumberTests() + { + Number value1 = ushort.MaxValue; + Number value2 = ushort.MaxValue; + Assert.True(value1 == value2); - value2 = short.MaxValue; - Assert.True(value1 != value2); + value2 = short.MaxValue; + Assert.True(value1 != value2); - value1 = -1; - value2 = -2; - Assert.True(value1 > value2); + value1 = -1; + value2 = -2; + Assert.True(value1 > value2); - value1 = -6; - Assert.True(value1 <= value2); + value1 = -6; + Assert.True(value1 <= value2); - value1 = 10; - value2 = 10; - Assert.True(value1 >= value2); + value1 = 10; + value2 = 10; + Assert.True(value1 >= value2); - Assert.True(value1.Equals(value2)); - Assert.True(value1.GetHashCode() == value2.GetHashCode()); + Assert.True(value1.Equals(value2)); + Assert.True(value1.GetHashCode() == value2.GetHashCode()); - value1 = 1; - Assert.False(value1.Equals(value2)); - } + value1 = 1; + Assert.False(value1.Equals(value2)); + } - [Theory] - [MemberData(nameof(NumberTags))] - public void ExifNumberTests(ExifTag tag) - { - Number expected = ushort.MaxValue; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(NumberTags))] + public void ExifNumberTests(ExifTag tag) + { + Number expected = ushort.MaxValue; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((uint)expected)); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((uint)expected)); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifNumber)value; - Assert.Equal(expected, typed.Value); + var typed = (ExifNumber)value; + Assert.Equal(expected, typed.Value); - typed.Value = ushort.MaxValue + 1; - Assert.True(expected < typed.Value); - } + typed.Value = ushort.MaxValue + 1; + Assert.True(expected < typed.Value); + } - [Theory] - [MemberData(nameof(NumberArrayTags))] - public void ExifNumberArrayTests(ExifTag tag) - { - Number[] expected = new[] { new Number(uint.MaxValue) }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(NumberArrayTags))] + public void ExifNumberArrayTests(ExifTag tag) + { + Number[] expected = new[] { new Number(uint.MaxValue) }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifNumberArray)value; - Assert.Equal(expected, typed.Value); + var typed = (ExifNumberArray)value; + Assert.Equal(expected, typed.Value); - Assert.True(value.TrySetValue(int.MaxValue)); - Assert.Equal(new[] { (Number)int.MaxValue }, value.GetValue()); + Assert.True(value.TrySetValue(int.MaxValue)); + Assert.Equal(new[] { (Number)int.MaxValue }, value.GetValue()); - Assert.True(value.TrySetValue(new[] { 1u, 2u, 5u })); - Assert.Equal(new[] { (Number)1u, (Number)2u, (Number)5u }, value.GetValue()); + Assert.True(value.TrySetValue(new[] { 1u, 2u, 5u })); + Assert.Equal(new[] { (Number)1u, (Number)2u, (Number)5u }, value.GetValue()); - Assert.True(value.TrySetValue(new[] { (short)1, (short)2, (short)5 })); - Assert.Equal(new[] { (Number)(short)1, (Number)(short)2, (Number)(short)5 }, value.GetValue()); - } + Assert.True(value.TrySetValue(new[] { (short)1, (short)2, (short)5 })); + Assert.Equal(new[] { (Number)(short)1, (Number)(short)2, (Number)(short)5 }, value.GetValue()); + } - [Theory] - [MemberData(nameof(RationalTags))] - public void ExifRationalTests(ExifTag tag) - { - var expected = new Rational(21, 42); - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(RationalTags))] + public void ExifRationalTests(ExifTag tag) + { + var expected = new Rational(21, 42); + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(new SignedRational(expected.ToDouble()))); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(new SignedRational(expected.ToDouble()))); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifRational)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifRational)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(RationalArrayTags))] - public void ExifRationalArrayTests(ExifTag tag) - { - Rational[] expected = new[] { new Rational(21, 42) }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(RationalArrayTags))] + public void ExifRationalArrayTests(ExifTag tag) + { + Rational[] expected = new[] { new Rational(21, 42) }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifRationalArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifRationalArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(ShortTags))] - public void ExifShortTests(ExifTag tag) - { - const ushort expected = (ushort)short.MaxValue; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(ShortTags))] + public void ExifShortTests(ExifTag tag) + { + const ushort expected = (ushort)short.MaxValue; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue((short)expected)); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue((short)expected)); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifShort)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifShort)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(ShortArrayTags))] - public void ExifShortArrayTests(ExifTag tag) - { - ushort[] expected = new[] { ushort.MaxValue }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(ShortArrayTags))] + public void ExifShortArrayTests(ExifTag tag) + { + ushort[] expected = new[] { ushort.MaxValue }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifShortArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifShortArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(SignedRationalTags))] - public void ExifSignedRationalTests(ExifTag tag) - { - var expected = new SignedRational(21, 42); - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(SignedRationalTags))] + public void ExifSignedRationalTests(ExifTag tag) + { + var expected = new SignedRational(21, 42); + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifSignedRational)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifSignedRational)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(SignedRationalArrayTags))] - public void ExifSignedRationalArrayTests(ExifTag tag) - { - SignedRational[] expected = new[] { new SignedRational(21, 42) }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(SignedRationalArrayTags))] + public void ExifSignedRationalArrayTests(ExifTag tag) + { + SignedRational[] expected = new[] { new SignedRational(21, 42) }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifSignedRationalArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifSignedRationalArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(StringTags))] - public void ExifStringTests(ExifTag tag) - { - const string expected = "ImageSharp"; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(StringTags))] + public void ExifStringTests(ExifTag tag) + { + const string expected = "ImageSharp"; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(0M)); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(0M)); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifString)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifString)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(UndefinedTags))] - public void ExifUndefinedTests(ExifTag tag) - { - const byte expected = byte.MaxValue; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(UndefinedTags))] + public void ExifUndefinedTests(ExifTag tag) + { + const byte expected = byte.MaxValue; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue((int)expected)); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue((int)expected)); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifByte)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifByte)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(UndefinedArrayTags))] - public void ExifUndefinedArrayTests(ExifTag tag) - { - byte[] expected = new[] { byte.MaxValue }; - ExifValue value = ExifValues.Create(tag); + [Theory] + [MemberData(nameof(UndefinedArrayTags))] + public void ExifUndefinedArrayTests(ExifTag tag) + { + byte[] expected = new[] { byte.MaxValue }; + ExifValue value = ExifValues.Create(tag); - Assert.False(value.TrySetValue(expected.ToString())); - Assert.True(value.TrySetValue(expected)); + Assert.False(value.TrySetValue(expected.ToString())); + Assert.True(value.TrySetValue(expected)); - var typed = (ExifByteArray)value; - Assert.Equal(expected, typed.Value); - } + var typed = (ExifByteArray)value; + Assert.Equal(expected, typed.Value); + } - [Theory] - [MemberData(nameof(EncodedStringTags))] - public void ExifEncodedStringTests(ExifTag tag) + [Theory] + [MemberData(nameof(EncodedStringTags))] + public void ExifEncodedStringTests(ExifTag tag) + { + foreach (object code in Enum.GetValues(typeof(EncodedString.CharacterCode))) { - foreach (object code in Enum.GetValues(typeof(EncodedString.CharacterCode))) - { - var charCode = (EncodedString.CharacterCode)code; - - Assert.Equal(ExifEncodedStringHelpers.CharacterCodeBytesLength, ExifEncodedStringHelpers.GetCodeBytes(charCode).Length); - - const string expectedText = "test string"; - var expected = new EncodedString(charCode, expectedText); - ExifValue value = ExifValues.Create(tag); - - Assert.False(value.TrySetValue(123)); - Assert.True(value.TrySetValue(expected)); + var charCode = (EncodedString.CharacterCode)code; - var typed = (ExifEncodedString)value; - Assert.Equal(expected, typed.Value); - Assert.Equal(expectedText, (string)typed.Value); - Assert.Equal(charCode, typed.Value.Code); - } - } + Assert.Equal(ExifEncodedStringHelpers.CharacterCodeBytesLength, ExifEncodedStringHelpers.GetCodeBytes(charCode).Length); - [Theory] - [MemberData(nameof(Ucs2StringTags))] - public void ExifUcs2StringTests(ExifTag tag) - { - const string expected = "Dan Petitt"; + const string expectedText = "test string"; + var expected = new EncodedString(charCode, expectedText); ExifValue value = ExifValues.Create(tag); Assert.False(value.TrySetValue(123)); Assert.True(value.TrySetValue(expected)); - var typed = (ExifUcs2String)value; - Assert.Equal(expected, typed.Value); - - Assert.True(value.TrySetValue(Encoding.GetEncoding("UCS-2").GetBytes(expected))); + var typed = (ExifEncodedString)value; Assert.Equal(expected, typed.Value); + Assert.Equal(expectedText, (string)typed.Value); + Assert.Equal(charCode, typed.Value.Code); } } + + [Theory] + [MemberData(nameof(Ucs2StringTags))] + public void ExifUcs2StringTests(ExifTag tag) + { + const string expected = "Dan Petitt"; + ExifValue value = ExifValues.Create(tag); + + Assert.False(value.TrySetValue(123)); + Assert.True(value.TrySetValue(expected)); + + var typed = (ExifUcs2String)value; + Assert.Equal(expected, typed.Value); + + Assert.True(value.TrySetValue(Encoding.GetEncoding("UCS-2").GetBytes(expected))); + Assert.Equal(expected, typed.Value); + } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs index 51fd264c8e..73ae46c2c9 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs @@ -2,82 +2,80 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderCurvesTests { - [Trait("Profile", "Icc")] - public class IccDataReaderCurvesTests + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) { - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) - { - IccDataReader reader = CreateReader(data); + IccDataReader reader = CreateReader(data); - IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); + IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) + { + IccDataReader reader = CreateReader(data); - IccResponseCurve output = reader.ReadResponseCurve(channelCount); + IccResponseCurve output = reader.ReadResponseCurve(channelCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) + { + IccDataReader reader = CreateReader(data); - IccParametricCurve output = reader.ReadParametricCurve(); + IccParametricCurve output = reader.ReadParametricCurve(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) + { + IccDataReader reader = CreateReader(data); - IccCurveSegment output = reader.ReadCurveSegment(); + IccCurveSegment output = reader.ReadCurveSegment(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) + { + IccDataReader reader = CreateReader(data); - IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); + IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) + { + IccDataReader reader = CreateReader(data); - IccSampledCurveElement output = reader.ReadSampledCurveElement(); + IccSampledCurveElement output = reader.ReadSampledCurveElement(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs index 327cd68e97..63585a3bd4 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs @@ -2,82 +2,80 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderLutTests { - [Trait("Profile", "Icc")] - public class IccDataReaderLutTests + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) { - [Theory] - [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 = CreateReader(data); - IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); + IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); - IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); + IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); - IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); + IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataReader reader = CreateReader(data); - IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); + IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut8(byte[] data, IccLut expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut8(byte[] data, IccLut expected) + { + IccDataReader reader = CreateReader(data); - IccLut output = reader.ReadLut8(); + IccLut output = reader.ReadLut8(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] - internal void ReadLut16(byte[] data, IccLut expected, int count) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void ReadLut16(byte[] data, IccLut expected, int count) + { + IccDataReader reader = CreateReader(data); - IccLut output = reader.ReadLut16(count); + IccLut output = reader.ReadLut16(count); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs index a9ea9bade4..b81395bb2e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs @@ -2,38 +2,36 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderMatrixTests { - [Trait("Profile", "Icc")] - public class IccDataReaderMatrixTests + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) { - [Theory] - [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 = CreateReader(data); - float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); + float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) + { + IccDataReader reader = CreateReader(data); - float[] output = reader.ReadMatrix(yCount, isSingle); + float[] output = reader.ReadMatrix(yCount, isSingle); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs index 87d96aa2fb..9023b1b723 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs @@ -2,60 +2,58 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderMultiProcessElementTests { - [Trait("Profile", "Icc")] - public class IccDataReaderMultiProcessElementTests + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) { - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) - { - IccDataReader reader = CreateReader(data); + IccDataReader reader = CreateReader(data); - IccMultiProcessElement output = reader.ReadMultiProcessElement(); + IccMultiProcessElement output = reader.ReadMultiProcessElement(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); - IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); + IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); - IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); + IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) + { + IccDataReader reader = CreateReader(data); - IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); + IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs index e85192ce53..91294a3dab 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs @@ -1,129 +1,126 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderNonPrimitivesTests { - [Trait("Profile", "Icc")] - public class IccDataReaderNonPrimitivesTests + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadDateTime(byte[] data, DateTime expected) { - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadDateTime(byte[] data, DateTime expected) - { - IccDataReader reader = CreateReader(data); + IccDataReader reader = CreateReader(data); - DateTime output = reader.ReadDateTime(); + DateTime output = reader.ReadDateTime(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadVersionNumber(byte[] data, IccVersion expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadVersionNumber(byte[] data, IccVersion expected) + { + IccDataReader reader = CreateReader(data); - IccVersion output = reader.ReadVersionNumber(); + IccVersion output = reader.ReadVersionNumber(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void ReadXyzNumber(byte[] data, Vector3 expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void ReadXyzNumber(byte[] data, Vector3 expected) + { + IccDataReader reader = CreateReader(data); - Vector3 output = reader.ReadXyzNumber(); + Vector3 output = reader.ReadXyzNumber(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileId(byte[] data, IccProfileId expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileId(byte[] data, IccProfileId expected) + { + IccDataReader reader = CreateReader(data); - IccProfileId output = reader.ReadProfileId(); + IccProfileId output = reader.ReadProfileId(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) + { + IccDataReader reader = CreateReader(data); - IccPositionNumber output = reader.ReadPositionNumber(); + IccPositionNumber output = reader.ReadPositionNumber(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) + { + IccDataReader reader = CreateReader(data); - IccResponseNumber output = reader.ReadResponseNumber(); + IccResponseNumber output = reader.ReadResponseNumber(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) + { + IccDataReader reader = CreateReader(data); - IccNamedColor output = reader.ReadNamedColor(coordinateCount); + IccNamedColor output = reader.ReadNamedColor(coordinateCount); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) + { + IccDataReader reader = CreateReader(data); - IccProfileDescription output = reader.ReadProfileDescription(); + IccProfileDescription output = reader.ReadProfileDescription(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) + { + IccDataReader reader = CreateReader(data); - IccColorantTableEntry output = reader.ReadColorantTableEntry(); + IccColorantTableEntry output = reader.ReadColorantTableEntry(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) + { + IccDataReader reader = CreateReader(data); - IccScreeningChannel output = reader.ReadScreeningChannel(); + IccScreeningChannel output = reader.ReadScreeningChannel(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs index 9cb18baa17..b6135cd197 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs @@ -1,89 +1,86 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderPrimitivesTests { - [Trait("Profile", "Icc")] - public class IccDataReaderPrimitivesTests + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadAsciiString(byte[] textBytes, int length, string expected) { - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadAsciiString(byte[] textBytes, int length, string expected) - { - IccDataReader reader = CreateReader(textBytes); + IccDataReader reader = CreateReader(textBytes); - string output = reader.ReadAsciiString(length); + string output = reader.ReadAsciiString(length); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Fact] - public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); + [Fact] + public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); - Assert.Throws(() => reader.ReadAsciiString(-1)); - } + Assert.Throws(() => reader.ReadAsciiString(-1)); + } - [Fact] - public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() - { - IccDataReader reader = CreateReader(new byte[4]); + [Fact] + public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() + { + IccDataReader reader = CreateReader(new byte[4]); - Assert.Throws(() => reader.ReadUnicodeString(-1)); - } + Assert.Throws(() => reader.ReadUnicodeString(-1)); + } - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); - float output = reader.ReadFix16(); + float output = reader.ReadFix16(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix16(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix16(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); - float output = reader.ReadUFix16(); + float output = reader.ReadUFix16(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadU1Fix15(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadU1Fix15(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); - float output = reader.ReadU1Fix15(); + float output = reader.ReadU1Fix15(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void ReadUFix8(byte[] data, float expected) - { - IccDataReader reader = CreateReader(data); + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void ReadUFix8(byte[] data, float expected) + { + IccDataReader reader = CreateReader(data); - float output = reader.ReadUFix8(); + float output = reader.ReadUFix8(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs index 4a9ab33be1..d41707b7ce 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs @@ -2,447 +2,445 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderTagDataEntryTests { - [Trait("Profile", "Icc")] - public class IccDataReaderTagDataEntryTests - { - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Read), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadProfileSequenceIdentifierTagDataEntry( - byte[] data, - IccProfileSequenceIdentifierTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) - { - IccDataReader reader = CreateReader(data); - - IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData( - nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), - MemberType = typeof(IccTestDataTagDataEntry))] - internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) - { - IccDataReader reader = CreateReader(data); - - IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); - - Assert.Equal(expected, output); - } - - private static IccDataReader CreateReader(byte[] data) - { - return new IccDataReader(data); - } + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUnknownTagDataEntry(byte[] data, IccUnknownTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUnknownTagDataEntry output = reader.ReadUnknownTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadChromaticityTagDataEntry(byte[] data, IccChromaticityTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccChromaticityTagDataEntry output = reader.ReadChromaticityTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantOrderTagDataEntry(byte[] data, IccColorantOrderTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantOrderTagDataEntry output = reader.ReadColorantOrderTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadColorantTableTagDataEntry(byte[] data, IccColorantTableTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccColorantTableTagDataEntry output = reader.ReadColorantTableTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCurveTagDataEntry(byte[] data, IccCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCurveTagDataEntry output = reader.ReadCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDataTagDataEntry(byte[] data, IccDataTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccDataTagDataEntry output = reader.ReadDataTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadDateTimeTagDataEntry(byte[] data, IccDateTimeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccDateTimeTagDataEntry output = reader.ReadDateTimeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut16TagDataEntry(byte[] data, IccLut16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut16TagDataEntry output = reader.ReadLut16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLut8TagDataEntry(byte[] data, IccLut8TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLut8TagDataEntry output = reader.ReadLut8TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutAToBTagDataEntry(byte[] data, IccLutAToBTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutAToBTagDataEntry output = reader.ReadLutAtoBTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadLutBToATagDataEntry(byte[] data, IccLutBToATagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccLutBToATagDataEntry output = reader.ReadLutBtoATagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMeasurementTagDataEntry(byte[] data, IccMeasurementTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMeasurementTagDataEntry output = reader.ReadMeasurementTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Read), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiLocalizedUnicodeTagDataEntry(byte[] data, IccMultiLocalizedUnicodeTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiLocalizedUnicodeTagDataEntry output = reader.ReadMultiLocalizedUnicodeTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadMultiProcessElementsTagDataEntry(byte[] data, IccMultiProcessElementsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccMultiProcessElementsTagDataEntry output = reader.ReadMultiProcessElementsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadNamedColor2TagDataEntry(byte[] data, IccNamedColor2TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccNamedColor2TagDataEntry output = reader.ReadNamedColor2TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadParametricCurveTagDataEntry(byte[] data, IccParametricCurveTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccParametricCurveTagDataEntry output = reader.ReadParametricCurveTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceDescTagDataEntry(byte[] data, IccProfileSequenceDescTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceDescTagDataEntry output = reader.ReadProfileSequenceDescTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadProfileSequenceIdentifierTagDataEntry( + byte[] data, + IccProfileSequenceIdentifierTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccProfileSequenceIdentifierTagDataEntry output = reader.ReadProfileSequenceIdentifierTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadResponseCurveSet16TagDataEntry(byte[] data, IccResponseCurveSet16TagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccResponseCurveSet16TagDataEntry output = reader.ReadResponseCurveSet16TagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadFix16ArrayTagDataEntry(byte[] data, IccFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccFix16ArrayTagDataEntry output = reader.ReadFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadSignatureTagDataEntry(byte[] data, IccSignatureTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccSignatureTagDataEntry output = reader.ReadSignatureTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextTagDataEntry(byte[] data, IccTextTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccTextTagDataEntry output = reader.ReadTextTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUFix16ArrayTagDataEntry(byte[] data, IccUFix16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUFix16ArrayTagDataEntry output = reader.ReadUFix16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt16ArrayTagDataEntry(byte[] data, IccUInt16ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt16ArrayTagDataEntry output = reader.ReadUInt16ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt32ArrayTagDataEntry(byte[] data, IccUInt32ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt32ArrayTagDataEntry output = reader.ReadUInt32ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt64ArrayTagDataEntry(byte[] data, IccUInt64ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt64ArrayTagDataEntry output = reader.ReadUInt64ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUInt8ArrayTagDataEntry(byte[] data, IccUInt8ArrayTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUInt8ArrayTagDataEntry output = reader.ReadUInt8ArrayTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadViewingConditionsTagDataEntry(byte[] data, IccViewingConditionsTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccViewingConditionsTagDataEntry output = reader.ReadViewingConditionsTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadXyzTagDataEntry(byte[] data, IccXyzTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccXyzTagDataEntry output = reader.ReadXyzTagDataEntry(size); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadTextDescriptionTagDataEntry(byte[] data, IccTextDescriptionTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccTextDescriptionTagDataEntry output = reader.ReadTextDescriptionTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadCrdInfoTagDataEntry(byte[] data, IccCrdInfoTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccCrdInfoTagDataEntry output = reader.ReadCrdInfoTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadScreeningTagDataEntry(byte[] data, IccScreeningTagDataEntry expected) + { + IccDataReader reader = CreateReader(data); + + IccScreeningTagDataEntry output = reader.ReadScreeningTagDataEntry(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData( + nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), + MemberType = typeof(IccTestDataTagDataEntry))] + internal void ReadUcrBgTagDataEntry(byte[] data, IccUcrBgTagDataEntry expected, uint size) + { + IccDataReader reader = CreateReader(data); + + IccUcrBgTagDataEntry output = reader.ReadUcrBgTagDataEntry(size); + + Assert.Equal(expected, output); + } + + private static IccDataReader CreateReader(byte[] data) + { + return new IccDataReader(data); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs index 09ebd1c20d..f2150cc03a 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTests.cs @@ -1,19 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataReader; + +[Trait("Profile", "Icc")] +public class IccDataReaderTests { - [Trait("Profile", "Icc")] - public class IccDataReaderTests + [Fact] + public void ConstructorThrowsNullException() { - [Fact] - public void ConstructorThrowsNullException() - { - Assert.Throws(() => new IccDataReader(null)); - } + Assert.Throws(() => new IccDataReader(null)); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs index 1fbb1b5b08..1a23c8d002 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs @@ -2,88 +2,86 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterCurvesTests { - [Trait("Profile", "Icc")] - public class IccDataWriterCurvesTests + [Theory] + [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteOneDimensionalCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteResponseCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteParametricCurve(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteCurveSegment(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteFormulaCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] + internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteSampledCurveElement(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Theory] - [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteOneDimensionalCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteResponseCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteParametricCurve(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteCurveSegment(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteFormulaCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] - internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteSampledCurveElement(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs index f681d98098..4a3dc48bcb 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs @@ -2,88 +2,86 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterLutTests { - [Trait("Profile", "Icc")] - public class IccDataWriterLutTests + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs index 3910938589..1973d94b89 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs @@ -2,88 +2,86 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterLutTests1 { - [Trait("Profile", "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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + 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 index a42ef11037..4ffc9e0c36 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs @@ -2,88 +2,86 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterLutTests2 { - [Trait("Profile", "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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = 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) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Theory] - [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] - internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = 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) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteLut16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs index 451e5bfa23..7d046aa49b 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs @@ -3,76 +3,74 @@ using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterMatrixTests { - [Trait("Profile", "Icc")] - public class IccDataWriterMatrixTests + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] + internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] + public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteMatrix(data, isSingle); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] - internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] - public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteMatrix(data, isSingle); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs index 8f1f8a0d18..ba2add5eb9 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs @@ -2,64 +2,62 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterMultiProcessElementTests { - [Trait("Profile", "Icc")] - public class IccDataWriterMultiProcessElementTests + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) { - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) - { - using IccDataWriter writer = CreateWriter(); + using IccDataWriter writer = CreateWriter(); - writer.WriteMultiProcessElement(data); - byte[] output = writer.GetData(); + writer.WriteMultiProcessElement(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteCurveSetProcessElement(data); - byte[] output = writer.GetData(); + writer.WriteCurveSetProcessElement(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteMatrixProcessElement(data); - byte[] output = writer.GetData(); + writer.WriteMatrixProcessElement(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] - internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] + internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteClutProcessElement(data); - byte[] output = writer.GetData(); + writer.WriteClutProcessElement(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + private static IccDataWriter CreateWriter() + { + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs index 08b7455d26..b17ed44419 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs @@ -1,127 +1,124 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterNonPrimitivesTests { - [Trait("Profile", "Icc")] - public class IccDataWriterNonPrimitivesTests + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteDateTime(byte[] expected, DateTime data) { - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteDateTime(byte[] expected, DateTime data) - { - using IccDataWriter writer = CreateWriter(); + using IccDataWriter writer = CreateWriter(); + + writer.WriteDateTime(data); + byte[] output = writer.GetData(); - writer.WriteDateTime(data); - byte[] output = writer.GetData(); + Assert.Equal(expected, output); + } - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteVersionNumber(byte[] expected, IccVersion data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteVersionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - public void WriteXyzNumber(byte[] expected, Vector3 data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteXyzNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileId(byte[] expected, IccProfileId data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileId(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WritePositionNumber(byte[] expected, IccPositionNumber data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WritePositionNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteResponseNumber(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteNamedColor(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteProfileDescription(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] - internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteVersionNumber(byte[] expected, IccVersion data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteScreeningChannel(data); - byte[] output = writer.GetData(); + writer.WriteVersionNumber(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + public void WriteXyzNumber(byte[] expected, Vector3 data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteXyzNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileId(byte[] expected, IccProfileId data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteProfileId(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WritePositionNumber(byte[] expected, IccPositionNumber data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WritePositionNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteResponseNumber(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteNamedColor(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteProfileDescription(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] + internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteScreeningChannel(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() + { + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs index 275e825385..fbe8fe1ced 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -1,122 +1,119 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterPrimitivesTests { - [Trait("Profile", "Icc")] - public class IccDataWriterPrimitivesTests + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiString(byte[] expected, string data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteAsciiString(data, length, ensureNullTerminator); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Fact] + public void WriteAsciiStringWithNullWritesEmpty() { - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiString(byte[] expected, string data) - { - using IccDataWriter writer = CreateWriter(); + using IccDataWriter writer = CreateWriter(); - writer.WriteAsciiString(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteAsciiString(data, length, ensureNullTerminator); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Fact] - public void WriteAsciiStringWithNullWritesEmpty() - { - using IccDataWriter writer = CreateWriter(); - - int count = writer.WriteAsciiString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal(Array.Empty(), output); - } - - [Fact] - public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() - { - using IccDataWriter writer = CreateWriter(); - - Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); - } - - [Fact] - public void WriteUnicodeStringWithNullWritesEmpty() - { - using IccDataWriter writer = CreateWriter(); - - int count = writer.WriteUnicodeString(null); - byte[] output = writer.GetData(); - - Assert.Equal(0, count); - Assert.Equal(Array.Empty(), output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteFix16(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix16(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUFix16(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteU1Fix15(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteU1Fix15(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] - public void WriteUFix8(byte[] expected, float data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteUFix8(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } + int count = writer.WriteAsciiString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } + + [Fact] + public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() + { + using IccDataWriter writer = CreateWriter(); + + Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); + } + + [Fact] + public void WriteUnicodeStringWithNullWritesEmpty() + { + using IccDataWriter writer = CreateWriter(); + + int count = writer.WriteUnicodeString(null); + byte[] output = writer.GetData(); + + Assert.Equal(0, count); + Assert.Equal(Array.Empty(), output); + } - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + [Theory] + [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteFix16(byte[] expected, float data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix16(byte[] expected, float data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteUFix16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteU1Fix15(byte[] expected, float data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteU1Fix15(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] + public void WriteUFix8(byte[] expected, float data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteUFix8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() + { + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs index ff6cbf29cb..7eda24c8cf 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs @@ -2,412 +2,410 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterTagDataEntryTests { - [Trait("Profile", "Icc")] - public class IccDataWriterTagDataEntryTests + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) { - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + using IccDataWriter writer = CreateWriter(); - writer.WriteUnknownTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUnknownTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteChromaticityTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteChromaticityTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteColorantOrderTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteColorantOrderTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteColorantTableTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteColorantTableTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteCurveTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteCurveTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteDataTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteDataTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteDateTimeTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteDateTimeTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteLut16TagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteLut16TagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteLut8TagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteLut8TagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteLutAtoBTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteLutAtoBTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteLutBtoATagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteLutBtoATagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteMeasurementTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteMeasurementTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteMultiLocalizedUnicodeTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteMultiLocalizedUnicodeTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteMultiProcessElementsTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteMultiProcessElementsTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteNamedColor2TagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteNamedColor2TagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteParametricCurveTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteParametricCurveTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteProfileSequenceDescTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteProfileSequenceDescTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteProfileSequenceIdentifierTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteProfileSequenceIdentifierTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteResponseCurveSet16TagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteResponseCurveSet16TagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteSignatureTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteSignatureTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteTextTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteTextTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUFix16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUFix16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUInt16ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUInt16ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUInt32ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUInt32ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUInt64ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUInt64ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUInt8ArrayTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUInt8ArrayTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteViewingConditionsTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteViewingConditionsTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteXyzTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteXyzTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteTextDescriptionTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteTextDescriptionTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteCrdInfoTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteCrdInfoTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteScreeningTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteScreeningTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - [Theory] - [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] - internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) - { - using IccDataWriter writer = CreateWriter(); + [Theory] + [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] + internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) + { + using IccDataWriter writer = CreateWriter(); - writer.WriteUcrBgTagDataEntry(data); - byte[] output = writer.GetData(); + writer.WriteUcrBgTagDataEntry(data); + byte[] output = writer.GetData(); - Assert.Equal(expected, output); - } + Assert.Equal(expected, output); + } - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + private static IccDataWriter CreateWriter() + { + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs index 918bcb1999..205941fcec 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -2,113 +2,111 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.DataWriter; + +[Trait("Profile", "Icc")] +public class IccDataWriterTests { - [Trait("Profile", "Icc")] - public class IccDataWriterTests + [Fact] + public void WriteEmpty() + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(4); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[4], output); + } + + [Theory] + [InlineData(1, 4)] + [InlineData(4, 4)] + public void WritePadding(int writePosition, int expectedLength) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteEmpty(writePosition); + writer.WritePadding(); + byte[] output = writer.GetData(); + + Assert.Equal(new byte[expectedLength], output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt8(byte[] data, byte[] expected) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt16(byte[] expected, ushort[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt16(byte[] expected, short[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt32(byte[] expected, uint[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayInt32(byte[] expected, int[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] + public void WriteArrayUInt64(byte[] expected, ulong[] data) + { + using IccDataWriter writer = CreateWriter(); + + writer.WriteArray(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private static IccDataWriter CreateWriter() { - [Fact] - public void WriteEmpty() - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(4); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[4], output); - } - - [Theory] - [InlineData(1, 4)] - [InlineData(4, 4)] - public void WritePadding(int writePosition, int expectedLength) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteEmpty(writePosition); - writer.WritePadding(); - byte[] output = writer.GetData(); - - Assert.Equal(new byte[expectedLength], output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt8(byte[] data, byte[] expected) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt16(byte[] expected, ushort[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt16(byte[] expected, short[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt32(byte[] expected, uint[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayInt32(byte[] expected, int[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - [Theory] - [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] - public void WriteArrayUInt64(byte[] expected, ulong[] data) - { - using IccDataWriter writer = CreateWriter(); - - writer.WriteArray(data); - byte[] output = writer.GetData(); - - Assert.Equal(expected, output); - } - - private static IccDataWriter CreateWriter() - { - return new IccDataWriter(); - } + return new IccDataWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs index 636b78917f..fbbea97fb3 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccProfileTests.cs @@ -1,45 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; + +[Trait("Profile", "Icc")] +public class IccProfileTests { - [Trait("Profile", "Icc")] - public class IccProfileTests + [Theory] + [MemberData(nameof(IccTestDataProfiles.ProfileIdTestData), MemberType = typeof(IccTestDataProfiles))] + public void CalculateHash_WithByteArray_CalculatesProfileHash(byte[] data, IccProfileId expected) { - [Theory] - [MemberData(nameof(IccTestDataProfiles.ProfileIdTestData), MemberType = typeof(IccTestDataProfiles))] - public void CalculateHash_WithByteArray_CalculatesProfileHash(byte[] data, IccProfileId expected) - { - IccProfileId result = IccProfile.CalculateHash(data); + IccProfileId result = IccProfile.CalculateHash(data); - Assert.Equal(expected, result); - } + Assert.Equal(expected, result); + } - [Fact] - public void CalculateHash_WithByteArray_DoesNotModifyData() - { - byte[] data = IccTestDataProfiles.Profile_Random_Array; - var copy = new byte[data.Length]; - Buffer.BlockCopy(data, 0, copy, 0, data.Length); + [Fact] + public void CalculateHash_WithByteArray_DoesNotModifyData() + { + byte[] data = IccTestDataProfiles.Profile_Random_Array; + var copy = new byte[data.Length]; + Buffer.BlockCopy(data, 0, copy, 0, data.Length); - IccProfile.CalculateHash(data); + IccProfile.CalculateHash(data); - Assert.Equal(data, copy); - } + Assert.Equal(data, copy); + } - [Theory] - [MemberData(nameof(IccTestDataProfiles.ProfileValidityTestData), MemberType = typeof(IccTestDataProfiles))] - public void CheckIsValid_WithProfiles_ReturnsValidity(byte[] data, bool expected) - { - var profile = new IccProfile(data); + [Theory] + [MemberData(nameof(IccTestDataProfiles.ProfileValidityTestData), MemberType = typeof(IccTestDataProfiles))] + public void CheckIsValid_WithProfiles_ReturnsValidity(byte[] data, bool expected) + { + var profile = new IccProfile(data); - bool result = profile.CheckIsValid(); + bool result = profile.CheckIsValid(); - Assert.Equal(expected, result); - } + Assert.Equal(expected, result); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs index 184a6c42e9..9b2ca2a275 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs @@ -2,58 +2,56 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; + +[Trait("Profile", "Icc")] +public class IccReaderTests { - [Trait("Profile", "Icc")] - public class IccReaderTests + [Fact] + public void ReadProfile_NoEntries() + { + IccReader reader = this.CreateReader(); + + IccProfile output = IccReader.Read(IccTestDataProfiles.Header_Random_Array); + + Assert.Equal(0, output.Entries.Length); + Assert.NotNull(output.Header); + + IccProfileHeader header = output.Header; + IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; + Assert.Equal(header.Class, expected.Class); + Assert.Equal(header.CmmType, expected.CmmType); + Assert.Equal(header.CreationDate, expected.CreationDate); + Assert.Equal(header.CreatorSignature, expected.CreatorSignature); + Assert.Equal(header.DataColorSpace, expected.DataColorSpace); + Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); + Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); + Assert.Equal(header.DeviceModel, expected.DeviceModel); + Assert.Equal(header.FileSignature, expected.FileSignature); + Assert.Equal(header.Flags, expected.Flags); + Assert.Equal(header.Id, expected.Id); + Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); + Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); + Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); + Assert.Equal(header.RenderingIntent, expected.RenderingIntent); + Assert.Equal(header.Size, expected.Size); + Assert.Equal(header.Version, expected.Version); + } + + [Fact] + public void ReadProfile_DuplicateEntry() + { + IccReader reader = this.CreateReader(); + + IccProfile output = IccReader.Read(IccTestDataProfiles.Profile_Random_Array); + + Assert.Equal(2, output.Entries.Length); + Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); + } + + private IccReader CreateReader() { - [Fact] - public void ReadProfile_NoEntries() - { - IccReader reader = this.CreateReader(); - - IccProfile output = IccReader.Read(IccTestDataProfiles.Header_Random_Array); - - Assert.Equal(0, output.Entries.Length); - Assert.NotNull(output.Header); - - IccProfileHeader header = output.Header; - IccProfileHeader expected = IccTestDataProfiles.Header_Random_Read; - Assert.Equal(header.Class, expected.Class); - Assert.Equal(header.CmmType, expected.CmmType); - Assert.Equal(header.CreationDate, expected.CreationDate); - Assert.Equal(header.CreatorSignature, expected.CreatorSignature); - Assert.Equal(header.DataColorSpace, expected.DataColorSpace); - Assert.Equal(header.DeviceAttributes, expected.DeviceAttributes); - Assert.Equal(header.DeviceManufacturer, expected.DeviceManufacturer); - Assert.Equal(header.DeviceModel, expected.DeviceModel); - Assert.Equal(header.FileSignature, expected.FileSignature); - Assert.Equal(header.Flags, expected.Flags); - Assert.Equal(header.Id, expected.Id); - Assert.Equal(header.PcsIlluminant, expected.PcsIlluminant); - Assert.Equal(header.PrimaryPlatformSignature, expected.PrimaryPlatformSignature); - Assert.Equal(header.ProfileConnectionSpace, expected.ProfileConnectionSpace); - Assert.Equal(header.RenderingIntent, expected.RenderingIntent); - Assert.Equal(header.Size, expected.Size); - Assert.Equal(header.Version, expected.Version); - } - - [Fact] - public void ReadProfile_DuplicateEntry() - { - IccReader reader = this.CreateReader(); - - IccProfile output = IccReader.Read(IccTestDataProfiles.Profile_Random_Array); - - Assert.Equal(2, output.Entries.Length); - Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); - } - - private IccReader CreateReader() - { - return new IccReader(); - } + return new IccReader(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs index 039d126630..89b63b7dcc 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs @@ -2,40 +2,38 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Icc; + +[Trait("Profile", "Icc")] +public class IccWriterTests { - [Trait("Profile", "Icc")] - public class IccWriterTests + [Fact] + public void WriteProfile_NoEntries() { - [Fact] - public void WriteProfile_NoEntries() - { - IccWriter writer = this.CreateWriter(); + IccWriter writer = this.CreateWriter(); - var profile = new IccProfile - { - Header = IccTestDataProfiles.Header_Random_Write - }; - byte[] output = IccWriter.Write(profile); + var profile = new IccProfile + { + Header = IccTestDataProfiles.Header_Random_Write + }; + byte[] output = IccWriter.Write(profile); - Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); - } + Assert.Equal(IccTestDataProfiles.Header_Random_Array, output); + } - [Fact] - public void WriteProfile_DuplicateEntry() - { - IccWriter writer = this.CreateWriter(); + [Fact] + public void WriteProfile_DuplicateEntry() + { + IccWriter writer = this.CreateWriter(); - byte[] output = IccWriter.Write(IccTestDataProfiles.Profile_Random_Val); + byte[] output = IccWriter.Write(IccTestDataProfiles.Profile_Random_Val); - Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); - } + Assert.Equal(IccTestDataProfiles.Profile_Random_Array, output); + } - private IccWriter CreateWriter() - { - return new IccWriter(); - } + private IccWriter CreateWriter() + { + return new IccWriter(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs index 25d834bc2b..968fa86c4e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccProfileIdTests.cs @@ -2,32 +2,30 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Metadata.Profiles.Icc; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various; + +[Trait("Profile", "Icc")] +public class IccProfileIdTests { - [Trait("Profile", "Icc")] - public class IccProfileIdTests + [Fact] + public void ZeroIsEqualToDefault() { - [Fact] - public void ZeroIsEqualToDefault() - { - Assert.True(IccProfileId.Zero.Equals(default)); + Assert.True(IccProfileId.Zero.Equals(default)); - Assert.False(default(IccProfileId).IsSet); - } + Assert.False(default(IccProfileId).IsSet); + } - [Fact] - public void SetIsTrueWhenNonDefaultValue() - { - var id = new IccProfileId(1, 2, 3, 4); + [Fact] + public void SetIsTrueWhenNonDefaultValue() + { + var id = new IccProfileId(1, 2, 3, 4); - Assert.True(id.IsSet); + Assert.True(id.IsSet); - Assert.Equal(1u, id.Part1); - Assert.Equal(2u, id.Part2); - Assert.Equal(3u, id.Part3); - Assert.Equal(4u, id.Part4); - } + Assert.Equal(1u, id.Part1); + Assert.Equal(2u, id.Part2); + Assert.Equal(3u, id.Part3); + Assert.Equal(4u, id.Part4); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 7d486f0af8..8606dae54f 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -1,407 +1,401 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC; + +public class IptcProfileTests { - public class IptcProfileTests - { - private static JpegDecoder JpegDecoder => new(); + private static JpegDecoder JpegDecoder => new(); - private static TiffDecoder TiffDecoder => new(); + private static TiffDecoder TiffDecoder => new(); - public static IEnumerable AllIptcTags() + public static IEnumerable AllIptcTags() + { + foreach (object tag in Enum.GetValues(typeof(IptcTag))) { - foreach (object tag in Enum.GetValues(typeof(IptcTag))) - { - yield return new object[] { tag }; - } + yield return new object[] { tag }; } + } - [Fact] - public void IptcProfile_WithUtf8Data_WritesEnvelopeRecord_Works() - { - // arrange - var profile = new IptcProfile(); - profile.SetValue(IptcTag.City, "ESPAÑA"); - profile.UpdateData(); - byte[] expectedEnvelopeData = { 28, 1, 90, 0, 3, 27, 37, 71 }; + [Fact] + public void IptcProfile_WithUtf8Data_WritesEnvelopeRecord_Works() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.City, "ESPAÑA"); + profile.UpdateData(); + byte[] expectedEnvelopeData = { 28, 1, 90, 0, 3, 27, 37, 71 }; - // act - byte[] profileBytes = profile.Data; + // act + byte[] profileBytes = profile.Data; - // assert - Assert.True(profileBytes.AsSpan(0, 8).SequenceEqual(expectedEnvelopeData)); - } + // assert + Assert.True(profileBytes.AsSpan(0, 8).SequenceEqual(expectedEnvelopeData)); + } - [Theory] - [MemberData(nameof(AllIptcTags))] - public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - string value = new('s', tag.MaxLength() + 1); - int expectedLength = tag.MaxLength(); + [Theory] + [MemberData(nameof(AllIptcTags))] + public void IptcProfile_SetValue_WithStrictEnabled_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + string value = new('s', tag.MaxLength() + 1); + int expectedLength = tag.MaxLength(); - // act - profile.SetValue(tag, value); + // act + profile.SetValue(tag, value); - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal(expectedLength, actual.Value.Length); - } + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal(expectedLength, actual.Value.Length); + } - [Theory] - [MemberData(nameof(AllIptcTags))] - public void IptcProfile_SetValue_WithStrictDisabled_Works(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - string value = new('s', tag.MaxLength() + 1); - int expectedLength = value.Length; + [Theory] + [MemberData(nameof(AllIptcTags))] + public void IptcProfile_SetValue_WithStrictDisabled_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + string value = new('s', tag.MaxLength() + 1); + int expectedLength = value.Length; - // act - profile.SetValue(tag, value, false); + // act + profile.SetValue(tag, value, false); - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal(expectedLength, actual.Value.Length); - } + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal(expectedLength, actual.Value.Length); + } - [Theory] - [InlineData(IptcTag.DigitalCreationDate)] - [InlineData(IptcTag.ExpirationDate)] - [InlineData(IptcTag.CreatedDate)] - [InlineData(IptcTag.ReferenceDate)] - [InlineData(IptcTag.ReleaseDate)] - public void IptcProfile_SetDateValue_Works(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - var datetime = new DateTimeOffset(new DateTime(1994, 3, 17)); + [Theory] + [InlineData(IptcTag.DigitalCreationDate)] + [InlineData(IptcTag.ExpirationDate)] + [InlineData(IptcTag.CreatedDate)] + [InlineData(IptcTag.ReferenceDate)] + [InlineData(IptcTag.ReleaseDate)] + public void IptcProfile_SetDateValue_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var datetime = new DateTimeOffset(new DateTime(1994, 3, 17)); - // act - profile.SetDateTimeValue(tag, datetime); + // act + profile.SetDateTimeValue(tag, datetime); - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal("19940317", actual.Value); - } + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal("19940317", actual.Value); + } - [Theory] - [InlineData(IptcTag.CreatedTime)] - [InlineData(IptcTag.DigitalCreationTime)] - [InlineData(IptcTag.ExpirationTime)] - [InlineData(IptcTag.ReleaseTime)] - public void IptcProfile_SetTimeValue_Works(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - var dateTimeUtc = new DateTime(1994, 3, 17, 14, 15, 16, DateTimeKind.Utc); - DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTimeUtc).ToOffset(TimeSpan.FromHours(2)); + [Theory] + [InlineData(IptcTag.CreatedTime)] + [InlineData(IptcTag.DigitalCreationTime)] + [InlineData(IptcTag.ExpirationTime)] + [InlineData(IptcTag.ReleaseTime)] + public void IptcProfile_SetTimeValue_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + var dateTimeUtc = new DateTime(1994, 3, 17, 14, 15, 16, DateTimeKind.Utc); + DateTimeOffset dateTimeOffset = new DateTimeOffset(dateTimeUtc).ToOffset(TimeSpan.FromHours(2)); - // act - profile.SetDateTimeValue(tag, dateTimeOffset); + // act + profile.SetDateTimeValue(tag, dateTimeOffset); - // assert - IptcValue actual = profile.GetValues(tag).First(); - Assert.Equal("161516+0200", actual.Value); - } + // assert + IptcValue actual = profile.GetValues(tag).First(); + Assert.Equal("161516+0200", actual.Value); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] - public void ReadIptcMetadata_FromJpg_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] + public void ReadIptcMetadata_FromJpg_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(JpegDecoder)) { - using (Image image = provider.GetImage(JpegDecoder)) - { - Assert.NotNull(image.Metadata.IptcProfile); - var iptcValues = image.Metadata.IptcProfile.Values.ToList(); - IptcProfileContainsExpectedValues(iptcValues); - } + Assert.NotNull(image.Metadata.IptcProfile); + var iptcValues = image.Metadata.IptcProfile.Values.ToList(); + IptcProfileContainsExpectedValues(iptcValues); } + } - [Theory] - [WithFile(TestImages.Tiff.IptcData, PixelTypes.Rgba32)] - public void ReadIptcMetadata_FromTiff_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Tiff.IptcData, PixelTypes.Rgba32)] + public void ReadIptcMetadata_FromTiff_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) { - using (Image image = provider.GetImage(TiffDecoder)) - { - IptcProfile iptc = image.Frames.RootFrame.Metadata.IptcProfile; - Assert.NotNull(iptc); - var iptcValues = iptc.Values.ToList(); - IptcProfileContainsExpectedValues(iptcValues); - } + IptcProfile iptc = image.Frames.RootFrame.Metadata.IptcProfile; + Assert.NotNull(iptc); + var iptcValues = iptc.Values.ToList(); + IptcProfileContainsExpectedValues(iptcValues); } + } - private static void IptcProfileContainsExpectedValues(List iptcValues) - { - ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); - ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); - ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); - ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); - ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); - ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); - ContainsIptcValue(iptcValues, IptcTag.Source, "source"); - ContainsIptcValue(iptcValues, IptcTag.Name, "title"); - ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); - ContainsIptcValue(iptcValues, IptcTag.City, "city"); - ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); - ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); - ContainsIptcValue(iptcValues, IptcTag.Country, "country"); - ContainsIptcValue(iptcValues, IptcTag.Category, "category"); - ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); - ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); - ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); - } + private static void IptcProfileContainsExpectedValues(List iptcValues) + { + ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); + ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); + ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); + ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); + ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); + ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); + ContainsIptcValue(iptcValues, IptcTag.Source, "source"); + ContainsIptcValue(iptcValues, IptcTag.Name, "title"); + ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); + ContainsIptcValue(iptcValues, IptcTag.City, "city"); + ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); + ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); + ContainsIptcValue(iptcValues, IptcTag.Country, "country"); + ContainsIptcValue(iptcValues, IptcTag.Category, "category"); + ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); + ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); + ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] - public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(JpegDecoder); - Assert.Null(image.Metadata.IptcProfile); - } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] + public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder); + Assert.Null(image.Metadata.IptcProfile); + } - [Fact] - public void IptcProfile_ToAndFromByteArray_Works() - { - // arrange - var profile = new IptcProfile(); - const string expectedCaptionWriter = "unittest"; - const string expectedCaption = "test"; - profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); - profile.SetValue(IptcTag.Caption, expectedCaption); - - // act - profile.UpdateData(); - byte[] profileBytes = profile.Data; - var profileFromBytes = new IptcProfile(profileBytes); - - // assert - var iptcValues = profileFromBytes.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); - ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); - } + [Fact] + public void IptcProfile_ToAndFromByteArray_Works() + { + // arrange + var profile = new IptcProfile(); + const string expectedCaptionWriter = "unittest"; + const string expectedCaption = "test"; + profile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + profile.SetValue(IptcTag.Caption, expectedCaption); + + // act + profile.UpdateData(); + byte[] profileBytes = profile.Data; + var profileFromBytes = new IptcProfile(profileBytes); + + // assert + var iptcValues = profileFromBytes.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } - [Fact] - public void IptcProfile_CloneIsDeep() - { - // arrange - var profile = new IptcProfile(); - const string captionWriter = "unittest"; - const string caption = "test"; - profile.SetValue(IptcTag.CaptionWriter, captionWriter); - profile.SetValue(IptcTag.Caption, caption); - - // act - IptcProfile clone = profile.DeepClone(); - clone.SetValue(IptcTag.Caption, "changed"); - - // assert - Assert.Equal(2, clone.Values.Count()); - var cloneValues = clone.Values.ToList(); - ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter); - ContainsIptcValue(cloneValues, IptcTag.Caption, "changed"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption); - } + [Fact] + public void IptcProfile_CloneIsDeep() + { + // arrange + var profile = new IptcProfile(); + const string captionWriter = "unittest"; + const string caption = "test"; + profile.SetValue(IptcTag.CaptionWriter, captionWriter); + profile.SetValue(IptcTag.Caption, caption); + + // act + IptcProfile clone = profile.DeepClone(); + clone.SetValue(IptcTag.Caption, "changed"); + + // assert + Assert.Equal(2, clone.Values.Count()); + var cloneValues = clone.Values.ToList(); + ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter); + ContainsIptcValue(cloneValues, IptcTag.Caption, "changed"); + ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption); + } - [Fact] - public void IptcValue_CloneIsDeep() - { - // arrange - var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true); + [Fact] + public void IptcValue_CloneIsDeep() + { + // arrange + var iptcValue = new IptcValue(IptcTag.Caption, System.Text.Encoding.UTF8, "test", true); - // act - IptcValue clone = iptcValue.DeepClone(); - clone.Value = "changed"; + // act + IptcValue clone = iptcValue.DeepClone(); + clone.Value = "changed"; - // assert - Assert.NotEqual(iptcValue.Value, clone.Value); - } + // assert + Assert.NotEqual(iptcValue.Value, clone.Value); + } - [Fact] - public void WritingImage_PreservesIptcProfile() - { - // arrange - var image = new Image(1, 1); - image.Metadata.IptcProfile = new IptcProfile(); - const string expectedCaptionWriter = "unittest"; - const string expectedCaption = "test"; - image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); - image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); - - // act - using Image reloadedImage = WriteAndReadJpeg(image); - - // assert - IptcProfile actual = reloadedImage.Metadata.IptcProfile; - Assert.NotNull(actual); - var iptcValues = actual.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); - ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); - } + [Fact] + public void WritingImage_PreservesIptcProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.IptcProfile = new IptcProfile(); + const string expectedCaptionWriter = "unittest"; + const string expectedCaption = "test"; + image.Metadata.IptcProfile.SetValue(IptcTag.CaptionWriter, expectedCaptionWriter); + image.Metadata.IptcProfile.SetValue(IptcTag.Caption, expectedCaption); + + // act + using Image reloadedImage = WriteAndReadJpeg(image); + + // assert + IptcProfile actual = reloadedImage.Metadata.IptcProfile; + Assert.NotNull(actual); + var iptcValues = actual.Values.ToList(); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); + ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); + } - [Theory] - [InlineData(IptcTag.ObjectAttribute)] - [InlineData(IptcTag.SubjectReference)] - [InlineData(IptcTag.SupplementalCategories)] - [InlineData(IptcTag.Keywords)] - [InlineData(IptcTag.LocationCode)] - [InlineData(IptcTag.LocationName)] - [InlineData(IptcTag.ReferenceService)] - [InlineData(IptcTag.ReferenceDate)] - [InlineData(IptcTag.ReferenceNumber)] - [InlineData(IptcTag.Byline)] - [InlineData(IptcTag.BylineTitle)] - [InlineData(IptcTag.Contact)] - [InlineData(IptcTag.LocalCaption)] - [InlineData(IptcTag.CaptionWriter)] - public void IptcProfile_AddRepeatable_Works(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - const string expectedValue1 = "test"; - const string expectedValue2 = "another one"; - profile.SetValue(tag, expectedValue1, false); - - // act - profile.SetValue(tag, expectedValue2, false); - - // assert - var values = profile.Values.ToList(); - Assert.Equal(2, values.Count); - ContainsIptcValue(values, tag, expectedValue1); - ContainsIptcValue(values, tag, expectedValue2); - } + [Theory] + [InlineData(IptcTag.ObjectAttribute)] + [InlineData(IptcTag.SubjectReference)] + [InlineData(IptcTag.SupplementalCategories)] + [InlineData(IptcTag.Keywords)] + [InlineData(IptcTag.LocationCode)] + [InlineData(IptcTag.LocationName)] + [InlineData(IptcTag.ReferenceService)] + [InlineData(IptcTag.ReferenceDate)] + [InlineData(IptcTag.ReferenceNumber)] + [InlineData(IptcTag.Byline)] + [InlineData(IptcTag.BylineTitle)] + [InlineData(IptcTag.Contact)] + [InlineData(IptcTag.LocalCaption)] + [InlineData(IptcTag.CaptionWriter)] + public void IptcProfile_AddRepeatable_Works(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + const string expectedValue1 = "test"; + const string expectedValue2 = "another one"; + profile.SetValue(tag, expectedValue1, false); + + // act + profile.SetValue(tag, expectedValue2, false); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(2, values.Count); + ContainsIptcValue(values, tag, expectedValue1); + ContainsIptcValue(values, tag, expectedValue2); + } - [Theory] - [InlineData(IptcTag.RecordVersion)] - [InlineData(IptcTag.ObjectType)] - [InlineData(IptcTag.Name)] - [InlineData(IptcTag.EditStatus)] - [InlineData(IptcTag.EditorialUpdate)] - [InlineData(IptcTag.Urgency)] - [InlineData(IptcTag.Category)] - [InlineData(IptcTag.FixtureIdentifier)] - [InlineData(IptcTag.ReleaseDate)] - [InlineData(IptcTag.ReleaseTime)] - [InlineData(IptcTag.ExpirationDate)] - [InlineData(IptcTag.ExpirationTime)] - [InlineData(IptcTag.SpecialInstructions)] - [InlineData(IptcTag.ActionAdvised)] - [InlineData(IptcTag.CreatedDate)] - [InlineData(IptcTag.CreatedTime)] - [InlineData(IptcTag.DigitalCreationDate)] - [InlineData(IptcTag.DigitalCreationTime)] - [InlineData(IptcTag.OriginatingProgram)] - [InlineData(IptcTag.ProgramVersion)] - [InlineData(IptcTag.ObjectCycle)] - [InlineData(IptcTag.City)] - [InlineData(IptcTag.SubLocation)] - [InlineData(IptcTag.ProvinceState)] - [InlineData(IptcTag.CountryCode)] - [InlineData(IptcTag.Country)] - [InlineData(IptcTag.OriginalTransmissionReference)] - [InlineData(IptcTag.Headline)] - [InlineData(IptcTag.Credit)] - [InlineData(IptcTag.CopyrightNotice)] - [InlineData(IptcTag.Caption)] - [InlineData(IptcTag.ImageType)] - [InlineData(IptcTag.ImageOrientation)] - public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag) - { - // arrange - var profile = new IptcProfile(); - const string expectedValue = "another one"; - profile.SetValue(tag, "test", false); - - // act - profile.SetValue(tag, expectedValue, false); - - // assert - var values = profile.Values.ToList(); - Assert.Equal(1, values.Count); - ContainsIptcValue(values, tag, expectedValue); - } + [Theory] + [InlineData(IptcTag.RecordVersion)] + [InlineData(IptcTag.ObjectType)] + [InlineData(IptcTag.Name)] + [InlineData(IptcTag.EditStatus)] + [InlineData(IptcTag.EditorialUpdate)] + [InlineData(IptcTag.Urgency)] + [InlineData(IptcTag.Category)] + [InlineData(IptcTag.FixtureIdentifier)] + [InlineData(IptcTag.ReleaseDate)] + [InlineData(IptcTag.ReleaseTime)] + [InlineData(IptcTag.ExpirationDate)] + [InlineData(IptcTag.ExpirationTime)] + [InlineData(IptcTag.SpecialInstructions)] + [InlineData(IptcTag.ActionAdvised)] + [InlineData(IptcTag.CreatedDate)] + [InlineData(IptcTag.CreatedTime)] + [InlineData(IptcTag.DigitalCreationDate)] + [InlineData(IptcTag.DigitalCreationTime)] + [InlineData(IptcTag.OriginatingProgram)] + [InlineData(IptcTag.ProgramVersion)] + [InlineData(IptcTag.ObjectCycle)] + [InlineData(IptcTag.City)] + [InlineData(IptcTag.SubLocation)] + [InlineData(IptcTag.ProvinceState)] + [InlineData(IptcTag.CountryCode)] + [InlineData(IptcTag.Country)] + [InlineData(IptcTag.OriginalTransmissionReference)] + [InlineData(IptcTag.Headline)] + [InlineData(IptcTag.Credit)] + [InlineData(IptcTag.CopyrightNotice)] + [InlineData(IptcTag.Caption)] + [InlineData(IptcTag.ImageType)] + [InlineData(IptcTag.ImageOrientation)] + public void IptcProfile_AddNoneRepeatable_DoesOverrideOldValue(IptcTag tag) + { + // arrange + var profile = new IptcProfile(); + const string expectedValue = "another one"; + profile.SetValue(tag, "test", false); + + // act + profile.SetValue(tag, expectedValue, false); + + // assert + var values = profile.Values.ToList(); + Assert.Equal(1, values.Count); + ContainsIptcValue(values, tag, expectedValue); + } - [Fact] - public void IptcProfile_RemoveByTag_RemovesAllEntrys() - { - // arrange - var profile = new IptcProfile(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); + [Fact] + public void IptcProfile_RemoveByTag_RemovesAllEntrys() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); - // act - bool result = profile.RemoveValue(IptcTag.Byline); + // act + bool result = profile.RemoveValue(IptcTag.Byline); - // assert - Assert.True(result, "removed result should be true"); - Assert.Empty(profile.Values); - } + // assert + Assert.True(result, "removed result should be true"); + Assert.Empty(profile.Values); + } - [Fact] - public void IptcProfile_RemoveByTagAndValue_Works() - { - // arrange - var profile = new IptcProfile(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); + [Fact] + public void IptcProfile_RemoveByTagAndValue_Works() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); - // act - bool result = profile.RemoveValue(IptcTag.Byline, "test2"); + // act + bool result = profile.RemoveValue(IptcTag.Byline, "test2"); - // assert - Assert.True(result, "removed result should be true"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test"); - } + // assert + Assert.True(result, "removed result should be true"); + ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test"); + } - [Fact] - public void IptcProfile_GetValue_RetrievesAllEntries() - { - // arrange - var profile = new IptcProfile(); - profile.SetValue(IptcTag.Byline, "test"); - profile.SetValue(IptcTag.Byline, "test2"); - profile.SetValue(IptcTag.Caption, "test"); - - // act - List result = profile.GetValues(IptcTag.Byline); - - // assert - Assert.NotNull(result); - Assert.Equal(2, result.Count); - } + [Fact] + public void IptcProfile_GetValue_RetrievesAllEntries() + { + // arrange + var profile = new IptcProfile(); + profile.SetValue(IptcTag.Byline, "test"); + profile.SetValue(IptcTag.Byline, "test2"); + profile.SetValue(IptcTag.Caption, "test"); + + // act + List result = profile.GetValues(IptcTag.Byline); + + // assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + } - private static void ContainsIptcValue(List values, IptcTag tag, string value) - { - Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}"); - Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'"); - } + private static void ContainsIptcValue(List values, IptcTag tag, string value) + { + Assert.True(values.Any(val => val.Tag == tag), $"Missing iptc tag {tag}"); + Assert.True(values.Contains(new IptcValue(tag, System.Text.Encoding.UTF8.GetBytes(value), false)), $"expected iptc value '{value}' was not found for tag '{tag}'"); + } - private static Image WriteAndReadJpeg(Image image) + private static Image WriteAndReadJpeg(Image image) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) - { - image.SaveAsJpeg(memStream); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } + image.SaveAsJpeg(memStream); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 8a40ad8ad1..8ef31a2af2 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using System.Text; -using System.Threading.Tasks; using System.Xml.Linq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; @@ -14,248 +11,246 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp -{ - public class XmpProfileTests - { - private static GifDecoder GifDecoder => new(); - - private static JpegDecoder JpegDecoder => new(); +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp; - private static PngDecoder PngDecoder => new(); +public class XmpProfileTests +{ + private static GifDecoder GifDecoder => new(); - private static TiffDecoder TiffDecoder => new(); + private static JpegDecoder JpegDecoder => new(); - private static WebpDecoder WebpDecoder => new(); + private static PngDecoder PngDecoder => new(); - [Theory] - [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromGif_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(GifDecoder)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } + private static TiffDecoder TiffDecoder => new(); - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Metadata, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.ExtendedXmp, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromJpg_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(JpegDecoder)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } + private static WebpDecoder WebpDecoder => new(); - [Theory] - [WithFile(TestImages.Png.XmpColorPalette, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromPng_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(PngDecoder)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromTiff_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = await provider.GetImageAsync(TiffDecoder)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } - } - - [Theory] - [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32)] - public async Task ReadXmpMetadata_FromWebp_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] + public async Task ReadXmpMetadata_FromGif_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(GifDecoder)) { - using (Image image = await provider.GetImageAsync(WebpDecoder)) - { - XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - } + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); } + } - [Fact] - public void XmpProfile_ToFromByteArray_ReturnsClone() + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Metadata, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.ExtendedXmp, PixelTypes.Rgba32)] + public async Task ReadXmpMetadata_FromJpg_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(JpegDecoder)) { - // arrange - XmpProfile profile = CreateMinimalXmlProfile(); - byte[] original = profile.ToByteArray(); - - // act - byte[] actual = profile.ToByteArray(); - - // assert - Assert.False(ReferenceEquals(original, actual)); + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); } + } - [Fact] - public void XmpProfile_CloneIsDeep() + [Theory] + [WithFile(TestImages.Png.XmpColorPalette, PixelTypes.Rgba32)] + public async Task ReadXmpMetadata_FromPng_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(PngDecoder)) { - // arrange - XmpProfile profile = CreateMinimalXmlProfile(); - byte[] original = profile.ToByteArray(); - - // act - XmpProfile clone = profile.DeepClone(); - byte[] actual = clone.ToByteArray(); - - // assert - Assert.False(ReferenceEquals(original, actual)); + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); } + } - [Fact] - public void WritingGif_PreservesXmpProfile() + [Theory] + [WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] + public async Task ReadXmpMetadata_FromTiff_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(TiffDecoder)) { - // arrange - var image = new Image(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - var encoder = new GifEncoder(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); } + } - [Fact] - public void WritingJpeg_PreservesXmpProfile() + [Theory] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32)] + public async Task ReadXmpMetadata_FromWebp_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(WebpDecoder)) { - // arrange - var image = new Image(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - var encoder = new JpegEncoder(); - - // act - using Image reloadedImage = WriteAndRead(image, encoder); - - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); } + } - [Fact] - public async Task WritingJpeg_PreservesExtendedXmpProfile() - { - // arrange - var provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp); - using Image image = await provider.GetImageAsync(JpegDecoder); - XmpProfile original = image.Metadata.XmpProfile; - var encoder = new JpegEncoder(); + [Fact] + public void XmpProfile_ToFromByteArray_ReturnsClone() + { + // arrange + XmpProfile profile = CreateMinimalXmlProfile(); + byte[] original = profile.ToByteArray(); - // act - using Image reloadedImage = WriteAndRead(image, encoder); + // act + byte[] actual = profile.ToByteArray(); - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } + // assert + Assert.False(ReferenceEquals(original, actual)); + } - [Fact] - public void WritingPng_PreservesXmpProfile() - { - // arrange - var image = new Image(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - var encoder = new PngEncoder(); + [Fact] + public void XmpProfile_CloneIsDeep() + { + // arrange + XmpProfile profile = CreateMinimalXmlProfile(); + byte[] original = profile.ToByteArray(); - // act - using Image reloadedImage = WriteAndRead(image, encoder); + // act + XmpProfile clone = profile.DeepClone(); + byte[] actual = clone.ToByteArray(); - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } + // assert + Assert.False(ReferenceEquals(original, actual)); + } - [Fact] - public void WritingTiff_PreservesXmpProfile() - { - // arrange - var image = new Image(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Frames.RootFrame.Metadata.XmpProfile = original; - var encoder = new TiffEncoder(); + [Fact] + public void WritingGif_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; + var encoder = new GifEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - // act - using Image reloadedImage = WriteAndRead(image, encoder); + [Fact] + public void WritingJpeg_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; + var encoder = new JpegEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } + [Fact] + public async Task WritingJpeg_PreservesExtendedXmpProfile() + { + // arrange + var provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp); + using Image image = await provider.GetImageAsync(JpegDecoder); + XmpProfile original = image.Metadata.XmpProfile; + var encoder = new JpegEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - [Fact] - public void WritingWebp_PreservesXmpProfile() - { - // arrange - var image = new Image(1, 1); - XmpProfile original = CreateMinimalXmlProfile(); - image.Metadata.XmpProfile = original; - var encoder = new WebpEncoder(); + [Fact] + public void WritingPng_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; + var encoder = new PngEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - // act - using Image reloadedImage = WriteAndRead(image, encoder); + [Fact] + public void WritingTiff_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + XmpProfile original = CreateMinimalXmlProfile(); + image.Frames.RootFrame.Metadata.XmpProfile = original; + var encoder = new TiffEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - // assert - XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.Data, actual.Data); - } + [Fact] + public void WritingWebp_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; + var encoder = new WebpEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original.Data, actual.Data); + } - private static void XmpProfileContainsExpectedValues(XmpProfile xmp) - { - Assert.NotNull(xmp); - XDocument document = xmp.GetDocument(); - Assert.NotNull(document); - Assert.Equal("xmpmeta", document.Root.Name.LocalName); - Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); - } + private static void XmpProfileContainsExpectedValues(XmpProfile xmp) + { + Assert.NotNull(xmp); + XDocument document = xmp.GetDocument(); + Assert.NotNull(document); + Assert.Equal("xmpmeta", document.Root.Name.LocalName); + Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); + } - private static XmpProfile CreateMinimalXmlProfile() - { - string content = $" "; - byte[] data = Encoding.UTF8.GetBytes(content); - var profile = new XmpProfile(data); - return profile; - } + private static XmpProfile CreateMinimalXmlProfile() + { + string content = $" "; + byte[] data = Encoding.UTF8.GetBytes(content); + var profile = new XmpProfile(data); + return profile; + } - private static Image WriteAndRead(Image image, IImageEncoder encoder) + private static Image WriteAndRead(Image image, IImageEncoder encoder) + { + using (var memStream = new MemoryStream()) { - using (var memStream = new MemoryStream()) - { - image.Save(memStream, encoder); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream); - } + image.Save(memStream, encoder); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); } } } diff --git a/tests/ImageSharp.Tests/Numerics/RationalTests.cs b/tests/ImageSharp.Tests/Numerics/RationalTests.cs index d88123561f..d165bd9d39 100644 --- a/tests/ImageSharp.Tests/Numerics/RationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/RationalTests.cs @@ -1,113 +1,110 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +/// +/// Tests the struct. +/// +public class RationalTests { /// - /// Tests the struct. + /// Tests the equality operators for equality. /// - public class RationalTests + [Fact] + public void AreEqual() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var r1 = new Rational(3, 2); - var r2 = new Rational(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - var r3 = new Rational(7.55); - var r4 = new Rational(755, 100); - var r5 = new Rational(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var first = new Rational(0, 100); - var second = new Rational(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } - - /// - /// Tests whether the Rational constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var rational = new Rational(7, 55); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(55U, rational.Denominator); - - rational = new Rational(755, 100); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(755, 100, false); - Assert.Equal(755U, rational.Numerator); - Assert.Equal(100U, rational.Denominator); - - rational = new Rational(-7.55); - Assert.Equal(151U, rational.Numerator); - Assert.Equal(20U, rational.Denominator); - - rational = new Rational(7); - Assert.Equal(7U, rational.Numerator); - Assert.Equal(1U, rational.Denominator); - } - - [Fact] - public void Fraction() - { - var first = new Rational(1.0 / 1600); - var second = new Rational(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - var rational = new Rational(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new Rational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresentation() - { - var rational = new Rational(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new Rational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(double.NegativeInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new Rational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new Rational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new Rational(1, 2); - Assert.Equal("1/2", rational.ToString()); - } + var r1 = new Rational(3, 2); + var r2 = new Rational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + var r3 = new Rational(7.55); + var r4 = new Rational(755, 100); + var r5 = new Rational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var first = new Rational(0, 100); + var second = new Rational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var rational = new Rational(7, 55); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(55U, rational.Denominator); + + rational = new Rational(755, 100); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(755, 100, false); + Assert.Equal(755U, rational.Numerator); + Assert.Equal(100U, rational.Denominator); + + rational = new Rational(-7.55); + Assert.Equal(151U, rational.Numerator); + Assert.Equal(20U, rational.Denominator); + + rational = new Rational(7); + Assert.Equal(7U, rational.Numerator); + Assert.Equal(1U, rational.Denominator); + } + + [Fact] + public void Fraction() + { + var first = new Rational(1.0 / 1600); + var second = new Rational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + var rational = new Rational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new Rational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresentation() + { + var rational = new Rational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new Rational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(double.NegativeInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new Rational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new Rational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new Rational(1, 2); + Assert.Equal("1/2", rational.ToString()); } } diff --git a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs index 71043b01dc..04183571b5 100644 --- a/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs +++ b/tests/ImageSharp.Tests/Numerics/SignedRationalTests.cs @@ -1,120 +1,117 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +/// +/// Tests the struct. +/// +public class SignedRationalTests { /// - /// Tests the struct. + /// Tests the equality operators for equality. /// - public class SignedRationalTests + [Fact] + public void AreEqual() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var r1 = new SignedRational(3, 2); - var r2 = new SignedRational(3, 2); - - Assert.Equal(r1, r2); - Assert.True(r1 == r2); - - var r3 = new SignedRational(7.55); - var r4 = new SignedRational(755, 100); - var r5 = new SignedRational(151, 20); - - Assert.Equal(r3, r4); - Assert.Equal(r4, r5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var first = new SignedRational(0, 100); - var second = new SignedRational(100, 100); - - Assert.NotEqual(first, second); - Assert.True(first != second); - } - - /// - /// Tests whether the Rational constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var rational = new SignedRational(7, -55); - Assert.Equal(7, rational.Numerator); - Assert.Equal(-55, rational.Denominator); - - rational = new SignedRational(-755, 100); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(-755, -100, false); - Assert.Equal(-755, rational.Numerator); - Assert.Equal(-100, rational.Denominator); - - rational = new SignedRational(-151, -20); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(-20, rational.Denominator); - - rational = new SignedRational(-7.55); - Assert.Equal(-151, rational.Numerator); - Assert.Equal(20, rational.Denominator); - - rational = new SignedRational(7); - Assert.Equal(7, rational.Numerator); - Assert.Equal(1, rational.Denominator); - } - - [Fact] - public void Fraction() - { - var first = new SignedRational(1.0 / 1600); - var second = new SignedRational(1.0 / 1600, true); - Assert.False(first.Equals(second)); - } - - [Fact] - public void ToDouble() - { - var rational = new SignedRational(0, 0); - Assert.Equal(double.NaN, rational.ToDouble()); - - rational = new SignedRational(2, 0); - Assert.Equal(double.PositiveInfinity, rational.ToDouble()); - - rational = new SignedRational(-2, 0); - Assert.Equal(double.NegativeInfinity, rational.ToDouble()); - } - - [Fact] - public void ToStringRepresentation() - { - var rational = new SignedRational(0, 0); - Assert.Equal("[ Indeterminate ]", rational.ToString()); - - rational = new SignedRational(double.PositiveInfinity); - Assert.Equal("[ PositiveInfinity ]", rational.ToString()); - - rational = new SignedRational(double.NegativeInfinity); - Assert.Equal("[ NegativeInfinity ]", rational.ToString()); - - rational = new SignedRational(0, 1); - Assert.Equal("0", rational.ToString()); - - rational = new SignedRational(2, 1); - Assert.Equal("2", rational.ToString()); - - rational = new SignedRational(1, 2); - Assert.Equal("1/2", rational.ToString()); - } + var r1 = new SignedRational(3, 2); + var r2 = new SignedRational(3, 2); + + Assert.Equal(r1, r2); + Assert.True(r1 == r2); + + var r3 = new SignedRational(7.55); + var r4 = new SignedRational(755, 100); + var r5 = new SignedRational(151, 20); + + Assert.Equal(r3, r4); + Assert.Equal(r4, r5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var first = new SignedRational(0, 100); + var second = new SignedRational(100, 100); + + Assert.NotEqual(first, second); + Assert.True(first != second); + } + + /// + /// Tests whether the Rational constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var rational = new SignedRational(7, -55); + Assert.Equal(7, rational.Numerator); + Assert.Equal(-55, rational.Denominator); + + rational = new SignedRational(-755, 100); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(-755, -100, false); + Assert.Equal(-755, rational.Numerator); + Assert.Equal(-100, rational.Denominator); + + rational = new SignedRational(-151, -20); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(-20, rational.Denominator); + + rational = new SignedRational(-7.55); + Assert.Equal(-151, rational.Numerator); + Assert.Equal(20, rational.Denominator); + + rational = new SignedRational(7); + Assert.Equal(7, rational.Numerator); + Assert.Equal(1, rational.Denominator); + } + + [Fact] + public void Fraction() + { + var first = new SignedRational(1.0 / 1600); + var second = new SignedRational(1.0 / 1600, true); + Assert.False(first.Equals(second)); + } + + [Fact] + public void ToDouble() + { + var rational = new SignedRational(0, 0); + Assert.Equal(double.NaN, rational.ToDouble()); + + rational = new SignedRational(2, 0); + Assert.Equal(double.PositiveInfinity, rational.ToDouble()); + + rational = new SignedRational(-2, 0); + Assert.Equal(double.NegativeInfinity, rational.ToDouble()); + } + + [Fact] + public void ToStringRepresentation() + { + var rational = new SignedRational(0, 0); + Assert.Equal("[ Indeterminate ]", rational.ToString()); + + rational = new SignedRational(double.PositiveInfinity); + Assert.Equal("[ PositiveInfinity ]", rational.ToString()); + + rational = new SignedRational(double.NegativeInfinity); + Assert.Equal("[ NegativeInfinity ]", rational.ToString()); + + rational = new SignedRational(0, 1); + Assert.Equal("0", rational.ToString()); + + rational = new SignedRational(2, 1); + Assert.Equal("2", rational.ToString()); + + rational = new SignedRational(1, 2); + Assert.Equal("1/2", rational.ToString()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs index 85c4a77e2e..afccdbd05d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/A8Tests.cs @@ -3,111 +3,109 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class A8Tests { - [Trait("Category", "PixelFormats")] - public class A8Tests + [Fact] + public void A8_Constructor() + { + // Test the limits. + Assert.Equal(byte.MinValue, new A8(0F).PackedValue); + Assert.Equal(byte.MaxValue, new A8(1F).PackedValue); + + // Test clamping. + Assert.Equal(byte.MinValue, new A8(-1234F).PackedValue); + Assert.Equal(byte.MaxValue, new A8(1234F).PackedValue); + + // Test ordering + Assert.Equal(124, new A8(124F / byte.MaxValue).PackedValue); + Assert.Equal(26, new A8(0.1F).PackedValue); + } + + [Fact] + public void A8_Equality() + { + var left = new A8(16); + var right = new A8(32); + + Assert.True(left == new A8(16)); + Assert.True(left != right); + Assert.Equal(left, (object)new A8(16)); + } + + [Fact] + public void A8_FromScaledVector4() + { + // Arrange + A8 alpha = default; + int expected = 128; + Vector4 scaled = new A8(.5F).ToScaledVector4(); + + // Act + alpha.FromScaledVector4(scaled); + byte actual = alpha.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_ToScaledVector4() { - [Fact] - public void A8_Constructor() - { - // Test the limits. - Assert.Equal(byte.MinValue, new A8(0F).PackedValue); - Assert.Equal(byte.MaxValue, new A8(1F).PackedValue); - - // Test clamping. - Assert.Equal(byte.MinValue, new A8(-1234F).PackedValue); - Assert.Equal(byte.MaxValue, new A8(1234F).PackedValue); - - // Test ordering - Assert.Equal(124, new A8(124F / byte.MaxValue).PackedValue); - Assert.Equal(26, new A8(0.1F).PackedValue); - } - - [Fact] - public void A8_Equality() - { - var left = new A8(16); - var right = new A8(32); - - Assert.True(left == new A8(16)); - Assert.True(left != right); - Assert.Equal(left, (object)new A8(16)); - } - - [Fact] - public void A8_FromScaledVector4() - { - // Arrange - A8 alpha = default; - int expected = 128; - Vector4 scaled = new A8(.5F).ToScaledVector4(); - - // Act - alpha.FromScaledVector4(scaled); - byte actual = alpha.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void A8_ToScaledVector4() - { - // Arrange - var alpha = new A8(.5F); - - // Act - Vector4 actual = alpha.ToScaledVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, 2); - } - - [Fact] - public void A8_ToVector4() - { - // Arrange - var alpha = new A8(.5F); - - // Act - var actual = alpha.ToVector4(); - - // Assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(.5F, actual.W, 2); - } - - [Fact] - public void A8_ToRgba32() - { - var input = new A8(128); - var expected = new Rgba32(0, 0, 0, 128); - - Rgba32 actual = default; - input.ToRgba32(ref actual); - Assert.Equal(expected, actual); - } - - [Fact] - public void A8_FromBgra5551() - { - // arrange - var alpha = default(A8); - byte expected = byte.MaxValue; - - // act - alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); - - // assert - Assert.Equal(expected, alpha.PackedValue); - } + // Arrange + var alpha = new A8(.5F); + + // Act + Vector4 actual = alpha.ToScaledVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void A8_ToVector4() + { + // Arrange + var alpha = new A8(.5F); + + // Act + var actual = alpha.ToVector4(); + + // Assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(.5F, actual.W, 2); + } + + [Fact] + public void A8_ToRgba32() + { + var input = new A8(128); + var expected = new Rgba32(0, 0, 0, 128); + + Rgba32 actual = default; + input.ToRgba32(ref actual); + Assert.Equal(expected, actual); + } + + [Fact] + public void A8_FromBgra5551() + { + // arrange + var alpha = default(A8); + byte expected = byte.MaxValue; + + // act + alpha.FromBgra5551(new Bgra5551(0.0f, 0.0f, 0.0f, 1.0f)); + + // assert + Assert.Equal(expected, alpha.PackedValue); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs index 9df548f982..13c84784cc 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs @@ -3,146 +3,144 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Abgr32Tests { - [Trait("Category", "PixelFormats")] - public class Abgr32Tests + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + var color1 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - Assert.Equal(color1, color2); - } + Assert.Equal(color1, color2); + } - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Abgr32(0, 0, byte.MaxValue, byte.MaxValue); - var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = - new() - { - { 1, 2, 3, 4 }, - { 4, 5, 6, 7 }, - { 0, 255, 42, 0 }, - { 1, 2, 3, 255 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte b, byte g, byte r, byte a) - { - var p = new Abgr32(r, g, b, a); + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Abgr32(0, 0, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - Assert.Equal(a, p.A); - } + Assert.NotEqual(color1, color2); + } - [Fact] - public unsafe void ByteLayoutIsSequentialBgra() - { - var color = new Abgr32(1, 2, 3, 4); - byte* ptr = (byte*)&color; - - Assert.Equal(4, ptr[0]); - Assert.Equal(3, ptr[1]); - Assert.Equal(2, ptr[2]); - Assert.Equal(1, ptr[3]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equality_WhenTrue(byte r, byte g, byte b, byte a) + public static readonly TheoryData ColorData = + new() { - var x = new Abgr32(r, g, b, a); - var y = new Abgr32(r, g, b, a); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] - [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] - [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] - [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] - public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) - { - var x = new Abgr32(r1, g1, b1, a1); - var y = new Abgr32(r2, g2, b2, a2); + { 1, 2, 3, 4 }, + { 4, 5, 6, 7 }, + { 0, 255, 42, 0 }, + { 1, 2, 3, 255 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte b, byte g, byte r, byte a) + { + var p = new Abgr32(r, g, b, a); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - } + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + Assert.Equal(a, p.A); + } - [Fact] - public void FromRgba32() - { - var abgr = default(Abgr32); - abgr.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, abgr.R); - Assert.Equal(2, abgr.G); - Assert.Equal(3, abgr.B); - Assert.Equal(4, abgr.A); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - var c = default(Abgr32); - c.FromVector4(Vec(1, 2, 3, 4)); + [Fact] + public unsafe void ByteLayoutIsSequentialBgra() + { + var color = new Abgr32(1, 2, 3, 4); + byte* ptr = (byte*)&color; - Assert.Equal(1, c.R); - Assert.Equal(2, c.G); - Assert.Equal(3, c.B); - Assert.Equal(4, c.A); - } + Assert.Equal(4, ptr[0]); + Assert.Equal(3, ptr[1]); + Assert.Equal(2, ptr[2]); + Assert.Equal(1, ptr[3]); + } - [Fact] - public void ToVector4() - { - var abgr = new Abgr32(1, 2, 3, 4); + [Theory] + [MemberData(nameof(ColorData))] + public void Equality_WhenTrue(byte r, byte g, byte b, byte a) + { + var x = new Abgr32(r, g, b, a); + var y = new Abgr32(r, g, b, a); - Assert.Equal(Vec(1, 2, 3, 4), abgr.ToVector4()); - } + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } - [Fact] - public void Abgr32_FromBgra5551() - { - // arrange - var abgr = default(Abgr32); - uint expected = uint.MaxValue; + [Theory] + [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] + [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] + [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] + [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] + public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) + { + var x = new Abgr32(r1, g1, b1, a1); + var y = new Abgr32(r2, g2, b2, a2); + + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + } + + [Fact] + public void FromRgba32() + { + var abgr = default(Abgr32); + abgr.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, abgr.R); + Assert.Equal(2, abgr.G); + Assert.Equal(3, abgr.B); + Assert.Equal(4, abgr.A); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var c = default(Abgr32); + c.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, c.R); + Assert.Equal(2, c.G); + Assert.Equal(3, c.B); + Assert.Equal(4, c.A); + } + + [Fact] + public void ToVector4() + { + var abgr = new Abgr32(1, 2, 3, 4); + + Assert.Equal(Vec(1, 2, 3, 4), abgr.ToVector4()); + } + + [Fact] + public void Abgr32_FromBgra5551() + { + // arrange + var abgr = default(Abgr32); + uint expected = uint.MaxValue; - // act - abgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + abgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, abgr.PackedValue); - } + // assert + Assert.Equal(expected, abgr.PackedValue); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index 7fdaa33dd5..70012afb02 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -1,147 +1,144 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Argb32Tests { - [Trait("Category", "PixelFormats")] - public class Argb32Tests + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Argb32(new Vector4(0.0f)); + var color3 = new Argb32(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Argb32(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Argb32(new Vector4(1.0f)); + var color3 = new Argb32(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Argb32(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new Argb32(1, .1f, .133f, .864f); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); + + var color2 = new Argb32(1, .1f, .133f); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); + + var color4 = new Argb32(new Vector3(1, .1f, .133f)); + Assert.Equal(255, color4.R); + Assert.Equal(Math.Round(.1f * 255), color4.G); + Assert.Equal(Math.Round(.133f * 255), color4.B); + Assert.Equal(255, color4.A); + + var color5 = new Argb32(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(255, color5.R); + Assert.Equal(Math.Round(.1f * 255), color5.G); + Assert.Equal(Math.Round(.133f * 255), color5.B); + Assert.Equal(Math.Round(.5f * 255), color5.A); + } + + [Fact] + public void Argb32_PackedValue() + { + Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + Assert.Equal(0x0U, new Argb32(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); + } + + [Fact] + public void Argb32_ToVector4() + { + Assert.Equal(Vector4.One, new Argb32(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Argb32(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Argb32(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Argb32(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Argb32(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Argb32(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Argb32_ToScaledVector4() + { + // arrange + var argb = new Argb32(Vector4.One); + + // act + Vector4 actual = argb.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Argb32_FromScaledVector4() + { + // arrange + Vector4 scaled = new Argb32(Vector4.One).ToScaledVector4(); + var pixel = default(Argb32); + uint expected = 0xFFFFFFFF; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Argb32_FromBgra5551() + { + // arrange + var argb = default(Argb32); + uint expected = uint.MaxValue; + + // act + argb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, argb.PackedValue); + } + + [Fact] + public void Argb32_Clamping() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Argb32(new Vector4(0.0f)); - var color3 = new Argb32(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new Argb32(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Argb32(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Argb32(new Vector4(1.0f)); - var color3 = new Argb32(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new Argb32(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var color1 = new Argb32(1, .1f, .133f, .864f); - Assert.Equal(255, color1.R); - Assert.Equal((byte)Math.Round(.1f * 255), color1.G); - Assert.Equal((byte)Math.Round(.133f * 255), color1.B); - Assert.Equal((byte)Math.Round(.864f * 255), color1.A); - - var color2 = new Argb32(1, .1f, .133f); - Assert.Equal(255, color2.R); - Assert.Equal(Math.Round(.1f * 255), color2.G); - Assert.Equal(Math.Round(.133f * 255), color2.B); - Assert.Equal(255, color2.A); - - var color4 = new Argb32(new Vector3(1, .1f, .133f)); - Assert.Equal(255, color4.R); - Assert.Equal(Math.Round(.1f * 255), color4.G); - Assert.Equal(Math.Round(.133f * 255), color4.B); - Assert.Equal(255, color4.A); - - var color5 = new Argb32(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(255, color5.R); - Assert.Equal(Math.Round(.1f * 255), color5.G); - Assert.Equal(Math.Round(.133f * 255), color5.B); - Assert.Equal(Math.Round(.5f * 255), color5.A); - } - - [Fact] - public void Argb32_PackedValue() - { - Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - Assert.Equal(0x0U, new Argb32(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); - } - - [Fact] - public void Argb32_ToVector4() - { - Assert.Equal(Vector4.One, new Argb32(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Argb32(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Argb32(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Argb32(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Argb32(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Argb32(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Argb32_ToScaledVector4() - { - // arrange - var argb = new Argb32(Vector4.One); - - // act - Vector4 actual = argb.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Argb32_FromScaledVector4() - { - // arrange - Vector4 scaled = new Argb32(Vector4.One).ToScaledVector4(); - var pixel = default(Argb32); - uint expected = 0xFFFFFFFF; - - // act - pixel.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Argb32_FromBgra5551() - { - // arrange - var argb = default(Argb32); - uint expected = uint.MaxValue; - - // act - argb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, argb.PackedValue); - } - - [Fact] - public void Argb32_Clamping() - { - Assert.Equal(Vector4.Zero, new Argb32(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Argb32(Vector4.One * +1234.0f).ToVector4()); - } + Assert.Equal(Vector4.Zero, new Argb32(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Argb32(Vector4.One * +1234.0f).ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index fda922d4bd..730e3996d9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -3,129 +3,127 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Bgr24Tests { - [Trait("Category", "PixelFormats")] - public class Bgr24Tests + [Fact] + public void AreEqual() + { + var color1 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() { - [Fact] - public void AreEqual() - { - var color1 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); - var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - var color1 = new Bgr24(byte.MaxValue, 0, 0); - var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = new() { { 1, 2, 3 }, { 4, 5, 6 }, { 0, 255, 42 } }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte r, byte g, byte b) - { - var p = new Rgb24(r, g, b); - - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - } - - [Fact] - public unsafe void ByteLayoutIsSequentialBgr() - { - var color = new Bgr24(1, 2, 3); - byte* ptr = (byte*)&color; - - Assert.Equal(3, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(1, ptr[2]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equals_WhenTrue(byte r, byte g, byte b) - { - var x = new Bgr24(r, g, b); - var y = new Bgr24(r, g, b); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 1, 2, 4)] - [InlineData(0, 255, 0, 0, 244, 0)] - [InlineData(1, 255, 0, 0, 255, 0)] - public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) - { - var a = new Bgr24(r1, g1, b1); - var b = new Bgr24(r2, g2, b2); - - Assert.False(a.Equals(b)); - Assert.False(a.Equals((object)b)); - } - - [Fact] - public void FromRgba32() - { - var rgb = default(Bgr24); - rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - var rgb = default(Bgr24); - rgb.FromVector4(Vec(1, 2, 3, 4)); - - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } - - [Fact] - public void ToVector4() - { - var rgb = new Bgr24(1, 2, 3); - - Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); - } - - [Fact] - public void Bgr24_FromBgra5551() - { - // arrange - var bgr = default(Bgr24); - - // act - bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(255, bgr.R); - Assert.Equal(255, bgr.G); - Assert.Equal(255, bgr.B); - } + var color1 = new Bgr24(byte.MaxValue, 0, 0); + var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); + + Assert.NotEqual(color1, color2); + } + + public static readonly TheoryData ColorData = new() { { 1, 2, 3 }, { 4, 5, 6 }, { 0, 255, 42 } }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte r, byte g, byte b) + { + var p = new Rgb24(r, g, b); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public unsafe void ByteLayoutIsSequentialBgr() + { + var color = new Bgr24(1, 2, 3); + byte* ptr = (byte*)&color; + + Assert.Equal(3, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(1, ptr[2]); + } + + [Theory] + [MemberData(nameof(ColorData))] + public void Equals_WhenTrue(byte r, byte g, byte b) + { + var x = new Bgr24(r, g, b); + var y = new Bgr24(r, g, b); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 1, 2, 4)] + [InlineData(0, 255, 0, 0, 244, 0)] + [InlineData(1, 255, 0, 0, 255, 0)] + public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) + { + var a = new Bgr24(r1, g1, b1); + var b = new Bgr24(r2, g2, b2); + + Assert.False(a.Equals(b)); + Assert.False(a.Equals((object)b)); + } + + [Fact] + public void FromRgba32() + { + var rgb = default(Bgr24); + rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var rgb = default(Bgr24); + rgb.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } + + [Fact] + public void ToVector4() + { + var rgb = new Bgr24(1, 2, 3); + + Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); + } + + [Fact] + public void Bgr24_FromBgra5551() + { + // arrange + var bgr = default(Bgr24); + + // act + bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(255, bgr.R); + Assert.Equal(255, bgr.G); + Assert.Equal(255, bgr.B); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs index 97eba56e40..8245a578f9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -3,251 +3,249 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Bgr565Tests { - [Trait("Category", "PixelFormats")] - public class Bgr565Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Bgr565(0.0f, 0.0f, 0.0f); - var color2 = new Bgr565(new Vector3(0.0f)); - var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 1.0f)); - var color4 = new Bgr565(1.0f, 0.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Bgr565(0.0f, 0.0f, 0.0f); - var color2 = new Bgr565(new Vector3(1.0f)); - var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 0.0f)); - var color4 = new Bgr565(1.0f, 1.0f, 0.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgr565_PackedValue() - { - 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); - Assert.Equal(0x001F, new Bgr565(Vector3.UnitZ).PackedValue); - } - - [Fact] - public void Bgr565_ToVector3() - { - Assert.Equal(Vector3.One, new Bgr565(Vector3.One).ToVector3()); - Assert.Equal(Vector3.Zero, new Bgr565(Vector3.Zero).ToVector3()); - Assert.Equal(Vector3.UnitX, new Bgr565(Vector3.UnitX).ToVector3()); - Assert.Equal(Vector3.UnitY, new Bgr565(Vector3.UnitY).ToVector3()); - Assert.Equal(Vector3.UnitZ, new Bgr565(Vector3.UnitZ).ToVector3()); - } - - [Fact] - public void Bgr565_ToScaledVector4() - { - // arrange - var bgr = new Bgr565(Vector3.One); - - // act - Vector4 actual = bgr.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgr565_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgr565(Vector3.One).ToScaledVector4(); - int expected = 0xFFFF; - var pixel = default(Bgr565); - - // act - pixel.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgr565_FromBgra5551() - { - // arrange - var bgr = default(Bgr565); - ushort expected = ushort.MaxValue; - - // act - bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromArgb32() - { - // arrange - var bgr1 = default(Bgr565); - var bgr2 = default(Bgr565); - ushort expected1 = ushort.MaxValue; - ushort expected2 = ushort.MaxValue; - - // act - bgr1.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 1.0f)); - bgr2.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 0.0f)); - - // assert - Assert.Equal(expected1, bgr1.PackedValue); - Assert.Equal(expected2, bgr2.PackedValue); - } - - [Fact] - public void Bgr565_FromRgba32() - { - // arrange - var bgr1 = default(Bgr565); - var bgr2 = default(Bgr565); - ushort expected1 = ushort.MaxValue; - ushort expected2 = ushort.MaxValue; - - // act - bgr1.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 1.0f)); - bgr2.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 0.0f)); - - // assert - Assert.Equal(expected1, bgr1.PackedValue); - Assert.Equal(expected2, bgr2.PackedValue); - } - - [Fact] - public void Bgr565_ToRgba32() - { - // arrange - var bgra = new Bgr565(Vector3.One); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - bgra.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra565_FromRgb48() - { - // arrange - var bgr = default(Bgr565); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgr.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgr.PackedValue); - } - - [Fact] - public void Bgra565_FromRgba64() - { - // arrange - var bgr = default(Bgr565); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgr.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromBgr24() - { - // 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); - } - - [Fact] - public void Bgr565_FromRgb24() - { - // arrange - var bgr = default(Bgr565); - ushort expected = ushort.MaxValue; - - // act - bgr.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromGrey8() - { - // arrange - var bgr = default(Bgr565); - ushort expected = ushort.MaxValue; - - // act - bgr.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_FromGrey16() - { - // arrange - var bgr = default(Bgr565); - ushort expected = ushort.MaxValue; - - // act - bgr.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, bgr.PackedValue); - } - - [Fact] - public void Bgr565_Clamping() - { - Assert.Equal(Vector3.Zero, new Bgr565(Vector3.One * -1234F).ToVector3()); - Assert.Equal(Vector3.One, new Bgr565(Vector3.One * 1234F).ToVector3()); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgr565(0.0f, 0.0f, 0.0f); + var color2 = new Bgr565(new Vector3(0.0f)); + var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 1.0f)); + var color4 = new Bgr565(1.0f, 0.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgr565(0.0f, 0.0f, 0.0f); + var color2 = new Bgr565(new Vector3(1.0f)); + var color3 = new Bgr565(new Vector3(1.0f, 0.0f, 0.0f)); + var color4 = new Bgr565(1.0f, 1.0f, 0.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgr565_PackedValue() + { + 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); + Assert.Equal(0x001F, new Bgr565(Vector3.UnitZ).PackedValue); + } + + [Fact] + public void Bgr565_ToVector3() + { + Assert.Equal(Vector3.One, new Bgr565(Vector3.One).ToVector3()); + Assert.Equal(Vector3.Zero, new Bgr565(Vector3.Zero).ToVector3()); + Assert.Equal(Vector3.UnitX, new Bgr565(Vector3.UnitX).ToVector3()); + Assert.Equal(Vector3.UnitY, new Bgr565(Vector3.UnitY).ToVector3()); + Assert.Equal(Vector3.UnitZ, new Bgr565(Vector3.UnitZ).ToVector3()); + } + + [Fact] + public void Bgr565_ToScaledVector4() + { + // arrange + var bgr = new Bgr565(Vector3.One); + + // act + Vector4 actual = bgr.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgr565_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgr565(Vector3.One).ToScaledVector4(); + int expected = 0xFFFF; + var pixel = default(Bgr565); + + // act + pixel.FromScaledVector4(scaled); + ushort actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgr565_FromBgra5551() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromArgb32() + { + // arrange + var bgr1 = default(Bgr565); + var bgr2 = default(Bgr565); + ushort expected1 = ushort.MaxValue; + ushort expected2 = ushort.MaxValue; + + // act + bgr1.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 1.0f)); + bgr2.FromArgb32(new Argb32(1.0f, 1.0f, 1.0f, 0.0f)); + + // assert + Assert.Equal(expected1, bgr1.PackedValue); + Assert.Equal(expected2, bgr2.PackedValue); + } + + [Fact] + public void Bgr565_FromRgba32() + { + // arrange + var bgr1 = default(Bgr565); + var bgr2 = default(Bgr565); + ushort expected1 = ushort.MaxValue; + ushort expected2 = ushort.MaxValue; + + // act + bgr1.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 1.0f)); + bgr2.FromRgba32(new Rgba32(1.0f, 1.0f, 1.0f, 0.0f)); + + // assert + Assert.Equal(expected1, bgr1.PackedValue); + Assert.Equal(expected2, bgr2.PackedValue); + } + + [Fact] + public void Bgr565_ToRgba32() + { + // arrange + var bgra = new Bgr565(Vector3.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra565_FromRgb48() + { + // arrange + var bgr = default(Bgr565); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgr.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgr.PackedValue); + } + + [Fact] + public void Bgra565_FromRgba64() + { + // arrange + var bgr = default(Bgr565); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgr.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromBgr24() + { + // 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); + } + + [Fact] + public void Bgr565_FromRgb24() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey8() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_FromGrey16() + { + // arrange + var bgr = default(Bgr565); + ushort expected = ushort.MaxValue; + + // act + bgr.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, bgr.PackedValue); + } + + [Fact] + public void Bgr565_Clamping() + { + Assert.Equal(Vector3.Zero, new Bgr565(Vector3.One * -1234F).ToVector3()); + Assert.Equal(Vector3.One, new Bgr565(Vector3.One * 1234F).ToVector3()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index baed870e74..04c8813d58 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -3,146 +3,144 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Bgra32Tests { - [Trait("Category", "PixelFormats")] - public class Bgra32Tests + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + 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); - } + Assert.Equal(color1, color2); + } - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Bgra32(0, 0, byte.MaxValue, byte.MaxValue); - var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - - Assert.NotEqual(color1, color2); - } - - public static readonly TheoryData ColorData = - new() - { - { 1, 2, 3, 4 }, - { 4, 5, 6, 7 }, - { 0, 255, 42, 0 }, - { 1, 2, 3, 255 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte b, byte g, byte r, byte a) - { - var p = new Bgra32(r, g, b, a); + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra32(0, 0, byte.MaxValue, byte.MaxValue); + var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - Assert.Equal(a, p.A); - } + Assert.NotEqual(color1, color2); + } - [Fact] - public unsafe void ByteLayoutIsSequentialBgra() - { - var color = new Bgra32(1, 2, 3, 4); - byte* ptr = (byte*)&color; - - Assert.Equal(3, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(1, ptr[2]); - Assert.Equal(4, ptr[3]); - } - - [Theory] - [MemberData(nameof(ColorData))] - public void Equality_WhenTrue(byte b, byte g, byte r, byte a) + public static readonly TheoryData ColorData = + new() { - var x = new Bgra32(r, g, b, a); - var y = new Bgra32(r, g, b, a); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] - [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] - [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] - [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] - public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) - { - var x = new Bgra32(r1, g1, b1, a1); - var y = new Bgra32(r2, g2, b2, a2); + { 1, 2, 3, 4 }, + { 4, 5, 6, 7 }, + { 0, 255, 42, 0 }, + { 1, 2, 3, 255 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte b, byte g, byte r, byte a) + { + var p = new Bgra32(r, g, b, a); - Assert.False(x.Equals(y)); - Assert.False(x.Equals((object)y)); - } + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + Assert.Equal(a, p.A); + } - [Fact] - public void FromRgba32() - { - var bgra = default(Bgra32); - bgra.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, bgra.R); - Assert.Equal(2, bgra.G); - Assert.Equal(3, bgra.B); - Assert.Equal(4, bgra.A); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - var c = default(Bgra32); - c.FromVector4(Vec(1, 2, 3, 4)); + [Fact] + public unsafe void ByteLayoutIsSequentialBgra() + { + var color = new Bgra32(1, 2, 3, 4); + byte* ptr = (byte*)&color; - Assert.Equal(1, c.R); - Assert.Equal(2, c.G); - Assert.Equal(3, c.B); - Assert.Equal(4, c.A); - } + Assert.Equal(3, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(1, ptr[2]); + Assert.Equal(4, ptr[3]); + } - [Fact] - public void ToVector4() - { - var rgb = new Bgra32(1, 2, 3, 4); + [Theory] + [MemberData(nameof(ColorData))] + public void Equality_WhenTrue(byte b, byte g, byte r, byte a) + { + var x = new Bgra32(r, g, b, a); + var y = new Bgra32(r, g, b, a); - Assert.Equal(Vec(1, 2, 3, 4), rgb.ToVector4()); - } + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } - [Fact] - public void Bgra32_FromBgra5551() - { - // arrange - var bgra = default(Bgra32); - uint expected = uint.MaxValue; + [Theory] + [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] + [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] + [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] + [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] + public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) + { + var x = new Bgra32(r1, g1, b1, a1); + var y = new Bgra32(r2, g2, b2, a2); + + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + } + + [Fact] + public void FromRgba32() + { + var bgra = default(Bgra32); + bgra.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, bgra.R); + Assert.Equal(2, bgra.G); + Assert.Equal(3, bgra.B); + Assert.Equal(4, bgra.A); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var c = default(Bgra32); + c.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, c.R); + Assert.Equal(2, c.G); + Assert.Equal(3, c.B); + Assert.Equal(4, c.A); + } + + [Fact] + public void ToVector4() + { + var rgb = new Bgra32(1, 2, 3, 4); + + Assert.Equal(Vec(1, 2, 3, 4), rgb.ToVector4()); + } + + [Fact] + public void Bgra32_FromBgra5551() + { + // arrange + var bgra = default(Bgra32); + uint expected = uint.MaxValue; - // act - bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, bgra.PackedValue); - } + // assert + Assert.Equal(expected, bgra.PackedValue); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs index e60ca37939..543b5814f9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra4444Tests.cs @@ -3,247 +3,245 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Bgra4444Tests { - [Trait("Category", "PixelFormats")] - public class Bgra4444Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Bgra4444(new Vector4(0.0f)); - var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new Bgra4444(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Bgra4444(new Vector4(1.0f)); - var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new Bgra4444(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgra4444_PackedValue() - { - Assert.Equal(520, new Bgra4444(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal(0x0, new Bgra4444(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFF, new Bgra4444(Vector4.One).PackedValue); - Assert.Equal(0x0F00, new Bgra4444(Vector4.UnitX).PackedValue); - Assert.Equal(0x00F0, new Bgra4444(Vector4.UnitY).PackedValue); - Assert.Equal(0x000F, new Bgra4444(Vector4.UnitZ).PackedValue); - Assert.Equal(0xF000, new Bgra4444(Vector4.UnitW).PackedValue); - } - - [Fact] - public void Bgra4444_ToVector4() - { - Assert.Equal(Vector4.One, new Bgra4444(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Bgra4444(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Bgra4444(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Bgra4444(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Bgra4444(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Bgra4444_ToScaledVector4() - { - // arrange - var bgra = new Bgra4444(Vector4.One); - - // act - Vector4 actual = bgra.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgra4444_ToRgba32() - { - // arrange - var bgra = new Bgra4444(Vector4.One); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - bgra.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra4444_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgra4444(Vector4.One).ToScaledVector4(); - int expected = 0xFFFF; - var bgra = default(Bgra4444); - - // act - bgra.FromScaledVector4(scaled); - ushort actual = bgra.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra4444_FromBgra5551() - { - // arrange - var bgra = default(Bgra4444); - ushort expected = ushort.MaxValue; - - // act - bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromArgb32() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgba32() - { - // arrange - var bgra1 = default(Bgra4444); - var bgra2 = default(Bgra4444); - ushort expectedPackedValue1 = ushort.MaxValue; - ushort expectedPackedValue2 = 0xFF0F; - - // act - bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); - bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgb48() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgba64() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromGrey16() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromGrey8() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromBgr24() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_FromRgb24() - { - // arrange - var bgra = default(Bgra4444); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra4444_Clamping() - { - Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Bgra4444(Vector4.One * 1234.0f).ToVector4()); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra4444(new Vector4(0.0f)); + var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Bgra4444(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra4444(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra4444(new Vector4(1.0f)); + var color3 = new Bgra4444(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Bgra4444(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgra4444_PackedValue() + { + Assert.Equal(520, new Bgra4444(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(0x0, new Bgra4444(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra4444(Vector4.One).PackedValue); + Assert.Equal(0x0F00, new Bgra4444(Vector4.UnitX).PackedValue); + Assert.Equal(0x00F0, new Bgra4444(Vector4.UnitY).PackedValue); + Assert.Equal(0x000F, new Bgra4444(Vector4.UnitZ).PackedValue); + Assert.Equal(0xF000, new Bgra4444(Vector4.UnitW).PackedValue); + } + + [Fact] + public void Bgra4444_ToVector4() + { + Assert.Equal(Vector4.One, new Bgra4444(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Bgra4444(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Bgra4444(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Bgra4444(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Bgra4444(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Bgra4444_ToScaledVector4() + { + // arrange + var bgra = new Bgra4444(Vector4.One); + + // act + Vector4 actual = bgra.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgra4444_ToRgba32() + { + // arrange + var bgra = new Bgra4444(Vector4.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra4444_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgra4444(Vector4.One).ToScaledVector4(); + int expected = 0xFFFF; + var bgra = default(Bgra4444); + + // act + bgra.FromScaledVector4(scaled); + ushort actual = bgra.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra4444_FromBgra5551() + { + // arrange + var bgra = default(Bgra4444); + ushort expected = ushort.MaxValue; + + // act + bgra.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromArgb32() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgba32() + { + // arrange + var bgra1 = default(Bgra4444); + var bgra2 = default(Bgra4444); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFF0F; + + // act + bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); + bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgb48() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgba64() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey16() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromGrey8() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromBgr24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_FromRgb24() + { + // arrange + var bgra = default(Bgra4444); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra4444_Clamping() + { + Assert.Equal(Vector4.Zero, new Bgra4444(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Bgra4444(Vector4.One * 1234.0f).ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index fa0d16b1b0..ec54173101 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -3,272 +3,270 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Bgra5551Tests { - [Trait("Category", "PixelFormats")] - public class Bgra5551Tests + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); + 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); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Bgra5551(new Vector4(1.0f)); + var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Bgra5551(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Bgra5551_PackedValue() + { + float x = 0x1a; + float y = 0x16; + float z = 0xd; + float w = 0x1; + Assert.Equal(0xeacd, new Bgra5551(x / 0x1f, y / 0x1f, z / 0x1f, w).PackedValue); + Assert.Equal(3088, new Bgra5551(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + + Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); + Assert.Equal(0x7C00, new Bgra5551(Vector4.UnitX).PackedValue); + Assert.Equal(0x03E0, new Bgra5551(Vector4.UnitY).PackedValue); + Assert.Equal(0x001F, new Bgra5551(Vector4.UnitZ).PackedValue); + Assert.Equal(0x8000, new Bgra5551(Vector4.UnitW).PackedValue); + + // Test the limits. + Assert.Equal(0x0, new Bgra5551(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); + } + + [Fact] + public void Bgra5551_ToVector4() + { + Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new Bgra5551(Vector4.One).ToVector4()); + } + + [Fact] + public void Bgra5551_ToScaledVector4() + { + // arrange + var bgra = new Bgra5551(Vector4.One); + + // act + Vector4 actual = bgra.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Bgra5551_ToRgba32() + { + // arrange + var bgra = new Bgra5551(Vector4.One); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + bgra.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromScaledVector4() + { + // arrange + Vector4 scaled = new Bgra5551(Vector4.One).ToScaledVector4(); + int expected = 0xFFFF; + var pixel = default(Bgra5551); + + // act + pixel.FromScaledVector4(scaled); + ushort actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromBgra5551() + { + // arrange + var bgra = default(Bgra5551); + var actual = default(Bgra5551); + var expected = new Bgra5551(1.0f, 0.0f, 1.0f, 1.0f); + + // act + bgra.FromBgra5551(expected); + actual.FromBgra5551(bgra); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Bgra5551_FromRgba32() + { + // arrange + var bgra1 = default(Bgra5551); + var bgra2 = default(Bgra5551); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFC1F; + + // act + bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); + bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra5551_FromBgra32() + { + // arrange + var bgra1 = default(Bgra5551); + var bgra2 = default(Bgra5551); + ushort expectedPackedValue1 = ushort.MaxValue; + ushort expectedPackedValue2 = 0xFC1F; + + // act + bgra1.FromBgra32(new Bgra32(255, 255, 255, 255)); + bgra2.FromBgra32(new Bgra32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, bgra1.PackedValue); + Assert.Equal(expectedPackedValue2, bgra2.PackedValue); + } + + [Fact] + public void Bgra5551_FromArgb32() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgb48() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgba64() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey16() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromGrey8() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromBgr24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_FromRgb24() + { + // arrange + var bgra = default(Bgra5551); + ushort expectedPackedValue = ushort.MaxValue; + + // act + bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, bgra.PackedValue); + } + + [Fact] + public void Bgra5551_Clamping() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); - 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); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Bgra5551(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Bgra5551(new Vector4(1.0f)); - var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new Bgra5551(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Bgra5551_PackedValue() - { - float x = 0x1a; - float y = 0x16; - float z = 0xd; - float w = 0x1; - Assert.Equal(0xeacd, new Bgra5551(x / 0x1f, y / 0x1f, z / 0x1f, w).PackedValue); - Assert.Equal(3088, new Bgra5551(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - - Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); - Assert.Equal(0x7C00, new Bgra5551(Vector4.UnitX).PackedValue); - Assert.Equal(0x03E0, new Bgra5551(Vector4.UnitY).PackedValue); - Assert.Equal(0x001F, new Bgra5551(Vector4.UnitZ).PackedValue); - Assert.Equal(0x8000, new Bgra5551(Vector4.UnitW).PackedValue); - - // Test the limits. - Assert.Equal(0x0, new Bgra5551(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFF, new Bgra5551(Vector4.One).PackedValue); - } - - [Fact] - public void Bgra5551_ToVector4() - { - Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new Bgra5551(Vector4.One).ToVector4()); - } - - [Fact] - public void Bgra5551_ToScaledVector4() - { - // arrange - var bgra = new Bgra5551(Vector4.One); - - // act - Vector4 actual = bgra.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Bgra5551_ToRgba32() - { - // arrange - var bgra = new Bgra5551(Vector4.One); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - bgra.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromScaledVector4() - { - // arrange - Vector4 scaled = new Bgra5551(Vector4.One).ToScaledVector4(); - int expected = 0xFFFF; - var pixel = default(Bgra5551); - - // act - pixel.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromBgra5551() - { - // arrange - var bgra = default(Bgra5551); - var actual = default(Bgra5551); - var expected = new Bgra5551(1.0f, 0.0f, 1.0f, 1.0f); - - // act - bgra.FromBgra5551(expected); - actual.FromBgra5551(bgra); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Bgra5551_FromRgba32() - { - // arrange - var bgra1 = default(Bgra5551); - var bgra2 = default(Bgra5551); - ushort expectedPackedValue1 = ushort.MaxValue; - ushort expectedPackedValue2 = 0xFC1F; - - // act - bgra1.FromRgba32(new Rgba32(255, 255, 255, 255)); - bgra2.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra5551_FromBgra32() - { - // arrange - var bgra1 = default(Bgra5551); - var bgra2 = default(Bgra5551); - ushort expectedPackedValue1 = ushort.MaxValue; - ushort expectedPackedValue2 = 0xFC1F; - - // act - bgra1.FromBgra32(new Bgra32(255, 255, 255, 255)); - bgra2.FromBgra32(new Bgra32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, bgra1.PackedValue); - Assert.Equal(expectedPackedValue2, bgra2.PackedValue); - } - - [Fact] - public void Bgra5551_FromArgb32() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgb48() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgba64() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromGrey16() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromGrey8() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromBgr24() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_FromRgb24() - { - // arrange - var bgra = default(Bgra5551); - ushort expectedPackedValue = ushort.MaxValue; - - // act - bgra.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, bgra.PackedValue); - } - - [Fact] - public void Bgra5551_Clamping() - { - Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Bgra5551(Vector4.One * 1234.0f).ToVector4()); - } + Assert.Equal(Vector4.Zero, new Bgra5551(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Bgra5551(Vector4.One * 1234.0f).ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index 30e3a4499a..435f073915 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -3,240 +3,238 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Byte4Tests { - [Trait("Category", "PixelFormats")] - public class Byte4Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Byte4(new Vector4(0.0f)); - var color3 = new Byte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new Byte4(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Byte4(new Vector4(1.0f)); - var color3 = new Byte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new Byte4(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Byte4_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); - } - - [Fact] - public void Byte4_ToVector4() - { - Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 255).ToVector4()); - Assert.Equal(Vector4.Zero, new Byte4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX * 255, new Byte4(Vector4.UnitX * 255).ToVector4()); - Assert.Equal(Vector4.UnitY * 255, new Byte4(Vector4.UnitY * 255).ToVector4()); - Assert.Equal(Vector4.UnitZ * 255, new Byte4(Vector4.UnitZ * 255).ToVector4()); - Assert.Equal(Vector4.UnitW * 255, new Byte4(Vector4.UnitW * 255).ToVector4()); - } - - [Fact] - public void Byte4_ToScaledVector4() - { - // arrange - var byte4 = new Byte4(Vector4.One * 255); - - // act - Vector4 actual = byte4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Byte4_ToRgba32() - { - // arrange - var byte4 = new Byte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - byte4.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } - - [Fact] - public void Byte4_FromScaledVector4() - { - // arrange - Vector4 scaled = new Byte4(Vector4.One * 255).ToScaledVector4(); - var pixel = default(Byte4); - uint expected = 0xFFFFFFFF; - - // act - pixel.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Byte4_FromArgb32() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromBgr24() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromGrey8() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromGrey16() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromRgb24() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromBgra5551() - { - // arrange - var byte4 = default(Byte4); - uint expected = 0xFFFFFFFF; - - // act - byte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromRgba32() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue1 = uint.MaxValue; - - // act - byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromRgb48() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_FromRgba64() - { - // arrange - var byte4 = default(Byte4); - uint expectedPackedValue = uint.MaxValue; - - // act - byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, byte4.PackedValue); - } - - [Fact] - public void Byte4_Clamping() - { - Assert.Equal(Vector4.Zero, new Byte4(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 1234.0f).ToVector4()); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Byte4(new Vector4(0.0f)); + var color3 = new Byte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Byte4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Byte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Byte4(new Vector4(1.0f)); + var color3 = new Byte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Byte4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Byte4_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); + } + + [Fact] + public void Byte4_ToVector4() + { + Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 255).ToVector4()); + Assert.Equal(Vector4.Zero, new Byte4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX * 255, new Byte4(Vector4.UnitX * 255).ToVector4()); + Assert.Equal(Vector4.UnitY * 255, new Byte4(Vector4.UnitY * 255).ToVector4()); + Assert.Equal(Vector4.UnitZ * 255, new Byte4(Vector4.UnitZ * 255).ToVector4()); + Assert.Equal(Vector4.UnitW * 255, new Byte4(Vector4.UnitW * 255).ToVector4()); + } + + [Fact] + public void Byte4_ToScaledVector4() + { + // arrange + var byte4 = new Byte4(Vector4.One * 255); + + // act + Vector4 actual = byte4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Byte4_ToRgba32() + { + // arrange + var byte4 = new Byte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void Byte4_FromScaledVector4() + { + // arrange + Vector4 scaled = new Byte4(Vector4.One * 255).ToScaledVector4(); + var pixel = default(Byte4); + uint expected = 0xFFFFFFFF; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Byte4_FromArgb32() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromBgr24() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey8() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromGrey16() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb24() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromBgra5551() + { + // arrange + var byte4 = default(Byte4); + uint expected = 0xFFFFFFFF; + + // act + byte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba32() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue1 = uint.MaxValue; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgb48() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_FromRgba64() + { + // arrange + var byte4 = default(Byte4); + uint expectedPackedValue = uint.MaxValue; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, byte4.PackedValue); + } + + [Fact] + public void Byte4_Clamping() + { + Assert.Equal(Vector4.Zero, new Byte4(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One * 255, new Byte4(Vector4.One * 1234.0f).ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 5ae19e460e..9ffaf3e21c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -3,69 +3,67 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class HalfSingleTests { - [Trait("Category", "PixelFormats")] - public class HalfSingleTests + [Fact] + public void HalfSingle_PackedValue() { - [Fact] - public void HalfSingle_PackedValue() - { - Assert.Equal(11878, new HalfSingle(0.1F).PackedValue); - Assert.Equal(46285, new HalfSingle(-0.3F).PackedValue); + Assert.Equal(11878, new HalfSingle(0.1F).PackedValue); + Assert.Equal(46285, new HalfSingle(-0.3F).PackedValue); - // Test limits - Assert.Equal(15360, new HalfSingle(1F).PackedValue); - Assert.Equal(0, new HalfSingle(0F).PackedValue); - Assert.Equal(48128, new HalfSingle(-1F).PackedValue); - } + // Test limits + Assert.Equal(15360, new HalfSingle(1F).PackedValue); + Assert.Equal(0, new HalfSingle(0F).PackedValue); + Assert.Equal(48128, new HalfSingle(-1F).PackedValue); + } - [Fact] - public void HalfSingle_ToVector4() - { - // arrange - var halfSingle = new HalfSingle(0.5f); - var expected = new Vector4(0.5f, 0, 0, 1); + [Fact] + public void HalfSingle_ToVector4() + { + // arrange + var halfSingle = new HalfSingle(0.5f); + var expected = new Vector4(0.5f, 0, 0, 1); - // act - var actual = halfSingle.ToVector4(); + // act + var actual = halfSingle.ToVector4(); - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void HalfSingle_ToScaledVector4() - { - // arrange - var halfSingle = new HalfSingle(-1F); + [Fact] + public void HalfSingle_ToScaledVector4() + { + // arrange + var halfSingle = new HalfSingle(-1F); - // act - Vector4 actual = halfSingle.ToScaledVector4(); + // act + Vector4 actual = halfSingle.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } - [Fact] - public void HalfSingle_FromScaledVector4() - { - // arrange - Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); - int expected = 48128; - var halfSingle = default(HalfSingle); + [Fact] + public void HalfSingle_FromScaledVector4() + { + // arrange + Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); + int expected = 48128; + var halfSingle = default(HalfSingle); - // act - halfSingle.FromScaledVector4(scaled); - ushort actual = halfSingle.PackedValue; + // act + halfSingle.FromScaledVector4(scaled); + ushort actual = halfSingle.PackedValue; - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index 0729504d2d..eda9315363 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -3,91 +3,89 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class HalfVector2Tests { - [Trait("Category", "PixelFormats")] - public class HalfVector2Tests + [Fact] + public void HalfVector2_PackedValue() + { + Assert.Equal(0u, new HalfVector2(Vector2.Zero).PackedValue); + Assert.Equal(1006648320u, new HalfVector2(Vector2.One).PackedValue); + Assert.Equal(3033345638u, new HalfVector2(0.1f, -0.3f).PackedValue); + } + + [Fact] + public void HalfVector2_ToVector2() + { + Assert.Equal(Vector2.Zero, new HalfVector2(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); + Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); + } + + [Fact] + public void HalfVector2_ToScaledVector4() + { + // arrange + var halfVector = new HalfVector2(Vector2.One); + + // act + Vector4 actual = halfVector.ToScaledVector4(); + + // assert + Assert.Equal(1F, actual.X); + Assert.Equal(1F, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void HalfVector2_FromScaledVector4() { - [Fact] - public void HalfVector2_PackedValue() - { - Assert.Equal(0u, new HalfVector2(Vector2.Zero).PackedValue); - Assert.Equal(1006648320u, new HalfVector2(Vector2.One).PackedValue); - Assert.Equal(3033345638u, new HalfVector2(0.1f, -0.3f).PackedValue); - } - - [Fact] - public void HalfVector2_ToVector2() - { - Assert.Equal(Vector2.Zero, new HalfVector2(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); - Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); - } - - [Fact] - public void HalfVector2_ToScaledVector4() - { - // arrange - var halfVector = new HalfVector2(Vector2.One); - - // act - Vector4 actual = halfVector.ToScaledVector4(); - - // assert - Assert.Equal(1F, actual.X); - Assert.Equal(1F, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void HalfVector2_FromScaledVector4() - { - // arrange - Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); - uint expected = 1006648320u; - var halfVector = default(HalfVector2); - - // act - halfVector.FromScaledVector4(scaled); - uint actual = halfVector.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfVector2_ToVector4() - { - // arrange - var halfVector = new HalfVector2(.5F, .25F); - var expected = new Vector4(0.5f, .25F, 0, 1); - - // act - var actual = halfVector.ToVector4(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void HalfVector2_FromBgra5551() - { - // arrange - var halfVector2 = default(HalfVector2); - - // act - halfVector2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Vector4 actual = halfVector2.ToScaledVector4(); - Assert.Equal(1F, actual.X); - Assert.Equal(1F, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // arrange + Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); + uint expected = 1006648320u; + var halfVector = default(HalfVector2); + + // act + halfVector.FromScaledVector4(scaled); + uint actual = halfVector.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector2_ToVector4() + { + // arrange + var halfVector = new HalfVector2(.5F, .25F); + var expected = new Vector4(0.5f, .25F, 0, 1); + + // act + var actual = halfVector.ToVector4(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void HalfVector2_FromBgra5551() + { + // arrange + var halfVector2 = default(HalfVector2); + + // act + halfVector2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Vector4 actual = halfVector2.ToScaledVector4(); + Assert.Equal(1F, actual.X); + Assert.Equal(1F, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 94528e01ca..77120192bb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -3,82 +3,80 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class HalfVector4Tests { - [Trait("Category", "PixelFormats")] - public class HalfVector4Tests + [Fact] + public void HalfVector4_PackedValue() { - [Fact] - public void HalfVector4_PackedValue() - { - Assert.Equal(0uL, new HalfVector4(Vector4.Zero).PackedValue); - Assert.Equal(4323521613979991040uL, new HalfVector4(Vector4.One).PackedValue); - Assert.Equal(13547034390470638592uL, new HalfVector4(-Vector4.One).PackedValue); - Assert.Equal(15360uL, new HalfVector4(Vector4.UnitX).PackedValue); - Assert.Equal(1006632960uL, new HalfVector4(Vector4.UnitY).PackedValue); - Assert.Equal(65970697666560uL, new HalfVector4(Vector4.UnitZ).PackedValue); - Assert.Equal(4323455642275676160uL, new HalfVector4(Vector4.UnitW).PackedValue); - Assert.Equal(4035285078724390502uL, new HalfVector4(0.1f, 0.3f, 0.4f, 0.5f).PackedValue); - } + Assert.Equal(0uL, new HalfVector4(Vector4.Zero).PackedValue); + Assert.Equal(4323521613979991040uL, new HalfVector4(Vector4.One).PackedValue); + Assert.Equal(13547034390470638592uL, new HalfVector4(-Vector4.One).PackedValue); + Assert.Equal(15360uL, new HalfVector4(Vector4.UnitX).PackedValue); + Assert.Equal(1006632960uL, new HalfVector4(Vector4.UnitY).PackedValue); + Assert.Equal(65970697666560uL, new HalfVector4(Vector4.UnitZ).PackedValue); + Assert.Equal(4323455642275676160uL, new HalfVector4(Vector4.UnitW).PackedValue); + Assert.Equal(4035285078724390502uL, new HalfVector4(0.1f, 0.3f, 0.4f, 0.5f).PackedValue); + } - [Fact] - public void HalfVector4_ToVector4() - { - Assert.Equal(Vector4.Zero, new HalfVector4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new HalfVector4(Vector4.One).ToVector4()); - Assert.Equal(-Vector4.One, new HalfVector4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.UnitX, new HalfVector4(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); - } + [Fact] + public void HalfVector4_ToVector4() + { + Assert.Equal(Vector4.Zero, new HalfVector4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new HalfVector4(Vector4.One).ToVector4()); + Assert.Equal(-Vector4.One, new HalfVector4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.UnitX, new HalfVector4(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); + } - [Fact] - public void HalfVector4_ToScaledVector4() - { - // arrange - var halfVector4 = new HalfVector4(-Vector4.One); + [Fact] + public void HalfVector4_ToScaledVector4() + { + // arrange + var halfVector4 = new HalfVector4(-Vector4.One); - // act - Vector4 actual = halfVector4.ToScaledVector4(); + // act + Vector4 actual = halfVector4.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(0, actual.W); + } - [Fact] - public void HalfVector4_FromScaledVector4() - { - // arrange - var halfVector4 = default(HalfVector4); - Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); - ulong expected = 13547034390470638592uL; + [Fact] + public void HalfVector4_FromScaledVector4() + { + // arrange + var halfVector4 = default(HalfVector4); + Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); + ulong expected = 13547034390470638592uL; - // act - halfVector4.FromScaledVector4(scaled); - ulong actual = halfVector4.PackedValue; + // act + halfVector4.FromScaledVector4(scaled); + ulong actual = halfVector4.PackedValue; - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void HalfVector4_FromBgra5551() - { - // arrange - var halfVector4 = default(HalfVector4); - Vector4 expected = Vector4.One; + [Fact] + public void HalfVector4_FromBgra5551() + { + // arrange + var halfVector4 = default(HalfVector4); + Vector4 expected = Vector4.One; - // act - halfVector4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + halfVector4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, halfVector4.ToScaledVector4()); - } + // assert + Assert.Equal(expected, halfVector4.ToScaledVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs index 2b232de261..685da2ddf8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/L16Tests.cs @@ -3,161 +3,159 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class L16Tests { - [Trait("Category", "PixelFormats")] - public class L16Tests + [Fact] + public void AreEqual() + { + var color1 = new L16(3000); + var color2 = new L16(3000); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new L16(12345); + var color2 = new L16(54321); + + Assert.NotEqual(color1, color2); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + [InlineData(42)] + public void L16_PackedValue_EqualsInput(ushort input) + => Assert.Equal(input, new L16(input).PackedValue); + + [Fact] + public void L16_FromScaledVector4() + { + // Arrange + L16 gray = default; + const ushort expected = 32767; + Vector4 scaled = new L16(expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToScaledVector4(ushort input) + { + // Arrange + var gray = new L16(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void L16_FromVector4() + { + // Arrange + L16 gray = default; + const ushort expected = 32767; + var vector = new L16(expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void L16_ToVector4(ushort input) + { + // Arrange + var gray = new L16(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(1F, actual.W); + } + + [Fact] + public void L16_FromRgba32() { - [Fact] - public void AreEqual() - { - var color1 = new L16(3000); - var color2 = new L16(3000); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - var color1 = new L16(12345); - var color2 = new L16(54321); - - Assert.NotEqual(color1, color2); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - [InlineData(42)] - public void L16_PackedValue_EqualsInput(ushort input) - => Assert.Equal(input, new L16(input).PackedValue); - - [Fact] - public void L16_FromScaledVector4() - { - // Arrange - L16 gray = default; - const ushort expected = 32767; - Vector4 scaled = new L16(expected).ToScaledVector4(); - - // Act - gray.FromScaledVector4(scaled); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void L16_ToScaledVector4(ushort input) - { - // Arrange - var gray = new L16(input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void L16_FromVector4() - { - // Arrange - L16 gray = default; - const ushort expected = 32767; - var vector = new L16(expected).ToVector4(); - - // Act - gray.FromVector4(vector); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void L16_ToVector4(ushort input) - { - // Arrange - var gray = new L16(input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(1F, actual.W); - } - - [Fact] - public void L16_FromRgba32() - { - // Arrange - L16 gray = default; - const byte rgb = 128; - ushort scaledRgb = ColorNumerics.UpscaleFrom8BitTo16Bit(rgb); - ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); - - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - ushort actual = gray.PackedValue; - - // Assert - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(8100)] - public void L16_ToRgba32(ushort input) - { - // Arrange - ushort expected = ColorNumerics.DownScaleFrom16BitTo8Bit(input); - var gray = new L16(input); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(expected, actual.R); - Assert.Equal(expected, actual.G); - Assert.Equal(expected, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void L16_FromBgra5551() - { - // arrange - var gray = default(L16); - ushort expected = ushort.MaxValue; - - // act - gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, gray.PackedValue); - } + // Arrange + L16 gray = default; + const byte rgb = 128; + ushort scaledRgb = ColorNumerics.UpscaleFrom8BitTo16Bit(rgb); + ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + ushort actual = gray.PackedValue; + + // Assert + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void L16_ToRgba32(ushort input) + { + // Arrange + ushort expected = ColorNumerics.DownScaleFrom16BitTo8Bit(input); + var gray = new L16(input); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(expected, actual.R); + Assert.Equal(expected, actual.G); + Assert.Equal(expected, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void L16_FromBgra5551() + { + // arrange + var gray = default(L16); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.PackedValue); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs index 766a0d6155..afdc039a28 100644 --- a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs @@ -3,257 +3,255 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class L8Tests { - [Trait("Category", "PixelFormats")] - public class L8Tests + public static readonly TheoryData LuminanceData + = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + + [Theory] + [InlineData(0)] + [InlineData(255)] + [InlineData(10)] + [InlineData(42)] + public void L8_PackedValue_EqualsInput(byte input) + => Assert.Equal(input, new L8(input).PackedValue); + + [Fact] + public void AreEqual() { - public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + var color1 = new L8(100); + var color2 = new L8(100); - [Theory] - [InlineData(0)] - [InlineData(255)] - [InlineData(10)] - [InlineData(42)] - public void L8_PackedValue_EqualsInput(byte input) - => Assert.Equal(input, new L8(input).PackedValue); - - [Fact] - public void AreEqual() - { - var color1 = new L8(100); - var color2 = new L8(100); + Assert.Equal(color1, color2); + } - Assert.Equal(color1, color2); - } + [Fact] + public void AreNotEqual() + { + var color1 = new L8(100); + var color2 = new L8(200); - [Fact] - public void AreNotEqual() - { - var color1 = new L8(100); - var color2 = new L8(200); + Assert.NotEqual(color1, color2); + } - Assert.NotEqual(color1, color2); - } + [Fact] + public void L8_FromScaledVector4() + { + // Arrange + L8 gray = default; + const byte expected = 128; + Vector4 scaled = new L8(expected).ToScaledVector4(); - [Fact] - public void L8_FromScaledVector4() - { - // Arrange - L8 gray = default; - const byte expected = 128; - Vector4 scaled = new L8(expected).ToScaledVector4(); + // Act + gray.FromScaledVector4(scaled); + byte actual = gray.PackedValue; - // Act - gray.FromScaledVector4(scaled); - byte actual = gray.PackedValue; + // Assert + Assert.Equal(expected, actual); + } - // Assert - Assert.Equal(expected, actual); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToScaledVector4(byte input) + { + // Arrange + var gray = new L8(input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToScaledVector4(byte input) - { - // Arrange - var gray = new L8(input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromVector4(byte luminance) + { + // Arrange + L8 gray = default; + var vector = new L8(luminance).ToVector4(); - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromVector4(byte luminance) - { - // Arrange - L8 gray = default; - var vector = new L8(luminance).ToVector4(); + // Act + gray.FromVector4(vector); + byte actual = gray.PackedValue; - // Act - gray.FromVector4(vector); - byte actual = gray.PackedValue; + // Assert + Assert.Equal(luminance, actual); + } - // Assert - Assert.Equal(luminance, actual); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToVector4(byte input) + { + // Arrange + var gray = new L8(input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(1, actual.W); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToVector4(byte input) - { - // Arrange - var gray = new L8(input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(1, actual.W); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromRgba32(byte rgb) + { + // Arrange + L8 gray = default; + byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromRgba32(byte rgb) - { - // Arrange - L8 gray = default; - byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + byte actual = gray.PackedValue; - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - byte actual = gray.PackedValue; + // Assert + Assert.Equal(expected, actual); + } - // Assert - Assert.Equal(expected, actual); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_ToRgba32(byte luminance) + { + // Arrange + var gray = new L8(luminance); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(luminance, actual.R); + Assert.Equal(luminance, actual.G); + Assert.Equal(luminance, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_ToRgba32(byte luminance) - { - // Arrange - var gray = new L8(luminance); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(luminance, actual.R); - Assert.Equal(luminance, actual.G); - Assert.Equal(luminance, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } + [Fact] + public void L8_FromBgra5551() + { + // arrange + var grey = default(L8); + byte expected = byte.MaxValue; - [Fact] - public void L8_FromBgra5551() - { - // arrange - var grey = default(L8); - byte expected = byte.MaxValue; + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // act - grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // assert + Assert.Equal(expected, grey.PackedValue); + } - // assert - Assert.Equal(expected, grey.PackedValue); - } + public class Rgba32Compatibility + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public static readonly TheoryData LuminanceData = L8Tests.LuminanceData; - public class Rgba32Compatibility + [Theory] + [MemberData(nameof(LuminanceData))] + public void L8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) { - // ReSharper disable once MemberHidesStaticFromOuterClass - public static readonly TheoryData LuminanceData = L8Tests.LuminanceData; + var original = new L8(luminance); - [Theory] - [MemberData(nameof(LuminanceData))] - public void L8_FromRgba32_IsInverseOf_ToRgba32(byte luminance) - { - var original = new L8(luminance); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + L8 mirror = default; + mirror.FromRgba32(rgba); - L8 mirror = default; - mirror.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) - { - var original = new L8(luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) + { + var original = new L8(luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - L8 mirror = default; - mirror.FromRgba32(rgba); + L8 mirror = default; + mirror.FromRgba32(rgba); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToVector4_IsRgba32Compatible(byte luminance) - { - var original = new L8(luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - var l8Vector = original.ToVector4(); - var rgbaVector = 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] - [MemberData(nameof(LuminanceData))] - public void FromVector4_IsRgba32Compatible(byte luminance) - { - var original = new L8(luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - var rgbaVector = original.ToVector4(); + var rgbaVector = original.ToVector4(); - L8 mirror = default; - mirror.FromVector4(rgbaVector); + L8 mirror = default; + mirror.FromVector4(rgbaVector); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new L8(luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Vector4 l8Vector = original.ToScaledVector4(); - Vector4 rgbaVector = 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] - [MemberData(nameof(LuminanceData))] - public void FromScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new L8(luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new L8(luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Vector4 rgbaVector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); - L8 mirror = default; - mirror.FromScaledVector4(rgbaVector); + L8 mirror = default; + mirror.FromScaledVector4(rgbaVector); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs index 71727fb85d..92b4a4e1a1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs @@ -3,261 +3,259 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class La16Tests { - [Trait("Category", "PixelFormats")] - public class La16Tests + public static readonly TheoryData LuminanceData + = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + + [Theory] + [InlineData(0, 0)] + [InlineData(255, 65535)] + [InlineData(10, 2570)] + [InlineData(42, 10794)] + public void La16_PackedValue_EqualsPackedInput(byte input, ushort packed) + => Assert.Equal(packed, new La16(input, input).PackedValue); + + [Fact] + public void AreEqual() { - public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + var color1 = new La16(100, 50); + var color2 = new La16(100, 50); - [Theory] - [InlineData(0, 0)] - [InlineData(255, 65535)] - [InlineData(10, 2570)] - [InlineData(42, 10794)] - public void La16_PackedValue_EqualsPackedInput(byte input, ushort packed) - => Assert.Equal(packed, new La16(input, input).PackedValue); - - [Fact] - public void AreEqual() - { - var color1 = new La16(100, 50); - var color2 = new La16(100, 50); + Assert.Equal(color1, color2); + } - Assert.Equal(color1, color2); - } + [Fact] + public void AreNotEqual() + { + var color1 = new La16(100, 50); + var color2 = new La16(200, 50); - [Fact] - public void AreNotEqual() - { - var color1 = new La16(100, 50); - var color2 = new La16(200, 50); + Assert.NotEqual(color1, color2); + } - Assert.NotEqual(color1, color2); - } + [Fact] + public void La16_FromScaledVector4() + { + // Arrange + La16 gray = default; + const ushort expected = 32896; + Vector4 scaled = new La16(128, 128).ToScaledVector4(); - [Fact] - public void La16_FromScaledVector4() - { - // Arrange - La16 gray = default; - const ushort expected = 32896; - Vector4 scaled = new La16(128, 128).ToScaledVector4(); + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.PackedValue; - // Act - gray.FromScaledVector4(scaled); - ushort actual = gray.PackedValue; + // Assert + Assert.Equal(expected, actual); + } - // Assert - Assert.Equal(expected, actual); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToScaledVector4(byte input) + { + // Arrange + var gray = new La16(input, input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(scaledInput, actual.W); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToScaledVector4(byte input) - { - // Arrange - var gray = new La16(input, input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(scaledInput, actual.W); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromVector4(byte luminance) + { + // Arrange + La16 gray = default; + var vector = new La16(luminance, luminance).ToVector4(); + + // Act + gray.FromVector4(vector); + byte actualL = gray.L; + byte actualA = gray.A; + + // Assert + Assert.Equal(luminance, actualL); + Assert.Equal(luminance, actualA); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromVector4(byte luminance) - { - // Arrange - La16 gray = default; - var vector = new La16(luminance, luminance).ToVector4(); - - // Act - gray.FromVector4(vector); - byte actualL = gray.L; - byte actualA = gray.A; - - // Assert - Assert.Equal(luminance, actualL); - Assert.Equal(luminance, actualA); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToVector4(byte input) + { + // Arrange + var gray = new La16(input, input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float scaledInput = input / 255F; + Assert.Equal(scaledInput, actual.X); + Assert.Equal(scaledInput, actual.Y); + Assert.Equal(scaledInput, actual.Z); + Assert.Equal(scaledInput, actual.W); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToVector4(byte input) - { - // Arrange - var gray = new La16(input, input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float scaledInput = input / 255F; - Assert.Equal(scaledInput, actual.X); - Assert.Equal(scaledInput, actual.Y); - Assert.Equal(scaledInput, actual.Z); - Assert.Equal(scaledInput, actual.W); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromRgba32(byte rgb) + { + // Arrange + La16 gray = default; + byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromRgba32(byte rgb) - { - // Arrange - La16 gray = default; - byte expected = ColorNumerics.Get8BitBT709Luminance(rgb, rgb, rgb); + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + byte actual = gray.L; - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - byte actual = gray.L; + // Assert + Assert.Equal(expected, actual); + Assert.Equal(255, gray.A); + } - // Assert - Assert.Equal(expected, actual); - Assert.Equal(255, gray.A); - } + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_ToRgba32(byte luminance) + { + // Arrange + var gray = new La16(luminance, luminance); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(luminance, actual.R); + Assert.Equal(luminance, actual.G); + Assert.Equal(luminance, actual.B); + Assert.Equal(luminance, actual.A); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_ToRgba32(byte luminance) - { - // Arrange - var gray = new La16(luminance, luminance); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(luminance, actual.R); - Assert.Equal(luminance, actual.G); - Assert.Equal(luminance, actual.B); - Assert.Equal(luminance, actual.A); - } + [Fact] + public void La16_FromBgra5551() + { + // arrange + var grey = default(La16); + byte expected = byte.MaxValue; - [Fact] - public void La16_FromBgra5551() - { - // arrange - var grey = default(La16); - byte expected = byte.MaxValue; + // act + grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // act - grey.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // assert + Assert.Equal(expected, grey.L); + Assert.Equal(expected, grey.A); + } - // assert - Assert.Equal(expected, grey.L); - Assert.Equal(expected, grey.A); - } + public class Rgba32Compatibility + { + // ReSharper disable once MemberHidesStaticFromOuterClass + public static readonly TheoryData LuminanceData = La16Tests.LuminanceData; - public class Rgba32Compatibility + [Theory] + [MemberData(nameof(LuminanceData))] + public void La16_FromRgba32_IsInverseOf_ToRgba32(byte luminance) { - // ReSharper disable once MemberHidesStaticFromOuterClass - public static readonly TheoryData LuminanceData = La16Tests.LuminanceData; + var original = new La16(luminance, luminance); - [Theory] - [MemberData(nameof(LuminanceData))] - public void La16_FromRgba32_IsInverseOf_ToRgba32(byte luminance) - { - var original = new La16(luminance, luminance); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + La16 mirror = default; + mirror.FromRgba32(rgba); - La16 mirror = default; - mirror.FromRgba32(rgba); - - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) - { - var original = new La16(luminance, luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) + { + var original = new La16(luminance, luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - La16 mirror = default; - mirror.FromRgba32(rgba); + La16 mirror = default; + mirror.FromRgba32(rgba); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToVector4_IsRgba32Compatible(byte luminance) - { - var original = new La16(luminance, luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - var la16Vector = original.ToVector4(); - var rgbaVector = 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] - [MemberData(nameof(LuminanceData))] - public void FromVector4_IsRgba32Compatible(byte luminance) - { - var original = new La16(luminance, luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - var rgbaVector = original.ToVector4(); + var rgbaVector = original.ToVector4(); - La16 mirror = default; - mirror.FromVector4(rgbaVector); + La16 mirror = default; + mirror.FromVector4(rgbaVector); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); + } - [Theory] - [MemberData(nameof(LuminanceData))] - public void ToScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new La16(luminance, luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void ToScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Vector4 la16Vector = original.ToScaledVector4(); - Vector4 rgbaVector = 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] - [MemberData(nameof(LuminanceData))] - public void FromScaledVector4_IsRgba32Compatible(byte luminance) - { - var original = new La16(luminance, luminance); + [Theory] + [MemberData(nameof(LuminanceData))] + public void FromScaledVector4_IsRgba32Compatible(byte luminance) + { + var original = new La16(luminance, luminance); - Rgba32 rgba = default; - original.ToRgba32(ref rgba); + Rgba32 rgba = default; + original.ToRgba32(ref rgba); - Vector4 rgbaVector = original.ToScaledVector4(); + Vector4 rgbaVector = original.ToScaledVector4(); - La16 mirror = default; - mirror.FromScaledVector4(rgbaVector); + La16 mirror = default; + mirror.FromScaledVector4(rgbaVector); - Assert.Equal(original, mirror); - } + Assert.Equal(original, mirror); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs index 0db1ed62d4..d333818c78 100644 --- a/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/La32Tests.cs @@ -3,167 +3,165 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class La32Tests { - [Trait("Category", "PixelFormats")] - public class La32Tests + [Fact] + public void AreEqual() + { + var color1 = new La32(3000, 100); + var color2 = new La32(3000, 100); + + Assert.Equal(color1, color2); + } + + [Fact] + public void AreNotEqual() + { + var color1 = new La32(12345, 100); + var color2 = new La32(54321, 100); + + Assert.NotEqual(color1, color2); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(65535, 4294967295)] + [InlineData(32767, 2147450879)] + [InlineData(42, 2752554)] + public void La32_PackedValue_EqualsInput(ushort input, uint packed) + => Assert.Equal(packed, new La32(input, input).PackedValue); + + [Fact] + public void La32_FromScaledVector4() + { + // Arrange + La32 gray = default; + const ushort expected = 32767; + Vector4 scaled = new La32(expected, expected).ToScaledVector4(); + + // Act + gray.FromScaledVector4(scaled); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToScaledVector4(ushort input) + { + // Arrange + var gray = new La32(input, input); + + // Act + Vector4 actual = gray.ToScaledVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(vectorInput, actual.W); + } + + [Fact] + public void La32_FromVector4() + { + // Arrange + La32 gray = default; + const ushort expected = 32767; + var vector = new La32(expected, expected).ToVector4(); + + // Act + gray.FromVector4(vector); + ushort actual = gray.L; + ushort actualA = gray.A; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(expected, actualA); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(32767)] + public void La32_ToVector4(ushort input) { - [Fact] - public void AreEqual() - { - var color1 = new La32(3000, 100); - var color2 = new La32(3000, 100); - - Assert.Equal(color1, color2); - } - - [Fact] - public void AreNotEqual() - { - var color1 = new La32(12345, 100); - var color2 = new La32(54321, 100); - - Assert.NotEqual(color1, color2); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(65535, 4294967295)] - [InlineData(32767, 2147450879)] - [InlineData(42, 2752554)] - public void La32_PackedValue_EqualsInput(ushort input, uint packed) - => Assert.Equal(packed, new La32(input, input).PackedValue); - - [Fact] - public void La32_FromScaledVector4() - { - // Arrange - La32 gray = default; - const ushort expected = 32767; - Vector4 scaled = new La32(expected, expected).ToScaledVector4(); - - // Act - gray.FromScaledVector4(scaled); - ushort actual = gray.L; - ushort actualA = gray.A; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(expected, actualA); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void La32_ToScaledVector4(ushort input) - { - // Arrange - var gray = new La32(input, input); - - // Act - Vector4 actual = gray.ToScaledVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(vectorInput, actual.W); - } - - [Fact] - public void La32_FromVector4() - { - // Arrange - La32 gray = default; - const ushort expected = 32767; - var vector = new La32(expected, expected).ToVector4(); - - // Act - gray.FromVector4(vector); - ushort actual = gray.L; - ushort actualA = gray.A; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(expected, actualA); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(32767)] - public void La32_ToVector4(ushort input) - { - // Arrange - var gray = new La32(input, input); - - // Act - var actual = gray.ToVector4(); - - // Assert - float vectorInput = input / 65535F; - Assert.Equal(vectorInput, actual.X); - Assert.Equal(vectorInput, actual.Y); - Assert.Equal(vectorInput, actual.Z); - Assert.Equal(vectorInput, actual.W); - } - - [Fact] - public void La32_FromRgba32() - { - // Arrange - La32 gray = default; - const byte rgb = 128; - ushort scaledRgb = ColorNumerics.UpscaleFrom8BitTo16Bit(rgb); - ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); - - // Act - gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); - ushort actual = gray.L; - - // Assert - Assert.Equal(expected, actual); - Assert.Equal(ushort.MaxValue, gray.A); - } - - [Theory] - [InlineData(0)] - [InlineData(65535)] - [InlineData(8100)] - public void La32_ToRgba32(ushort input) - { - // Arrange - ushort expected = ColorNumerics.DownScaleFrom16BitTo8Bit(input); - var gray = new La32(input, ushort.MaxValue); - - // Act - Rgba32 actual = default; - gray.ToRgba32(ref actual); - - // Assert - Assert.Equal(expected, actual.R); - Assert.Equal(expected, actual.G); - Assert.Equal(expected, actual.B); - Assert.Equal(byte.MaxValue, actual.A); - } - - [Fact] - public void La32_FromBgra5551() - { - // arrange - var gray = default(La32); - ushort expected = ushort.MaxValue; - - // act - gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, gray.L); - Assert.Equal(expected, gray.A); - } + // Arrange + var gray = new La32(input, input); + + // Act + var actual = gray.ToVector4(); + + // Assert + float vectorInput = input / 65535F; + Assert.Equal(vectorInput, actual.X); + Assert.Equal(vectorInput, actual.Y); + Assert.Equal(vectorInput, actual.Z); + Assert.Equal(vectorInput, actual.W); + } + + [Fact] + public void La32_FromRgba32() + { + // Arrange + La32 gray = default; + const byte rgb = 128; + ushort scaledRgb = ColorNumerics.UpscaleFrom8BitTo16Bit(rgb); + ushort expected = ColorNumerics.Get16BitBT709Luminance(scaledRgb, scaledRgb, scaledRgb); + + // Act + gray.FromRgba32(new Rgba32(rgb, rgb, rgb)); + ushort actual = gray.L; + + // Assert + Assert.Equal(expected, actual); + Assert.Equal(ushort.MaxValue, gray.A); + } + + [Theory] + [InlineData(0)] + [InlineData(65535)] + [InlineData(8100)] + public void La32_ToRgba32(ushort input) + { + // Arrange + ushort expected = ColorNumerics.DownScaleFrom16BitTo8Bit(input); + var gray = new La32(input, ushort.MaxValue); + + // Act + Rgba32 actual = default; + gray.ToRgba32(ref actual); + + // Assert + Assert.Equal(expected, actual.R); + Assert.Equal(expected, actual.G); + Assert.Equal(expected, actual.B); + Assert.Equal(byte.MaxValue, actual.A); + } + + [Fact] + public void La32_FromBgra5551() + { + // arrange + var gray = default(La32); + ushort expected = ushort.MaxValue; + + // act + gray.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, gray.L); + Assert.Equal(expected, gray.A); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index 8d1050be46..14c590d0b5 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -3,83 +3,81 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class NormalizedByte2Tests { - [Trait("Category", "PixelFormats")] - public class NormalizedByte2Tests + [Fact] + public void NormalizedByte2_PackedValue() { - [Fact] - public void NormalizedByte2_PackedValue() - { - Assert.Equal(0xda0d, new NormalizedByte2(0.1f, -0.3f).PackedValue); - Assert.Equal(0x0, new NormalizedByte2(Vector2.Zero).PackedValue); - Assert.Equal(0x7F7F, new NormalizedByte2(Vector2.One).PackedValue); - Assert.Equal(0x8181, new NormalizedByte2(-Vector2.One).PackedValue); - } + Assert.Equal(0xda0d, new NormalizedByte2(0.1f, -0.3f).PackedValue); + Assert.Equal(0x0, new NormalizedByte2(Vector2.Zero).PackedValue); + Assert.Equal(0x7F7F, new NormalizedByte2(Vector2.One).PackedValue); + Assert.Equal(0x8181, new NormalizedByte2(-Vector2.One).PackedValue); + } - [Fact] - public void NormalizedByte2_ToVector2() - { - Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.Zero, new NormalizedByte2(Vector2.Zero).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedByte2(-Vector2.One).ToVector2()); - Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One * 1234.0f).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); - } + [Fact] + public void NormalizedByte2_ToVector2() + { + Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.Zero, new NormalizedByte2(Vector2.Zero).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedByte2(-Vector2.One).ToVector2()); + Assert.Equal(Vector2.One, new NormalizedByte2(Vector2.One * 1234.0f).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); + } - [Fact] - public void NormalizedByte2_ToVector4() - { - Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedByte2(Vector2.One).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedByte2(Vector2.Zero).ToVector4()); - } + [Fact] + public void NormalizedByte2_ToVector4() + { + Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedByte2(Vector2.One).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedByte2(Vector2.Zero).ToVector4()); + } - [Fact] - public void NormalizedByte2_ToScaledVector4() - { - // arrange - var byte2 = new NormalizedByte2(-Vector2.One); + [Fact] + public void NormalizedByte2_ToScaledVector4() + { + // arrange + var byte2 = new NormalizedByte2(-Vector2.One); - // act - Vector4 actual = byte2.ToScaledVector4(); + // act + Vector4 actual = byte2.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1F, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1F, actual.W); + } - [Fact] - public void NormalizedByte2_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedByte2(-Vector2.One).ToScaledVector4(); - var byte2 = default(NormalizedByte2); - uint expected = 0x8181; + [Fact] + public void NormalizedByte2_FromScaledVector4() + { + // arrange + Vector4 scaled = new NormalizedByte2(-Vector2.One).ToScaledVector4(); + var byte2 = default(NormalizedByte2); + uint expected = 0x8181; - // act - byte2.FromScaledVector4(scaled); - uint actual = byte2.PackedValue; + // act + byte2.FromScaledVector4(scaled); + uint actual = byte2.PackedValue; - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void NormalizedByte2_FromBgra5551() - { - // arrange - var normalizedByte2 = default(NormalizedByte2); - var expected = new Vector4(1, 1, 0, 1); + [Fact] + public void NormalizedByte2_FromBgra5551() + { + // arrange + var normalizedByte2 = default(NormalizedByte2); + var expected = new Vector4(1, 1, 0, 1); - // act - normalizedByte2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + normalizedByte2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, normalizedByte2.ToVector4()); - } + // assert + Assert.Equal(expected, normalizedByte2.ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index d97cd79cc2..ca73a6c1f4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -3,233 +3,231 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class NormalizedByte4Tests { - [Trait("Category", "PixelFormats")] - public class NormalizedByte4Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new NormalizedByte4(new Vector4(0.0f)); - var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new NormalizedByte4(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new NormalizedByte4(new Vector4(1.0f)); - var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new NormalizedByte4(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void NormalizedByte4_PackedValues() - { - Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).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); - } - - [Fact] - public void NormalizedByte4_ToVector4() - { - Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); - } - - [Fact] - public void NormalizedByte4_ToScaledVector4() - { - // arrange - var short4 = new NormalizedByte4(-Vector4.One); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); - } - - [Fact] - public void NormalizedByte4_FromScaledVector4() - { - // arrange - var pixel = default(NormalizedByte4); - Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); - uint expected = 0x81818181; - - // act - pixel.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedByte4_FromArgb32() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromBgr24() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromGrey8() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromGrey16() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgb24() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgba32() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromBgra5551() - { - // arrange - var normalizedByte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - normalizedByte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, normalizedByte4.ToVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgb48() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_FromRgba64() - { - // arrange - var byte4 = default(NormalizedByte4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedByte4_ToRgba32() - { - // arrange - var byte4 = new NormalizedByte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - byte4.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedByte4(new Vector4(0.0f)); + var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new NormalizedByte4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new NormalizedByte4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedByte4(new Vector4(1.0f)); + var color3 = new NormalizedByte4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new NormalizedByte4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void NormalizedByte4_PackedValues() + { + Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).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); + } + + [Fact] + public void NormalizedByte4_ToVector4() + { + Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new NormalizedByte4(Vector4.Zero).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedByte4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.One, new NormalizedByte4(Vector4.One * 1234.0f).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); + } + + [Fact] + public void NormalizedByte4_ToScaledVector4() + { + // arrange + var short4 = new NormalizedByte4(-Vector4.One); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(0, actual.W); + } + + [Fact] + public void NormalizedByte4_FromScaledVector4() + { + // arrange + var pixel = default(NormalizedByte4); + Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); + uint expected = 0x81818181; + + // act + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedByte4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromBgra5551() + { + // arrange + var normalizedByte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + normalizedByte4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedByte4.ToVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedByte4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedByte4_ToRgba32() + { + // arrange + var byte4 = new NormalizedByte4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index 0de351d2ad..70bfa1be71 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -3,87 +3,85 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class NormalizedShort2Tests { - [Trait("Category", "PixelFormats")] - public class NormalizedShort2Tests + [Fact] + public void NormalizedShort2_PackedValue() { - [Fact] - public void NormalizedShort2_PackedValue() - { - Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); - Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).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); + Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); + Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).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); - } + // TODO: I don't think this can ever pass since the bytes are already truncated. + // Assert.Equal(3650751693, n.PackedValue); + } - [Fact] - public void NormalizedShort2_ToVector2() - { - Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One).ToVector2()); - Assert.Equal(Vector2.Zero, new NormalizedShort2(Vector2.Zero).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedShort2(-Vector2.One).ToVector2()); - Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One * 1234.0f).ToVector2()); - Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); - } + [Fact] + public void NormalizedShort2_ToVector2() + { + Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One).ToVector2()); + Assert.Equal(Vector2.Zero, new NormalizedShort2(Vector2.Zero).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedShort2(-Vector2.One).ToVector2()); + Assert.Equal(Vector2.One, new NormalizedShort2(Vector2.One * 1234.0f).ToVector2()); + Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); + } - [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()); - } + [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()); + } - [Fact] - public void NormalizedShort2_ToScaledVector4() - { - // arrange - var short2 = new NormalizedShort2(-Vector2.One); + [Fact] + public void NormalizedShort2_ToScaledVector4() + { + // arrange + var short2 = new NormalizedShort2(-Vector2.One); - // act - Vector4 actual = short2.ToScaledVector4(); + // act + Vector4 actual = short2.ToScaledVector4(); - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // assert + Assert.Equal(0, actual.X); + Assert.Equal(0, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } - [Fact] - public void NormalizedShort2_FromScaledVector4() - { - // arrange - Vector4 scaled = new NormalizedShort2(-Vector2.One).ToScaledVector4(); - var short2 = default(NormalizedShort2); - uint expected = 0x80018001; + [Fact] + public void NormalizedShort2_FromScaledVector4() + { + // arrange + Vector4 scaled = new NormalizedShort2(-Vector2.One).ToScaledVector4(); + var short2 = default(NormalizedShort2); + uint expected = 0x80018001; - // act - short2.FromScaledVector4(scaled); - uint actual = short2.PackedValue; + // act + short2.FromScaledVector4(scaled); + uint actual = short2.PackedValue; - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void NormalizedShort2_FromBgra5551() - { - // arrange - var normalizedShort2 = default(NormalizedShort2); - var expected = new Vector4(1, 1, 0, 1); + [Fact] + public void NormalizedShort2_FromBgra5551() + { + // arrange + var normalizedShort2 = default(NormalizedShort2); + var expected = new Vector4(1, 1, 0, 1); - // act - normalizedShort2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + normalizedShort2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, normalizedShort2.ToVector4()); - } + // assert + Assert.Equal(expected, normalizedShort2.ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index d30b3d1db9..997c6df82b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -3,234 +3,232 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class NormalizedShort4Tests { - [Trait("Category", "PixelFormats")] - public class NormalizedShort4Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new NormalizedShort4(new Vector4(0.0f)); - var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new NormalizedShort4(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new NormalizedShort4(new Vector4(1.0f)); - var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new NormalizedShort4(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void NormalizedShort4_PackedValues() - { - Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).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); - } - - [Fact] - public void NormalizedShort4_ToVector4() - { - // Test ToVector4 - Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4()); - Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4()); - Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); - } - - [Fact] - public void NormalizedShort4_ToScaledVector4() - { - // arrange - var short4 = new NormalizedShort4(Vector4.One); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void NormalizedShort4_FromScaledVector4() - { - // arrange - var pixel = default(NormalizedShort4); - Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); - ulong expected = 0x7FFF7FFF7FFF7FFF; - - // act - pixel.FromScaledVector4(scaled); - ulong actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedShort4_FromArgb32() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromBgr24() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromGrey8() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromGrey16() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgb24() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgba32() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgb48() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_FromRgba64() - { - // arrange - var byte4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expected, byte4.ToScaledVector4()); - } - - [Fact] - public void NormalizedShort4_ToRgba32() - { - // arrange - var byte4 = new NormalizedShort4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); - var expected = new Rgba32(Vector4.One); - var actual = default(Rgba32); - - // act - byte4.ToRgba32(ref actual); - - Assert.Equal(expected, actual); - } - - [Fact] - public void NormalizedShort4_FromBgra5551() - { - // arrange - var normalizedShort4 = default(NormalizedShort4); - Vector4 expected = Vector4.One; - - // act - normalizedShort4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, normalizedShort4.ToVector4()); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedShort4(new Vector4(0.0f)); + var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new NormalizedShort4(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new NormalizedShort4(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new NormalizedShort4(new Vector4(1.0f)); + var color3 = new NormalizedShort4(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new NormalizedShort4(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void NormalizedShort4_PackedValues() + { + Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).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); + } + + [Fact] + public void NormalizedShort4_ToVector4() + { + // Test ToVector4 + Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new NormalizedShort4(Vector4.Zero).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedShort4(-Vector4.One).ToVector4()); + Assert.Equal(Vector4.One, new NormalizedShort4(Vector4.One * 1234.0f).ToVector4()); + Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); + } + + [Fact] + public void NormalizedShort4_ToScaledVector4() + { + // arrange + var short4 = new NormalizedShort4(Vector4.One); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void NormalizedShort4_FromScaledVector4() + { + // arrange + var pixel = default(NormalizedShort4); + Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); + ulong expected = 0x7FFF7FFF7FFF7FFF; + + // act + pixel.FromScaledVector4(scaled); + ulong actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_FromArgb32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromBgr24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey8() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromGrey16() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb24() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba32() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgb48() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_FromRgba64() + { + // arrange + var byte4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + byte4.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expected, byte4.ToScaledVector4()); + } + + [Fact] + public void NormalizedShort4_ToRgba32() + { + // arrange + var byte4 = new NormalizedShort4(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var expected = new Rgba32(Vector4.One); + var actual = default(Rgba32); + + // act + byte4.ToRgba32(ref actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void NormalizedShort4_FromBgra5551() + { + // arrange + var normalizedShort4 = default(NormalizedShort4); + Vector4 expected = Vector4.One; + + // act + normalizedShort4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, normalizedShort4.ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index e0e417e510..68cdf2d633 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -1,98 +1,95 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class PixelBlenderTests { - [Trait("Category", "PixelFormats")] - public class PixelBlenderTests + public static TheoryData BlenderMappings = new() { - public static TheoryData BlenderMappings = new() - { - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { 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.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { 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.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { 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.NormalSrcOver), PixelColorBlendingMode.Normal }, + { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, + { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, + { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, + { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, + { 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 }, + }; - [Theory] - [MemberData(nameof(BlenderMappings))] - public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) - where TPixel : unmanaged, IPixel - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - Assert.IsType(type, blender); - } + [Theory] + [MemberData(nameof(BlenderMappings))] + public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) + where TPixel : unmanaged, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); + Assert.IsType(type, blender); + } - public static TheoryData ColorBlendingExpectedResults = new() - { - { 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) }, - }; + public static TheoryData ColorBlendingExpectedResults = new() + { + { 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] - [MemberData(nameof(ColorBlendingExpectedResults))] - 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); + [Theory] + [MemberData(nameof(ColorBlendingExpectedResults))] + 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()); - } + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); + } - public static TheoryData AlphaCompositionExpectedResults = new() - { - { 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 }, - }; + public static TheoryData AlphaCompositionExpectedResults = new() + { + { 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] - [MemberData(nameof(AlphaCompositionExpectedResults))] - public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) - { - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); + [Theory] + [MemberData(nameof(AlphaCompositionExpectedResults))] + public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode); - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); + Rgba32 actualResult = blender.Blend(backdrop, source, opacity); - // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); - } + // 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 02858743a2..c81b0a74ff 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -1,59 +1,58 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders -{ - using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +using Xunit; - using Xunit; +public class PorterDuffCompositorTests +{ + // TODO: Add other modes to compare. + public static readonly TheoryData CompositingOperators = + new TheoryData + { + PixelAlphaCompositionMode.Src, + PixelAlphaCompositionMode.SrcAtop, + PixelAlphaCompositionMode.SrcOver, + PixelAlphaCompositionMode.SrcIn, + PixelAlphaCompositionMode.SrcOut, + PixelAlphaCompositionMode.Dest, + PixelAlphaCompositionMode.DestAtop, + PixelAlphaCompositionMode.DestOver, + PixelAlphaCompositionMode.DestIn, + PixelAlphaCompositionMode.DestOut, + PixelAlphaCompositionMode.Clear, + PixelAlphaCompositionMode.Xor + }; - public class PorterDuffCompositorTests + [Theory] + [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] + public void PorterDuffOutputIsCorrect(TestImageProvider provider, PixelAlphaCompositionMode mode) { - // TODO: Add other modes to compare. - public static readonly TheoryData CompositingOperators = - new TheoryData - { - PixelAlphaCompositionMode.Src, - PixelAlphaCompositionMode.SrcAtop, - PixelAlphaCompositionMode.SrcOver, - PixelAlphaCompositionMode.SrcIn, - PixelAlphaCompositionMode.SrcOut, - PixelAlphaCompositionMode.Dest, - PixelAlphaCompositionMode.DestAtop, - PixelAlphaCompositionMode.DestOver, - PixelAlphaCompositionMode.DestIn, - PixelAlphaCompositionMode.DestOut, - PixelAlphaCompositionMode.Clear, - PixelAlphaCompositionMode.Xor - }; - - [Theory] - [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] - public void PorterDuffOutputIsCorrect(TestImageProvider provider, PixelAlphaCompositionMode mode) + var srcFile = TestFile.Create(TestImages.Png.PDSrc); + using (Image src = srcFile.CreateRgba32Image()) + using (Image dest = provider.GetImage()) { - var srcFile = TestFile.Create(TestImages.Png.PDSrc); - using (Image src = srcFile.CreateRgba32Image()) - using (Image dest = provider.GetImage()) + var options = new GraphicsOptions { - var options = new GraphicsOptions - { - Antialias = false, - AlphaCompositionMode = mode - }; - - using (Image res = dest.Clone(x => x.DrawImage(src, options))) - { - string combinedMode = mode.ToString(); + Antialias = false, + AlphaCompositionMode = mode + }; - if (combinedMode != "Src" && combinedMode.StartsWith("Src")) - { - combinedMode = combinedMode.Substring(3); - } + using (Image res = dest.Clone(x => x.DrawImage(src, options))) + { + string combinedMode = mode.ToString(); - res.DebugSave(provider, combinedMode); - res.CompareToReferenceOutput(provider, combinedMode); + 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 f87622ac35..45dece8ec8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -4,144 +4,142 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; + +public class PorterDuffFunctionsTests { - 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) } - }; - - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.NormalSrcOver((Vector4)back, source, amount); - 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) } - }; - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); - 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) } - }; - - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); - 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) } - }; - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.SubtractSrcOver((Vector4)back, source, amount); - 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) } - }; - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.ScreenSrcOver((Vector4)back, source, amount); - 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) } - }; - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.DarkenSrcOver((Vector4)back, source, amount); - 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) }, - }; - - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.LightenSrcOver((Vector4)back, source, amount); - 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) }, - }; - - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.OverlaySrcOver((Vector4)back, source, amount); - 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) }, - }; - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) - { - Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount); - VectorAssert.Equal(expected, actual, 5); - } + 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] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.NormalSrcOver((Vector4)back, source, amount); + 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) } + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); + 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) } + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplySrcOver((Vector4)back, source, amount); + 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) } + }; + + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.SubtractSrcOver((Vector4)back, source, amount); + 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) } + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.ScreenSrcOver((Vector4)back, source, amount); + 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) } + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.DarkenSrcOver((Vector4)back, source, amount); + 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) }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.LightenSrcOver((Vector4)back, source, amount); + 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) }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.OverlaySrcOver((Vector4)back, source, amount); + 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) }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.HardLightSrcOver((Vector4)back, source, amount); + VectorAssert.Equal(expected, actual, 5); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs index d95913c1a8..d375a74c67 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -1,386 +1,383 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; + +public class PorterDuffFunctionsTestsTPixel { - public class PorterDuffFunctionsTestsTPixel + private static Span AsSpan(T value) + where T : struct { - private static Span AsSpan(T value) - where T : struct - { - return new Span(new[] { value }); - } + 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; + private Configuration Configuration => Configuration.Default; - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(NormalBlendFunctionData))] - public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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) }, - { - 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) - }, - }; - - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 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) }, + { + 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) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(MultiplyFunctionData))] - public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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) - }, - { - 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 : unmanaged, IPixel + public static TheoryData AddFunctionData = new TheoryData + { { - TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); + 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) + }, + { + 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 AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(AddFunctionData))] - public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 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) }, - { - 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) - }, - }; - - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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); + } - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 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) }, + { + 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) + }, + }; + + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(SubtractFunctionData))] - public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 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) }, - { - 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) - }, - }; - - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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); + } - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 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) }, + { + 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) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(ScreenFunctionData))] - public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 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) }, - { - 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) - }, - }; - - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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); + } - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 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) }, + { + 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) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(DarkenFunctionData))] - public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 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) }, - { - 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 : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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); + } - [Theory] - [MemberData(nameof(LightenFunctionData))] - public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel + 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) }, { - TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); + 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 LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - 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); - } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 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) }, - { - 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 : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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); + } - [Theory] - [MemberData(nameof(OverlayFunctionData))] - public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel + 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) }, { - 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); + 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) } + }; - 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), - .5f, - new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - }, - }; - - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); - VectorAssert.Equal(expected.AsPixel(), actual, 2); - } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } - [Theory] - [MemberData(nameof(HardLightFunctionData))] - public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : unmanaged, IPixel - { - var dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); - } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + 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) }, + { + 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(HardLightFunctionData))] + public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + VectorAssert.Equal(expected.AsPixel(), actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : unmanaged, IPixel + { + var dest = new Span(new TPixel[1]); + new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 2d46084343..006cb9eb61 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -1,139 +1,135 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.PixelFormats; -namespace SixLabors.ImageSharp.Tests.PixelFormats +[Trait("Category", "PixelFormats")] +public abstract partial class PixelConverterTests { - [Trait("Category", "PixelFormats")] - public abstract partial class PixelConverterTests + public static class ReferenceImplementations { - public static class ReferenceImplementations + public static byte[] MakeRgba32ByteArray(byte r, byte g, byte b, byte a) { - public static byte[] MakeRgba32ByteArray(byte r, byte g, byte b, byte a) - { - var buffer = new byte[256]; - - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = r; - buffer[i + 1] = g; - buffer[i + 2] = b; - buffer[i + 3] = a; - } + var buffer = new byte[256]; - return buffer; + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = r; + buffer[i + 1] = g; + buffer[i + 2] = b; + buffer[i + 3] = a; } - public static byte[] MakeArgb32ByteArray(byte r, byte g, byte b, byte a) - { - var buffer = new byte[256]; + return buffer; + } - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = a; - buffer[i + 1] = r; - buffer[i + 2] = g; - buffer[i + 3] = b; - } + public static byte[] MakeArgb32ByteArray(byte r, byte g, byte b, byte a) + { + var buffer = new byte[256]; - return buffer; + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = a; + buffer[i + 1] = r; + buffer[i + 2] = g; + buffer[i + 3] = b; } - public static byte[] MakeBgra32ByteArray(byte r, byte g, byte b, byte a) - { - var buffer = new byte[256]; + return buffer; + } - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = b; - buffer[i + 1] = g; - buffer[i + 2] = r; - buffer[i + 3] = a; - } + public static byte[] MakeBgra32ByteArray(byte r, byte g, byte b, byte a) + { + var buffer = new byte[256]; - return buffer; + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = b; + buffer[i + 1] = g; + buffer[i + 2] = r; + buffer[i + 3] = a; } - public static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) - { - var buffer = new byte[256]; + return buffer; + } - for (int i = 0; i < buffer.Length; i += 4) - { - buffer[i] = a; - buffer[i + 1] = b; - buffer[i + 2] = g; - buffer[i + 3] = r; - } + public static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) + { + var buffer = new byte[256]; - return buffer; + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = a; + buffer[i + 1] = b; + buffer[i + 2] = g; + buffer[i + 3] = r; } - internal static void To( - Configuration configuration, - ReadOnlySpan sourcePixels, - Span destinationPixels) - where TSourcePixel : unmanaged, IPixel - where TDestinationPixel : unmanaged, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + return buffer; + } - int count = sourcePixels.Length; - ref TSourcePixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + internal static void To( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + where TSourcePixel : unmanaged, IPixel + where TDestinationPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); - if (typeof(TSourcePixel) == typeof(TDestinationPixel)) - { - Span uniformDest = - MemoryMarshal.Cast(destinationPixels); - sourcePixels.CopyTo(uniformDest); - return; - } + int count = sourcePixels.Length; + ref TSourcePixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); - // L8 and L16 are special implementations of IPixel in that they do not conform to the - // standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm. - // One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and - // 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)); - 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); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); - } - - return; - } + if (typeof(TSourcePixel) == typeof(TDestinationPixel)) + { + Span uniformDest = + MemoryMarshal.Cast(destinationPixels); + sourcePixels.CopyTo(uniformDest); + return; + } - if (typeof(TDestinationPixel) == typeof(L8)) + // L8 and L16 are special implementations of IPixel in that they do not conform to the + // standard RGBA colorspace format and must be converted from RGBA using the special ITU BT709 algorithm. + // One of the requirements of FromScaledVector4/ToScaledVector4 is that it unaware of this and + // 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)); + for (int i = 0; i < count; i++) { - 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); - dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); - } - - return; + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref l16Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } - // Normal conversion - ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); + return; + } + + if (typeof(TDestinationPixel) == typeof(L8)) + { + 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 TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - dp.FromScaledVector4(sp.ToScaledVector4()); + ref L8 dp = ref Unsafe.Add(ref l8Ref, i); + dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } + + return; + } + + // Normal conversion + ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); + for (int i = 0; i < count; i++) + { + ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); + ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); + dp.FromScaledVector4(sp.ToScaledVector4()); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index f416882742..41a6bfc4fb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -3,180 +3,177 @@ using SixLabors.ImageSharp.PixelFormats.Utils; -using Xunit; +namespace SixLabors.ImageSharp.Tests.PixelFormats; -namespace SixLabors.ImageSharp.Tests.PixelFormats +[Trait("Category", "PixelFormats")] +public abstract partial class PixelConverterTests { - [Trait("Category", "PixelFormats")] - public abstract partial class PixelConverterTests + public static readonly TheoryData RgbaData = + new() + { + { 0, 0, 0, 0 }, + { 0, 0, 0, 255 }, + { 0, 0, 255, 0 }, + { 0, 255, 0, 0 }, + { 255, 0, 0, 0 }, + { 255, 255, 255, 255 }, + { 0, 0, 0, 1 }, + { 0, 0, 1, 0 }, + { 0, 1, 0, 0 }, + { 1, 0, 0, 0 }, + { 3, 5, 7, 11 }, + { 67, 71, 101, 109 } + }; + + public class FromRgba32 : PixelConverterTests { - public static readonly TheoryData RgbaData = - new() - { - { 0, 0, 0, 0 }, - { 0, 0, 0, 255 }, - { 0, 0, 255, 0 }, - { 0, 255, 0, 0 }, - { 255, 0, 0, 0 }, - { 255, 255, 255, 255 }, - { 0, 0, 0, 1 }, - { 0, 0, 1, 0 }, - { 0, 1, 0, 0 }, - { 1, 0, 0, 0 }, - { 3, 5, 7, 11 }, - { 67, 71, 101, 109 } - }; - - public class FromRgba32 : PixelConverterTests + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromRgba32.ToArgb32(source, actual); + PixelConverter.FromRgba32.ToArgb32(source, actual); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromRgba32.ToBgra32(source, actual); + PixelConverter.FromRgba32.ToBgra32(source, actual); - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToAbgr32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToAbgr32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromRgba32.ToAbgr32(source, actual); + PixelConverter.FromRgba32.ToAbgr32(source, actual); - byte[] expected = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } + } - public class FromArgb32 : PixelConverterTests + public class FromArgb32 : PixelConverterTests + { + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromArgb32.ToRgba32(source, actual); + PixelConverter.FromArgb32.ToRgba32(source, actual); - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromArgb32.ToBgra32(source, actual); + PixelConverter.FromArgb32.ToBgra32(source, actual); - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } + } - public class FromBgra32 : PixelConverterTests + public class FromBgra32 : PixelConverterTests + { + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromBgra32.ToArgb32(source, actual); + PixelConverter.FromBgra32.ToArgb32(source, actual); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromBgra32.ToRgba32(source, actual); + PixelConverter.FromBgra32.ToRgba32(source, actual); - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } + } - public class FromAbgr32 : PixelConverterTests + public class FromAbgr32 : PixelConverterTests + { + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) { - [Theory] - [MemberData(nameof(RgbaData))] - public void ToArgb32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromAbgr32.ToArgb32(source, actual); + PixelConverter.FromAbgr32.ToArgb32(source, actual); - byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToRgba32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromAbgr32.ToRgba32(source, actual); + PixelConverter.FromAbgr32.ToRgba32(source, actual); - byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); + } - [Theory] - [MemberData(nameof(RgbaData))] - public void ToBgra32(byte r, byte g, byte b, byte a) - { - byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); - byte[] actual = new byte[source.Length]; + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; - PixelConverter.FromAbgr32.ToBgra32(source, actual); + PixelConverter.FromAbgr32.ToBgra32(source, actual); - byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs index 5f34e413a9..5ba5c1bedf 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs @@ -8,561 +8,588 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; + +public partial class PixelOperationsTests { - public partial class PixelOperationsTests + + public partial class A8_OperationsTests : PixelOperationsTests { - public partial class A8_OperationsTests : PixelOperationsTests + public A8_OperationsTests(ITestOutputHelper output) + : base(output) { - public A8_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => A8.PixelOperations.Instance; + protected override PixelOperations Operations => A8.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Argb32_OperationsTests : PixelOperationsTests + } + + public partial class Argb32_OperationsTests : PixelOperationsTests + { + public Argb32_OperationsTests(ITestOutputHelper output) + : base(output) { - public Argb32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Argb32.PixelOperations.Instance; + protected override PixelOperations Operations => Argb32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Abgr32_OperationsTests : PixelOperationsTests + } + + public partial class Abgr32_OperationsTests : PixelOperationsTests + { + public Abgr32_OperationsTests(ITestOutputHelper output) + : base(output) { - public Abgr32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Abgr32.PixelOperations.Instance; + protected override PixelOperations Operations => Abgr32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Bgr24_OperationsTests : PixelOperationsTests + } + + public partial class Bgr24_OperationsTests : PixelOperationsTests + { + public Bgr24_OperationsTests(ITestOutputHelper output) + : base(output) { - public Bgr24_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Bgr24.PixelOperations.Instance; + protected override PixelOperations Operations => Bgr24.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Bgr565_OperationsTests : PixelOperationsTests + } + + public partial class Bgr565_OperationsTests : PixelOperationsTests + { + public Bgr565_OperationsTests(ITestOutputHelper output) + : base(output) { - public Bgr565_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Bgr565.PixelOperations.Instance; + protected override PixelOperations Operations => Bgr565.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Bgra32_OperationsTests : PixelOperationsTests + } + + public partial class Bgra32_OperationsTests : PixelOperationsTests + { + public Bgra32_OperationsTests(ITestOutputHelper output) + : base(output) { - public Bgra32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Bgra32.PixelOperations.Instance; + protected override PixelOperations Operations => Bgra32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Bgra4444_OperationsTests : PixelOperationsTests + } + + public partial class Bgra4444_OperationsTests : PixelOperationsTests + { + public Bgra4444_OperationsTests(ITestOutputHelper output) + : base(output) { - public Bgra4444_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Bgra4444.PixelOperations.Instance; + protected override PixelOperations Operations => Bgra4444.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Bgra5551_OperationsTests : PixelOperationsTests + } + + public partial class Bgra5551_OperationsTests : PixelOperationsTests + { + public Bgra5551_OperationsTests(ITestOutputHelper output) + : base(output) { - public Bgra5551_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Bgra5551.PixelOperations.Instance; + protected override PixelOperations Operations => Bgra5551.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Byte4_OperationsTests : PixelOperationsTests + } + + public partial class Byte4_OperationsTests : PixelOperationsTests + { + public Byte4_OperationsTests(ITestOutputHelper output) + : base(output) { - public Byte4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Byte4.PixelOperations.Instance; + protected override PixelOperations Operations => Byte4.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class HalfSingle_OperationsTests : PixelOperationsTests + } + + public partial class HalfSingle_OperationsTests : PixelOperationsTests + { + public HalfSingle_OperationsTests(ITestOutputHelper output) + : base(output) { - public HalfSingle_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => HalfSingle.PixelOperations.Instance; + protected override PixelOperations Operations => HalfSingle.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class HalfVector2_OperationsTests : PixelOperationsTests + } + + public partial class HalfVector2_OperationsTests : PixelOperationsTests + { + public HalfVector2_OperationsTests(ITestOutputHelper output) + : base(output) { - public HalfVector2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => HalfVector2.PixelOperations.Instance; + protected override PixelOperations Operations => HalfVector2.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class HalfVector4_OperationsTests : PixelOperationsTests + } + + public partial class HalfVector4_OperationsTests : PixelOperationsTests + { + public HalfVector4_OperationsTests(ITestOutputHelper output) + : base(output) { - public HalfVector4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => HalfVector4.PixelOperations.Instance; + protected override PixelOperations Operations => HalfVector4.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class L16_OperationsTests : PixelOperationsTests + } + + public partial class L16_OperationsTests : PixelOperationsTests + { + public L16_OperationsTests(ITestOutputHelper output) + : base(output) { - public L16_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => L16.PixelOperations.Instance; + protected override PixelOperations Operations => L16.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class L8_OperationsTests : PixelOperationsTests + } + + public partial class L8_OperationsTests : PixelOperationsTests + { + public L8_OperationsTests(ITestOutputHelper output) + : base(output) { - public L8_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => L8.PixelOperations.Instance; + protected override PixelOperations Operations => L8.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class La16_OperationsTests : PixelOperationsTests + } + + public partial class La16_OperationsTests : PixelOperationsTests + { + public La16_OperationsTests(ITestOutputHelper output) + : base(output) { - public La16_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => La16.PixelOperations.Instance; + protected override PixelOperations Operations => La16.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class La32_OperationsTests : PixelOperationsTests + } + + public partial class La32_OperationsTests : PixelOperationsTests + { + public La32_OperationsTests(ITestOutputHelper output) + : base(output) { - public La32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => La32.PixelOperations.Instance; + protected override PixelOperations Operations => La32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class NormalizedByte2_OperationsTests : PixelOperationsTests + } + + public partial class NormalizedByte2_OperationsTests : PixelOperationsTests + { + public NormalizedByte2_OperationsTests(ITestOutputHelper output) + : base(output) { - public NormalizedByte2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => NormalizedByte2.PixelOperations.Instance; + protected override PixelOperations Operations => NormalizedByte2.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class NormalizedByte4_OperationsTests : PixelOperationsTests + } + + public partial class NormalizedByte4_OperationsTests : PixelOperationsTests + { + public NormalizedByte4_OperationsTests(ITestOutputHelper output) + : base(output) { - public NormalizedByte4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => NormalizedByte4.PixelOperations.Instance; + protected override PixelOperations Operations => NormalizedByte4.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class NormalizedShort2_OperationsTests : PixelOperationsTests + } + + public partial class NormalizedShort2_OperationsTests : PixelOperationsTests + { + public NormalizedShort2_OperationsTests(ITestOutputHelper output) + : base(output) { - public NormalizedShort2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => NormalizedShort2.PixelOperations.Instance; + protected override PixelOperations Operations => NormalizedShort2.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class NormalizedShort4_OperationsTests : PixelOperationsTests + } + + public partial class NormalizedShort4_OperationsTests : PixelOperationsTests + { + public NormalizedShort4_OperationsTests(ITestOutputHelper output) + : base(output) { - public NormalizedShort4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => NormalizedShort4.PixelOperations.Instance; + protected override PixelOperations Operations => NormalizedShort4.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Rg32_OperationsTests : PixelOperationsTests + } + + public partial class Rg32_OperationsTests : PixelOperationsTests + { + public Rg32_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rg32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rg32.PixelOperations.Instance; + protected override PixelOperations Operations => Rg32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Rgb24_OperationsTests : PixelOperationsTests + } + + public partial class Rgb24_OperationsTests : PixelOperationsTests + { + public Rgb24_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rgb24_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rgb24.PixelOperations.Instance; + protected override PixelOperations Operations => Rgb24.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Rgb48_OperationsTests : PixelOperationsTests + } + + public partial class Rgb48_OperationsTests : PixelOperationsTests + { + public Rgb48_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rgb48_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rgb48.PixelOperations.Instance; + protected override PixelOperations Operations => Rgb48.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Rgba1010102_OperationsTests : PixelOperationsTests + } + + public partial class Rgba1010102_OperationsTests : PixelOperationsTests + { + public Rgba1010102_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rgba1010102_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rgba1010102.PixelOperations.Instance; + protected override PixelOperations Operations => Rgba1010102.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Rgba32_OperationsTests : PixelOperationsTests + } + + public partial class Rgba32_OperationsTests : PixelOperationsTests + { + public Rgba32_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rgba32_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rgba32.PixelOperations.Instance; + protected override PixelOperations Operations => Rgba32.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Rgba64_OperationsTests : PixelOperationsTests + } + + public partial class Rgba64_OperationsTests : PixelOperationsTests + { + public Rgba64_OperationsTests(ITestOutputHelper output) + : base(output) { - public Rgba64_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Rgba64.PixelOperations.Instance; + protected override PixelOperations Operations => Rgba64.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class RgbaVector_OperationsTests : PixelOperationsTests + } + + public partial class RgbaVector_OperationsTests : PixelOperationsTests + { + public RgbaVector_OperationsTests(ITestOutputHelper output) + : base(output) { - public RgbaVector_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => RgbaVector.PixelOperations.Instance; + protected override PixelOperations Operations => RgbaVector.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } - public partial class Short2_OperationsTests : PixelOperationsTests + } + + public partial class Short2_OperationsTests : PixelOperationsTests + { + public Short2_OperationsTests(ITestOutputHelper output) + : base(output) { - public Short2_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Short2.PixelOperations.Instance; + protected override PixelOperations Operations => Short2.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } - public partial class Short4_OperationsTests : PixelOperationsTests + } + + public partial class Short4_OperationsTests : PixelOperationsTests + { + public Short4_OperationsTests(ITestOutputHelper output) + : base(output) { - public Short4_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations Operations => Short4.PixelOperations.Instance; + protected override PixelOperations Operations => Short4.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } } - diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt index 502b66fb51..5013835935 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.tt @@ -1,11 +1,9 @@ <#@include file="_Common.ttinclude" #> <#@ output extension=".cs" #> -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; + +public partial class PixelOperationsTests { - public partial class PixelOperationsTests - { - <# GenerateAllSpecializedClasses(); #> - } + <# GenerateAllSpecializedClasses(); #> } - diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index 43924556b5..0e7b1f3354 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -69,27 +69,27 @@ using Xunit.Abstractions; }; void GenerateSpecializedClass(string pixelType, string alpha) + {#> + + public partial class <#=pixelType#>_OperationsTests : PixelOperationsTests<<#=pixelType#>> { -#> - public partial class <#=pixelType#>_OperationsTests : PixelOperationsTests<<#=pixelType#>> + public <#=pixelType#>_OperationsTests(ITestOutputHelper output) + : base(output) { - public <#=pixelType#>_OperationsTests(ITestOutputHelper output) - : base(output) - { - } + } - protected override PixelOperations<<#=pixelType#>> Operations => <#=pixelType#>.PixelOperations.Instance; + protected override PixelOperations<<#=pixelType#>> Operations => <#=pixelType#>.PixelOperations.Instance; - [Fact] - public void IsSpecialImplementation() => Assert.IsType<<#=pixelType#>.PixelOperations>(PixelOperations<<#=pixelType#>>.Instance); + [Fact] + public void IsSpecialImplementation() => Assert.IsType<<#=pixelType#>.PixelOperations>(PixelOperations<<#=pixelType#>>.Instance); - [Fact] - public void PixelTypeInfoHasCorrectAlphaRepresentation() - { - var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; - Assert.Equal(<#=alpha#>, alphaRepresentation); - } + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(<#=alpha#>, alphaRepresentation); } + } <#+ } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs index e0f9d55e89..77fa68798e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -3,59 +3,56 @@ using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +[Trait("Category", "PixelFormats")] +public class PixelConversionModifiersExtensionsTests { - [Trait("Category", "PixelFormats")] - public class PixelConversionModifiersExtensionsTests + [Theory] + [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.None, true)] + [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, false)] + [InlineData(PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Premultiply, false)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply, + true)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + true)] + [InlineData( + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, + PixelConversionModifiers.Scale, + true)] + internal void IsDefined( + PixelConversionModifiers baselineModifiers, + PixelConversionModifiers checkModifiers, + bool expected) { - [Theory] - [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.None, true)] - [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, false)] - [InlineData(PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Premultiply, false)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Premultiply, - true)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - true)] - [InlineData( - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale, - PixelConversionModifiers.Scale, - true)] - internal void IsDefined( - PixelConversionModifiers baselineModifiers, - PixelConversionModifiers checkModifiers, - bool expected) - { - Assert.Equal(expected, baselineModifiers.IsDefined(checkModifiers)); - } + Assert.Equal(expected, baselineModifiers.IsDefined(checkModifiers)); + } - [Theory] - [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, - PixelConversionModifiers toRemove, - PixelConversionModifiers expected) - { - PixelConversionModifiers result = baselineModifiers.Remove(toRemove); - Assert.Equal(expected, result); - } + [Theory] + [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, + PixelConversionModifiers toRemove, + PixelConversionModifiers expected) + { + PixelConversionModifiers result = baselineModifiers.Remove(toRemove); + Assert.Equal(expected, result); + } - [Theory] - [InlineData(PixelConversionModifiers.Premultiply, false, PixelConversionModifiers.Premultiply)] - [InlineData(PixelConversionModifiers.Premultiply, true, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)] - internal void ApplyCompanding( - PixelConversionModifiers baselineModifiers, - bool compand, - PixelConversionModifiers expected) - { - PixelConversionModifiers result = baselineModifiers.ApplyCompanding(compand); - Assert.Equal(expected, result); - } + [Theory] + [InlineData(PixelConversionModifiers.Premultiply, false, PixelConversionModifiers.Premultiply)] + [InlineData(PixelConversionModifiers.Premultiply, true, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)] + internal void ApplyCompanding( + PixelConversionModifiers baselineModifiers, + bool compand, + PixelConversionModifiers expected) + { + PixelConversionModifiers result = baselineModifiers.ApplyCompanding(compand); + Assert.Equal(expected, result); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs index e87028155f..e6b65b96b7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Rgba32OperationsTests.cs @@ -5,31 +5,29 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; + +[Trait("Category", "PixelFormats")] +public partial class PixelOperationsTests { - [Trait("Category", "PixelFormats")] - public partial class PixelOperationsTests + public partial class Rgba32_OperationsTests : PixelOperationsTests { - public partial class Rgba32_OperationsTests : PixelOperationsTests + [Fact(Skip = SkipProfilingBenchmarks)] + public void Benchmark_ToVector4() { - [Fact(Skip = SkipProfilingBenchmarks)] - public void Benchmark_ToVector4() - { - const int times = 200000; - const int count = 1024; + const int times = 200000; + const int count = 1024; - using (IMemoryOwner source = Configuration.Default.MemoryAllocator.Allocate(count)) - using (IMemoryOwner dest = Configuration.Default.MemoryAllocator.Allocate(count)) - { - this.Measure( - times, - () => PixelOperations.Instance.ToVector4( - this.Configuration, - source.GetSpan(), - dest.GetSpan())); - } + using (IMemoryOwner source = Configuration.Default.MemoryAllocator.Allocate(count)) + using (IMemoryOwner dest = Configuration.Default.MemoryAllocator.Allocate(count)) + { + this.Measure( + times, + () => PixelOperations.Instance.ToVector4( + this.Configuration, + source.GetSpan(), + dest.GetSpan())); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 6f11e7aecb..a9b3ee9a42 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -12,1252 +10,1250 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Common; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations +namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; + +[Trait("Category", "PixelFormats")] +public partial class PixelOperationsTests { - [Trait("Category", "PixelFormats")] - public partial class PixelOperationsTests - { - [Theory] - [WithBlankImages(1, 1, PixelTypes.All)] - public void GetGlobalInstance(TestImageProvider _) - where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void GetGlobalInstance(TestImageProvider _) + where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); +} - public abstract class PixelOperationsTests : MeasureFixture - where TPixel : unmanaged, IPixel - { - public const string SkipProfilingBenchmarks = +public abstract class PixelOperationsTests : MeasureFixture + where TPixel : unmanaged, IPixel +{ + public const string SkipProfilingBenchmarks = #if true - "Profiling benchmark - enable manually!"; + "Profiling benchmark - enable manually!"; #else - null; + null; #endif - protected PixelOperationsTests(ITestOutputHelper output) - : base(output) - { - } - - public static TheoryData ArraySizesData => - new TheoryData - { - 0, - 1, - 2, - 7, - 16, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520, - 521, - 522, - 523, - 524, - 525, - 526, - 527, - 528, - 1111 - }; - - protected Configuration Configuration => Configuration.Default; - - protected virtual PixelOperations Operations { get; } = PixelOperations.Instance; - - protected bool HasUnassociatedAlpha => this.Operations.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; - - internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) - { - var expected = new TPixel[source.Length]; + protected PixelOperationsTests(ITestOutputHelper output) + : base(output) + { + } - for (int i = 0; i < expected.Length; i++) + public static TheoryData ArraySizesData => + new TheoryData { - Vector4 v = source[i]; - vectorModifier?.Invoke(ref v); + 0, + 1, + 2, + 7, + 16, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 1111 + }; - expected[i].FromVector4(v); - } + protected Configuration Configuration => Configuration.Default; - return expected; - } + protected virtual PixelOperations Operations { get; } = PixelOperations.Instance; - internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) - { - var expected = new TPixel[source.Length]; + protected bool HasUnassociatedAlpha => this.Operations.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; - for (int i = 0; i < expected.Length; i++) - { - Vector4 v = source[i]; - vectorModifier?.Invoke(ref v); + internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) + { + var expected = new TPixel[source.Length]; - expected[i].FromScaledVector4(v); - } + for (int i = 0; i < expected.Length; i++) + { + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); - return expected; + expected[i].FromVector4(v); } - [Fact] - public void PixelTypeInfoHasCorrectBitsPerPixel() + return expected; + } + + internal static TPixel[] CreateScaledExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) + { + var expected = new TPixel[source.Length]; + + for (int i = 0; i < expected.Length; i++) { - int bits = this.Operations.GetPixelTypeInfo().BitsPerPixel; - Assert.Equal(Unsafe.SizeOf() * 8, bits); + Vector4 v = source[i]; + vectorModifier?.Invoke(ref v); + + expected[i].FromScaledVector4(v); } - [Fact] - public void PixelAlphaRepresentation_DefinesPresenceOfAlphaChannel() - { - // We use 0 - 255 as we have pixel formats that store - // the alpha component in less than 8 bits. - const byte Alpha = byte.MinValue; - const byte NoAlpha = byte.MaxValue; + return expected; + } - TPixel pixel = default; - pixel.FromRgba32(new Rgba32(0, 0, 0, Alpha)); + [Fact] + public void PixelTypeInfoHasCorrectBitsPerPixel() + { + int bits = this.Operations.GetPixelTypeInfo().BitsPerPixel; + Assert.Equal(Unsafe.SizeOf() * 8, bits); + } - Rgba32 dest = default; - pixel.ToRgba32(ref dest); + [Fact] + public void PixelAlphaRepresentation_DefinesPresenceOfAlphaChannel() + { + // We use 0 - 255 as we have pixel formats that store + // the alpha component in less than 8 bits. + const byte Alpha = byte.MinValue; + const byte NoAlpha = byte.MaxValue; - bool hasAlpha = this.Operations.GetPixelTypeInfo().AlphaRepresentation != PixelAlphaRepresentation.None; + TPixel pixel = default; + pixel.FromRgba32(new Rgba32(0, 0, 0, Alpha)); - byte expectedAlpha = hasAlpha ? Alpha : NoAlpha; - Assert.Equal(expectedAlpha, dest.A); - } + Rgba32 dest = default; + pixel.ToRgba32(ref dest); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromVector4(int count) - { - Vector4[] source = CreateVector4TestData(count); - TPixel[] expected = CreateExpectedPixelData(source); + bool hasAlpha = this.Operations.GetPixelTypeInfo().AlphaRepresentation != PixelAlphaRepresentation.None; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); - } + byte expectedAlpha = hasAlpha ? Alpha : NoAlpha; + Assert.Equal(expectedAlpha, dest.A); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromScaledVector4(int count) - { - Vector4[] source = CreateVector4TestData(count); - TPixel[] expected = CreateScaledExpectedPixelData(source); - - TestOperation( - source, - expected, - (s, d) => - { - Span destPixels = d.GetSpan(); - this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); - }); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromVector4(int count) + { + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = CreateExpectedPixelData(source); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromCompandedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref 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)); - - TestOperation( - source, - expected, - (s, d) => this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale), - false); - } + TestOperation( + source, + expected, + (s, d) => this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromPremultipliedVector4(int count) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromScaledVector4(int count) + { + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = CreateScaledExpectedPixelData(source); + + TestOperation( + source, + expected, + (s, d) => + { + Span destPixels = d.GetSpan(); + this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); + }); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref 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)); + + TestOperation( + source, + expected, + (s, d) => this.Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale), + false); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedVector4(int count) + { + void SourceAction(ref Vector4 v) { - void SourceAction(ref Vector4 v) + if (this.HasUnassociatedAlpha) { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + Numerics.Premultiply(ref v); } + } - void ExpectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) + { + if (this.HasUnassociatedAlpha) { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } + Numerics.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) => - { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; + TestOperation( + source, + expected, + (s, d) => + { + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply + : PixelConversionModifiers.None; - this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); - }); - } + this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); + }); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromPremultipliedScaledVector4(int count) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) { - void SourceAction(ref Vector4 v) + if (this.HasUnassociatedAlpha) { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + Numerics.Premultiply(ref v); } + } - void ExpectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) + { + if (this.HasUnassociatedAlpha) { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } + Numerics.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, - expected, - (s, d) => - { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.Scale); - }); - } + TestOperation( + source, + expected, + (s, d) => + { + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply + : PixelConversionModifiers.None; + + this.Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + modifiers | PixelConversionModifiers.Scale); + }); + } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromCompandedPremultipliedScaledVector4(int count) + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromCompandedPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) { - void SourceAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); + SRgbCompanding.Expand(ref v); - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } + if (this.HasUnassociatedAlpha) + { + Numerics.Premultiply(ref v); } + } - void ExpectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) + { + if (this.HasUnassociatedAlpha) { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } - - SRgbCompanding.Compress(ref v); + Numerics.UnPremultiply(ref v); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - - TestOperation( - source, - expected, - (s, d) => - { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); - }, - false); + SRgbCompanding.Compress(ref v); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToVector4(int count) - { - TPixel[] source = CreatePixelTestData(count); - Vector4[] expected = CreateExpectedVector4Data(source); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan())); - } + TestOperation( + source, + expected, + (s, d) => + { + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply + : PixelConversionModifiers.None; + + this.Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); + }, + false); + } - public static readonly TheoryData Generic_To_Data = new() - { - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - }; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToVector4(int count) + { + TPixel[] source = CreatePixelTestData(count); + Vector4[] expected = CreateExpectedVector4Data(source); - [Theory] - [MemberData(nameof(Generic_To_Data))] - public void Generic_To(TestPixel _) - where TDestPixel : unmanaged, IPixel - { - const int count = 2134; - TPixel[] source = CreatePixelTestData(count); - var expected = new TDestPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan())); + } - PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + public static readonly TheoryData Generic_To_Data = new() + { + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + }; + + [Theory] + [MemberData(nameof(Generic_To_Data))] + public void Generic_To(TestPixel _) + where TDestPixel : unmanaged, IPixel + { + const int count = 2134; + TPixel[] source = CreatePixelTestData(count); + var expected = new TDestPixel[count]; - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); - } + PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToScaledVector4(int count) + TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToScaledVector4(int count) + { + TPixel[] source = CreateScaledPixelTestData(count); + Vector4[] expected = CreateExpectedScaledVector4Data(source); + + TestOperation( + source, + expected, + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Scale)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) { - TPixel[] source = CreateScaledPixelTestData(count); - Vector4[] expected = CreateExpectedScaledVector4Data(source); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.Scale)); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToCompandedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref 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, + expected, + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); + } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedVector4(int count) + { + void SourceAction(ref Vector4 v) + { } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToPremultipliedVector4(int count) - { - void SourceAction(ref Vector4 v) - { - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); - void ExpectedAction(ref Vector4 v) => Numerics.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) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); + } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToPremultipliedScaledVector4(int count) - { - void SourceAction(ref Vector4 v) - { - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); - void ExpectedAction(ref Vector4 v) => Numerics.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, + expected, + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + } - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToCompandedPremultipliedScaledVector4(int count) + { + void SourceAction(ref Vector4 v) + { } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToCompandedPremultipliedScaledVector4(int count) + void ExpectedAction(ref Vector4 v) { - void SourceAction(ref Vector4 v) - { - } - - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - Numerics.Premultiply(ref v); - } - - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); - - TestOperation( - source, - expected, - (s, d) => this.Operations.ToVector4( - this.Configuration, - s, - d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + SRgbCompanding.Expand(ref v); + Numerics.Premultiply(ref v); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromArgb32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - var expected = new TPixel[count]; + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); + + TestOperation( + source, + expected, + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromArgb32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - expected[i].FromArgb32(new Argb32(source[i4 + 1], source[i4 + 2], source[i4 + 3], source[i4 + 0])); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromArgb32(new Argb32(source[i4 + 1], source[i4 + 2], source[i4 + 3], source[i4 + 0])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToArgb32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - var argb = default(Argb32); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; - argb.FromScaledVector4(source[i].ToScaledVector4()); + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToArgb32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var argb = default(Argb32); - expected[i4] = argb.A; - expected[i4 + 1] = argb.R; - expected[i4 + 2] = argb.G; - expected[i4 + 3] = argb.B; - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + argb.FromScaledVector4(source[i].ToScaledVector4()); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i4] = argb.A; + expected[i4 + 1] = argb.R; + expected[i4 + 2] = argb.G; + expected[i4 + 3] = argb.B; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgr24Bytes(int count) - { - byte[] source = CreateByteTestData(count * 3); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i3 = i * 3; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgr24Bytes(int count) + { + byte[] source = CreateByteTestData(count * 3); + var expected = new TPixel[count]; - expected[i].FromBgr24(new Bgr24(source[i3 + 2], source[i3 + 1], source[i3])); - } + for (int i = 0; i < count; i++) + { + int i3 = i * 3; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromBgr24(new Bgr24(source[i3 + 2], source[i3 + 1], source[i3])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgr24Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 3]; - var bgr = default(Bgr24); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i3 = i * 3; - bgr.FromScaledVector4(source[i].ToScaledVector4()); - expected[i3] = bgr.B; - expected[i3 + 1] = bgr.G; - expected[i3 + 2] = bgr.R; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgr24Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + var bgr = default(Bgr24); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + bgr.FromScaledVector4(source[i].ToScaledVector4()); + expected[i3] = bgr.B; + expected[i3 + 1] = bgr.G; + expected[i3 + 2] = bgr.R; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgra32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgra32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - expected[i].FromBgra32(new Bgra32(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3])); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromBgra32(new Bgra32(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgra32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - var bgra = default(Bgra32); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; - bgra.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = bgra.B; - expected[i4 + 1] = bgra.G; - expected[i4 + 2] = bgra.R; - expected[i4 + 3] = bgra.A; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgra32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var bgra = default(Bgra32); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + bgra.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = bgra.B; + expected[i4 + 1] = bgra.G; + expected[i4 + 2] = bgra.R; + expected[i4 + 3] = bgra.A; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromAbgr32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromAbgr32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - expected[i].FromAbgr32(new Abgr32(source[i4 + 3], source[i4 + 2], source[i4 + 1], source[i4 + 0])); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromAbgr32(new Abgr32(source[i4 + 3], source[i4 + 2], source[i4 + 1], source[i4 + 0])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToAbgr32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - var abgr = default(Abgr32); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; - abgr.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = abgr.A; - expected[i4 + 1] = abgr.B; - expected[i4 + 2] = abgr.G; - expected[i4 + 3] = abgr.R; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToAbgr32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var abgr = default(Abgr32); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + abgr.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = abgr.A; + expected[i4 + 1] = abgr.B; + expected[i4 + 2] = abgr.G; + expected[i4 + 3] = abgr.R; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromBgra5551Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromBgra5551Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; - Bgra5551 bgra = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i].FromBgra5551(bgra); - } + for (int i = 0; i < count; i++) + { + int offset = i * size; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + Bgra5551 bgra = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromBgra5551(bgra); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToBgra5551Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; - Bgra5551 bgra = default; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; - bgra.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref bgra); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToBgra5551Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + Bgra5551 bgra = default; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int offset = i * size; + bgra.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref bgra); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromL8(int count) - { - byte[] sourceBytes = CreateByteTestData(count); - L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - expected[i].FromL8(source[i]); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL8(int count) + { + byte[] sourceBytes = CreateByteTestData(count); + L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); + var expected = new TPixel[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromL8(this.Configuration, s, d.GetSpan())); + for (int i = 0; i < count; i++) + { + expected[i].FromL8(source[i]); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToL8(int count) - { - TPixel[] source = CreatePixelTestData(count); - var expected = new L8[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromL8(this.Configuration, s, d.GetSpan())); + } - for (int i = 0; i < count; i++) - { - expected[i].FromScaledVector4(source[i].ToScaledVector4()); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL8(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L8[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToL8(this.Configuration, s, d.GetSpan())); + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromL16(int count) - { - L16[] source = CreateVector4TestData(count).Select(v => - { - L16 g = default; - g.FromVector4(v); - return g; - }).ToArray(); + TestOperation( + source, + expected, + (s, d) => this.Operations.ToL8(this.Configuration, s, d.GetSpan())); + } - var expected = new TPixel[count]; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromL16(int count) + { + L16[] source = CreateVector4TestData(count).Select(v => + { + L16 g = default; + g.FromVector4(v); + return g; + }).ToArray(); - for (int i = 0; i < count; i++) - { - expected[i].FromL16(source[i]); - } + var expected = new TPixel[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromL16(this.Configuration, s, d.GetSpan())); + for (int i = 0; i < count; i++) + { + expected[i].FromL16(source[i]); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToL16(int count) - { - TPixel[] source = CreatePixelTestData(count); - var expected = new L16[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromL16(this.Configuration, s, d.GetSpan())); + } - for (int i = 0; i < count; i++) - { - expected[i].FromScaledVector4(source[i].ToScaledVector4()); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToL16(int count) + { + TPixel[] source = CreatePixelTestData(count); + var expected = new L16[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToL16(this.Configuration, s, d.GetSpan())); + for (int i = 0; i < count; i++) + { + expected[i].FromScaledVector4(source[i].ToScaledVector4()); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromLa16Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToL16(this.Configuration, s, d.GetSpan())); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa16Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; - La16 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i].FromLa16(la); - } + for (int i = 0; i < count; i++) + { + int offset = i * size; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + La16 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa16(la); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToLa16Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; - La16 la = default; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; - la.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref la); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToLa16Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + La16 la = default; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref la); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromLa32Bytes(int count) - { - int size = Unsafe.SizeOf(); - byte[] source = CreateByteTestData(count * size); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + byte[] source = CreateByteTestData(count * size); + var expected = new TPixel[count]; - La32 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; - expected[i].FromLa32(la); - } + for (int i = 0; i < count; i++) + { + int offset = i * size; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + La32 la = MemoryMarshal.Cast(source.AsSpan().Slice(offset, size))[0]; + expected[i].FromLa32(la); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToLa32Bytes(int count) - { - int size = Unsafe.SizeOf(); - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * size]; - La32 la = default; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int offset = i * size; - la.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes bytes = Unsafe.As(ref la); - expected[offset] = bytes[0]; - expected[offset + 1] = bytes[1]; - expected[offset + 2] = bytes[2]; - expected[offset + 3] = bytes[3]; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToLa32Bytes(int count) + { + int size = Unsafe.SizeOf(); + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * size]; + La32 la = default; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int offset = i * size; + la.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes bytes = Unsafe.As(ref la); + expected[offset] = bytes[0]; + expected[offset + 1] = bytes[1]; + expected[offset + 2] = bytes[2]; + expected[offset + 3] = bytes[3]; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgb24Bytes(int count) - { - byte[] source = CreateByteTestData(count * 3); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i3 = i * 3; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgb24Bytes(int count) + { + byte[] source = CreateByteTestData(count * 3); + var expected = new TPixel[count]; - expected[i].FromRgb24(new Rgb24(source[i3 + 0], source[i3 + 1], source[i3 + 2])); - } + for (int i = 0; i < count; i++) + { + int i3 = i * 3; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromRgb24(new Rgb24(source[i3 + 0], source[i3 + 1], source[i3 + 2])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgb24Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 3]; - var rgb = default(Rgb24); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i3 = i * 3; - rgb.FromScaledVector4(source[i].ToScaledVector4()); - expected[i3] = rgb.R; - expected[i3 + 1] = rgb.G; - expected[i3 + 2] = rgb.B; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgb24Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 3]; + var rgb = default(Rgb24); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i3 = i * 3; + rgb.FromScaledVector4(source[i].ToScaledVector4()); + expected[i3] = rgb.R; + expected[i3 + 1] = rgb.G; + expected[i3 + 2] = rgb.B; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgba32Bytes(int count) - { - byte[] source = CreateByteTestData(count * 4); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgba32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; - expected[i].FromRgba32(new Rgba32(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3])); - } + for (int i = 0; i < count; i++) + { + int i4 = i * 4; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); + expected[i].FromRgba32(new Rgba32(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3])); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgba32Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 4]; - var rgba = default(Rgba32); + TestOperation( + source, + expected, + (s, d) => this.Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i4 = i * 4; - rgba.FromScaledVector4(source[i].ToScaledVector4()); - expected[i4] = rgba.R; - expected[i4 + 1] = rgba.G; - expected[i4 + 2] = rgba.B; - expected[i4 + 3] = rgba.A; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgba32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var rgba = default(Rgba32); - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + rgba.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = rgba.R; + expected[i4 + 1] = rgba.G; + expected[i4 + 2] = rgba.B; + expected[i4 + 3] = rgba.A; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgb48Bytes(int count) - { - byte[] source = CreateByteTestData(count * 6); - Span sourceSpan = source.AsSpan(); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i6 = i * 6; - expected[i].FromRgb48(MemoryMarshal.Cast(sourceSpan.Slice(i6, 6))[0]); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgb48Bytes(int count) + { + byte[] source = CreateByteTestData(count * 6); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + expected[i].FromRgb48(MemoryMarshal.Cast(sourceSpan.Slice(i6, 6))[0]); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgb48Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 6]; - Rgb48 rgb = default; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i6 = i * 6; - rgb.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes rgb48Bytes = Unsafe.As(ref rgb); - expected[i6] = rgb48Bytes[0]; - expected[i6 + 1] = rgb48Bytes[1]; - expected[i6 + 2] = rgb48Bytes[2]; - expected[i6 + 3] = rgb48Bytes[3]; - expected[i6 + 4] = rgb48Bytes[4]; - expected[i6 + 5] = rgb48Bytes[5]; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgb48Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 6]; + Rgb48 rgb = default; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i6 = i * 6; + rgb.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes rgb48Bytes = Unsafe.As(ref rgb); + expected[i6] = rgb48Bytes[0]; + expected[i6 + 1] = rgb48Bytes[1]; + expected[i6 + 2] = rgb48Bytes[2]; + expected[i6 + 3] = rgb48Bytes[3]; + expected[i6 + 4] = rgb48Bytes[4]; + expected[i6 + 5] = rgb48Bytes[5]; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void FromRgba64Bytes(int count) - { - byte[] source = CreateByteTestData(count * 8); - Span sourceSpan = source.AsSpan(); - var expected = new TPixel[count]; + TestOperation( + source, + expected, + (s, d) => this.Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i8 = i * 8; - expected[i].FromRgba64(MemoryMarshal.Cast(sourceSpan.Slice(i8, 8))[0]); - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromRgba64Bytes(int count) + { + byte[] source = CreateByteTestData(count * 8); + Span sourceSpan = source.AsSpan(); + var expected = new TPixel[count]; - TestOperation( - source, - expected, - (s, d) => this.Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + expected[i].FromRgba64(MemoryMarshal.Cast(sourceSpan.Slice(i8, 8))[0]); } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void ToRgba64Bytes(int count) - { - TPixel[] source = CreatePixelTestData(count); - byte[] expected = new byte[count * 8]; - Rgba64 rgba = default; + TestOperation( + source, + expected, + (s, d) => this.Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); + } - for (int i = 0; i < count; i++) - { - int i8 = i * 8; - rgba.FromScaledVector4(source[i].ToScaledVector4()); - OctetBytes rgba64Bytes = Unsafe.As(ref rgba); - expected[i8] = rgba64Bytes[0]; - expected[i8 + 1] = rgba64Bytes[1]; - expected[i8 + 2] = rgba64Bytes[2]; - expected[i8 + 3] = rgba64Bytes[3]; - expected[i8 + 4] = rgba64Bytes[4]; - expected[i8 + 5] = rgba64Bytes[5]; - expected[i8 + 6] = rgba64Bytes[6]; - expected[i8 + 7] = rgba64Bytes[7]; - } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToRgba64Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 8]; + Rgba64 rgba = default; - TestOperation( - source, - expected, - (s, d) => this.Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); + for (int i = 0; i < count; i++) + { + int i8 = i * 8; + rgba.FromScaledVector4(source[i].ToScaledVector4()); + OctetBytes rgba64Bytes = Unsafe.As(ref rgba); + expected[i8] = rgba64Bytes[0]; + expected[i8 + 1] = rgba64Bytes[1]; + expected[i8 + 2] = rgba64Bytes[2]; + expected[i8 + 3] = rgba64Bytes[3]; + expected[i8 + 4] = rgba64Bytes[4]; + expected[i8 + 5] = rgba64Bytes[5]; + expected[i8 + 6] = rgba64Bytes[6]; + expected[i8 + 7] = rgba64Bytes[7]; } - [Theory] - [MemberData(nameof(ArraySizesData))] - public void PackFromRgbPlanes(int count) - => SimdUtilsTests.TestPackFromRgbPlanes( - count, - (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(r, g, b, actual)); + TestOperation( + source, + expected, + (s, d) => this.Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); + } - public delegate void RefAction(ref T1 arg1); + [Theory] + [MemberData(nameof(ArraySizesData))] + public void PackFromRgbPlanes(int count) + => SimdUtilsTests.TestPackFromRgbPlanes( + count, + (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(r, g, b, actual)); - internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) - { - var expected = new Vector4[source.Length]; + public delegate void RefAction(ref T1 arg1); - for (int i = 0; i < expected.Length; i++) - { - var v = source[i].ToVector4(); + internal static Vector4[] CreateExpectedVector4Data(TPixel[] source, RefAction vectorModifier = null) + { + var expected = new Vector4[source.Length]; - vectorModifier?.Invoke(ref v); + for (int i = 0; i < expected.Length; i++) + { + var v = source[i].ToVector4(); - expected[i] = v; - } + vectorModifier?.Invoke(ref v); - return expected; + expected[i] = v; } - internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) - { - var expected = new Vector4[source.Length]; + return expected; + } - for (int i = 0; i < expected.Length; i++) - { - Vector4 v = source[i].ToScaledVector4(); + internal static Vector4[] CreateExpectedScaledVector4Data(TPixel[] source, RefAction vectorModifier = null) + { + var expected = new Vector4[source.Length]; - vectorModifier?.Invoke(ref v); + for (int i = 0; i < expected.Length; i++) + { + Vector4 v = source[i].ToScaledVector4(); - expected[i] = v; - } + vectorModifier?.Invoke(ref v); - return expected; + expected[i] = v; } - internal static void TestOperation( - TSource[] source, - TDest[] expected, - Action> action, - bool preferExactComparison = true) - where TSource : struct - where TDest : struct - { - using (var buffers = new TestBuffers(source, expected, preferExactComparison)) - { - action(buffers.SourceBuffer, buffers.ActualDestBuffer); - buffers.Verify(); - } - } + return expected; + } - internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) + internal static void TestOperation( + TSource[] source, + TDest[] expected, + Action> action, + bool preferExactComparison = true) + where TSource : struct + where TDest : struct + { + using (var buffers = new TestBuffers(source, expected, preferExactComparison)) { - var result = new Vector4[length]; - var rnd = new Random(42); // Deterministic random values + action(buffers.SourceBuffer, buffers.ActualDestBuffer); + buffers.Verify(); + } + } - for (int i = 0; i < result.Length; i++) - { - Vector4 v = GetScaledVector(rnd); - vectorModifier?.Invoke(ref v); + internal static Vector4[] CreateVector4TestData(int length, RefAction vectorModifier = null) + { + var result = new Vector4[length]; + var rnd = new Random(42); // Deterministic random values - result[i] = v; - } + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetScaledVector(rnd); + vectorModifier?.Invoke(ref v); - return result; + result[i] = v; } - internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) - { - var result = new TPixel[length]; + return result; + } - var rnd = new Random(42); // Deterministic random values + internal static TPixel[] CreatePixelTestData(int length, RefAction vectorModifier = null) + { + var result = new TPixel[length]; - for (int i = 0; i < result.Length; i++) - { - Vector4 v = GetScaledVector(rnd); + var rnd = new Random(42); // Deterministic random values - vectorModifier?.Invoke(ref v); + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetScaledVector(rnd); - result[i].FromVector4(v); - } + vectorModifier?.Invoke(ref v); - return result; + result[i].FromVector4(v); } - internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) - { - var result = new TPixel[length]; + return result; + } - var rnd = new Random(42); // Deterministic random values + internal static TPixel[] CreateScaledPixelTestData(int length, RefAction vectorModifier = null) + { + var result = new TPixel[length]; - for (int i = 0; i < result.Length; i++) - { - Vector4 v = GetScaledVector(rnd); + var rnd = new Random(42); // Deterministic random values - vectorModifier?.Invoke(ref v); + for (int i = 0; i < result.Length; i++) + { + Vector4 v = GetScaledVector(rnd); - result[i].FromScaledVector4(v); - } + vectorModifier?.Invoke(ref v); - return result; + result[i].FromScaledVector4(v); } - internal static byte[] CreateByteTestData(int length, int seed = 42) - { - byte[] result = new byte[length]; - var rnd = new Random(seed); // Deterministic random values + return result; + } - for (int i = 0; i < result.Length; i++) - { - result[i] = (byte)rnd.Next(255); - } + internal static byte[] CreateByteTestData(int length, int seed = 42) + { + byte[] result = new byte[length]; + var rnd = new Random(seed); // Deterministic random values - return result; + for (int i = 0; i < result.Length; i++) + { + result[i] = (byte)rnd.Next(255); } - internal static Vector4 GetScaledVector(Random rnd) - => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); + return result; + } - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OctetBytes - { - public fixed byte Data[8]; + internal static Vector4 GetScaledVector(Random rnd) + => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); - public byte this[int idx] + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OctetBytes + { + public fixed byte Data[8]; + + public byte this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref byte self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } + ref byte self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); } } + } - private class TestBuffers : IDisposable - where TSource : struct - where TDest : struct - { - public TSource[] SourceBuffer { get; } + private class TestBuffers : IDisposable + where TSource : struct + where TDest : struct + { + public TSource[] SourceBuffer { get; } - public IMemoryOwner ActualDestBuffer { get; } + public IMemoryOwner ActualDestBuffer { get; } - public TDest[] ExpectedDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } - public bool PreferExactComparison { get; } + public bool PreferExactComparison { get; } - public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true) - { - this.SourceBuffer = source; - this.ExpectedDestBuffer = expectedDest; - this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); - this.PreferExactComparison = preferExactComparison; - } + public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true) + { + this.SourceBuffer = source; + this.ExpectedDestBuffer = expectedDest; + this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); + this.PreferExactComparison = preferExactComparison; + } + + public void Dispose() => this.ActualDestBuffer.Dispose(); - public void Dispose() => this.ActualDestBuffer.Dispose(); + public void Verify() + { + int count = this.ExpectedDestBuffer.Length; - public void Verify() + if (typeof(TDest) == typeof(Vector4)) { - int count = this.ExpectedDestBuffer.Length; + Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); + Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - if (typeof(TDest) == typeof(Vector4)) + for (int i = 0; i < count; i++) { - Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); - Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); - var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - - for (int i = 0; i < count; i++) - { - Assert.Equal(expected[i], actual[i], comparer); - } + Assert.Equal(expected[i], actual[i], comparer); } - else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) + } + else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) + { + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); + + for (int i = 0; i < count; i++) { - Span expected = this.ExpectedDestBuffer.AsSpan(); - Span actual = this.ActualDestBuffer.GetSpan(); - var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - - for (int i = 0; i < count; i++) - { - Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer); - } + Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer); } - else - { - Span expected = this.ExpectedDestBuffer.AsSpan(); - Span actual = this.ActualDestBuffer.GetSpan(); + } + else + { + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); - for (int i = 0; i < count; i++) - { - Assert.Equal(expected[i], actual[i]); - } + for (int i = 0; i < count; i++) + { + Assert.Equal(expected[i], actual[i]); } } - - // TODO: We really need a PixelTypeInfo.BitsPerComponent property!! - private static bool IsComplexPixel() => default(TDest) switch - { - HalfSingle or HalfVector2 or L16 or La32 or NormalizedShort2 or Rg32 or Short2 => true, - _ => Unsafe.SizeOf() > sizeof(int), - }; } + + // TODO: We really need a PixelTypeInfo.BitsPerComponent property!! + private static bool IsComplexPixel() => default(TDest) switch + { + HalfSingle or HalfVector2 or L16 or La32 or NormalizedShort2 or Rg32 or Short2 => true, + _ => Unsafe.SizeOf() > sizeof(int), + }; } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index 731699d334..2900b0d292 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -3,85 +3,83 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rg32Tests { - [Trait("Category", "PixelFormats")] - public class Rg32Tests + [Fact] + public void Rg32_PackedValues() { - [Fact] - public void Rg32_PackedValues() - { - float x = 0xb6dc; - float y = 0xA59f; - Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); - Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); + float x = 0xb6dc; + float y = 0xA59f; + Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); + Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); - // Test the limits. - Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); - } + // Test the limits. + Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); + } - [Fact] - public void Rg32_ToVector2() - { - Assert.Equal(Vector2.Zero, new Rg32(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One, new Rg32(Vector2.One).ToVector2()); - } + [Fact] + public void Rg32_ToVector2() + { + Assert.Equal(Vector2.Zero, new Rg32(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One, new Rg32(Vector2.One).ToVector2()); + } - [Fact] - public void Rg32_ToScaledVector4() - { - // arrange - var rg32 = new Rg32(Vector2.One); + [Fact] + public void Rg32_ToScaledVector4() + { + // arrange + var rg32 = new Rg32(Vector2.One); - // act - Vector4 actual = rg32.ToScaledVector4(); + // act + Vector4 actual = rg32.ToScaledVector4(); - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } - [Fact] - public void Rg32_FromScaledVector4() - { - // arrange - var rg32 = new Rg32(Vector2.One); - var pixel = default(Rg32); - uint expected = 0xFFFFFFFF; + [Fact] + public void Rg32_FromScaledVector4() + { + // arrange + var rg32 = new Rg32(Vector2.One); + var pixel = default(Rg32); + uint expected = 0xFFFFFFFF; - // act - Vector4 scaled = rg32.ToScaledVector4(); - pixel.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; + // act + Vector4 scaled = rg32.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void Rg32_FromBgra5551() - { - // arrange - var rg32 = new Rg32(Vector2.One); - uint expected = 0xFFFFFFFF; + [Fact] + public void Rg32_FromBgra5551() + { + // arrange + var rg32 = new Rg32(Vector2.One); + uint expected = 0xFFFFFFFF; - // act - rg32.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + rg32.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, rg32.PackedValue); - } + // assert + Assert.Equal(expected, rg32.PackedValue); + } - [Fact] - public void Rg32_Clamping() - { - Assert.Equal(Vector2.Zero, new Rg32(Vector2.One * -1234.0f).ToVector2()); - Assert.Equal(Vector2.One, new Rg32(Vector2.One * 1234.0f).ToVector2()); - } + [Fact] + public void Rg32_Clamping() + { + Assert.Equal(Vector2.Zero, new Rg32(Vector2.One * -1234.0f).ToVector2()); + Assert.Equal(Vector2.One, new Rg32(Vector2.One * 1234.0f).ToVector2()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs index 6113d0a941..2d1be8ab44 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb24Tests.cs @@ -3,132 +3,130 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgb24Tests { - [Trait("Category", "PixelFormats")] - public class Rgb24Tests - { - public static readonly TheoryData ColorData = - new() - { - { 1, 2, 3 }, - { 4, 5, 6 }, - { 0, 255, 42 } - }; - - [Theory] - [MemberData(nameof(ColorData))] - public void Constructor(byte r, byte g, byte b) + public static readonly TheoryData ColorData = + new() { - var p = new Rgb24(r, g, b); + { 1, 2, 3 }, + { 4, 5, 6 }, + { 0, 255, 42 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte r, byte g, byte b) + { + var p = new Rgb24(r, g, b); - Assert.Equal(r, p.R); - Assert.Equal(g, p.G); - Assert.Equal(b, p.B); - } + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } - [Fact] - public unsafe void ByteLayoutIsSequentialRgb() - { - var color = new Rgb24(1, 2, 3); - byte* ptr = (byte*)&color; + [Fact] + public unsafe void ByteLayoutIsSequentialRgb() + { + var color = new Rgb24(1, 2, 3); + byte* ptr = (byte*)&color; - Assert.Equal(1, ptr[0]); - Assert.Equal(2, ptr[1]); - Assert.Equal(3, ptr[2]); - } + Assert.Equal(1, ptr[0]); + Assert.Equal(2, ptr[1]); + Assert.Equal(3, ptr[2]); + } - [Theory] - [MemberData(nameof(ColorData))] - public void Equals_WhenTrue(byte r, byte g, byte b) - { - var x = new Rgb24(r, g, b); - var y = new Rgb24(r, g, b); - - Assert.True(x.Equals(y)); - Assert.True(x.Equals((object)y)); - Assert.Equal(x.GetHashCode(), y.GetHashCode()); - } - - [Theory] - [InlineData(1, 2, 3, 1, 2, 4)] - [InlineData(0, 255, 0, 0, 244, 0)] - [InlineData(1, 255, 0, 0, 255, 0)] - public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) - { - var a = new Rgb24(r1, g1, b1); - var b = new Rgb24(r2, g2, b2); + [Theory] + [MemberData(nameof(ColorData))] + public void Equals_WhenTrue(byte r, byte g, byte b) + { + var x = new Rgb24(r, g, b); + var y = new Rgb24(r, g, b); - Assert.False(a.Equals(b)); - Assert.False(a.Equals((object)b)); - } + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } - [Fact] - public void FromRgba32() - { - var rgb = default(Rgb24); - rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); - - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } - - private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( - r / 255f, - g / 255f, - b / 255f, - a / 255f); - - [Fact] - public void FromVector4() - { - var rgb = default(Rgb24); - rgb.FromVector4(Vec(1, 2, 3, 4)); + [Theory] + [InlineData(1, 2, 3, 1, 2, 4)] + [InlineData(0, 255, 0, 0, 244, 0)] + [InlineData(1, 255, 0, 0, 255, 0)] + public void Equals_WhenFalse(byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) + { + var a = new Rgb24(r1, g1, b1); + var b = new Rgb24(r2, g2, b2); - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); - } + Assert.False(a.Equals(b)); + Assert.False(a.Equals((object)b)); + } - [Fact] - public void ToVector4() - { - var rgb = new Rgb24(1, 2, 3); + [Fact] + public void FromRgba32() + { + var rgb = default(Rgb24); + rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } - Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); - } + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new( + r / 255f, + g / 255f, + b / 255f, + a / 255f); - [Fact] - public void ToRgba32() - { - // arrange - var rgb = new Rgb24(1, 2, 3); - Rgba32 rgba = default; - var expected = new Rgba32(1, 2, 3, 255); + [Fact] + public void FromVector4() + { + var rgb = default(Rgb24); + rgb.FromVector4(Vec(1, 2, 3, 4)); - // act - rgb.ToRgba32(ref rgba); + Assert.Equal(1, rgb.R); + Assert.Equal(2, rgb.G); + Assert.Equal(3, rgb.B); + } - // assert - Assert.Equal(expected, rgba); - } + [Fact] + public void ToVector4() + { + var rgb = new Rgb24(1, 2, 3); - [Fact] - public void Rgb24_FromBgra5551() - { - // arrange - var rgb = new Rgb24(255, 255, 255); + Assert.Equal(Vec(1, 2, 3), rgb.ToVector4()); + } + + [Fact] + public void ToRgba32() + { + // arrange + var rgb = new Rgb24(1, 2, 3); + Rgba32 rgba = default; + var expected = new Rgba32(1, 2, 3, 255); + + // act + rgb.ToRgba32(ref rgba); + + // assert + Assert.Equal(expected, rgba); + } + + [Fact] + public void Rgb24_FromBgra5551() + { + // arrange + var rgb = new Rgb24(255, 255, 255); - // act - rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(255, rgb.R); - Assert.Equal(255, rgb.G); - Assert.Equal(255, rgb.B); - } + // assert + Assert.Equal(255, rgb.R); + Assert.Equal(255, rgb.G); + Assert.Equal(255, rgb.B); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs index c384631a04..d8a61940b2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -3,77 +3,75 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgb48Tests { - [Trait("Category", "PixelFormats")] - public class Rgb48Tests + [Fact] + public void Rgb48_Values() { - [Fact] - public void Rgb48_Values() - { - var rgb = new Rgba64(5243, 9830, 19660, 29491); + var rgb = new Rgba64(5243, 9830, 19660, 29491); - Assert.Equal(5243, rgb.R); - Assert.Equal(9830, rgb.G); - Assert.Equal(19660, rgb.B); - Assert.Equal(29491, rgb.A); - } + Assert.Equal(5243, rgb.R); + Assert.Equal(9830, rgb.G); + Assert.Equal(19660, rgb.B); + Assert.Equal(29491, rgb.A); + } - [Fact] - public void Rgb48_ToVector4() - => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + [Fact] + public void Rgb48_ToVector4() + => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - [Fact] - public void Rgb48_ToScaledVector4() - => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + [Fact] + public void Rgb48_ToScaledVector4() + => Assert.Equal(Vector4.One, new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - [Fact] - public void Rgb48_FromScaledVector4() - { - // arrange - var pixel = default(Rgb48); - var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + [Fact] + public void Rgb48_FromScaledVector4() + { + // arrange + var pixel = default(Rgb48); + var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); + var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - // act - Vector4 scaled = short3.ToScaledVector4(); - pixel.FromScaledVector4(scaled); + // act + Vector4 scaled = short3.ToScaledVector4(); + pixel.FromScaledVector4(scaled); - // assert - Assert.Equal(expected, pixel); - } + // assert + Assert.Equal(expected, pixel); + } - [Fact] - public void Rgb48_ToRgba32() - { - // arrange - var rgba48 = new Rgb48(5140, 9766, 19532); - var expected = new Rgba32(20, 38, 76, 255); + [Fact] + public void Rgb48_ToRgba32() + { + // arrange + var rgba48 = new Rgb48(5140, 9766, 19532); + var expected = new Rgba32(20, 38, 76, 255); - // act - Rgba32 actual = default; - rgba48.ToRgba32(ref actual); + // act + Rgba32 actual = default; + rgba48.ToRgba32(ref actual); - // assert - Assert.Equal(expected, actual); - } + // assert + Assert.Equal(expected, actual); + } - [Fact] - public void Rgb48_FromBgra5551() - { - // arrange - var rgb = default(Rgb48); - ushort expected = ushort.MaxValue; + [Fact] + public void Rgb48_FromBgra5551() + { + // arrange + var rgb = default(Rgb48); + ushort expected = ushort.MaxValue; - // act - rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - // assert - Assert.Equal(expected, rgb.R); - Assert.Equal(expected, rgb.G); - Assert.Equal(expected, rgb.B); - } + // assert + Assert.Equal(expected, rgb.R); + Assert.Equal(expected, rgb.G); + Assert.Equal(expected, rgb.B); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs index d46f508c5d..0c28b35c69 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -3,248 +3,246 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgba1010102Tests { - [Trait("Category", "PixelFormats")] - public class Rgba1010102Tests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Rgba1010102(new Vector4(0.0f)); - var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); - var color4 = new Rgba1010102(1.0f, 0.0f, 1.0f, 1.0f); - - Assert.Equal(color1, color2); - Assert.Equal(color3, color4); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); - var color2 = new Rgba1010102(new Vector4(1.0f)); - var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); - var color4 = new Rgba1010102(1.0f, 1.0f, 0.0f, 1.0f); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color3, color4); - } - - [Fact] - public void Rgba1010102_PackedValue() - { - float x = 0x2db; - float y = 0x36d; - float z = 0x3b7; - float w = 0x1; - Assert.Equal(0x7B7DB6DBU, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); - - Assert.Equal(536871014U, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); - } - - [Fact] - public void Rgba1010102_ToVector4() - { - Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One).ToVector4()); - } - - [Fact] - public void Rgba1010102_ToScaledVector4() - { - // arrange - var rgba = new Rgba1010102(Vector4.One); - - // act - Vector4 actual = rgba.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Rgba1010102_FromScaledVector4() - { - // arrange - var rgba = new Rgba1010102(Vector4.One); - var actual = default(Rgba1010102); - uint expected = 0xFFFFFFFF; - - // act - Vector4 scaled = rgba.ToScaledVector4(); - actual.FromScaledVector4(scaled); - - // assert - Assert.Equal(expected, actual.PackedValue); - } - - [Fact] - public void Rgba1010102_FromBgra5551() - { - // arrange - var rgba = new Rgba1010102(Vector4.One); - uint expected = 0xFFFFFFFF; - - // act - rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromArgb32() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromArgb32(new Argb32(255, 255, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgba32() - { - // arrange - var rgba1 = default(Rgba1010102); - var rgba2 = default(Rgba1010102); - uint expectedPackedValue1 = uint.MaxValue; - uint expectedPackedValue2 = 0xFFF003FF; - - // act - rgba1.FromRgba32(new Rgba32(255, 255, 255, 255)); - rgba2.FromRgba32(new Rgba32(255, 0, 255, 255)); - - // assert - Assert.Equal(expectedPackedValue1, rgba1.PackedValue); - Assert.Equal(expectedPackedValue2, rgba2.PackedValue); - } - - [Fact] - public void Rgba1010102_FromBgr24() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromGrey8() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromGrey16() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgb24() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgb48() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_FromRgba64() - { - // arrange - var rgba = default(Rgba1010102); - uint expectedPackedValue = uint.MaxValue; - - // act - rgba.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); - - // assert - Assert.Equal(expectedPackedValue, rgba.PackedValue); - } - - [Fact] - public void Rgba1010102_Clamping() - { - Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One * 1234.0f).ToVector4()); - } - - [Fact] - public void Rgba1010102_ToRgba32() - { - // arrange - var rgba = new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f); - var expected = new Rgba32(25, 0, 128, 0); - - // act - Rgba32 actual = default; - rgba.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Rgba1010102(new Vector4(0.0f)); + var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 1.0f, 1.0f)); + var color4 = new Rgba1010102(1.0f, 0.0f, 1.0f, 1.0f); + + Assert.Equal(color1, color2); + Assert.Equal(color3, color4); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Rgba1010102(0.0f, 0.0f, 0.0f, 0.0f); + var color2 = new Rgba1010102(new Vector4(1.0f)); + var color3 = new Rgba1010102(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + var color4 = new Rgba1010102(1.0f, 1.0f, 0.0f, 1.0f); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color3, color4); + } + + [Fact] + public void Rgba1010102_PackedValue() + { + float x = 0x2db; + float y = 0x36d; + float z = 0x3b7; + float w = 0x1; + Assert.Equal(0x7B7DB6DBU, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); + + Assert.Equal(536871014U, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + + // Test the limits. + Assert.Equal(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); + } + + [Fact] + public void Rgba1010102_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One).ToVector4()); + } + + [Fact] + public void Rgba1010102_ToScaledVector4() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + + // act + Vector4 actual = rgba.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgba1010102_FromScaledVector4() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + var actual = default(Rgba1010102); + uint expected = 0xFFFFFFFF; + + // act + Vector4 scaled = rgba.ToScaledVector4(); + actual.FromScaledVector4(scaled); + + // assert + Assert.Equal(expected, actual.PackedValue); + } + + [Fact] + public void Rgba1010102_FromBgra5551() + { + // arrange + var rgba = new Rgba1010102(Vector4.One); + uint expected = 0xFFFFFFFF; + + // act + rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromArgb32() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromArgb32(new Argb32(255, 255, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgba32() + { + // arrange + var rgba1 = default(Rgba1010102); + var rgba2 = default(Rgba1010102); + uint expectedPackedValue1 = uint.MaxValue; + uint expectedPackedValue2 = 0xFFF003FF; + + // act + rgba1.FromRgba32(new Rgba32(255, 255, 255, 255)); + rgba2.FromRgba32(new Rgba32(255, 0, 255, 255)); + + // assert + Assert.Equal(expectedPackedValue1, rgba1.PackedValue); + Assert.Equal(expectedPackedValue2, rgba2.PackedValue); + } + + [Fact] + public void Rgba1010102_FromBgr24() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey8() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromGrey16() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb24() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb24(new Rgb24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgb48() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgb48(new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_FromRgba64() + { + // arrange + var rgba = default(Rgba1010102); + uint expectedPackedValue = uint.MaxValue; + + // act + rgba.FromRgba64(new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)); + + // assert + Assert.Equal(expectedPackedValue, rgba.PackedValue); + } + + [Fact] + public void Rgba1010102_Clamping() + { + Assert.Equal(Vector4.Zero, new Rgba1010102(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Rgba1010102(Vector4.One * 1234.0f).ToVector4()); + } + + [Fact] + public void Rgba1010102_ToRgba32() + { + // arrange + var rgba = new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f); + var expected = new Rgba32(25, 0, 128, 0); + + // act + Rgba32 actual = default; + rgba.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index 54856975aa..64903f65bb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -1,311 +1,308 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Tests the struct. +/// +[Trait("Category", "PixelFormats")] +public class Rgba32Tests { /// - /// Tests the struct. + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Rgba32(0, 0, 0); + var color2 = new Rgba32(0, 0, 0, 1F); + 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); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Rgba32(255, 0, 0, 255); + var color2 = new Rgba32(0, 0, 0, 255); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000000"); + var color5 = Rgba32.ParseHex("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. /// - [Trait("Category", "PixelFormats")] - public class Rgba32Tests + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new Rgba32(1, .1f, .133f, .864f); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); + + var color2 = new Rgba32(1, .1f, .133f); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); + + var color4 = new Rgba32(new Vector3(1, .1f, .133f)); + Assert.Equal(255, color4.R); + Assert.Equal(Math.Round(.1f * 255), color4.G); + Assert.Equal(Math.Round(.133f * 255), color4.B); + Assert.Equal(255, color4.A); + + var color5 = new Rgba32(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(255, color5.R); + Assert.Equal(Math.Round(.1f * 255), color5.G); + Assert.Equal(Math.Round(.133f * 255), color5.B); + Assert.Equal(Math.Round(.5f * 255), color5.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + // 8 digit hex matches css4 spec. RRGGBBAA + var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 + Assert.Equal(170, color.R); + Assert.Equal(187, color.G); + Assert.Equal(204, color.B); + Assert.Equal(221, color.A); + + Assert.Equal("AABBCCDD", color.ToHex()); + + color.R = 0; + + Assert.Equal("00BBCCDD", color.ToHex()); + + color.A = 255; + + Assert.Equal("00BBCCFF", color.ToHex()); + } + + /// + /// Tests that the individual byte elements are laid out in RGBA order. + /// + [Fact] + public unsafe void ByteLayout() + { + var color = new Rgba32(1, 2, 3, 4); + byte* colorBase = (byte*)&color; + Assert.Equal(1, colorBase[0]); + Assert.Equal(2, colorBase[1]); + Assert.Equal(3, colorBase[2]); + Assert.Equal(4, colorBase[3]); + + Assert.Equal(4, sizeof(Rgba32)); + } + + [Fact] + public void Rgba32_PackedValues() + { + Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + + // Test the limits. + Assert.Equal(0x0U, new Rgba32(Vector4.Zero).PackedValue); + Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); + } + + [Fact] + public void Rgba32_ToVector4() + { + Assert.Equal(Vector4.One, new Rgba32(Vector4.One).ToVector4()); + Assert.Equal(Vector4.Zero, new Rgba32(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.UnitX, new Rgba32(Vector4.UnitX).ToVector4()); + Assert.Equal(Vector4.UnitY, new Rgba32(Vector4.UnitY).ToVector4()); + Assert.Equal(Vector4.UnitZ, new Rgba32(Vector4.UnitZ).ToVector4()); + Assert.Equal(Vector4.UnitW, new Rgba32(Vector4.UnitW).ToVector4()); + } + + [Fact] + public void Rgba32_ToScaledVector4() + { + // arrange + var rgba = new Rgba32(Vector4.One); + + // act + Vector4 actual = rgba.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Rgba32_FromScaledVector4() + { + // arrange + var rgba = new Rgba32(Vector4.One); + var actual = default(Rgba32); + uint expected = 0xFFFFFFFF; + + // act + Vector4 scaled = rgba.ToScaledVector4(); + actual.FromScaledVector4(scaled); + + // assert + Assert.Equal(expected, actual.PackedValue); + } + + [Fact] + public void Rgba32_Clamping() + { + Assert.Equal(Vector4.Zero, new Rgba32(Vector4.One * -1234.0f).ToVector4()); + Assert.Equal(Vector4.One, new Rgba32(Vector4.One * +1234.0f).ToVector4()); + } + + [Fact] + public void Rgba32_ToRgba32() + { + // arrange + var rgba = new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f); + var actual = default(Rgba32); + var expected = new Rgba32(0x1a, 0, 0x80, 0); + + // act + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgba32_ToRgba32() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new Rgba32(0, 0, 0); - var color2 = new Rgba32(0, 0, 0, 1F); - 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); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - Assert.Equal(color1, color6); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new Rgba32(255, 0, 0, 255); - var color2 = new Rgba32(0, 0, 0, 255); - var color3 = Rgba32.ParseHex("#000"); - var color4 = Rgba32.ParseHex("#000000"); - var color5 = Rgba32.ParseHex("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var color1 = new Rgba32(1, .1f, .133f, .864f); - Assert.Equal(255, color1.R); - Assert.Equal((byte)Math.Round(.1f * 255), color1.G); - Assert.Equal((byte)Math.Round(.133f * 255), color1.B); - Assert.Equal((byte)Math.Round(.864f * 255), color1.A); - - var color2 = new Rgba32(1, .1f, .133f); - Assert.Equal(255, color2.R); - Assert.Equal(Math.Round(.1f * 255), color2.G); - Assert.Equal(Math.Round(.133f * 255), color2.B); - Assert.Equal(255, color2.A); - - var color4 = new Rgba32(new Vector3(1, .1f, .133f)); - Assert.Equal(255, color4.R); - Assert.Equal(Math.Round(.1f * 255), color4.G); - Assert.Equal(Math.Round(.133f * 255), color4.B); - Assert.Equal(255, color4.A); - - var color5 = new Rgba32(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(255, color5.R); - Assert.Equal(Math.Round(.1f * 255), color5.G); - Assert.Equal(Math.Round(.133f * 255), color5.B); - Assert.Equal(Math.Round(.5f * 255), color5.A); - } - - /// - /// Tests whether FromHex and ToHex work correctly. - /// - [Fact] - public void FromAndToHex() - { - // 8 digit hex matches css4 spec. RRGGBBAA - var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 - Assert.Equal(170, color.R); - Assert.Equal(187, color.G); - Assert.Equal(204, color.B); - Assert.Equal(221, color.A); - - Assert.Equal("AABBCCDD", color.ToHex()); - - color.R = 0; - - Assert.Equal("00BBCCDD", color.ToHex()); - - color.A = 255; - - Assert.Equal("00BBCCFF", color.ToHex()); - } - - /// - /// Tests that the individual byte elements are laid out in RGBA order. - /// - [Fact] - public unsafe void ByteLayout() - { - var color = new Rgba32(1, 2, 3, 4); - byte* colorBase = (byte*)&color; - Assert.Equal(1, colorBase[0]); - Assert.Equal(2, colorBase[1]); - Assert.Equal(3, colorBase[2]); - Assert.Equal(4, colorBase[3]); - - Assert.Equal(4, sizeof(Rgba32)); - } - - [Fact] - public void Rgba32_PackedValues() - { - Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - - // Test the limits. - Assert.Equal(0x0U, new Rgba32(Vector4.Zero).PackedValue); - Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); - } - - [Fact] - public void Rgba32_ToVector4() - { - Assert.Equal(Vector4.One, new Rgba32(Vector4.One).ToVector4()); - Assert.Equal(Vector4.Zero, new Rgba32(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.UnitX, new Rgba32(Vector4.UnitX).ToVector4()); - Assert.Equal(Vector4.UnitY, new Rgba32(Vector4.UnitY).ToVector4()); - Assert.Equal(Vector4.UnitZ, new Rgba32(Vector4.UnitZ).ToVector4()); - Assert.Equal(Vector4.UnitW, new Rgba32(Vector4.UnitW).ToVector4()); - } - - [Fact] - public void Rgba32_ToScaledVector4() - { - // arrange - var rgba = new Rgba32(Vector4.One); - - // act - Vector4 actual = rgba.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Rgba32_FromScaledVector4() - { - // arrange - var rgba = new Rgba32(Vector4.One); - var actual = default(Rgba32); - uint expected = 0xFFFFFFFF; - - // act - Vector4 scaled = rgba.ToScaledVector4(); - actual.FromScaledVector4(scaled); - - // assert - Assert.Equal(expected, actual.PackedValue); - } - - [Fact] - public void Rgba32_Clamping() - { - Assert.Equal(Vector4.Zero, new Rgba32(Vector4.One * -1234.0f).ToVector4()); - Assert.Equal(Vector4.One, new Rgba32(Vector4.One * +1234.0f).ToVector4()); - } - - [Fact] - public void Rgba32_ToRgba32() - { - // arrange - var rgba = new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f); - var actual = default(Rgba32); - var expected = new Rgba32(0x1a, 0, 0x80, 0); - - // act - actual.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgba32_ToRgba32() - { - // arrange - var rgba = default(Rgba32); - var actual = default(Rgba32); - var expected = new Rgba32(0x1a, 0, 0x80, 0); - - // act - rgba.FromRgba32(expected); - actual.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromBgra32_ToRgba32() - { - // arrange - var rgba = default(Rgba32); - var actual = default(Bgra32); - var expected = new Bgra32(0x1a, 0, 0x80, 0); - - // act - rgba.FromBgra32(expected); - actual.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromAbgr32_ToRgba32() - { - // arrange - var rgba = default(Rgba32); - var actual = default(Abgr32); - var expected = new Abgr32(0x1a, 0, 0x80, 0); - - // act - rgba.FromAbgr32(expected); - actual.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromArgb32_ToArgb32() - { - // arrange - var rgba = default(Rgba32); - var actual = default(Argb32); - var expected = new Argb32(0x1a, 0, 0x80, 0); - - // act - rgba.FromArgb32(expected); - actual.FromRgba32(rgba); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgb48() - { - // arrange - var input = default(Rgba32); - var actual = default(Rgb48); - var expected = new Rgb48(65535, 0, 65535); - - // act - input.FromRgb48(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromRgba64() - { - // arrange - var input = default(Rgba32); - var actual = default(Rgba64); - var expected = new Rgba64(65535, 0, 65535, 0); - - // act - input.FromRgba64(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba32_FromBgra5551() - { - // arrange - var rgb = default(Rgba32); - uint expected = 0xFFFFFFFF; - - // act - rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgb.PackedValue); - } + // arrange + var rgba = default(Rgba32); + var actual = default(Rgba32); + var expected = new Rgba32(0x1a, 0, 0x80, 0); + + // act + rgba.FromRgba32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromBgra32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Bgra32); + var expected = new Bgra32(0x1a, 0, 0x80, 0); + + // act + rgba.FromBgra32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromAbgr32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Abgr32); + var expected = new Abgr32(0x1a, 0, 0x80, 0); + + // act + rgba.FromAbgr32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromArgb32_ToArgb32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Argb32); + var expected = new Argb32(0x1a, 0, 0x80, 0); + + // act + rgba.FromArgb32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgb48() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromRgba64() + { + // arrange + var input = default(Rgba32); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba32_FromBgra5551() + { + // arrange + var rgb = default(Rgba32); + uint expected = 0xFFFFFFFF; + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgb.PackedValue); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 72441bc9a6..f60ed8a529 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -3,310 +3,308 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgba64Tests { - [Trait("Category", "PixelFormats")] - public class Rgba64Tests + [Fact] + public void Rgba64_PackedValues() + { + Assert.Equal(0x73334CCC2666147BUL, new Rgba64(5243, 9830, 19660, 29491).PackedValue); + + // Test the limits. + 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); + } + + [Fact] + public void Rgba64_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba64(0, 0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); + } + + [Theory] + [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] + [InlineData(0, 0, 0, 0)] + [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] + public void Rgba64_ToScaledVector4(ushort r, ushort g, ushort b, ushort a) + { + // arrange + var short2 = new Rgba64(r, g, b, a); + + float max = ushort.MaxValue; + float rr = r / max; + float gg = g / max; + float bb = b / max; + float aa = a / max; + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(aa, actual.W); + } + + [Theory] + [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] + [InlineData(0, 0, 0, 0)] + [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); + + // assert + Assert.Equal(source, actual); + } + + [Fact] + public void Rgba64_Clamping() + { + var zero = default(Rgba64); + var one = default(Rgba64); + zero.FromVector4(Vector4.One * -1234.0f); + one.FromVector4(Vector4.One * 1234.0f); + Assert.Equal(Vector4.Zero, zero.ToVector4()); + Assert.Equal(Vector4.One, one.ToVector4()); + } + + [Fact] + public void Rgba64_ToRgba32() + { + // arrange + var rgba64 = new Rgba64(5140, 9766, 19532, 29555); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 76, 115); + + // act + rgba64.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba64_FromBgra5551() + { + // arrange + var rgba = default(Rgba64); + ushort expected = ushort.MaxValue; + + // act + rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgba.R); + Assert.Equal(expected, rgba.G); + Assert.Equal(expected, rgba.B); + Assert.Equal(expected, rgba.A); + } + + [Fact] + public void Equality_WhenTrue() + { + var c1 = new Rgba64(100, 2000, 3000, 40000); + var c2 = new Rgba64(100, 2000, 3000, 40000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + var c1 = new Rgba64(100, 2000, 3000, 40000); + var c2 = new Rgba64(101, 2000, 3000, 40000); + var c3 = new Rgba64(100, 2000, 3000, 40001); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgba64_FromRgba32() + { + var source = new Rgba32(20, 38, 76, 115); + var expected = new Rgba64(5140, 9766, 19532, 29555); + + Rgba64 actual = default; + actual.FromRgba32(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Rgba32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Rgba32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Bgra32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Bgra32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Argb32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Argb32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Abgr32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Abgr32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Rgb24() + { + var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); + var source = new Rgb24(20, 38, 76); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Bgr24() { - [Fact] - public void Rgba64_PackedValues() - { - Assert.Equal(0x73334CCC2666147BUL, new Rgba64(5243, 9830, 19660, 29491).PackedValue); - - // Test the limits. - 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); - } - - [Fact] - public void Rgba64_ToVector4() - { - Assert.Equal(Vector4.Zero, new Rgba64(0, 0, 0, 0).ToVector4()); - Assert.Equal(Vector4.One, new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).ToVector4()); - } - - [Theory] - [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] - [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] - public void Rgba64_ToScaledVector4(ushort r, ushort g, ushort b, ushort a) - { - // arrange - var short2 = new Rgba64(r, g, b, a); - - float max = ushort.MaxValue; - float rr = r / max; - float gg = g / max; - float bb = b / max; - float aa = a / max; - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(rr, actual.X); - Assert.Equal(gg, actual.Y); - Assert.Equal(bb, actual.Z); - Assert.Equal(aa, actual.W); - } - - [Theory] - [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] - [InlineData(0, 0, 0, 0)] - [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); - - // assert - Assert.Equal(source, actual); - } - - [Fact] - public void Rgba64_Clamping() - { - var zero = default(Rgba64); - var one = default(Rgba64); - zero.FromVector4(Vector4.One * -1234.0f); - one.FromVector4(Vector4.One * 1234.0f); - Assert.Equal(Vector4.Zero, zero.ToVector4()); - Assert.Equal(Vector4.One, one.ToVector4()); - } - - [Fact] - public void Rgba64_ToRgba32() - { - // arrange - var rgba64 = new Rgba64(5140, 9766, 19532, 29555); - var actual = default(Rgba32); - var expected = new Rgba32(20, 38, 76, 115); - - // act - rgba64.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Rgba64_FromBgra5551() - { - // arrange - var rgba = default(Rgba64); - ushort expected = ushort.MaxValue; - - // act - rgba.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgba.R); - Assert.Equal(expected, rgba.G); - Assert.Equal(expected, rgba.B); - Assert.Equal(expected, rgba.A); - } - - [Fact] - public void Equality_WhenTrue() - { - var c1 = new Rgba64(100, 2000, 3000, 40000); - var c2 = new Rgba64(100, 2000, 3000, 40000); - - Assert.True(c1.Equals(c2)); - Assert.True(c1.GetHashCode() == c2.GetHashCode()); - } - - [Fact] - public void Equality_WhenFalse() - { - var c1 = new Rgba64(100, 2000, 3000, 40000); - var c2 = new Rgba64(101, 2000, 3000, 40000); - var c3 = new Rgba64(100, 2000, 3000, 40001); - - Assert.False(c1.Equals(c2)); - Assert.False(c2.Equals(c3)); - Assert.False(c3.Equals(c1)); - } - - [Fact] - public void Rgba64_FromRgba32() - { - var source = new Rgba32(20, 38, 76, 115); - var expected = new Rgba64(5140, 9766, 19532, 29555); - - Rgba64 actual = default; - actual.FromRgba32(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Rgba32() - { - var expected = new Rgba64(5140, 9766, 19532, 29555); - var source = new Rgba32(20, 38, 76, 115); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Bgra32() - { - var expected = new Rgba64(5140, 9766, 19532, 29555); - var source = new Bgra32(20, 38, 76, 115); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Argb32() - { - var expected = new Rgba64(5140, 9766, 19532, 29555); - var source = new Argb32(20, 38, 76, 115); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Abgr32() - { - var expected = new Rgba64(5140, 9766, 19532, 29555); - var source = new Abgr32(20, 38, 76, 115); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Rgb24() - { - var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); - var source = new Rgb24(20, 38, 76); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Bgr24() - { - var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); - var source = new Bgr24(20, 38, 76); - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ConstructFrom_Vector4() - { - var source = new Vector4(0f, 0.2f, 0.5f, 1f); - Rgba64 expected = default; - expected.FromScaledVector4(source); - - var actual = new Rgba64(source); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ToRgba32_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Rgba32(20, 38, 76, 115); - - // act - var actual = source.ToRgba32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToBgra32_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Bgra32(20, 38, 76, 115); - - // act - var actual = source.ToBgra32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToArgb32_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Argb32(20, 38, 76, 115); - - // act - var actual = source.ToArgb32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToAbgr32_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Abgr32(20, 38, 76, 115); - - // act - var actual = source.ToAbgr32(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToRgb24_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Rgb24(20, 38, 76); - - // act - var actual = source.ToRgb24(); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void ToBgr24_Retval() - { - // arrange - var source = new Rgba64(5140, 9766, 19532, 29555); - var expected = new Bgr24(20, 38, 76); - - // act - var actual = source.ToBgr24(); - - // assert - Assert.Equal(expected, actual); - } + var expected = new Rgba64(5140, 9766, 19532, ushort.MaxValue); + var source = new Bgr24(20, 38, 76); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ConstructFrom_Vector4() + { + var source = new Vector4(0f, 0.2f, 0.5f, 1f); + Rgba64 expected = default; + expected.FromScaledVector4(source); + + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ToRgba32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Rgba32(20, 38, 76, 115); + + // act + var actual = source.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToBgra32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Bgra32(20, 38, 76, 115); + + // act + var actual = source.ToBgra32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToArgb32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Argb32(20, 38, 76, 115); + + // act + var actual = source.ToArgb32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToAbgr32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Abgr32(20, 38, 76, 115); + + // act + var actual = source.ToAbgr32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToRgb24_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Rgb24(20, 38, 76); + + // act + var actual = source.ToRgb24(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void ToBgr24_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Bgr24(20, 38, 76); + + // act + var actual = source.ToBgr24(); + + // assert + Assert.Equal(expected, actual); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs index 34d2394c02..ac6d2e3ca5 100644 --- a/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaVectorTests.cs @@ -4,206 +4,204 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Tests the struct. +/// +[Trait("Category", "PixelFormats")] +public class RgbaVectorTests { /// - /// Tests the struct. + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new RgbaVector(0, 0, 0F); + var color2 = new RgbaVector(0, 0, 0, 1F); + var color3 = RgbaVector.FromHex("#000"); + var color4 = RgbaVector.FromHex("#000F"); + var color5 = RgbaVector.FromHex("#000000"); + var color6 = RgbaVector.FromHex("#000000FF"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. /// - [Trait("Category", "PixelFormats")] - public class RgbaVectorTests + [Fact] + public void AreNotEqual() { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - var color1 = new RgbaVector(0, 0, 0F); - var color2 = new RgbaVector(0, 0, 0, 1F); - var color3 = RgbaVector.FromHex("#000"); - var color4 = RgbaVector.FromHex("#000F"); - var color5 = RgbaVector.FromHex("#000000"); - var color6 = RgbaVector.FromHex("#000000FF"); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - Assert.Equal(color1, color6); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - var color1 = new RgbaVector(1, 0, 0, 1); - var color2 = new RgbaVector(0, 0, 0, 1); - var color3 = RgbaVector.FromHex("#000"); - var color4 = RgbaVector.FromHex("#000000"); - var color5 = RgbaVector.FromHex("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - var color1 = new RgbaVector(1, .1F, .133F, .864F); - Assert.Equal(1F, color1.R); - Assert.Equal(.1F, color1.G); - Assert.Equal(.133F, color1.B); - Assert.Equal(.864F, color1.A); - - var color2 = new RgbaVector(1, .1f, .133f); - Assert.Equal(1F, color2.R); - Assert.Equal(.1F, color2.G); - Assert.Equal(.133F, color2.B); - Assert.Equal(1F, color2.A); - } - - /// - /// Tests whether FromHex and ToHex work correctly. - /// - [Fact] - public void FromAndToHex() - { - var color = RgbaVector.FromHex("#AABBCCDD"); - Assert.Equal(170 / 255F, color.R); - Assert.Equal(187 / 255F, color.G); - Assert.Equal(204 / 255F, color.B); - Assert.Equal(221 / 255F, color.A); - - color.A = 170 / 255F; - color.B = 187 / 255F; - color.G = 204 / 255F; - color.R = 221 / 255F; - - Assert.Equal("DDCCBBAA", color.ToHex()); - - color.R = 0; - - Assert.Equal("00CCBBAA", color.ToHex()); - - color.A = 255 / 255F; - - Assert.Equal("00CCBBFF", color.ToHex()); - } - - /// - /// Tests that the individual float elements are laid out in RGBA order. - /// - [Fact] - public void FloatLayout() - { - var color = new RgbaVector(1F, 2, 3, 4); - Vector4 colorBase = Unsafe.As(ref Unsafe.Add(ref color, 0)); - float[] ordered = new float[4]; - colorBase.CopyTo(ordered); - - Assert.Equal(1, ordered[0]); - Assert.Equal(2, ordered[1]); - Assert.Equal(3, ordered[2]); - Assert.Equal(4, ordered[3]); - } - - [Fact] - public void RgbaVector_FromRgb48() - { - // arrange - var input = default(RgbaVector); - var actual = default(Rgb48); - var expected = new Rgb48(65535, 0, 65535); - - // act - input.FromRgb48(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void RgbaVector_FromRgba64() - { - // arrange - var input = default(RgbaVector); - var actual = default(Rgba64); - var expected = new Rgba64(65535, 0, 65535, 0); - - // act - input.FromRgba64(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void RgbaVector_FromBgra5551() - { - // arrange - var rgb = default(RgbaVector); - Vector4 expected = Vector4.One; - - // act - rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, rgb.ToScaledVector4()); - } - - [Fact] - public void RgbaVector_FromGrey16() - { - // arrange - var rgba = default(RgbaVector); - Vector4 expected = Vector4.One; - - // act - rgba.FromL16(new L16(ushort.MaxValue)); - - // assert - Assert.Equal(expected, rgba.ToScaledVector4()); - } - - [Fact] - public void RgbaVector_FromGrey8() - { - // arrange - var rgba = default(RgbaVector); - Vector4 expected = Vector4.One; - - // act - rgba.FromL8(new L8(byte.MaxValue)); - - // assert - Assert.Equal(expected, rgba.ToScaledVector4()); - } - - [Fact] - public void Issue2048() - { - // https://github.com/SixLabors/ImageSharp/issues/2048 - RgbaVector green = Color.Green.ToPixel(); - using Image source = new(Configuration.Default, 1, 1, green); - using Image clone = source.CloneAs(); - - Rgba32 srcColor = default; - Rgba32 cloneColor = default; - source[0, 0].ToRgba32(ref srcColor); - clone[0, 0].ToRgba32(ref cloneColor); - - Assert.Equal(srcColor, cloneColor); - } + var color1 = new RgbaVector(1, 0, 0, 1); + var color2 = new RgbaVector(0, 0, 0, 1); + var color3 = RgbaVector.FromHex("#000"); + var color4 = RgbaVector.FromHex("#000000"); + var color5 = RgbaVector.FromHex("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + var color1 = new RgbaVector(1, .1F, .133F, .864F); + Assert.Equal(1F, color1.R); + Assert.Equal(.1F, color1.G); + Assert.Equal(.133F, color1.B); + Assert.Equal(.864F, color1.A); + + var color2 = new RgbaVector(1, .1f, .133f); + Assert.Equal(1F, color2.R); + Assert.Equal(.1F, color2.G); + Assert.Equal(.133F, color2.B); + Assert.Equal(1F, color2.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + var color = RgbaVector.FromHex("#AABBCCDD"); + Assert.Equal(170 / 255F, color.R); + Assert.Equal(187 / 255F, color.G); + Assert.Equal(204 / 255F, color.B); + Assert.Equal(221 / 255F, color.A); + + color.A = 170 / 255F; + color.B = 187 / 255F; + color.G = 204 / 255F; + color.R = 221 / 255F; + + Assert.Equal("DDCCBBAA", color.ToHex()); + + color.R = 0; + + Assert.Equal("00CCBBAA", color.ToHex()); + + color.A = 255 / 255F; + + Assert.Equal("00CCBBFF", color.ToHex()); + } + + /// + /// Tests that the individual float elements are laid out in RGBA order. + /// + [Fact] + public void FloatLayout() + { + var color = new RgbaVector(1F, 2, 3, 4); + Vector4 colorBase = Unsafe.As(ref Unsafe.Add(ref color, 0)); + float[] ordered = new float[4]; + colorBase.CopyTo(ordered); + + Assert.Equal(1, ordered[0]); + Assert.Equal(2, ordered[1]); + Assert.Equal(3, ordered[2]); + Assert.Equal(4, ordered[3]); + } + + [Fact] + public void RgbaVector_FromRgb48() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void RgbaVector_FromRgba64() + { + // arrange + var input = default(RgbaVector); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void RgbaVector_FromBgra5551() + { + // arrange + var rgb = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgb.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, rgb.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey16() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromL16(new L16(ushort.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } + + [Fact] + public void RgbaVector_FromGrey8() + { + // arrange + var rgba = default(RgbaVector); + Vector4 expected = Vector4.One; + + // act + rgba.FromL8(new L8(byte.MaxValue)); + + // assert + Assert.Equal(expected, rgba.ToScaledVector4()); + } + + [Fact] + public void Issue2048() + { + // https://github.com/SixLabors/ImageSharp/issues/2048 + RgbaVector green = Color.Green.ToPixel(); + using Image source = new(Configuration.Default, 1, 1, green); + using Image clone = source.CloneAs(); + + Rgba32 srcColor = default; + Rgba32 cloneColor = default; + source[0, 0].ToRgba32(ref srcColor); + clone[0, 0].ToRgba32(ref cloneColor); + + Assert.Equal(srcColor, cloneColor); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index 7f05940a14..8fc080d818 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -3,162 +3,160 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Short2Tests { - [Trait("Category", "PixelFormats")] - public class Short2Tests + [Fact] + public void Short2_PackedValues() + { + // Test ordering + Assert.Equal(0x361d2db1U, new Short2(0x2db1, 0x361d).PackedValue); + Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); + + // Test the limits. + 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); + } + + [Fact] + public void Short2_ToVector2() + { + Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 0x7FFF).ToVector2()); + Assert.Equal(Vector2.Zero, new Short2(Vector2.Zero).ToVector2()); + Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -0x8000).ToVector2()); + Assert.Equal(Vector2.UnitX * 0x7FFF, new Short2(Vector2.UnitX * 0x7FFF).ToVector2()); + Assert.Equal(Vector2.UnitY * 0x7FFF, new Short2(Vector2.UnitY * 0x7FFF).ToVector2()); + } + + [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()); + } + + [Fact] + public void Short2_Clamping() + { + Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 1234567.0f).ToVector2()); + Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -1234567.0f).ToVector2()); + } + + [Fact] + public void Short2_ToScaledVector4() + { + // arrange + var short2 = new Short2(Vector2.One * 0x7FFF); + + // act + Vector4 actual = short2.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Short2_FromScaledVector4() + { + // arrange + var pixel = default(Short2); + var short2 = new Short2(Vector2.One * 0x7FFF); + const ulong expected = 0x7FFF7FFF; + + // act + Vector4 scaled = short2.ToScaledVector4(); + pixel.FromScaledVector4(scaled); + uint actual = pixel.PackedValue; + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_ToRgba32() + { + // arrange + var short2 = new Short2(127.5f, -5.3f); + var actual = default(Rgba32); + var expected = new Rgba32(128, 127, 0, 255); + + // act + short2.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgba32_ToRgba32() + { + // arrange + var short2 = default(Short2); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 0, 255); + + // act + short2.FromRgba32(expected); + short2.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgb48() + { + // arrange + var input = default(Short2); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 65535, 0); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromRgba64() + { + // arrange + var input = default(Short2); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 65535, 0, 65535); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short2_FromBgra5551() { - [Fact] - public void Short2_PackedValues() - { - // Test ordering - Assert.Equal(0x361d2db1U, new Short2(0x2db1, 0x361d).PackedValue); - Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); - - // Test the limits. - 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); - } - - [Fact] - public void Short2_ToVector2() - { - Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 0x7FFF).ToVector2()); - Assert.Equal(Vector2.Zero, new Short2(Vector2.Zero).ToVector2()); - Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -0x8000).ToVector2()); - Assert.Equal(Vector2.UnitX * 0x7FFF, new Short2(Vector2.UnitX * 0x7FFF).ToVector2()); - Assert.Equal(Vector2.UnitY * 0x7FFF, new Short2(Vector2.UnitY * 0x7FFF).ToVector2()); - } - - [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()); - } - - [Fact] - public void Short2_Clamping() - { - Assert.Equal(Vector2.One * 0x7FFF, new Short2(Vector2.One * 1234567.0f).ToVector2()); - Assert.Equal(Vector2.One * -0x8000, new Short2(Vector2.One * -1234567.0f).ToVector2()); - } - - [Fact] - public void Short2_ToScaledVector4() - { - // arrange - var short2 = new Short2(Vector2.One * 0x7FFF); - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Short2_FromScaledVector4() - { - // arrange - var pixel = default(Short2); - var short2 = new Short2(Vector2.One * 0x7FFF); - const ulong expected = 0x7FFF7FFF; - - // act - Vector4 scaled = short2.ToScaledVector4(); - pixel.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_ToRgba32() - { - // arrange - var short2 = new Short2(127.5f, -5.3f); - var actual = default(Rgba32); - var expected = new Rgba32(128, 127, 0, 255); - - // act - short2.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgba32_ToRgba32() - { - // arrange - var short2 = default(Short2); - var actual = default(Rgba32); - var expected = new Rgba32(20, 38, 0, 255); - - // act - short2.FromRgba32(expected); - short2.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgb48() - { - // arrange - var input = default(Short2); - var actual = default(Rgb48); - var expected = new Rgb48(65535, 65535, 0); - - // act - input.FromRgb48(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromRgba64() - { - // arrange - var input = default(Short2); - var actual = default(Rgba64); - var expected = new Rgba64(65535, 65535, 0, 65535); - - // act - input.FromRgba64(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short2_FromBgra5551() - { - // arrange - var short2 = default(Short2); - - // act - short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Vector4 actual = short2.ToScaledVector4(); - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); - } + // arrange + var short2 = default(Short2); + + // act + short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Vector4 actual = short2.ToScaledVector4(); + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(0, actual.Z); + Assert.Equal(1, actual.W); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 37399f81e4..c420627034 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -3,215 +3,213 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Short4Tests { - [Trait("Category", "PixelFormats")] - public class Short4Tests + [Fact] + public void Short4_PackedValues() + { + var shortValue1 = new Short4(11547, 12653, 29623, 193); + var shortValue2 = new Short4(0.1f, -0.3f, 0.5f, -0.7f); + + Assert.Equal(0x00c173b7316d2d1bUL, shortValue1.PackedValue); + Assert.Equal(18446462598732840960, shortValue2.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); + } + + [Fact] + public void Short4_ToVector4() + { + Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); + Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); + Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); + Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); + } + + [Fact] + public void Short4_ToScaledVector4() + { + // arrange + var short4 = new Short4(Vector4.One * 0x7FFF); + + // act + Vector4 actual = short4.ToScaledVector4(); + + // assert + Assert.Equal(1, actual.X); + Assert.Equal(1, actual.Y); + Assert.Equal(1, actual.Z); + Assert.Equal(1, actual.W); + } + + [Fact] + public void Short4_FromScaledVector4() + { + // arrange + var short4 = new Short4(Vector4.One * 0x7FFF); + Vector4 scaled = short4.ToScaledVector4(); + const long expected = 0x7FFF7FFF7FFF7FFF; + + // act + var pixel = default(Short4); + pixel.FromScaledVector4(scaled); + + // assert + Assert.Equal((ulong)expected, pixel.PackedValue); + } + + [Fact] + public void Short4_Clamping() + { + // arrange + var short1 = new Short4(Vector4.One * 1234567.0f); + var short2 = new Short4(Vector4.One * -1234567.0f); + + // act + var vector1 = short1.ToVector4(); + var vector2 = short2.ToVector4(); + + // assert + Assert.Equal(Vector4.One * 0x7FFF, vector1); + Assert.Equal(Vector4.One * -0x8000, vector2); + } + + [Fact] + public void Short4_ToRgba32() + { + // arrange + var shortValue = new Short4(11547, 12653, 29623, 193); + var actual = default(Rgba32); + var expected = new Rgba32(172, 177, 243, 128); + + // act + shortValue.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgba32_ToRgba32() { - [Fact] - public void Short4_PackedValues() - { - var shortValue1 = new Short4(11547, 12653, 29623, 193); - var shortValue2 = new Short4(0.1f, -0.3f, 0.5f, -0.7f); - - Assert.Equal(0x00c173b7316d2d1bUL, shortValue1.PackedValue); - Assert.Equal(18446462598732840960, shortValue2.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); - } - - [Fact] - public void Short4_ToVector4() - { - Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.Zero, new Short4(Vector4.Zero).ToVector4()); - Assert.Equal(Vector4.One * -0x8000, new Short4(Vector4.One * -0x8000).ToVector4()); - Assert.Equal(Vector4.UnitX * 0x7FFF, new Short4(Vector4.UnitX * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitY * 0x7FFF, new Short4(Vector4.UnitY * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitZ * 0x7FFF, new Short4(Vector4.UnitZ * 0x7FFF).ToVector4()); - Assert.Equal(Vector4.UnitW * 0x7FFF, new Short4(Vector4.UnitW * 0x7FFF).ToVector4()); - } - - [Fact] - public void Short4_ToScaledVector4() - { - // arrange - var short4 = new Short4(Vector4.One * 0x7FFF); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); - } - - [Fact] - public void Short4_FromScaledVector4() - { - // arrange - var short4 = new Short4(Vector4.One * 0x7FFF); - Vector4 scaled = short4.ToScaledVector4(); - const long expected = 0x7FFF7FFF7FFF7FFF; - - // act - var pixel = default(Short4); - pixel.FromScaledVector4(scaled); - - // assert - Assert.Equal((ulong)expected, pixel.PackedValue); - } - - [Fact] - public void Short4_Clamping() - { - // arrange - var short1 = new Short4(Vector4.One * 1234567.0f); - var short2 = new Short4(Vector4.One * -1234567.0f); - - // act - var vector1 = short1.ToVector4(); - var vector2 = short2.ToVector4(); - - // assert - Assert.Equal(Vector4.One * 0x7FFF, vector1); - Assert.Equal(Vector4.One * -0x8000, vector2); - } - - [Fact] - public void Short4_ToRgba32() - { - // arrange - var shortValue = new Short4(11547, 12653, 29623, 193); - var actual = default(Rgba32); - var expected = new Rgba32(172, 177, 243, 128); - - // act - shortValue.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgba32_ToRgba32() - { - // arrange - var short4 = default(Short4); - var actual = default(Rgba32); - var expected = new Rgba32(20, 38, 0, 255); - - // act - short4.FromRgba32(expected); - short4.ToRgba32(ref actual); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromBgra32_ToRgba32() - { - // arrange - var short4 = default(Short4); - var actual = default(Bgra32); - var expected = new Bgra32(20, 38, 0, 255); - - // act - short4.FromBgra32(expected); - Rgba32 temp = default; - short4.ToRgba32(ref temp); - actual.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromArgb32_ToRgba32() - { - // arrange - var short4 = default(Short4); - var actual = default(Argb32); - var expected = new Argb32(20, 38, 0, 255); - - // act - short4.FromArgb32(expected); - Rgba32 temp = default; - short4.ToRgba32(ref temp); - actual.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromAbgrb32_ToRgba32() - { - // arrange - var short4 = default(Short4); - var actual = default(Abgr32); - var expected = new Abgr32(20, 38, 0, 255); - - // act - short4.FromAbgr32(expected); - Rgba32 temp = default; - short4.ToRgba32(ref temp); - actual.FromRgba32(temp); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgb48_ToRgb48() - { - // arrange - var input = default(Short4); - var actual = default(Rgb48); - var expected = new Rgb48(65535, 0, 65535); - - // act - input.FromRgb48(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromRgba64_ToRgba64() - { - // arrange - var input = default(Short4); - var actual = default(Rgba64); - var expected = new Rgba64(65535, 0, 65535, 0); - - // act - input.FromRgba64(expected); - actual.FromScaledVector4(input.ToScaledVector4()); - - // assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Short4_FromBgra5551() - { - // arrange - var short4 = default(Short4); - Vector4 expected = Vector4.One; - - // act - short4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); - - // assert - Assert.Equal(expected, short4.ToScaledVector4()); - } + // arrange + var short4 = default(Short4); + var actual = default(Rgba32); + var expected = new Rgba32(20, 38, 0, 255); + + // act + short4.FromRgba32(expected); + short4.ToRgba32(ref actual); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromBgra32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Bgra32); + var expected = new Bgra32(20, 38, 0, 255); + + // act + short4.FromBgra32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromArgb32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Argb32); + var expected = new Argb32(20, 38, 0, 255); + + // act + short4.FromArgb32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromAbgrb32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Abgr32); + var expected = new Abgr32(20, 38, 0, 255); + + // act + short4.FromAbgr32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgb48_ToRgb48() + { + // arrange + var input = default(Short4); + var actual = default(Rgb48); + var expected = new Rgb48(65535, 0, 65535); + + // act + input.FromRgb48(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromRgba64_ToRgba64() + { + // arrange + var input = default(Short4); + var actual = default(Rgba64); + var expected = new Rgba64(65535, 0, 65535, 0); + + // act + input.FromRgba64(expected); + actual.FromScaledVector4(input.ToScaledVector4()); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Short4_FromBgra5551() + { + // arrange + var short4 = default(Short4); + Vector4 expected = Vector4.One; + + // act + short4.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, short4.ToScaledVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs index c4f6e327a1..5cc35b43ed 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -3,104 +3,102 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; -namespace SixLabors.ImageSharp.Tests.PixelFormats +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class UnPackedPixelTests { - [Trait("Category", "PixelFormats")] - public class UnPackedPixelTests + [Fact] + public void Color_Types_From_Bytes_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Floats_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Vector4_Produce_Equal_Scaled_Component_OutPut() + { + var color = new Rgba32(new Vector4(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F)); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Vector3_Produce_Equal_Scaled_Component_OutPut() { - [Fact] - public void Color_Types_From_Bytes_Produce_Equal_Scaled_Component_OutPut() - { - var color = new Rgba32(24, 48, 96, 192); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Floats_Produce_Equal_Scaled_Component_OutPut() - { - var color = new Rgba32(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Vector4_Produce_Equal_Scaled_Component_OutPut() - { - var color = new Rgba32(new Vector4(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F)); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Vector3_Produce_Equal_Scaled_Component_OutPut() - { - var color = new Rgba32(new Vector3(24 / 255F, 48 / 255F, 96 / 255F)); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() - { - var color = Rgba32.ParseHex("183060C0"); - var colorVector = RgbaVector.FromHex("183060C0"); - - Assert.Equal(color.R, (byte)(colorVector.R * 255)); - Assert.Equal(color.G, (byte)(colorVector.G * 255)); - Assert.Equal(color.B, (byte)(colorVector.B * 255)); - Assert.Equal(color.A, (byte)(colorVector.A * 255)); - } - - [Fact] - public void Color_Types_To_Vector4_Produce_Equal_OutPut() - { - var color = new Rgba32(24, 48, 96, 192); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Assert.Equal(color.ToVector4(), colorVector.ToVector4()); - } - - [Fact] - public void Color_Types_To_RgbaBytes_Produce_Equal_OutPut() - { - var color = new Rgba32(24, 48, 96, 192); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - Rgba32 rgba = default; - Rgba32 rgbaVector = default; - color.ToRgba32(ref rgba); - colorVector.ToRgba32(ref rgbaVector); - - Assert.Equal(rgba, rgbaVector); - } - - [Fact] - public void Color_Types_To_Hex_Produce_Equal_OutPut() - { - var color = new Rgba32(24, 48, 96, 192); - var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); - - // 183060C0 - Assert.Equal(color.ToHex(), colorVector.ToHex()); - } + var color = new Rgba32(new Vector3(24 / 255F, 48 / 255F, 96 / 255F)); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() + { + var color = Rgba32.ParseHex("183060C0"); + var colorVector = RgbaVector.FromHex("183060C0"); + + Assert.Equal(color.R, (byte)(colorVector.R * 255)); + Assert.Equal(color.G, (byte)(colorVector.G * 255)); + Assert.Equal(color.B, (byte)(colorVector.B * 255)); + Assert.Equal(color.A, (byte)(colorVector.A * 255)); + } + + [Fact] + public void Color_Types_To_Vector4_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Assert.Equal(color.ToVector4(), colorVector.ToVector4()); + } + + [Fact] + public void Color_Types_To_RgbaBytes_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + Rgba32 rgba = default; + Rgba32 rgbaVector = default; + color.ToRgba32(ref rgba); + colorVector.ToRgba32(ref rgbaVector); + + Assert.Equal(rgba, rgbaVector); + } + + [Fact] + public void Color_Types_To_Hex_Produce_Equal_OutPut() + { + var color = new Rgba32(24, 48, 96, 192); + var colorVector = new RgbaVector(24 / 255F, 48 / 255F, 96 / 255F, 192 / 255F); + + // 183060C0 + Assert.Equal(color.ToHex(), colorVector.ToHex()); } } diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs index 2e418c5a7a..94309d2008 100644 --- a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -1,264 +1,261 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Primitives +namespace SixLabors.ImageSharp.Tests.Primitives; + +public class ColorMatrixTests { - public class ColorMatrixTests - { - private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); - [Fact] - public void ColorMatrixIdentityIsCorrect() - { - ColorMatrix val = default; - val.M11 = val.M22 = val.M33 = val.M44 = 1F; + [Fact] + public void ColorMatrixIdentityIsCorrect() + { + 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] - public void ColorMatrixCanDetectIdentity() - { - ColorMatrix m = ColorMatrix.Identity; - Assert.True(m.IsIdentity); + [Fact] + public void ColorMatrixCanDetectIdentity() + { + ColorMatrix m = ColorMatrix.Identity; + Assert.True(m.IsIdentity); - m.M12 = 1F; - Assert.False(m.IsIdentity); - } + m.M12 = 1F; + Assert.False(m.IsIdentity); + } - [Fact] - public void ColorMatrixEquality() - { - ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F); - ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F); - object obj = m2; + [Fact] + public void ColorMatrixEquality() + { + ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F); + ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F); + object obj = m2; - Assert.True(m.Equals(obj)); - Assert.True(m.Equals(m2)); - Assert.True(m == m2); - Assert.False(m != m2); - } + Assert.True(m.Equals(obj)); + Assert.True(m.Equals(m2)); + Assert.True(m == m2); + Assert.False(m != m2); + } - [Fact] - public void ColorMatrixMultiply() - { - ColorMatrix value1 = CreateAllTwos(); - ColorMatrix value2 = CreateAllThrees(); + [Fact] + public void ColorMatrixMultiply() + { + ColorMatrix value1 = CreateAllTwos(); + ColorMatrix value2 = CreateAllThrees(); - var m = default(ColorMatrix); + var m = default(ColorMatrix); - // First row - m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); - m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); - m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); - m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); + // First row + m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); + m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42); + m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43); + m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44); - // Second row - m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); - m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); - m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); - m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); + // Second row + m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41); + m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42); + m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43); + m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44); - // Third row - m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); - m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); - m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); - m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); + // Third row + m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41); + m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42); + m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43); + m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44); - // Fourth row - m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); - m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); - m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); - m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); + // Fourth row + m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41); + m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42); + m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43); + m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44); - // Fifth row - m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; - m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; - 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; + // Fifth row + m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51; + m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52; + 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 = CreateAllTwos(); - Assert.Equal(CreateAllFours(), m * 2, this.approximateFloatComparer); - } + [Fact] + public void ColorMatrixMultiplyScalar() + { + ColorMatrix m = CreateAllTwos(); + Assert.Equal(CreateAllFours(), m * 2, this.approximateFloatComparer); + } - [Fact] - public void ColorMatrixSubtract() - { - ColorMatrix m = CreateAllOnes() + CreateAllTwos(); - Assert.Equal(CreateAllThrees(), m); - } + [Fact] + public void ColorMatrixSubtract() + { + ColorMatrix m = CreateAllOnes() + CreateAllTwos(); + Assert.Equal(CreateAllThrees(), m); + } - [Fact] - public void ColorMatrixNegate() - { - ColorMatrix m = CreateAllOnes() * -1F; - Assert.Equal(m, -CreateAllOnes()); - } + [Fact] + public void ColorMatrixNegate() + { + ColorMatrix m = CreateAllOnes() * -1F; + Assert.Equal(m, -CreateAllOnes()); + } - [Fact] - public void ColorMatrixAdd() - { - ColorMatrix m = CreateAllOnes() + CreateAllTwos(); - Assert.Equal(CreateAllThrees(), m); - } + [Fact] + public void ColorMatrixAdd() + { + ColorMatrix m = CreateAllOnes() + CreateAllTwos(); + Assert.Equal(CreateAllThrees(), m); + } - [Fact] - public void ColorMatrixHashCode() - { - ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); - HashCode hash = default; - hash.Add(m.M11); - hash.Add(m.M12); - hash.Add(m.M13); - hash.Add(m.M14); - hash.Add(m.M21); - hash.Add(m.M22); - hash.Add(m.M23); - hash.Add(m.M24); - hash.Add(m.M31); - hash.Add(m.M32); - hash.Add(m.M33); - hash.Add(m.M34); - hash.Add(m.M41); - hash.Add(m.M42); - hash.Add(m.M43); - hash.Add(m.M44); - hash.Add(m.M51); - hash.Add(m.M52); - hash.Add(m.M53); - hash.Add(m.M54); + [Fact] + public void ColorMatrixHashCode() + { + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + HashCode hash = default; + hash.Add(m.M11); + hash.Add(m.M12); + hash.Add(m.M13); + hash.Add(m.M14); + hash.Add(m.M21); + hash.Add(m.M22); + hash.Add(m.M23); + hash.Add(m.M24); + hash.Add(m.M31); + hash.Add(m.M32); + hash.Add(m.M33); + hash.Add(m.M34); + hash.Add(m.M41); + hash.Add(m.M42); + hash.Add(m.M43); + hash.Add(m.M44); + hash.Add(m.M51); + hash.Add(m.M52); + hash.Add(m.M53); + hash.Add(m.M54); - Assert.Equal(hash.ToHashCode(), m.GetHashCode()); - } + Assert.Equal(hash.ToHashCode(), m.GetHashCode()); + } - [Fact] - public void ColorMatrixToString() - { - ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); + [Fact] + public void ColorMatrixToString() + { + ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F); - CultureInfo ci = CultureInfo.CurrentCulture; + 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)); + 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)); #pragma warning restore SA1117 // Parameters should be on same line or separate lines - Assert.Equal(expected, m.ToString()); - } + Assert.Equal(expected, m.ToString()); + } - private static ColorMatrix CreateAllOnes() - => new() - { - M11 = 1F, - M12 = 1F, - M13 = 1F, - M14 = 1F, - M21 = 1F, - M22 = 1F, - M23 = 1F, - M24 = 1F, - M31 = 1F, - M32 = 1F, - M33 = 1F, - M34 = 1F, - M41 = 1F, - M42 = 1F, - M43 = 1F, - M44 = 1F, - M51 = 1F, - M52 = 1F, - M53 = 1F, - M54 = 1F - }; + private static ColorMatrix CreateAllOnes() + => new() + { + M11 = 1F, + M12 = 1F, + M13 = 1F, + M14 = 1F, + M21 = 1F, + M22 = 1F, + M23 = 1F, + M24 = 1F, + M31 = 1F, + M32 = 1F, + M33 = 1F, + M34 = 1F, + M41 = 1F, + M42 = 1F, + M43 = 1F, + M44 = 1F, + M51 = 1F, + M52 = 1F, + M53 = 1F, + M54 = 1F + }; - private static ColorMatrix CreateAllTwos() - => new() - { - M11 = 2F, - M12 = 2F, - M13 = 2F, - M14 = 2F, - M21 = 2F, - M22 = 2F, - M23 = 2F, - M24 = 2F, - M31 = 2F, - M32 = 2F, - M33 = 2F, - M34 = 2F, - M41 = 2F, - M42 = 2F, - M43 = 2F, - M44 = 2F, - M51 = 2F, - M52 = 2F, - M53 = 2F, - M54 = 2F - }; + private static ColorMatrix CreateAllTwos() + => new() + { + M11 = 2F, + M12 = 2F, + M13 = 2F, + M14 = 2F, + M21 = 2F, + M22 = 2F, + M23 = 2F, + M24 = 2F, + M31 = 2F, + M32 = 2F, + M33 = 2F, + M34 = 2F, + M41 = 2F, + M42 = 2F, + M43 = 2F, + M44 = 2F, + M51 = 2F, + M52 = 2F, + M53 = 2F, + M54 = 2F + }; - private static ColorMatrix CreateAllThrees() - => new() - { - M11 = 3F, - M12 = 3F, - M13 = 3F, - M14 = 3F, - M21 = 3F, - M22 = 3F, - M23 = 3F, - M24 = 3F, - M31 = 3F, - M32 = 3F, - M33 = 3F, - M34 = 3F, - M41 = 3F, - M42 = 3F, - M43 = 3F, - M44 = 3F, - M51 = 3F, - M52 = 3F, - M53 = 3F, - M54 = 3F - }; + private static ColorMatrix CreateAllThrees() + => new() + { + M11 = 3F, + M12 = 3F, + M13 = 3F, + M14 = 3F, + M21 = 3F, + M22 = 3F, + M23 = 3F, + M24 = 3F, + M31 = 3F, + M32 = 3F, + M33 = 3F, + M34 = 3F, + M41 = 3F, + M42 = 3F, + M43 = 3F, + M44 = 3F, + M51 = 3F, + M52 = 3F, + M53 = 3F, + M54 = 3F + }; - private static ColorMatrix CreateAllFours() - => new() - { - M11 = 4F, - M12 = 4F, - M13 = 4F, - M14 = 4F, - M21 = 4F, - M22 = 4F, - M23 = 4F, - M24 = 4F, - M31 = 4F, - M32 = 4F, - M33 = 4F, - M34 = 4F, - M41 = 4F, - M42 = 4F, - M43 = 4F, - M44 = 4F, - M51 = 4F, - M52 = 4F, - M53 = 4F, - M54 = 4F - }; - } + private static ColorMatrix CreateAllFours() + => new() + { + M11 = 4F, + M12 = 4F, + M13 = 4F, + M14 = 4F, + M21 = 4F, + M22 = 4F, + M23 = 4F, + M24 = 4F, + M31 = 4F, + M32 = 4F, + M33 = 4F, + M34 = 4F, + M41 = 4F, + M42 = 4F, + M43 = 4F, + M44 = 4F, + M51 = 4F, + M52 = 4F, + M53 = 4F, + M54 = 4F + }; } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index db60eccbc9..ecc5923264 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -1,140 +1,136 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Primitives; -namespace SixLabors.ImageSharp.Tests.Primitives +public class DenseMatrixTests { - public class DenseMatrixTests + private static readonly float[,] FloydSteinbergMatrix = { - private static readonly float[,] FloydSteinbergMatrix = - { - { 0, 0, 7 }, - { 3, 5, 1 } - }; + { 0, 0, 7 }, + { 3, 5, 1 } + }; - [Fact] - public void DenseMatrixThrowsOnNullInitializer() - { - Assert.Throws(() => new DenseMatrix(null)); - } + [Fact] + public void DenseMatrixThrowsOnNullInitializer() + { + Assert.Throws(() => new DenseMatrix(null)); + } - [Fact] - public void DenseMatrixThrowsOnEmptyZeroWidth() - { - Assert.Throws(() => new DenseMatrix(0, 10)); - } + [Fact] + public void DenseMatrixThrowsOnEmptyZeroWidth() + { + Assert.Throws(() => new DenseMatrix(0, 10)); + } - [Fact] - public void DenseMatrixThrowsOnEmptyZeroHeight() - { - Assert.Throws(() => new DenseMatrix(10, 0)); - } + [Fact] + public void DenseMatrixThrowsOnEmptyZeroHeight() + { + Assert.Throws(() => new DenseMatrix(10, 0)); + } - [Fact] - public void DenseMatrixThrowsOnEmptyInitializer() - { - Assert.Throws(() => new DenseMatrix(new float[0, 0])); - } + [Fact] + public void DenseMatrixThrowsOnEmptyInitializer() + { + Assert.Throws(() => new DenseMatrix(new float[0, 0])); + } - [Fact] - public void DenseMatrixReturnsCorrectDimensions() - { - var dense = new DenseMatrix(FloydSteinbergMatrix); - Assert.True(dense.Columns == FloydSteinbergMatrix.GetLength(1)); - Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); - Assert.Equal(3, dense.Columns); - Assert.Equal(2, dense.Rows); - Assert.Equal(new Size(3, 2), dense.Size); - } + [Fact] + public void DenseMatrixReturnsCorrectDimensions() + { + var dense = new DenseMatrix(FloydSteinbergMatrix); + Assert.True(dense.Columns == FloydSteinbergMatrix.GetLength(1)); + Assert.True(dense.Rows == FloydSteinbergMatrix.GetLength(0)); + Assert.Equal(3, dense.Columns); + Assert.Equal(2, dense.Rows); + Assert.Equal(new Size(3, 2), dense.Size); + } - [Fact] - public void DenseMatrixGetReturnsCorrectResults() - { - DenseMatrix dense = FloydSteinbergMatrix; + [Fact] + public void DenseMatrixGetReturnsCorrectResults() + { + DenseMatrix dense = FloydSteinbergMatrix; - for (int row = 0; row < dense.Rows; row++) + for (int row = 0; row < dense.Rows; row++) + { + for (int column = 0; column < dense.Columns; column++) { - for (int column = 0; column < dense.Columns; column++) - { - Assert.True(Math.Abs(dense[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); - } + Assert.True(Math.Abs(dense[row, column] - FloydSteinbergMatrix[row, column]) < Constants.Epsilon); } } + } - [Fact] - public void DenseMatrixGetSetReturnsCorrectResults() - { - var dense = new DenseMatrix(4, 4); - const int Val = 5; - - dense[3, 3] = Val; - - Assert.Equal(Val, dense[3, 3]); - } - - [Fact] - public void DenseMatrixCanFillAndClear() - { - var dense = new DenseMatrix(9); - dense.Fill(4); + [Fact] + public void DenseMatrixGetSetReturnsCorrectResults() + { + var dense = new DenseMatrix(4, 4); + const int Val = 5; - for (int i = 0; i < dense.Data.Length; i++) - { - Assert.Equal(4, dense.Data[i]); - } + dense[3, 3] = Val; - dense.Clear(); + Assert.Equal(Val, dense[3, 3]); + } - for (int i = 0; i < dense.Data.Length; i++) - { - Assert.Equal(0, dense.Data[i]); - } - } + [Fact] + public void DenseMatrixCanFillAndClear() + { + var dense = new DenseMatrix(9); + dense.Fill(4); - [Fact] - public void DenseMatrixCorrectlyCasts() + for (int i = 0; i < dense.Data.Length; i++) { - float[,] actual = new DenseMatrix(FloydSteinbergMatrix); - Assert.Equal(FloydSteinbergMatrix, actual); + Assert.Equal(4, dense.Data[i]); } - [Fact] - public void DenseMatrixCanTranspose() - { - var dense = new DenseMatrix(3, 1); - dense[0, 0] = 1; - dense[0, 1] = 2; - dense[0, 2] = 3; - - DenseMatrix transposed = dense.Transpose(); - - Assert.Equal(dense.Columns, transposed.Rows); - Assert.Equal(dense.Rows, transposed.Columns); - Assert.Equal(1, transposed[0, 0]); - Assert.Equal(2, transposed[1, 0]); - Assert.Equal(3, transposed[2, 0]); - } + dense.Clear(); - [Fact] - public void DenseMatrixEquality() + for (int i = 0; i < dense.Data.Length; i++) { - 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()); + Assert.Equal(0, dense.Data[i]); } } + + [Fact] + public void DenseMatrixCorrectlyCasts() + { + float[,] actual = new DenseMatrix(FloydSteinbergMatrix); + Assert.Equal(FloydSteinbergMatrix, actual); + } + + [Fact] + public void DenseMatrixCanTranspose() + { + var dense = new DenseMatrix(3, 1); + dense[0, 0] = 1; + dense[0, 1] = 2; + dense[0, 2] = 3; + + DenseMatrix transposed = dense.Transpose(); + + Assert.Equal(dense.Columns, transposed.Rows); + Assert.Equal(dense.Rows, transposed.Columns); + Assert.Equal(1, transposed[0, 0]); + 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/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs index 2d0de3d56e..c3d9e8176b 100644 --- a/tests/ImageSharp.Tests/Primitives/PointFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointFTests.cs @@ -1,213 +1,210 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class PointFTests { - public class PointFTests + private static readonly ApproximateFloatComparer ApproximateFloatComparer = + new ApproximateFloatComparer(1e-6f); + + [Fact] + public void CanReinterpretCastFromVector2() { - private static readonly ApproximateFloatComparer ApproximateFloatComparer = - new ApproximateFloatComparer(1e-6f); + var vector = new Vector2(1, 2); - [Fact] - public void CanReinterpretCastFromVector2() - { - var vector = new Vector2(1, 2); + PointF point = Unsafe.As(ref vector); - PointF point = Unsafe.As(ref vector); + Assert.Equal(vector.X, point.X); + Assert.Equal(vector.Y, point.Y); + } - Assert.Equal(vector.X, point.X); - Assert.Equal(vector.Y, point.Y); - } + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, PointF.Empty); + } - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, PointF.Empty); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0.0, 0.0)] + public void NonDefaultConstructorTest(float x, float y) + { + var p1 = new PointF(x, y); - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(0.0, 0.0)] - public void NonDefaultConstructorTest(float x, float y) - { - var p1 = new PointF(x, y); + Assert.Equal(x, p1.X); + Assert.Equal(y, p1.Y); + } - Assert.Equal(x, p1.X); - Assert.Equal(y, p1.Y); - } + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(PointF.Empty.IsEmpty); + Assert.True(default(PointF).IsEmpty); + Assert.True(new PointF(0, 0).IsEmpty); + } - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(PointF.Empty.IsEmpty); - Assert.True(default(PointF).IsEmpty); - Assert.True(new PointF(0, 0).IsEmpty); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float x, float y) + { + Assert.False(new PointF(x, y).IsEmpty); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - public void IsEmptyRandomTest(float x, float y) - { - Assert.False(new PointF(x, y).IsEmpty); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(float x, float y) + { + var p = new PointF(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void CoordinatesTest(float x, float y) - { - var p = new PointF(x, y); - Assert.Equal(x, p.X); - Assert.Equal(y, p.Y); + p.X = 10; + Assert.Equal(10, p.X); - p.X = 10; - Assert.Equal(10, p.X); + p.Y = -10.123f; + Assert.Equal(-10.123, p.Y, 3); + } - p.Y = -10.123f; - Assert.Equal(-10.123, p.Y, 3); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] + [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void ArithmeticTestWithSize(float x, float y, int x1, int y1) + { + var p = new PointF(x, y); + var s = new Size(x1, y1); + + var addExpected = new PointF(x + x1, y + y1); + var subExpected = new PointF(x - x1, y - y1); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] - [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(0, 0, 0, 0)] - public void ArithmeticTestWithSize(float x, float y, int x1, int y1) - { - var p = new PointF(x, y); - var s = new Size(x1, y1); - - var addExpected = new PointF(x + x1, y + y1); - var subExpected = new PointF(x - x1, y - y1); - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, PointF.Add(p, s)); - Assert.Equal(subExpected, PointF.Subtract(p, s)); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTestWithSizeF(float x, float y) + { + var p = new PointF(x, y); + var s = new SizeF(y, x); + + var addExpected = new PointF(x + y, y + x); + var subExpected = new PointF(x - y, y - x); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTestWithSizeF(float x, float y) - { - var p = new PointF(x, y); - var s = new SizeF(y, x); - - var addExpected = new PointF(x + y, y + x); - var subExpected = new PointF(x - y, y - x); - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, PointF.Add(p, s)); - Assert.Equal(subExpected, PointF.Subtract(p, s)); - } + [Fact] + public void RotateTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); - [Fact] - public void RotateTest() - { - var p = new PointF(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); + var pout = PointF.Transform(p, matrix); - var pout = PointF.Transform(p, matrix); + Assert.Equal(-2.82842732F, pout.X, ApproximateFloatComparer); + Assert.Equal(21.2132034F, pout.Y, ApproximateFloatComparer); + } - Assert.Equal(-2.82842732F, pout.X, ApproximateFloatComparer); - Assert.Equal(21.2132034F, pout.Y, ApproximateFloatComparer); - } + [Fact] + public void SkewTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); - [Fact] - public void SkewTest() - { - var p = new PointF(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, PointF.Empty); + var pout = PointF.Transform(p, matrix); + Assert.Equal(new PointF(30, 30), pout); + } - var pout = PointF.Transform(p, matrix); - Assert.Equal(new PointF(30, 30), pout); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float x, float y) + { + var pLeft = new PointF(x, y); + var pRight = new PointF(y, x); - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(float x, float y) + if (x == y) { - var pLeft = new PointF(x, y); - var pRight = new PointF(y, x); - - if (x == y) - { - Assert.True(pLeft == pRight); - Assert.False(pLeft != pRight); - Assert.True(pLeft.Equals(pRight)); - Assert.True(pLeft.Equals((object)pRight)); - Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); - return; - } - - Assert.True(pLeft != pRight); - Assert.False(pLeft == pRight); - Assert.False(pLeft.Equals(pRight)); - Assert.False(pLeft.Equals((object)pRight)); + Assert.True(pLeft == pRight); + Assert.False(pLeft != pRight); + Assert.True(pLeft.Equals(pRight)); + Assert.True(pLeft.Equals((object)pRight)); + Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); + return; } - [Fact] - public void EqualityTest_NotPointF() - { - var point = new PointF(0, 0); - Assert.False(point.Equals(null)); - Assert.False(point.Equals(0)); + Assert.True(pLeft != pRight); + Assert.False(pLeft == pRight); + Assert.False(pLeft.Equals(pRight)); + Assert.False(pLeft.Equals((object)pRight)); + } - // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly - // convertible to var can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType()); - Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); + [Fact] + public void EqualityTest_NotPointF() + { + var point = new PointF(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); - Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast - } + // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly + // convertible to var can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType()); + Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); - [Fact] - public void GetHashCodeTest() - { - var point = new PointF(10, 10); - Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); - } + Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast + } - [Fact] - public void ToStringTest() - { - var p = new PointF(5.1F, -5.123F); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); - } + [Fact] + public void GetHashCodeTest() + { + var point = new PointF(10, 10); + Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(float x, float y) - { - PointF p = new PointF(x, y); + [Fact] + public void ToStringTest() + { + var p = new PointF(5.1F, -5.123F); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); + } - (float deconstructedX, float deconstructedY) = p; + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(float x, float y) + { + PointF p = new PointF(x, y); - Assert.Equal(x, deconstructedX); - Assert.Equal(y, deconstructedY); - } + (float deconstructedX, float deconstructedY) = p; + + Assert.Equal(x, deconstructedX); + Assert.Equal(y, deconstructedY); } } diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs index 20722a0fe6..f5c64abf52 100644 --- a/tests/ImageSharp.Tests/Primitives/PointTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -1,258 +1,255 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Numerics; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class PointTests { - public class PointTests + [Fact] + public void DefaultConstructorTest() { - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Point.Empty); - } + Assert.Equal(default, Point.Empty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorTest(int x, int y) - { - var p1 = new Point(x, y); - var p2 = new Point(new Size(x, y)); + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(new Size(x, y)); - Assert.Equal(p1, p2); - } + Assert.Equal(p1, p2); + } - [Theory] - [InlineData(int.MaxValue)] - [InlineData(int.MinValue)] - [InlineData(0)] - public void SingleIntConstructorTest(int x) - { - var p1 = new Point(x); - var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0)] + public void SingleIntConstructorTest(int x) + { + var p1 = new Point(x); + var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); - Assert.Equal(p1, p2); - } + Assert.Equal(p1, p2); + } - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(Point.Empty.IsEmpty); - Assert.True(default(Point).IsEmpty); - Assert.True(new Point(0, 0).IsEmpty); - } + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Point.Empty.IsEmpty); + Assert.True(default(Point).IsEmpty); + Assert.True(new Point(0, 0).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - public void IsEmptyRandomTest(int x, int y) - { - Assert.False(new Point(x, y).IsEmpty); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int x, int y) + { + Assert.False(new Point(x, y).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void CoordinatesTest(int x, int y) - { - var p = new Point(x, y); - Assert.Equal(x, p.X); - Assert.Equal(y, p.Y); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(int x, int y) + { + var p = new Point(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void PointFConversionTest(int x, int y) - { - PointF p = new Point(x, y); - Assert.Equal(new PointF(x, y), p); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int x, int y) + { + PointF p = new Point(x, y); + Assert.Equal(new PointF(x, y), p); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void SizeConversionTest(int x, int y) - { - var sz = (Size)new Point(x, y); - Assert.Equal(new Size(x, y), sz); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int x, int y) + { + var sz = (Size)new Point(x, y); + Assert.Equal(new Size(x, y), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int x, int y) + { + Point addExpected, subExpected, p = new Point(x, y); + var s = new Size(y, x); - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(int x, int y) + unchecked { - Point addExpected, subExpected, p = new Point(x, y); - var s = new Size(y, x); - - unchecked - { - addExpected = new Point(x + y, y + x); - subExpected = new Point(x - y, y - x); - } - - Assert.Equal(addExpected, p + s); - Assert.Equal(subExpected, p - s); - Assert.Equal(addExpected, Point.Add(p, s)); - Assert.Equal(subExpected, Point.Subtract(p, s)); + addExpected = new Point(x + y, y + x); + subExpected = new Point(x - y, y - x); } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void PointFMathematicalTest(float x, float y) + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, Point.Add(p, s)); + Assert.Equal(subExpected, Point.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float x, float y) + { + var pf = new PointF(x, y); + Point pCeiling, pTruncate, pRound; + + unchecked { - var pf = new PointF(x, y); - Point pCeiling, pTruncate, pRound; - - unchecked - { - pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); - pTruncate = new Point((int)x, (int)y); - pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); - } - - Assert.Equal(pCeiling, Point.Ceiling(pf)); - Assert.Equal(pRound, Point.Round(pf)); - Assert.Equal(pTruncate, (Point)pf); + pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); + pTruncate = new Point((int)x, (int)y); + pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void OffsetTest(int x, int y) - { - var p1 = new Point(x, y); - var p2 = new Point(y, x); + Assert.Equal(pCeiling, Point.Ceiling(pf)); + Assert.Equal(pRound, Point.Round(pf)); + Assert.Equal(pTruncate, (Point)pf); + } - p1.Offset(p2); + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void OffsetTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(y, x); - Assert.Equal(unchecked(p2.X + p2.Y), p1.X); - Assert.Equal(p1.X, p1.Y); + p1.Offset(p2); - p2.Offset(x, y); - Assert.Equal(p1, p2); - } + Assert.Equal(unchecked(p2.X + p2.Y), p1.X); + Assert.Equal(p1.X, p1.Y); - [Fact] - public void RotateTest() - { - var p = new Point(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); + p2.Offset(x, y); + Assert.Equal(p1, p2); + } - var pout = Point.Transform(p, matrix); + [Fact] + public void RotateTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); - Assert.Equal(new Point(-3, 21), pout); - } + var pout = Point.Transform(p, matrix); - [Fact] - public void SkewTest() - { - var p = new Point(13, 17); - Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); + Assert.Equal(new Point(-3, 21), pout); + } - var pout = Point.Transform(p, matrix); - Assert.Equal(new Point(30, 30), pout); - } + [Fact] + public void SkewTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(int x, int y) - { - var p1 = new Point(x, y); - var p2 = new Point((x / 2) - 1, (y / 2) - 1); - var p3 = new Point(x, y); + var pout = Point.Transform(p, matrix); + Assert.Equal(new Point(30, 30), pout); + } - Assert.True(p1 == p3); - Assert.True(p1 != p2); - Assert.True(p2 != p3); + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point((x / 2) - 1, (y / 2) - 1); + var p3 = new Point(x, y); - Assert.True(p1.Equals(p3)); - Assert.False(p1.Equals(p2)); - Assert.False(p2.Equals(p3)); + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); - Assert.True(p1.Equals((object)p3)); - Assert.False(p1.Equals((object)p2)); - Assert.False(p2.Equals((object)p3)); + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); - Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); - } + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); - [Fact] - public void EqualityTest_NotPoint() - { - var point = new Point(0, 0); - Assert.False(point.Equals(null)); - Assert.False(point.Equals(0)); - Assert.False(point.Equals(new PointF(0, 0))); - } + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } - [Fact] - public void GetHashCodeTest() - { - var point = new Point(10, 10); - Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); - Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); - } + [Fact] + public void EqualityTest_NotPoint() + { + var point = new Point(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + Assert.False(point.Equals(new PointF(0, 0))); + } - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(1, -2, 3, -4)] - public void ConversionTest(int x, int y, int width, int height) - { - var rect = new Rectangle(x, y, width, height); - RectangleF rectF = rect; - Assert.Equal(x, rectF.X); - Assert.Equal(y, rectF.Y); - Assert.Equal(width, rectF.Width); - Assert.Equal(height, rectF.Height); - } + [Fact] + public void GetHashCodeTest() + { + var point = new Point(10, 10); + Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); + } - [Fact] - public void ToStringTest() - { - var p = new Point(5, -5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); - } + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(1, -2, 3, -4)] + public void ConversionTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + RectangleF rectF = rect; + Assert.Equal(x, rectF.X); + Assert.Equal(y, rectF.Y); + Assert.Equal(width, rectF.Width); + Assert.Equal(height, rectF.Height); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(int x, int y) - { - Point p = new Point(x, y); + [Fact] + public void ToStringTest() + { + var p = new Point(5, -5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(int x, int y) + { + Point p = new Point(x, y); - (int deconstructedX, int deconstructedY) = p; + (int deconstructedX, int deconstructedY) = p; - Assert.Equal(x, deconstructedX); - Assert.Equal(y, deconstructedY); - } + Assert.Equal(x, deconstructedX); + Assert.Equal(y, deconstructedY); } } diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs index 9e8d6e81f4..a9b3251ca3 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -1,285 +1,282 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the struct. +/// +public class RectangleFTests { - /// - /// Tests the struct. - /// - public class RectangleFTests + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(default, RectangleF.Empty); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void NonDefaultConstructorTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + var rect2 = new RectangleF(p, s); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void FromLTRBTest(float left, float top, float right, float bottom) + { + var expected = new RectangleF(left, top, right - left, bottom - top); + var actual = RectangleF.FromLTRB(left, top, right, bottom); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void DimensionsTest(float x, float y, float width, float height) { - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, RectangleF.Empty); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void NonDefaultConstructorTest(float x, float y, float width, float height) - { - var rect1 = new RectangleF(x, y, width, height); - var p = new PointF(x, y); - var s = new SizeF(width, height); - var rect2 = new RectangleF(p, s); - - Assert.Equal(rect1, rect2); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void FromLTRBTest(float left, float top, float right, float bottom) - { - var expected = new RectangleF(left, top, right - left, bottom - top); - var actual = RectangleF.FromLTRB(left, top, right, bottom); - - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void DimensionsTest(float x, float y, float width, float height) - { - var rect = new RectangleF(x, y, width, height); - var p = new PointF(x, y); - var s = new SizeF(width, height); - - Assert.Equal(p, rect.Location); - Assert.Equal(s, rect.Size); - Assert.Equal(x, rect.X); - Assert.Equal(y, rect.Y); - Assert.Equal(width, rect.Width); - Assert.Equal(height, rect.Height); - Assert.Equal(x, rect.Left); - Assert.Equal(y, rect.Top); - Assert.Equal(x + width, rect.Right); - Assert.Equal(y + height, rect.Bottom); - } - - [Fact] - public void IsEmptyTest() - { - Assert.True(RectangleF.Empty.IsEmpty); - Assert.True(default(RectangleF).IsEmpty); - Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); - Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); - Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); - - Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(float.MaxValue, float.MinValue)] - public void LocationSetTest(float x, float y) - { - var point = new PointF(x, y); - var rect = new RectangleF(10, 10, 10, 10) { Location = point }; - Assert.Equal(point, rect.Location); - Assert.Equal(point.X, rect.X); - Assert.Equal(point.Y, rect.Y); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(float.MaxValue, float.MinValue)] - public void SizeSetTest(float x, float y) - { - var size = new SizeF(x, y); - var rect = new RectangleF(10, 10, 10, 10) { Size = size }; - Assert.Equal(size, rect.Size); - Assert.Equal(size.Width, rect.Width); - Assert.Equal(size.Height, rect.Height); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void EqualityTest(float x, float y, float width, float height) - { - var rect1 = new RectangleF(x, y, width, height); - var rect2 = new RectangleF(width, height, x, y); - - Assert.True(rect1 != rect2); - Assert.False(rect1 == rect2); - Assert.False(rect1.Equals(rect2)); - Assert.False(rect1.Equals((object)rect2)); - } - - [Fact] - public void EqualityTestNotRectangleF() - { - var rectangle = new RectangleF(0, 0, 0, 0); - Assert.False(rectangle.Equals(null)); - Assert.False(rectangle.Equals(0)); - - // If RectangleF implements IEquatable (e.g. in .NET Core), then classes that are implicitly - // convertible to RectangleF can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToRectangleF = typeof(IEquatable).IsAssignableFrom(rectangle.GetType()); - Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); - - Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast - } - - [Fact] - public void GetHashCodeTest() - { - var rect1 = new RectangleF(10, 10, 10, 10); - var rect2 = new RectangleF(10, 10, 10, 10); - Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void ContainsTest(float x, float y, float width, float height) - { - var rect = new RectangleF(x, y, width, height); - float x1 = (x + width) / 2; - float y1 = (y + height) / 2; - var p = new PointF(x1, y1); - var r = new RectangleF(x1, y1, width / 2, height / 2); - - Assert.False(rect.Contains(x1, y1)); - Assert.False(rect.Contains(p)); - Assert.False(rect.Contains(r)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void InflateTest(float x, float y, float width, float height) - { - var rect = new RectangleF(x, y, width, height); - var inflatedRect = new RectangleF(x - width, y - height, width + (2 * width), height + (2 * height)); - - rect.Inflate(width, height); - Assert.Equal(inflatedRect, rect); - - var s = new SizeF(x, y); - inflatedRect = RectangleF.Inflate(rect, x, y); - - rect.Inflate(s); - Assert.Equal(inflatedRect, rect); - } - - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void IntersectTest(float x, float y, float width, float height) - { - var rect1 = new RectangleF(x, y, width, height); - var rect2 = new RectangleF(y, x, width, height); - var expectedRect = RectangleF.Intersect(rect1, rect2); - rect1.Intersect(rect2); - Assert.Equal(expectedRect, rect1); - Assert.False(rect1.IntersectsWith(expectedRect)); - } - - [Fact] - public void IntersectIntersectingRectsTest() - { - var rect1 = new RectangleF(0, 0, 5, 5); - var rect2 = new RectangleF(1, 1, 3, 3); - var expected = new RectangleF(1, 1, 3, 3); - - Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void UnionTest(float x, float y, float width, float height) - { - var a = new RectangleF(x, y, width, height); - var b = new RectangleF(width, height, x, y); - - float x1 = Math.Min(a.X, b.X); - float x2 = Math.Max(a.X + a.Width, b.X + b.Width); - float y1 = Math.Min(a.Y, b.Y); - float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); - - var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1); - - Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); - } - - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, 0, 0, float.MaxValue)] - [InlineData(0, float.MinValue, float.MaxValue, 0)] - public void OffsetTest(float x, float y, float width, float height) - { - var r1 = new RectangleF(x, y, width, height); - var expectedRect = new RectangleF(x + width, y + height, width, height); - var p = new PointF(width, height); - - r1.Offset(p); - Assert.Equal(expectedRect, r1); - - expectedRect.Offset(p); - r1.Offset(width, height); - Assert.Equal(expectedRect, r1); - } - - [Fact] - public void ToStringTest() - { - var r = new RectangleF(5, 5.1F, 1.3F, 1); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); - } - - [Theory] - [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MinValue)] - [InlineData(0, 0, 0, 0)] - public void DeconstructTest(float x, float y, float width, float height) - { - RectangleF r = new RectangleF(x, y, width, height); - - (float dx, float dy, float dw, float dh) = r; - - Assert.Equal(x, dx); - Assert.Equal(y, dy); - Assert.Equal(width, dw); - Assert.Equal(height, dh); - } + var rect = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(x + width, rect.Right); + Assert.Equal(y + height, rect.Bottom); + } + + [Fact] + public void IsEmptyTest() + { + Assert.True(RectangleF.Empty.IsEmpty); + Assert.True(default(RectangleF).IsEmpty); + Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); + Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); + Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); + + Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public void LocationSetTest(float x, float y) + { + var point = new PointF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public void SizeSetTest(float x, float y) + { + var size = new SizeF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void EqualityTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(width, height, x, y); + + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } + + [Fact] + public void EqualityTestNotRectangleF() + { + var rectangle = new RectangleF(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + + // If RectangleF implements IEquatable (e.g. in .NET Core), then classes that are implicitly + // convertible to RectangleF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToRectangleF = typeof(IEquatable).IsAssignableFrom(rectangle.GetType()); + Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); + + Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast + } + + [Fact] + public void GetHashCodeTest() + { + var rect1 = new RectangleF(10, 10, 10, 10); + var rect2 = new RectangleF(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void ContainsTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + float x1 = (x + width) / 2; + float y1 = (y + height) / 2; + var p = new PointF(x1, y1); + var r = new RectangleF(x1, y1, width / 2, height / 2); + + Assert.False(rect.Contains(x1, y1)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void InflateTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + var inflatedRect = new RectangleF(x - width, y - height, width + (2 * width), height + (2 * height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new SizeF(x, y); + inflatedRect = RectangleF.Inflate(rect, x, y); + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void IntersectTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(y, x, width, height); + var expectedRect = RectangleF.Intersect(rect1, rect2); + rect1.Intersect(rect2); + Assert.Equal(expectedRect, rect1); + Assert.False(rect1.IntersectsWith(expectedRect)); + } + + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new RectangleF(0, 0, 5, 5); + var rect2 = new RectangleF(1, 1, 3, 3); + var expected = new RectangleF(1, 1, 3, 3); + + Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void UnionTest(float x, float y, float width, float height) + { + var a = new RectangleF(x, y, width, height); + var b = new RectangleF(width, height, x, y); + + float x1 = Math.Min(a.X, b.X); + float x2 = Math.Max(a.X + a.Width, b.X + b.Width); + float y1 = Math.Min(a.Y, b.Y); + float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void OffsetTest(float x, float y, float width, float height) + { + var r1 = new RectangleF(x, y, width, height); + var expectedRect = new RectangleF(x + width, y + height, width, height); + var p = new PointF(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new RectangleF(5, 5.1F, 1.3F, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + + [Theory] + [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue, float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MaxValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MinValue)] + [InlineData(0, 0, 0, 0)] + public void DeconstructTest(float x, float y, float width, float height) + { + RectangleF r = new RectangleF(x, y, width, height); + + (float dx, float dy, float dw, float dh) = r; + + Assert.Equal(x, dx); + Assert.Equal(y, dy); + Assert.Equal(width, dw); + Assert.Equal(height, dh); } } diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs index c60d8969ff..2ba3a77315 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -1,336 +1,333 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the struct. +/// +public class RectangleTests { - /// - /// Tests the struct. - /// - public class RectangleTests + [Fact] + public void DefaultConstructorTest() { - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Rectangle.Empty); - } + Assert.Equal(default, Rectangle.Empty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void NonDefaultConstructorTest(int x, int y, int width, int height) - { - var rect1 = new Rectangle(x, y, width, height); - var rect2 = new Rectangle(new Point(x, y), new Size(width, height)); + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonDefaultConstructorTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(new Point(x, y), new Size(width, height)); - Assert.Equal(rect1, rect2); - } + Assert.Equal(rect1, rect2); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void FromLTRBTest(int left, int top, int right, int bottom) - { - var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); - var rect2 = Rectangle.FromLTRB(left, top, right, bottom); + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void FromLTRBTest(int left, int top, int right, int bottom) + { + var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + var rect2 = Rectangle.FromLTRB(left, top, right, bottom); - Assert.Equal(rect1, rect2); - } + Assert.Equal(rect1, rect2); + } - [Fact] - public void EmptyTest() - { - Assert.True(Rectangle.Empty.IsEmpty); - Assert.True(default(Rectangle).IsEmpty); - Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); - } + [Fact] + public void EmptyTest() + { + Assert.True(Rectangle.Empty.IsEmpty); + Assert.True(default(Rectangle).IsEmpty); + Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - public void NonEmptyTest(int x, int y, int width, int height) - { - Assert.False(new Rectangle(x, y, width, height).IsEmpty); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonEmptyTest(int x, int y, int width, int height) + { + Assert.False(new Rectangle(x, y, width, height).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, 0, 0, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - public void DimensionsTest(int x, int y, int width, int height) - { - var rect = new Rectangle(x, y, width, height); - Assert.Equal(new Point(x, y), rect.Location); - Assert.Equal(new Size(width, height), rect.Size); - - Assert.Equal(x, rect.X); - Assert.Equal(y, rect.Y); - Assert.Equal(width, rect.Width); - Assert.Equal(height, rect.Height); - Assert.Equal(x, rect.Left); - Assert.Equal(y, rect.Top); - Assert.Equal(unchecked(x + width), rect.Right); - Assert.Equal(unchecked(y + height), rect.Bottom); - - var p = new Point(width, height); - var s = new Size(x, y); - rect.Location = p; - rect.Size = s; - - Assert.Equal(p, rect.Location); - Assert.Equal(s, rect.Size); - - Assert.Equal(width, rect.X); - Assert.Equal(height, rect.Y); - Assert.Equal(x, rect.Width); - Assert.Equal(y, rect.Height); - Assert.Equal(width, rect.Left); - Assert.Equal(height, rect.Top); - Assert.Equal(unchecked(x + width), rect.Right); - Assert.Equal(unchecked(y + height), rect.Bottom); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void DimensionsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + Assert.Equal(new Point(x, y), rect.Location); + Assert.Equal(new Size(width, height), rect.Size); + + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + + var p = new Point(width, height); + var s = new Size(x, y); + rect.Location = p; + rect.Size = s; + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + + Assert.Equal(width, rect.X); + Assert.Equal(height, rect.Y); + Assert.Equal(x, rect.Width); + Assert.Equal(y, rect.Height); + Assert.Equal(width, rect.Left); + Assert.Equal(height, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + } - [Theory] - [InlineData(0, 0)] - [InlineData(int.MaxValue, int.MinValue)] - public void LocationSetTest(int x, int y) - { - var point = new Point(x, y); - var rect = new Rectangle(10, 10, 10, 10) { Location = point }; - Assert.Equal(point, rect.Location); - Assert.Equal(point.X, rect.X); - Assert.Equal(point.Y, rect.Y); - } + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public void LocationSetTest(int x, int y) + { + var point = new Point(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } - [Theory] - [InlineData(0, 0)] - [InlineData(int.MaxValue, int.MinValue)] - public void SizeSetTest(int x, int y) - { - var size = new Size(x, y); - var rect = new Rectangle(10, 10, 10, 10) { Size = size }; - Assert.Equal(size, rect.Size); - Assert.Equal(size.Width, rect.Width); - Assert.Equal(size.Height, rect.Height); - } + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public void SizeSetTest(int x, int y) + { + var size = new Size(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, 0, int.MinValue, 0)] - [InlineData(0, int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - public void EqualityTest(int x, int y, int width, int height) - { - var rect1 = new Rectangle(x, y, width, height); - var rect2 = new Rectangle(width / 2, height / 2, x, y); + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void EqualityTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(width / 2, height / 2, x, y); - Assert.True(rect1 != rect2); - Assert.False(rect1 == rect2); - Assert.False(rect1.Equals(rect2)); - Assert.False(rect1.Equals((object)rect2)); - } + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } - [Fact] - public void EqualityTestNotRectangle() - { - var rectangle = new Rectangle(0, 0, 0, 0); - Assert.False(rectangle.Equals(null)); - Assert.False(rectangle.Equals(0)); - Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); - } + [Fact] + public void EqualityTestNotRectangle() + { + var rectangle = new Rectangle(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); + } - [Fact] - public void GetHashCodeTest() - { - var rect1 = new Rectangle(10, 10, 10, 10); - var rect2 = new Rectangle(10, 10, 10, 10); - Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); - Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); - } + [Fact] + public void GetHashCodeTest() + { + var rect1 = new Rectangle(10, 10, 10, 10); + var rect2 = new Rectangle(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] - [InlineData(0, 0, 0, 0)] - public void RectangleFConversionTest(float x, float y, float width, float height) - { - var rect = new RectangleF(x, y, width, height); - Rectangle rCeiling, rTruncate, rRound; - - unchecked - { - rCeiling = new Rectangle( - (int)Math.Ceiling(x), - (int)Math.Ceiling(y), - (int)Math.Ceiling(width), - (int)Math.Ceiling(height)); - - rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); - - rRound = new Rectangle( - (int)Math.Round(x), - (int)Math.Round(y), - (int)Math.Round(width), - (int)Math.Round(height)); - } - - Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); - Assert.Equal(rTruncate, Rectangle.Truncate(rect)); - Assert.Equal(rRound, Rectangle.Round(rect)); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void RectangleFConversionTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + Rectangle rCeiling, rTruncate, rRound; - [Theory] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void ContainsTest(int x, int y, int width, int height) + unchecked { - var rect = new Rectangle(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); - var p = new Point(x, y); - var r = new Rectangle(x, y, width / 2, height / 2); - - Assert.False(rect.Contains(x, y)); - Assert.False(rect.Contains(p)); - Assert.False(rect.Contains(r)); + rCeiling = new Rectangle( + (int)Math.Ceiling(x), + (int)Math.Ceiling(y), + (int)Math.Ceiling(width), + (int)Math.Ceiling(height)); + + rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); + + rRound = new Rectangle( + (int)Math.Round(x), + (int)Math.Round(y), + (int)Math.Round(width), + (int)Math.Round(height)); } - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void InflateTest(int x, int y, int width, int height) - { - Rectangle inflatedRect, rect = new Rectangle(x, y, width, height); - unchecked - { - inflatedRect = new Rectangle(x - width, y - height, width + (2 * width), height + (2 * height)); - } - - Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); + Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); + Assert.Equal(rTruncate, Rectangle.Truncate(rect)); + Assert.Equal(rRound, Rectangle.Round(rect)); + } - rect.Inflate(width, height); - Assert.Equal(inflatedRect, rect); + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void ContainsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); + var p = new Point(x, y); + var r = new Rectangle(x, y, width / 2, height / 2); - var s = new Size(x, y); - unchecked - { - inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); - } + Assert.False(rect.Contains(x, y)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } - rect.Inflate(s); - Assert.Equal(inflatedRect, rect); + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void InflateTest(int x, int y, int width, int height) + { + Rectangle inflatedRect, rect = new Rectangle(x, y, width, height); + unchecked + { + inflatedRect = new Rectangle(x - width, y - height, width + (2 * width), height + (2 * height)); } - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void IntersectTest(int x, int y, int width, int height) + Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new Size(x, y); + unchecked { - var rect = new Rectangle(x, y, width, height); - var expectedRect = Rectangle.Intersect(rect, rect); - rect.Intersect(rect); - Assert.Equal(expectedRect, rect); - Assert.False(rect.IntersectsWith(expectedRect)); + inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); } - [Fact] - public void IntersectIntersectingRectsTest() - { - var rect1 = new Rectangle(0, 0, 5, 5); - var rect2 = new Rectangle(1, 1, 3, 3); - var expected = new Rectangle(1, 1, 3, 3); + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } - Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); - } + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void IntersectTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + var expectedRect = Rectangle.Intersect(rect, rect); + rect.Intersect(rect); + Assert.Equal(expectedRect, rect); + Assert.False(rect.IntersectsWith(expectedRect)); + } - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, 0, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void UnionTest(int x, int y, int width, int height) - { - var a = new Rectangle(x, y, width, height); - var b = new Rectangle(width, height, x, y); + [Fact] + public void IntersectIntersectingRectsTest() + { + var rect1 = new Rectangle(0, 0, 5, 5); + var rect2 = new Rectangle(1, 1, 3, 3); + var expected = new Rectangle(1, 1, 3, 3); - int x1 = Math.Min(a.X, b.X); - int x2 = Math.Max(a.X + a.Width, b.X + b.Width); - int y1 = Math.Min(a.Y, b.Y); - int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); + } - var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void UnionTest(int x, int y, int width, int height) + { + var a = new Rectangle(x, y, width, height); + var b = new Rectangle(width, height, x, y); - Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); - } + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.X + a.Width, b.X + b.Width); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); - [Theory] - [InlineData(0, 0, 0, 0)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, 0, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue, 0)] - public void OffsetTest(int x, int y, int width, int height) - { - var r1 = new Rectangle(x, y, width, height); - var expectedRect = new Rectangle(x + width, y + height, width, height); - var p = new Point(width, height); + var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); - r1.Offset(p); - Assert.Equal(expectedRect, r1); + Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); + } - expectedRect.Offset(p); - r1.Offset(width, height); - Assert.Equal(expectedRect, r1); - } + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void OffsetTest(int x, int y, int width, int height) + { + var r1 = new Rectangle(x, y, width, height); + var expectedRect = new Rectangle(x + width, y + height, width, height); + var p = new Point(width, height); - [Fact] - public void ToStringTest() - { - var r = new Rectangle(5, -5, 0, 1); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); - } + r1.Offset(p); + Assert.Equal(expectedRect, r1); - [Theory] - [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MinValue)] - [InlineData(0, 0, 0, 0)] - public void DeconstructTest(int x, int y, int width, int height) - { - var r = new Rectangle(x, y, width, height); + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new Rectangle(5, -5, 0, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } - (int dx, int dy, int dw, int dh) = r; + [Theory] + [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue, int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MinValue)] + [InlineData(0, 0, 0, 0)] + public void DeconstructTest(int x, int y, int width, int height) + { + var r = new Rectangle(x, y, width, height); - Assert.Equal(x, dx); - Assert.Equal(y, dy); - Assert.Equal(width, dw); - Assert.Equal(height, dh); - } + (int dx, int dy, int dw, int dh) = r; + + Assert.Equal(x, dx); + Assert.Equal(y, dy); + Assert.Equal(width, dw); + Assert.Equal(height, dh); } } diff --git a/tests/ImageSharp.Tests/Primitives/SizeFTests.cs b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs index ef09f762ed..1a36b4921a 100644 --- a/tests/ImageSharp.Tests/Primitives/SizeFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/SizeFTests.cs @@ -1,249 +1,246 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class SizeFTests { - public class SizeFTests + [Fact] + public void DefaultConstructorTest() { - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, SizeF.Empty); - } + Assert.Equal(default, SizeF.Empty); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorAndDimensionsTest(float width, float height) - { - var s1 = new SizeF(width, height); - var p1 = new PointF(width, height); - var s2 = new SizeF(s1); + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorAndDimensionsTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = new PointF(width, height); + var s2 = new SizeF(s1); - Assert.Equal(s1, s2); - Assert.Equal(s1, new SizeF(p1)); - Assert.Equal(s2, new SizeF(p1)); + Assert.Equal(s1, s2); + Assert.Equal(s1, new SizeF(p1)); + Assert.Equal(s2, new SizeF(p1)); - Assert.Equal(width, s1.Width); - Assert.Equal(height, s1.Height); + Assert.Equal(width, s1.Width); + Assert.Equal(height, s1.Height); - s1.Width = 10; - Assert.Equal(10, s1.Width); + s1.Width = 10; + Assert.Equal(10, s1.Width); - s1.Height = -10.123f; - Assert.Equal(-10.123, s1.Height, 3); - } + s1.Height = -10.123f; + Assert.Equal(-10.123, s1.Height, 3); + } - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(SizeF.Empty.IsEmpty); - Assert.True(default(SizeF).IsEmpty); - Assert.True(new SizeF(0, 0).IsEmpty); - } + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(SizeF.Empty.IsEmpty); + Assert.True(default(SizeF).IsEmpty); + Assert.True(new SizeF(0, 0).IsEmpty); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - public void IsEmptyRandomTest(float width, float height) - { - Assert.False(new SizeF(width, height).IsEmpty); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float width, float height) + { + Assert.False(new SizeF(width, height).IsEmpty); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(float width, float height) - { - var s1 = new SizeF(width, height); - var s2 = new SizeF(height, width); - var addExpected = new SizeF(width + height, width + height); - var subExpected = new SizeF(width - height, height - width); + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(float width, float height) + { + var s1 = new SizeF(width, height); + var s2 = new SizeF(height, width); + var addExpected = new SizeF(width + height, width + height); + var subExpected = new SizeF(width - height, height - width); - Assert.Equal(addExpected, s1 + s2); - Assert.Equal(addExpected, SizeF.Add(s1, s2)); + Assert.Equal(addExpected, s1 + s2); + Assert.Equal(addExpected, SizeF.Add(s1, s2)); - Assert.Equal(subExpected, s1 - s2); - Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); - } + Assert.Equal(subExpected, s1 - s2); + Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float width, float height) + { + var sLeft = new SizeF(width, height); + var sRight = new SizeF(height, width); - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(float width, float height) + if (width == height) { - var sLeft = new SizeF(width, height); - var sRight = new SizeF(height, width); - - if (width == height) - { - Assert.True(sLeft == sRight); - Assert.False(sLeft != sRight); - Assert.True(sLeft.Equals(sRight)); - Assert.True(sLeft.Equals((object)sRight)); - Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); - return; - } - - Assert.True(sLeft != sRight); - Assert.False(sLeft == sRight); - Assert.False(sLeft.Equals(sRight)); - Assert.False(sLeft.Equals((object)sRight)); + Assert.True(sLeft == sRight); + Assert.False(sLeft != sRight); + Assert.True(sLeft.Equals(sRight)); + Assert.True(sLeft.Equals((object)sRight)); + Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); + return; } - [Fact] - public void EqualityTest_NotSizeF() - { - var size = new SizeF(0, 0); - Assert.False(size.Equals(null)); - Assert.False(size.Equals(0)); + Assert.True(sLeft != sRight); + Assert.False(sLeft == sRight); + Assert.False(sLeft.Equals(sRight)); + Assert.False(sLeft.Equals((object)sRight)); + } - // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly - // convertible to SizeF can potentially be equal. - // See https://github.com/dotnet/corefx/issues/5255. - bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType()); - Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); + [Fact] + public void EqualityTest_NotSizeF() + { + var size = new SizeF(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); - Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast - } + // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly + // convertible to SizeF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType()); + Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); - [Fact] - public void GetHashCodeTest() - { - var size = new SizeF(10, 10); - Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); - } + Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void ConversionTest(float width, float height) - { - var s1 = new SizeF(width, height); - var p1 = (PointF)s1; - var s2 = new Size(unchecked((int)width), unchecked((int)height)); + [Fact] + public void GetHashCodeTest() + { + var size = new SizeF(10, 10); + Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); + } - Assert.Equal(new PointF(width, height), p1); - Assert.Equal(p1, (PointF)s1); - Assert.Equal(s2, (Size)s1); - } + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ConversionTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = (PointF)s1; + var s2 = new Size(unchecked((int)width), unchecked((int)height)); - [Fact] - public void ToStringTest() - { - var sz = new SizeF(10, 5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); - } + Assert.Equal(new PointF(width, height), p1); + Assert.Equal(p1, (PointF)s1); + Assert.Equal(s2, (Size)s1); + } - [Theory] - [InlineData(1000.234f, 0.0f)] - [InlineData(1000.234f, 1.0f)] - [InlineData(1000.234f, 2400.933f)] - [InlineData(1000.234f, float.MaxValue)] - [InlineData(1000.234f, -1.0f)] - [InlineData(1000.234f, -2400.933f)] - [InlineData(1000.234f, float.MinValue)] - [InlineData(float.MaxValue, 0.0f)] - [InlineData(float.MaxValue, 1.0f)] - [InlineData(float.MaxValue, 2400.933f)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, -1.0f)] - [InlineData(float.MaxValue, -2400.933f)] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, 0.0f)] - [InlineData(float.MinValue, 1.0f)] - [InlineData(float.MinValue, 2400.933f)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, -1.0f)] - [InlineData(float.MinValue, -2400.933f)] - [InlineData(float.MinValue, float.MinValue)] - public void MultiplicationTest(float dimension, float multiplier) - { - SizeF sz1 = new SizeF(dimension, dimension); - SizeF mulExpected; + [Fact] + public void ToStringTest() + { + var sz = new SizeF(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); + } - mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); + [Theory] + [InlineData(1000.234f, 0.0f)] + [InlineData(1000.234f, 1.0f)] + [InlineData(1000.234f, 2400.933f)] + [InlineData(1000.234f, float.MaxValue)] + [InlineData(1000.234f, -1.0f)] + [InlineData(1000.234f, -2400.933f)] + [InlineData(1000.234f, float.MinValue)] + [InlineData(float.MaxValue, 0.0f)] + [InlineData(float.MaxValue, 1.0f)] + [InlineData(float.MaxValue, 2400.933f)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, -1.0f)] + [InlineData(float.MaxValue, -2400.933f)] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, 0.0f)] + [InlineData(float.MinValue, 1.0f)] + [InlineData(float.MinValue, 2400.933f)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, -1.0f)] + [InlineData(float.MinValue, -2400.933f)] + [InlineData(float.MinValue, float.MinValue)] + public void MultiplicationTest(float dimension, float multiplier) + { + SizeF sz1 = new SizeF(dimension, dimension); + SizeF mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } + mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); - [Theory] - [InlineData(1111.1111f, 2222.2222f, 3333.3333f)] - public void MultiplicationTestWidthHeightMultiplier(float width, float height, float multiplier) - { - SizeF sz1 = new SizeF(width, height); - SizeF mulExpected; + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - mulExpected = new SizeF(width * multiplier, height * multiplier); + [Theory] + [InlineData(1111.1111f, 2222.2222f, 3333.3333f)] + public void MultiplicationTestWidthHeightMultiplier(float width, float height, float multiplier) + { + SizeF sz1 = new SizeF(width, height); + SizeF mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } + mulExpected = new SizeF(width * multiplier, height * multiplier); - [Theory] - [InlineData(0.0f, 1.0f)] - [InlineData(1.0f, 1.0f)] - [InlineData(-1.0f, 1.0f)] - [InlineData(1.0f, -1.0f)] - [InlineData(-1.0f, -1.0f)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MaxValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, 1.0f)] - [InlineData(float.MinValue, 1.0f)] - [InlineData(float.MaxValue, -1.0f)] - [InlineData(float.MinValue, -1.0f)] - [InlineData(float.MinValue, 0.0f)] - [InlineData(1.0f, float.MinValue)] - [InlineData(1.0f, float.MaxValue)] - [InlineData(-1.0f, float.MinValue)] - [InlineData(-1.0f, float.MaxValue)] - public void DivideTestSizeFloat(float dimension, float divisor) - { - SizeF size = new SizeF(dimension, dimension); - SizeF expected = new SizeF(dimension / divisor, dimension / divisor); - Assert.Equal(expected, size / divisor); - } + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - [Theory] - [InlineData(-111.111f, 222.222f, 333.333f)] - public void DivideTestSizeFloatWidthHeightDivisor(float width, float height, float divisor) - { - SizeF size = new SizeF(width, height); - SizeF expected = new SizeF(width / divisor, height / divisor); - Assert.Equal(expected, size / divisor); - } + [Theory] + [InlineData(0.0f, 1.0f)] + [InlineData(1.0f, 1.0f)] + [InlineData(-1.0f, 1.0f)] + [InlineData(1.0f, -1.0f)] + [InlineData(-1.0f, -1.0f)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, 1.0f)] + [InlineData(float.MinValue, 1.0f)] + [InlineData(float.MaxValue, -1.0f)] + [InlineData(float.MinValue, -1.0f)] + [InlineData(float.MinValue, 0.0f)] + [InlineData(1.0f, float.MinValue)] + [InlineData(1.0f, float.MaxValue)] + [InlineData(-1.0f, float.MinValue)] + [InlineData(-1.0f, float.MaxValue)] + public void DivideTestSizeFloat(float dimension, float divisor) + { + SizeF size = new SizeF(dimension, dimension); + SizeF expected = new SizeF(dimension / divisor, dimension / divisor); + Assert.Equal(expected, size / divisor); + } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(float width, float height) - { - SizeF s = new SizeF(width, height); + [Theory] + [InlineData(-111.111f, 222.222f, 333.333f)] + public void DivideTestSizeFloatWidthHeightDivisor(float width, float height, float divisor) + { + SizeF size = new SizeF(width, height); + SizeF expected = new SizeF(width / divisor, height / divisor); + Assert.Equal(expected, size / divisor); + } - (float deconstructedWidth, float deconstructedHeight) = s; + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(float width, float height) + { + SizeF s = new SizeF(width, height); - Assert.Equal(width, deconstructedWidth); - Assert.Equal(height, deconstructedHeight); - } + (float deconstructedWidth, float deconstructedHeight) = s; + + Assert.Equal(width, deconstructedWidth); + Assert.Equal(height, deconstructedHeight); } } diff --git a/tests/ImageSharp.Tests/Primitives/SizeTests.cs b/tests/ImageSharp.Tests/Primitives/SizeTests.cs index d400c0bcd1..eec1fb63b0 100644 --- a/tests/ImageSharp.Tests/Primitives/SizeTests.cs +++ b/tests/ImageSharp.Tests/Primitives/SizeTests.cs @@ -1,379 +1,376 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Tests the struct. +/// +public class SizeTests { - /// - /// Tests the struct. - /// - public class SizeTests + [Fact] + public void DefaultConstructorTest() { - [Fact] - public void DefaultConstructorTest() - { - Assert.Equal(default, Size.Empty); - } + Assert.Equal(default, Size.Empty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void NonDefaultConstructorTest(int width, int height) - { - var s1 = new Size(width, height); - var s2 = new Size(new Point(width, height)); + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int width, int height) + { + var s1 = new Size(width, height); + var s2 = new Size(new Point(width, height)); - Assert.Equal(s1, s2); + Assert.Equal(s1, s2); - s1.Width = 10; - Assert.Equal(10, s1.Width); + s1.Width = 10; + Assert.Equal(10, s1.Width); - s1.Height = -10; - Assert.Equal(-10, s1.Height); - } + s1.Height = -10; + Assert.Equal(-10, s1.Height); + } - [Fact] - public void IsEmptyDefaultsTest() - { - Assert.True(Size.Empty.IsEmpty); - Assert.True(default(Size).IsEmpty); - Assert.True(new Size(0, 0).IsEmpty); - } + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Size.Empty.IsEmpty); + Assert.True(default(Size).IsEmpty); + Assert.True(new Size(0, 0).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - public void IsEmptyRandomTest(int width, int height) - { - Assert.False(new Size(width, height).IsEmpty); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int width, int height) + { + Assert.False(new Size(width, height).IsEmpty); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DimensionsTest(int width, int height) - { - var p = new Size(width, height); - Assert.Equal(width, p.Width); - Assert.Equal(height, p.Height); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DimensionsTest(int width, int height) + { + var p = new Size(width, height); + Assert.Equal(width, p.Width); + Assert.Equal(height, p.Height); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void PointFConversionTest(int width, int height) - { - SizeF sz = new Size(width, height); - Assert.Equal(new SizeF(width, height), sz); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int width, int height) + { + SizeF sz = new Size(width, height); + Assert.Equal(new SizeF(width, height), sz); + } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void SizeConversionTest(int width, int height) - { - var sz = (Point)new Size(width, height); - Assert.Equal(new Point(width, height), sz); - } + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int width, int height) + { + var sz = (Point)new Size(width, height); + Assert.Equal(new Point(width, height), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int width, int height) + { + var sz1 = new Size(width, height); + var sz2 = new Size(height, width); + Size addExpected, subExpected; - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void ArithmeticTest(int width, int height) + unchecked { - var sz1 = new Size(width, height); - var sz2 = new Size(height, width); - Size addExpected, subExpected; - - unchecked - { - addExpected = new Size(width + height, height + width); - subExpected = new Size(width - height, height - width); - } - - Assert.Equal(addExpected, sz1 + sz2); - Assert.Equal(subExpected, sz1 - sz2); - Assert.Equal(addExpected, Size.Add(sz1, sz2)); - Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); + addExpected = new Size(width + height, height + width); + subExpected = new Size(width - height, height - width); } - [Theory] - [InlineData(float.MaxValue, float.MinValue)] - [InlineData(float.MinValue, float.MinValue)] - [InlineData(float.MaxValue, float.MaxValue)] - [InlineData(0, 0)] - public void PointFMathematicalTest(float width, float height) + Assert.Equal(addExpected, sz1 + sz2); + Assert.Equal(subExpected, sz1 - sz2); + Assert.Equal(addExpected, Size.Add(sz1, sz2)); + Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float width, float height) + { + var szF = new SizeF(width, height); + Size pCeiling, pTruncate, pRound; + + unchecked { - var szF = new SizeF(width, height); - Size pCeiling, pTruncate, pRound; - - unchecked - { - pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); - pTruncate = new Size((int)width, (int)height); - pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); - } - - Assert.Equal(pCeiling, Size.Ceiling(szF)); - Assert.Equal(pRound, Size.Round(szF)); - Assert.Equal(pTruncate, (Size)szF); + pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); + pTruncate = new Size((int)width, (int)height); + pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); } - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void EqualityTest(int width, int height) - { - var p1 = new Size(width, height); - var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); - var p3 = new Size(width, height); + Assert.Equal(pCeiling, Size.Ceiling(szF)); + Assert.Equal(pRound, Size.Round(szF)); + Assert.Equal(pTruncate, (Size)szF); + } - Assert.True(p1 == p3); - Assert.True(p1 != p2); - Assert.True(p2 != p3); + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int width, int height) + { + var p1 = new Size(width, height); + var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); + var p3 = new Size(width, height); - Assert.True(p1.Equals(p3)); - Assert.False(p1.Equals(p2)); - Assert.False(p2.Equals(p3)); + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); - Assert.True(p1.Equals((object)p3)); - Assert.False(p1.Equals((object)p2)); - Assert.False(p2.Equals((object)p3)); + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); - Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); - } + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); - [Fact] - public void EqualityTest_NotSize() - { - var size = new Size(0, 0); - Assert.False(size.Equals(null)); - Assert.False(size.Equals(0)); - Assert.False(size.Equals(new SizeF(0, 0))); - } + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } - [Fact] - public void GetHashCodeTest() - { - var size = new Size(10, 10); - Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); - Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); - } + [Fact] + public void EqualityTest_NotSize() + { + var size = new Size(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + Assert.False(size.Equals(new SizeF(0, 0))); + } - [Fact] - public void ToStringTest() - { - var sz = new Size(10, 5); - Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); - } + [Fact] + public void GetHashCodeTest() + { + var size = new Size(10, 10); + Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); + } - [Theory] - [InlineData(1000, 0)] - [InlineData(1000, 1)] - [InlineData(1000, 2400)] - [InlineData(1000, int.MaxValue)] - [InlineData(1000, -1)] - [InlineData(1000, -2400)] - [InlineData(1000, int.MinValue)] - [InlineData(int.MaxValue, 0)] - [InlineData(int.MaxValue, 1)] - [InlineData(int.MaxValue, 2400)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, -1)] - [InlineData(int.MaxValue, -2400)] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, 0)] - [InlineData(int.MinValue, 1)] - [InlineData(int.MinValue, 2400)] - [InlineData(int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, -1)] - [InlineData(int.MinValue, -2400)] - [InlineData(int.MinValue, int.MinValue)] - public void MultiplicationTestSizeInt(int dimension, int multiplier) - { - Size sz1 = new Size(dimension, dimension); - Size mulExpected; + [Fact] + public void ToStringTest() + { + var sz = new Size(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); + } - unchecked - { - mulExpected = new Size(dimension * multiplier, dimension * multiplier); - } + [Theory] + [InlineData(1000, 0)] + [InlineData(1000, 1)] + [InlineData(1000, 2400)] + [InlineData(1000, int.MaxValue)] + [InlineData(1000, -1)] + [InlineData(1000, -2400)] + [InlineData(1000, int.MinValue)] + [InlineData(int.MaxValue, 0)] + [InlineData(int.MaxValue, 1)] + [InlineData(int.MaxValue, 2400)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, -1)] + [InlineData(int.MaxValue, -2400)] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, 0)] + [InlineData(int.MinValue, 1)] + [InlineData(int.MinValue, 2400)] + [InlineData(int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, -1)] + [InlineData(int.MinValue, -2400)] + [InlineData(int.MinValue, int.MinValue)] + public void MultiplicationTestSizeInt(int dimension, int multiplier) + { + Size sz1 = new Size(dimension, dimension); + Size mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); + unchecked + { + mulExpected = new Size(dimension * multiplier, dimension * multiplier); } - [Theory] - [InlineData(1000, 2000, 3000)] - public void MultiplicationTestSizeIntWidthHeightMultiplier(int width, int height, int multiplier) - { - Size sz1 = new Size(width, height); - Size mulExpected; + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - unchecked - { - mulExpected = new Size(width * multiplier, height * multiplier); - } + [Theory] + [InlineData(1000, 2000, 3000)] + public void MultiplicationTestSizeIntWidthHeightMultiplier(int width, int height, int multiplier) + { + Size sz1 = new Size(width, height); + Size mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); + unchecked + { + mulExpected = new Size(width * multiplier, height * multiplier); } - [Theory] - [InlineData(1000, 0.0f)] - [InlineData(1000, 1.0f)] - [InlineData(1000, 2400.933f)] - [InlineData(1000, float.MaxValue)] - [InlineData(1000, -1.0f)] - [InlineData(1000, -2400.933f)] - [InlineData(1000, float.MinValue)] - [InlineData(int.MaxValue, 0.0f)] - [InlineData(int.MaxValue, 1.0f)] - [InlineData(int.MaxValue, 2400.933f)] - [InlineData(int.MaxValue, float.MaxValue)] - [InlineData(int.MaxValue, -1.0f)] - [InlineData(int.MaxValue, -2400.933f)] - [InlineData(int.MaxValue, float.MinValue)] - [InlineData(int.MinValue, 0.0f)] - [InlineData(int.MinValue, 1.0f)] - [InlineData(int.MinValue, 2400.933f)] - [InlineData(int.MinValue, float.MaxValue)] - [InlineData(int.MinValue, -1.0f)] - [InlineData(int.MinValue, -2400.933f)] - [InlineData(int.MinValue, float.MinValue)] - public void MultiplicationTestSizeFloat(int dimension, float multiplier) - { - Size sz1 = new Size(dimension, dimension); - SizeF mulExpected; + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); + [Theory] + [InlineData(1000, 0.0f)] + [InlineData(1000, 1.0f)] + [InlineData(1000, 2400.933f)] + [InlineData(1000, float.MaxValue)] + [InlineData(1000, -1.0f)] + [InlineData(1000, -2400.933f)] + [InlineData(1000, float.MinValue)] + [InlineData(int.MaxValue, 0.0f)] + [InlineData(int.MaxValue, 1.0f)] + [InlineData(int.MaxValue, 2400.933f)] + [InlineData(int.MaxValue, float.MaxValue)] + [InlineData(int.MaxValue, -1.0f)] + [InlineData(int.MaxValue, -2400.933f)] + [InlineData(int.MaxValue, float.MinValue)] + [InlineData(int.MinValue, 0.0f)] + [InlineData(int.MinValue, 1.0f)] + [InlineData(int.MinValue, 2400.933f)] + [InlineData(int.MinValue, float.MaxValue)] + [InlineData(int.MinValue, -1.0f)] + [InlineData(int.MinValue, -2400.933f)] + [InlineData(int.MinValue, float.MinValue)] + public void MultiplicationTestSizeFloat(int dimension, float multiplier) + { + Size sz1 = new Size(dimension, dimension); + SizeF mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } + mulExpected = new SizeF(dimension * multiplier, dimension * multiplier); - [Theory] - [InlineData(1000, 2000, 30.33f)] - public void MultiplicationTestSizeFloatWidthHeightMultiplier(int width, int height, float multiplier) - { - Size sz1 = new Size(width, height); - SizeF mulExpected; + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - mulExpected = new SizeF(width * multiplier, height * multiplier); + [Theory] + [InlineData(1000, 2000, 30.33f)] + public void MultiplicationTestSizeFloatWidthHeightMultiplier(int width, int height, float multiplier) + { + Size sz1 = new Size(width, height); + SizeF mulExpected; - Assert.Equal(mulExpected, sz1 * multiplier); - Assert.Equal(mulExpected, multiplier * sz1); - } + mulExpected = new SizeF(width * multiplier, height * multiplier); - [Fact] - public void DivideByZeroChecks() - { - Size size = new Size(100, 100); - Assert.Throws(() => size / 0); + Assert.Equal(mulExpected, sz1 * multiplier); + Assert.Equal(mulExpected, multiplier * sz1); + } - SizeF expectedSizeF = new SizeF(float.PositiveInfinity, float.PositiveInfinity); - Assert.Equal(expectedSizeF, size / 0.0f); - } + [Fact] + public void DivideByZeroChecks() + { + Size size = new Size(100, 100); + Assert.Throws(() => size / 0); - [Theory] - [InlineData(0, 1)] - [InlineData(1, 1)] - [InlineData(-1, 1)] - [InlineData(1, -1)] - [InlineData(-1, -1)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, 1)] - [InlineData(int.MinValue, 1)] - [InlineData(int.MaxValue, -1)] - public void DivideTestSizeInt(int dimension, int divisor) - { - Size size = new Size(dimension, dimension); - Size expected; + SizeF expectedSizeF = new SizeF(float.PositiveInfinity, float.PositiveInfinity); + Assert.Equal(expectedSizeF, size / 0.0f); + } - expected = new Size(dimension / divisor, dimension / divisor); + [Theory] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(-1, 1)] + [InlineData(1, -1)] + [InlineData(-1, -1)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, 1)] + [InlineData(int.MinValue, 1)] + [InlineData(int.MaxValue, -1)] + public void DivideTestSizeInt(int dimension, int divisor) + { + Size size = new Size(dimension, dimension); + Size expected; - Assert.Equal(expected, size / divisor); - } + expected = new Size(dimension / divisor, dimension / divisor); - [Theory] - [InlineData(1111, 2222, 3333)] - public void DivideTestSizeIntWidthHeightDivisor(int width, int height, int divisor) - { - Size size = new Size(width, height); - Size expected; + Assert.Equal(expected, size / divisor); + } - expected = new Size(width / divisor, height / divisor); + [Theory] + [InlineData(1111, 2222, 3333)] + public void DivideTestSizeIntWidthHeightDivisor(int width, int height, int divisor) + { + Size size = new Size(width, height); + Size expected; - Assert.Equal(expected, size / divisor); - } + expected = new Size(width / divisor, height / divisor); - [Theory] - [InlineData(0, 1.0f)] - [InlineData(1, 1.0f)] - [InlineData(-1, 1.0f)] - [InlineData(1, -1.0f)] - [InlineData(-1, -1.0f)] - [InlineData(int.MaxValue, float.MaxValue)] - [InlineData(int.MaxValue, float.MinValue)] - [InlineData(int.MinValue, float.MaxValue)] - [InlineData(int.MinValue, float.MinValue)] - [InlineData(int.MaxValue, 1.0f)] - [InlineData(int.MinValue, 1.0f)] - [InlineData(int.MaxValue, -1.0f)] - [InlineData(int.MinValue, -1.0f)] - public void DivideTestSizeFloat(int dimension, float divisor) - { - SizeF size = new SizeF(dimension, dimension); - SizeF expected; + Assert.Equal(expected, size / divisor); + } - expected = new SizeF(dimension / divisor, dimension / divisor); - Assert.Equal(expected, size / divisor); - } + [Theory] + [InlineData(0, 1.0f)] + [InlineData(1, 1.0f)] + [InlineData(-1, 1.0f)] + [InlineData(1, -1.0f)] + [InlineData(-1, -1.0f)] + [InlineData(int.MaxValue, float.MaxValue)] + [InlineData(int.MaxValue, float.MinValue)] + [InlineData(int.MinValue, float.MaxValue)] + [InlineData(int.MinValue, float.MinValue)] + [InlineData(int.MaxValue, 1.0f)] + [InlineData(int.MinValue, 1.0f)] + [InlineData(int.MaxValue, -1.0f)] + [InlineData(int.MinValue, -1.0f)] + public void DivideTestSizeFloat(int dimension, float divisor) + { + SizeF size = new SizeF(dimension, dimension); + SizeF expected; - [Theory] - [InlineData(1111, 2222, -333.33f)] - public void DivideTestSizeFloatWidthHeightDivisor(int width, int height, float divisor) - { - SizeF size = new SizeF(width, height); - SizeF expected; + expected = new SizeF(dimension / divisor, dimension / divisor); + Assert.Equal(expected, size / divisor); + } - expected = new SizeF(width / divisor, height / divisor); - Assert.Equal(expected, size / divisor); - } + [Theory] + [InlineData(1111, 2222, -333.33f)] + public void DivideTestSizeFloatWidthHeightDivisor(int width, int height, float divisor) + { + SizeF size = new SizeF(width, height); + SizeF expected; - [Theory] - [InlineData(int.MaxValue, int.MinValue)] - [InlineData(int.MinValue, int.MinValue)] - [InlineData(int.MaxValue, int.MaxValue)] - [InlineData(0, 0)] - public void DeconstructTest(int width, int height) - { - Size s = new Size(width, height); + expected = new SizeF(width / divisor, height / divisor); + Assert.Equal(expected, size / divisor); + } - (int deconstructedWidth, int deconstructedHeight) = s; + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DeconstructTest(int width, int height) + { + Size s = new Size(width, height); - Assert.Equal(width, deconstructedWidth); - Assert.Equal(height, deconstructedHeight); - } + (int deconstructedWidth, int deconstructedHeight) = s; + + Assert.Equal(width, deconstructedWidth); + Assert.Equal(height, deconstructedHeight); } } diff --git a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs index 7a26560b23..9070b81583 100644 --- a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs @@ -1,65 +1,61 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing; -namespace SixLabors.ImageSharp.Tests.Processing +public abstract class BaseImageOperationsExtensionTest : IDisposable { - public abstract class BaseImageOperationsExtensionTest : IDisposable - { - protected readonly IImageProcessingContext operations; - private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; - protected readonly Rectangle rect; - protected readonly GraphicsOptions options; - private readonly Image source; - - public Rectangle SourceBounds() => this.source.Bounds(); + protected readonly IImageProcessingContext operations; + private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; + protected readonly Rectangle rect; + protected readonly GraphicsOptions options; + private readonly Image source; - public BaseImageOperationsExtensionTest() - { - this.options = new GraphicsOptions { Antialias = false }; - this.source = new Image(91 + 324, 123 + 56); - this.rect = new Rectangle(91, 123, 324, 56); // make this random? - this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source.GetConfiguration(), this.source, false); - this.internalOperations.SetGraphicsOptions(this.options); - this.operations = this.internalOperations; - } + public Rectangle SourceBounds() => this.source.Bounds(); - public T Verify(int index = 0) - { - Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); + public BaseImageOperationsExtensionTest() + { + this.options = new GraphicsOptions { Antialias = false }; + this.source = new Image(91 + 324, 123 + 56); + this.rect = new Rectangle(91, 123, 324, 56); // make this random? + this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source.GetConfiguration(), this.source, false); + this.internalOperations.SetGraphicsOptions(this.options); + this.operations = this.internalOperations; + } - FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; + public T Verify(int index = 0) + { + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - if (operation.NonGenericProcessor != null) - { - return Assert.IsType(operation.NonGenericProcessor); - } + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; - return Assert.IsType(operation.GenericProcessor); + if (operation.NonGenericProcessor != null) + { + return Assert.IsType(operation.NonGenericProcessor); } - public T Verify(Rectangle rect, int index = 0) - { - Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); + return Assert.IsType(operation.GenericProcessor); + } - FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; + public T Verify(Rectangle rect, int index = 0) + { + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - Assert.Equal(rect, operation.Rectangle); + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; - if (operation.NonGenericProcessor != null) - { - return Assert.IsType(operation.NonGenericProcessor); - } + Assert.Equal(rect, operation.Rectangle); - return Assert.IsType(operation.GenericProcessor); + if (operation.NonGenericProcessor != null) + { + return Assert.IsType(operation.NonGenericProcessor); } - public void Dispose() => this.source?.Dispose(); + return Assert.IsType(operation.GenericProcessor); } + + public void Dispose() => this.source?.Dispose(); } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs index e9304299d1..c797677860 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/AdaptiveThresholdTests.cs @@ -5,126 +5,124 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization +namespace SixLabors.ImageSharp.Tests.Processing.Binarization; + +[Trait("Category", "Processors")] +public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class AdaptiveThresholdTests : BaseImageOperationsExtensionTest + [Fact] + public void AdaptiveThreshold_UsesDefaults_Works() { - [Fact] - public void AdaptiveThreshold_UsesDefaults_Works() - { - // arrange - float expectedThresholdLimit = .85f; - Color expectedUpper = Color.White; - Color expectedLower = Color.Black; - - // act - this.operations.AdaptiveThreshold(); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } + // arrange + float expectedThresholdLimit = .85f; + Color expectedUpper = Color.White; + Color expectedLower = Color.Black; + + // act + this.operations.AdaptiveThreshold(); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } - [Fact] - public void AdaptiveThreshold_SettingThresholdLimit_Works() - { - // arrange - float expectedThresholdLimit = .65f; + [Fact] + public void AdaptiveThreshold_SettingThresholdLimit_Works() + { + // arrange + float expectedThresholdLimit = .65f; - // act - this.operations.AdaptiveThreshold(expectedThresholdLimit); + // act + this.operations.AdaptiveThreshold(expectedThresholdLimit); - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(Color.White, p.Upper); - Assert.Equal(Color.Black, p.Lower); - } + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(Color.White, p.Upper); + Assert.Equal(Color.Black, p.Lower); + } - [Fact] - public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() - { - // arrange - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; + [Fact] + public void AdaptiveThreshold_SettingUpperLowerThresholds_Works() + { + // arrange + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower); + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower); - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } - [Fact] - public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() - { - // arrange - float expectedThresholdLimit = .77f; - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; - - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); - - // assert - AdaptiveThresholdProcessor p = this.Verify(); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_Works() + { + // arrange + float expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit); + + // assert + AdaptiveThresholdProcessor p = this.Verify(); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } - [Fact] - public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() - { - // arrange - float expectedThresholdLimit = .77f; - Color expectedUpper = Color.HotPink; - Color expectedLower = Color.Yellow; - - // act - this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); - - // assert - AdaptiveThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); - Assert.Equal(expectedUpper, p.Upper); - Assert.Equal(expectedLower, p.Lower); - } + [Fact] + public void AdaptiveThreshold_SettingUpperLowerWithThresholdLimit_WithRectangle_Works() + { + // arrange + float expectedThresholdLimit = .77f; + Color expectedUpper = Color.HotPink; + Color expectedLower = Color.Yellow; + + // act + this.operations.AdaptiveThreshold(expectedUpper, expectedLower, expectedThresholdLimit, this.rect); + + // assert + AdaptiveThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(expectedThresholdLimit, p.ThresholdLimit); + Assert.Equal(expectedUpper, p.Upper); + Assert.Equal(expectedLower, p.Lower); + } - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Issue2217, PixelTypes.Rgba32)] - public void AdaptiveThreshold_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Issue2217, PixelTypes.Rgba32)] + public void AdaptiveThreshold_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(img => img.AdaptiveThreshold()); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + image.Mutate(img => img.AdaptiveThreshold()); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + } - [Theory] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - public void AdaptiveThreshold_WithRectangle_Works(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + public void AdaptiveThreshold_WithRectangle_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); - image.DebugSave(provider); - image.CompareToReferenceOutput(ImageComparer.Exact, provider); - } + image.Mutate(img => img.AdaptiveThreshold(Color.White, Color.Black, new Rectangle(60, 90, 200, 30))); + image.DebugSave(provider); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 6b62d3ded5..1ccb073998 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -4,143 +4,140 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Binarization; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Binarization; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization +[Trait("Category", "Processors")] +public class BinaryThresholdTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class BinaryThresholdTest : BaseImageOperationsExtensionTest + [Fact] + public void BinaryThreshold_CorrectProcessor() { - [Fact] - public void BinaryThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + this.operations.BinaryThreshold(.23f); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinaryThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + [Fact] + public void BinaryThreshold_rect_CorrectProcessor() + { + this.operations.BinaryThreshold(.93f, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinaryThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinaryThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); + } - [Fact] - public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(BinaryThresholdMode.Luminance, p.Mode); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); + } - [Fact] - public void BinarySaturationThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f, BinaryThresholdMode.Saturation); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + [Fact] + public void BinarySaturationThreshold_CorrectProcessor() + { + this.operations.BinaryThreshold(.23f, BinaryThresholdMode.Saturation); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinarySaturationThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, BinaryThresholdMode.Saturation, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + [Fact] + public void BinarySaturationThreshold_rect_CorrectProcessor() + { + this.operations.BinaryThreshold(.93f, BinaryThresholdMode.Saturation, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinarySaturationThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinarySaturationThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); + } - [Fact] - public void BinarySaturationThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinarySaturationThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.Saturation, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(BinaryThresholdMode.Saturation, p.Mode); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); + } - [Fact] - public void BinaryMaxChromaThreshold_CorrectProcessor() - { - this.operations.BinaryThreshold(.23f, BinaryThresholdMode.MaxChroma); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + [Fact] + public void BinaryMaxChromaThreshold_CorrectProcessor() + { + this.operations.BinaryThreshold(.23f, BinaryThresholdMode.MaxChroma); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinaryMaxChromaThreshold_rect_CorrectProcessor() - { - this.operations.BinaryThreshold(.93f, BinaryThresholdMode.MaxChroma, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } + [Fact] + public void BinaryMaxChromaThreshold_rect_CorrectProcessor() + { + this.operations.BinaryThreshold(.93f, BinaryThresholdMode.MaxChroma, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); + Assert.Equal(Color.White, p.UpperColor); + Assert.Equal(Color.Black, p.LowerColor); + } - [Fact] - public void BinaryMaxChromaThreshold_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma); - BinaryThresholdProcessor p = this.Verify(); - Assert.Equal(.23f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinaryMaxChromaThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma); + BinaryThresholdProcessor p = this.Verify(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); + } - [Fact] - public void BinaryMaxChromaThreshold_rect_CorrectProcessorWithUpperLower() - { - this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma, this.rect); - BinaryThresholdProcessor p = this.Verify(this.rect); - Assert.Equal(.93f, p.Threshold); - Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } + [Fact] + public void BinaryMaxChromaThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, Color.HotPink, Color.Yellow, BinaryThresholdMode.MaxChroma, this.rect); + BinaryThresholdProcessor p = this.Verify(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(BinaryThresholdMode.MaxChroma, p.Mode); + Assert.Equal(Color.HotPink, p.UpperColor); + Assert.Equal(Color.Yellow, p.LowerColor); } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs index fe6ebb06f1..c7eb959535 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -3,103 +3,100 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Binarization; -namespace SixLabors.ImageSharp.Tests.Processing.Binarization +[Trait("Category", "Processors")] +public class OrderedDitherFactoryTests { - [Trait("Category", "Processors")] - 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] - { - { 0, 2 }, - { 3, 1 } - }); + private static readonly DenseMatrix Expected2x2Matrix = new DenseMatrix( + new uint[2, 2] + { + { 0, 2 }, + { 3, 1 } + }); - private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( - new uint[3, 3] - { - { 0, 5, 2 }, - { 7, 4, 8 }, - { 3, 6, 1 } - }); + private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( + new uint[3, 3] + { + { 0, 5, 2 }, + { 7, 4, 8 }, + { 3, 6, 1 } + }); - private static readonly DenseMatrix Expected4x4Matrix = new DenseMatrix( - new uint[4, 4] - { - { 0, 8, 2, 10 }, - { 12, 4, 14, 6 }, - { 3, 11, 1, 9 }, - { 15, 7, 13, 5 } - }); + private static readonly DenseMatrix Expected4x4Matrix = new DenseMatrix( + new uint[4, 4] + { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }); - private static readonly DenseMatrix Expected8x8Matrix = new DenseMatrix( - new uint[8, 8] - { - { 0, 32, 8, 40, 2, 34, 10, 42 }, - { 48, 16, 56, 24, 50, 18, 58, 26 }, - { 12, 44, 4, 36, 14, 46, 6, 38 }, - { 60, 28, 52, 20, 62, 30, 54, 22 }, - { 3, 35, 11, 43, 1, 33, 9, 41 }, - { 51, 19, 59, 27, 49, 17, 57, 25 }, - { 15, 47, 7, 39, 13, 45, 5, 37 }, - { 63, 31, 55, 23, 61, 29, 53, 21 } - }); + private static readonly DenseMatrix Expected8x8Matrix = new DenseMatrix( + new uint[8, 8] + { + { 0, 32, 8, 40, 2, 34, 10, 42 }, + { 48, 16, 56, 24, 50, 18, 58, 26 }, + { 12, 44, 4, 36, 14, 46, 6, 38 }, + { 60, 28, 52, 20, 62, 30, 54, 22 }, + { 3, 35, 11, 43, 1, 33, 9, 41 }, + { 51, 19, 59, 27, 49, 17, 57, 25 }, + { 15, 47, 7, 39, 13, 45, 5, 37 }, + { 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() + [Fact] + public void OrderedDitherFactoryCreatesCorrect2x2Matrix() + { + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(2); + for (int y = 0; y < actual.Rows; y++) { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(2); - for (int y = 0; y < actual.Rows; y++) + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); - } + Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); } } + } - [Fact] - public void OrderedDitherFactoryCreatesCorrect3x3Matrix() + [Fact] + public void OrderedDitherFactoryCreatesCorrect3x3Matrix() + { + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(3); + for (int y = 0; y < actual.Rows; y++) { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(3); - for (int y = 0; y < actual.Rows; y++) + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); - } + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); } } + } - [Fact] - public void OrderedDitherFactoryCreatesCorrect4x4Matrix() + [Fact] + public void OrderedDitherFactoryCreatesCorrect4x4Matrix() + { + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(4); + for (int y = 0; y < actual.Rows; y++) { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(4); - for (int y = 0; y < actual.Rows; y++) + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); - } + Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); } } + } - [Fact] - public void OrderedDitherFactoryCreatesCorrect8x8Matrix() + [Fact] + public void OrderedDitherFactoryCreatesCorrect8x8Matrix() + { + DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(8); + for (int y = 0; y < actual.Rows; y++) { - DenseMatrix actual = OrderedDitherFactory.CreateDitherMatrix(8); - for (int y = 0; y < actual.Rows; y++) + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); - } + Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); } } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs index 3f86d641ce..da2bc465f6 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -3,38 +3,36 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; + +[Trait("Category", "Processors")] +public class BoxBlurTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class BoxBlurTest : BaseImageOperationsExtensionTest + [Fact] + public void BoxBlur_BoxBlurProcessorDefaultsSet() + { + this.operations.BoxBlur(); + var processor = this.Verify(); + + Assert.Equal(7, processor.Radius); + } + + [Fact] + public void BoxBlur_amount_BoxBlurProcessorDefaultsSet() { - [Fact] - public void BoxBlur_BoxBlurProcessorDefaultsSet() - { - this.operations.BoxBlur(); - var processor = this.Verify(); - - Assert.Equal(7, processor.Radius); - } - - [Fact] - public void BoxBlur_amount_BoxBlurProcessorDefaultsSet() - { - this.operations.BoxBlur(34); - var processor = this.Verify(); - - Assert.Equal(34, processor.Radius); - } - - [Fact] - public void BoxBlur_amount_rect_BoxBlurProcessorDefaultsSet() - { - this.operations.BoxBlur(5, this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(5, processor.Radius); - } + this.operations.BoxBlur(34); + var processor = this.Verify(); + + Assert.Equal(34, processor.Radius); + } + + [Fact] + public void BoxBlur_amount_rect_BoxBlurProcessorDefaultsSet() + { + this.operations.BoxBlur(5, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(5, processor.Radius); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 9e0fd2d938..2a1273ebb7 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -3,198 +3,196 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; + +[Trait("Category", "Processors")] +public class DetectEdgesTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class DetectEdgesTest : BaseImageOperationsExtensionTest + [Fact] + public void DetectEdges_EdgeDetector2DProcessorDefaultsSet() { - [Fact] - public void DetectEdges_EdgeDetector2DProcessorDefaultsSet() - { - this.operations.DetectEdges(); - EdgeDetector2DProcessor processor = this.Verify(); + this.operations.DetectEdges(); + EdgeDetector2DProcessor processor = this.Verify(); - Assert.True(processor.Grayscale); - Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); + } - [Fact] - public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet() - { - this.operations.DetectEdges(this.rect); - EdgeDetector2DProcessor processor = this.Verify(this.rect); - - Assert.True(processor.Grayscale); - Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); - } - - public static TheoryData EdgeDetector2DKernelData { get; } = - new() - { - { KnownEdgeDetectorKernels.Kayyali, true }, - { KnownEdgeDetectorKernels.Kayyali, false }, - { KnownEdgeDetectorKernels.Prewitt, true }, - { KnownEdgeDetectorKernels.Prewitt, false }, - { KnownEdgeDetectorKernels.RobertsCross, true }, - { KnownEdgeDetectorKernels.RobertsCross, false }, - { KnownEdgeDetectorKernels.Scharr, true }, - { KnownEdgeDetectorKernels.Scharr, false }, - { KnownEdgeDetectorKernels.Sobel, true }, - { KnownEdgeDetectorKernels.Sobel, false }, - }; - - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetector2DProcessor processor = this.Verify(); + [Fact] + public void DetectEdges_Rect_EdgeDetector2DProcessorDefaultsSet() + { + this.operations.DetectEdges(this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(KnownEdgeDetectorKernels.Sobel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + public static TheoryData EdgeDetector2DKernelData { get; } = + new() { - this.operations.DetectEdges(kernel, this.rect); - EdgeDetector2DProcessor processor = this.Verify(this.rect); + { KnownEdgeDetectorKernels.Kayyali, true }, + { KnownEdgeDetectorKernels.Kayyali, false }, + { KnownEdgeDetectorKernels.Prewitt, true }, + { KnownEdgeDetectorKernels.Prewitt, false }, + { KnownEdgeDetectorKernels.RobertsCross, true }, + { KnownEdgeDetectorKernels.RobertsCross, false }, + { KnownEdgeDetectorKernels.Scharr, true }, + { KnownEdgeDetectorKernels.Scharr, false }, + { KnownEdgeDetectorKernels.Sobel, true }, + { KnownEdgeDetectorKernels.Sobel, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetector2DProcessor processor = this.Verify(); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetector2DProcessor processor = this.Verify(); + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_Rect_EdgeDetector2DProcessor_DefaultGrayScale_Set(EdgeDetector2DKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetector2DKernelData))] - public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale, this.rect); - EdgeDetector2DProcessor processor = this.Verify(this.rect); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - public static TheoryData EdgeDetectorKernelData { get; } = - new() - { - { KnownEdgeDetectorKernels.Laplacian3x3, true }, - { KnownEdgeDetectorKernels.Laplacian3x3, false }, - { KnownEdgeDetectorKernels.Laplacian5x5, true }, - { KnownEdgeDetectorKernels.Laplacian5x5, false }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, true }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, false }, - }; - - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetectorProcessor processor = this.Verify(); + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale); + EdgeDetector2DProcessor processor = this.Verify(); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) - { - this.operations.DetectEdges(kernel, this.rect); - EdgeDetectorProcessor processor = this.Verify(this.rect); + [Theory] + [MemberData(nameof(EdgeDetector2DKernelData))] + public void DetectEdges_Rect_EdgeDetector2DProcessorSet(EdgeDetector2DKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetector2DProcessor processor = this.Verify(this.rect); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) + public static TheoryData EdgeDetectorKernelData { get; } = + new() { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetectorProcessor processor = this.Verify(); + { KnownEdgeDetectorKernels.Laplacian3x3, true }, + { KnownEdgeDetectorKernels.Laplacian3x3, false }, + { KnownEdgeDetectorKernels.Laplacian5x5, true }, + { KnownEdgeDetectorKernels.Laplacian5x5, false }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, true }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetectorProcessor processor = this.Verify(); - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorKernelData))] - public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale, this.rect); - EdgeDetectorProcessor processor = this.Verify(this.rect); - - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } - - public static TheoryData EdgeDetectorCompassKernelData { get; } = - new() - { - { KnownEdgeDetectorKernels.Kirsch, true }, - { KnownEdgeDetectorKernels.Kirsch, false }, - { KnownEdgeDetectorKernels.Robinson, true }, - { KnownEdgeDetectorKernels.Robinson, false }, - }; - - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) - { - this.operations.DetectEdges(kernel); - EdgeDetectorCompassProcessor processor = this.Verify(); + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_Rect_EdgeDetectorProcessor_DefaultGrayScale_Set(EdgeDetectorKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) - { - this.operations.DetectEdges(kernel, this.rect); - EdgeDetectorCompassProcessor processor = this.Verify(this.rect); + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale); + EdgeDetectorProcessor processor = this.Verify(); - Assert.True(processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) - { - this.operations.DetectEdges(kernel, grayscale); - EdgeDetectorCompassProcessor processor = this.Verify(); + [Theory] + [MemberData(nameof(EdgeDetectorKernelData))] + public void DetectEdges_Rect_EdgeDetectorProcessorSet(EdgeDetectorKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetectorProcessor processor = this.Verify(this.rect); - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } - [Theory] - [MemberData(nameof(EdgeDetectorCompassKernelData))] - public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) + public static TheoryData EdgeDetectorCompassKernelData { get; } = + new() { - this.operations.DetectEdges(kernel, grayscale, this.rect); - EdgeDetectorCompassProcessor processor = this.Verify(this.rect); + { KnownEdgeDetectorKernels.Kirsch, true }, + { KnownEdgeDetectorKernels.Kirsch, false }, + { KnownEdgeDetectorKernels.Robinson, true }, + { KnownEdgeDetectorKernels.Robinson, false }, + }; + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) + { + this.operations.DetectEdges(kernel); + EdgeDetectorCompassProcessor processor = this.Verify(); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_Rect_EdgeDetectorCompassProcessor_DefaultGrayScale_Set(EdgeDetectorCompassKernel kernel, bool _) + { + this.operations.DetectEdges(kernel, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); + + Assert.True(processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale); + EdgeDetectorCompassProcessor processor = this.Verify(); + + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); + } + + [Theory] + [MemberData(nameof(EdgeDetectorCompassKernelData))] + public void DetectEdges_Rect_EdgeDetectorCompassProcessorSet(EdgeDetectorCompassKernel kernel, bool grayscale) + { + this.operations.DetectEdges(kernel, grayscale, this.rect); + EdgeDetectorCompassProcessor processor = this.Verify(this.rect); - Assert.Equal(grayscale, processor.Grayscale); - Assert.Equal(kernel, processor.Kernel); - } + Assert.Equal(grayscale, processor.Grayscale); + Assert.Equal(kernel, processor.Kernel); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 1e0654192e..5142791603 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -3,38 +3,36 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; + +[Trait("Category", "Processors")] +public class GaussianBlurTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class GaussianBlurTest : BaseImageOperationsExtensionTest + [Fact] + public void GaussianBlur_GaussianBlurProcessorDefaultsSet() + { + this.operations.GaussianBlur(); + var processor = this.Verify(); + + Assert.Equal(3f, processor.Sigma); + } + + [Fact] + public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() { - [Fact] - public void GaussianBlur_GaussianBlurProcessorDefaultsSet() - { - this.operations.GaussianBlur(); - var processor = this.Verify(); - - Assert.Equal(3f, processor.Sigma); - } - - [Fact] - public void GaussianBlur_amount_GaussianBlurProcessorDefaultsSet() - { - this.operations.GaussianBlur(0.2f); - var processor = this.Verify(); - - Assert.Equal(.2f, processor.Sigma); - } - - [Fact] - public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() - { - this.operations.GaussianBlur(0.6f, this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(.6f, processor.Sigma); - } + this.operations.GaussianBlur(0.2f); + var processor = this.Verify(); + + Assert.Equal(.2f, processor.Sigma); + } + + [Fact] + public void GaussianBlur_amount_rect_GaussianBlurProcessorDefaultsSet() + { + this.operations.GaussianBlur(0.6f, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(.6f, processor.Sigma); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index 03bf697a35..b48ebac0dc 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -3,38 +3,36 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; + +[Trait("Category", "Processors")] +public class GaussianSharpenTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class GaussianSharpenTest : BaseImageOperationsExtensionTest + [Fact] + public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() + { + this.operations.GaussianSharpen(); + var processor = this.Verify(); + + Assert.Equal(3f, processor.Sigma); + } + + [Fact] + public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() { - [Fact] - public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() - { - this.operations.GaussianSharpen(); - var processor = this.Verify(); - - Assert.Equal(3f, processor.Sigma); - } - - [Fact] - public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() - { - this.operations.GaussianSharpen(0.2f); - var processor = this.Verify(); - - Assert.Equal(.2f, processor.Sigma); - } - - [Fact] - public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() - { - this.operations.GaussianSharpen(0.6f, this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(.6f, processor.Sigma); - } + this.operations.GaussianSharpen(0.2f); + var processor = this.Verify(); + + Assert.Equal(.2f, processor.Sigma); + } + + [Fact] + public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() + { + this.operations.GaussianSharpen(0.6f, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(.6f, processor.Sigma); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs index e87c845f80..476e5b0a90 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs @@ -2,420 +2,418 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; + +[Trait("Category", "Processors")] +public class KernelSamplingMapTest { - [Trait("Category", "Processors")] - public class KernelSamplingMapTest + [Fact] + public void KernalSamplingMap_Kernel5Image7x7RepeatBorder() { - [Fact] - public void KernalSamplingMap_Kernel5Image7x7RepeatBorder() + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Repeat; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Repeat; - int[] expected = - { - 0, 0, 0, 1, 2, - 0, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 6, - 4, 5, 6, 6, 6, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 0, 0, 0, 1, 2, + 0, 0, 1, 2, 3, + 0, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 6, + 4, 5, 6, 6, 6, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7BounceBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7BounceBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Bounce; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Bounce; - int[] expected = - { - 2, 1, 0, 1, 2, - 1, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 5, - 4, 5, 6, 5, 4, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 2, 1, 0, 1, 2, + 1, 0, 1, 2, 3, + 0, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 5, + 4, 5, 6, 5, 4, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7MirrorBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7MirrorBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Mirror; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Mirror; - int[] expected = - { - 1, 0, 0, 1, 2, - 0, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 6, - 4, 5, 6, 6, 5, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 1, 0, 0, 1, 2, + 0, 0, 1, 2, 3, + 0, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 6, + 4, 5, 6, 6, 5, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7WrapBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7WrapBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Wrap; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Wrap; - int[] expected = - { - 5, 6, 0, 1, 2, - 6, 0, 1, 2, 3, - 0, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 0, - 4, 5, 6, 0, 1, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 5, 6, 0, 1, 2, + 6, 0, 1, 2, 3, + 0, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 0, + 4, 5, 6, 0, 1, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image9x9BounceBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image9x9BounceBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(1, 1, 9, 9); + var mode = BorderWrappingMode.Bounce; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(1, 1, 9, 9); - var mode = BorderWrappingMode.Bounce; - int[] expected = - { - 3, 2, 1, 2, 3, - 2, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 8, - 7, 8, 9, 8, 7, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 3, 2, 1, 2, 3, + 2, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 9, + 6, 7, 8, 9, 8, + 7, 8, 9, 8, 7, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image9x9MirrorBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image9x9MirrorBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(1, 1, 9, 9); + var mode = BorderWrappingMode.Mirror; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(1, 1, 9, 9); - var mode = BorderWrappingMode.Mirror; - int[] expected = - { - 2, 1, 1, 2, 3, - 1, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 9, - 7, 8, 9, 9, 8, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 2, 1, 1, 2, 3, + 1, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 9, + 6, 7, 8, 9, 9, + 7, 8, 9, 9, 8, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image9x9WrapBorder() + [Fact] + public void KernalSamplingMap_Kernel5Image9x9WrapBorder() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(1, 1, 9, 9); + var mode = BorderWrappingMode.Wrap; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(1, 1, 9, 9); - var mode = BorderWrappingMode.Wrap; - int[] expected = - { - 8, 9, 1, 2, 3, - 9, 1, 2, 3, 4, - 1, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 9, - 6, 7, 8, 9, 1, - 7, 8, 9, 1, 2, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 8, 9, 1, 2, 3, + 9, 1, 2, 3, 4, + 1, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 9, + 6, 7, 8, 9, 1, + 7, 8, 9, 1, 2, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7RepeatBorderTile() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7RepeatBorderTile() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Repeat; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Repeat; - int[] expected = - { - 2, 2, 2, 3, 4, - 2, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 8, - 6, 7, 8, 8, 8, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 2, 2, 2, 3, 4, + 2, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 8, + 6, 7, 8, 8, 8, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Bounce; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Bounce; - int[] expected = - { - 4, 3, 2, 3, 4, - 3, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 7, - 6, 7, 8, 7, 6, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 4, 3, 2, 3, 4, + 3, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 7, + 6, 7, 8, 7, 6, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Mirror; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Mirror; - int[] expected = - { - 3, 2, 2, 3, 4, - 2, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 8, - 6, 7, 8, 8, 7, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 3, 2, 2, 3, 4, + 2, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 8, + 6, 7, 8, 8, 7, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel5Image7x7WrapBorderTile() + [Fact] + public void KernalSamplingMap_Kernel5Image7x7WrapBorderTile() + { + var kernelSize = new Size(5, 5); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Wrap; + int[] expected = { - var kernelSize = new Size(5, 5); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Wrap; - int[] expected = - { - 7, 8, 2, 3, 4, - 8, 2, 3, 4, 5, - 2, 3, 4, 5, 6, - 3, 4, 5, 6, 7, - 4, 5, 6, 7, 8, - 5, 6, 7, 8, 2, - 6, 7, 8, 2, 3, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 7, 8, 2, 3, 4, + 8, 2, 3, 4, 5, + 2, 3, 4, 5, 6, + 3, 4, 5, 6, 7, + 4, 5, 6, 7, 8, + 5, 6, 7, 8, 2, + 6, 7, 8, 2, 3, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7RepeatBorder() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7RepeatBorder() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Repeat; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Repeat; - int[] expected = - { - 0, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 6, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 0, 0, 1, + 0, 1, 2, + 1, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 6, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7BounceBorder() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7BounceBorder() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Bounce; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Bounce; - int[] expected = - { - 1, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 5, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 1, 0, 1, + 0, 1, 2, + 1, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 5, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7MirrorBorder() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7MirrorBorder() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Mirror; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Mirror; - int[] expected = - { - 0, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 6, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 0, 0, 1, + 0, 1, 2, + 1, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 6, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7WrapBorder() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7WrapBorder() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(0, 0, 7, 7); + var mode = BorderWrappingMode.Wrap; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(0, 0, 7, 7); - var mode = BorderWrappingMode.Wrap; - int[] expected = - { - 6, 0, 1, - 0, 1, 2, - 1, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 0, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 6, 0, 1, + 0, 1, 2, + 1, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 0, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7RepeatBorderTile() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7RepeatBorderTile() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Repeat; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Repeat; - int[] expected = - { - 2, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 8, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 2, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 7, + 6, 7, 8, + 7, 8, 8, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7BounceBorderTile() + [Fact] + public void KernalSamplingMap_Kernel3Image7BounceBorderTile() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Bounce; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Bounce; - int[] expected = - { - 3, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 7, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 3, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 7, + 6, 7, 8, + 7, 8, 7, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7MirrorBorderTile() + [Fact] + public void KernalSamplingMap_Kernel3Image7MirrorBorderTile() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Mirror; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Mirror; - int[] expected = - { - 2, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 8, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 2, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 7, + 6, 7, 8, + 7, 8, 8, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x7WrapBorderTile() + [Fact] + public void KernalSamplingMap_Kernel3Image7x7WrapBorderTile() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(2, 2, 7, 7); + var mode = BorderWrappingMode.Wrap; + int[] expected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(2, 2, 7, 7); - var mode = BorderWrappingMode.Wrap; - int[] expected = - { - 8, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 2, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); - } + 8, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 7, + 6, 7, 8, + 7, 8, 2, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected); + } - [Fact] - public void KernalSamplingMap_Kernel3Image7x5WrapBorderTile() + [Fact] + public void KernalSamplingMap_Kernel3Image7x5WrapBorderTile() + { + var kernelSize = new Size(3, 3); + var bounds = new Rectangle(2, 2, 7, 5); + var mode = BorderWrappingMode.Wrap; + int[] xExpected = { - var kernelSize = new Size(3, 3); - var bounds = new Rectangle(2, 2, 7, 5); - var mode = BorderWrappingMode.Wrap; - int[] xExpected = - { - 8, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 7, - 6, 7, 8, - 7, 8, 2, - }; - int[] yExpected = - { - 6, 2, 3, - 2, 3, 4, - 3, 4, 5, - 4, 5, 6, - 5, 6, 2, - }; - this.AssertOffsets(kernelSize, bounds, mode, mode, xExpected, yExpected); - } - - private void AssertOffsets(Size kernelSize, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode, int[] xExpected, int[] yExpected) + 8, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 7, + 6, 7, 8, + 7, 8, 2, + }; + int[] yExpected = { - // Arrange - var map = new KernelSamplingMap(Configuration.Default.MemoryAllocator); + 6, 2, 3, + 2, 3, 4, + 3, 4, 5, + 4, 5, 6, + 5, 6, 2, + }; + this.AssertOffsets(kernelSize, bounds, mode, mode, xExpected, yExpected); + } + + private void AssertOffsets(Size kernelSize, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode, int[] xExpected, int[] yExpected) + { + // Arrange + var map = new KernelSamplingMap(Configuration.Default.MemoryAllocator); - // Act - map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode); + // Act + map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode); - // Assert - var xOffsets = map.GetColumnOffsetSpan().ToArray(); - Assert.Equal(xExpected, xOffsets); - var yOffsets = map.GetRowOffsetSpan().ToArray(); - Assert.Equal(yExpected, yOffsets); - } + // Assert + var xOffsets = map.GetColumnOffsetSpan().ToArray(); + Assert.Equal(xExpected, xOffsets); + var yOffsets = map.GetRowOffsetSpan().ToArray(); + Assert.Equal(yExpected, yOffsets); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs index aac1a1eca8..dc497628fb 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/MedianBlurTest.cs @@ -4,31 +4,28 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Convolution; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution +[Trait("Category", "Processors")] +public class MedianBlurTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class MedianBlurTest : BaseImageOperationsExtensionTest + [Fact] + public void Median_radius_MedianProcessorDefaultsSet() { - [Fact] - public void Median_radius_MedianProcessorDefaultsSet() - { - this.operations.MedianBlur(3, true); - var processor = this.Verify(); + this.operations.MedianBlur(3, true); + var processor = this.Verify(); - Assert.Equal(3, processor.Radius); - Assert.True(processor.PreserveAlpha); - } + Assert.Equal(3, processor.Radius); + Assert.True(processor.PreserveAlpha); + } - [Fact] - public void Median_radius_rect_MedianProcessorDefaultsSet() - { - this.operations.MedianBlur(5, false, this.rect); - var processor = this.Verify(this.rect); + [Fact] + public void Median_radius_rect_MedianProcessorDefaultsSet() + { + this.operations.MedianBlur(5, false, this.rect); + var processor = this.Verify(this.rect); - Assert.Equal(5, processor.Radius); - Assert.False(processor.PreserveAlpha); - } + Assert.Equal(5, processor.Radius); + Assert.False(processor.PreserveAlpha); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs index c6d23289a0..62344ae6e7 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/EdgeDetectorKernelTests.cs @@ -3,79 +3,77 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors +namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors; + +public class EdgeDetectorKernelTests { - public class EdgeDetectorKernelTests + [Fact] + public void EdgeDetectorKernelEqualityOperatorTest() { - [Fact] - public void EdgeDetectorKernelEqualityOperatorTest() - { - EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3; - EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3; - EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5; + EdgeDetectorKernel kernel0 = KnownEdgeDetectorKernels.Laplacian3x3; + EdgeDetectorKernel kernel1 = KnownEdgeDetectorKernels.Laplacian3x3; + EdgeDetectorKernel kernel2 = KnownEdgeDetectorKernels.Laplacian5x5; - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); + } - [Fact] - public void EdgeDetector2DKernelEqualityOperatorTest() - { - EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt; - EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt; - EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross; + [Fact] + public void EdgeDetector2DKernelEqualityOperatorTest() + { + EdgeDetector2DKernel kernel0 = KnownEdgeDetectorKernels.Prewitt; + EdgeDetector2DKernel kernel1 = KnownEdgeDetectorKernels.Prewitt; + EdgeDetector2DKernel kernel2 = KnownEdgeDetectorKernels.RobertsCross; - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); + } - [Fact] - public void EdgeDetectorCompassKernelEqualityOperatorTest() - { - EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch; - EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch; - EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson; + [Fact] + public void EdgeDetectorCompassKernelEqualityOperatorTest() + { + EdgeDetectorCompassKernel kernel0 = KnownEdgeDetectorKernels.Kirsch; + EdgeDetectorCompassKernel kernel1 = KnownEdgeDetectorKernels.Kirsch; + EdgeDetectorCompassKernel kernel2 = KnownEdgeDetectorKernels.Robinson; - Assert.True(kernel0 == kernel1); - Assert.False(kernel0 != kernel1); + Assert.True(kernel0 == kernel1); + Assert.False(kernel0 != kernel1); - Assert.True(kernel0 != kernel2); - Assert.False(kernel0 == kernel2); + Assert.True(kernel0 != kernel2); + Assert.False(kernel0 == kernel2); - Assert.True(kernel0.Equals((object)kernel1)); - Assert.True(kernel0.Equals(kernel1)); + Assert.True(kernel0.Equals((object)kernel1)); + Assert.True(kernel0.Equals(kernel1)); - Assert.False(kernel0.Equals((object)kernel2)); - Assert.False(kernel0.Equals(kernel2)); + Assert.False(kernel0.Equals((object)kernel2)); + Assert.False(kernel0.Equals(kernel2)); - Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); - Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); - } + Assert.Equal(kernel0.GetHashCode(), kernel1.GetHashCode()); + Assert.NotEqual(kernel0.GetHashCode(), kernel2.GetHashCode()); } } diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs index 698d58c541..800cb06ac1 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -1,66 +1,63 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing.Processors.Convolution; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors -{ - public class LaplacianKernelFactoryTests - { - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); +namespace SixLabors.ImageSharp.Tests.Processing.Convolution.Processors; - private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( - new float[,] - { - { -1, -1, -1 }, - { -1, 8, -1 }, - { -1, -1, -1 } - }); +public class LaplacianKernelFactoryTests +{ + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); - 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 } - }); + private static readonly DenseMatrix Expected3x3Matrix = new DenseMatrix( + new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }); - [Fact] - public void LaplacianKernelFactoryOutOfRangeThrows() - { - Assert.Throws(() => + private static readonly DenseMatrix Expected5x5Matrix = new DenseMatrix( + new float[,] { - LaplacianKernelFactory.CreateKernel(2); + { -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] - public void LaplacianKernelFactoryCreatesCorrect3x3Matrix() + [Fact] + public void LaplacianKernelFactoryOutOfRangeThrows() + { + Assert.Throws(() => { - DenseMatrix actual = LaplacianKernelFactory.CreateKernel(3); - for (int y = 0; y < actual.Rows; y++) + LaplacianKernelFactory.CreateKernel(2); + }); + } + + [Fact] + public void LaplacianKernelFactoryCreatesCorrect3x3Matrix() + { + DenseMatrix actual = LaplacianKernelFactory.CreateKernel(3); + for (int y = 0; y < actual.Rows; y++) + { + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected3x3Matrix[y, x], actual[y, x], ApproximateComparer); - } + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x], ApproximateComparer); } } + } - [Fact] - public void LaplacianKernelFactoryCreatesCorrect5x5Matrix() + [Fact] + public void LaplacianKernelFactoryCreatesCorrect5x5Matrix() + { + DenseMatrix actual = LaplacianKernelFactory.CreateKernel(5); + for (int y = 0; y < actual.Rows; y++) { - DenseMatrix actual = LaplacianKernelFactory.CreateKernel(5); - for (int y = 0; y < actual.Rows; y++) + for (int x = 0; x < actual.Columns; x++) { - for (int x = 0; x < actual.Columns; x++) - { - Assert.Equal(Expected5x5Matrix[y, x], actual[y, x], ApproximateComparer); - } + Assert.Equal(Expected5x5Matrix[y, x], actual[y, x], ApproximateComparer); } } } diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 1ffbd83d7e..1b3d1bd9ac 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -1,175 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Dithering +namespace SixLabors.ImageSharp.Tests.Processing.Dithering; + +[Trait("Category", "Processors")] +public class DitherTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class DitherTest : BaseImageOperationsExtensionTest + private class Assert : Xunit.Assert { - private class Assert : Xunit.Assert + public static void Equal(ReadOnlySpan a, ReadOnlySpan b) { - public static void Equal(ReadOnlySpan a, ReadOnlySpan b) - { - True(a.SequenceEqual(b)); - } + True(a.SequenceEqual(b)); } + } - private readonly IDither orderedDither; - private readonly IDither errorDiffuser; - private readonly Color[] testPalette = - { - Color.Red, - Color.Green, - Color.Blue - }; + private readonly IDither orderedDither; + private readonly IDither errorDiffuser; + private readonly Color[] testPalette = + { + Color.Red, + Color.Green, + Color.Blue + }; - public DitherTest() - { - this.orderedDither = KnownDitherings.Bayer4x4; - this.errorDiffuser = KnownDitherings.FloydSteinberg; - } + public DitherTest() + { + this.orderedDither = KnownDitherings.Bayer4x4; + this.errorDiffuser = KnownDitherings.FloydSteinberg; + } - [Fact] - public void Dither_CorrectProcessor() - { - this.operations.Dither(this.orderedDither); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } + [Fact] + public void Dither_CorrectProcessor() + { + this.operations.Dither(this.orderedDither); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(Color.WebSafePalette, p.Palette); + } - [Fact] - public void Dither_rect_CorrectProcessor() - { - this.operations.Dither(this.orderedDither, this.rect); - PaletteDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.WebSafePalette, p.Palette); - } + [Fact] + public void Dither_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, 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); - PaletteDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.testPalette, p.Palette); - } + [Fact] + public void Dither_index_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.testPalette); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.testPalette, p.Palette); + } - [Fact] - public void Dither_index_rect_CorrectProcessor() - { - 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); - } + [Fact] + public void Dither_index_rect_CorrectProcessor() + { + 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); + } - [Fact] - public void Dither_ErrorDiffuser_CorrectProcessor() - { - 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_CorrectProcessor() + { + 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.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_rect_CorrectProcessor() + { + 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.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_CorrectProcessorWithColors() + { + 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.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 Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + 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 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()); - } + [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 222fb61d03..3ee4ec454d 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -3,51 +3,49 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Effects; + +[Trait("Category", "Processors")] +public class BackgroundColorTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class BackgroundColorTest : BaseImageOperationsExtensionTest + [Fact] + public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(Color.BlanchedAlmond); + BackgroundColorProcessor processor = this.Verify(); + + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Color.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); + + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Color.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() { - [Fact] - public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(Color.BlanchedAlmond); - BackgroundColorProcessor processor = this.Verify(); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); - BackgroundColorProcessor processor = this.Verify(this.rect); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); - BackgroundColorProcessor processor = this.Verify(); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } - - [Fact] - public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet() - { - this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); - BackgroundColorProcessor processor = this.Verify(this.rect); - - Assert.Equal(this.options, processor.GraphicsOptions); - Assert.Equal(Color.BlanchedAlmond, processor.Color); - } + this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); + BackgroundColorProcessor processor = this.Verify(); + + Assert.Equal(this.options, processor.GraphicsOptions); + Assert.Equal(Color.BlanchedAlmond, processor.Color); + } + + [Fact] + public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet() + { + this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); + BackgroundColorProcessor processor = this.Verify(this.rect); + + Assert.Equal(this.options, processor.GraphicsOptions); + 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 68b1f9d2c2..6d5973dbbb 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -3,51 +3,49 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Effects; + +[Trait("Category", "Processors")] +public class OilPaintTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class OilPaintTest : BaseImageOperationsExtensionTest + [Fact] + public void OilPaint_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(); + var processor = this.Verify(); + + Assert.Equal(10, processor.Levels); + Assert.Equal(15, processor.BrushSize); + } + + [Fact] + public void OilPaint_rect_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(10, processor.Levels); + Assert.Equal(15, processor.BrushSize); + } + + [Fact] + public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() { - [Fact] - public void OilPaint_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(); - var processor = this.Verify(); - - Assert.Equal(10, processor.Levels); - Assert.Equal(15, processor.BrushSize); - } - - [Fact] - public void OilPaint_rect_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(10, processor.Levels); - Assert.Equal(15, processor.BrushSize); - } - - [Fact] - public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(34, 65); - var processor = this.Verify(); - - Assert.Equal(34, processor.Levels); - Assert.Equal(65, processor.BrushSize); - } - - [Fact] - public void OilPaint_Levels_Brush_rect_OilPaintingProcessorDefaultsSet() - { - this.operations.OilPaint(54, 43, this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(54, processor.Levels); - Assert.Equal(43, processor.BrushSize); - } + this.operations.OilPaint(34, 65); + var processor = this.Verify(); + + Assert.Equal(34, processor.Levels); + Assert.Equal(65, processor.BrushSize); + } + + [Fact] + public void OilPaint_Levels_Brush_rect_OilPaintingProcessorDefaultsSet() + { + this.operations.OilPaint(54, 43, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(54, processor.Levels); + Assert.Equal(43, processor.BrushSize); } } diff --git a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs index 18b2be7c02..6531f7b442 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -3,38 +3,36 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Effects; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Effects +namespace SixLabors.ImageSharp.Tests.Processing.Effects; + +[Trait("Category", "Processors")] +public class PixelateTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class PixelateTest : BaseImageOperationsExtensionTest + [Fact] + public void Pixelate_PixelateProcessorDefaultsSet() + { + this.operations.Pixelate(); + var processor = this.Verify(); + + Assert.Equal(4, processor.Size); + } + + [Fact] + public void Pixelate_Size_PixelateProcessorDefaultsSet() { - [Fact] - public void Pixelate_PixelateProcessorDefaultsSet() - { - this.operations.Pixelate(); - var processor = this.Verify(); - - Assert.Equal(4, processor.Size); - } - - [Fact] - public void Pixelate_Size_PixelateProcessorDefaultsSet() - { - this.operations.Pixelate(12); - var processor = this.Verify(); - - Assert.Equal(12, processor.Size); - } - - [Fact] - public void Pixelate_Size_rect_PixelateProcessorDefaultsSet() - { - this.operations.Pixelate(23, this.rect); - var processor = this.Verify(this.rect); - - Assert.Equal(23, processor.Size); - } + this.operations.Pixelate(12); + var processor = this.Verify(); + + Assert.Equal(12, processor.Size); + } + + [Fact] + public void Pixelate_Size_rect_PixelateProcessorDefaultsSet() + { + this.operations.Pixelate(23, this.rect); + var processor = this.Verify(this.rect); + + Assert.Equal(23, processor.Size); } } diff --git a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs index dc5b57a06c..071c2fa68f 100644 --- a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs @@ -2,100 +2,97 @@ // Licensed under the Six Labors Split License. using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -namespace SixLabors.ImageSharp.Tests.Processing +namespace SixLabors.ImageSharp.Tests.Processing; + +internal class FakeImageOperationsProvider : IImageProcessingContextFactory { - internal class FakeImageOperationsProvider : IImageProcessingContextFactory + private readonly List imageOperators = new List(); + + public bool HasCreated(Image source) + where TPixel : unmanaged, IPixel { - private readonly List imageOperators = new List(); + return this.Created(source).Any(); + } - public bool HasCreated(Image source) - where TPixel : unmanaged, IPixel - { - return this.Created(source).Any(); - } + public IEnumerable> Created(Image source) + where TPixel : unmanaged, IPixel + { + return this.imageOperators.OfType>() + .Where(x => x.Source == source); + } - public IEnumerable> Created(Image source) - where TPixel : unmanaged, IPixel - { - return this.imageOperators.OfType>() - .Where(x => x.Source == source); - } + public IEnumerable.AppliedOperation> AppliedOperations(Image source) + where TPixel : unmanaged, IPixel + { + return this.Created(source) + .SelectMany(x => x.Applied); + } - public IEnumerable.AppliedOperation> AppliedOperations(Image source) - where TPixel : unmanaged, IPixel - { - return this.Created(source) - .SelectMany(x => x.Applied); - } + public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) + where TPixel : unmanaged, IPixel + { + var op = new FakeImageOperations(configuration, source, mutate); + this.imageOperators.Add(op); + return op; + } - public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : unmanaged, IPixel + public class FakeImageOperations : IInternalImageProcessingContext + where TPixel : unmanaged, IPixel + { + public FakeImageOperations(Configuration configuration, Image source, bool mutate) { - var op = new FakeImageOperations(configuration, source, mutate); - this.imageOperators.Add(op); - return op; + this.Configuration = configuration; + this.Source = mutate ? source : source?.Clone(); } - public class FakeImageOperations : IInternalImageProcessingContext - where TPixel : unmanaged, IPixel - { - public FakeImageOperations(Configuration configuration, Image source, bool mutate) - { - this.Configuration = configuration; - this.Source = mutate ? source : source?.Clone(); - } + public Image Source { get; } - public Image Source { get; } + public List Applied { get; } = new List(); - public List Applied { get; } = new List(); + public Configuration Configuration { get; } - public Configuration Configuration { get; } + public IDictionary Properties { get; } = new ConcurrentDictionary(); - public IDictionary Properties { get; } = new ConcurrentDictionary(); + public Image GetResultImage() + { + return this.Source; + } - public Image GetResultImage() - { - return this.Source; - } + public Size GetCurrentSize() + { + return this.Source.Size(); + } - public Size GetCurrentSize() + public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) + { + this.Applied.Add(new AppliedOperation { - return this.Source.Size(); - } + Rectangle = rectangle, + NonGenericProcessor = processor + }); + return this; + } - public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) - { - this.Applied.Add(new AppliedOperation - { - Rectangle = rectangle, - NonGenericProcessor = processor - }); - return this; - } - - public IImageProcessingContext ApplyProcessor(IImageProcessor processor) - { - this.Applied.Add(new AppliedOperation - { - NonGenericProcessor = processor - }); - return this; - } - - public struct AppliedOperation + public IImageProcessingContext ApplyProcessor(IImageProcessor processor) + { + this.Applied.Add(new AppliedOperation { - public Rectangle? Rectangle { get; set; } + NonGenericProcessor = processor + }); + return this; + } + + public struct AppliedOperation + { + public Rectangle? Rectangle { get; set; } - public IImageProcessor GenericProcessor { get; set; } + public IImageProcessor GenericProcessor { get; set; } - public IImageProcessor NonGenericProcessor { get; set; } - } + public IImageProcessor NonGenericProcessor { get; set; } } } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs index 2171a4ab94..edacc26145 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BlackWhiteTest.cs @@ -3,25 +3,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class BlackWhiteTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class BlackWhiteTest : BaseImageOperationsExtensionTest + [Fact] + public void BlackWhite_CorrectProcessor() { - [Fact] - public void BlackWhite_CorrectProcessor() - { - this.operations.BlackWhite(); - this.Verify(); - } + this.operations.BlackWhite(); + this.Verify(); + } - [Fact] - public void BlackWhite_rect_CorrectProcessor() - { - this.operations.BlackWhite(this.rect); - this.Verify(this.rect); - } + [Fact] + public void BlackWhite_rect_CorrectProcessor() + { + this.operations.BlackWhite(this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 64190bfc4b..87436e4a35 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -4,57 +4,55 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class BrightnessTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class BrightnessTest : BaseImageOperationsExtensionTest + [Fact] + public void Brightness_amount_BrightnessProcessorDefaultsSet() { - [Fact] - public void Brightness_amount_BrightnessProcessorDefaultsSet() - { - this.operations.Brightness(1.5F); - BrightnessProcessor processor = this.Verify(); + this.operations.Brightness(1.5F); + BrightnessProcessor processor = this.Verify(); - Assert.Equal(1.5F, processor.Amount); - } + Assert.Equal(1.5F, processor.Amount); + } - [Fact] - public void Brightness_amount_rect_BrightnessProcessorDefaultsSet() - { - this.operations.Brightness(1.5F, this.rect); - BrightnessProcessor processor = this.Verify(this.rect); + [Fact] + public void Brightness_amount_rect_BrightnessProcessorDefaultsSet() + { + this.operations.Brightness(1.5F, this.rect); + BrightnessProcessor processor = this.Verify(this.rect); - Assert.Equal(1.5F, processor.Amount); - } + Assert.Equal(1.5F, processor.Amount); + } - [Fact] - public void Brightness_scaled_vector() - { - var rgbImage = new Image(Configuration.Default, 100, 100, new Rgb24(0, 0, 0)); + [Fact] + public void Brightness_scaled_vector() + { + var rgbImage = new Image(Configuration.Default, 100, 100, new Rgb24(0, 0, 0)); - rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); + rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new Rgb24(0, 0, 0), rgbImage[0, 0]); + Assert.Equal(new Rgb24(0, 0, 0), rgbImage[0, 0]); - rgbImage = new Image(Configuration.Default, 100, 100, new Rgb24(10, 10, 10)); + rgbImage = new Image(Configuration.Default, 100, 100, new Rgb24(10, 10, 10)); - rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); + rgbImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new Rgb24(20, 20, 20), rgbImage[0, 0]); + Assert.Equal(new Rgb24(20, 20, 20), rgbImage[0, 0]); - var halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-1)); + var halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-1)); - halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); + halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new HalfSingle(-1), halfSingleImage[0, 0]); + Assert.Equal(new HalfSingle(-1), halfSingleImage[0, 0]); - halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-0.5f)); + halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-0.5f)); - halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); + halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new HalfSingle(0), halfSingleImage[0, 0]); - } + Assert.Equal(new HalfSingle(0), halfSingleImage[0, 0]); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index f7ebc6ae58..7fb93bba54 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -1,47 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +[Trait("Category", "Processors")] +public class ColorBlindnessTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class ColorBlindnessTest : BaseImageOperationsExtensionTest + public static IEnumerable TheoryData = new[] { - 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 } - }; + 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] - [MemberData(nameof(TheoryData))] - public void ColorBlindness_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor - { - this.operations.ColorBlindness(colorBlindness); - this.Verify(); - } + [Theory] + [MemberData(nameof(TheoryData))] + public void ColorBlindness_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) + where T : IImageProcessor + { + this.operations.ColorBlindness(colorBlindness); + this.Verify(); + } - [Theory] - [MemberData(nameof(TheoryData))] - public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) - where T : IImageProcessor - { - this.operations.ColorBlindness(colorBlindness, this.rect); - this.Verify(this.rect); - } + [Theory] + [MemberData(nameof(TheoryData))] + public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) + where T : IImageProcessor + { + this.operations.ColorBlindness(colorBlindness, this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs index 8fcda5e242..441680b196 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ContrastTest.cs @@ -3,29 +3,27 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class ContrastTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class ContrastTest : BaseImageOperationsExtensionTest + [Fact] + public void Contrast_amount_ContrastProcessorDefaultsSet() { - [Fact] - public void Contrast_amount_ContrastProcessorDefaultsSet() - { - this.operations.Contrast(1.5F); - ContrastProcessor processor = this.Verify(); + this.operations.Contrast(1.5F); + ContrastProcessor processor = this.Verify(); - Assert.Equal(1.5F, processor.Amount); - } + Assert.Equal(1.5F, processor.Amount); + } - [Fact] - public void Contrast_amount_rect_ContrastProcessorDefaultsSet() - { - this.operations.Contrast(1.5F, this.rect); - ContrastProcessor processor = this.Verify(this.rect); + [Fact] + public void Contrast_amount_rect_ContrastProcessorDefaultsSet() + { + this.operations.Contrast(1.5F, this.rect); + ContrastProcessor processor = this.Verify(this.rect); - Assert.Equal(1.5F, processor.Amount); - } + Assert.Equal(1.5F, processor.Amount); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs index be368ff156..d6df97419c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/FilterTest.cs @@ -3,25 +3,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class FilterTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class FilterTest : BaseImageOperationsExtensionTest + [Fact] + public void Filter_CorrectProcessor() { - [Fact] - public void Filter_CorrectProcessor() - { - this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); - this.Verify(); - } + this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F)); + this.Verify(); + } - [Fact] - public void Filter_rect_CorrectProcessor() - { - this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); - this.Verify(this.rect); - } + [Fact] + public void Filter_rect_CorrectProcessor() + { + this.operations.Filter(KnownFilterMatrices.AchromatomalyFilter * KnownFilterMatrices.CreateHueFilter(90F), this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index 1a41803fdf..36edc10e5d 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -1,47 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +[Trait("Category", "Processors")] +public class GrayscaleTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class GrayscaleTest : BaseImageOperationsExtensionTest + public static IEnumerable ModeTheoryData = new[] { - public static IEnumerable ModeTheoryData = new[] - { - new object[] { new TestType(), GrayscaleMode.Bt709 } - }; + new object[] { new TestType(), GrayscaleMode.Bt709 } + }; - [Theory] - [MemberData(nameof(ModeTheoryData))] - public void Grayscale_mode_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor - { - this.operations.Grayscale(mode); - this.Verify(); - } + [Theory] + [MemberData(nameof(ModeTheoryData))] + public void Grayscale_mode_CorrectProcessor(TestType testType, GrayscaleMode mode) + where T : IImageProcessor + { + this.operations.Grayscale(mode); + this.Verify(); + } - [Theory] - [MemberData(nameof(ModeTheoryData))] - public void Grayscale_mode_rect_CorrectProcessor(TestType testType, GrayscaleMode mode) - where T : IImageProcessor - { - this.operations.Grayscale(mode, this.rect); - this.Verify(this.rect); - } + [Theory] + [MemberData(nameof(ModeTheoryData))] + public void Grayscale_mode_rect_CorrectProcessor(TestType testType, GrayscaleMode mode) + where T : IImageProcessor + { + this.operations.Grayscale(mode, this.rect); + this.Verify(this.rect); + } - [Fact] - public void Grayscale_rect_CorrectProcessor() - { - this.operations.Grayscale(this.rect); - this.Verify(this.rect); - } + [Fact] + public void Grayscale_rect_CorrectProcessor() + { + this.operations.Grayscale(this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs index 3e61f061e4..0c197a96f7 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/HueTest.cs @@ -4,29 +4,26 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +[Trait("Category", "Processors")] +public class HueTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class HueTest : BaseImageOperationsExtensionTest + [Fact] + public void Hue_amount_HueProcessorDefaultsSet() { - [Fact] - public void Hue_amount_HueProcessorDefaultsSet() - { - this.operations.Hue(34f); - var processor = this.Verify(); + this.operations.Hue(34f); + var processor = this.Verify(); - Assert.Equal(34f, processor.Degrees); - } + Assert.Equal(34f, processor.Degrees); + } - [Fact] - public void Hue_amount_rect_HueProcessorDefaultsSet() - { - this.operations.Hue(5f, this.rect); - var processor = this.Verify(this.rect); + [Fact] + public void Hue_amount_rect_HueProcessorDefaultsSet() + { + this.operations.Hue(5f, this.rect); + var processor = this.Verify(this.rect); - Assert.Equal(5f, processor.Degrees); - } + Assert.Equal(5f, processor.Degrees); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs index fbcb2e5d77..a91d6f5bef 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/InvertTest.cs @@ -3,25 +3,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class InvertTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class InvertTest : BaseImageOperationsExtensionTest + [Fact] + public void Invert_InvertProcessorDefaultsSet() { - [Fact] - public void Invert_InvertProcessorDefaultsSet() - { - this.operations.Invert(); - this.Verify(); - } + this.operations.Invert(); + this.Verify(); + } - [Fact] - public void Pixelate_rect_PixelateProcessorDefaultsSet() - { - this.operations.Invert(this.rect); - this.Verify(this.rect); - } + [Fact] + public void Pixelate_rect_PixelateProcessorDefaultsSet() + { + this.operations.Invert(this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs index ac464517ab..7cac7932c3 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/KodachromeTest.cs @@ -3,25 +3,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class KodachromeTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class KodachromeTest : BaseImageOperationsExtensionTest + [Fact] + public void Kodachrome_amount_KodachromeProcessorDefaultsSet() { - [Fact] - public void Kodachrome_amount_KodachromeProcessorDefaultsSet() - { - this.operations.Kodachrome(); - this.Verify(); - } + this.operations.Kodachrome(); + this.Verify(); + } - [Fact] - public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() - { - this.operations.Kodachrome(this.rect); - this.Verify(this.rect); - } + [Fact] + public void Kodachrome_amount_rect_KodachromeProcessorDefaultsSet() + { + this.operations.Kodachrome(this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs index 9833d391d3..77fea16c54 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LightnessTest.cs @@ -3,29 +3,27 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class LightnessTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class LightnessTest : BaseImageOperationsExtensionTest + [Fact] + public void Lightness_amount_LightnessProcessorDefaultsSet() { - [Fact] - public void Lightness_amount_LightnessProcessorDefaultsSet() - { - this.operations.Lightness(.5F); - LightnessProcessor processor = this.Verify(); + this.operations.Lightness(.5F); + LightnessProcessor processor = this.Verify(); - Assert.Equal(.5F, processor.Amount); - } + Assert.Equal(.5F, processor.Amount); + } - [Fact] - public void Lightness_amount_rect_LightnessProcessorDefaultsSet() - { - this.operations.Lightness(.5F, this.rect); - LightnessProcessor processor = this.Verify(this.rect); + [Fact] + public void Lightness_amount_rect_LightnessProcessorDefaultsSet() + { + this.operations.Lightness(.5F, this.rect); + LightnessProcessor processor = this.Verify(this.rect); - Assert.Equal(.5F, processor.Amount); - } + Assert.Equal(.5F, processor.Amount); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs index ce91932859..e84bd243e4 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/LomographTest.cs @@ -3,27 +3,25 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class LomographTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class LomographTest : BaseImageOperationsExtensionTest + [Fact] + public void Lomograph_amount_LomographProcessorDefaultsSet() { - [Fact] - public void Lomograph_amount_LomographProcessorDefaultsSet() - { - this.operations.Lomograph(); - var processor = this.Verify(); - Assert.Equal(processor.GraphicsOptions, this.options); - } + this.operations.Lomograph(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); + } - [Fact] - public void Lomograph_amount_rect_LomographProcessorDefaultsSet() - { - this.operations.Lomograph(this.rect); - var processor = this.Verify(this.rect); - Assert.Equal(processor.GraphicsOptions, this.options); - } + [Fact] + public void Lomograph_amount_rect_LomographProcessorDefaultsSet() + { + this.operations.Lomograph(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs index 2158f70711..6726471ca8 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/OpacityTest.cs @@ -3,29 +3,27 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class OpacityTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class OpacityTest : BaseImageOperationsExtensionTest + [Fact] + public void Alpha_amount_AlphaProcessorDefaultsSet() { - [Fact] - public void Alpha_amount_AlphaProcessorDefaultsSet() - { - this.operations.Opacity(0.2f); - OpacityProcessor processor = this.Verify(); + this.operations.Opacity(0.2f); + OpacityProcessor processor = this.Verify(); - Assert.Equal(.2f, processor.Amount); - } + Assert.Equal(.2f, processor.Amount); + } - [Fact] - public void Alpha_amount_rect_AlphaProcessorDefaultsSet() - { - this.operations.Opacity(0.6f, this.rect); - OpacityProcessor processor = this.Verify(this.rect); + [Fact] + public void Alpha_amount_rect_AlphaProcessorDefaultsSet() + { + this.operations.Opacity(0.6f, this.rect); + OpacityProcessor processor = this.Verify(this.rect); - Assert.Equal(.6f, processor.Amount); - } + Assert.Equal(.6f, processor.Amount); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs index 88a7baa998..7d3e0aed0f 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/PolaroidTest.cs @@ -4,27 +4,24 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Filters; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +[Trait("Category", "Processors")] +public class PolaroidTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class PolaroidTest : BaseImageOperationsExtensionTest + [Fact] + public void Polaroid_amount_PolaroidProcessorDefaultsSet() { - [Fact] - public void Polaroid_amount_PolaroidProcessorDefaultsSet() - { - this.operations.Polaroid(); - var processor = this.Verify(); - Assert.Equal(processor.GraphicsOptions, this.options); - } + this.operations.Polaroid(); + var processor = this.Verify(); + Assert.Equal(processor.GraphicsOptions, this.options); + } - [Fact] - public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() - { - this.operations.Polaroid(this.rect); - var processor = this.Verify(this.rect); - Assert.Equal(processor.GraphicsOptions, this.options); - } + [Fact] + public void Polaroid_amount_rect_PolaroidProcessorDefaultsSet() + { + this.operations.Polaroid(this.rect); + var processor = this.Verify(this.rect); + Assert.Equal(processor.GraphicsOptions, this.options); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs index dc5e57fcd7..ae90ebca7c 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SaturateTest.cs @@ -3,29 +3,27 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class SaturateTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class SaturateTest : BaseImageOperationsExtensionTest + [Fact] + public void Saturation_amount_SaturationProcessorDefaultsSet() { - [Fact] - public void Saturation_amount_SaturationProcessorDefaultsSet() - { - this.operations.Saturate(34); - SaturateProcessor processor = this.Verify(); + this.operations.Saturate(34); + SaturateProcessor processor = this.Verify(); - Assert.Equal(34, processor.Amount); - } + Assert.Equal(34, processor.Amount); + } - [Fact] - public void Saturation_amount_rect_SaturationProcessorDefaultsSet() - { - this.operations.Saturate(5, this.rect); - SaturateProcessor processor = this.Verify(this.rect); + [Fact] + public void Saturation_amount_rect_SaturationProcessorDefaultsSet() + { + this.operations.Saturate(5, this.rect); + SaturateProcessor processor = this.Verify(this.rect); - Assert.Equal(5, processor.Amount); - } + Assert.Equal(5, processor.Amount); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs index 335c7af760..0646f4a3f4 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/SepiaTest.cs @@ -3,25 +3,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Filters; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Filters; + +[Trait("Category", "Processors")] +public class SepiaTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class SepiaTest : BaseImageOperationsExtensionTest + [Fact] + public void Sepia_amount_SepiaProcessorDefaultsSet() { - [Fact] - public void Sepia_amount_SepiaProcessorDefaultsSet() - { - this.operations.Sepia(); - this.Verify(); - } + this.operations.Sepia(); + this.Verify(); + } - [Fact] - public void Sepia_amount_rect_SepiaProcessorDefaultsSet() - { - this.operations.Sepia(this.rect); - this.Verify(this.rect); - } + [Fact] + public void Sepia_amount_rect_SepiaProcessorDefaultsSet() + { + this.operations.Sepia(this.rect); + this.Verify(this.rect); } } diff --git a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs index b8776d88b5..744e26d8f7 100644 --- a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs @@ -1,177 +1,172 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing; -namespace SixLabors.ImageSharp.Tests.Processing +/// +/// Tests the configuration class. +/// +public class ImageOperationTests : IDisposable { - /// - /// Tests the configuration class. - /// - public class ImageOperationTests : IDisposable + private readonly Image image; + private readonly FakeImageOperationsProvider provider; + private readonly IImageProcessor processorDefinition; + + private static readonly string ExpectedExceptionMessage = GetExpectedExceptionText(); + + public ImageOperationTests() { - private readonly Image image; - private readonly FakeImageOperationsProvider provider; - private readonly IImageProcessor processorDefinition; + this.provider = new FakeImageOperationsProvider(); - private static readonly string ExpectedExceptionMessage = GetExpectedExceptionText(); + var processorMock = new Mock(); + this.processorDefinition = processorMock.Object; - public ImageOperationTests() - { - this.provider = new FakeImageOperationsProvider(); - - 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() - { - this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition)); + [Fact] + public void MutateCallsImageOperationsProvider_Func_OriginalImage() + { + this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition)); - Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() - { - this.image.Mutate(this.processorDefinition); + [Fact] + public void MutateCallsImageOperationsProvider_ListOfProcessors_OriginalImage() + { + this.image.Mutate(this.processorDefinition); - Assert.True(this.provider.HasCreated(this.image)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } + Assert.True(this.provider.HasCreated(this.image)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() - { - Image returned = this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); + [Fact] + public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() + { + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); - Assert.True(this.provider.HasCreated(returned)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); - } + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() - { - Image returned = this.image.Clone(this.processorDefinition); + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() + { + Image returned = this.image.Clone(this.processorDefinition); - Assert.True(this.provider.HasCreated(returned)); - Assert.Contains( - this.processorDefinition, - this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); - } + Assert.True(this.provider.HasCreated(returned)); + Assert.Contains( + this.processorDefinition, + this.provider.AppliedOperations(returned).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void CloneCallsImageOperationsProvider_Func_NotOnOriginal() - { - this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); - Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } + [Fact] + public void CloneCallsImageOperationsProvider_Func_NotOnOriginal() + { + this.image.Clone(x => x.ApplyProcessor(this.processorDefinition)); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOriginal() - { - this.image.Clone(this.processorDefinition); - Assert.False(this.provider.HasCreated(this.image)); - Assert.DoesNotContain( - this.processorDefinition, - this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); - } + [Fact] + public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOriginal() + { + this.image.Clone(this.processorDefinition); + Assert.False(this.provider.HasCreated(this.image)); + Assert.DoesNotContain( + this.processorDefinition, + this.provider.AppliedOperations(this.image).Select(x => x.NonGenericProcessor)); + } - [Fact] - public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() - { - var operations = new FakeImageOperationsProvider.FakeImageOperations(Configuration.Default, null, false); - operations.ApplyProcessors(this.processorDefinition); - Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); - } + [Fact] + public void ApplyProcessors_ListOfProcessors_AppliesAllProcessorsToOperation() + { + var operations = new FakeImageOperationsProvider.FakeImageOperations(Configuration.Default, null, false); + operations.ApplyProcessors(this.processorDefinition); + Assert.Contains(this.processorDefinition, operations.Applied.Select(x => x.NonGenericProcessor)); + } - public void Dispose() => this.image.Dispose(); + public void Dispose() => this.image.Dispose(); - [Fact] - public void GenericMutate_WhenDisposed_Throws() - { - this.image.Dispose(); + [Fact] + public void GenericMutate_WhenDisposed_Throws() + { + this.image.Dispose(); - CheckThrowsCorrectObjectDisposedException( - () => this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition))); - } + CheckThrowsCorrectObjectDisposedException( + () => this.image.Mutate(x => x.ApplyProcessor(this.processorDefinition))); + } - [Fact] - public void GenericClone_WhenDisposed_Throws() - { - this.image.Dispose(); + [Fact] + public void GenericClone_WhenDisposed_Throws() + { + this.image.Dispose(); - CheckThrowsCorrectObjectDisposedException( - () => this.image.Clone(x => x.ApplyProcessor(this.processorDefinition))); - } + CheckThrowsCorrectObjectDisposedException( + () => this.image.Clone(x => x.ApplyProcessor(this.processorDefinition))); + } - [Fact] - public void AgnosticMutate_WhenDisposed_Throws() - { - this.image.Dispose(); - Image img = this.image; + [Fact] + public void AgnosticMutate_WhenDisposed_Throws() + { + this.image.Dispose(); + Image img = this.image; - CheckThrowsCorrectObjectDisposedException( - () => img.Mutate(x => x.ApplyProcessor(this.processorDefinition))); - } + CheckThrowsCorrectObjectDisposedException( + () => img.Mutate(x => x.ApplyProcessor(this.processorDefinition))); + } - [Fact] - public void AgnosticClone_WhenDisposed_Throws() - { - this.image.Dispose(); - Image img = this.image; + [Fact] + public void AgnosticClone_WhenDisposed_Throws() + { + this.image.Dispose(); + Image img = this.image; - CheckThrowsCorrectObjectDisposedException( - () => img.Clone(x => x.ApplyProcessor(this.processorDefinition))); - } + CheckThrowsCorrectObjectDisposedException( + () => img.Clone(x => x.ApplyProcessor(this.processorDefinition))); + } - private static string GetExpectedExceptionText() + private static string GetExpectedExceptionText() + { + try { - try - { - var img = new Image(1, 1); - img.Dispose(); - img.EnsureNotDisposed(); - } - catch (ObjectDisposedException ex) - { - return ex.Message; - } - - return "?"; + var img = new Image(1, 1); + img.Dispose(); + img.EnsureNotDisposed(); } - - private static void CheckThrowsCorrectObjectDisposedException(Action action) + catch (ObjectDisposedException ex) { - ObjectDisposedException ex = Assert.Throws(action); - Assert.Equal(ExpectedExceptionMessage, ex.Message); + return ex.Message; } + + return "?"; + } + + private static void CheckThrowsCorrectObjectDisposedException(Action action) + { + ObjectDisposedException ex = Assert.Throws(action); + Assert.Equal(ExpectedExceptionMessage, ex.Message); } } diff --git a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs index 710cd70414..88707c60ff 100644 --- a/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs @@ -1,202 +1,199 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing +namespace SixLabors.ImageSharp.Tests.Processing; + +/// +/// Contains test cases for default implementation. +/// +public class ImageProcessingContextTests : IDisposable { - /// - /// Contains test cases for default implementation. - /// - public class ImageProcessingContextTests : IDisposable - { - private readonly Image image = new Image(10, 10); + private readonly Image image = new Image(10, 10); - private readonly Mock processorDefinition; + private readonly Mock processorDefinition; - private readonly Mock cloningProcessorDefinition; + private readonly Mock cloningProcessorDefinition; - private readonly Mock> regularProcessorImpl; + private readonly Mock> regularProcessorImpl; - private readonly Mock> cloningProcessorImpl; + private readonly Mock> cloningProcessorImpl; - private static readonly Rectangle Bounds = new Rectangle(3, 3, 5, 5); + private static readonly Rectangle Bounds = new Rectangle(3, 3, 5, 5); - public ImageProcessingContextTests() + public ImageProcessingContextTests() + { + this.processorDefinition = new Mock(); + this.cloningProcessorDefinition = new Mock(); + this.regularProcessorImpl = new Mock>(); + this.cloningProcessorImpl = new Mock>(); + } + + // bool throwException, bool useBounds + public static readonly TheoryData ProcessorTestData = new TheoryData() + { + { false, false }, + { false, true }, + { true, false }, + { true, true } + }; + + [Theory] + [MemberData(nameof(ProcessorTestData))] + public void Mutate_RegularProcessor(bool throwException, bool useBounds) + { + this.SetupRegularProcessor(throwException); + + if (throwException) { - this.processorDefinition = new Mock(); - this.cloningProcessorDefinition = new Mock(); - this.regularProcessorImpl = new Mock>(); - this.cloningProcessorImpl = new Mock>(); + Assert.Throws(() => this.MutateRegularApply(useBounds)); } - - // bool throwException, bool useBounds - public static readonly TheoryData ProcessorTestData = new TheoryData() + else { - { false, false }, - { false, true }, - { true, false }, - { true, true } - }; + this.MutateRegularApply(useBounds); + } - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Mutate_RegularProcessor(bool throwException, bool useBounds) - { - this.SetupRegularProcessor(throwException); + this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once()); + this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once()); + } - if (throwException) - { - Assert.Throws(() => this.MutateRegularApply(useBounds)); - } - else - { - this.MutateRegularApply(useBounds); - } + [Theory] + [MemberData(nameof(ProcessorTestData))] + public void Clone_RegularProcessor(bool throwException, bool useBounds) + { + this.SetupRegularProcessor(throwException); - this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once()); - this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once()); + if (throwException) + { + Assert.Throws(() => this.CloneRegularApply(useBounds)); } - - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Clone_RegularProcessor(bool throwException, bool useBounds) + else { - this.SetupRegularProcessor(throwException); + this.CloneRegularApply(useBounds); + } - if (throwException) - { - Assert.Throws(() => this.CloneRegularApply(useBounds)); - } - else - { - this.CloneRegularApply(useBounds); - } + this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once); + this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once); + } - this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once); - this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once); - } + [Theory] + [MemberData(nameof(ProcessorTestData))] + public void Mutate_CloningProcessor(bool throwException, bool useBounds) + { + this.SetupCloningProcessor(throwException); - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Mutate_CloningProcessor(bool throwException, bool useBounds) + if (throwException) { - this.SetupCloningProcessor(throwException); + Assert.Throws(() => this.MutateCloneApply(useBounds)); + } + else + { + this.MutateCloneApply(useBounds); + } - if (throwException) - { - Assert.Throws(() => this.MutateCloneApply(useBounds)); - } - else - { - this.MutateCloneApply(useBounds); - } + this.cloningProcessorImpl.Verify(p => p.Execute(), Times.Once()); + this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); + } - this.cloningProcessorImpl.Verify(p => p.Execute(), Times.Once()); - this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); - } + [Theory] + [MemberData(nameof(ProcessorTestData))] + public void Clone_CloningProcessor(bool throwException, bool useBounds) + { + this.SetupCloningProcessor(throwException); - [Theory] - [MemberData(nameof(ProcessorTestData))] - public void Clone_CloningProcessor(bool throwException, bool useBounds) + if (throwException) + { + Assert.Throws(() => this.CloneCloneApply(useBounds)); + } + else { - this.SetupCloningProcessor(throwException); + this.CloneCloneApply(useBounds); + } - if (throwException) - { - Assert.Throws(() => this.CloneCloneApply(useBounds)); - } - else - { - this.CloneCloneApply(useBounds); - } + this.cloningProcessorImpl.Verify(p => p.CloneAndExecute(), Times.Once()); + this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); + } - this.cloningProcessorImpl.Verify(p => p.CloneAndExecute(), Times.Once()); - this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); + private void MutateRegularApply(bool useBounds) + { + if (useBounds) + { + this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)); } - - private void MutateRegularApply(bool useBounds) + else { - if (useBounds) - { - this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)); - } - else - { - this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object)); - } + this.image.Mutate(c => c.ApplyProcessor(this.processorDefinition.Object)); } + } - private void MutateCloneApply(bool useBounds) + private void MutateCloneApply(bool useBounds) + { + if (useBounds) { - if (useBounds) - { - this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)); - } - else - { - this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)); - } + this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)); } - - private void CloneRegularApply(bool useBounds) + else { - if (useBounds) - { - this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)).Dispose(); - } - else - { - this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object)).Dispose(); - } + this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)); } + } - private void CloneCloneApply(bool useBounds) + private void CloneRegularApply(bool useBounds) + { + if (useBounds) { - if (useBounds) - { - this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)).Dispose(); - } - else - { - this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)).Dispose(); - } + this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object, Bounds)).Dispose(); } - - private void SetupRegularProcessor(bool throwsException) + else { - if (throwsException) - { - this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); - } + this.image.Clone(c => c.ApplyProcessor(this.processorDefinition.Object)).Dispose(); + } + } - this.processorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.regularProcessorImpl.Object); + private void CloneCloneApply(bool useBounds) + { + if (useBounds) + { + this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)).Dispose(); } + else + { + this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)).Dispose(); + } + } - private void SetupCloningProcessor(bool throwsException) + private void SetupRegularProcessor(bool throwsException) + { + if (throwsException) { - if (throwsException) - { - this.cloningProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); - this.cloningProcessorImpl.Setup(p => p.CloneAndExecute()).Throws(new ImageProcessingException("Test")); - } + this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); + } - this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificCloningProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.cloningProcessorImpl.Object); + this.processorDefinition + .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) + .Returns(this.regularProcessorImpl.Object); + } - this.cloningProcessorDefinition - .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) - .Returns(this.cloningProcessorImpl.Object); + private void SetupCloningProcessor(bool throwsException) + { + if (throwsException) + { + this.cloningProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test")); + this.cloningProcessorImpl.Setup(p => p.CloneAndExecute()).Throws(new ImageProcessingException("Test")); } - public void Dispose() => this.image?.Dispose(); + this.cloningProcessorDefinition + .Setup(p => p.CreatePixelSpecificCloningProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) + .Returns(this.cloningProcessorImpl.Object); + + this.cloningProcessorDefinition + .Setup(p => p.CreatePixelSpecificProcessor(Configuration.Default, It.IsAny>(), It.IsAny())) + .Returns(this.cloningProcessorImpl.Object); } + + public void Dispose() => this.image?.Dispose(); } diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs index 869f577f79..cd61d68be5 100644 --- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs +++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs @@ -4,155 +4,153 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing +namespace SixLabors.ImageSharp.Tests.Processing; + +public class IntegralImageTests : BaseImageOperationsExtensionTest { - public class IntegralImageTests : BaseImageOperationsExtensionTest + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + public void CalculateIntegralImage_Rgba32Works(TestImageProvider provider) { - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - public void CalculateIntegralImage_Rgba32Works(TestImageProvider provider) + using Image image = provider.GetImage(); + + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(); + + // Assert: + VerifySumValues(provider, integralBuffer, (Rgba32 pixel) => { - using Image image = provider.GetImage(); + L8 outputPixel = default; + + outputPixel.FromRgba32(pixel); - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(); + return outputPixel.PackedValue; + }); + } - // Assert: - VerifySumValues(provider, integralBuffer, (Rgba32 pixel) => - { - L8 outputPixel = default; + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + public void CalculateIntegralImage_WithBounds_Rgba32Works(TestImageProvider provider) + { + using Image image = provider.GetImage(); - outputPixel.FromRgba32(pixel); + Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - return outputPixel.PackedValue; - }); - } + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(interest); - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] - public void CalculateIntegralImage_WithBounds_Rgba32Works(TestImageProvider provider) + // Assert: + VerifySumValues(provider, integralBuffer, interest, (Rgba32 pixel) => { - using Image image = provider.GetImage(); + L8 outputPixel = default; - Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + outputPixel.FromRgba32(pixel); + + return outputPixel.PackedValue; + }); + } - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(interest); + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] + public void CalculateIntegralImage_L8Works(TestImageProvider provider) + { + using Image image = provider.GetImage(); - // Assert: - VerifySumValues(provider, integralBuffer, interest, (Rgba32 pixel) => - { - L8 outputPixel = default; + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(); - outputPixel.FromRgba32(pixel); + // Assert: + VerifySumValues(provider, integralBuffer, (L8 pixel) => pixel.PackedValue); + } - return outputPixel.PackedValue; - }); - } + [Theory] + [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] + [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] + public void CalculateIntegralImage_WithBounds_L8Works(TestImageProvider provider) + { + using Image image = provider.GetImage(); - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] - public void CalculateIntegralImage_L8Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); + Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(); + // Act: + Buffer2D integralBuffer = image.CalculateIntegralImage(interest); - // Assert: - VerifySumValues(provider, integralBuffer, (L8 pixel) => pixel.PackedValue); - } + // Assert: + VerifySumValues(provider, integralBuffer, interest, (L8 pixel) => pixel.PackedValue); + } - [Theory] - [WithFile(TestImages.Png.Bradley01, PixelTypes.L8)] - [WithFile(TestImages.Png.Bradley02, PixelTypes.L8)] - public void CalculateIntegralImage_WithBounds_L8Works(TestImageProvider provider) - { - using Image image = provider.GetImage(); - - Rectangle interest = new(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - // Act: - Buffer2D integralBuffer = image.CalculateIntegralImage(interest); - - // Assert: - VerifySumValues(provider, integralBuffer, interest, (L8 pixel) => pixel.PackedValue); - } - - private static void VerifySumValues( - TestImageProvider provider, - Buffer2D integralBuffer, - System.Func getPixel) - where TPixel : unmanaged, IPixel - => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); - - private static void VerifySumValues( - TestImageProvider provider, - Buffer2D integralBuffer, - Rectangle bounds, - System.Func getPixel) - where TPixel : unmanaged, IPixel - { - Buffer2DRegion image = provider.GetImage().GetRootFramePixelBuffer().GetRegion(bounds); + private static void VerifySumValues( + TestImageProvider provider, + Buffer2D integralBuffer, + System.Func getPixel) + where TPixel : unmanaged, IPixel + => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); + + private static void VerifySumValues( + TestImageProvider provider, + Buffer2D integralBuffer, + Rectangle bounds, + System.Func getPixel) + where TPixel : unmanaged, IPixel + { + Buffer2DRegion image = provider.GetImage().GetRootFramePixelBuffer().GetRegion(bounds); - // Check top-left corner - Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]); + // Check top-left corner + Assert.Equal(getPixel(image[0, 0]), integralBuffer[0, 0]); - ulong pixelValues = 0; + ulong pixelValues = 0; - pixelValues += getPixel(image[0, 0]); - pixelValues += getPixel(image[1, 0]); - pixelValues += getPixel(image[0, 1]); - pixelValues += getPixel(image[1, 1]); + pixelValues += getPixel(image[0, 0]); + pixelValues += getPixel(image[1, 0]); + pixelValues += getPixel(image[0, 1]); + pixelValues += getPixel(image[1, 1]); - // Check top-left 2x2 pixels - Assert.Equal(pixelValues, integralBuffer[1, 1]); + // Check top-left 2x2 pixels + Assert.Equal(pixelValues, integralBuffer[1, 1]); - pixelValues = 0; + pixelValues = 0; - pixelValues += getPixel(image[image.Width - 3, 0]); - pixelValues += getPixel(image[image.Width - 2, 0]); - pixelValues += getPixel(image[image.Width - 1, 0]); - pixelValues += getPixel(image[image.Width - 3, 1]); - pixelValues += getPixel(image[image.Width - 2, 1]); - pixelValues += getPixel(image[image.Width - 1, 1]); + pixelValues += getPixel(image[image.Width - 3, 0]); + pixelValues += getPixel(image[image.Width - 2, 0]); + pixelValues += getPixel(image[image.Width - 1, 0]); + pixelValues += getPixel(image[image.Width - 3, 1]); + pixelValues += getPixel(image[image.Width - 2, 1]); + pixelValues += getPixel(image[image.Width - 1, 1]); - // Check top-right 3x2 pixels - Assert.Equal(pixelValues, integralBuffer[image.Width - 1, 1] + 0 - 0 - integralBuffer[image.Width - 4, 1]); + // Check top-right 3x2 pixels + Assert.Equal(pixelValues, integralBuffer[image.Width - 1, 1] + 0 - 0 - integralBuffer[image.Width - 4, 1]); - pixelValues = 0; + pixelValues = 0; - pixelValues += getPixel(image[0, image.Height - 3]); - pixelValues += getPixel(image[0, image.Height - 2]); - pixelValues += getPixel(image[0, image.Height - 1]); - pixelValues += getPixel(image[1, image.Height - 3]); - pixelValues += getPixel(image[1, image.Height - 2]); - pixelValues += getPixel(image[1, image.Height - 1]); + pixelValues += getPixel(image[0, image.Height - 3]); + pixelValues += getPixel(image[0, image.Height - 2]); + pixelValues += getPixel(image[0, image.Height - 1]); + pixelValues += getPixel(image[1, image.Height - 3]); + pixelValues += getPixel(image[1, image.Height - 2]); + pixelValues += getPixel(image[1, image.Height - 1]); - // Check bottom-left 2x3 pixels - Assert.Equal(pixelValues, integralBuffer[1, image.Height - 1] + 0 - integralBuffer[1, image.Height - 4] - 0); + // Check bottom-left 2x3 pixels + Assert.Equal(pixelValues, integralBuffer[1, image.Height - 1] + 0 - integralBuffer[1, image.Height - 4] - 0); - pixelValues = 0; + pixelValues = 0; - pixelValues += getPixel(image[image.Width - 3, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 3]); - pixelValues += getPixel(image[image.Width - 3, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 2]); - pixelValues += getPixel(image[image.Width - 3, image.Height - 1]); - pixelValues += getPixel(image[image.Width - 2, image.Height - 1]); - pixelValues += getPixel(image[image.Width - 1, image.Height - 1]); + pixelValues += getPixel(image[image.Width - 3, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 3]); + pixelValues += getPixel(image[image.Width - 3, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 2]); + pixelValues += getPixel(image[image.Width - 3, image.Height - 1]); + pixelValues += getPixel(image[image.Width - 2, image.Height - 1]); + pixelValues += getPixel(image[image.Width - 1, image.Height - 1]); - // Check bottom-right 3x3 pixels - Assert.Equal(pixelValues, integralBuffer[image.Width - 1, image.Height - 1] + integralBuffer[image.Width - 4, image.Height - 4] - integralBuffer[image.Width - 1, image.Height - 4] - integralBuffer[image.Width - 4, image.Height - 1]); - } + // Check bottom-right 3x3 pixels + Assert.Equal(pixelValues, integralBuffer[image.Width - 1, image.Height - 1] + integralBuffer[image.Width - 4, image.Height - 4] - integralBuffer[image.Width - 1, image.Height - 4] - integralBuffer[image.Width - 4, image.Height - 1]); } } diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 4420b2769d..09ba486a6f 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -5,201 +5,199 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Normalization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Normalization +namespace SixLabors.ImageSharp.Tests.Processing.Normalization; + +// ReSharper disable InconsistentNaming +[Trait("Category", "Processors")] +public class HistogramEqualizationTests { - // ReSharper disable InconsistentNaming - [Trait("Category", "Processors")] - public class HistogramEqualizationTests - { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); - [Theory] - [InlineData(256)] - [InlineData(65536)] - public void GlobalHistogramEqualization_WithDifferentLuminanceLevels(int luminanceLevels) + [Theory] + [InlineData(256)] + [InlineData(65536)] + public void GlobalHistogramEqualization_WithDifferentLuminanceLevels(int luminanceLevels) + { + // Arrange + byte[] pixels = { - // Arrange - byte[] pixels = - { - 52, 55, 61, 59, 70, 61, 76, 61, - 62, 59, 55, 104, 94, 85, 59, 71, - 63, 65, 66, 113, 144, 104, 63, 72, - 64, 70, 70, 126, 154, 109, 71, 69, - 67, 73, 68, 106, 122, 88, 68, 68, - 68, 79, 60, 79, 77, 66, 58, 75, - 69, 85, 64, 58, 55, 61, 65, 83, - 70, 87, 69, 68, 65, 73, 78, 90 - }; - - using (var image = new Image(8, 8)) + 52, 55, 61, 59, 70, 61, 76, 61, + 62, 59, 55, 104, 94, 85, 59, 71, + 63, 65, 66, 113, 144, 104, 63, 72, + 64, 70, 70, 126, 154, 109, 71, 69, + 67, 73, 68, 106, 122, 88, 68, 68, + 68, 79, 60, 79, 77, 66, 58, 75, + 69, 85, 64, 58, 55, 61, 65, 83, + 70, 87, 69, 68, 65, 73, 78, 90 + }; + + using (var image = new Image(8, 8)) + { + for (int y = 0; y < 8; y++) { - for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) { - for (int x = 0; x < 8; x++) - { - byte luminance = pixels[(y * 8) + x]; - image[x, y] = new Rgba32(luminance, luminance, luminance); - } + byte luminance = pixels[(y * 8) + x]; + image[x, y] = new Rgba32(luminance, luminance, luminance); } + } - byte[] expected = - { - 0, 12, 53, 32, 146, 53, 174, 53, - 57, 32, 12, 227, 219, 202, 32, 154, - 65, 85, 93, 239, 251, 227, 65, 158, - 73, 146, 146, 247, 255, 235, 154, 130, - 97, 166, 117, 231, 243, 210, 117, 117, - 117, 190, 36, 190, 178, 93, 20, 170, - 130, 202, 73, 20, 12, 53, 85, 194, - 146, 206, 130, 117, 85, 166, 182, 215 - }; - - // Act - image.Mutate(x => x.HistogramEqualization(new HistogramEqualizationOptions - { - LuminanceLevels = luminanceLevels, - Method = HistogramEqualizationMethod.Global - })); + byte[] expected = + { + 0, 12, 53, 32, 146, 53, 174, 53, + 57, 32, 12, 227, 219, 202, 32, 154, + 65, 85, 93, 239, 251, 227, 65, 158, + 73, 146, 146, 247, 255, 235, 154, 130, + 97, 166, 117, 231, 243, 210, 117, 117, + 117, 190, 36, 190, 178, 93, 20, 170, + 130, 202, 73, 20, 12, 53, 85, 194, + 146, 206, 130, 117, 85, 166, 182, 215 + }; - // Assert - for (int y = 0; y < 8; y++) - { - for (int x = 0; x < 8; x++) - { - Rgba32 actual = image[x, y]; - Assert.Equal(expected[(y * 8) + x], actual.R); - Assert.Equal(expected[(y * 8) + x], actual.G); - Assert.Equal(expected[(y * 8) + x], actual.B); - } - } - } - } + // Act + image.Mutate(x => x.HistogramEqualization(new HistogramEqualizationOptions + { + LuminanceLevels = luminanceLevels, + Method = HistogramEqualizationMethod.Global + })); - [Theory] - [WithFile(TestImages.Jpeg.Baseline.HistogramEqImage, PixelTypes.Rgba32)] - public void GlobalHistogramEqualization_CompareToReferenceOutput(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image image = provider.GetImage()) + // Assert + for (int y = 0; y < 8; y++) { - var options = new HistogramEqualizationOptions + for (int x = 0; x < 8; x++) { - Method = HistogramEqualizationMethod.Global, - LuminanceLevels = 256, - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png"); + Rgba32 actual = image[x, y]; + Assert.Equal(expected[(y * 8) + x], actual.R); + Assert.Equal(expected[(y * 8) + x], actual.G); + Assert.Equal(expected[(y * 8) + x], actual.B); + } } } + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] - public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.HistogramEqImage, PixelTypes.Rgba32)] + public void GlobalHistogramEqualization_CompareToReferenceOutput(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new HistogramEqualizationOptions { - var options = new HistogramEqualizationOptions - { - Method = HistogramEqualizationMethod.AdaptiveSlidingWindow, - LuminanceLevels = 256, - ClipHistogram = true, - NumberOfTiles = 15 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + Method = HistogramEqualizationMethod.Global, + LuminanceLevels = 256, + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, extension: "png"); } + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] - public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] + public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new HistogramEqualizationOptions { - var options = new HistogramEqualizationOptions - { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 256, - ClipHistogram = true, - NumberOfTiles = 10 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + Method = HistogramEqualizationMethod.AdaptiveSlidingWindow, + LuminanceLevels = 256, + ClipHistogram = true, + NumberOfTiles = 15 + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } + } - /// - /// This is regression test for a bug with the calculation of the y-start positions, - /// 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. - /// The test image provider. - [Theory] - [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] - [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] - public void Issue984(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] + public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new HistogramEqualizationOptions { - var options = new HistogramEqualizationOptions() - { - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 256, - ClipHistogram = true, - ClipLimit = 5, - NumberOfTiles = 10 - }; - image.Mutate(x => x.HistogramEqualization(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 256, + ClipHistogram = true, + NumberOfTiles = 10 + }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } + } - [Theory] - [WithTestPatternImages(5120, 9234, PixelTypes.L16)] - public unsafe void Issue1640(TestImageProvider provider) - where TPixel : unmanaged, IPixel + /// + /// This is regression test for a bug with the calculation of the y-start positions, + /// 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. + /// The test image provider. + [Theory] + [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] + [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] + public void Issue984(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - // https://github.com/SixLabors/ImageSharp/discussions/1640 - // Test using isolated memory to ensure clean buffers for reference - provider.Configuration = Configuration.CreateDefaultInstance(); - var options = new HistogramEqualizationOptions + var options = new HistogramEqualizationOptions() { Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, - LuminanceLevels = 4096, - ClipHistogram = false, - ClipLimit = 350, - NumberOfTiles = 8 + LuminanceLevels = 256, + ClipHistogram = true, + ClipLimit = 5, + NumberOfTiles = 10 }; + image.Mutate(x => x.HistogramEqualization(options)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + } - using Image image = provider.GetImage(); - using Image referenceResult = image.Clone(ctx => - { - ctx.HistogramEqualization(options); - ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); - }); + [Theory] + [WithTestPatternImages(5120, 9234, PixelTypes.L16)] + public unsafe void Issue1640(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } - using Image processed = image.Clone(ctx => - { - ctx.HistogramEqualization(options); - ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); - }); + // https://github.com/SixLabors/ImageSharp/discussions/1640 + // Test using isolated memory to ensure clean buffers for reference + provider.Configuration = Configuration.CreateDefaultInstance(); + var options = new HistogramEqualizationOptions + { + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation, + LuminanceLevels = 4096, + ClipHistogram = false, + ClipLimit = 350, + NumberOfTiles = 8 + }; + + using Image image = provider.GetImage(); + using Image referenceResult = image.Clone(ctx => + { + ctx.HistogramEqualization(options); + ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); + }); - ValidatorComparer.VerifySimilarity(referenceResult, processed); - } + using Image processed = image.Clone(ctx => + { + ctx.HistogramEqualization(options); + ctx.Resize(image.Width / 4, image.Height / 4, KnownResamplers.Bicubic); + }); + + ValidatorComparer.VerifySimilarity(referenceResult, processed); } } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index 1f511bb4bb..7eec6eb0c1 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -3,56 +3,54 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Overlays +namespace SixLabors.ImageSharp.Tests.Processing.Overlays; + +[Trait("Category", "Processors")] +public class GlowTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class GlowTest : BaseImageOperationsExtensionTest + [Fact] + public void Glow_GlowProcessorWithDefaultValues() + { + this.operations.Glow(); + GlowProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Black, p.GlowColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + } + + [Fact] + public void Glow_Color_GlowProcessorWithDefaultValues() + { + this.operations.Glow(Color.Aquamarine); + GlowProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Aquamarine, p.GlowColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); + } + + [Fact] + public void Glow_Radux_GlowProcessorWithDefaultValues() { - [Fact] - public void Glow_GlowProcessorWithDefaultValues() - { - this.operations.Glow(); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); - } - - [Fact] - public void Glow_Color_GlowProcessorWithDefaultValues() - { - this.operations.Glow(Color.Aquamarine); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Aquamarine, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); - } - - [Fact] - public void Glow_Radux_GlowProcessorWithDefaultValues() - { - this.operations.Glow(3.5f); - GlowProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); - } - - [Fact] - public void Glow_Rect_GlowProcessorWithDefaultValues() - { - var rect = new Rectangle(12, 123, 43, 65); - this.operations.Glow(rect); - GlowProcessor p = this.Verify(rect); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.GlowColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); - } + this.operations.Glow(3.5f); + GlowProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Black, p.GlowColor); + Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); + } + + [Fact] + public void Glow_Rect_GlowProcessorWithDefaultValues() + { + var rect = new Rectangle(12, 123, 43, 65); + this.operations.Glow(rect); + GlowProcessor p = this.Verify(rect); + + Assert.Equal(this.options, p.GraphicsOptions); + 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 472859b1fa..1602b5c7a2 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -3,60 +3,58 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Overlays; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Overlays +namespace SixLabors.ImageSharp.Tests.Processing.Overlays; + +[Trait("Category", "Processors")] +public class VignetteTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class VignetteTest : BaseImageOperationsExtensionTest + [Fact] + public void Vignette_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(); + VignetteProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Black, p.VignetteColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); + Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + } + + [Fact] + public void Vignette_Color_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(Color.Aquamarine); + VignetteProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Aquamarine, p.VignetteColor); + Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); + Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); + } + + [Fact] + public void Vignette_Radux_VignetteProcessorWithDefaultValues() + { + this.operations.Vignette(3.5f, 12123f); + VignetteProcessor p = this.Verify(); + + Assert.Equal(this.options, p.GraphicsOptions); + Assert.Equal(Color.Black, p.VignetteColor); + Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); + Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); + } + + [Fact] + public void Vignette_Rect_VignetteProcessorWithDefaultValues() { - [Fact] - public void Vignette_VignetteProcessorWithDefaultValues() - { - this.operations.Vignette(); - VignetteProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); - } - - [Fact] - public void Vignette_Color_VignetteProcessorWithDefaultValues() - { - this.operations.Vignette(Color.Aquamarine); - VignetteProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Aquamarine, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); - } - - [Fact] - public void Vignette_Radux_VignetteProcessorWithDefaultValues() - { - this.operations.Vignette(3.5f, 12123f); - VignetteProcessor p = this.Verify(); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); - Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); - } - - [Fact] - public void Vignette_Rect_VignetteProcessorWithDefaultValues() - { - var rect = new Rectangle(12, 123, 43, 65); - this.operations.Vignette(rect); - VignetteProcessor p = this.Verify(rect); - - Assert.Equal(this.options, p.GraphicsOptions); - Assert.Equal(Color.Black, p.VignetteColor); - Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); - Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); - } + var rect = new Rectangle(12, 123, 43, 65); + this.operations.Vignette(rect); + VignetteProcessor p = this.Verify(rect); + + Assert.Equal(this.options, p.GraphicsOptions); + 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 330d7469cb..9694aeb222 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -6,128 +6,125 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization -{ - [Trait("Category", "Processors")] - public class BinaryDitherTests - { - public static readonly string[] CommonTestImages = - { - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike - }; - - public static readonly TheoryData OrderedDitherers = new TheoryData - { - { "Bayer8x8", KnownDitherings.Bayer8x8 }, - { "Bayer4x4", KnownDitherings.Bayer4x4 }, - { "Ordered3x3", KnownDitherings.Ordered3x3 }, - { "Bayer2x2", KnownDitherings.Bayer2x2 } - }; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization; - public static readonly TheoryData ErrorDiffusers = new TheoryData +[Trait("Category", "Processors")] +public class BinaryDitherTests +{ + public static readonly string[] CommonTestImages = { - { "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 }, + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - - private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - - private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; + public static readonly TheoryData OrderedDitherers = new TheoryData + { + { "Bayer8x8", KnownDitherings.Bayer8x8 }, + { "Bayer4x4", KnownDitherings.Bayer4x4 }, + { "Ordered3x3", KnownDitherings.Ordered3x3 }, + { "Bayer2x2", KnownDitherings.Bayer2x2 } + }; - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, PixelTypes.Rgba32)] - public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IDither ditherer) - where TPixel : unmanaged, IPixel + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "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 IDither DefaultDitherer => KnownDitherings.Bayer4x4; + + 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, IDither ditherer) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryDither(ditherer)); - image.DebugSave(provider, name); - } + image.Mutate(x => x.BinaryDither(ditherer)); + image.DebugSave(provider, name); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IDither diffuser) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IDither diffuser) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryDither(diffuser)); - image.DebugSave(provider, name); - } + image.Mutate(x => x.BinaryDither(diffuser)); + image.DebugSave(provider, name); } + } - [Theory] - [WithFile(TestImages.Png.Bike, TestPixelTypes)] - public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Bike, TestPixelTypes)] + public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryDither(DefaultDitherer)); - image.DebugSave(provider); - } + image.Mutate(x => x.BinaryDither(DefaultDitherer)); + image.DebugSave(provider); } + } - [Theory] - [WithFile(TestImages.Png.Bike, TestPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.Bike, TestPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); - image.DebugSave(provider); - } + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); + image.DebugSave(provider); } + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); - image.DebugSave(provider); + image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); + image.DebugSave(provider); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); - image.DebugSave(provider); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); + image.DebugSave(provider); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index d95ae3f070..ecbbb21abd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -5,130 +5,128 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization; + +[Trait("Category", "Processors")] +public class BinaryThresholdTest { - [Trait("Category", "Processors")] - public class BinaryThresholdTest + public static readonly TheoryData BinaryThresholdValues + = new TheoryData { - public static readonly TheoryData BinaryThresholdValues - = new TheoryData - { - .25F, - .75F - }; + .25F, + .75F + }; - public static readonly string[] CommonTestImages = - { - TestImages.Png.Rgb48Bpp, - TestImages.Png.ColorsSaturationLightness, - }; + public static readonly string[] CommonTestImages = + { + TestImages.Png.Rgb48Bpp, + TestImages.Png.ColorsSaturationLightness, + }; - public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; + 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 : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryThreshold(value)); - image.DebugSave(provider, value); - } + image.Mutate(x => x.BinaryThreshold(value)); + image.DebugSave(provider, value); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); + var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - image.Mutate(x => x.BinaryThreshold(value, bounds)); - image.DebugSave(provider, value); + image.Mutate(x => x.BinaryThreshold(value, bounds)); + image.DebugSave(provider, value); - ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); - } + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinarySaturationThresholdFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinarySaturationThresholdFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation)); - image.DebugSave(provider, value); - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } + image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation)); + image.DebugSave(provider, value); + image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinarySaturationThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinarySaturationThresholdInBox(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) - { - var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); + var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation, bounds)); - image.DebugSave(provider, value); - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } + image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.Saturation, bounds)); + image.DebugSave(provider, value); + image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryMaxChromaThresholdFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinaryMaxChromaThresholdFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma)); + image.DebugSave(provider, value); + + if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) { - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma)); - image.DebugSave(provider, value); - - if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) - { - var comparer = ImageComparer.TolerantPercentage(0.0004F); - image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - else - { - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } + var comparer = ImageComparer.TolerantPercentage(0.0004F); + image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); + } + else + { + image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); } } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] - public void ImageShouldApplyBinaryMaxChromaThresholdInBox(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] + public void ImageShouldApplyBinaryMaxChromaThresholdInBox(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) { - using (Image source = provider.GetImage()) - using (Image image = source.Clone()) + var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); + + image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma, bounds)); + image.DebugSave(provider, value); + + if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) + { + var comparer = ImageComparer.TolerantPercentage(0.0004F); + image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); + } + else { - var bounds = new Rectangle(image.Width / 8, image.Height / 8, 6 * image.Width / 8, 6 * image.Width / 8); - - image.Mutate(x => x.BinaryThreshold(value, BinaryThresholdMode.MaxChroma, bounds)); - image.DebugSave(provider, value); - - if (!TestEnvironment.Is64BitProcess && TestEnvironment.IsFramework) - { - var comparer = ImageComparer.TolerantPercentage(0.0004F); - image.CompareToReferenceOutput(comparer, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } - else - { - image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); - } + image.CompareToReferenceOutput(ImageComparer.Exact, provider, value.ToString("0.00", NumberFormatInfo.InvariantInfo)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs index 4c8553ef0d..13ddf85c1e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -5,50 +5,47 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +[GroupOutput("Convolution")] +public abstract class Basic1ParameterConvolutionTests { - [GroupOutput("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, + TestImages.Png.CalliphoraPartial, + TestImages.Png.Blur + }; + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void OnFullImage(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => this.Apply(x, value), + value, + ValidatorComparer); + } + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel { - 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, - TestImages.Png.CalliphoraPartial, - TestImages.Png.Blur - }; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void OnFullImage(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunValidatingProcessorTest( - x => this.Apply(x, value), - value, - ValidatorComparer); - } - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => this.Apply(x, value, rect), - value, - ValidatorComparer); - } - - protected abstract void Apply(IImageProcessingContext ctx, int value); - - protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => this.Apply(x, value, rect), + value, + ValidatorComparer); } + + protected abstract void Apply(IImageProcessingContext ctx, int value); + + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index 9edb5f3109..175e31633e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text.RegularExpressions; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -12,15 +9,14 @@ using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +public class BokehBlurTest { - [Trait("Category", "Processors")] - public class BokehBlurTest - { - private static readonly string Components10x2 = @" + 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 0.01747482+0.04687464j 0.04243676+0.03451751j 0.05564306+0.01742537j @@ -36,160 +32,159 @@ [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j 0.02565295+0.01611732j 0.0153483+0.01605112j 0.00698622+0.01370844j 0.00135338+0.00998296j -0.00152245+0.00604545j -0.00227282+0.002851j ]]"; - [Theory] - [InlineData(-10, 2, 3f)] - [InlineData(-1, 2, 3f)] - [InlineData(0, 2, 3f)] - [InlineData(20, -1, 3f)] - [InlineData(20, -0, 3f)] - [InlineData(20, 4, -10f)] - [InlineData(20, 4, 0f)] - public void VerifyBokehBlurProcessorArguments_Fail(int radius, int components, float gamma) - => Assert.Throws( - () => new BokehBlurProcessor(radius, components, gamma)); - - [Fact] - public void VerifyComplexComponents() + [Theory] + [InlineData(-10, 2, 3f)] + [InlineData(-1, 2, 3f)] + [InlineData(0, 2, 3f)] + [InlineData(20, -1, 3f)] + [InlineData(20, -0, 3f)] + [InlineData(20, 4, -10f)] + [InlineData(20, 4, 0f)] + public void VerifyBokehBlurProcessorArguments_Fail(int radius, int components, float gamma) + => Assert.Throws( + () => new BokehBlurProcessor(radius, components, gamma)); + + [Fact] + public void VerifyComplexComponents() + { + // Get the saved components + var components = new List(); + foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline)) { - // Get the saved components - var components = new List(); - foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline)) - { - string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - Complex64[] component = values.Select( - value => - { - Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); - return new Complex64( - float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), - float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); - }).ToArray(); - components.Add(component); - } + string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Complex64[] component = values.Select( + value => + { + Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); + return new Complex64( + float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), + float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); + }).ToArray(); + components.Add(component); + } - // Make sure the kernel components are the same - using (var image = new Image(1, 1)) + // Make sure the kernel components are the same + using (var image = new Image(1, 1)) + { + Configuration configuration = image.GetConfiguration(); + var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); + using (var processor = (BokehBlurProcessor)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds())) { - Configuration configuration = image.GetConfiguration(); - var definition = new BokehBlurProcessor(10, BokehBlurProcessor.DefaultComponents, BokehBlurProcessor.DefaultGamma); - using (var processor = (BokehBlurProcessor)definition.CreatePixelSpecificProcessor(configuration, image, image.Bounds())) + Assert.Equal(components.Count, processor.Kernels.Count); + foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b))) { - Assert.Equal(components.Count, processor.Kernels.Count); - foreach ((Complex64[] a, Complex64[] b) in components.Zip(processor.Kernels, (a, b) => (a, b))) + Span spanA = a.AsSpan(), spanB = b.AsSpan(); + Assert.Equal(spanA.Length, spanB.Length); + for (int i = 0; i < spanA.Length; i++) { - Span spanA = a.AsSpan(), spanB = b.AsSpan(); - Assert.Equal(spanA.Length, spanB.Length); - for (int i = 0; i < spanA.Length; i++) - { - Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f); - Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f); - } + Assert.True(Math.Abs(Math.Abs(spanA[i].Real) - Math.Abs(spanB[i].Real)) < 0.0001f); + Assert.True(Math.Abs(Math.Abs(spanA[i].Imaginary) - Math.Abs(spanB[i].Imaginary)) < 0.0001f); } } } } + } - public sealed class BokehBlurInfo : IXunitSerializable - { - public int Radius { get; set; } - - public int Components { get; set; } + public sealed class BokehBlurInfo : IXunitSerializable + { + public int Radius { get; set; } - public float Gamma { get; set; } + public int Components { get; set; } - public void Deserialize(IXunitSerializationInfo info) - { - this.Radius = info.GetValue(nameof(this.Radius)); - this.Components = info.GetValue(nameof(this.Components)); - this.Gamma = info.GetValue(nameof(this.Gamma)); - } + public float Gamma { get; set; } - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue(nameof(this.Radius), this.Radius, typeof(int)); - info.AddValue(nameof(this.Components), this.Components, typeof(int)); - info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float)); - } + public void Deserialize(IXunitSerializationInfo info) + { + this.Radius = info.GetValue(nameof(this.Radius)); + this.Components = info.GetValue(nameof(this.Components)); + this.Gamma = info.GetValue(nameof(this.Gamma)); + } - public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}"; + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(this.Radius), this.Radius, typeof(int)); + info.AddValue(nameof(this.Components), this.Components, typeof(int)); + info.AddValue(nameof(this.Gamma), this.Gamma, typeof(float)); } - public static readonly TheoryData BokehBlurValues = new TheoryData + public override string ToString() => $"R{this.Radius}_C{this.Components}_G{this.Gamma}"; + } + + public static readonly TheoryData BokehBlurValues = new TheoryData + { + new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 }, + new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 }, + new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 } + }; + + public static readonly string[] TestFiles = { - new BokehBlurInfo { Radius = 8, Components = 1, Gamma = 1 }, - new BokehBlurInfo { Radius = 16, Components = 1, Gamma = 3 }, - new BokehBlurInfo { Radius = 16, Components = 2, Gamma = 3 } + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike, + TestImages.Png.BikeGrayscale, + TestImages.Png.Cross, }; - public static readonly string[] TestFiles = - { - TestImages.Png.CalliphoraPartial, - TestImages.Png.Bike, - TestImages.Png.BikeGrayscale, - TestImages.Png.Cross, - }; - - [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] - [WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] - public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) - where TPixel : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => x.BokehBlur(value.Radius, value.Components, value.Gamma), - testOutputDetails: value.ToString(), - appendPixelTypeToFileName: false); - - [Theory] - /* - 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 : unmanaged, IPixel - => provider.RunValidatingProcessorTest( - x => x.BokehBlur(8, 2, 3), - appendSourceFileOrDescription: false); - - [Theory] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] - public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) + [Theory] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BokehBlurValues), 50, 50, "Red", PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 200, 100, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] + public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) + where TPixel : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + x => x.BokehBlur(value.Radius, value.Components, value.Gamma), + testOutputDetails: value.ToString(), + appendPixelTypeToFileName: false); + + [Theory] + /* + 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 : unmanaged, IPixel + => provider.RunValidatingProcessorTest( + x => x.BokehBlur(8, 2, 3), + appendSourceFileOrDescription: false); + + [Theory] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] + public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) + { + static void RunTest(string arg1, string arg2) { - static void RunTest(string arg1, string arg2) - { - TestImageProvider provider = - FeatureTestRunner.DeserializeForXunit>(arg1); + TestImageProvider provider = + FeatureTestRunner.DeserializeForXunit>(arg1); - BokehBlurInfo value = - FeatureTestRunner.DeserializeForXunit(arg2); + BokehBlurInfo value = + FeatureTestRunner.DeserializeForXunit(arg2); - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); - x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); - }, - testOutputDetails: value.ToString(), - ImageComparer.TolerantPercentage(0.05f), - appendPixelTypeToFileName: false); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - intrinsicsFilter, - provider, - value); + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); + x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); + }, + testOutputDetails: value.ToString(), + ImageComparer.TolerantPercentage(0.05f), + appendPixelTypeToFileName: false); } - [Theory] - [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] - public void WorksWithDiscoBuffers(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => provider.RunBufferCapacityLimitProcessorTest(260, c => c.BokehBlur()); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + intrinsicsFilter, + provider, + value); } + + [Theory] + [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => provider.RunBufferCapacityLimitProcessorTest(260, c => c.BokehBlur()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs index 0c923681c3..2b166c5b9b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BoxBlurTest.cs @@ -2,17 +2,15 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class BoxBlurTest : Basic1ParameterConvolutionTests { - [Trait("Category", "Processors")] - [GroupOutput("Convolution")] - public class BoxBlurTest : Basic1ParameterConvolutionTests - { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.BoxBlur(value); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.BoxBlur(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.BoxBlur(value, bounds); - } + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.BoxBlur(value, bounds); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 02bdb82678..d51012f9ec 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,213 +1,210 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class DetectEdgesTest { - [Trait("Category", "Processors")] - [GroupOutput("Convolution")] - public class DetectEdgesTest - { - private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); + private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); - private static readonly ImageComparer TransparentComparer = ImageComparer.TolerantPercentage(0.5F); + private static readonly ImageComparer TransparentComparer = ImageComparer.TolerantPercentage(0.5F); - public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; + public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; - public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - public static readonly TheoryData DetectEdgesFilters - = new TheoryData - { - { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) }, - { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) }, - { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) }, - }; + public static readonly TheoryData DetectEdgesFilters + = new TheoryData + { + { KnownEdgeDetectorKernels.Laplacian3x3, nameof(KnownEdgeDetectorKernels.Laplacian3x3) }, + { KnownEdgeDetectorKernels.Laplacian5x5, nameof(KnownEdgeDetectorKernels.Laplacian5x5) }, + { KnownEdgeDetectorKernels.LaplacianOfGaussian, nameof(KnownEdgeDetectorKernels.LaplacianOfGaussian) }, + }; - public static readonly TheoryData DetectEdges2DFilters - = new TheoryData - { - { KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) }, - { KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) }, - { KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) }, - { KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) }, - { KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) }, - }; - - public static readonly TheoryData DetectEdgesCompassFilters - = new TheoryData - { - { KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) }, - { KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) }, - }; - - [Theory] - [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] - public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - ctx => - { - Size size = ctx.GetCurrentSize(); - var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); - ctx.DetectEdges(bounds); - }, - comparer: OpaqueComparer, - useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); - } + public static readonly TheoryData DetectEdges2DFilters + = new TheoryData + { + { KnownEdgeDetectorKernels.Kayyali, nameof(KnownEdgeDetectorKernels.Kayyali) }, + { KnownEdgeDetectorKernels.Prewitt, nameof(KnownEdgeDetectorKernels.Prewitt) }, + { KnownEdgeDetectorKernels.RobertsCross, nameof(KnownEdgeDetectorKernels.RobertsCross) }, + { KnownEdgeDetectorKernels.Scharr, nameof(KnownEdgeDetectorKernels.Scharr) }, + { KnownEdgeDetectorKernels.Sobel, nameof(KnownEdgeDetectorKernels.Sobel) }, + }; + + public static readonly TheoryData DetectEdgesCompassFilters + = new TheoryData + { + { KnownEdgeDetectorKernels.Kirsch, nameof(KnownEdgeDetectorKernels.Kirsch) }, + { KnownEdgeDetectorKernels.Robinson, nameof(KnownEdgeDetectorKernels.Robinson) }, + }; + + [Theory] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] + public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + ctx => + { + Size size = ctx.GetCurrentSize(); + var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); + ctx.DetectEdges(bounds); + }, + comparer: OpaqueComparer, + useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); + } - [Theory] - [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void DetectEdges_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetectorKernel detector, - string name) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void DetectEdges_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetectorKernel detector, + string name) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image image = provider.GetImage()) { - 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, name); - image.CompareToReferenceOutput(comparer, provider, name); - } + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } + } - [Theory] - [WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void DetectEdges2D_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetector2DKernel detector, - string name) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(DetectEdges2DFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] + public void DetectEdges2D_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string name) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image image = provider.GetImage()) { - 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, name); - image.CompareToReferenceOutput(comparer, provider, name); - } + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } + } - [Theory] - [WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)] - [WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] - public void DetectEdgesCompass_WorksWithAllFilters( - TestImageProvider provider, - EdgeDetectorCompassKernel detector, - string name) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(DetectEdgesCompassFilters), 100, 100, PixelTypes.Rgba32)] + [WithFileCollection(nameof(TestImages), nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] + public void DetectEdgesCompass_WorksWithAllFilters( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string name) + where TPixel : unmanaged, IPixel + { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; + using (Image image = provider.GetImage()) { - 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, name); - image.CompareToReferenceOutput(comparer, provider, name); - } + image.Mutate(x => x.DetectEdges(detector)); + image.DebugSave(provider, name); + image.CompareToReferenceOutput(comparer, provider, name); } + } - [Theory] - [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] - public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] + public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) + 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()) { - // 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(comparer, provider); - } + image.Mutate(x => x.DetectEdges()); + image.DebugSave(provider); + image.CompareToReferenceOutput(comparer, provider); } + } - [Theory] - [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.DetectEdges()); - image.DebugSave(provider, extension: "gif"); - } + image.Mutate(x => x.DetectEdges()); + image.DebugSave(provider, extension: "gif"); } + } - [Theory] - [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] - public void DetectEdges_InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] + public void DetectEdges_InBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - - image.Mutate(x => x.DetectEdges(bounds)); - image.DebugSave(provider); - image.CompareToReferenceOutput(OpaqueComparer, provider); - } - } + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers( - TestImageProvider provider, - EdgeDetectorKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); + image.Mutate(x => x.DetectEdges(bounds)); + image.DebugSave(provider); + image.CompareToReferenceOutput(OpaqueComparer, provider); } + } - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffers2D( - TestImageProvider provider, - EdgeDetector2DKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); - } + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers( + TestImageProvider provider, + EdgeDetectorKernel detector, + string _) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } - [Theory] - [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] - public void WorksWithDiscoBuffersCompass( - TestImageProvider provider, - EdgeDetectorCompassKernel detector, - string _) - where TPixel : unmanaged, IPixel - { - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.DetectEdges(detector), - detector); - } + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdges2DFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers2D( + TestImageProvider provider, + EdgeDetector2DKernel detector, + string _) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesCompassFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffersCompass( + TestImageProvider provider, + EdgeDetectorCompassKernel detector, + string _) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 9643ec59d3..47ccaa46cd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -2,17 +2,15 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class GaussianBlurTest : Basic1ParameterConvolutionTests { - [Trait("Category", "Processors")] - [GroupOutput("Convolution")] - public class GaussianBlurTest : Basic1ParameterConvolutionTests - { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianBlur(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianBlur(value, bounds); - } + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.GaussianBlur(value, bounds); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs index 0051fda45b..96c58f7010 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianSharpenTest.cs @@ -2,17 +2,15 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class GaussianSharpenTest : Basic1ParameterConvolutionTests { - [Trait("Category", "Processors")] - [GroupOutput("Convolution")] - public class GaussianSharpenTest : Basic1ParameterConvolutionTests - { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.GaussianSharpen(value); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.GaussianSharpen(value, bounds); - } + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.GaussianSharpen(value, bounds); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs index f63fbbcf2b..7cc0ef9d50 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/MedianBlurTest.cs @@ -2,17 +2,15 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class MedianBlurTest : Basic1ParameterConvolutionTests { - [Trait("Category", "Processors")] - [GroupOutput("Convolution")] - public class MedianBlurTest : Basic1ParameterConvolutionTests - { - protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true); + protected override void Apply(IImageProcessingContext ctx, int value) => ctx.MedianBlur(value, true); - protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => - ctx.MedianBlur(value, true, bounds); - } + protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => + ctx.MedianBlur(value, true, bounds); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 4dd1901931..b8e968f73a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,199 +1,195 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering +[Trait("Category", "Processors")] +public class DitherTests { - [Trait("Category", "Processors")] - public class DitherTests + 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 + { + { 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.Bayer16x16, nameof(KnownDitherings.Bayer16x16) }, + { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } + }; + + public static readonly TheoryData DefaultInstanceDitherers + = new TheoryData + { + default(ErrorDither), + default(OrderedDither) + }; + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; + + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; + + /// + /// The output is visually correct old 32bit runtime, + /// but it is very different because of floating point inaccuracies. + /// + private static readonly bool SkipAllDitherTests = + !TestEnvironment.Is64BitProcess && TestEnvironment.NetCoreVersion == null; + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - 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 - { - { 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.Bayer16x16, nameof(KnownDitherings.Bayer16x16) }, - { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } - }; - - public static readonly TheoryData DefaultInstanceDitherers - = new TheoryData - { - default(ErrorDither), - default(OrderedDither) - }; - - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - - private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - - private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; - - /// - /// The output is visually correct old 32bit runtime, - /// but it is very different because of floating point inaccuracies. - /// - private static readonly bool SkipAllDitherTests = - !TestEnvironment.Is64BitProcess && TestEnvironment.NetCoreVersion == null; - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Dither(DefaultErrorDiffuser, rect), - comparer: ValidatorComparer); + return; } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Dither(DefaultErrorDiffuser, rect), + comparer: ValidatorComparer); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Dither(DefaultDitherer, rect), - comparer: ValidatorComparer); + return; } - [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Dither(DefaultDitherer, rect), + comparer: ValidatorComparer); + } + + [Theory] + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - // Increased tolerance because of compatibility issues on .NET 4.6.2: - var comparer = ImageComparer.TolerantPercentage(1f); - provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); + return; } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers( - TestImageProvider provider, - IDither diffuser, - string name) - where TPixel : unmanaged, IPixel + // Increased tolerance because of compatibility issues on .NET 4.6.2: + var comparer = ImageComparer.TolerantPercentage(1f); + provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] + public void DiffusionFilter_WorksWithAllErrorDiffusers( + TestImageProvider provider, + IDither diffuser, + string name) + where TPixel : unmanaged, IPixel + { + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - provider.RunValidatingProcessorTest( - x => x.Dither(diffuser), - testOutputDetails: name, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); + return; } - [Theory] - [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] - public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel + provider.RunValidatingProcessorTest( + x => x.Dither(diffuser), + testOutputDetails: name, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] + public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - provider.RunValidatingProcessorTest( - x => x.Dither(DefaultDitherer), - comparer: ValidatorComparer); + return; } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] - public void DitherFilter_WorksWithAllDitherers( - TestImageProvider provider, - IDither ditherer, - string name) - where TPixel : unmanaged, IPixel + provider.RunValidatingProcessorTest( + x => x.Dither(DefaultDitherer), + comparer: ValidatorComparer); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] + public void DitherFilter_WorksWithAllDitherers( + TestImageProvider provider, + IDither ditherer, + string name) + where TPixel : unmanaged, IPixel + { + if (SkipAllDitherTests) { - if (SkipAllDitherTests) - { - return; - } - - provider.RunValidatingProcessorTest( - x => x.Dither(ditherer), - testOutputDetails: name, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); + return; } - [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 + provider.RunValidatingProcessorTest( + x => x.Dither(ditherer), + 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) { - IDither dither = TestUtils.GetDither(name); - if (SkipAllDitherTests) - { - return; - } - - provider.RunBufferCapacityLimitProcessorTest( - 41, - c => c.Dither(dither), - name); + return; } - [Theory] - [MemberData(nameof(DefaultInstanceDitherers))] - public void ShouldThrowForDefaultDitherInstance(IDither dither) - { - void Command() - { - using var image = new Image(10, 10); - image.Mutate(x => x.Dither(dither)); - } + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.Dither(dither), + name); + } - Assert.Throws(Command); + [Theory] + [MemberData(nameof(DefaultInstanceDitherers))] + public void ShouldThrowForDefaultDitherInstance(IDither dither) + { + void Command() + { + using var image = new Image(10, 10); + image.Mutate(x => x.Dither(dither)); } + + Assert.Throws(Command); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index d57b79ca58..ed3be0f677 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -4,35 +4,32 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +[Trait("Category", "Processors")] +[GroupOutput("Effects")] +public class BackgroundColorTest { - [Trait("Category", "Processors")] - [GroupOutput("Effects")] - public class BackgroundColorTest - { - public static readonly string[] InputImages = - { - TestImages.Png.Splash, - TestImages.Png.Ducky - }; - - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public static readonly string[] InputImages = { - provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); - } + TestImages.Png.Splash, + TestImages.Png.Ducky + }; - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.BackgroundColor(Color.HotPink, rect)); - } + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); + } + + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.BackgroundColor(Color.HotPink, rect)); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index 6dbaa1efbe..990a97bed0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -4,51 +4,49 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects -{ - [Trait("Category", "Processors")] - [GroupOutput("Effects")] - public class OilPaintTest - { - public static readonly TheoryData OilPaintValues = new TheoryData - { - { 15, 10 }, - { 6, 5 } - }; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; - public static readonly string[] InputImages = - { - TestImages.Png.CalliphoraPartial, - TestImages.Bmp.Car - }; +[Trait("Category", "Processors")] +[GroupOutput("Effects")] +public class OilPaintTest +{ + public static readonly TheoryData OilPaintValues = new TheoryData + { + { 15, 10 }, + { 6, 5 } + }; - [Theory] - [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider, int levels, int brushSize) - where TPixel : unmanaged, IPixel + public static readonly string[] InputImages = { - provider.RunValidatingProcessorTest( - x => - { - x.OilPaint(levels, brushSize); - return $"{levels}-{brushSize}"; - }, - ImageComparer.TolerantPercentage(0.01F), - appendPixelTypeToFileName: false); - } + TestImages.Png.CalliphoraPartial, + TestImages.Bmp.Car + }; - [Theory] - [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 : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.OilPaint(levels, brushSize, rect), - $"{levels}-{brushSize}", - ImageComparer.TolerantPercentage(0.01F)); - } + [Theory] + [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int levels, int brushSize) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest( + x => + { + x.OilPaint(levels, brushSize); + return $"{levels}-{brushSize}"; + }, + ImageComparer.TolerantPercentage(0.01F), + appendPixelTypeToFileName: false); + } + + [Theory] + [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 : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.OilPaint(levels, brushSize, rect), + $"{levels}-{brushSize}", + ImageComparer.TolerantPercentage(0.01F)); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs index 65d455f6ff..fd732f570f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs @@ -1,26 +1,42 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +[Trait("Category", "Processors")] +[GroupOutput("Effects")] +public class PixelShaderTest { - [Trait("Category", "Processors")] - [GroupOutput("Effects")] - public class PixelShaderTest + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest( - x => x.ProcessPixelRowsAsVector4( + provider.RunValidatingProcessorTest( + x => x.ProcessPixelRowsAsVector4( + span => + { + for (int i = 0; i < span.Length; i++) + { + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + span[i] = new Vector4(avg); + } + }), + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.ProcessPixelRowsAsVector4( span => { for (int i = 0; i < span.Length; i++) @@ -29,87 +45,67 @@ public void FullImage(TestImageProvider provider) float avg = (v4.X + v4.Y + v4.Z) / 3f; span[i] = new Vector4(avg); } - }), - appendPixelTypeToFileName: false); - } - - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.ProcessPixelRowsAsVector4( - span => - { - for (int i = 0; i < span.Length; i++) - { - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - span[i] = new Vector4(avg); - } - }, - rect)); - } + }, + rect)); + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void PositionAwareFullImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest( - c => c.ProcessPixelRowsAsVector4( - (span, offset) => + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void PositionAwareFullImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest( + c => c.ProcessPixelRowsAsVector4( + (span, offset) => + { + int y = offset.Y; + int x = offset.X; + for (int i = 0; i < span.Length; i++) { - int y = offset.Y; - int x = offset.X; - for (int i = 0; i < span.Length; i++) - { - float - sine = MathF.Sin(y), - cosine = MathF.Cos(x + i), - sum = sine + cosine, - abs = MathF.Abs(sum), - a = 0.5f + (abs / 2); // Max value for sin(y) + cos(x) is 2 + float + sine = MathF.Sin(y), + cosine = MathF.Cos(x + i), + sum = sine + cosine, + abs = MathF.Abs(sum), + a = 0.5f + (abs / 2); // Max value for sin(y) + cos(x) is 2 - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - var gray = new Vector4(avg, avg, avg, a); + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + var gray = new Vector4(avg, avg, avg, a); - span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); - } - }), - appendPixelTypeToFileName: false); - } + span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); + } + }), + appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void PositionAwareInBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest( - (c, rect) => c.ProcessPixelRowsAsVector4( - (span, offset) => + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void PositionAwareInBox(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunRectangleConstrainedValidatingProcessorTest( + (c, rect) => c.ProcessPixelRowsAsVector4( + (span, offset) => + { + int y = offset.Y; + int x = offset.X; + for (int i = 0; i < span.Length; i++) { - int y = offset.Y; - int x = offset.X; - for (int i = 0; i < span.Length; i++) - { - float - sine = MathF.Sin(y), - cosine = MathF.Cos(x + i), - sum = sine + cosine, - abs = MathF.Abs(sum), - a = 0.5f + (abs / 2); + float + sine = MathF.Sin(y), + cosine = MathF.Cos(x + i), + sum = sine + cosine, + abs = MathF.Abs(sum), + a = 0.5f + (abs / 2); - Vector4 v4 = span[i]; - float avg = (v4.X + v4.Y + v4.Z) / 3f; - var gray = new Vector4(avg, avg, avg, a); + Vector4 v4 = span[i]; + float avg = (v4.X + v4.Y + v4.Z) / 3f; + var gray = new Vector4(avg, avg, avg, a); - span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); - } - }, - rect)); - } + span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One); + } + }, + rect)); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index 6f433c3267..29b9524030 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -4,31 +4,28 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects +[Trait("Category", "Processors")] +[GroupOutput("Effects")] +public class PixelateTest { - [Trait("Category", "Processors")] - [GroupOutput("Effects")] - public class PixelateTest - { - public static readonly TheoryData PixelateValues = new TheoryData { 4, 8 }; + public static readonly TheoryData PixelateValues = new TheoryData { 4, 8 }; - [Theory] - [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] - public void FullImage(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); - } + [Theory] + [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] + public void FullImage(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); + } - [Theory] - [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] - [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); - } + [Theory] + [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider, int value) + 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 513ba83ec2..c8d8ca01a5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -4,20 +4,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class BlackWhiteTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class BlackWhiteTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyBlackWhiteFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyBlackWhiteFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); - } + 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 4136b15d68..7da96d13dd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -4,26 +4,24 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class BrightnessTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class BrightnessTest - { - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); - public static readonly TheoryData BrightnessValues - = new TheoryData - { - .5F, - 1.5F - }; + public static readonly TheoryData BrightnessValues + = new TheoryData + { + .5F, + 1.5F + }; - [Theory] - [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyBrightnessFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); - } + [Theory] + [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyBrightnessFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index f9ebc6b5a4..0727a5b979 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -4,32 +4,30 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class ColorBlindnessTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class ColorBlindnessTest - { - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F); + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F); - public static readonly TheoryData ColorBlindnessFilters - = new TheoryData - { - ColorBlindnessMode.Achromatomaly, - ColorBlindnessMode.Achromatopsia, - ColorBlindnessMode.Deuteranomaly, - ColorBlindnessMode.Deuteranopia, - ColorBlindnessMode.Protanomaly, - ColorBlindnessMode.Protanopia, - ColorBlindnessMode.Tritanomaly, - ColorBlindnessMode.Tritanopia - }; + public static readonly TheoryData ColorBlindnessFilters + = new TheoryData + { + ColorBlindnessMode.Achromatomaly, + ColorBlindnessMode.Achromatopsia, + ColorBlindnessMode.Deuteranomaly, + ColorBlindnessMode.Deuteranopia, + ColorBlindnessMode.Protanomaly, + ColorBlindnessMode.Protanopia, + ColorBlindnessMode.Tritanomaly, + ColorBlindnessMode.Tritanopia + }; - [Theory] - [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] - public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); - } + [Theory] + [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] + public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs index 7823844147..5e2a7f55d1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -3,27 +3,25 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class ContrastTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class ContrastTest + public static readonly TheoryData ContrastValues + = new TheoryData { - public static readonly TheoryData ContrastValues - = new TheoryData - { - .5F, - 1.5F - }; + .5F, + 1.5F + }; - [Theory] - [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyContrastFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Contrast(value), value); - } + [Theory] + [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyContrastFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Contrast(value), value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index 8f375e7999..0e5f14d805 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -4,54 +4,52 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class FilterTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class FilterTest + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3); + + // Testing the generic FilterProcessor with more than one pixel type intentionally. + // There is no need to do this with the specialized ones. + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void ApplyFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer); + } + + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyFilterInBox(TestImageProvider provider) + 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() { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3); - - // Testing the generic FilterProcessor with more than one pixel type intentionally. - // There is no need to do this with the specialized ones. - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void ApplyFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - ColorMatrix m = CreateCombinedTestFilterMatrix(); - - provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer); - } - - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyFilterInBox(TestImageProvider provider) - 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); - ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F); - ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); - return brightness * hue * saturation; - } + ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); + ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F); + ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); + return brightness * hue * saturation; } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs index 28ad806ed0..50a2621524 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -3,31 +3,29 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters -{ - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class GrayscaleTest - { - public static readonly TheoryData GrayscaleModeTypes - = new TheoryData - { - GrayscaleMode.Bt601, - GrayscaleMode.Bt709 - }; +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 : unmanaged, IPixel +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class GrayscaleTest +{ + public static readonly TheoryData GrayscaleModeTypes + = new TheoryData { - provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); - } + GrayscaleMode.Bt601, + GrayscaleMode.Bt709 + }; + + /// + /// 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 : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs index 62350b0b8b..65ac4245d7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -3,27 +3,25 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class HueTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class HueTest + public static readonly TheoryData HueValues + = new TheoryData { - public static readonly TheoryData HueValues - = new TheoryData - { - 180, - -180 - }; + 180, + -180 + }; - [Theory] - [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyHueFilter(TestImageProvider provider, int value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Hue(value), value); - } + [Theory] + [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyHueFilter(TestImageProvider provider, int value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Hue(value), value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs index f987be8718..245427f4c4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -3,20 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class InvertTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class InvertTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyInvertFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyInvertFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Invert()); - } + 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 ab25836b70..b5da47ffdb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -3,20 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class KodachromeTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class KodachromeTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyKodachromeFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyKodachromeFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Kodachrome()); - } + 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 681fdb63fb..39e69aa4a5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs @@ -4,26 +4,24 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class LightnessTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class LightnessTest - { - private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); + private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F); - public static readonly TheoryData LightnessValues - = new TheoryData - { - .5F, - 1.5F - }; + public static readonly TheoryData LightnessValues + = new TheoryData + { + .5F, + 1.5F + }; - [Theory] - [WithTestPatternImages(nameof(LightnessValues), 48, 48, PixelTypes.Rgba32)] - public void ApplyLightnessFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Lightness(value), value, this.imageComparer); - } + [Theory] + [WithTestPatternImages(nameof(LightnessValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyLightnessFilter(TestImageProvider provider, float value) + 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 e691db3cab..145e9d1406 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -3,20 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class LomographTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class LomographTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyLomographFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyLomographFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Lomograph()); - } + 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 04a47b0c8a..10ddf3a47d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -3,27 +3,25 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class OpacityTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class OpacityTest + public static readonly TheoryData AlphaValues + = new TheoryData { - 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 : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Opacity(value), value); - } + [Theory] + [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] + public void ApplyAlphaFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Opacity(value), value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs index 00964cda65..c72dbeb1fa 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -3,20 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class PolaroidTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class PolaroidTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplyPolaroidFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplyPolaroidFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Polaroid()); - } + 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 ce12aa33c9..8632d46259 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -3,27 +3,25 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class SaturateTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class SaturateTest + public static readonly TheoryData SaturationValues + = new TheoryData { - public static readonly TheoryData SaturationValues - = new TheoryData - { - .5F, - 1.5F, - }; + .5F, + 1.5F, + }; - [Theory] - [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] - public void ApplySaturationFilter(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Saturate(value), value); - } + [Theory] + [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] + public void ApplySaturationFilter(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Saturate(value), value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs index ca7a68b748..9806d2d6c2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -3,20 +3,18 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters; + +[Trait("Category", "Processors")] +[GroupOutput("Filters")] +public class SepiaTest { - [Trait("Category", "Processors")] - [GroupOutput("Filters")] - public class SepiaTest + [Theory] + [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] + public void ApplySepiaFilter(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] - public void ApplySepiaFilter(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Sepia()); - } + provider.RunValidatingProcessorTest(x => x.Sepia()); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs index a933ea5af2..209eb372c4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/GlowTest.cs @@ -2,19 +2,17 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; + +[Trait("Category", "Processors")] +[GroupOutput("Overlays")] +public class GlowTest : OverlayTestBase { - [Trait("Category", "Processors")] - [GroupOutput("Overlays")] - public class GlowTest : OverlayTestBase - { - protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Glow(color); + protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Glow(color); - protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => - ctx.Glow(radiusX); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Glow(radiusX); - protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Glow(rect); - } + protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Glow(rect); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index f31f84c9a4..cf2b7d4dad 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -5,68 +5,65 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays +[Trait("Category", "Processors")] +[GroupOutput("Overlays")] +public abstract class OverlayTestBase { - [Trait("Category", "Processors")] - [GroupOutput("Overlays")] - public abstract class OverlayTestBase - { - public static string[] ColorNames = { "Blue", "White" }; + public static string[] ColorNames = { "Blue", "White" }; - public static string[] InputImages = { TestImages.Png.Ducky, TestImages.Png.Splash }; + public static string[] InputImages = { TestImages.Png.Ducky, TestImages.Png.Splash }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - [Theory] - [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] - public void FullImage_ApplyColor(TestImageProvider provider, string colorName) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - Color color = TestUtils.GetColorByName(colorName); + [Theory] + [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] + public void FullImage_ApplyColor(TestImageProvider provider, string colorName) + where TPixel : unmanaged, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + Color color = TestUtils.GetColorByName(colorName); - provider.RunValidatingProcessorTest(x => this.Apply(x, color), colorName, ValidatorComparer, appendPixelTypeToFileName: false); - } + provider.RunValidatingProcessorTest(x => this.Apply(x, color), colorName, ValidatorComparer, appendPixelTypeToFileName: false); + } - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void FullImage_ApplyRadius(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunValidatingProcessorTest( - x => - { - Size size = x.GetCurrentSize(); - this.Apply(x, size.Width / 4f, size.Height / 4f); - }, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void FullImage_ApplyRadius(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.Utility.TestGroupName = this.GetType().Name; + provider.RunValidatingProcessorTest( + x => + { + Size size = x.GetCurrentSize(); + this.Apply(x, size.Width / 4f, size.Height / 4f); + }, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] - public void InBox(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.Utility.TestGroupName = this.GetType().Name; - provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply); - } + [Theory] + [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] + public void InBox(TestImageProvider provider) + 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)); - } + [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, Color color); - protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); + protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); - protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect); - } + protected abstract void Apply(IImageProcessingContext ctx, Rectangle rect); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs index eb8ee812fe..603eb30456 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/VignetteTest.cs @@ -2,19 +2,17 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays; + +[Trait("Category", "Processors")] +[GroupOutput("Overlays")] +public class VignetteTest : OverlayTestBase { - [Trait("Category", "Processors")] - [GroupOutput("Overlays")] - public class VignetteTest : OverlayTestBase - { - protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Vignette(color); + protected override void Apply(IImageProcessingContext ctx, Color color) => ctx.Vignette(color); - protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => - ctx.Vignette(radiusX, radiusY); + protected override void Apply(IImageProcessingContext ctx, float radiusX, float radiusY) => + ctx.Vignette(radiusX, radiusY); - protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Vignette(rect); - } + protected override void Apply(IImageProcessingContext ctx, Rectangle rect) => ctx.Vignette(rect); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index ed9f664333..c6880d3a81 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -4,61 +4,59 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; + +[Trait("Category", "Processors")] +public class OctreeQuantizerTests { - [Trait("Category", "Processors")] - public class OctreeQuantizerTests + [Fact] + public void OctreeQuantizerConstructor() { - [Fact] - public void OctreeQuantizerConstructor() - { - 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] - public void OctreeQuantizerCanCreateFrameQuantizer() - { - var quantizer = new OctreeQuantizer(); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + 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); + } - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - } + [Fact] + public void OctreeQuantizerCanCreateFrameQuantizer() + { + var quantizer = new OctreeQuantizer(); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); + + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); + + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + 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 c4b3152baf..91cf90bc46 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -4,77 +4,75 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; + +[Trait("Category", "Processors")] +public class PaletteQuantizerTests { - [Trait("Category", "Processors")] - public class PaletteQuantizerTests - { - private static readonly Color[] Palette = { Color.Red, Color.Green, Color.Blue }; + private static readonly Color[] Palette = { Color.Red, Color.Green, Color.Blue }; - [Fact] - public void PaletteQuantizerConstructor() - { - var expected = new QuantizerOptions { MaxColors = 128 }; - var quantizer = new PaletteQuantizer(Palette, expected); + [Fact] + public void PaletteQuantizerConstructor() + { + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - expected = new QuantizerOptions { Dither = null }; - quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); - Assert.Null(quantizer.Options.Dither); + expected = new QuantizerOptions { Dither = null }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); - 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 }; + 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); - } + 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(Palette); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + [Fact] + public void PaletteQuantizerCanCreateFrameQuantizer() + { + var quantizer = new PaletteQuantizer(Palette); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + Assert.NotNull(frameQuantizer); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + Assert.NotNull(frameQuantizer); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - } + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); + } - [Fact] - public void KnownQuantizersWebSafeTests() - { - IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - } + [Fact] + public void KnownQuantizersWebSafeTests() + { + IQuantizer quantizer = KnownQuantizers.WebSafe; + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + } - [Fact] - public void KnownQuantizersWernerTests() - { - IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - } + [Fact] + public void KnownQuantizersWernerTests() + { + IQuantizer quantizer = KnownQuantizers.Werner; + 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 index 4b7be99ce3..d26032c7ef 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -1,243 +1,240 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; + +[Trait("Category", "Processors")] +public class QuantizerTests { - [Trait("Category", "Processors")] - 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 = { - /// - /// 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 - }; + 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 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_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_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_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 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_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_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_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .5F + }; - private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions - { - Dither = KnownDitherings.Bayer8x8, - DitherScale = .75F - }; + private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .75F + }; - public static readonly TheoryData Quantizers - = new TheoryData + 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), + }; + + public static readonly TheoryData DefaultInstanceDitherers + = 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) + default(ErrorDither), + default(OrderedDither) }; - public static readonly TheoryData DitherScaleQuantizers - = new TheoryData + 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) { - 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), - }; + return; + } - public static readonly TheoryData DefaultInstanceDitherers - = new TheoryData - { - default(ErrorDither), - default(OrderedDither) - }; + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Quantize(quantizer, rect), + testOutputDetails: testOutputDetails, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] - public void ApplyQuantizationInBox(TestImageProvider provider, IQuantizer quantizer) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) { - 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), - testOutputDetails: testOutputDetails, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); + return; } - [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); - } + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; - [Theory] - [WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)] - public void ApplyQuantizationWithDitheringScale(TestImageProvider provider, IQuantizer quantizer) - where TPixel : unmanaged, IPixel + 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) { - 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); + return; } - [Theory] - [MemberData(nameof(DefaultInstanceDitherers))] - public void ShouldThrowForDefaultDitherInstance(IDither dither) + 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); + } + + [Theory] + [MemberData(nameof(DefaultInstanceDitherers))] + public void ShouldThrowForDefaultDitherInstance(IDither dither) + { + void Command() { - void Command() - { - using var image = new Image(10, 10); - var quantizer = new WebSafePaletteQuantizer(); - quantizer.Options.Dither = dither; - image.Mutate(x => x.Quantize(quantizer)); - } - - Assert.Throws(Command); + using var image = new Image(10, 10); + var quantizer = new WebSafePaletteQuantizer(); + quantizer.Options.Dither = dither; + image.Mutate(x => x.Quantize(quantizer)); } + + Assert.Throws(Command); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 6a1f6cdf95..ab4304d898 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -4,61 +4,59 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; + +[Trait("Category", "Processors")] +public class WuQuantizerTests { - [Trait("Category", "Processors")] - public class WuQuantizerTests + [Fact] + public void WuQuantizerConstructor() { - [Fact] - public void WuQuantizerConstructor() - { - 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] - public void WuQuantizerCanCreateFrameQuantizer() - { - var quantizer = new WuQuantizer(); - IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.NotNull(frameQuantizer.Options); - Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - - quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - - Assert.NotNull(frameQuantizer); - Assert.Null(frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); + 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); + } - quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); - frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); - Assert.NotNull(frameQuantizer); - Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); - frameQuantizer.Dispose(); - } + [Fact] + public void WuQuantizerCanCreateFrameQuantizer() + { + var quantizer = new WuQuantizer(); + IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); + + quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + + Assert.NotNull(frameQuantizer); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); + + quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); + Assert.NotNull(frameQuantizer); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 95953aa927..a8182bd07d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -1,304 +1,299 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Reflection; -using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class AffineTransformTests { - [Trait("Category", "Processors")] - public class AffineTransformTests + private readonly ITestOutputHelper output; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.033F, 3); + + /// + /// angleDeg, sx, sy, tx, ty + /// + public static readonly TheoryData TransformValues + = new TheoryData + { + { 0, 1, 1, 0, 0 }, + { 50, 1, 1, 0, 0 }, + { 0, 1, 1, 20, 10 }, + { 50, 1, 1, 20, 10 }, + { 0, 1, 1, -20, -10 }, + { 50, 1, 1, -20, -10 }, + { 50, 1.5f, 1.5f, 0, 0 }, + { 50, 1.1F, 1.3F, 30, -20 }, + { 0, 2f, 1f, 0, 0 }, + { 0, 1f, 2f, 0, 0 }, + }; + + public static readonly TheoryData ResamplerNames = new TheoryData { - private readonly ITestOutputHelper output; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.033F, 3); - - /// - /// angleDeg, sx, sy, tx, ty - /// - public static readonly TheoryData TransformValues - = new TheoryData - { - { 0, 1, 1, 0, 0 }, - { 50, 1, 1, 0, 0 }, - { 0, 1, 1, 20, 10 }, - { 50, 1, 1, 20, 10 }, - { 0, 1, 1, -20, -10 }, - { 50, 1, 1, -20, -10 }, - { 50, 1.5f, 1.5f, 0, 0 }, - { 50, 1.1F, 1.3F, 30, -20 }, - { 0, 2f, 1f, 0, 0 }, - { 0, 1f, 2f, 0, 0 }, - }; - - public static readonly TheoryData ResamplerNames = new TheoryData - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; - - public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = - new TheoryData - { - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Lanczos8), - }; - - 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 : unmanaged, IPixel - { - IResampler resampler = GetResampler(resamplerName); - using (Image image = provider.GetImage()) + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + + public static readonly TheoryData Transform_DoesNotCreateEdgeArtifacts_ResamplerNames = + new TheoryData { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(30); + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Lanczos8), + }; + + 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 : unmanaged, IPixel + { + IResampler resampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(30); - image.Mutate(c => c.Transform(builder, resampler)); - image.DebugSave(provider, resamplerName); + image.Mutate(c => c.Transform(builder, resampler)); + image.DebugSave(provider, resamplerName); - VerifyAllPixelsAreWhiteOrTransparent(image); - } + VerifyAllPixelsAreWhiteOrTransparent(image); } + } - [Theory] - [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] - public void Transform_RotateScaleTranslate( - TestImageProvider provider, - float angleDeg, - float sx, - float sy, - float tx, - float ty) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(TransformValues), 100, 50, PixelTypes.Rgba32)] + public void Transform_RotateScaleTranslate( + TestImageProvider provider, + float angleDeg, + float sx, + float sy, + float tx, + float ty) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(sx, sy)) - .AppendTranslation(new PointF(tx, ty)); + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(sx, sy)) + .AppendTranslation(new PointF(tx, ty)); - this.PrintMatrix(builder.BuildMatrix(image.Size())); + this.PrintMatrix(builder.BuildMatrix(image.Size())); - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } + FormattableString testOutputDetails = $"R({angleDeg})_S({sx},{sy})_T({tx},{ty})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); } + } - [Theory] - [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] - public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] + public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(angleDeg) - .AppendScale(new SizeF(s, s)); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(angleDeg) + .AppendScale(new SizeF(s, s)); - image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); + image.Mutate(i => i.Transform(builder, KnownResamplers.Bicubic)); - FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); - } + FormattableString testOutputDetails = $"R({angleDeg})_S({s})"; + image.DebugSave(provider, testOutputDetails); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails); } + } - public static readonly TheoryData Transform_IntoRectangle_Data = - new TheoryData - { - { 0, 0, 10, 10 }, - { 0, 0, 5, 10 }, - { 0, 0, 10, 5 }, - { 5, 0, 5, 10 }, - { -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 : unmanaged, IPixel - { - var rectangle = new Rectangle(48, 0, 48, 24); - - using (Image image = provider.GetImage()) + public static readonly TheoryData Transform_IntoRectangle_Data = + new TheoryData { - image.DebugSave(provider, $"_original"); - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(2, 1.5F)); - - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } + { 0, 0, 10, 10 }, + { 0, 0, 5, 10 }, + { 0, 0, 10, 5 }, + { 5, 0, 5, 10 }, + { -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 : unmanaged, IPixel + { + var rectangle = new Rectangle(48, 0, 48, 24); - [Theory] - [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] - public void Transform_FromSourceRectangle2(TestImageProvider provider) - where TPixel : unmanaged, IPixel + using (Image image = provider.GetImage()) { - var rectangle = new Rectangle(0, 24, 48, 24); - - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendScale(new SizeF(1F, 2F)); + image.DebugSave(provider, $"_original"); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(2, 1.5F)); - image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } + } - [Theory] - [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] + public void Transform_FromSourceRectangle2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var rectangle = new Rectangle(0, 24, 48, 24); + + using (Image image = provider.GetImage()) { - IResampler sampler = GetResampler(resamplerName); - using (Image image = provider.GetImage()) - { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(50) - .AppendScale(new SizeF(.6F, .6F)); + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendScale(new SizeF(1F, 2F)); - image.Mutate(i => i.Transform(builder, sampler)); + image.Mutate(i => i.Transform(rectangle, builder, KnownResamplers.Spline)); - image.DebugSave(provider, resamplerName); - image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)] - public void WorksWithDiscoBuffers(TestImageProvider provider, int bufferCapacityInPixelRows) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithSampler(TestImageProvider provider, string resamplerName) + where TPixel : unmanaged, IPixel + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) { AffineTransformBuilder builder = new AffineTransformBuilder() .AppendRotationDegrees(50) .AppendScale(new SizeF(.6F, .6F)); - provider.RunBufferCapacityLimitProcessorTest( - bufferCapacityInPixelRows, - c => c.Transform(builder)); - } - [Fact] - public void Issue1911() - { - using var image = new Image(100, 100); - image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix3x2.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); + image.Mutate(i => i.Transform(builder, sampler)); - Assert.Equal(99, image.Width); - Assert.Equal(100, image.Height); + image.DebugSave(provider, resamplerName); + image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Identity(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + [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)); + } - Matrix3x2 m = Matrix3x2.Identity; - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + [Fact] + public void Issue1911() + { + using var image = new Image(100, 100); + image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix3x2.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] - public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + Assert.Equal(99, image.Width); + Assert.Equal(100, image.Height); + } - var m = Matrix3x2.CreateRotation(radians, new Vector2(50, 50)); - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider, testOutputDetails: radians); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void Identity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - private static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + Matrix3x2 m = Matrix3x2.Identity; + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } - if (property is null) - { - throw new Exception($"No resampler named {name}"); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] + public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + var m = Matrix3x2.CreateRotation(radians, new Vector2(50, 50)); + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider, testOutputDetails: radians); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); + } + + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - return (IResampler)property.GetValue(null); + if (property is null) + { + throw new Exception($"No resampler named {name}"); } - private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) - where TPixel : unmanaged, IPixel + return (IResampler)property.GetValue(null); + } + + private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) + where TPixel : unmanaged, IPixel + { + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory data)); + var white = new Rgb24(255, 255, 255); + foreach (TPixel pixel in data.Span) { - Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory data)); - var white = new Rgb24(255, 255, 255); - foreach (TPixel pixel in data.Span) + Rgba32 rgba = default; + pixel.ToRgba32(ref rgba); + if (rgba.A == 0) { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - if (rgba.A == 0) - { - continue; - } - - Assert.Equal(white, rgba.Rgb); + continue; } - } - 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); + Assert.Equal(white, rgba.Rgb); } } + + 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); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index 70e1ec90b7..6a61538ea1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -1,91 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class AutoOrientTests { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class AutoOrientTests - { - public const string FlipTestFile = TestImages.Bmp.F; - - public static readonly TheoryData InvalidOrientationValues - = new() - { - { ExifDataType.Byte, new byte[] { 1 } }, - { ExifDataType.SignedByte, new byte[] { 2 } }, - { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, - { ExifDataType.Long, BitConverter.GetBytes(4U) }, - { ExifDataType.SignedLong, BitConverter.GetBytes(5) } - }; + public const string FlipTestFile = TestImages.Bmp.F; - public static readonly TheoryData ExifOrientationValues - = new() - { - ExifOrientationMode.Unknown, - ExifOrientationMode.TopLeft, - ExifOrientationMode.TopRight, - ExifOrientationMode.BottomRight, - ExifOrientationMode.BottomLeft, - ExifOrientationMode.LeftTop, - ExifOrientationMode.RightTop, - ExifOrientationMode.RightBottom, - ExifOrientationMode.LeftBottom - }; + public static readonly TheoryData InvalidOrientationValues + = new() + { + { ExifDataType.Byte, new byte[] { 1 } }, + { ExifDataType.SignedByte, new byte[] { 2 } }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, + { ExifDataType.Long, BitConverter.GetBytes(4U) }, + { ExifDataType.SignedLong, BitConverter.GetBytes(5) } + }; - [Theory] - [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] - public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) - where TPixel : unmanaged, IPixel + public static readonly TheoryData ExifOrientationValues + = new() { - using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new ExifProfile(); - image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); + ExifOrientationMode.Unknown, + ExifOrientationMode.TopLeft, + ExifOrientationMode.TopRight, + ExifOrientationMode.BottomRight, + ExifOrientationMode.BottomLeft, + ExifOrientationMode.LeftTop, + ExifOrientationMode.RightTop, + ExifOrientationMode.RightBottom, + ExifOrientationMode.LeftBottom + }; + + [Theory] + [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation); - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); - } + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); + } - [Theory] - [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] - public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) - where TPixel : unmanaged, IPixel - { - var profile = new ExifProfile(); - profile.SetValue(ExifTag.JPEGTables, orientation); + [Theory] + [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] + public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) + where TPixel : unmanaged, IPixel + { + var profile = new ExifProfile(); + profile.SetValue(ExifTag.JPEGTables, orientation); - byte[] bytes = profile.ToByteArray(); + byte[] bytes = profile.ToByteArray(); - // Change the tag into ExifTag.Orientation - bytes[16] = 18; - bytes[17] = 1; + // Change the tag into ExifTag.Orientation + bytes[16] = 18; + bytes[17] = 1; - // Change the data type - bytes[18] = (byte)dataType; + // Change the data type + bytes[18] = (byte)dataType; - // Change the number of components - bytes[20] = 1; + // Change the number of components + bytes[20] = 1; - byte[] orientationCodeData = new byte[8]; - Array.Copy(orientation, orientationCodeData, orientation.Length); + byte[] orientationCodeData = new byte[8]; + Array.Copy(orientation, orientationCodeData, orientation.Length); - ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); + ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); - using Image image = provider.GetImage(); - using Image reference = image.Clone(); - image.Metadata.ExifProfile = new ExifProfile(bytes); - image.Mutate(x => x.AutoOrient()); - image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); - ImageComparer.Exact.VerifySimilarity(image, reference); - } + using Image image = provider.GetImage(); + using Image reference = image.Clone(); + image.Metadata.ExifProfile = new ExifProfile(bytes); + image.Mutate(x => x.AutoOrient()); + image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); + ImageComparer.Exact.VerifySimilarity(image, reference); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index fa9ee8133d..18a7a9bd09 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -1,32 +1,28 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class CropTest { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class CropTest + [Theory] + [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 : unmanaged, IPixel { - [Theory] - [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 : unmanaged, IPixel - { - var rect = new Rectangle(x, y, w, h); - FormattableString info = $"X{x}Y{y}.W{w}H{h}"; - provider.RunValidatingProcessorTest( - ctx => ctx.Crop(rect), - info, - appendPixelTypeToFileName: false, - comparer: ImageComparer.Exact); - } + var rect = new Rectangle(x, y, w, h); + FormattableString info = $"X{x}Y{y}.W{w}H{h}"; + provider.RunValidatingProcessorTest( + ctx => ctx.Crop(rect), + info, + appendPixelTypeToFileName: false, + comparer: ImageComparer.Exact); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index cb3035234e..ca4b00d8aa 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -3,48 +3,46 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class EntropyCropTest { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class EntropyCropTest - { - public static readonly TheoryData EntropyCropValues = new TheoryData { .25F, .75F }; - - public static readonly string[] InputImages = - { - TestImages.Png.Ducky, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK - }; - - [Theory] - [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] - public void EntropyCrop(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); - } - - [Theory] - [WithBlankImages(40, 30, PixelTypes.Rgba32)] - [WithBlankImages(30, 40, PixelTypes.Rgba32)] - public void Entropy_WillNotCropWhiteImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public static readonly TheoryData EntropyCropValues = new TheoryData { .25F, .75F }; + + public static readonly string[] InputImages = { - // arrange - using Image image = provider.GetImage(); - var expectedHeight = image.Height; - var expectedWidth = image.Width; - - // act - image.Mutate(img => img.EntropyCrop()); - - // assert - Assert.Equal(image.Width, expectedWidth); - Assert.Equal(image.Height, expectedHeight); - } + TestImages.Png.Ducky, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK + }; + + [Theory] + [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] + public void EntropyCrop(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); + } + + [Theory] + [WithBlankImages(40, 30, PixelTypes.Rgba32)] + [WithBlankImages(30, 40, PixelTypes.Rgba32)] + public void Entropy_WillNotCropWhiteImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // arrange + using Image image = provider.GetImage(); + var expectedHeight = image.Height; + var expectedWidth = image.Width; + + // act + image.Mutate(img => img.EntropyCrop()); + + // assert + Assert.Equal(image.Width, expectedWidth); + Assert.Equal(image.Height, expectedHeight); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index fde24358d0..717582274f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -4,47 +4,44 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; - // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class FlipTests { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class FlipTests - { - public static readonly TheoryData FlipValues = - new TheoryData - { - FlipMode.None, - FlipMode.Vertical, - FlipMode.Horizontal, - }; + public static readonly TheoryData FlipValues = + new TheoryData + { + FlipMode.None, + FlipMode.Vertical, + FlipMode.Horizontal, + }; - [Theory] - [WithTestPatternImages(nameof(FlipValues), 20, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] - public void Flip(TestImageProvider provider, FlipMode flipMode) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest( - ctx => ctx.Flip(flipMode), - testOutputDetails: flipMode, - appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(nameof(FlipValues), 20, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] + public void Flip(TestImageProvider provider, FlipMode flipMode) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest( + ctx => ctx.Flip(flipMode), + testOutputDetails: flipMode, + appendPixelTypeToFileName: false); + } - [Theory] - [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] - public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTestOnWrappedMemoryImage( - ctx => ctx.Flip(flipMode), - testOutputDetails: flipMode, - useReferenceOutputFrom: nameof(this.Flip), - appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] + public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTestOnWrappedMemoryImage( + ctx => ctx.Flip(flipMode), + testOutputDetails: flipMode, + useReferenceOutputFrom: nameof(this.Flip), + appendPixelTypeToFileName: false); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index 6ef6762f4e..8949049fbc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -3,55 +3,53 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +public class PadTest { - [Trait("Category", "Processors")] - public class PadTest + public static readonly string[] CommonTestImages = { - public static readonly string[] CommonTestImages = - { - TestImages.Png.CalliphoraPartial, TestImages.Png.Bike - }; + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void ImageShouldPad(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); - image.DebugSave(provider); + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + public void ImageShouldPad(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50)); + image.DebugSave(provider); - // Check pixels are empty - for (int y = 0; y < 25; y++) + // Check pixels are empty + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) { - for (int x = 0; x < 25; x++) - { - Assert.Equal(default, image[x, y]); - } + Assert.Equal(default, image[x, y]); } } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Color color = Color.Red; - TPixel expected = color.ToPixel(); - using Image image = provider.GetImage(); - image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); - image.DebugSave(provider); + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Color color = Color.Red; + TPixel expected = color.ToPixel(); + using Image image = provider.GetImage(); + image.Mutate(x => x.Pad(image.Width + 50, image.Height + 50, color)); + image.DebugSave(provider); - // Check pixels are filled - for (int y = 0; y < 25; y++) + // Check pixels are filled + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) { - for (int x = 0; x < 25; x++) - { - Assert.Equal(expected, image[x, y]); - } + Assert.Equal(expected, image[x, y]); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs index bcfafdac96..0fea41647c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -4,67 +4,64 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +[Trait("Category", "Processors")] +public class ResamplerTests { - [Trait("Category", "Processors")] - public class ResamplerTests + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void BicubicWindowOscillatesCorrectly(float x, float expected) { - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void BicubicWindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Bicubic; - float result = sampler.GetValue(x); + IResampler sampler = KnownResamplers.Bicubic; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Lanczos3; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Lanczos3; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-4, 0)] - [InlineData(-2, 0)] - [InlineData(0, 1)] - [InlineData(2, 0)] - [InlineData(4, 0)] - public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Lanczos5; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-4, 0)] + [InlineData(-2, 0)] + [InlineData(0, 1)] + [InlineData(2, 0)] + [InlineData(4, 0)] + public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Lanczos5; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); + } - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void TriangleWindowOscillatesCorrectly(float x, float expected) - { - IResampler sampler = KnownResamplers.Triangle; - float result = sampler.GetValue(x); + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void TriangleWindowOscillatesCorrectly(float x, float expected) + { + IResampler sampler = KnownResamplers.Triangle; + float result = sampler.GetValue(x); - Assert.Equal(result, expected); - } + Assert.Equal(result, expected); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs index 436cc44070..84d834cc50 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs @@ -4,156 +4,153 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +[Trait("Category", "Processors")] +public class ResizeHelperTests { - [Trait("Category", "Processors")] - public class ResizeHelperTests + [Theory] + [InlineData(20, 100, 1, 2)] + [InlineData(20, 100, 20 * 100 * 16, 2)] + [InlineData(20, 100, 40 * 100 * 16, 2)] + [InlineData(20, 100, 59 * 100 * 16, 2)] + [InlineData(20, 100, 60 * 100 * 16, 3)] + [InlineData(17, 63, 5 * 17 * 63 * 16, 5)] + [InlineData(17, 63, (5 * 17 * 63 * 16) + 1, 5)] + [InlineData(17, 63, (6 * 17 * 63 * 16) - 1, 5)] + [InlineData(33, 400, 1 * 1024 * 1024, 4)] + [InlineData(33, 400, 8 * 1024 * 1024, 39)] + [InlineData(50, 300, 1 * 1024 * 1024, 4)] + public void CalculateResizeWorkerHeightInWindowBands( + int windowDiameter, + int width, + int sizeLimitHintInBytes, + int expectedCount) { - [Theory] - [InlineData(20, 100, 1, 2)] - [InlineData(20, 100, 20 * 100 * 16, 2)] - [InlineData(20, 100, 40 * 100 * 16, 2)] - [InlineData(20, 100, 59 * 100 * 16, 2)] - [InlineData(20, 100, 60 * 100 * 16, 3)] - [InlineData(17, 63, 5 * 17 * 63 * 16, 5)] - [InlineData(17, 63, (5 * 17 * 63 * 16) + 1, 5)] - [InlineData(17, 63, (6 * 17 * 63 * 16) - 1, 5)] - [InlineData(33, 400, 1 * 1024 * 1024, 4)] - [InlineData(33, 400, 8 * 1024 * 1024, 39)] - [InlineData(50, 300, 1 * 1024 * 1024, 4)] - public void CalculateResizeWorkerHeightInWindowBands( - int windowDiameter, - int width, - int sizeLimitHintInBytes, - int expectedCount) - { - int actualCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(windowDiameter, width, sizeLimitHintInBytes); - Assert.Equal(expectedCount, actualCount); - } - - [Fact] - public void CalculateMinRectangleWhenSourceIsSmallerThanTarget() - { - var sourceSize = new Size(200, 100); - var target = new Size(400, 200); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Min, - Size = target - }); - - Assert.Equal(sourceSize, size); - Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), rectangle); - } - - [Fact] - public void MaxSizeAndRectangleAreCorrect() - { - var sourceSize = new Size(5072, 6761); - var target = new Size(0, 450); - - var expectedSize = new Size(338, 450); - var expectedRectangle = new Rectangle(Point.Empty, expectedSize); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Max, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void CropSizeAndRectangleAreCorrect() - { - var sourceSize = new Size(100, 100); - var target = new Size(25, 50); - - var expectedSize = new Size(25, 50); - var expectedRectangle = new Rectangle(-12, 0, 50, 50); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Crop, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void BoxPadSizeAndRectangleAreCorrect() - { - var sourceSize = new Size(100, 100); - var target = new Size(120, 110); - - var expectedSize = new Size(120, 110); - var expectedRectangle = new Rectangle(10, 5, 100, 100); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.BoxPad, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void PadSizeAndRectangleAreCorrect() - { - var sourceSize = new Size(100, 100); - var target = new Size(120, 110); - - var expectedSize = new Size(120, 110); - var expectedRectangle = new Rectangle(5, 0, 110, 110); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Pad, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } - - [Fact] - public void StretchSizeAndRectangleAreCorrect() - { - var sourceSize = new Size(100, 100); - var target = new Size(57, 32); - - var expectedSize = new Size(57, 32); - var expectedRectangle = new Rectangle(Point.Empty, expectedSize); - - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( - sourceSize, - new ResizeOptions - { - Mode = ResizeMode.Stretch, - Size = target - }); - - Assert.Equal(expectedSize, size); - Assert.Equal(expectedRectangle, rectangle); - } + int actualCount = ResizeHelper.CalculateResizeWorkerHeightInWindowBands(windowDiameter, width, sizeLimitHintInBytes); + Assert.Equal(expectedCount, actualCount); + } + + [Fact] + public void CalculateMinRectangleWhenSourceIsSmallerThanTarget() + { + var sourceSize = new Size(200, 100); + var target = new Size(400, 200); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.Min, + Size = target + }); + + Assert.Equal(sourceSize, size); + Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), rectangle); + } + + [Fact] + public void MaxSizeAndRectangleAreCorrect() + { + var sourceSize = new Size(5072, 6761); + var target = new Size(0, 450); + + var expectedSize = new Size(338, 450); + var expectedRectangle = new Rectangle(Point.Empty, expectedSize); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.Max, + Size = target + }); + + Assert.Equal(expectedSize, size); + Assert.Equal(expectedRectangle, rectangle); + } + + [Fact] + public void CropSizeAndRectangleAreCorrect() + { + var sourceSize = new Size(100, 100); + var target = new Size(25, 50); + + var expectedSize = new Size(25, 50); + var expectedRectangle = new Rectangle(-12, 0, 50, 50); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.Crop, + Size = target + }); + + Assert.Equal(expectedSize, size); + Assert.Equal(expectedRectangle, rectangle); + } + + [Fact] + public void BoxPadSizeAndRectangleAreCorrect() + { + var sourceSize = new Size(100, 100); + var target = new Size(120, 110); + + var expectedSize = new Size(120, 110); + var expectedRectangle = new Rectangle(10, 5, 100, 100); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.BoxPad, + Size = target + }); + + Assert.Equal(expectedSize, size); + Assert.Equal(expectedRectangle, rectangle); + } + + [Fact] + public void PadSizeAndRectangleAreCorrect() + { + var sourceSize = new Size(100, 100); + var target = new Size(120, 110); + + var expectedSize = new Size(120, 110); + var expectedRectangle = new Rectangle(5, 0, 110, 110); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.Pad, + Size = target + }); + + Assert.Equal(expectedSize, size); + Assert.Equal(expectedRectangle, rectangle); + } + + [Fact] + public void StretchSizeAndRectangleAreCorrect() + { + var sourceSize = new Size(100, 100); + var target = new Size(57, 32); + + var expectedSize = new Size(57, 32); + var expectedRectangle = new Rectangle(Point.Empty, expectedSize); + + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds( + sourceSize, + new ResizeOptions + { + Mode = ResizeMode.Stretch, + Size = target + }); + + Assert.Equal(expectedSize, size); + Assert.Equal(expectedRectangle, rectangle); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 58e7bc2690..290a3b37ac 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -1,113 +1,109 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +public partial class ResizeKernelMapTests { - [Trait("Category", "Processors")] - public partial class ResizeKernelMapTests + /// + /// Simplified reference implementation for functionality. + /// + internal class ReferenceKernelMap { - /// - /// Simplified reference implementation for functionality. - /// - internal class ReferenceKernelMap + private readonly ReferenceKernel[] kernels; + + public ReferenceKernelMap(ReferenceKernel[] kernels) { - private readonly ReferenceKernel[] kernels; + this.kernels = kernels; + } - public ReferenceKernelMap(ReferenceKernel[] kernels) - { - this.kernels = kernels; - } + public int DestinationSize => this.kernels.Length; - public int DestinationSize => this.kernels.Length; + public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; + 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; - public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) - where TResampler : struct, IResampler + if (scale < 1F) { - double ratio = (double)sourceSize / destinationSize; - double scale = ratio; + scale = 1F; + } - if (scale < 1F) - { - scale = 1F; - } + TolerantMath tolerantMath = TolerantMath.Default; - TolerantMath tolerantMath = TolerantMath.Default; + double radius = tolerantMath.Ceiling(scale * sampler.Radius); - double radius = tolerantMath.Ceiling(scale * sampler.Radius); + var result = new List(); - var result = new List(); + for (int i = 0; i < destinationSize; i++) + { + double center = ((i + .5) * ratio) - .5; - for (int i = 0; i < destinationSize; i++) + // Keep inside bounds. + int left = (int)tolerantMath.Ceiling(center - radius); + if (left < 0) { - double center = ((i + .5) * ratio) - .5; - - // Keep inside bounds. - int left = (int)tolerantMath.Ceiling(center - radius); - if (left < 0) - { - left = 0; - } + left = 0; + } - int right = (int)tolerantMath.Floor(center + radius); - if (right > sourceSize - 1) - { - right = sourceSize - 1; - } + int right = (int)tolerantMath.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } - double sum = 0; + double sum = 0; - double[] values = new double[right - left + 1]; + double[] values = new double[right - left + 1]; - for (int j = left; j <= right; j++) - { - double weight = sampler.GetValue((float)((j - center) / scale)); - sum += weight; + for (int j = left; j <= right; j++) + { + double weight = sampler.GetValue((float)((j - center) / scale)); + sum += weight; - values[j - left] = weight; - } + values[j - left] = weight; + } - if (sum > 0 && normalize) + if (sum > 0 && normalize) + { + for (int w = 0; w < values.Length; w++) { - for (int w = 0; w < values.Length; w++) - { - values[w] /= sum; - } + values[w] /= sum; } - - float[] floatVals = values.Select(v => (float)v).ToArray(); - - result.Add(new ReferenceKernel(left, floatVals)); } - return new ReferenceKernelMap(result.ToArray()); + float[] floatVals = values.Select(v => (float)v).ToArray(); + + result.Add(new ReferenceKernel(left, floatVals)); } + + return new ReferenceKernelMap(result.ToArray()); } + } - internal struct ReferenceKernel + internal struct ReferenceKernel + { + public ReferenceKernel(int left, float[] values) { - public ReferenceKernel(int left, float[] values) - { - this.Left = left; - this.Values = values; - } + this.Left = left; + this.Values = values; + } - public int Left { get; } + public int Left { get; } - public float[] Values { get; } + public float[] Values { get; } - public int Length => this.Values.Length; + public int Length => this.Values.Length; - public static implicit operator ReferenceKernel(ResizeKernel orig) - { - return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray()); - } + public static implicit operator ReferenceKernel(ResizeKernel orig) + { + return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray()); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index bc70db0d98..3cea431fbb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -1,229 +1,224 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Text; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +public partial class ResizeKernelMapTests { - [Trait("Category", "Processors")] - public partial class ResizeKernelMapTests - { - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public ResizeKernelMapTests(ITestOutputHelper output) - { - this.Output = output; - } + public ResizeKernelMapTests(ITestOutputHelper output) + { + this.Output = output; + } - /// - /// resamplerName, srcSize, destSize - /// - public static readonly TheoryData KernelMapData - = new TheoryData - { - { 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: - { KnownResamplers.Box, 378, 149 }, - { KnownResamplers.Box, 349, 174 }, - - // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { 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 - { KnownResamplers.Bicubic, 10, 50 }, - { KnownResamplers.Bicubic, 49, 301 }, - { KnownResamplers.Bicubic, 301, 49 }, - { KnownResamplers.Bicubic, 1680, 1200 }, - { KnownResamplers.Box, 13, 299 }, - { KnownResamplers.Lanczos5, 3032, 600 }, - - // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 - { KnownResamplers.Bicubic, 207773, 51943 } - }; - - public static TheoryData GeneratedImageResizeData = - GenerateImageResizeData(); - - [Theory(Skip = "Only for debugging and development")] - [MemberData(nameof(KernelMapData))] - public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); + /// + /// resamplerName, srcSize, destSize + /// + public static readonly TheoryData KernelMapData + = new TheoryData + { + { 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: + { KnownResamplers.Box, 378, 149 }, + { KnownResamplers.Box, 349, 174 }, + + // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData + { 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 + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { KnownResamplers.Lanczos5, 3032, 600 }, + + // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 + { KnownResamplers.Bicubic, 207773, 51943 } + }; + + public static TheoryData GeneratedImageResizeData = + GenerateImageResizeData(); + + [Theory(Skip = "Only for debugging and development")] + [MemberData(nameof(KernelMapData))] + public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler + { + var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); - this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); - } + this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); + } - [Theory] - [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); - } + [Theory] + [MemberData(nameof(KernelMapData))] + public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler + { + this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); + } - // Comprehensive but expensive tests, for ResizeKernelMap. - // Enabling them can kill you, but sometimes you have to wear the burden! - // AppVeyor will never follow you to these shadows of Mordor. + // Comprehensive but expensive tests, for ResizeKernelMap. + // Enabling them can kill you, but sometimes you have to wear the burden! + // AppVeyor will never follow you to these shadows of Mordor. #if false - [Theory] - [MemberData(nameof(GeneratedImageResizeData))] - public void KernelMapContentIsCorrect_ExtendedGeneratedValues(string resamplerName, int srcSize, int destSize) - { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); - } + [Theory] + [MemberData(nameof(GeneratedImageResizeData))] + public void KernelMapContentIsCorrect_ExtendedGeneratedValues(string resamplerName, int srcSize, int destSize) + { + this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + } #endif - private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : struct, IResampler - { - var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler + { + 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); - this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); - this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); + this.Output.WriteLine(kernelMap.Info); + this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); + this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); #endif - var comparer = new ApproximateFloatComparer(1e-6f); + var comparer = new ApproximateFloatComparer(1e-6f); - for (int i = 0; i < kernelMap.DestinationLength; i++) - { - ResizeKernel kernel = kernelMap.GetKernel(i); + for (int i = 0; i < kernelMap.DestinationLength; i++) + { + ResizeKernel kernel = kernelMap.GetKernel(i); - ReferenceKernel referenceKernel = referenceMap.GetKernel(i); + ReferenceKernel referenceKernel = referenceMap.GetKernel(i); + Assert.True( + referenceKernel.Length == kernel.Length, + $"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}"); + Assert.True( + referenceKernel.Left == kernel.StartIndex, + $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}"); + float[] expectedValues = referenceKernel.Values; + Span actualValues = kernel.Values; + + Assert.Equal(expectedValues.Length, actualValues.Length); + + for (int x = 0; x < expectedValues.Length; x++) + { Assert.True( - referenceKernel.Length == kernel.Length, - $"referenceKernel.Length != kernel.Length: {referenceKernel.Length} != {kernel.Length}"); - Assert.True( - referenceKernel.Left == kernel.StartIndex, - $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}"); - float[] expectedValues = referenceKernel.Values; - Span actualValues = kernel.Values; - - Assert.Equal(expectedValues.Length, actualValues.Length); - - for (int x = 0; x < expectedValues.Length; x++) - { - Assert.True( - comparer.Equals(expectedValues[x], actualValues[x]), - $"{expectedValues[x]} != {actualValues[x]} @ (Row:{i}, Col:{x})"); - } + comparer.Equals(expectedValues[x], actualValues[x]), + $"{expectedValues[x]} != {actualValues[x]} @ (Row:{i}, Col:{x})"); } } + } - 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, - Func getDestinationSize, - Func getKernel) + private static string PrintKernelMap( + TKernelMap kernelMap, + Func getDestinationSize, + Func getKernel) + { + var bld = new StringBuilder(); + + if (kernelMap is ResizeKernelMap actualMap) { - var bld = new StringBuilder(); + bld.AppendLine(actualMap.Info); + } - if (kernelMap is ResizeKernelMap actualMap) - { - bld.AppendLine(actualMap.Info); - } + int destinationSize = getDestinationSize(kernelMap); - int destinationSize = getDestinationSize(kernelMap); + for (int i = 0; i < destinationSize; i++) + { + ReferenceKernel kernel = getKernel(kernelMap, i); + bld.Append($"[{i:D3}] (L{kernel.Left:D3}) || "); + Span span = kernel.Values; - for (int i = 0; i < destinationSize; i++) + for (int j = 0; j < kernel.Length; j++) { - ReferenceKernel kernel = getKernel(kernelMap, i); - bld.Append($"[{i:D3}] (L{kernel.Left:D3}) || "); - Span span = kernel.Values; - - for (int j = 0; j < kernel.Length; j++) - { - float value = span[j]; - bld.Append($"{value,8:F5}"); - bld.Append(" | "); - } - - bld.AppendLine(); + float value = span[j]; + bld.Append($"{value,8:F5}"); + bld.Append(" | "); } - return bld.ToString(); + bld.AppendLine(); } - private static TheoryData GenerateImageResizeData() - { - var result = new TheoryData(); + return bld.ToString(); + } - string[] resamplerNames = TestUtils.GetAllResamplerNames(false); + private static TheoryData GenerateImageResizeData() + { + var result = new TheoryData(); - int[] dimensionVals = - { - // Arbitrary, small dimensions: - 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, + string[] resamplerNames = TestUtils.GetAllResamplerNames(false); - // Typical image sizes: - 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, - 1920, 3032, 2008, 3072, 2304, 3264, 2448 - }; + int[] dimensionVals = + { + // Arbitrary, small dimensions: + 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, + + // Typical image sizes: + 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, + 1920, 3032, 2008, 3072, 2304, 3264, 2448 + }; - IOrderedEnumerable<(int S, int D)> source2Dest = dimensionVals - .SelectMany(s => dimensionVals.Select(d => (s, d))) - .OrderBy(x => x.s + x.d); + IOrderedEnumerable<(int S, int D)> source2Dest = dimensionVals + .SelectMany(s => dimensionVals.Select(d => (s, d))) + .OrderBy(x => x.s + x.d); - foreach (string resampler in resamplerNames) + foreach (string resampler in resamplerNames) + { + foreach ((int S, int D) x in source2Dest) { - foreach ((int S, int D) x in source2Dest) - { - result.Add(resampler, x.S, x.D); - } + result.Add(resampler, x.S, x.D); } - - return result; } + + return result; } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 49f460d942..38615f09e9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; @@ -10,678 +8,676 @@ 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 +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +public class ResizeTests { - [Trait("Category", "Processors")] - public class ResizeTests - { - private const PixelTypes CommonNonDefaultPixelTypes = - PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; + private const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; - public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames(); + public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames(); - public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; - public static readonly string[] SmokeTestResamplerNames = - { - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.Lanczos5), - }; + public static readonly string[] SmokeTestResamplerNames = + { + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.Lanczos5), + }; - private static readonly ImageComparer ValidatorComparer = - ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); + private static readonly ImageComparer ValidatorComparer = + ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); - [Fact] - public void Resize_PixelAgnostic() - { - string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + [Fact] + public void Resize_PixelAgnostic() + { + string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); - using (var image = Image.Load(filePath)) - { - image.Mutate(x => x.Resize(image.Size() / 2)); - string path = System.IO.Path.Combine( - TestEnvironment.CreateOutputDirectory(nameof(ResizeTests)), - nameof(this.Resize_PixelAgnostic) + ".png"); + using (var image = Image.Load(filePath)) + { + image.Mutate(x => x.Resize(image.Size() / 2)); + string path = System.IO.Path.Combine( + TestEnvironment.CreateOutputDirectory(nameof(ResizeTests)), + nameof(this.Resize_PixelAgnostic) + ".png"); - image.Save(path); - } + image.Save(path); } + } - [Theory(Skip = "Debug only, enable manually")] - [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)] - [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 : unmanaged, IPixel + [Theory(Skip = "Debug only, enable manually")] + [WithTestPatternImages(4000, 4000, PixelTypes.Rgba32, 300, 1024)] + [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 : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) { - if (!TestEnvironment.Is64BitProcess) - { - return; - } + return; + } - provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; + provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(destSize, destSize)); - image.DebugSave(provider, appendPixelTypeToFileName: false); - } + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(destSize, destSize)); + image.DebugSave(provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] - [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 : unmanaged, IPixel + [Theory] + [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] + [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 : 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()) { - // 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); - image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); - FormattableString outputInfo = $"({wN}÷{wD},{hN}÷{hD})"; - image.DebugSave(provider, outputInfo, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, outputInfo, appendPixelTypeToFileName: false); - } + var destSize = new Size(image.Width * wN / wD, image.Height * hN / hD); + image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); + FormattableString outputInfo = $"({wN}÷{wD},{hN}÷{hD})"; + image.DebugSave(provider, outputInfo, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, outputInfo, appendPixelTypeToFileName: false); } + } - private static readonly int SizeOfVector4 = Unsafe.SizeOf(); - - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)] - [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)] - [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)] - [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)] - [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)] - [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)] - public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( - TestImageProvider provider, - int workingBufferLimitInRows) - where TPixel : unmanaged, IPixel + private static readonly int SizeOfVector4 = Unsafe.SizeOf(); + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 50)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 60)] + [WithTestPatternImages(100, 400, PixelTypes.Rgba32, 110)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(79, 97, PixelTypes.Rgba32, 5)] + [WithTestPatternImages(47, 193, PixelTypes.Rgba32, 73)] + [WithTestPatternImages(23, 211, PixelTypes.Rgba32, 31)] + public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( + TestImageProvider provider, + int workingBufferLimitInRows) + where TPixel : unmanaged, IPixel + { + using (Image image0 = provider.GetImage()) { - using (Image image0 = provider.GetImage()) - { - Size destSize = image0.Size() / 4; + Size destSize = image0.Size() / 4; - var configuration = Configuration.CreateDefaultInstance(); + var configuration = Configuration.CreateDefaultInstance(); - int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; - var allocator = new TestMemoryAllocator(); - allocator.EnableNonThreadSafeLogging(); - configuration.MemoryAllocator = allocator; - configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; + int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; + var allocator = new TestMemoryAllocator(); + allocator.EnableNonThreadSafeLogging(); + configuration.MemoryAllocator = allocator; + configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - var verticalKernelMap = ResizeKernelMap.Calculate( - default, - destSize.Height, - image0.Height, - Configuration.Default.MemoryAllocator); - int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; - verticalKernelMap.Dispose(); + var verticalKernelMap = ResizeKernelMap.Calculate( + default, + destSize.Height, + image0.Height, + Configuration.Default.MemoryAllocator); + int minimumWorkerAllocationInBytes = verticalKernelMap.MaxDiameter * 2 * destSize.Width * SizeOfVector4; + verticalKernelMap.Dispose(); - using (Image image = image0.Clone(configuration)) - { - image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); + using (Image image = image0.Clone(configuration)) + { + image.Mutate(x => x.Resize(destSize, KnownResamplers.Bicubic, false)); - image.DebugSave( - provider, - testOutputDetails: workingBufferLimitInRows, - appendPixelTypeToFileName: false); - image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.004f), - provider, - testOutputDetails: workingBufferLimitInRows, - appendPixelTypeToFileName: false); + image.DebugSave( + provider, + testOutputDetails: workingBufferLimitInRows, + appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.004f), + provider, + testOutputDetails: workingBufferLimitInRows, + appendPixelTypeToFileName: false); - Assert.NotEmpty(allocator.AllocationLog); + Assert.NotEmpty(allocator.AllocationLog); - int maxAllocationSize = allocator.AllocationLog.Where( - e => e.ElementType == typeof(Vector4)).Max(e => e.LengthInBytes); + int maxAllocationSize = allocator.AllocationLog.Where( + e => e.ElementType == typeof(Vector4)).Max(e => e.LengthInBytes); - Assert.True(maxAllocationSize <= Math.Max(workingBufferSizeHintInBytes, minimumWorkerAllocationInBytes)); - } + Assert.True(maxAllocationSize <= Math.Max(workingBufferSizeHintInBytes, minimumWorkerAllocationInBytes)); } } + } - [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)); + [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(); + // 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; + // 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}"); + using Image actual = provider.GetImage(); + actual.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + actual.DebugSave(provider, $"{workingBufferLimitInRows}-{bufferCapacityInRows}"); - ImageComparer.Exact.VerifySimilarity(expected, actual); - } + ImageComparer.Exact.VerifySimilarity(expected, actual); + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Resize_Compand(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void Resize_Compand(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Size() / 2, true)); + image.Mutate(x => x.Resize(image.Size() / 2, true)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); } + } - [Theory] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] - public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) - where TPixel : unmanaged, IPixel - { - string details = compand ? "Compand" : string.Empty; + [Theory] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] + public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) + where TPixel : unmanaged, IPixel + { + string details = compand ? "Compand" : string.Empty; - provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2, compand), - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2, compand), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } - [Theory] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] - public void Resize_PremultiplyAlpha(TestImageProvider provider, bool premultiplyAlpha) - where TPixel : unmanaged, IPixel - { - string details = premultiplyAlpha ? "On" : "Off"; + [Theory] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32, true)] + public void Resize_PremultiplyAlpha(TestImageProvider provider, bool premultiplyAlpha) + where TPixel : unmanaged, IPixel + { + string details = premultiplyAlpha ? "On" : "Off"; - provider.RunValidatingProcessorTest( - x => + provider.RunValidatingProcessorTest( + x => + { + var resizeOptions = new ResizeOptions() { - var resizeOptions = new ResizeOptions() - { - Size = x.GetCurrentSize() / 2, - Mode = ResizeMode.Crop, - Sampler = KnownResamplers.Bicubic, - Compand = false, - PremultiplyAlpha = premultiplyAlpha - }; - x.Resize(resizeOptions); - }, - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + Size = x.GetCurrentSize() / 2, + Mode = ResizeMode.Crop, + Sampler = KnownResamplers.Bicubic, + Compand = false, + PremultiplyAlpha = premultiplyAlpha + }; + x.Resize(resizeOptions); + }, + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void Resize_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void Resize_IsAppliedToAllFrames(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, KnownResamplers.Bicubic)); - // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( - image.DebugSave(provider, extension: "gif"); - } + // Comparer fights decoder with gif-s. Could not use CompareToReferenceOutput here :( + image.DebugSave(provider, extension: "gif"); } + } - [Theory] - [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] - public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); - } + [Theory] + [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] + public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] - public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] + public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image0 = provider.GetImage()) { - using (Image image0 = provider.GetImage()) - { - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); - using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) - { - Assert.ThrowsAny( - () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); }); - } + using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) + { + Assert.ThrowsAny( + () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); }); } } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 1)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 4)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 8)] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, -1)] - public void Resize_WorksWithAllParallelismLevels( - TestImageProvider provider, - int maxDegreeOfParallelism) - where TPixel : unmanaged, IPixel - { - provider.Configuration.MaxDegreeOfParallelism = - maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 1)] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 4)] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, 8)] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24, -1)] + public void Resize_WorksWithAllParallelismLevels( + TestImageProvider provider, + int maxDegreeOfParallelism) + where TPixel : unmanaged, IPixel + { + provider.Configuration.MaxDegreeOfParallelism = + maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; - FormattableString details = $"MDP{maxDegreeOfParallelism}"; + FormattableString details = $"MDP{maxDegreeOfParallelism}"; - provider.RunValidatingProcessorTest( - x => x.Resize(x.GetCurrentSize() / 2), - details, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), PixelTypes.Rgba32 | PixelTypes.Rgb24, 0.5f, null, null)] - [WithFileCollection( - nameof(CommonTestImages), - nameof(SmokeTestResamplerNames), - PixelTypes.Rgba32 | PixelTypes.Rgb24, - 0.3f, - null, - null)] - [WithFileCollection( - nameof(CommonTestImages), - nameof(SmokeTestResamplerNames), - PixelTypes.Rgba32 | PixelTypes.Rgb24, - 1.8f, - null, - null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 0.5f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 1f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, PixelTypes.Rgba32, 8f, null, null)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, PixelTypes.Rgba32, null, 100, 99)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, PixelTypes.Rgba32, null, 300, 480)] - [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, PixelTypes.Rgba32, null, 301, 100)] - public void Resize_WorksWithAllResamplers( - TestImageProvider provider, - string samplerName, - float? ratio, - int? specificDestWidth, - int? specificDestHeight) - where TPixel : unmanaged, IPixel - { - IResampler sampler = TestUtils.GetResampler(samplerName); + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(AllResamplerNames), PixelTypes.Rgba32 | PixelTypes.Rgb24, 0.5f, null, null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + PixelTypes.Rgba32 | PixelTypes.Rgb24, + 0.3f, + null, + null)] + [WithFileCollection( + nameof(CommonTestImages), + nameof(SmokeTestResamplerNames), + PixelTypes.Rgba32 | PixelTypes.Rgb24, + 1.8f, + null, + null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 0.5f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 100, 100, PixelTypes.Rgba32, 1f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 50, 50, PixelTypes.Rgba32, 8f, null, null)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 201, 199, PixelTypes.Rgba32, null, 100, 99)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 301, 1180, PixelTypes.Rgba32, null, 300, 480)] + [WithTestPatternImages(nameof(SmokeTestResamplerNames), 49, 80, PixelTypes.Rgba32, null, 301, 100)] + public void Resize_WorksWithAllResamplers( + TestImageProvider provider, + string samplerName, + float? ratio, + int? specificDestWidth, + int? specificDestHeight) + where TPixel : unmanaged, IPixel + { + IResampler sampler = TestUtils.GetResampler(samplerName); - // NearestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit - // most likely because of differences in numeric behavior. - // The difference is well visible when comparing output for - // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png - // TODO: Should we investigate this? - bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess - && TestEnvironment.NetCoreVersion == null - && sampler is NearestNeighborResampler; + // NearestNeighbourResampler is producing slightly different results With classic .NET framework on 32bit + // most likely because of differences in numeric behavior. + // The difference is well visible when comparing output for + // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png + // TODO: Should we investigate this? + bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess + && TestEnvironment.NetCoreVersion == null + && sampler is NearestNeighborResampler; - var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); + var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); - // Let's make the working buffer size non-default: - provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4; + // Let's make the working buffer size non-default: + provider.Configuration.WorkingBufferSizeHintInBytes = 16 * 1024 * SizeOfVector4; - provider.RunValidatingProcessorTest( - ctx => + 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; - }, - comparer, - appendPixelTypeToFileName: false); - } + ctx.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, + comparer, + appendPixelTypeToFileName: false); + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeFromSourceRectangle(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeFromSourceRectangle(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var sourceRectangle = new Rectangle( - image.Width / 8, - image.Height / 8, - image.Width / 4, - image.Height / 4); - var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - - image.Mutate( - x => x.Resize( - image.Width, - image.Height, - KnownResamplers.Bicubic, - sourceRectangle, - destRectangle, - false)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + var sourceRectangle = new Rectangle( + image.Width / 8, + image.Height / 8, + image.Width / 4, + image.Height / 4); + var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + + image.Mutate( + x => x.Resize( + image.Width, + image.Height, + KnownResamplers.Bicubic, + sourceRectangle, + destRectangle, + false)); + + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeHeightAndKeepAspect(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeHeightAndKeepAspect(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(0, image.Height / 3, false)); + image.Mutate(x => x.Resize(0, image.Height / 3, false)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithTestPatternImages(10, 100, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(10, 100, PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(0, 5)); - Assert.Equal(1, image.Width); - Assert.Equal(5, image.Height); - } + image.Mutate(x => x.Resize(0, 5)); + Assert.Equal(1, image.Width); + Assert.Equal(5, image.Height); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWidthAndKeepAspect(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWidthAndKeepAspect(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Width / 3, 0, false)); + image.Mutate(x => x.Resize(image.Width / 3, 0, false)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithTestPatternImages(100, 10, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 10, PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(5, 0)); - Assert.Equal(5, image.Width); - Assert.Equal(1, image.Height); - } + image.Mutate(x => x.Resize(5, 0)); + Assert.Equal(5, image.Width); + Assert.Equal(1, image.Height); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithBoxPadMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithBoxPadMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad, - PadColor = Color.HotPink - }; + Size = new Size(image.Width + 200, image.Height + 200), + Mode = ResizeMode.BoxPad, + PadColor = Color.HotPink + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithCropHeightMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithCropHeightMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var options = new ResizeOptions { Size = new Size(image.Width, image.Height / 2) }; + var options = new ResizeOptions { Size = new Size(image.Width, image.Height / 2) }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithCropWidthMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithCropWidthMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) }; + var options = new ResizeOptions { Size = new Size(image.Width / 2, image.Height) }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void CanResizeLargeImageWithCropMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void CanResizeLargeImageWithCropMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(480, 600), - Mode = ResizeMode.Crop - }; + Size = new Size(480, 600), + Mode = ResizeMode.Crop + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithMaxMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithMaxMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max }; + var options = new ResizeOptions { Size = new Size(300, 300), Mode = ResizeMode.Max }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithMinMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithMinMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), - Mode = ResizeMode.Min - }; + Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), + Mode = ResizeMode.Min + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithPadMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithPadMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad, - PadColor = Color.Lavender - }; + Size = new Size(image.Width + 200, image.Height), + Mode = ResizeMode.Pad, + PadColor = Color.Lavender + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] - public void ResizeWithStretchMode(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32 | PixelTypes.Rgb24)] + public void ResizeWithStretchMode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + var options = new ResizeOptions { - var options = new ResizeOptions - { - Size = new Size(image.Width / 2, image.Height), - Mode = ResizeMode.Stretch - }; + Size = new Size(image.Width / 2, image.Height), + Mode = ResizeMode.Stretch + }; - image.Mutate(x => x.Resize(options)); + image.Mutate(x => x.Resize(options)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithFile(TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, PixelTypes.Rgb24)] - [WithFile(TestImages.Jpeg.Issues.ExifResize1049, PixelTypes.Rgb24)] - public void CanResizeExifIssueImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.Issues.ExifResize1049, PixelTypes.Rgb24)] + public void CanResizeExifIssueImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Test images are large so skip on 32bit for now. + if (!TestEnvironment.Is64BitProcess) { - // Test images are large so skip on 32bit for now. - if (!TestEnvironment.Is64BitProcess) - { - return; - } - - using (Image image = provider.GetImage()) - { - // Don't bother saving, we're testing the EXIF metadata updates. - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); - } + return; } - [Fact] - public void Issue1195() + using (Image image = provider.GetImage()) { - using (var image = new Image(2, 300)) - { - var size = new Size(50, 50); - image.Mutate(x => x - .Resize( - new ResizeOptions - { - Size = size, - Mode = ResizeMode.Max - })); - } + // Don't bother saving, we're testing the EXIF metadata updates. + image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); } + } - [Theory] - [InlineData(1, 1)] - [InlineData(4, 6)] - [InlineData(2, 10)] - [InlineData(8, 1)] - [InlineData(3, 7)] - public void Issue1342(int width, int height) + [Fact] + public void Issue1195() + { + using (var image = new Image(2, 300)) { - using (var image = new Image(1, 1)) - { - var size = new Size(width, height); - image.Mutate(x => x - .Resize( - new ResizeOptions - { - Size = size, - Sampler = KnownResamplers.NearestNeighbor - })); - - Assert.Equal(width, image.Width); - Assert.Equal(height, image.Height); - } + var size = new Size(50, 50); + image.Mutate(x => x + .Resize( + new ResizeOptions + { + Size = size, + Mode = ResizeMode.Max + })); } + } - [Theory] - [WithBasicTestPatternImages(20, 20, PixelTypes.Rgba32)] - public void Issue1625_LimitedAllocator(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [InlineData(1, 1)] + [InlineData(4, 6)] + [InlineData(2, 10)] + [InlineData(8, 1)] + [InlineData(3, 7)] + public void Issue1342(int width, int height) + { + using (var image = new Image(1, 1)) { - provider.LimitAllocatorBufferCapacity().InBytes(1000); - provider.RunValidatingProcessorTest( - x => x.Resize(30, 30), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); + var size = new Size(width, height); + image.Mutate(x => x + .Resize( + new ResizeOptions + { + Size = size, + Sampler = KnownResamplers.NearestNeighbor + })); + + Assert.Equal(width, image.Width); + Assert.Equal(height, image.Height); } } + + [Theory] + [WithBasicTestPatternImages(20, 20, PixelTypes.Rgba32)] + public void Issue1625_LimitedAllocator(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytes(1000); + provider.RunValidatingProcessorTest( + x => x.Resize(30, 30), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs index 9337b06f1e..55d26974aa 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -3,36 +3,34 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +public class RotateFlipTests { - [Trait("Category", "Processors")] - public class RotateFlipTests - { - public static readonly string[] FlipFiles = { TestImages.Bmp.F }; + public static readonly string[] FlipFiles = { TestImages.Bmp.F }; - public static readonly TheoryData RotateFlipValues - = new TheoryData - { - { RotateMode.None, FlipMode.Vertical }, - { RotateMode.None, FlipMode.Horizontal }, - { RotateMode.Rotate90, FlipMode.None }, - { RotateMode.Rotate180, FlipMode.None }, - { RotateMode.Rotate270, FlipMode.None }, - }; + public static readonly TheoryData RotateFlipValues + = new TheoryData + { + { RotateMode.None, FlipMode.Vertical }, + { RotateMode.None, FlipMode.Horizontal }, + { RotateMode.Rotate90, FlipMode.None }, + { RotateMode.Rotate180, FlipMode.None }, + { RotateMode.Rotate270, FlipMode.None }, + }; - [Theory] - [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 : unmanaged, IPixel + [Theory] + [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 : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.RotateFlip(rotateType, flipType)); - image.DebugSave(provider, string.Join("_", rotateType, flipType)); - } + image.Mutate(x => x.RotateFlip(rotateType, flipType)); + image.DebugSave(provider, string.Join("_", rotateType, flipType)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index 146a6b82d8..b150da1d8e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -3,45 +3,43 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class RotateTests { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class RotateTests + public static readonly TheoryData RotateAngles + = new TheoryData { - public static readonly TheoryData RotateAngles - = new TheoryData - { - 50, -50, 170, -170 - }; + 50, -50, 170, -170 + }; - public static readonly TheoryData RotateEnumValues - = new TheoryData - { - RotateMode.None, - RotateMode.Rotate90, - RotateMode.Rotate180, - RotateMode.Rotate270 - }; + public static readonly TheoryData RotateEnumValues + = new TheoryData + { + RotateMode.None, + RotateMode.Rotate90, + RotateMode.Rotate180, + RotateMode.Rotate270 + }; - [Theory] - [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] - public void Rotate_WithAngle(TestImageProvider provider, float value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] + public void Rotate_WithAngle(TestImageProvider provider, float value) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); + } - [Theory] - [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] - [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] - public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); - } + [Theory] + [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] + public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) + 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 08109ae3e6..1e217ae24a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -6,63 +6,60 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class SkewTests { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class SkewTests - { - private const PixelTypes CommonPixelTypes = PixelTypes.Bgra32 | PixelTypes.Rgb24; + private const PixelTypes CommonPixelTypes = PixelTypes.Bgra32 | PixelTypes.Rgb24; - public static readonly string[] ResamplerNames = new[] - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; + public static readonly string[] ResamplerNames = new[] + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; - public static readonly TheoryData SkewValues = new TheoryData - { - { 20, 10 }, - { -20, -10 } - }; + public static readonly TheoryData SkewValues = new TheoryData + { + { 20, 10 }, + { -20, -10 } + }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.01f); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.01f); - [Theory] - [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] - public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) - where TPixel : unmanaged, IPixel - { - provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); - } + [Theory] + [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] + public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) + where TPixel : unmanaged, IPixel + { + provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); + } - [Theory] - [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] - public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel - { - IResampler sampler = TestUtils.GetResampler(resamplerName); + [Theory] + [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] + public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) + where TPixel : unmanaged, IPixel + { + IResampler sampler = TestUtils.GetResampler(resamplerName); - provider.RunValidatingProcessorTest( - x => x.Skew(21, 32, sampler), - resamplerName, - comparer: ValidatorComparer, - appendPixelTypeToFileName: false); - } + provider.RunValidatingProcessorTest( + x => x.Skew(21, 32, sampler), + resamplerName, + comparer: ValidatorComparer, + appendPixelTypeToFileName: false); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs index 0315e896d8..de02502e2b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs @@ -3,57 +3,54 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Extensions.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; + +[Trait("Category", "Processors")] +[GroupOutput("Transforms")] +public class SwizzleTests { - [Trait("Category", "Processors")] - [GroupOutput("Transforms")] - public class SwizzleTests + private struct InvertXAndYSwizzler : ISwizzler { - private struct InvertXAndYSwizzler : ISwizzler + public InvertXAndYSwizzler(Size sourceSize) { - public InvertXAndYSwizzler(Size sourceSize) - { - this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); - } + this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); + } - public Size DestinationSize { get; } + public Size DestinationSize { get; } - public Point Transform(Point point) - => new Point(point.Y, point.X); - } + public Point Transform(Point point) + => new Point(point.Y, point.X); + } - [Theory] - [WithTestPatternImages(20, 37, PixelTypes.Rgba32)] - [WithTestPatternImages(53, 37, PixelTypes.Byte4)] - [WithTestPatternImages(17, 32, PixelTypes.Rgba32)] - public void InvertXAndYSwizzle(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image expectedImage = provider.GetImage(); - using Image image = provider.GetImage(); + [Theory] + [WithTestPatternImages(20, 37, PixelTypes.Rgba32)] + [WithTestPatternImages(53, 37, PixelTypes.Byte4)] + [WithTestPatternImages(17, 32, PixelTypes.Rgba32)] + public void InvertXAndYSwizzle(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image expectedImage = provider.GetImage(); + using Image image = provider.GetImage(); - image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height)))); + image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height)))); - image.DebugSave( - provider, - nameof(InvertXAndYSwizzler), - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); + image.DebugSave( + provider, + nameof(InvertXAndYSwizzler), + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); - image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height)))); + image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height)))); - image.DebugSave( - provider, - "Unswizzle", - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: true); + image.DebugSave( + provider, + "Unswizzle", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); - ImageComparer.Exact.VerifySimilarity(expectedImage, image); - } + ImageComparer.Exact.VerifySimilarity(expectedImage, image); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 30c0ee83a3..4b1ca92dd6 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -4,68 +4,67 @@ using System.Numerics; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +public class AffineTransformBuilderTests : TransformBuilderTestBase { - public class AffineTransformBuilderTests : TransformBuilderTestBase - { - protected override AffineTransformBuilder CreateBuilder() - => new AffineTransformBuilder(); + protected override AffineTransformBuilder CreateBuilder() + => new AffineTransformBuilder(); - protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) - => builder.AppendRotationDegrees(degrees); + protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees) + => builder.AppendRotationDegrees(degrees); - protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees, Vector2 origin) - => builder.AppendRotationDegrees(degrees, origin); + protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees, Vector2 origin) + => builder.AppendRotationDegrees(degrees, origin); - protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) - => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) + => builder.AppendRotationRadians(radians); - protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) - => builder.AppendRotationRadians(radians, origin); + protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) + => builder.AppendRotationRadians(radians, origin); - protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) - => builder.AppendScale(scale); + protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) + => builder.AppendScale(scale); - protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY) + => builder.AppendSkewDegrees(degreesX, degreesY); - protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) - => builder.AppendSkewDegrees(degreesX, degreesY, origin); + protected override void AppendSkewDegrees(AffineTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) + => builder.AppendSkewDegrees(degreesX, degreesY, origin); - protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) - => builder.AppendSkewRadians(radiansX, radiansY); + protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) + => builder.AppendSkewRadians(radiansX, radiansY); - protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.AppendSkewRadians(radiansX, radiansY, origin); + protected override void AppendSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.AppendSkewRadians(radiansX, radiansY, origin); - protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) - => builder.AppendTranslation(translate); + protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) + => builder.AppendTranslation(translate); - protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) - => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) + => builder.PrependRotationRadians(radians); - protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) - => builder.PrependRotationRadians(radians, origin); + protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians, Vector2 origin) + => builder.PrependRotationRadians(radians, origin); - protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) - => builder.PrependScale(scale); + protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) + => builder.PrependScale(scale); - protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) - => builder.PrependSkewRadians(radiansX, radiansY); + protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY) + => builder.PrependSkewRadians(radiansX, radiansY); - protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.PrependSkewRadians(radiansX, radiansY, origin); + protected override void PrependSkewRadians(AffineTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.PrependSkewRadians(radiansX, radiansY, origin); - protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) - => builder.PrependTranslation(translate); + protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) + => builder.PrependTranslation(translate); - protected override Vector2 Execute( - AffineTransformBuilder builder, - Rectangle rectangle, - Vector2 sourcePoint) - { - Matrix3x2 matrix = builder.BuildMatrix(rectangle); - return Vector2.Transform(sourcePoint, matrix); - } + protected override Vector2 Execute( + AffineTransformBuilder builder, + Rectangle rectangle, + Vector2 sourcePoint) + { + Matrix3x2 matrix = builder.BuildMatrix(rectangle); + return Vector2.Transform(sourcePoint, matrix); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index b30ebf0639..fb2eb9db62 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -3,18 +3,16 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class AutoOrientTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class AutoOrientTests : BaseImageOperationsExtensionTest + [Fact] + public void AutoOrient_AutoOrientProcessor() { - [Fact] - public void AutoOrient_AutoOrientProcessor() - { - this.operations.AutoOrient(); - this.Verify(); - } + this.operations.AutoOrient(); + this.Verify(); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index 4a468ab19f..30c89ec339 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -1,44 +1,41 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class CropTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class CropTest : BaseImageOperationsExtensionTest + [Theory] + [InlineData(10, 10)] + [InlineData(12, 123)] + public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) { - [Theory] - [InlineData(10, 10)] - [InlineData(12, 123)] - public void CropWidthHeightCropProcessorWithRectangleSet(int width, int height) - { - this.operations.Crop(width, height); - CropProcessor processor = this.Verify(); + this.operations.Crop(width, height); + CropProcessor processor = this.Verify(); - Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); - } + Assert.Equal(new Rectangle(0, 0, width, height), processor.CropRectangle); + } - [Theory] - [InlineData(10, 10, 2, 6)] - [InlineData(12, 123, 6, 2)] - public void CropRectangleCropProcessorWithRectangleSet(int x, int y, int width, int height) - { - var cropRectangle = new Rectangle(x, y, width, height); - this.operations.Crop(cropRectangle); - CropProcessor processor = this.Verify(); + [Theory] + [InlineData(10, 10, 2, 6)] + [InlineData(12, 123, 6, 2)] + public void CropRectangleCropProcessorWithRectangleSet(int x, int y, int width, int height) + { + var cropRectangle = new Rectangle(x, y, width, height); + this.operations.Crop(cropRectangle); + CropProcessor processor = this.Verify(); - Assert.Equal(cropRectangle, processor.CropRectangle); - } + Assert.Equal(cropRectangle, processor.CropRectangle); + } - [Fact] - public void CropRectangleWithInvalidBoundsThrowsException() - { - var cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5); - Assert.Throws(() => this.operations.Crop(cropRectangle)); - } + [Fact] + public void CropRectangleWithInvalidBoundsThrowsException() + { + var cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5); + Assert.Throws(() => this.operations.Crop(cropRectangle)); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs index 3b64ea46ba..061fb7a81d 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -3,22 +3,20 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class EntropyCropTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class EntropyCropTest : BaseImageOperationsExtensionTest + [Theory] + [InlineData(0.5F)] + [InlineData(.2F)] + public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) { - [Theory] - [InlineData(0.5F)] - [InlineData(.2F)] - public void EntropyCropThresholdFloatEntropyCropProcessorWithThreshold(float threshold) - { - this.operations.EntropyCrop(threshold); - EntropyCropProcessor processor = this.Verify(); + this.operations.EntropyCrop(threshold); + EntropyCropProcessor processor = this.Verify(); - Assert.Equal(threshold, processor.Threshold); - } + Assert.Equal(threshold, processor.Threshold); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index bbca121f04..b3f1aadc67 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -3,23 +3,21 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class FlipTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class FlipTests : BaseImageOperationsExtensionTest + [Theory] + [InlineData(FlipMode.None)] + [InlineData(FlipMode.Horizontal)] + [InlineData(FlipMode.Vertical)] + public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) { - [Theory] - [InlineData(FlipMode.None)] - [InlineData(FlipMode.Horizontal)] - [InlineData(FlipMode.Vertical)] - public void Flip_degreesFloat_RotateProcessorWithAnglesSetAndExpandTrue(FlipMode flip) - { - this.operations.Flip(flip); - FlipProcessor flipProcessor = this.Verify(); + this.operations.Flip(flip); + FlipProcessor flipProcessor = this.Verify(); - Assert.Equal(flip, flipProcessor.FlipMode); - } + Assert.Equal(flip, flipProcessor.FlipMode); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index f03bf49c76..ada52e1aeb 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -4,26 +4,23 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +[Trait("Category", "Processors")] +public class PadTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class PadTest : BaseImageOperationsExtensionTest + [Fact] + public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() { - [Fact] - public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() - { - int width = 500; - int height = 565; - IResampler sampler = KnownResamplers.NearestNeighbor; + int width = 500; + int height = 565; + IResampler sampler = KnownResamplers.NearestNeighbor; - this.operations.Pad(width, height); - ResizeProcessor resizeProcessor = this.Verify(); + this.operations.Pad(width, height); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - } + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index 58fc64ce17..7d0c0dc58f 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -3,67 +3,65 @@ using System.Numerics; using SixLabors.ImageSharp.Processing; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class ProjectiveTransformBuilderTests : TransformBuilderTestBase { - [Trait("Category", "Processors")] - public class ProjectiveTransformBuilderTests : TransformBuilderTestBase - { - protected override ProjectiveTransformBuilder CreateBuilder() - => new ProjectiveTransformBuilder(); + protected override ProjectiveTransformBuilder CreateBuilder() + => new ProjectiveTransformBuilder(); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) - => builder.AppendRotationDegrees(degrees); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees) + => builder.AppendRotationDegrees(degrees); - protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) - => builder.AppendRotationDegrees(degrees, origin); + protected override void AppendRotationDegrees(ProjectiveTransformBuilder builder, float degrees, Vector2 origin) + => builder.AppendRotationDegrees(degrees, origin); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) - => builder.AppendRotationRadians(radians); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians) + => builder.AppendRotationRadians(radians); - protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) - => builder.AppendRotationRadians(radians, origin); + protected override void AppendRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) + => builder.AppendRotationRadians(radians, origin); - protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); + protected override void AppendScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.AppendScale(scale); - protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) - => builder.AppendSkewDegrees(degreesX, degreesY); + protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY) + => builder.AppendSkewDegrees(degreesX, degreesY); - protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) - => builder.AppendSkewDegrees(degreesX, degreesY, origin); + protected override void AppendSkewDegrees(ProjectiveTransformBuilder builder, float degreesX, float degreesY, Vector2 origin) + => builder.AppendSkewDegrees(degreesX, degreesY, origin); - protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) - => builder.AppendSkewRadians(radiansX, radiansY); + protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) + => builder.AppendSkewRadians(radiansX, radiansY); - protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.AppendSkewRadians(radiansX, radiansY, origin); + protected override void AppendSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.AppendSkewRadians(radiansX, radiansY, origin); - protected override void AppendTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); + protected override void AppendTranslation(ProjectiveTransformBuilder builder, PointF translate) => builder.AppendTranslation(translate); - protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians) => builder.PrependRotationRadians(radians); - protected override void PrependScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); + protected override void PrependScale(ProjectiveTransformBuilder builder, SizeF scale) => builder.PrependScale(scale); - protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) - => builder.PrependSkewRadians(radiansX, radiansY); + protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY) + => builder.PrependSkewRadians(radiansX, radiansY); - protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) - => builder.PrependSkewRadians(radiansX, radiansY, origin); + protected override void PrependSkewRadians(ProjectiveTransformBuilder builder, float radiansX, float radiansY, Vector2 origin) + => builder.PrependSkewRadians(radiansX, radiansY, origin); - protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) - => builder.PrependTranslation(translate); + protected override void PrependTranslation(ProjectiveTransformBuilder builder, PointF translate) + => builder.PrependTranslation(translate); - protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => - builder.PrependRotationRadians(radians, origin); + protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => + builder.PrependRotationRadians(radians, origin); - protected override Vector2 Execute( - ProjectiveTransformBuilder builder, - Rectangle rectangle, - Vector2 sourcePoint) - { - Matrix4x4 matrix = builder.BuildMatrix(rectangle); - return Vector2.Transform(sourcePoint, matrix); - } + protected override Vector2 Execute( + ProjectiveTransformBuilder builder, + Rectangle rectangle, + Vector2 sourcePoint) + { + Matrix4x4 matrix = builder.BuildMatrix(rectangle); + return Vector2.Transform(sourcePoint, matrix); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 3ec909f803..a744a0ecc6 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -1,202 +1,199 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using System.Reflection; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class ProjectiveTransformTests { - [Trait("Category", "Processors")] - public class ProjectiveTransformTests - { - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3); - private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.03f, 3); + private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public static readonly TheoryData ResamplerNames = new TheoryData - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; - - public static readonly TheoryData TaperMatrixData = new TheoryData - { - { 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; - - [Theory] - [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] - public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : unmanaged, IPixel + public static readonly TheoryData ResamplerNames = new TheoryData + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + + public static readonly TheoryData TaperMatrixData = new TheoryData + { + { 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; + + [Theory] + [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithSampler(TestImageProvider provider, string resamplerName) + where TPixel : unmanaged, IPixel + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) { - IResampler sampler = GetResampler(resamplerName); - using (Image image = provider.GetImage()) - { - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F); + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F); - image.Mutate(i => i.Transform(builder, sampler)); + image.Mutate(i => i.Transform(builder, sampler)); - image.DebugSave(provider, resamplerName); - image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); - } + image.DebugSave(provider, resamplerName); + image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName); } + } - [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] - public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] + public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendTaper(taperSide, taperCorner, .5F); + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendTaper(taperSide, taperCorner, .5F); - image.Mutate(i => i.Transform(builder)); + image.Mutate(i => i.Transform(builder)); - FormattableString testOutputDetails = $"{taperSide}-{taperCorner}"; - image.DebugSave(provider, testOutputDetails); - image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); - } + FormattableString testOutputDetails = $"{taperSide}-{taperCorner}"; + image.DebugSave(provider, testOutputDetails); + image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); } + } - [Theory] - [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] - public void RawTransformMatchesDocumentedExample(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - // Printing some extra output to help investigating rounding errors: - this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); + [Theory] + [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] + public void RawTransformMatchesDocumentedExample(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // Printing some extra output to help investigating rounding errors: + this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); - // This test matches the output described in the example at - // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine - using (Image image = provider.GetImage()) - { - Matrix4x4 matrix = Matrix4x4.Identity; - matrix.M14 = 0.01F; + // This test matches the output described in the example at + // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine + using (Image image = provider.GetImage()) + { + Matrix4x4 matrix = Matrix4x4.Identity; + matrix.M14 = 0.01F; - ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() - .AppendMatrix(matrix); + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendMatrix(matrix); - image.Mutate(i => i.Transform(builder)); + image.Mutate(i => i.Transform(builder)); - image.DebugSave(provider); - image.CompareToReferenceOutput(TolerantComparer, provider); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(TolerantComparer, provider); } + } - [Theory] - [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] - public void PerspectiveTransformMatchesCSS(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] + public void PerspectiveTransformMatchesCSS(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // https://jsfiddle.net/dFrHS/545/ + // https://github.com/SixLabors/ImageSharp/issues/787 + using (Image image = provider.GetImage()) { - // 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); + 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); + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendMatrix(matrix); - image.Mutate(i => i.Transform(builder)); + image.Mutate(i => i.Transform(builder)); - image.DebugSave(provider); - image.CompareToReferenceOutput(TolerantComparer, provider); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(TolerantComparer, provider); } + } - [Fact] - public void Issue1911() - { - using var image = new Image(100, 100); - image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix4x4.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); - - Assert.Equal(99, image.Width); - Assert.Equal(100, image.Height); - } + [Fact] + public void Issue1911() + { + using var image = new Image(100, 100); + image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix4x4.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void Identity(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + Assert.Equal(99, image.Width); + Assert.Equal(100, image.Height); + } - Matrix4x4 m = Matrix4x4.Identity; - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void Identity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] - public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); + Matrix4x4 m = Matrix4x4.Identity; + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } - Matrix4x4 m = Matrix4x4.CreateRotationX(radians, new Vector3(50, 50, 1F)) * Matrix4x4.CreateRotationY(radians, new Vector3(50, 50, 1F)); - Rectangle r = new(25, 25, 50, 50); - image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); - image.DebugSave(provider, testOutputDetails: radians); - image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] + public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); - private static IResampler GetResampler(string name) - { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + Matrix4x4 m = Matrix4x4.CreateRotationX(radians, new Vector3(50, 50, 1F)) * Matrix4x4.CreateRotationY(radians, new Vector3(50, 50, 1F)); + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider, testOutputDetails: radians); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); + } - if (property is null) - { - throw new Exception($"No resampler named {name}"); - } + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - return (IResampler)property.GetValue(null); + if (property is null) + { + throw new Exception($"No resampler named {name}"); } + + return (IResampler)property.GetValue(null); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 3d084df079..f6c93ffd0e 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -5,105 +5,103 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class ResizeTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class ResizeTests : BaseImageOperationsExtensionTest + [Fact] + public void ResizeWidthAndHeight() { - [Fact] - public void ResizeWidthAndHeight() - { - int width = 50; - int height = 100; - this.operations.Resize(width, height); - ResizeProcessor resizeProcessor = this.Verify(); + int width = 50; + int height = 100; + this.operations.Resize(width, height); + ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - } + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); + } - [Fact] - public void ResizeWidthAndHeightAndSampler() - { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - this.operations.Resize(width, height, sampler); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - } + [Fact] + public void ResizeWidthAndHeightAndSampler() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + this.operations.Resize(width, height, sampler); + ResizeProcessor resizeProcessor = this.Verify(); + + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); + } - [Fact] - public void ResizeWidthAndHeightAndSamplerAndCompand() - { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - bool compand = true; - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - this.operations.Resize(width, height, sampler, compand); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - Assert.Equal(compand, resizeProcessor.Options.Compand); - } + [Fact] + public void ResizeWidthAndHeightAndSamplerAndCompand() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + this.operations.Resize(width, height, sampler, compand); + ResizeProcessor resizeProcessor = this.Verify(); + + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); + Assert.Equal(compand, resizeProcessor.Options.Compand); + } - [Fact] - public void ResizeWithOptions() + [Fact] + public void ResizeWithOptions() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + ResizeMode mode = ResizeMode.Stretch; + + var resizeOptions = new ResizeOptions { - int width = 50; - int height = 100; - IResampler sampler = KnownResamplers.Lanczos3; - bool compand = true; - ResizeMode mode = ResizeMode.Stretch; - - var resizeOptions = new ResizeOptions - { - Size = new Size(width, height), - Sampler = sampler, - Compand = compand, - Mode = mode - }; - - this.operations.Resize(resizeOptions); - ResizeProcessor resizeProcessor = this.Verify(); - - Assert.Equal(width, resizeProcessor.DestinationWidth); - Assert.Equal(height, resizeProcessor.DestinationHeight); - Assert.Equal(sampler, resizeProcessor.Options.Sampler); - Assert.Equal(compand, resizeProcessor.Options.Compand); - - // Ensure options are not altered. - Assert.Equal(width, resizeOptions.Size.Width); - Assert.Equal(height, resizeOptions.Size.Height); - Assert.Equal(sampler, resizeOptions.Sampler); - Assert.Equal(compand, resizeOptions.Compand); - Assert.Equal(mode, resizeOptions.Mode); - } + Size = new Size(width, height), + Sampler = sampler, + Compand = compand, + Mode = mode + }; + + this.operations.Resize(resizeOptions); + ResizeProcessor resizeProcessor = this.Verify(); + + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); + Assert.Equal(sampler, resizeProcessor.Options.Sampler); + Assert.Equal(compand, resizeProcessor.Options.Compand); + + // Ensure options are not altered. + Assert.Equal(width, resizeOptions.Size.Width); + Assert.Equal(height, resizeOptions.Size.Height); + Assert.Equal(sampler, resizeOptions.Sampler); + Assert.Equal(compand, resizeOptions.Compand); + Assert.Equal(mode, resizeOptions.Mode); + } - [Fact] - public void HwIntrinsics_Resize() + [Fact] + public void HwIntrinsics_Resize() + { + static void RunTest() { - static void RunTest() - { - using var image = new Image(50, 50); - image.Mutate(img => img.Resize(25, 25)); - - Assert.Equal(25, image.Width); - Assert.Equal(25, image.Height); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA); + using var image = new Image(50, 50); + image.Mutate(img => img.Resize(25, 25)); + + Assert.Equal(25, image.Width); + Assert.Equal(25, image.Height); } + + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs index 5662628f29..e6622ff386 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -3,34 +3,32 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class RotateFlipTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class RotateFlipTests : BaseImageOperationsExtensionTest + [Theory] + [InlineData(RotateMode.None, FlipMode.None, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.None, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.None, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.None, 270)] + [InlineData(RotateMode.None, FlipMode.Horizontal, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.Horizontal, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.Horizontal, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.Horizontal, 270)] + [InlineData(RotateMode.None, FlipMode.Vertical, 0)] + [InlineData(RotateMode.Rotate90, FlipMode.Vertical, 90)] + [InlineData(RotateMode.Rotate180, FlipMode.Vertical, 180)] + [InlineData(RotateMode.Rotate270, FlipMode.Vertical, 270)] + public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) { - [Theory] - [InlineData(RotateMode.None, FlipMode.None, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.None, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.None, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.None, 270)] - [InlineData(RotateMode.None, FlipMode.Horizontal, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.Horizontal, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.Horizontal, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.Horizontal, 270)] - [InlineData(RotateMode.None, FlipMode.Vertical, 0)] - [InlineData(RotateMode.Rotate90, FlipMode.Vertical, 90)] - [InlineData(RotateMode.Rotate180, FlipMode.Vertical, 180)] - [InlineData(RotateMode.Rotate270, FlipMode.Vertical, 270)] - public void RotateDegreesFloatRotateProcessorWithAnglesSet(RotateMode angle, FlipMode flip, float expectedAngle) - { - this.operations.RotateFlip(angle, flip); - RotateProcessor rotateProcessor = this.Verify(0); - FlipProcessor flipProcessor = this.Verify(1); + this.operations.RotateFlip(angle, flip); + RotateProcessor rotateProcessor = this.Verify(0); + FlipProcessor flipProcessor = this.Verify(1); - Assert.Equal(expectedAngle, rotateProcessor.Degrees); - Assert.Equal(flip, flipProcessor.FlipMode); - } + Assert.Equal(expectedAngle, rotateProcessor.Degrees); + Assert.Equal(flip, flipProcessor.FlipMode); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs index 2b2746002f..1d2947dfe8 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -4,35 +4,32 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +[Trait("Category", "Processors")] +public class RotateTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class RotateTests : BaseImageOperationsExtensionTest + [Theory] + [InlineData(85.6f)] + [InlineData(21)] + public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) { - [Theory] - [InlineData(85.6f)] - [InlineData(21)] - public void RotateDegreesFloatRotateProcessorWithAnglesSet(float angle) - { - this.operations.Rotate(angle); - RotateProcessor processor = this.Verify(); + this.operations.Rotate(angle); + RotateProcessor processor = this.Verify(); - Assert.Equal(angle, processor.Degrees); - } + Assert.Equal(angle, processor.Degrees); + } - [Theory] - [InlineData(RotateMode.None, 0)] - [InlineData(RotateMode.Rotate90, 90)] - [InlineData(RotateMode.Rotate180, 180)] - [InlineData(RotateMode.Rotate270, 270)] - public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) - { - this.operations.Rotate(angle); // is this api needed ??? - RotateProcessor processor = this.Verify(); + [Theory] + [InlineData(RotateMode.None, 0)] + [InlineData(RotateMode.Rotate90, 90)] + [InlineData(RotateMode.Rotate180, 180)] + [InlineData(RotateMode.Rotate270, 270)] + public void RotateRotateTypeRotateProcessorWithAnglesConvertedFromEnum(RotateMode angle, float expectedAngle) + { + this.operations.Rotate(angle); // is this api needed ??? + RotateProcessor processor = this.Verify(); - Assert.Equal(expectedAngle, processor.Degrees); - } + Assert.Equal(expectedAngle, processor.Degrees); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs index 6f980950fa..307a79b956 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -4,21 +4,18 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +[Trait("Category", "Processors")] +public class SkewTest : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class SkewTest : BaseImageOperationsExtensionTest + [Fact] + public void SkewXYCreateSkewProcessorWithAnglesSet() { - [Fact] - public void SkewXYCreateSkewProcessorWithAnglesSet() - { - this.operations.Skew(10, 20); - SkewProcessor processor = this.Verify(); + this.operations.Skew(10, 20); + SkewProcessor processor = this.Verify(); - Assert.Equal(10, processor.DegreesX); - Assert.Equal(20, processor.DegreesY); - } + Assert.Equal(10, processor.DegreesX); + Assert.Equal(20, processor.DegreesY); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs index 5f0ab71706..432bb5cacb 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/SwizzleTests.cs @@ -1,45 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Processing.Extensions.Transforms; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class SwizzleTests : BaseImageOperationsExtensionTest { - [Trait("Category", "Processors")] - public class SwizzleTests : BaseImageOperationsExtensionTest + private struct InvertXAndYSwizzler : ISwizzler { - private struct InvertXAndYSwizzler : ISwizzler + public InvertXAndYSwizzler(Size sourceSize) { - public InvertXAndYSwizzler(Size sourceSize) - { - this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); - } + this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width); + } - public Size DestinationSize { get; } + public Size DestinationSize { get; } - public Point Transform(Point point) - => new Point(point.Y, point.X); - } + public Point Transform(Point point) + => new Point(point.Y, point.X); + } - [Fact] - public void InvertXAndYSwizzlerSetsCorrectSizes() - { - int width = 5; - int height = 10; + [Fact] + public void InvertXAndYSwizzlerSetsCorrectSizes() + { + int width = 5; + int height = 10; - this.operations.Swizzle(new InvertXAndYSwizzler(new Size(width, height))); - SwizzleProcessor processor = this.Verify>(); + this.operations.Swizzle(new InvertXAndYSwizzler(new Size(width, height))); + SwizzleProcessor processor = this.Verify>(); - Assert.Equal(processor.Swizzler.DestinationSize.Width, height); - Assert.Equal(processor.Swizzler.DestinationSize.Height, width); + Assert.Equal(processor.Swizzler.DestinationSize.Width, height); + Assert.Equal(processor.Swizzler.DestinationSize.Height, width); - this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize)); - SwizzleProcessor processor2 = this.Verify>(1); + this.operations.Swizzle(new InvertXAndYSwizzler(processor.Swizzler.DestinationSize)); + SwizzleProcessor processor2 = this.Verify>(1); - Assert.Equal(processor2.Swizzler.DestinationSize.Width, width); - Assert.Equal(processor2.Swizzler.DestinationSize.Height, height); - } + Assert.Equal(processor2.Swizzler.DestinationSize.Width, width); + Assert.Equal(processor2.Swizzler.DestinationSize.Height, height); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index ed2af16cf7..c17fd23799 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -1,289 +1,285 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +[Trait("Category", "Processors")] +public abstract class TransformBuilderTestBase { - [Trait("Category", "Processors")] - public abstract class TransformBuilderTestBase - { - private static readonly ApproximateFloatComparer Comparer = new ApproximateFloatComparer(1e-6f); - - public static readonly TheoryData ScaleTranslate_Data = - new TheoryData - { - // scale, translate, source, expectedDest - { Vector2.One, Vector2.Zero, Vector2.Zero, Vector2.Zero }, - { Vector2.One, Vector2.Zero, new Vector2(10, 20), new Vector2(10, 20) }, - { Vector2.One, new Vector2(3, 1), new Vector2(10, 20), new Vector2(13, 21) }, - { new Vector2(2, 0.5f), new Vector2(3, 1), new Vector2(10, 20), new Vector2(23, 11) }, - }; - - [Theory] - [MemberData(nameof(ScaleTranslate_Data))] + private static readonly ApproximateFloatComparer Comparer = new ApproximateFloatComparer(1e-6f); + + public static readonly TheoryData ScaleTranslate_Data = + new TheoryData + { + // scale, translate, source, expectedDest + { Vector2.One, Vector2.Zero, Vector2.Zero, Vector2.Zero }, + { Vector2.One, Vector2.Zero, new Vector2(10, 20), new Vector2(10, 20) }, + { Vector2.One, new Vector2(3, 1), new Vector2(10, 20), new Vector2(13, 21) }, + { new Vector2(2, 0.5f), new Vector2(3, 1), new Vector2(10, 20), new Vector2(23, 11) }, + }; + + [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) + 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); - TBuilder builder = this.CreateBuilder(); + { + // These operations should be size-agnostic: + var size = new Size(123, 321); + TBuilder builder = this.CreateBuilder(); - this.AppendScale(builder, new SizeF(scale)); - this.AppendTranslation(builder, translate); + this.AppendScale(builder, new SizeF(scale)); + this.AppendTranslation(builder, translate); - Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); - Assert.True(Comparer.Equals(expectedDest, actualDest)); - } + Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); + Assert.True(Comparer.Equals(expectedDest, actualDest)); + } - public static readonly TheoryData TranslateScale_Data = - new TheoryData - { - // translate, scale, source, expectedDest - { Vector2.Zero, Vector2.One, Vector2.Zero, Vector2.Zero }, - { Vector2.Zero, Vector2.One, new Vector2(10, 20), new Vector2(10, 20) }, - { new Vector2(3, 1), new Vector2(2, 0.5f), new Vector2(10, 20), new Vector2(26, 10.5f) }, - }; - - [Theory] - [MemberData(nameof(TranslateScale_Data))] + public static readonly TheoryData TranslateScale_Data = + new TheoryData + { + // translate, scale, source, expectedDest + { Vector2.Zero, Vector2.One, Vector2.Zero, Vector2.Zero }, + { Vector2.Zero, Vector2.One, new Vector2(10, 20), new Vector2(10, 20) }, + { new Vector2(3, 1), new Vector2(2, 0.5f), new Vector2(10, 20), new Vector2(26, 10.5f) }, + }; + + [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) + 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); - TBuilder builder = this.CreateBuilder(); - - this.AppendTranslation(builder, translate); - this.AppendScale(builder, new SizeF(scale)); - - Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); - Assert.Equal(expectedDest, actualDest, Comparer); - } - - [Theory] - [InlineData(10, 20)] - [InlineData(-20, 10)] - public void LocationOffsetIsPrepended(int locationX, int locationY) - { - var rectangle = new Rectangle(locationX, locationY, 10, 10); - TBuilder builder = this.CreateBuilder(); - - this.AppendScale(builder, new SizeF(2, 2)); - - Vector2 actual = this.Execute(builder, rectangle, Vector2.One); - Vector2 expected = new Vector2(-locationX + 1, -locationY + 1) * 2; - - Assert.Equal(actual, expected, Comparer); - } - - [Theory] - [InlineData(200, 100, 10, 42, 84)] - [InlineData(200, 100, 100, 42, 84)] - [InlineData(100, 200, -10, 42, 84)] - public void AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter( - int width, - int height, - float degrees, - float x, - float y) - { - var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(); - - this.AppendRotationDegrees(builder, degrees); - - // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); - - var position = new Vector2(x, y); - var expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - - Assert.Equal(actual, expected, Comparer); - } - - [Theory] - [InlineData(200, 100, 10, 30, 61, 42, 84)] - [InlineData(200, 100, 100, 30, 10, 20, 84)] - [InlineData(100, 200, -10, 30, 20, 11, 84)] - public void AppendRotationDegrees_WithRotationCenter( - int width, - int height, - float degrees, - float cx, - float cy, - float x, - float y) - { - var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(); - - var centerPoint = new Vector2(cx, cy); - this.AppendRotationDegrees(builder, degrees, centerPoint); - - var matrix = Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); - - var position = new Vector2(x, y); - var expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - - Assert.Equal(actual, expected, Comparer); - } - - [Theory] - [InlineData(200, 100, 10, 10, 42, 84)] - [InlineData(200, 100, 100, 100, 42, 84)] - [InlineData(100, 200, -10, -10, 42, 84)] - public void AppendSkewDegrees_WithoutSpecificSkewCenter_SkewIsCenteredAroundImageCenter( - int width, - int height, - float degreesX, - float degreesY, - float x, - float y) - { - var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(); - - this.AppendSkewDegrees(builder, degreesX, degreesY); - - Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); - - var position = new Vector2(x, y); - var expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - Assert.Equal(actual, expected, Comparer); - } - - [Theory] - [InlineData(200, 100, 10, 10, 30, 61, 42, 84)] - [InlineData(200, 100, 100, 100, 30, 10, 20, 84)] - [InlineData(100, 200, -10, -10, 30, 20, 11, 84)] - public void AppendSkewDegrees_WithSkewCenter( - int width, - int height, - float degreesX, - float degreesY, - float cx, - float cy, - float x, - float y) - { - var size = new Size(width, height); - TBuilder builder = this.CreateBuilder(); - - var centerPoint = new Vector2(cx, cy); - this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); - - var matrix = Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); - - var position = new Vector2(x, y); - var expected = Vector2.Transform(position, matrix); - Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); - - Assert.Equal(actual, expected, Comparer); - } - - [Fact] - public void AppendPrependOpposite() - { - var rectangle = new Rectangle(-1, -1, 3, 3); - TBuilder b1 = this.CreateBuilder(); - TBuilder b2 = this.CreateBuilder(); - - const float pi = (float)Math.PI; - - // Forwards - this.AppendRotationRadians(b1, pi); - this.AppendSkewRadians(b1, pi, pi); - this.AppendScale(b1, new SizeF(2, 0.5f)); - this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f)); - this.AppendSkewRadians(b1, pi, pi / 2, new Vector2(-0.5f, -0.1f)); - this.AppendTranslation(b1, new PointF(123, 321)); - - // Backwards - this.PrependTranslation(b2, new PointF(123, 321)); - this.PrependSkewRadians(b2, pi, pi / 2, new Vector2(-0.5f, -0.1f)); - this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f)); - this.PrependScale(b2, new SizeF(2, 0.5f)); - this.PrependSkewRadians(b2, pi, pi); - this.PrependRotationRadians(b2, pi); - - Vector2 p1 = this.Execute(b1, rectangle, new Vector2(32, 65)); - Vector2 p2 = this.Execute(b2, rectangle, new Vector2(32, 65)); - - Assert.Equal(p1, p2, Comparer); - } - - [Theory] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(-1, 0)] - public void ThrowsForInvalidSizes(int width, int height) - { - var size = new Size(width, height); - - Assert.ThrowsAny( - () => - { - TBuilder builder = this.CreateBuilder(); - this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); - }); - } - - [Fact] - public void ThrowsForInvalidMatrix() - { - Assert.ThrowsAny( - () => + { + // Translate ans scale are size-agnostic: + var size = new Size(456, 432); + TBuilder builder = this.CreateBuilder(); + + this.AppendTranslation(builder, translate); + this.AppendScale(builder, new SizeF(scale)); + + Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source); + Assert.Equal(expectedDest, actualDest, Comparer); + } + + [Theory] + [InlineData(10, 20)] + [InlineData(-20, 10)] + public void LocationOffsetIsPrepended(int locationX, int locationY) + { + var rectangle = new Rectangle(locationX, locationY, 10, 10); + TBuilder builder = this.CreateBuilder(); + + this.AppendScale(builder, new SizeF(2, 2)); + + Vector2 actual = this.Execute(builder, rectangle, Vector2.One); + Vector2 expected = new Vector2(-locationX + 1, -locationY + 1) * 2; + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 42, 84)] + [InlineData(200, 100, 100, 42, 84)] + [InlineData(100, 200, -10, 42, 84)] + public void AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter( + int width, + int height, + float degrees, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(); + + this.AppendRotationDegrees(builder, degrees); + + // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness + Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 30, 61, 42, 84)] + [InlineData(200, 100, 100, 30, 10, 20, 84)] + [InlineData(100, 200, -10, 30, 20, 11, 84)] + public void AppendRotationDegrees_WithRotationCenter( + int width, + int height, + float degrees, + float cx, + float cy, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(); + + var centerPoint = new Vector2(cx, cy); + this.AppendRotationDegrees(builder, degrees, centerPoint); + + var matrix = Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 10, 42, 84)] + [InlineData(200, 100, 100, 100, 42, 84)] + [InlineData(100, 200, -10, -10, 42, 84)] + public void AppendSkewDegrees_WithoutSpecificSkewCenter_SkewIsCenteredAroundImageCenter( + int width, + int height, + float degreesX, + float degreesY, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(); + + this.AppendSkewDegrees(builder, degreesX, degreesY); + + Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + Assert.Equal(actual, expected, Comparer); + } + + [Theory] + [InlineData(200, 100, 10, 10, 30, 61, 42, 84)] + [InlineData(200, 100, 100, 100, 30, 10, 20, 84)] + [InlineData(100, 200, -10, -10, 30, 20, 11, 84)] + public void AppendSkewDegrees_WithSkewCenter( + int width, + int height, + float degreesX, + float degreesY, + float cx, + float cy, + float x, + float y) + { + var size = new Size(width, height); + TBuilder builder = this.CreateBuilder(); + + var centerPoint = new Vector2(cx, cy); + this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint); + + var matrix = Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint); + + var position = new Vector2(x, y); + var expected = Vector2.Transform(position, matrix); + Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position); + + Assert.Equal(actual, expected, Comparer); + } + + [Fact] + public void AppendPrependOpposite() + { + var rectangle = new Rectangle(-1, -1, 3, 3); + TBuilder b1 = this.CreateBuilder(); + TBuilder b2 = this.CreateBuilder(); + + const float pi = (float)Math.PI; + + // Forwards + this.AppendRotationRadians(b1, pi); + this.AppendSkewRadians(b1, pi, pi); + this.AppendScale(b1, new SizeF(2, 0.5f)); + this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f)); + this.AppendSkewRadians(b1, pi, pi / 2, new Vector2(-0.5f, -0.1f)); + this.AppendTranslation(b1, new PointF(123, 321)); + + // Backwards + this.PrependTranslation(b2, new PointF(123, 321)); + this.PrependSkewRadians(b2, pi, pi / 2, new Vector2(-0.5f, -0.1f)); + this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f)); + this.PrependScale(b2, new SizeF(2, 0.5f)); + this.PrependSkewRadians(b2, pi, pi); + this.PrependRotationRadians(b2, pi); + + Vector2 p1 = this.Execute(b1, rectangle, new Vector2(32, 65)); + Vector2 p2 = this.Execute(b2, rectangle, new Vector2(32, 65)); + + Assert.Equal(p1, p2, Comparer); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(-1, 0)] + public void ThrowsForInvalidSizes(int width, int height) + { + var size = new Size(width, height); + + Assert.ThrowsAny( + () => { TBuilder builder = this.CreateBuilder(); - this.AppendSkewDegrees(builder, 45, 45); - this.Execute(builder, new Rectangle(0, 0, 150, 150), Vector2.Zero); + this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero); }); - } + } + + [Fact] + public void ThrowsForInvalidMatrix() + { + Assert.ThrowsAny( + () => + { + TBuilder builder = this.CreateBuilder(); + this.AppendSkewDegrees(builder, 45, 45); + this.Execute(builder, new Rectangle(0, 0, 150, 150), Vector2.Zero); + }); + } - protected abstract TBuilder CreateBuilder(); + protected abstract TBuilder CreateBuilder(); - protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); + protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); - protected abstract void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 origin); + protected abstract void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 origin); - protected abstract void AppendRotationRadians(TBuilder builder, float radians); + protected abstract void AppendRotationRadians(TBuilder builder, float radians); - protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 origin); + protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 origin); - protected abstract void AppendScale(TBuilder builder, SizeF scale); + protected abstract void AppendScale(TBuilder builder, SizeF scale); - protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY); + protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY); - protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY, Vector2 origin); + protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY, Vector2 origin); - protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY); + protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY); - protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); + protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - protected abstract void AppendTranslation(TBuilder builder, PointF translate); + protected abstract void AppendTranslation(TBuilder builder, PointF translate); - protected abstract void PrependRotationRadians(TBuilder builder, float radians); + protected abstract void PrependRotationRadians(TBuilder builder, float radians); - protected abstract void PrependRotationRadians(TBuilder builder, float radians, Vector2 origin); + protected abstract void PrependRotationRadians(TBuilder builder, float radians, Vector2 origin); - protected abstract void PrependScale(TBuilder builder, SizeF scale); + protected abstract void PrependScale(TBuilder builder, SizeF scale); - protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY); + protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY); - protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); + protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin); - protected abstract void PrependTranslation(TBuilder builder, PointF translate); + protected abstract void PrependTranslation(TBuilder builder, PointF translate); - protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); - } + protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs index 5a7a44d28e..594eb7e7c8 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformsHelpersTest.cs @@ -4,33 +4,31 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Processing.Transforms +namespace SixLabors.ImageSharp.Tests.Processing.Transforms; + +[Trait("Category", "Processors")] +public class TransformsHelpersTest { - [Trait("Category", "Processors")] - public class TransformsHelpersTest + [Fact] + public void HelperCanChangeExifDataType() { - [Fact] - public void HelperCanChangeExifDataType() - { - int xy = 1; + int xy = 1; - using (var img = new Image(xy, xy)) - { - var profile = new ExifProfile(); - img.Metadata.ExifProfile = profile; - profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); - profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); + using (var img = new Image(xy, xy)) + { + var profile = new ExifProfile(); + img.Metadata.ExifProfile = profile; + profile.SetValue(ExifTag.PixelXDimension, xy + ushort.MaxValue); + profile.SetValue(ExifTag.PixelYDimension, xy + ushort.MaxValue); - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); + Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelXDimension).DataType); + Assert.Equal(ExifDataType.Long, profile.GetValue(ExifTag.PixelYDimension).DataType); - TransformProcessorHelpers.UpdateDimensionalMetadata(img); + TransformProcessorHelpers.UpdateDimensionalMetadata(img); - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); - Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); - } + Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelXDimension).DataType); + Assert.Equal(ExifDataType.Short, profile.GetValue(ExifTag.PixelYDimension).DataType); } } } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 869530f013..3aa879cf4d 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -1,49 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Processing; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; + +public class LoadResizeSaveProfilingBenchmarks : MeasureFixture { - public class LoadResizeSaveProfilingBenchmarks : MeasureFixture + public LoadResizeSaveProfilingBenchmarks(ITestOutputHelper output) + : base(output) { - public LoadResizeSaveProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } + } - [Theory(Skip = ProfilingSetup.SkipProfilingTests)] - [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] - public void LoadResizeSave(string imagePath) - { - var configuration = Configuration.CreateDefaultInstance(); - configuration.MaxDegreeOfParallelism = 1; + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [InlineData(TestImages.Jpeg.Baseline.Jpeg420Exif)] + public void LoadResizeSave(string imagePath) + { + var configuration = Configuration.CreateDefaultInstance(); + configuration.MaxDegreeOfParallelism = 1; - DecoderOptions options = new() - { - Configuration = configuration - }; + DecoderOptions options = new() + { + Configuration = configuration + }; - byte[] imageBytes = TestFile.Create(imagePath).Bytes; + byte[] imageBytes = TestFile.Create(imagePath).Bytes; - using var ms = new MemoryStream(); - this.Measure( - 30, - () => + using var ms = new MemoryStream(); + this.Measure( + 30, + () => + { + using (var image = Image.Load(options, imageBytes)) { - using (var image = Image.Load(options, imageBytes)) - { - image.Mutate(x => x.Resize(image.Size() / 4)); - image.SaveAsJpeg(ms); - } - - ms.Seek(0, SeekOrigin.Begin); - }); - } + image.Mutate(x => x.Resize(image.Size() / 4)); + image.SaveAsJpeg(ms); + } + + ms.Seek(0, SeekOrigin.Begin); + }); } } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs index fa8666891f..c054b9d6b1 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs @@ -3,15 +3,14 @@ // Uncomment to enable local profiling benchmarks. DO NOT PUSH TO MAIN! // #define PROFILING -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; + +public static class ProfilingSetup { - public static class ProfilingSetup - { - public const string SkipProfilingTests = + public const string SkipProfilingTests = #if PROFILING - null; + null; #else - "Profiling benchmark, enable manually!"; + "Profiling benchmark, enable manually!"; #endif - } } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs index a0fe79e609..f20ca8ce18 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -3,38 +3,35 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks +namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; + +public class ResizeProfilingBenchmarks : MeasureFixture { - public class ResizeProfilingBenchmarks : MeasureFixture - { - private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - public ResizeProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - this.configuration.MaxDegreeOfParallelism = 1; - } + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + this.configuration.MaxDegreeOfParallelism = 1; + } - public int ExecutionCount { get; set; } = 50; + 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, - () => + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] + [InlineData(100, 100)] + [InlineData(2000, 2000)] + public void ResizeBicubic(int width, int height) + { + this.Measure( + this.ExecutionCount, + () => + { + using (var image = new Image(this.configuration, width, height)) { - using (var image = new Image(this.configuration, width, height)) - { - image.Mutate(x => x.Resize(width / 5, height / 5)); - } - }); - } + image.Mutate(x => x.Resize(width / 5, height / 5)); + } + }); } } diff --git a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs index a9eaf7d811..f42726412d 100644 --- a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs +++ b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs @@ -1,99 +1,95 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.Memory; 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.Quantization +namespace SixLabors.ImageSharp.Tests.Quantization; + +public class PixelSamplingStrategyTests { - public class PixelSamplingStrategyTests - { - public static readonly TheoryData DefaultPixelSamplingStrategy_Data = new TheoryData() - { - { 100, 100, 1, 10000 }, - { 100, 100, 1, 5000 }, - { 100, 100, 10, 50000 }, - { 99, 100, 11, 30000 }, - { 97, 99, 11, 80000 }, - { 99, 100, 11, 20000 }, - { 99, 501, 20, 100000 }, - { 97, 500, 20, 10000 }, - { 103, 501, 20, 1000 }, - }; - - [Fact] - public void ExtensivePixelSamplingStrategy_EnumeratesAll() + public static readonly TheoryData DefaultPixelSamplingStrategy_Data = new TheoryData() { - using Image image = CreateTestImage(100, 100, 100); - var strategy = new ExtensivePixelSamplingStrategy(); + { 100, 100, 1, 10000 }, + { 100, 100, 1, 5000 }, + { 100, 100, 10, 50000 }, + { 99, 100, 11, 30000 }, + { 97, 99, 11, 80000 }, + { 99, 100, 11, 20000 }, + { 99, 501, 20, 100000 }, + { 97, 500, 20, 10000 }, + { 103, 501, 20, 1000 }, + }; + + [Fact] + public void ExtensivePixelSamplingStrategy_EnumeratesAll() + { + using Image image = CreateTestImage(100, 100, 100); + var strategy = new ExtensivePixelSamplingStrategy(); - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) - { - PaintWhite(region); - } + foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) + { + PaintWhite(region); + } - using Image expected = CreateTestImage(100, 100, 100, true); + using Image expected = CreateTestImage(100, 100, 100, true); - ImageComparer.Exact.VerifySimilarity(expected, image); - } + ImageComparer.Exact.VerifySimilarity(expected, image); + } - [Theory] - [WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)] - public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) - { - using Image image = CreateTestImage(width, height, noOfFrames); + [Theory] + [WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)] + public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) + { + using Image image = CreateTestImage(width, height, noOfFrames); - var strategy = new DefaultPixelSamplingStrategy(maximumNumberOfPixels, 0.1); + var strategy = new DefaultPixelSamplingStrategy(maximumNumberOfPixels, 0.1); - long visitedPixels = 0; - foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) - { - PaintWhite(region); - visitedPixels += region.Width * region.Height; - } + long visitedPixels = 0; + foreach (Buffer2DRegion region in strategy.EnumeratePixelRegions(image)) + { + PaintWhite(region); + visitedPixels += region.Width * region.Height; + } - image.DebugSaveMultiFrame( - dummyProvider, - $"W{width}_H{height}_noOfFrames_{noOfFrames}_maximumNumberOfPixels_{maximumNumberOfPixels}", - appendPixelTypeToFileName: false); + image.DebugSaveMultiFrame( + dummyProvider, + $"W{width}_H{height}_noOfFrames_{noOfFrames}_maximumNumberOfPixels_{maximumNumberOfPixels}", + appendPixelTypeToFileName: false); - int maximumPixels = image.Width * image.Height * image.Frames.Count / 10; - maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels); + int maximumPixels = image.Width * image.Height * image.Frames.Count / 10; + maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels); - // allow some inaccuracy: - double visitRatio = visitedPixels / (double)maximumPixels; - Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}"); - } + // allow some inaccuracy: + double visitRatio = visitedPixels / (double)maximumPixels; + Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}"); + } - private static void PaintWhite(Buffer2DRegion region) + private static void PaintWhite(Buffer2DRegion region) + { + var white = new L8(255); + for (int y = 0; y < region.Height; y++) { - var white = new L8(255); - for (int y = 0; y < region.Height; y++) - { - region.DangerousGetRowSpan(y).Fill(white); - } + region.DangerousGetRowSpan(y).Fill(white); } + } - private static Image CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) - { - L8 bg = paintWhite ? new L8(255) : default; - var image = new Image(width, height, bg); + private static Image CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) + { + L8 bg = paintWhite ? new L8(255) : default; + var image = new Image(width, height, bg); - for (int i = 1; i < noOfFrames; i++) + for (int i = 1; i < noOfFrames; i++) + { + ImageFrame f = image.Frames.CreateFrame(); + if (paintWhite) { - ImageFrame f = image.Frames.CreateFrame(); - if (paintWhite) - { - f.PixelBuffer.MemoryGroup.Fill(bg); - } + f.PixelBuffer.MemoryGroup.Fill(bg); } - - return image; } + + return image; } } diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 2a3fc800c9..59a0ad4274 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -1,147 +1,143 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public class QuantizedImageTests { - public class QuantizedImageTests - { - private Configuration Configuration => Configuration.Default; + private Configuration Configuration => Configuration.Default; - [Fact] - public void QuantizersDitherByDefault() - { - var werner = new WernerPaletteQuantizer(); - var webSafe = new WebSafePaletteQuantizer(); - var octree = new OctreeQuantizer(); - var wu = new WuQuantizer(); + [Fact] + public void QuantizersDitherByDefault() + { + var werner = new WernerPaletteQuantizer(); + var webSafe = new WebSafePaletteQuantizer(); + var octree = new OctreeQuantizer(); + var wu = new WuQuantizer(); - Assert.NotNull(werner.Options.Dither); - Assert.NotNull(webSafe.Options.Dither); - Assert.NotNull(octree.Options.Dither); - Assert.NotNull(wu.Options.Dither); + Assert.NotNull(werner.Options.Dither); + Assert.NotNull(webSafe.Options.Dither); + Assert.NotNull(octree.Options.Dither); + Assert.NotNull(wu.Options.Dither); - using (IQuantizer quantizer = werner.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } + using (IQuantizer quantizer = werner.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } - using (IQuantizer quantizer = webSafe.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } + using (IQuantizer quantizer = webSafe.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } - using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } + using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } - using (IQuantizer quantizer = wu.CreatePixelSpecificQuantizer(this.Configuration)) - { - Assert.NotNull(quantizer.Options.Dither); - } + using (IQuantizer quantizer = wu.CreatePixelSpecificQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); } + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void OctreeQuantizerYieldsCorrectTransparentPixel( - TestImageProvider provider, - bool dither) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void OctreeQuantizerYieldsCorrectTransparentPixel( + TestImageProvider provider, + bool dither) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - Assert.True(image[0, 0].Equals(default)); + Assert.True(image[0, 0].Equals(default)); - var options = new QuantizerOptions(); - if (!dither) - { - options.Dither = null; - } + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new OctreeQuantizer(options); + var quantizer = new OctreeQuantizer(options); - foreach (ImageFrame frame in image.Frames) + foreach (ImageFrame frame in image.Frames) + { + using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { - using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) - using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) - { - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); - } + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - Assert.True(image[0, 0].Equals(default)); + Assert.True(image[0, 0].Equals(default)); - var options = new QuantizerOptions(); - if (!dither) - { - options.Dither = null; - } + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new WuQuantizer(options); + var quantizer = new WuQuantizer(options); - foreach (ImageFrame frame in image.Frames) + foreach (ImageFrame frame in image.Frames) + { + using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { - using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(this.Configuration)) - using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) - { - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); - } + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } + } - // Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505 - [Theory] - [WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)] - public void Issue1505(TestImageProvider provider) - where TPixel : unmanaged, IPixel + // Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)] + public void Issue1505(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - var octreeQuantizer = new OctreeQuantizer(); - IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions() { MaxColors = 128 }); - ImageFrame frame = image.Frames[0]; - quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - } + var octreeQuantizer = new OctreeQuantizer(); + IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions() { MaxColors = 128 }); + ImageFrame frame = image.Frames[0]; + quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } + } - 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; - ReadOnlySpan paletteSpan = quantized.Palette.Span; - Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); + 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; + ReadOnlySpan paletteSpan = quantized.Palette.Span; + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); - for (int i = colorSpan.Length - 1; i >= 0; i--) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { - if (colorSpan[i].Equals(default)) - { - index = i; - } + index = i; } - - return index; } + + return index; } } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 1a8a917132..d6b1f1f980 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -1,198 +1,194 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -using Xunit; -namespace SixLabors.ImageSharp.Tests.Quantization +namespace SixLabors.ImageSharp.Tests.Quantization; + +public class WuQuantizerTests { - public class WuQuantizerTests + [Fact] + public void SinglePixelOpaque() { - [Fact] - public void SinglePixelOpaque() - { - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using var image = new Image(config, 1, 1, Color.Black); - ImageFrame frame = image.Frames.RootFrame; + using var image = new Image(config, 1, 1, Color.Black); + ImageFrame frame = image.Frames.RootFrame; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(1, result.Height); + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(1, result.Height); - Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); - Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); - } + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); + } - [Fact] - public void SinglePixelTransparent() - { - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + [Fact] + public void SinglePixelTransparent() + { + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using var image = new Image(config, 1, 1, default(Rgba32)); - ImageFrame frame = image.Frames.RootFrame; + using var image = new Image(config, 1, 1, default(Rgba32)); + ImageFrame frame = image.Frames.RootFrame; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(1, result.Height); + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(1, result.Height); - Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); - } + Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); + } - [Fact] - public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); + [Fact] + public void GrayScale() => TestScale(c => new Rgba32(c, c, c, 128)); - [Fact] - public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); + [Fact] + public void RedScale() => TestScale(c => new Rgba32(c, 0, 0, 128)); - [Fact] - public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); + [Fact] + public void GreenScale() => TestScale(c => new Rgba32(0, c, 0, 128)); - [Fact] - public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); + [Fact] + public void BlueScale() => TestScale(c => new Rgba32(0, 0, c, 128)); - [Fact] - public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); + [Fact] + public void AlphaScale() => TestScale(c => new Rgba32(0, 0, 0, c)); - [Fact] - public void Palette256() - { - using var image = new Image(1, 256); + [Fact] + public void Palette256() + { + using var image = new Image(1, 256); - 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); + 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); - 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(new QuantizerOptions { Dither = null }); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - ImageFrame frame = image.Frames.RootFrame; + ImageFrame frame = image.Frames.RootFrame; - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); - Assert.Equal(256, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(256, result.Height); + Assert.Equal(256, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); - using var actualImage = new Image(1, 256); + using var actualImage = new Image(1, 256); - actualImage.ProcessPixelRows(accessor => + actualImage.ProcessPixelRows(accessor => + { + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); - - for (int x = 0; x < accessor.Width; x++) - { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; - } - } - }); + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); - image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => - { - for (int y = 0; y < imageAccessor.Height; y++) + for (int x = 0; x < accessor.Width; x++) { - Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } - }); - } + } + }); - [Theory] - [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] - public void LowVariance(TestImageProvider provider) - where TPixel : unmanaged, IPixel + image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => { - // See https://github.com/SixLabors/ImageSharp/issues/866 - using (Image image = provider.GetImage()) + for (int y = 0; y < imageAccessor.Height; y++) { - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - ImageFrame frame = image.Frames.RootFrame; + Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); + } + }); + } - using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); - using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + [Theory] + [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] + public void LowVariance(TestImageProvider provider) + 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(new QuantizerOptions { Dither = null }); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(48, result.Palette.Length); - } + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(48, result.Palette.Length); } + } - private static void TestScale(Func pixelBuilder) + private static void TestScale(Func pixelBuilder) + { + using (var image = new Image(1, 256)) + using (var expectedImage = new Image(1, 256)) + using (var actualImage = new Image(1, 256)) { - using (var image = new Image(1, 256)) - using (var expectedImage = new Image(1, 256)) - using (var actualImage = new Image(1, 256)) + for (int i = 0; i < 256; i++) { - for (int i = 0; i < 256; i++) - { - byte c = (byte)i; - image[0, i] = pixelBuilder.Invoke(c); - } + byte c = (byte)i; + image[0, i] = pixelBuilder.Invoke(c); + } - for (int i = 0; i < 256; i++) - { - byte c = (byte)((i & ~7) + 4); - expectedImage[0, i] = pixelBuilder.Invoke(c); - } + for (int i = 0; i < 256; i++) + { + byte c = (byte)((i & ~7) + 4); + expectedImage[0, i] = pixelBuilder.Invoke(c); + } - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - ImageFrame frame = image.Frames.RootFrame; - using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config)) - using (IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) - { - Assert.Equal(4 * 8, result.Palette.Length); - Assert.Equal(1, result.Width); - Assert.Equal(256, result.Height); + ImageFrame frame = image.Frames.RootFrame; + using (IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(config)) + using (IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) + { + Assert.Equal(4 * 8, result.Palette.Length); + Assert.Equal(1, result.Width); + Assert.Equal(256, result.Height); - actualImage.ProcessPixelRows(accessor => + actualImage.ProcessPixelRows(accessor => + { + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); - for (int x = 0; x < accessor.Width; x++) - { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; - } + for (int x = 0; x < accessor.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; } - }); - } - - expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => - { - for (int y = 0; y < expectedAccessor.Height; y++) - { - Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); } }); } + + expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => + { + for (int y = 0; y < expectedAccessor.Height; y++) + { + Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); + } + }); } } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs index 53da71b147..8288a294dd 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -1,110 +1,109 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataArray { - internal static class IccTestDataArray + public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly object[][] UInt8TestData = + { + new object[] { UInt8, UInt8 } + }; + + public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt16_0, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9); + + public static readonly object[][] UInt16TestData = + { + new object[] { UInt16_Arr, UInt16_Val } + }; + + public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int16_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.Int16_0, + IccTestDataPrimitives.Int16_1, + IccTestDataPrimitives.Int16_2, + IccTestDataPrimitives.Int16_3, + IccTestDataPrimitives.Int16_4, + IccTestDataPrimitives.Int16_5, + IccTestDataPrimitives.Int16_6, + IccTestDataPrimitives.Int16_7, + IccTestDataPrimitives.Int16_8, + IccTestDataPrimitives.Int16_9); + + public static readonly object[][] Int16TestData = + { + new object[] { Int16_Arr, Int16_Val } + }; + + public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_0, + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt32_4, + IccTestDataPrimitives.UInt32_5, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.UInt32_7, + IccTestDataPrimitives.UInt32_8, + IccTestDataPrimitives.UInt32_9); + + public static readonly object[][] UInt32TestData = + { + new object[] { UInt32_Arr, UInt32_Val } + }; + + public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] Int32_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.Int32_0, + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.Int32_2, + IccTestDataPrimitives.Int32_3, + IccTestDataPrimitives.Int32_4, + IccTestDataPrimitives.Int32_5, + IccTestDataPrimitives.Int32_6, + IccTestDataPrimitives.Int32_7, + IccTestDataPrimitives.Int32_8, + IccTestDataPrimitives.Int32_9); + + public static readonly object[][] Int32TestData = + { + new object[] { Int32_Arr, Int32_Val } + }; + + public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt64_0, + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3, + IccTestDataPrimitives.UInt64_4, + IccTestDataPrimitives.UInt64_5, + IccTestDataPrimitives.UInt64_6, + IccTestDataPrimitives.UInt64_7, + IccTestDataPrimitives.UInt64_8, + IccTestDataPrimitives.UInt64_9); + + public static readonly object[][] UInt64TestData = { - public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly object[][] UInt8TestData = - { - new object[] { UInt8, UInt8 } - }; - - public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly byte[] UInt16_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt16_0, - IccTestDataPrimitives.UInt16_1, - IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_4, - IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_6, - IccTestDataPrimitives.UInt16_7, - IccTestDataPrimitives.UInt16_8, - IccTestDataPrimitives.UInt16_9); - - public static readonly object[][] UInt16TestData = - { - new object[] { UInt16_Arr, UInt16_Val } - }; - - public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly byte[] Int16_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Int16_0, - IccTestDataPrimitives.Int16_1, - IccTestDataPrimitives.Int16_2, - IccTestDataPrimitives.Int16_3, - IccTestDataPrimitives.Int16_4, - IccTestDataPrimitives.Int16_5, - IccTestDataPrimitives.Int16_6, - IccTestDataPrimitives.Int16_7, - IccTestDataPrimitives.Int16_8, - IccTestDataPrimitives.Int16_9); - - public static readonly object[][] Int16TestData = - { - new object[] { Int16_Arr, Int16_Val } - }; - - public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly byte[] UInt32_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_0, - IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3, - IccTestDataPrimitives.UInt32_4, - IccTestDataPrimitives.UInt32_5, - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.UInt32_7, - IccTestDataPrimitives.UInt32_8, - IccTestDataPrimitives.UInt32_9); - - public static readonly object[][] UInt32TestData = - { - new object[] { UInt32_Arr, UInt32_Val } - }; - - public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly byte[] Int32_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Int32_0, - IccTestDataPrimitives.Int32_1, - IccTestDataPrimitives.Int32_2, - IccTestDataPrimitives.Int32_3, - IccTestDataPrimitives.Int32_4, - IccTestDataPrimitives.Int32_5, - IccTestDataPrimitives.Int32_6, - IccTestDataPrimitives.Int32_7, - IccTestDataPrimitives.Int32_8, - IccTestDataPrimitives.Int32_9); - - public static readonly object[][] Int32TestData = - { - new object[] { Int32_Arr, Int32_Val } - }; - - public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - - public static readonly byte[] UInt64_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt64_0, - IccTestDataPrimitives.UInt64_1, - IccTestDataPrimitives.UInt64_2, - IccTestDataPrimitives.UInt64_3, - IccTestDataPrimitives.UInt64_4, - IccTestDataPrimitives.UInt64_5, - IccTestDataPrimitives.UInt64_6, - IccTestDataPrimitives.UInt64_7, - IccTestDataPrimitives.UInt64_8, - IccTestDataPrimitives.UInt64_9); - - public static readonly object[][] UInt64TestData = - { - new object[] { UInt64_Arr, UInt64_Val } - }; - } + new object[] { UInt64_Arr, UInt64_Val } + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs index 1b91a11b59..f76d2ba036 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -1,313 +1,311 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataCurves { - internal static class IccTestDataCurves + /// + /// Channels: 3 + /// + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve( + IccCurveMeasurementEncodings.StatusA, + new[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + 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 }, + }); + + /// + /// Channels: 3 + /// + 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); + + public static readonly object[][] ResponseCurveTestData = + { + new object[] { Response_Grad, Response_ValGrad, 3 }, + }; + + 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( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1); + + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3); + + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4); + + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5); + + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x04, + 0x00, 0x00, + }, + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Fix16_7); + + public static readonly object[][] ParametricCurveTestData = + { + new object[] { Parametric_Var1, Parametric_ValVar1 }, + new object[] { Parametric_Var2, Parametric_ValVar2 }, + new object[] { Parametric_Var3, Parametric_ValVar3 }, + new object[] { Parametric_Var4, Parametric_ValVar4 }, + new object[] { Parametric_Var5, Parametric_ValVar5 }, + }; + + // 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( + new byte[] + { + 0x00, 0x00, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4); + + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x01, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5); + + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x02, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6); + + public static readonly object[][] FormulaCurveSegmentTestData = + { + new object[] { Formula_Var1, Formula_ValVar1 }, + new object[] { Formula_Var2, Formula_ValVar2 }, + new object[] { Formula_Var3, Formula_ValVar3 }, + }; + + // 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( + IccTestDataPrimitives.UInt32_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[] Sampled_Grad2 = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_9, + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_1); + + public static readonly object[][] SampledCurveSegmentTestData = { - /// - /// Channels: 3 - /// - public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve( - IccCurveMeasurementEncodings.StatusA, - new[] - { - IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccTestDataNonPrimitives.XyzNumber_ValVar2, - 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 }, - }); - - /// - /// Channels: 3 - /// - 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); - - public static readonly object[][] ResponseCurveTestData = + new object[] { Sampled_Grad1, Sampled_ValGrad1 }, + new object[] { Sampled_Grad2, Sampled_ValGrad2 }, + }; + + 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( + new byte[] { - new object[] { Response_Grad, Response_ValGrad, 3 }, - }; - - 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( - new byte[] - { - 0x00, 0x00, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix16_1); - - public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x01, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3); - - public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x02, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4); - - public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_5); - - public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x04, - 0x00, 0x00, - }, - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_5, - IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_7); - - public static readonly object[][] ParametricCurveTestData = + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var1); + + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat( + new byte[] { - new object[] { Parametric_Var1, Parametric_ValVar1 }, - new object[] { Parametric_Var2, Parametric_ValVar2 }, - new object[] { Parametric_Var3, Parametric_ValVar3 }, - new object[] { Parametric_Var4, Parametric_ValVar4 }, - new object[] { Parametric_Var5, Parametric_ValVar5 }, - }; - - // 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( - new byte[] - { - 0x00, 0x00, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_1, - IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4); - - public static readonly byte[] Formula_Var2 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x01, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_1, - IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_5); - - public static readonly byte[] Formula_Var3 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x02, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_5, - IccTestDataPrimitives.Single_6); - - public static readonly object[][] FormulaCurveSegmentTestData = + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var2); + + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat( + new byte[] { - new object[] { Formula_Var1, Formula_ValVar1 }, - new object[] { Formula_Var2, Formula_ValVar2 }, - new object[] { Formula_Var3, Formula_ValVar3 }, - }; - - // 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( - IccTestDataPrimitives.UInt32_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[] Sampled_Grad2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_9, - IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_7, - IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_5, - IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_1); - - public static readonly object[][] SampledCurveSegmentTestData = + 0x70, 0x61, 0x72, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Formula_Var3); + + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat( + new byte[] { - new object[] { Sampled_Grad1, Sampled_ValGrad1 }, - new object[] { Sampled_Grad2, Sampled_ValGrad2 }, - }; - - 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( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - Formula_Var1); - - public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - Formula_Var2); - - public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat( - new byte[] - { - 0x70, 0x61, 0x72, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - Formula_Var3); - - public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat( - new byte[] - { - 0x73, 0x61, 0x6D, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - Sampled_Grad1); - - public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat( - new byte[] - { - 0x73, 0x61, 0x6D, 0x66, - 0x00, 0x00, 0x00, 0x00, - }, - Sampled_Grad2); - - public static readonly object[][] CurveSegmentTestData = + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad1); + + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat( + new byte[] + { + 0x73, 0x61, 0x6D, 0x66, + 0x00, 0x00, 0x00, 0x00, + }, + Sampled_Grad2); + + public static readonly object[][] CurveSegmentTestData = + { + new object[] { Segment_Formula1, Segment_ValFormula1 }, + new object[] { Segment_Formula2, Segment_ValFormula2 }, + new object[] { Segment_Formula3, Segment_ValFormula3 }, + new object[] { Segment_Sampled1, Segment_ValSampled1 }, + new object[] { Segment_Sampled2, Segment_ValSampled2 }, + }; + + 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 float[] { 0, 1 }, + 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 }); + + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat( + new byte[] + { + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula1, + Segment_Formula2, + Segment_Formula3); + + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat( + new byte[] { - new object[] { Segment_Formula1, Segment_ValFormula1 }, - new object[] { Segment_Formula2, Segment_ValFormula2 }, - new object[] { Segment_Formula3, Segment_ValFormula3 }, - new object[] { Segment_Sampled1, Segment_ValSampled1 }, - new object[] { Segment_Sampled2, Segment_ValSampled2 }, - }; - - 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 float[] { 0, 1 }, - 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 }); - - public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_0, - IccTestDataPrimitives.Single_1, - Segment_Formula1, - Segment_Formula2, - Segment_Formula3); - - public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_0, - IccTestDataPrimitives.Single_1, - Segment_Formula3, - Segment_Formula2, - Segment_Formula1); - - public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat( - new byte[] - { - 0x00, 0x03, - 0x00, 0x00, - }, - IccTestDataPrimitives.Single_0, - IccTestDataPrimitives.Single_1, - Segment_Sampled1, - Segment_Sampled2, - Segment_Sampled1); - - public static readonly object[][] OneDimensionalCurveTestData = + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Formula3, + Segment_Formula2, + Segment_Formula1); + + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat( + new byte[] { - new object[] { OneDimensional_Formula1, OneDimensional_ValFormula1 }, - new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, - new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, - }; - } + 0x00, 0x03, + 0x00, 0x00, + }, + IccTestDataPrimitives.Single_0, + IccTestDataPrimitives.Single_1, + Segment_Sampled1, + Segment_Sampled2, + Segment_Sampled1); + + public static readonly object[][] OneDimensionalCurveTestData = + { + new object[] { OneDimensional_Formula1, OneDimensional_ValFormula1 }, + new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, + new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index 7ed82a5d00..7a778f269b 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -3,239 +3,238 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataLut { - internal static class IccTestDataLut - { - public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); - public static readonly byte[] LUT8_Grad = CreateLUT8(); + public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); + public static readonly byte[] LUT8_Grad = CreateLUT8(); - private static IccLut CreateLUT8Val() + private static IccLut CreateLUT8Val() + { + float[] result = new float[256]; + for (int i = 0; i < 256; i++) { - float[] result = new float[256]; - for (int i = 0; i < 256; i++) - { - result[i] = i / 255f; - } - - return new IccLut(result); + result[i] = i / 255f; } - private static byte[] CreateLUT8() - { - byte[] result = new byte[256]; - for (int i = 0; i < 256; i++) - { - result[i] = (byte)i; - } + return new IccLut(result); + } - return result; + private static byte[] CreateLUT8() + { + byte[] result = new byte[256]; + for (int i = 0; i < 256; i++) + { + result[i] = (byte)i; } - public static readonly object[][] Lut8TestData = - { - new object[] { LUT8_Grad, LUT8_ValGrad }, - }; + return result; + } - public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] - { - 1f / ushort.MaxValue, - 2f / ushort.MaxValue, - 3f / ushort.MaxValue, - 4f / ushort.MaxValue, - 5f / ushort.MaxValue, - 6f / ushort.MaxValue, - 7f / ushort.MaxValue, - 8f / ushort.MaxValue, - 9f / ushort.MaxValue, - 32768f / ushort.MaxValue, - 1f - }); - - public static readonly byte[] LUT16_Grad = ArrayHelper.Concat( - IccTestDataPrimitives.UInt16_1, - IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_4, - IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_6, - IccTestDataPrimitives.UInt16_7, - IccTestDataPrimitives.UInt16_8, - IccTestDataPrimitives.UInt16_9, - IccTestDataPrimitives.UInt16_32768, - IccTestDataPrimitives.UInt16_Max); - - public static readonly object[][] Lut16TestData = - { - new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, - }; - - public static readonly IccClut CLUT8_ValGrad = new IccClut( - new float[][] - { - new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, - new float[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue }, - new float[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue }, - - new float[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue }, - new float[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue }, - new float[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue }, - - new float[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue }, - 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); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// Grid-point Count: { 3, 3 } - /// - public static readonly byte[] CLUT8_Grad = + public static readonly object[][] Lut8TestData = + { + new object[] { LUT8_Grad, LUT8_ValGrad }, + }; + + public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] + { + 1f / ushort.MaxValue, + 2f / ushort.MaxValue, + 3f / ushort.MaxValue, + 4f / ushort.MaxValue, + 5f / ushort.MaxValue, + 6f / ushort.MaxValue, + 7f / ushort.MaxValue, + 8f / ushort.MaxValue, + 9f / ushort.MaxValue, + 32768f / ushort.MaxValue, + 1f + }); + + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_4, + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_6, + IccTestDataPrimitives.UInt16_7, + IccTestDataPrimitives.UInt16_8, + IccTestDataPrimitives.UInt16_9, + IccTestDataPrimitives.UInt16_32768, + IccTestDataPrimitives.UInt16_Max); + + public static readonly object[][] Lut16TestData = + { + new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, + }; + + public static readonly IccClut CLUT8_ValGrad = new IccClut( + new float[][] { - 0x01, 0x02, 0x03, - 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, + new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, + new float[] { 4f / byte.MaxValue, 5f / byte.MaxValue, 6f / byte.MaxValue }, + new float[] { 7f / byte.MaxValue, 8f / byte.MaxValue, 9f / byte.MaxValue }, + + new float[] { 10f / byte.MaxValue, 11f / byte.MaxValue, 12f / byte.MaxValue }, + new float[] { 13f / byte.MaxValue, 14f / byte.MaxValue, 15f / byte.MaxValue }, + new float[] { 16f / byte.MaxValue, 17f / byte.MaxValue, 18f / byte.MaxValue }, + + new float[] { 19f / byte.MaxValue, 20f / byte.MaxValue, 21f / byte.MaxValue }, + 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); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT8_Grad = + { + 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, - 0x0A, 0x0B, 0x0C, - 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, + 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, - 0x16, 0x17, 0x18, - 0x19, 0x1A, 0x1B, - }; + 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, + }; - public static readonly object[][] Clut8TestData = - { - new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, - }; - - public static readonly IccClut CLUT16_ValGrad = new IccClut( - new float[][] - { - new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, - new float[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue }, - new float[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue }, - - new float[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue }, - new float[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue }, - new float[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue }, - - new float[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue }, - 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); - - /// - /// Input Channel Count: 2 - /// Output Channel Count: 3 - /// Grid-point Count: { 3, 3 } - /// - public static readonly byte[] CLUT16_Grad = + public static readonly object[][] Clut8TestData = + { + new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + public static readonly IccClut CLUT16_ValGrad = new IccClut( + new float[][] { - 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, - 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, - 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, + new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, + new float[] { 4f / ushort.MaxValue, 5f / ushort.MaxValue, 6f / ushort.MaxValue }, + new float[] { 7f / ushort.MaxValue, 8f / ushort.MaxValue, 9f / ushort.MaxValue }, + + new float[] { 10f / ushort.MaxValue, 11f / ushort.MaxValue, 12f / ushort.MaxValue }, + new float[] { 13f / ushort.MaxValue, 14f / ushort.MaxValue, 15f / ushort.MaxValue }, + new float[] { 16f / ushort.MaxValue, 17f / ushort.MaxValue, 18f / ushort.MaxValue }, + + new float[] { 19f / ushort.MaxValue, 20f / ushort.MaxValue, 21f / ushort.MaxValue }, + 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); + + /// + /// Input Channel Count: 2 + /// Output Channel Count: 3 + /// Grid-point Count: { 3, 3 } + /// + public static readonly byte[] CLUT16_Grad = + { + 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, + 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, + 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, - 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, - 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, - 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, + 0x00, 0x0A, 0x00, 0x0B, 0x00, 0x0C, + 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, + 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, - 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, - 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, - 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, - }; + 0x00, 0x13, 0x00, 0x14, 0x00, 0x15, + 0x00, 0x16, 0x00, 0x17, 0x00, 0x18, + 0x00, 0x19, 0x00, 0x1A, 0x00, 0x1B, + }; - public static readonly object[][] Clut16TestData = - { - new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, - }; - - public static readonly IccClut CLUTf32_ValGrad = new IccClut( - new float[][] - { - new float[] { 1f, 2f, 3f }, - new float[] { 4f, 5f, 6f }, - new float[] { 7f, 8f, 9f }, - - new float[] { 1f, 2f, 3f }, - new float[] { 4f, 5f, 6f }, - new float[] { 7f, 8f, 9f }, - - new float[] { 1f, 2f, 3f }, - new float[] { 4f, 5f, 6f }, - new float[] { 7f, 8f, 9f }, - }, - 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 object[][] ClutF32TestData = + public static readonly object[][] Clut16TestData = + { + new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; + + public static readonly IccClut CLUTf32_ValGrad = new IccClut( + new float[][] { - new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, - }; + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + + new float[] { 1f, 2f, 3f }, + new float[] { 4f, 5f, 6f }, + new float[] { 7f, 8f, 9f }, + }, + 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 object[][] ClutF32TestData = + { + new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, + }; - 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 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( - 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); + 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); - 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); + 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); - 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); + 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); - public static readonly object[][] ClutTestData = - { - new object[] { CLUT_8, CLUT_Val8, 2, 3, false }, - new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, - new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, - }; - } + public static readonly object[][] ClutTestData = + { + new object[] { CLUT_8, CLUT_Val8, 2, 3, false }, + new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, + new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs index 3c09ac6c39..94df8d69a6 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -3,151 +3,150 @@ using System.Numerics; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +using SixLabors.ImageSharp; + +internal static class IccTestDataMatrix { - using SixLabors.ImageSharp; + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValGrad = + { + { 1, 2, 3 }, + { 4, 5, 6 }, + { 7, 8, 9 }, + }; + + /// + /// 3x3 Matrix + /// + public static readonly float[,] Single_2DArray_ValIdentity = + { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 }, + }; + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValGrad = new Matrix4x4(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); + + /// + /// 3x3 Matrix + /// + public static readonly Matrix4x4 Single_Matrix4x4_ValIdentity = Matrix4x4.Identity; + + /// + /// 3x3 Matrix + /// + public static readonly DenseMatrix Single_DenseMatrix_ValGrad = new DenseMatrix(Single_2DArray_ValGrad); + + /// + /// 3x3 Matrix + /// + public static readonly DenseMatrix Single_DenseMatrix_ValIdentity = new DenseMatrix(Single_2DArray_ValIdentity); + + /// + /// 3x3 Matrix + /// + 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); + + /// + /// 3x3 Matrix + /// + 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); + + /// + /// 3x3 Matrix + /// + 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); + + public static readonly object[][] Matrix2D_FloatArrayTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_2DArray_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_2DArray_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_2DArray_ValGrad }, + }; + + public static readonly object[][] Matrix2D_DenseMatrixTestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_DenseMatrix_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_DenseMatrix_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_DenseMatrix_ValGrad }, + }; + + public static readonly object[][] Matrix2D_Matrix4x4TestData = + { + new object[] { Fix16_2D_Grad, 3, 3, false, Single_Matrix4x4_ValGrad }, + new object[] { Fix16_2D_Identity, 3, 3, false, Single_Matrix4x4_ValIdentity }, + new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, + }; + + /// + /// 3x1 Matrix + /// + public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + + /// + /// 3x1 Matrix + /// + public static readonly Vector3 Single_Vector3_ValGrad = new Vector3(1, 4, 7); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_7); + + /// + /// 3x1 Matrix + /// + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_7); + + public static readonly object[][] Matrix1D_ArrayTestData = + { + new object[] { Fix16_1D_Grad, 3, false, Single_1DArray_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_1DArray_ValGrad }, + }; - internal static class IccTestDataMatrix + public static readonly object[][] Matrix1D_Vector3TestData = { - /// - /// 3x3 Matrix - /// - public static readonly float[,] Single_2DArray_ValGrad = - { - { 1, 2, 3 }, - { 4, 5, 6 }, - { 7, 8, 9 }, - }; - - /// - /// 3x3 Matrix - /// - public static readonly float[,] Single_2DArray_ValIdentity = - { - { 1, 0, 0 }, - { 0, 1, 0 }, - { 0, 0, 1 }, - }; - - /// - /// 3x3 Matrix - /// - public static readonly Matrix4x4 Single_Matrix4x4_ValGrad = new Matrix4x4(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 0, 0, 0, 1); - - /// - /// 3x3 Matrix - /// - public static readonly Matrix4x4 Single_Matrix4x4_ValIdentity = Matrix4x4.Identity; - - /// - /// 3x3 Matrix - /// - public static readonly DenseMatrix Single_DenseMatrix_ValGrad = new DenseMatrix(Single_2DArray_ValGrad); - - /// - /// 3x3 Matrix - /// - public static readonly DenseMatrix Single_DenseMatrix_ValIdentity = new DenseMatrix(Single_2DArray_ValIdentity); - - /// - /// 3x3 Matrix - /// - 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); - - /// - /// 3x3 Matrix - /// - 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); - - /// - /// 3x3 Matrix - /// - 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); - - public static readonly object[][] Matrix2D_FloatArrayTestData = - { - new object[] { Fix16_2D_Grad, 3, 3, false, Single_2DArray_ValGrad }, - new object[] { Fix16_2D_Identity, 3, 3, false, Single_2DArray_ValIdentity }, - new object[] { Single_2D_Grad, 3, 3, true, Single_2DArray_ValGrad }, - }; - - public static readonly object[][] Matrix2D_DenseMatrixTestData = - { - new object[] { Fix16_2D_Grad, 3, 3, false, Single_DenseMatrix_ValGrad }, - new object[] { Fix16_2D_Identity, 3, 3, false, Single_DenseMatrix_ValIdentity }, - new object[] { Single_2D_Grad, 3, 3, true, Single_DenseMatrix_ValGrad }, - }; - - public static readonly object[][] Matrix2D_Matrix4x4TestData = - { - new object[] { Fix16_2D_Grad, 3, 3, false, Single_Matrix4x4_ValGrad }, - new object[] { Fix16_2D_Identity, 3, 3, false, Single_Matrix4x4_ValIdentity }, - new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, - }; - - /// - /// 3x1 Matrix - /// - public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; - - /// - /// 3x1 Matrix - /// - public static readonly Vector3 Single_Vector3_ValGrad = new Vector3(1, 4, 7); - - /// - /// 3x1 Matrix - /// - public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat( - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_7); - - /// - /// 3x1 Matrix - /// - public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat( - IccTestDataPrimitives.Single_1, - IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_7); - - public static readonly object[][] Matrix1D_ArrayTestData = - { - new object[] { Fix16_1D_Grad, 3, false, Single_1DArray_ValGrad }, - new object[] { Single_1D_Grad, 3, true, Single_1DArray_ValGrad }, - }; - - public static readonly object[][] Matrix1D_Vector3TestData = - { - new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, - new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, - }; - } + new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, + new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs index aef760eeda..2c5c432710 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -3,129 +3,128 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataMultiProcessElements { - internal static class IccTestDataMultiProcessElements + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccCurveSetProcessElement CurvePE_ValGrad = new IccCurveSetProcessElement(new IccOneDimensionalCurve[] { - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly IccCurveSetProcessElement CurvePE_ValGrad = new IccCurveSetProcessElement(new IccOneDimensionalCurve[] - { - IccTestDataCurves.OneDimensional_ValFormula1, - IccTestDataCurves.OneDimensional_ValFormula2, - IccTestDataCurves.OneDimensional_ValFormula1 - }); - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat( - IccTestDataCurves.OneDimensional_Formula1, - IccTestDataCurves.OneDimensional_Formula2, - IccTestDataCurves.OneDimensional_Formula1); - - public static readonly object[][] CurveSetTestData = - { - new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, - }; - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement( - IccTestDataMatrix.Single_2DArray_ValGrad, - IccTestDataMatrix.Single_1DArray_ValGrad); - - /// - /// Input Channel Count: 3 - /// Output Channel Count: 3 - /// - public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat( - IccTestDataMatrix.Single_2D_Grad, - IccTestDataMatrix.Single_1D_Grad); - - public static readonly object[][] MatrixTestData = - { - new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, - }; - - /// - /// 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 - /// - public static readonly byte[] CLUTPE_Grad = IccTestDataLut.CLUT_f32; - - public static readonly object[][] ClutTestData = - { - new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, - }; - - 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( - new byte[] - { - 0x6D, 0x61, 0x74, 0x66, - 0x00, 0x03, - 0x00, 0x03, - }, - MatrixPE_Grad); - - public static readonly byte[] MPE_CLUT = ArrayHelper.Concat( - new byte[] - { - 0x63, 0x6C, 0x75, 0x74, - 0x00, 0x02, - 0x00, 0x03, - }, - CLUTPE_Grad); - - public static readonly byte[] MPE_Curve = ArrayHelper.Concat( - new byte[] - { - 0x6D, 0x66, 0x6C, 0x74, - 0x00, 0x03, - 0x00, 0x03, - }, - CurvePE_Grad); - - public static readonly byte[] MPE_bACS = + IccTestDataCurves.OneDimensional_ValFormula1, + IccTestDataCurves.OneDimensional_ValFormula2, + IccTestDataCurves.OneDimensional_ValFormula1 + }); + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat( + IccTestDataCurves.OneDimensional_Formula1, + IccTestDataCurves.OneDimensional_Formula2, + IccTestDataCurves.OneDimensional_Formula1); + + public static readonly object[][] CurveSetTestData = + { + new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, + }; + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement( + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad); + + /// + /// Input Channel Count: 3 + /// Output Channel Count: 3 + /// + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat( + IccTestDataMatrix.Single_2D_Grad, + IccTestDataMatrix.Single_1D_Grad); + + public static readonly object[][] MatrixTestData = + { + new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, + }; + + /// + /// 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 + /// + public static readonly byte[] CLUTPE_Grad = IccTestDataLut.CLUT_f32; + + public static readonly object[][] ClutTestData = + { + new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, + }; + + 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( + new byte[] { - 0x62, 0x41, 0x43, 0x53, + 0x6D, 0x61, 0x74, 0x66, 0x00, 0x03, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; + }, + MatrixPE_Grad); - public static readonly byte[] MPE_eACS = + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat( + new byte[] { - 0x65, 0x41, 0x43, 0x53, + 0x63, 0x6C, 0x75, 0x74, + 0x00, 0x02, 0x00, 0x03, - 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; + }, + CLUTPE_Grad); - public static readonly object[][] MultiProcessElementTestData = + public static readonly byte[] MPE_Curve = ArrayHelper.Concat( + new byte[] { - new object[] { MPE_Matrix, MPE_ValMatrix }, - new object[] { MPE_CLUT, MPE_ValCLUT }, - new object[] { MPE_Curve, MPE_ValCurve }, - new object[] { MPE_bACS, MPE_ValbACS }, - new object[] { MPE_eACS, MPE_ValeACS }, - }; - } + 0x6D, 0x66, 0x6C, 0x74, + 0x00, 0x03, + 0x00, 0x03, + }, + CurvePE_Grad); + + public static readonly byte[] MPE_bACS = + { + 0x62, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] MPE_eACS = + { + 0x65, 0x41, 0x43, 0x53, + 0x00, 0x03, + 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly object[][] MultiProcessElementTestData = + { + new object[] { MPE_Matrix, MPE_ValMatrix }, + new object[] { MPE_CLUT, MPE_ValCLUT }, + new object[] { MPE_Curve, MPE_ValCurve }, + new object[] { MPE_bACS, MPE_ValbACS }, + new object[] { MPE_eACS, MPE_ValeACS }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs index 6f1180f5d4..c476f2e6c7 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -1,357 +1,355 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Globalization; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataNonPrimitives { - internal static class IccTestDataNonPrimitives - { - 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); + 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); - public static readonly byte[] DateTime_Min = - { - 0x00, 0x01, // Year 1 - 0x00, 0x01, // Month 1 - 0x00, 0x01, // Day 1 - 0x00, 0x00, // Hour 0 - 0x00, 0x00, // Minute 0 - 0x00, 0x00, // Second 0 - }; - - public static readonly byte[] DateTime_Max = - { - 0x27, 0x0F, // Year 9999 - 0x00, 0x0C, // Month 12 - 0x00, 0x1F, // Day 31 - 0x00, 0x17, // Hour 23 - 0x00, 0x3B, // Minute 59 - 0x00, 0x3B, // Second 59 - }; - - public static readonly byte[] DateTime_Invalid = - { - 0xFF, 0xFF, // Year 65535 - 0x00, 0x0E, // Month 14 - 0x00, 0x21, // Day 33 - 0x00, 0x19, // Hour 25 - 0x00, 0x3D, // Minute 61 - 0x00, 0x3D, // Second 61 - }; - - public static readonly byte[] DateTime_Rand1 = - { - 0x07, 0xC6, // Year 1990 - 0x00, 0x0B, // Month 11 - 0x00, 0x1A, // Day 26 - 0x00, 0x03, // Hour 3 - 0x00, 0x13, // Minute 19 - 0x00, 0x2F, // Second 47 - }; - - public static readonly object[][] DateTimeTestData = - { - new object[] { DateTime_Min, DateTime_ValMin }, - new object[] { DateTime_Max, DateTime_ValMax }, - new object[] { DateTime_Rand1, DateTime_ValRand1 }, - }; - - 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); - public static readonly IccVersion VersionNumber_ValMax = new IccVersion(255, 15, 15); - - public static readonly byte[] VersionNumber_Min = { 0x00, 0x00, 0x00, 0x00 }; - public static readonly byte[] VersionNumber_211 = { 0x02, 0x11, 0x00, 0x00 }; - public static readonly byte[] VersionNumber_430 = { 0x04, 0x30, 0x00, 0x00 }; - public static readonly byte[] VersionNumber_Max = { 0xFF, 0xFF, 0x00, 0x00 }; - - public static readonly object[][] VersionNumberTestData = - { - new object[] { VersionNumber_Min, VersionNumber_ValMin }, - new object[] { VersionNumber_211, VersionNumber_Val211 }, - new object[] { VersionNumber_430, VersionNumber_Val430 }, - new object[] { VersionNumber_Max, VersionNumber_ValMax }, - }; - - 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); - public static readonly Vector3 XyzNumber_ValVar1 = new Vector3(1, 2, 3); - public static readonly Vector3 XyzNumber_ValVar2 = new Vector3(4, 5, 6); - public static readonly Vector3 XyzNumber_ValVar3 = new Vector3(7, 8, 9); - public static readonly Vector3 XyzNumber_ValMax = new Vector3(IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax); - - public static readonly byte[] XyzNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min); - public static readonly byte[] XyzNumber_0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0); - public static readonly byte[] XyzNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1); - public static readonly byte[] XyzNumber_Var1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3); - public static readonly byte[] XyzNumber_Var2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6); - public static readonly byte[] XyzNumber_Var3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_7, IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_9); - public static readonly byte[] XyzNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max); - - public static readonly object[][] XyzNumberTestData = - { - new object[] { XyzNumber_Min, XyzNumber_ValMin }, - new object[] { XyzNumber_0, XyzNumber_Val0 }, - new object[] { XyzNumber_Var1, XyzNumber_ValVar1 }, - new object[] { XyzNumber_Max, XyzNumber_ValMax }, - }; + public static readonly byte[] DateTime_Min = + { + 0x00, 0x01, // Year 1 + 0x00, 0x01, // Month 1 + 0x00, 0x01, // Day 1 + 0x00, 0x00, // Hour 0 + 0x00, 0x00, // Minute 0 + 0x00, 0x00, // Second 0 + }; + + public static readonly byte[] DateTime_Max = + { + 0x27, 0x0F, // Year 9999 + 0x00, 0x0C, // Month 12 + 0x00, 0x1F, // Day 31 + 0x00, 0x17, // Hour 23 + 0x00, 0x3B, // Minute 59 + 0x00, 0x3B, // Second 59 + }; + + public static readonly byte[] DateTime_Invalid = + { + 0xFF, 0xFF, // Year 65535 + 0x00, 0x0E, // Month 14 + 0x00, 0x21, // Day 33 + 0x00, 0x19, // Hour 25 + 0x00, 0x3D, // Minute 61 + 0x00, 0x3D, // Second 61 + }; + + public static readonly byte[] DateTime_Rand1 = + { + 0x07, 0xC6, // Year 1990 + 0x00, 0x0B, // Month 11 + 0x00, 0x1A, // Day 26 + 0x00, 0x03, // Hour 3 + 0x00, 0x13, // Minute 19 + 0x00, 0x2F, // Second 47 + }; + + public static readonly object[][] DateTimeTestData = + { + new object[] { DateTime_Min, DateTime_ValMin }, + new object[] { DateTime_Max, DateTime_ValMax }, + new object[] { DateTime_Rand1, DateTime_ValRand1 }, + }; + + 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); + public static readonly IccVersion VersionNumber_ValMax = new IccVersion(255, 15, 15); + + public static readonly byte[] VersionNumber_Min = { 0x00, 0x00, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_211 = { 0x02, 0x11, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_430 = { 0x04, 0x30, 0x00, 0x00 }; + public static readonly byte[] VersionNumber_Max = { 0xFF, 0xFF, 0x00, 0x00 }; + + public static readonly object[][] VersionNumberTestData = + { + new object[] { VersionNumber_Min, VersionNumber_ValMin }, + new object[] { VersionNumber_211, VersionNumber_Val211 }, + new object[] { VersionNumber_430, VersionNumber_Val430 }, + new object[] { VersionNumber_Max, VersionNumber_ValMax }, + }; + + 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); + public static readonly Vector3 XyzNumber_ValVar1 = new Vector3(1, 2, 3); + public static readonly Vector3 XyzNumber_ValVar2 = new Vector3(4, 5, 6); + public static readonly Vector3 XyzNumber_ValVar3 = new Vector3(7, 8, 9); + public static readonly Vector3 XyzNumber_ValMax = new Vector3(IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] XyzNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] XyzNumber_0 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0); + public static readonly byte[] XyzNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] XyzNumber_Var1 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] XyzNumber_Var2 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] XyzNumber_Var3 = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_7, IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] XyzNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] XyzNumberTestData = + { + new object[] { XyzNumber_Min, XyzNumber_ValMin }, + new object[] { XyzNumber_0, XyzNumber_Val0 }, + new object[] { XyzNumber_Var1, XyzNumber_ValVar1 }, + new object[] { XyzNumber_Max, XyzNumber_ValMax }, + }; - 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); + 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); - public static readonly byte[] ProfileId_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); - public static readonly byte[] ProfileId_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2, IccTestDataPrimitives.UInt32_Rand3, IccTestDataPrimitives.UInt32_Rand4); - public static readonly byte[] ProfileId_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + public static readonly byte[] ProfileId_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] ProfileId_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2, IccTestDataPrimitives.UInt32_Rand3, IccTestDataPrimitives.UInt32_Rand4); + public static readonly byte[] ProfileId_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); - public static readonly object[][] ProfileIdTestData = - { - new object[] { ProfileId_Min, ProfileId_ValMin }, - new object[] { ProfileId_Rand, ProfileId_ValRand }, - new object[] { ProfileId_Max, ProfileId_ValMax }, - }; + public static readonly object[][] ProfileIdTestData = + { + new object[] { ProfileId_Min, ProfileId_ValMin }, + new object[] { ProfileId_Rand, ProfileId_ValRand }, + new object[] { ProfileId_Max, ProfileId_ValMax }, + }; - 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); + 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); - public static readonly byte[] PositionNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); - public static readonly byte[] PositionNumber_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2); - public static readonly byte[] PositionNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); + public static readonly byte[] PositionNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0); + public static readonly byte[] PositionNumber_Rand = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Rand1, IccTestDataPrimitives.UInt32_Rand2); + public static readonly byte[] PositionNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_Max, IccTestDataPrimitives.UInt32_Max); - public static readonly object[][] PositionNumberTestData = - { - new object[] { PositionNumber_Min, PositionNumber_ValMin }, - new object[] { PositionNumber_Rand, PositionNumber_ValRand }, - new object[] { PositionNumber_Max, PositionNumber_ValMax }, - }; - - 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); - public static readonly IccResponseNumber ResponseNumber_Val3 = new IccResponseNumber(3, 3); - public static readonly IccResponseNumber ResponseNumber_Val4 = new IccResponseNumber(4, 4); - public static readonly IccResponseNumber ResponseNumber_Val5 = new IccResponseNumber(5, 5); - public static readonly IccResponseNumber ResponseNumber_Val6 = new IccResponseNumber(6, 6); - public static readonly IccResponseNumber ResponseNumber_Val7 = new IccResponseNumber(7, 7); - public static readonly IccResponseNumber ResponseNumber_Val8 = new IccResponseNumber(8, 8); - public static readonly IccResponseNumber ResponseNumber_Val9 = new IccResponseNumber(9, 9); - public static readonly IccResponseNumber ResponseNumber_ValMax = new IccResponseNumber(ushort.MaxValue, IccTestDataPrimitives.Fix16_ValMax); - - public static readonly byte[] ResponseNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.Fix16_Min); - public static readonly byte[] ResponseNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.Fix16_1); - public static readonly byte[] ResponseNumber_2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.Fix16_2); - public static readonly byte[] ResponseNumber_3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.Fix16_3); - public static readonly byte[] ResponseNumber_4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.Fix16_4); - public static readonly byte[] ResponseNumber_5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_5, IccTestDataPrimitives.Fix16_5); - public static readonly byte[] ResponseNumber_6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.Fix16_6); - public static readonly byte[] ResponseNumber_7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.Fix16_7); - public static readonly byte[] ResponseNumber_8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.Fix16_8); - public static readonly byte[] ResponseNumber_9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.Fix16_9); - public static readonly byte[] ResponseNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_Max, IccTestDataPrimitives.Fix16_Max); - - public static readonly object[][] ResponseNumberTestData = - { - new object[] { ResponseNumber_Min, ResponseNumber_ValMin }, - new object[] { ResponseNumber_1, ResponseNumber_Val1 }, - new object[] { ResponseNumber_4, ResponseNumber_Val4 }, - new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, - }; - - 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( - ArrayHelper.Fill('5', 31), - new ushort[] { 10794, 10794, 10794 }, - 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 }); - - 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) + public static readonly object[][] PositionNumberTestData = + { + new object[] { PositionNumber_Min, PositionNumber_ValMin }, + new object[] { PositionNumber_Rand, PositionNumber_ValRand }, + new object[] { PositionNumber_Max, PositionNumber_ValMax }, + }; + + 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); + public static readonly IccResponseNumber ResponseNumber_Val3 = new IccResponseNumber(3, 3); + public static readonly IccResponseNumber ResponseNumber_Val4 = new IccResponseNumber(4, 4); + public static readonly IccResponseNumber ResponseNumber_Val5 = new IccResponseNumber(5, 5); + public static readonly IccResponseNumber ResponseNumber_Val6 = new IccResponseNumber(6, 6); + public static readonly IccResponseNumber ResponseNumber_Val7 = new IccResponseNumber(7, 7); + public static readonly IccResponseNumber ResponseNumber_Val8 = new IccResponseNumber(8, 8); + public static readonly IccResponseNumber ResponseNumber_Val9 = new IccResponseNumber(9, 9); + public static readonly IccResponseNumber ResponseNumber_ValMax = new IccResponseNumber(ushort.MaxValue, IccTestDataPrimitives.Fix16_ValMax); + + public static readonly byte[] ResponseNumber_Min = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.Fix16_Min); + public static readonly byte[] ResponseNumber_1 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.Fix16_1); + public static readonly byte[] ResponseNumber_2 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.Fix16_2); + public static readonly byte[] ResponseNumber_3 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.Fix16_3); + public static readonly byte[] ResponseNumber_4 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.Fix16_4); + public static readonly byte[] ResponseNumber_5 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_5, IccTestDataPrimitives.Fix16_5); + public static readonly byte[] ResponseNumber_6 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.Fix16_6); + public static readonly byte[] ResponseNumber_7 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.Fix16_7); + public static readonly byte[] ResponseNumber_8 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.Fix16_8); + public static readonly byte[] ResponseNumber_9 = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.Fix16_9); + public static readonly byte[] ResponseNumber_Max = ArrayHelper.Concat(IccTestDataPrimitives.UInt16_Max, IccTestDataPrimitives.Fix16_Max); + + public static readonly object[][] ResponseNumberTestData = + { + new object[] { ResponseNumber_Min, ResponseNumber_ValMin }, + new object[] { ResponseNumber_1, ResponseNumber_Val1 }, + new object[] { ResponseNumber_4, ResponseNumber_Val4 }, + new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, + }; + + 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( + ArrayHelper.Fill('5', 31), + new ushort[] { 10794, 10794, 10794 }, + 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 }); + + 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) + { + byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; + for (int i = 0; i < data.Length; i++) { - byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; - for (int i = 0; i < data.Length; i++) + if (i < 31) { - 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 - } + 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; } - public static readonly object[][] NamedColorTestData = - { - new object[] { NamedColor_Min, NamedColor_ValMin, 3u }, - new object[] { NamedColor_Rand, NamedColor_ValRand, 5u }, - new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, - }; + return data; + } - private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); - private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); + public static readonly object[][] NamedColorTestData = + { + new object[] { NamedColor_Min, NamedColor_ValMin, 3u }, + new object[] { NamedColor_Rand, NamedColor_ValRand, 5u }, + new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, + }; - private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(CultureEnUs, IccTestDataPrimitives.Unicode_ValRand2); - private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(CultureDeAT, IccTestDataPrimitives.Unicode_ValRand3); + private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); + private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); - private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] - { - LocalizedString_Rand1, - LocalizedString_Rand2, - }; - - private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); - 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( - 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 - - public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription( - 1, - 2, - IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, - IccProfileTag.ProfileDescription, - MultiLocalizedUnicode_Val.Texts, - MultiLocalizedUnicode_Val.Texts); - - public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription( - 1, - 2, - IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, - IccProfileTag.ProfileDescription, - new IccLocalizedString[] { LocalizedString_Rand1 }, - new IccLocalizedString[] { LocalizedString_Rand1 }); - - 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); - - 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); - - public static readonly object[][] ProfileDescriptionReadTestData = - { - new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, - new object[] { ProfileDescription_Rand2, ProfileDescription_ValRand2 }, - }; + private static readonly IccLocalizedString LocalizedString_Rand1 = new IccLocalizedString(CultureEnUs, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2 = new IccLocalizedString(CultureDeAT, IccTestDataPrimitives.Unicode_ValRand3); - public static readonly object[][] ProfileDescriptionWriteTestData = - { - new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, - }; - - 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( - ArrayHelper.Fill((byte)0x41, 31), - new byte[1], // null terminator - IccTestDataPrimitives.UInt16_1, - IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3); - - 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); - - public static readonly object[][] ColorantTableEntryTestData = - { - new object[] { ColorantTableEntry_Rand1, ColorantTableEntry_ValRand1 }, - new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, - }; + private static readonly IccLocalizedString[] LocalizedString_RandArr1 = new IccLocalizedString[] + { + LocalizedString_Rand1, + LocalizedString_Rand2, + }; + + private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); + 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( + 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 + + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription( + 1, + 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + MultiLocalizedUnicode_Val.Texts, + MultiLocalizedUnicode_Val.Texts); + + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription( + 1, + 2, + IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, + IccProfileTag.ProfileDescription, + new IccLocalizedString[] { LocalizedString_Rand1 }, + new IccLocalizedString[] { LocalizedString_Rand1 }); + + 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); + + 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); + + public static readonly object[][] ProfileDescriptionReadTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + new object[] { ProfileDescription_Rand2, ProfileDescription_ValRand2 }, + }; - 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 object[][] ProfileDescriptionWriteTestData = + { + new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, + }; + + 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( + ArrayHelper.Fill((byte)0x41, 31), + new byte[1], // null terminator + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3); + + 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); + + public static readonly object[][] ColorantTableEntryTestData = + { + new object[] { ColorantTableEntry_Rand1, ColorantTableEntry_ValRand1 }, + new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, + }; - public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat( - IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Int32_7); + 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_Rand2 = ArrayHelper.Concat( - IccTestDataPrimitives.Fix16_8, - IccTestDataPrimitives.Fix16_5, - IccTestDataPrimitives.Int32_3); + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat( + IccTestDataPrimitives.Fix16_4, + IccTestDataPrimitives.Fix16_6, + IccTestDataPrimitives.Int32_7); - public static readonly object[][] ScreeningChannelTestData = - { - new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, - new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, - }; - } + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat( + IccTestDataPrimitives.Fix16_8, + IccTestDataPrimitives.Fix16_5, + IccTestDataPrimitives.Int32_3); + + public static readonly object[][] ScreeningChannelTestData = + { + new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, + new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs index 7611dd04f7..f8e8717273 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -1,273 +1,272 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataPrimitives { - internal static class IccTestDataPrimitives + public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; + public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; + public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; + public static readonly byte[] UInt16_3 = { 0x00, 0x03 }; + public static readonly byte[] UInt16_4 = { 0x00, 0x04 }; + public static readonly byte[] UInt16_5 = { 0x00, 0x05 }; + public static readonly byte[] UInt16_6 = { 0x00, 0x06 }; + public static readonly byte[] UInt16_7 = { 0x00, 0x07 }; + public static readonly byte[] UInt16_8 = { 0x00, 0x08 }; + public static readonly byte[] UInt16_9 = { 0x00, 0x09 }; + public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; + public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; + + public static readonly byte[] Int16_Min = { 0x80, 0x00 }; + public static readonly byte[] Int16_0 = { 0x00, 0x00 }; + public static readonly byte[] Int16_1 = { 0x00, 0x01 }; + public static readonly byte[] Int16_2 = { 0x00, 0x02 }; + public static readonly byte[] Int16_3 = { 0x00, 0x03 }; + public static readonly byte[] Int16_4 = { 0x00, 0x04 }; + public static readonly byte[] Int16_5 = { 0x00, 0x05 }; + public static readonly byte[] Int16_6 = { 0x00, 0x06 }; + public static readonly byte[] Int16_7 = { 0x00, 0x07 }; + public static readonly byte[] Int16_8 = { 0x00, 0x08 }; + public static readonly byte[] Int16_9 = { 0x00, 0x09 }; + public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; + + 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 }; + public static readonly byte[] UInt32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt32_8 = { 0x00, 0x00, 0x00, 0x08 }; + 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; + public static readonly uint UInt32_ValRand4 = 3550252874; + + public static readonly byte[] UInt32_Rand1 = { 0x68, 0x3F, 0xD6, 0x6B }; + public static readonly byte[] UInt32_Rand2 = { 0xE6, 0xB4, 0x12, 0xDD }; + public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; + public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; + + 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 }; + public static readonly byte[] Int32_2 = { 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int32_3 = { 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int32_4 = { 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int32_5 = { 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int32_6 = { 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int32_7 = { 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int32_8 = { 0x00, 0x00, 0x00, 0x08 }; + public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; + public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + 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 }; + public static readonly byte[] UInt64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] UInt64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] UInt64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] UInt64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] UInt64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] UInt64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + 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 }; + + 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 }; + public static readonly byte[] Int64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; + public static readonly byte[] Int64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; + public static readonly byte[] Int64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; + public static readonly byte[] Int64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; + public static readonly byte[] Int64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; + public static readonly byte[] Int64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; + public static readonly byte[] Int64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; + 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 }; + + 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 }; + public static readonly byte[] Single_2 = { 0x40, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_3 = { 0x40, 0x40, 0x00, 0x00 }; + public static readonly byte[] Single_4 = { 0x40, 0x80, 0x00, 0x00 }; + public static readonly byte[] Single_5 = { 0x40, 0xA0, 0x00, 0x00 }; + public static readonly byte[] Single_6 = { 0x40, 0xC0, 0x00, 0x00 }; + public static readonly byte[] Single_7 = { 0x40, 0xE0, 0x00, 0x00 }; + public static readonly byte[] Single_8 = { 0x41, 0x00, 0x00, 0x00 }; + public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; + public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; + + 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 }; + + public const float Fix16_ValMin = short.MinValue; + 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 }; + public static readonly byte[] Fix16_1 = { 0x00, 0x01, 0x00, 0x00 }; + public static readonly byte[] Fix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] Fix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] Fix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] Fix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] Fix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] Fix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] Fix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] Fix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] Fix16_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] Fix16TestData = + { + new object[] { Fix16_Min, Fix16_ValMin }, + new object[] { Fix16_0, 0 }, + new object[] { Fix16_4, 4 }, + new object[] { Fix16_Max, Fix16_ValMax }, + }; + + public const float UFix16_ValMin = 0; + 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 }; + public static readonly byte[] UFix16_2 = { 0x00, 0x02, 0x00, 0x00 }; + public static readonly byte[] UFix16_3 = { 0x00, 0x03, 0x00, 0x00 }; + public static readonly byte[] UFix16_4 = { 0x00, 0x04, 0x00, 0x00 }; + public static readonly byte[] UFix16_5 = { 0x00, 0x05, 0x00, 0x00 }; + public static readonly byte[] UFix16_6 = { 0x00, 0x06, 0x00, 0x00 }; + public static readonly byte[] UFix16_7 = { 0x00, 0x07, 0x00, 0x00 }; + public static readonly byte[] UFix16_8 = { 0x00, 0x08, 0x00, 0x00 }; + public static readonly byte[] UFix16_9 = { 0x00, 0x09, 0x00, 0x00 }; + public static readonly byte[] UFix16_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; + + public static readonly object[][] UFix16TestData = + { + new object[] { UFix16_0, 0 }, + new object[] { UFix16_4, 4 }, + new object[] { UFix16_Max, UFix16_ValMax }, + }; + + public const float U1Fix15_ValMin = 0; + public const float U1Fix15_ValMax = 1f + (32767f / 32768f); + + public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; + public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; + public static readonly byte[] U1Fix15_Max = { 0xFF, 0xFF }; + + public static readonly object[][] U1Fix15TestData = + { + new object[] { U1Fix15_0, 0 }, + new object[] { U1Fix15_1, 1 }, + new object[] { U1Fix15_Max, U1Fix15_ValMax }, + }; + + public const float UFix8_ValMin = 0; + 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 }; + public static readonly byte[] UFix8_2 = { 0x02, 0x00 }; + public static readonly byte[] UFix8_3 = { 0x03, 0x00 }; + public static readonly byte[] UFix8_4 = { 0x04, 0x00 }; + public static readonly byte[] UFix8_5 = { 0x05, 0x00 }; + public static readonly byte[] UFix8_6 = { 0x06, 0x00 }; + public static readonly byte[] UFix8_7 = { 0x07, 0x00 }; + public static readonly byte[] UFix8_8 = { 0x08, 0x00 }; + public static readonly byte[] UFix8_9 = { 0x09, 0x00 }; + public static readonly byte[] UFix8_Max = { 0xFF, 0xFF }; + + public static readonly object[][] UFix8TestData = + { + new object[] { UFix8_0, 0 }, + new object[] { UFix8_4, 4 }, + new object[] { UFix8_Max, UFix8_ValMax }, + }; + + public const string Ascii_ValRand = "aBcdEf1234"; + public const string Ascii_ValRand1 = "Ecf3a"; + public const string Ascii_ValRand2 = "2Bd4c"; + public const string Ascii_ValRand3 = "cad14"; + public const string Ascii_ValRand4 = "fd4E1"; + public const string Ascii_ValRandLength4 = "aBcd"; + public const string Ascii_ValNullRand = "aBcd\0Ef\0123"; + + public static readonly byte[] Ascii_Rand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52 }; + public static readonly byte[] Ascii_Rand1 = { 69, 99, 102, 51, 97 }; + public static readonly byte[] Ascii_Rand2 = { 50, 66, 100, 52, 99 }; + public static readonly byte[] Ascii_Rand3 = { 99, 97, 100, 49, 52 }; + public static readonly byte[] Ascii_Rand4 = { 102, 100, 52, 69, 49 }; + public static readonly byte[] Ascii_RandLength4 = { 97, 66, 99, 100 }; + public static readonly byte[] Ascii_PaddedRand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0 }; + public static readonly byte[] Ascii_NullRand = { 97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51 }; + + public const int Ascii_Rand_Length = 10; + public const int Ascii_PaddedRand_Length = 14; + public const int Ascii_NullRand_Length = 11; + public const int Ascii_NullRand_LengthNoNull = 4; + + public static readonly object[][] AsciiTestData = + { + new object[] { Ascii_Rand, Ascii_Rand_Length, Ascii_ValRand }, + new object[] { Ascii_Rand, 4, Ascii_ValRandLength4 }, + new object[] { Ascii_NullRand, Ascii_NullRand_LengthNoNull, Ascii_ValRandLength4 }, + }; + + public static readonly object[][] AsciiWriteTestData = + { + new object[] { Ascii_Rand, Ascii_ValRand }, + new object[] { Ascii_NullRand, Ascii_ValNullRand }, + }; + + public static readonly object[][] AsciiPaddingTestData = + { + new object[] { Ascii_PaddedRand, Ascii_PaddedRand_Length, Ascii_ValRand, true }, + new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, + }; + + public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; + public const string Unicode_ValRand2 = ".6Abäñ"; + public const string Unicode_ValRand3 = "$€β𐐷𤭢"; + + public static readonly byte[] Unicode_Rand1 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; + + public static readonly byte[] Unicode_Rand2 = + { + 0x00, 0x2e, // . + 0x00, 0x36, // 6 + 0x00, 0x41, // A + 0x00, 0x62, // b + 0x00, 0xe4, // ä + 0x00, 0xf1, // ñ + }; + + public static readonly byte[] Unicode_Rand3 = { - public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; - public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; - public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; - public static readonly byte[] UInt16_3 = { 0x00, 0x03 }; - public static readonly byte[] UInt16_4 = { 0x00, 0x04 }; - public static readonly byte[] UInt16_5 = { 0x00, 0x05 }; - public static readonly byte[] UInt16_6 = { 0x00, 0x06 }; - public static readonly byte[] UInt16_7 = { 0x00, 0x07 }; - public static readonly byte[] UInt16_8 = { 0x00, 0x08 }; - public static readonly byte[] UInt16_9 = { 0x00, 0x09 }; - public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; - public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; - - public static readonly byte[] Int16_Min = { 0x80, 0x00 }; - public static readonly byte[] Int16_0 = { 0x00, 0x00 }; - public static readonly byte[] Int16_1 = { 0x00, 0x01 }; - public static readonly byte[] Int16_2 = { 0x00, 0x02 }; - public static readonly byte[] Int16_3 = { 0x00, 0x03 }; - public static readonly byte[] Int16_4 = { 0x00, 0x04 }; - public static readonly byte[] Int16_5 = { 0x00, 0x05 }; - public static readonly byte[] Int16_6 = { 0x00, 0x06 }; - public static readonly byte[] Int16_7 = { 0x00, 0x07 }; - public static readonly byte[] Int16_8 = { 0x00, 0x08 }; - public static readonly byte[] Int16_9 = { 0x00, 0x09 }; - public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; - - 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 }; - public static readonly byte[] UInt32_3 = { 0x00, 0x00, 0x00, 0x03 }; - public static readonly byte[] UInt32_4 = { 0x00, 0x00, 0x00, 0x04 }; - public static readonly byte[] UInt32_5 = { 0x00, 0x00, 0x00, 0x05 }; - public static readonly byte[] UInt32_6 = { 0x00, 0x00, 0x00, 0x06 }; - public static readonly byte[] UInt32_7 = { 0x00, 0x00, 0x00, 0x07 }; - public static readonly byte[] UInt32_8 = { 0x00, 0x00, 0x00, 0x08 }; - 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; - public static readonly uint UInt32_ValRand4 = 3550252874; - - public static readonly byte[] UInt32_Rand1 = { 0x68, 0x3F, 0xD6, 0x6B }; - public static readonly byte[] UInt32_Rand2 = { 0xE6, 0xB4, 0x12, 0xDD }; - public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; - public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; - - 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 }; - public static readonly byte[] Int32_2 = { 0x00, 0x00, 0x00, 0x02 }; - public static readonly byte[] Int32_3 = { 0x00, 0x00, 0x00, 0x03 }; - public static readonly byte[] Int32_4 = { 0x00, 0x00, 0x00, 0x04 }; - public static readonly byte[] Int32_5 = { 0x00, 0x00, 0x00, 0x05 }; - public static readonly byte[] Int32_6 = { 0x00, 0x00, 0x00, 0x06 }; - public static readonly byte[] Int32_7 = { 0x00, 0x00, 0x00, 0x07 }; - public static readonly byte[] Int32_8 = { 0x00, 0x00, 0x00, 0x08 }; - public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; - public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; - - 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 }; - public static readonly byte[] UInt64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; - public static readonly byte[] UInt64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; - public static readonly byte[] UInt64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; - public static readonly byte[] UInt64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; - public static readonly byte[] UInt64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; - public static readonly byte[] UInt64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; - 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 }; - - 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 }; - public static readonly byte[] Int64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; - public static readonly byte[] Int64_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03 }; - public static readonly byte[] Int64_4 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }; - public static readonly byte[] Int64_5 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05 }; - public static readonly byte[] Int64_6 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 }; - public static readonly byte[] Int64_7 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }; - public static readonly byte[] Int64_8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08 }; - 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 }; - - 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 }; - public static readonly byte[] Single_2 = { 0x40, 0x00, 0x00, 0x00 }; - public static readonly byte[] Single_3 = { 0x40, 0x40, 0x00, 0x00 }; - public static readonly byte[] Single_4 = { 0x40, 0x80, 0x00, 0x00 }; - public static readonly byte[] Single_5 = { 0x40, 0xA0, 0x00, 0x00 }; - public static readonly byte[] Single_6 = { 0x40, 0xC0, 0x00, 0x00 }; - public static readonly byte[] Single_7 = { 0x40, 0xE0, 0x00, 0x00 }; - public static readonly byte[] Single_8 = { 0x41, 0x00, 0x00, 0x00 }; - public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; - public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; - - 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 }; - - public const float Fix16_ValMin = short.MinValue; - 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 }; - public static readonly byte[] Fix16_1 = { 0x00, 0x01, 0x00, 0x00 }; - public static readonly byte[] Fix16_2 = { 0x00, 0x02, 0x00, 0x00 }; - public static readonly byte[] Fix16_3 = { 0x00, 0x03, 0x00, 0x00 }; - public static readonly byte[] Fix16_4 = { 0x00, 0x04, 0x00, 0x00 }; - public static readonly byte[] Fix16_5 = { 0x00, 0x05, 0x00, 0x00 }; - public static readonly byte[] Fix16_6 = { 0x00, 0x06, 0x00, 0x00 }; - public static readonly byte[] Fix16_7 = { 0x00, 0x07, 0x00, 0x00 }; - public static readonly byte[] Fix16_8 = { 0x00, 0x08, 0x00, 0x00 }; - public static readonly byte[] Fix16_9 = { 0x00, 0x09, 0x00, 0x00 }; - public static readonly byte[] Fix16_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; - - public static readonly object[][] Fix16TestData = - { - new object[] { Fix16_Min, Fix16_ValMin }, - new object[] { Fix16_0, 0 }, - new object[] { Fix16_4, 4 }, - new object[] { Fix16_Max, Fix16_ValMax }, - }; - - public const float UFix16_ValMin = 0; - 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 }; - public static readonly byte[] UFix16_2 = { 0x00, 0x02, 0x00, 0x00 }; - public static readonly byte[] UFix16_3 = { 0x00, 0x03, 0x00, 0x00 }; - public static readonly byte[] UFix16_4 = { 0x00, 0x04, 0x00, 0x00 }; - public static readonly byte[] UFix16_5 = { 0x00, 0x05, 0x00, 0x00 }; - public static readonly byte[] UFix16_6 = { 0x00, 0x06, 0x00, 0x00 }; - public static readonly byte[] UFix16_7 = { 0x00, 0x07, 0x00, 0x00 }; - public static readonly byte[] UFix16_8 = { 0x00, 0x08, 0x00, 0x00 }; - public static readonly byte[] UFix16_9 = { 0x00, 0x09, 0x00, 0x00 }; - public static readonly byte[] UFix16_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; - - public static readonly object[][] UFix16TestData = - { - new object[] { UFix16_0, 0 }, - new object[] { UFix16_4, 4 }, - new object[] { UFix16_Max, UFix16_ValMax }, - }; - - public const float U1Fix15_ValMin = 0; - public const float U1Fix15_ValMax = 1f + (32767f / 32768f); - - public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; - public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; - public static readonly byte[] U1Fix15_Max = { 0xFF, 0xFF }; - - public static readonly object[][] U1Fix15TestData = - { - new object[] { U1Fix15_0, 0 }, - new object[] { U1Fix15_1, 1 }, - new object[] { U1Fix15_Max, U1Fix15_ValMax }, - }; - - public const float UFix8_ValMin = 0; - 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 }; - public static readonly byte[] UFix8_2 = { 0x02, 0x00 }; - public static readonly byte[] UFix8_3 = { 0x03, 0x00 }; - public static readonly byte[] UFix8_4 = { 0x04, 0x00 }; - public static readonly byte[] UFix8_5 = { 0x05, 0x00 }; - public static readonly byte[] UFix8_6 = { 0x06, 0x00 }; - public static readonly byte[] UFix8_7 = { 0x07, 0x00 }; - public static readonly byte[] UFix8_8 = { 0x08, 0x00 }; - public static readonly byte[] UFix8_9 = { 0x09, 0x00 }; - public static readonly byte[] UFix8_Max = { 0xFF, 0xFF }; - - public static readonly object[][] UFix8TestData = - { - new object[] { UFix8_0, 0 }, - new object[] { UFix8_4, 4 }, - new object[] { UFix8_Max, UFix8_ValMax }, - }; - - public const string Ascii_ValRand = "aBcdEf1234"; - public const string Ascii_ValRand1 = "Ecf3a"; - public const string Ascii_ValRand2 = "2Bd4c"; - public const string Ascii_ValRand3 = "cad14"; - public const string Ascii_ValRand4 = "fd4E1"; - public const string Ascii_ValRandLength4 = "aBcd"; - public const string Ascii_ValNullRand = "aBcd\0Ef\0123"; - - public static readonly byte[] Ascii_Rand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52 }; - public static readonly byte[] Ascii_Rand1 = { 69, 99, 102, 51, 97 }; - public static readonly byte[] Ascii_Rand2 = { 50, 66, 100, 52, 99 }; - public static readonly byte[] Ascii_Rand3 = { 99, 97, 100, 49, 52 }; - public static readonly byte[] Ascii_Rand4 = { 102, 100, 52, 69, 49 }; - public static readonly byte[] Ascii_RandLength4 = { 97, 66, 99, 100 }; - public static readonly byte[] Ascii_PaddedRand = { 97, 66, 99, 100, 69, 102, 49, 50, 51, 52, 0, 0, 0, 0 }; - public static readonly byte[] Ascii_NullRand = { 97, 66, 99, 100, 0, 69, 102, 0, 49, 50, 51 }; - - public const int Ascii_Rand_Length = 10; - public const int Ascii_PaddedRand_Length = 14; - public const int Ascii_NullRand_Length = 11; - public const int Ascii_NullRand_LengthNoNull = 4; - - public static readonly object[][] AsciiTestData = - { - new object[] { Ascii_Rand, Ascii_Rand_Length, Ascii_ValRand }, - new object[] { Ascii_Rand, 4, Ascii_ValRandLength4 }, - new object[] { Ascii_NullRand, Ascii_NullRand_LengthNoNull, Ascii_ValRandLength4 }, - }; - - public static readonly object[][] AsciiWriteTestData = - { - new object[] { Ascii_Rand, Ascii_ValRand }, - new object[] { Ascii_NullRand, Ascii_ValNullRand }, - }; - - public static readonly object[][] AsciiPaddingTestData = - { - new object[] { Ascii_PaddedRand, Ascii_PaddedRand_Length, Ascii_ValRand, true }, - new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, - }; - - public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; - public const string Unicode_ValRand2 = ".6Abäñ"; - public const string Unicode_ValRand3 = "$€β𐐷𤭢"; - - public static readonly byte[] Unicode_Rand1 = - { - 0x00, 0x2e, // . - 0x00, 0x36, // 6 - 0x00, 0x41, // A - 0x00, 0x62, // b - 0x00, 0xe4, // ä - 0x00, 0xf1, // ñ - 0x00, 0x24, // $ - 0x20, 0xAC, // € - 0x03, 0xb2, // β - 0xD8, 0x01, 0xDC, 0x37, // 𐐷 - 0xD8, 0x52, 0xDF, 0x62, // 𤭢 - }; - - public static readonly byte[] Unicode_Rand2 = - { - 0x00, 0x2e, // . - 0x00, 0x36, // 6 - 0x00, 0x41, // A - 0x00, 0x62, // b - 0x00, 0xe4, // ä - 0x00, 0xf1, // ñ - }; - - public static readonly byte[] Unicode_Rand3 = - { - 0x00, 0x24, // $ - 0x20, 0xAC, // € - 0x03, 0xb2, // β - 0xD8, 0x01, 0xDC, 0x37, // 𐐷 - 0xD8, 0x52, 0xDF, 0x62, // 𤭢 - }; - } + 0x00, 0x24, // $ + 0x20, 0xAC, // € + 0x03, 0xb2, // β + 0xD8, 0x01, 0xDC, 0x37, // 𐐷 + 0xD8, 0x52, 0xDF, 0x62, // 𤭢 + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index feabf0e194..8fd6f7e4ae 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -1,231 +1,229 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests -{ - internal static class IccTestDataProfiles - { - public static readonly IccProfileId Header_Random_Id_Value = new IccProfileId(0x84A8D460, 0xC716B6F3, 0x9B0E4C3D, 0xAB95F838); - public static readonly IccProfileId Profile_Random_Id_Value = new IccProfileId(0x917D6DE6, 0x84C958D1, 0x3BB0F5BB, 0xADD1134F); - - public static readonly byte[] Header_Random_Id_Array = - { - 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38, - }; +namespace SixLabors.ImageSharp.Tests; - public static readonly byte[] Profile_Random_Id_Array = - { - 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F, - }; +internal static class IccTestDataProfiles +{ + public static readonly IccProfileId Header_Random_Id_Value = new IccProfileId(0x84A8D460, 0xC716B6F3, 0x9B0E4C3D, 0xAB95F838); + public static readonly IccProfileId Profile_Random_Id_Value = new IccProfileId(0x917D6DE6, 0x84C958D1, 0x3BB0F5BB, 0xADD1134F); - public static readonly IccProfileHeader Header_Random_Write = CreateHeaderRandomValue( - 562, // should be overwritten - new IccProfileId(1, 2, 3, 4), // should be overwritten - "ijkl"); // should be overwritten to "acsp" + public static readonly byte[] Header_Random_Id_Array = + { + 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38, + }; - public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, Header_Random_Id_Value, "acsp"); + public static readonly byte[] Profile_Random_Id_Array = + { + 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F, + }; - public static readonly byte[] Header_Random_Array = CreateHeaderRandomArray(132, 0, Header_Random_Id_Array); + public static readonly IccProfileHeader Header_Random_Write = CreateHeaderRandomValue( + 562, // should be overwritten + new IccProfileId(1, 2, 3, 4), // should be overwritten + "ijkl"); // should be overwritten to "acsp" - public static IccProfileHeader CreateHeaderRandomValue(uint size, IccProfileId id, string fileSignature) - { - return new IccProfileHeader - { - Class = IccProfileClass.DisplayDevice, - CmmType = "abcd", - CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), - CreatorSignature = "dcba", - DataColorSpace = IccColorSpaceType.Rgb, - DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, - DeviceManufacturer = 123456789u, - DeviceModel = 987654321u, - FileSignature = "acsp", - Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, - Id = id, - PcsIlluminant = new Vector3(4, 5, 6), - PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, - ProfileConnectionSpace = IccColorSpaceType.CieXyz, - RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, - Size = size, - Version = new IccVersion(4, 3, 0), - }; - } - - public static byte[] CreateHeaderRandomArray(uint size, uint nrOfEntries, byte[] profileId) - { - return ArrayHelper.Concat( - new byte[] - { - (byte)(size >> 24), (byte)(size >> 16), (byte)(size >> 8), (byte)size, // Size - 0x61, 0x62, 0x63, 0x64, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 0x00, 0x00, 0x00, 0x03, // RenderingIntent - 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant - 0x64, 0x63, 0x62, 0x61, // CreatorSignature - }, - profileId, - 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 - }); - } - - public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat( - CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), - 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) - }, - IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, - 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 byte[] Header_CorruptDataColorSpace_Array = - { - 0x00, 0x00, 0x00, 0x80, // Size - 0x61, 0x62, 0x63, 0x64, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x68, 0x45, 0x8D, 0x6A, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 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, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }; + public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, Header_Random_Id_Value, "acsp"); - public static readonly byte[] Header_CorruptProfileConnectionSpace_Array = - { - 0x00, 0x00, 0x00, 0x80, // Size - 0x62, 0x63, 0x64, 0x65, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x68, 0x45, 0x8D, 0x6A, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 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, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }; + public static readonly byte[] Header_Random_Array = CreateHeaderRandomArray(132, 0, Header_Random_Id_Array); - public static readonly byte[] Header_CorruptRenderingIntent_Array = + public static IccProfileHeader CreateHeaderRandomValue(uint size, IccProfileId id, string fileSignature) + { + return new IccProfileHeader { - 0x00, 0x00, 0x00, 0x80, // Size - 0x63, 0x64, 0x65, 0x66, // CmmType - 0x04, 0x30, 0x00, 0x00, // Version - 0x6D, 0x6E, 0x74, 0x72, // Class - 0x52, 0x47, 0x42, 0x20, // DataColorSpace - 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace - 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate - 0x61, 0x63, 0x73, 0x70, // FileSignature - 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature - 0x00, 0x00, 0x00, 0x01, // Flags - 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer - 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes - 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, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + Class = IccProfileClass.DisplayDevice, + CmmType = "abcd", + CreationDate = new DateTime(1990, 11, 26, 7, 21, 42), + CreatorSignature = "dcba", + DataColorSpace = IccColorSpaceType.Rgb, + DeviceAttributes = IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.OpacityTransparent, + DeviceManufacturer = 123456789u, + DeviceModel = 987654321u, + FileSignature = "acsp", + Flags = IccProfileFlag.Embedded | IccProfileFlag.Independent, + Id = id, + PcsIlluminant = new Vector3(4, 5, 6), + PrimaryPlatformSignature = IccPrimaryPlatformType.MicrosoftCorporation, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.AbsoluteColorimetric, + Size = size, + Version = new IccVersion(4, 3, 0), }; + } - public static readonly byte[] Header_DataTooSmall_Array = new byte[127]; - - public static readonly byte[] Header_InvalidSizeSmall_Array = CreateHeaderRandomArray(127, 0, Header_Random_Id_Array); - - public static readonly byte[] Header_InvalidSizeBig_Array = CreateHeaderRandomArray(50_000_000, 0, Header_Random_Id_Array); - - public static readonly byte[] Header_SizeBiggerThanData_Array = CreateHeaderRandomArray(160, 0, Header_Random_Id_Array); + public static byte[] CreateHeaderRandomArray(uint size, uint nrOfEntries, byte[] profileId) + { + return ArrayHelper.Concat( + new byte[] + { + (byte)(size >> 24), (byte)(size >> 16), (byte)(size >> 8), (byte)size, // Size + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 0x00, 0x00, 0x00, 0x03, // RenderingIntent + 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant + 0x64, 0x63, 0x62, 0x61, // CreatorSignature + }, + profileId, + 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 + }); + } - public static readonly object[][] ProfileIdTestData = + public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat( + CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), + new byte[] { - new object[] { Header_Random_Array, Header_Random_Id_Value }, - new object[] { Profile_Random_Array, Profile_Random_Id_Value }, - }; + 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) + }, + IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, + 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 byte[] Header_CorruptDataColorSpace_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x61, 0x62, 0x63, 0x64, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x68, 0x45, 0x8D, 0x6A, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_CorruptProfileConnectionSpace_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x62, 0x63, 0x64, 0x65, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x68, 0x45, 0x8D, 0x6A, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_CorruptRenderingIntent_Array = + { + 0x00, 0x00, 0x00, 0x80, // Size + 0x63, 0x64, 0x65, 0x66, // CmmType + 0x04, 0x30, 0x00, 0x00, // Version + 0x6D, 0x6E, 0x74, 0x72, // Class + 0x52, 0x47, 0x42, 0x20, // DataColorSpace + 0x58, 0x59, 0x5A, 0x20, // ProfileConnectionSpace + 0x07, 0xC6, 0x00, 0x0B, 0x00, 0x1A, 0x00, 0x07, 0x00, 0x15, 0x00, 0x2A, // CreationDate + 0x61, 0x63, 0x73, 0x70, // FileSignature + 0x4D, 0x53, 0x46, 0x54, // PrimaryPlatformSignature + 0x00, 0x00, 0x00, 0x01, // Flags + 0x07, 0x5B, 0xCD, 0x15, // DeviceManufacturer + 0x3A, 0xDE, 0x68, 0xB1, // DeviceModel + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, // DeviceAttributes + 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + public static readonly byte[] Header_DataTooSmall_Array = new byte[127]; + + public static readonly byte[] Header_InvalidSizeSmall_Array = CreateHeaderRandomArray(127, 0, Header_Random_Id_Array); + + public static readonly byte[] Header_InvalidSizeBig_Array = CreateHeaderRandomArray(50_000_000, 0, Header_Random_Id_Array); + + public static readonly byte[] Header_SizeBiggerThanData_Array = CreateHeaderRandomArray(160, 0, Header_Random_Id_Array); + + public static readonly object[][] ProfileIdTestData = + { + new object[] { Header_Random_Array, Header_Random_Id_Value }, + new object[] { Profile_Random_Array, Profile_Random_Id_Value }, + }; - public static readonly object[][] ProfileValidityTestData = - { - new object[] { Header_CorruptDataColorSpace_Array, false }, - new object[] { Header_CorruptProfileConnectionSpace_Array, false }, - new object[] { Header_CorruptRenderingIntent_Array, false }, - new object[] { Header_DataTooSmall_Array, false }, - new object[] { Header_InvalidSizeSmall_Array, false }, - new object[] { Header_InvalidSizeBig_Array, false }, - new object[] { Header_SizeBiggerThanData_Array, false }, - new object[] { Header_Random_Array, true }, - }; - } + public static readonly object[][] ProfileValidityTestData = + { + new object[] { Header_CorruptDataColorSpace_Array, false }, + new object[] { Header_CorruptProfileConnectionSpace_Array, false }, + new object[] { Header_CorruptRenderingIntent_Array, false }, + new object[] { Header_DataTooSmall_Array, false }, + new object[] { Header_InvalidSizeSmall_Array, false }, + new object[] { Header_InvalidSizeBig_Array, false }, + new object[] { Header_SizeBiggerThanData_Array, false }, + new object[] { Header_Random_Array, true }, + }; } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs index f6d5aadac4..69d375cedf 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -5,822 +5,821 @@ using System.Numerics; using SixLabors.ImageSharp.Metadata.Profiles.Icc; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +internal static class IccTestDataTagDataEntry { - internal static class IccTestDataTagDataEntry + public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; + public static readonly byte[] TagDataEntryHeader_UnknownArr = { - public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; - public static readonly byte[] TagDataEntryHeader_UnknownArr = - { - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }; - - public static readonly IccTypeSignature TagDataEntryHeader_MultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; - public static readonly byte[] TagDataEntryHeader_MultiLocalizedUnicodeArr = - { - 0x6D, 0x6C, 0x75, 0x63, - 0x00, 0x00, 0x00, 0x00, - }; + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; - public static readonly IccTypeSignature TagDataEntryHeader_CurveVal = IccTypeSignature.Curve; - public static readonly byte[] TagDataEntryHeader_CurveArr = - { - 0x63, 0x75, 0x72, 0x76, - 0x00, 0x00, 0x00, 0x00, - }; + public static readonly IccTypeSignature TagDataEntryHeader_MultiLocalizedUnicodeVal = IccTypeSignature.MultiLocalizedUnicode; + public static readonly byte[] TagDataEntryHeader_MultiLocalizedUnicodeArr = + { + 0x6D, 0x6C, 0x75, 0x63, + 0x00, 0x00, 0x00, 0x00, + }; - public static readonly object[][] TagDataEntryHeaderTestData = - { - new object[] { TagDataEntryHeader_UnknownArr, TagDataEntryHeader_UnknownVal }, - new object[] { TagDataEntryHeader_MultiLocalizedUnicodeArr, TagDataEntryHeader_MultiLocalizedUnicodeVal }, - new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, - }; + public static readonly IccTypeSignature TagDataEntryHeader_CurveVal = IccTypeSignature.Curve; + public static readonly byte[] TagDataEntryHeader_CurveArr = + { + 0x63, 0x75, 0x72, 0x76, + 0x00, 0x00, 0x00, 0x00, + }; - public static readonly IccUnknownTagDataEntry Unknown_Val = new(new byte[] { 0x00, 0x01, 0x02, 0x03 }); + public static readonly object[][] TagDataEntryHeaderTestData = + { + new object[] { TagDataEntryHeader_UnknownArr, TagDataEntryHeader_UnknownVal }, + new object[] { TagDataEntryHeader_MultiLocalizedUnicodeArr, TagDataEntryHeader_MultiLocalizedUnicodeVal }, + new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, + }; - public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; + public static readonly IccUnknownTagDataEntry Unknown_Val = new(new byte[] { 0x00, 0x01, 0x02, 0x03 }); - public static readonly object[][] UnknownTagDataEntryTestData = - { - new object[] { Unknown_Arr, Unknown_Val, 12u }, - }; - - public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new(IccColorantEncoding.ItuRBt709_2); - 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 - - public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new( - new double[][] - { - new double[] { 1, 2 }, - new double[] { 3, 4 }, - }); - - 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); - - /// - /// : channel count must be 3 for any enum other than - /// - public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_1); - - /// - /// : invalid enum value - /// - public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_9); - - public static readonly object[][] ChromaticityTagDataEntryTestData = - { - new object[] { Chromaticity_Arr1, Chromaticity_Val1 }, - new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, - }; + public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; - public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new(new byte[] { 0x00, 0x01, 0x02 }); - public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); + public static readonly object[][] UnknownTagDataEntryTestData = + { + new object[] { Unknown_Arr, Unknown_Val, 12u }, + }; + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new(IccColorantEncoding.ItuRBt709_2); + 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 + + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new( + new double[][] + { + new double[] { 1, 2 }, + new double[] { 3, 4 }, + }); - public static readonly object[][] ColorantOrderTagDataEntryTestData = - { - new object[] { ColorantOrder_Arr, ColorantOrder_Val }, - }; + 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); + + /// + /// : channel count must be 3 for any enum other than + /// + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat( + IccTestDataPrimitives.UInt16_5, + IccTestDataPrimitives.UInt16_1); + + /// + /// : invalid enum value + /// + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat( + IccTestDataPrimitives.UInt16_3, + IccTestDataPrimitives.UInt16_9); + + public static readonly object[][] ChromaticityTagDataEntryTestData = + { + new object[] { Chromaticity_Arr1, Chromaticity_Val1 }, + new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, + }; - public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new( - new IccColorantTableEntry[] - { - IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, - IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 - }); + public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new(new byte[] { 0x00, 0x01, 0x02 }); + public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); - public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_2, - IccTestDataNonPrimitives.ColorantTableEntry_Rand1, - IccTestDataNonPrimitives.ColorantTableEntry_Rand2); + public static readonly object[][] ColorantOrderTagDataEntryTestData = + { + new object[] { ColorantOrder_Arr, ColorantOrder_Val }, + }; - public static readonly object[][] ColorantTableTagDataEntryTestData = + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new( + new IccColorantTableEntry[] { - new object[] { ColorantTable_Arr, ColorantTable_Val }, - }; - - public static readonly IccCurveTagDataEntry Curve_Val_0 = new(); - public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; - - public static readonly IccCurveTagDataEntry Curve_Val_1 = new(1f); - public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UFix8_1); - - public static readonly IccCurveTagDataEntry Curve_Val_2 = new(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); - public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_3, - IccTestDataPrimitives.UInt16_1, - IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3); + IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, + IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 + }); - public static readonly object[][] CurveTagDataEntryTestData = - { - new object[] { Curve_Arr_0, Curve_Val_0 }, - new object[] { Curve_Arr_1, Curve_Val_1 }, - new object[] { Curve_Arr_2, Curve_Val_2 }, - }; + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ColorantTableEntry_Rand1, + IccTestDataNonPrimitives.ColorantTableEntry_Rand2); - public static readonly IccDataTagDataEntry Data_ValNoASCII = new( - new byte[] { 0x01, 0x02, 0x03, 0x04 }, - false); + public static readonly object[][] ColorantTableTagDataEntryTestData = + { + new object[] { ColorantTable_Arr, ColorantTable_Val }, + }; - public static readonly byte[] Data_ArrNoASCII = - { - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04 - }; + public static readonly IccCurveTagDataEntry Curve_Val_0 = new(); + public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; - public static readonly IccDataTagDataEntry Data_ValASCII = new( - new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, - true); + public static readonly IccCurveTagDataEntry Curve_Val_1 = new(1f); + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UFix8_1); - public static readonly byte[] Data_ArrASCII = - { - 0x00, 0x00, 0x00, 0x01, - (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' - }; + public static readonly IccCurveTagDataEntry Curve_Val_2 = new(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_3, + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3); - public static readonly object[][] DataTagDataEntryTestData = - { - new object[] { Data_ArrNoASCII, Data_ValNoASCII, 16u }, - new object[] { Data_ArrASCII, Data_ValASCII, 17u }, - }; + public static readonly object[][] CurveTagDataEntryTestData = + { + new object[] { Curve_Arr_0, Curve_Val_0 }, + new object[] { Curve_Arr_1, Curve_Val_1 }, + new object[] { Curve_Arr_2, Curve_Val_2 }, + }; - public static readonly IccDateTimeTagDataEntry DateTime_Val = new(IccTestDataNonPrimitives.DateTime_ValRand1); - public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; + public static readonly IccDataTagDataEntry Data_ValNoASCII = new( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + false); - public static readonly object[][] DateTimeTagDataEntryTestData = - { - new object[] { DateTime_Arr, DateTime_Val }, - }; - - public static readonly IccLut16TagDataEntry Lut16_Val = new( - 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 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); - - public static readonly object[][] Lut16TagDataEntryTestData = - { - new object[] { Lut16_Arr, Lut16_Val }, - }; - - public static readonly IccLut8TagDataEntry Lut8_Val = new( - 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 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); - - public static readonly object[][] Lut8TagDataEntryTestData = - { - new object[] { Lut8_Arr, Lut8_Val }, - }; + public static readonly byte[] Data_ArrNoASCII = + { + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x03, 0x04 + }; - private static readonly byte[] CurveFull_0 = ArrayHelper.Concat( - TagDataEntryHeader_CurveArr, - Curve_Arr_0); + public static readonly IccDataTagDataEntry Data_ValASCII = new( + new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, + true); - private static readonly byte[] CurveFull_1 = ArrayHelper.Concat( - TagDataEntryHeader_CurveArr, - Curve_Arr_1); + public static readonly byte[] Data_ArrASCII = + { + 0x00, 0x00, 0x00, 0x01, + (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' + }; - private static readonly byte[] CurveFull_2 = ArrayHelper.Concat( - TagDataEntryHeader_CurveArr, - Curve_Arr_2); + public static readonly object[][] DataTagDataEntryTestData = + { + new object[] { Data_ArrNoASCII, Data_ValNoASCII, 16u }, + new object[] { Data_ArrASCII, Data_ValASCII, 17u }, + }; - public static readonly IccLutAToBTagDataEntry LutAToB_Val - = new( - new IccCurveTagDataEntry[] - { - Curve_Val_0, - Curve_Val_1, - Curve_Val_2, - }, - IccTestDataMatrix.Single_2DArray_ValGrad, - IccTestDataMatrix.Single_1DArray_ValGrad, - 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 byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 - new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 - new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 - new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 - new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 - - // B - CurveFull_0, // 12 bytes - CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // Matrix - IccTestDataMatrix.Fix16_2D_Grad, // 36 bytes - IccTestDataMatrix.Fix16_1D_Grad, // 12 bytes - - // M - CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_0, // 12 bytes - - // CLUT - IccTestDataLut.CLUT_16, // 74 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // A - CurveFull_2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 }); // Padding - - public static readonly object[][] LutAToBTagDataEntryTestData = - { - new object[] { LutAToB_Arr, LutAToB_Val }, - }; + public static readonly IccDateTimeTagDataEntry DateTime_Val = new(IccTestDataNonPrimitives.DateTime_ValRand1); + public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; - public static readonly IccLutBToATagDataEntry LutBToA_Val = new( - new[] - { - Curve_Val_0, - Curve_Val_1, - }, - null, - null, - null, + public static readonly object[][] DateTimeTagDataEntryTestData = + { + new object[] { DateTime_Arr, DateTime_Val }, + }; + + public static readonly IccLut16TagDataEntry Lut16_Val = new( + 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 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); + + public static readonly object[][] Lut16TagDataEntryTestData = + { + new object[] { Lut16_Arr, Lut16_Val }, + }; + + public static readonly IccLut8TagDataEntry Lut8_Val = new( + 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 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); + + public static readonly object[][] Lut8TagDataEntryTestData = + { + new object[] { Lut8_Arr, Lut8_Val }, + }; + + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat( + TagDataEntryHeader_CurveArr, + Curve_Arr_0); + + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat( + TagDataEntryHeader_CurveArr, + Curve_Arr_1); + + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat( + TagDataEntryHeader_CurveArr, + Curve_Arr_2); + + public static readonly IccLutAToBTagDataEntry LutAToB_Val + = new( + new IccCurveTagDataEntry[] + { + Curve_Val_0, + Curve_Val_1, + Curve_Val_2, + }, + IccTestDataMatrix.Single_2DArray_ValGrad, + IccTestDataMatrix.Single_1DArray_ValGrad, + new IccCurveTagDataEntry[] { Curve_Val_1, Curve_Val_2, Curve_Val_0 }, IccTestDataLut.CLUT_Val16, - new[] { Curve_Val_2, Curve_Val_1, Curve_Val_0 }); - - public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 - new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 - new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 - - // B - CurveFull_0, // 12 bytes - CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // CLUT - IccTestDataLut.CLUT_16, // 74 bytes - new byte[] { 0x00, 0x00 }, // Padding - - // A - CurveFull_2, // 18 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 }, // Padding - CurveFull_0); // 12 bytes - - public static readonly object[][] LutBToATagDataEntryTestData = - { - new object[] { LutBToA_Arr, LutBToA_Val }, - }; - - public static readonly IccMeasurementTagDataEntry Measurement_Val = new( - IccStandardObserver.Cie1931Observer, - IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccMeasurementGeometry.Degree0ToDOrDTo0, - 1f, - IccStandardIlluminant.D50); - - public static readonly byte[] Measurement_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_1, - IccTestDataNonPrimitives.XyzNumber_Var1, - IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UFix16_1, - IccTestDataPrimitives.UInt32_1); - - public static readonly object[][] MeasurementTagDataEntryTestData = + new IccCurveTagDataEntry[] { Curve_Val_2, Curve_Val_1 }); + + 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 + new byte[] { 0x00, 0x00, 0x00, 0xB0 }, // clut: 176 + new byte[] { 0x00, 0x00, 0x00, 0xFC }, // a: 252 + + // B + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // Matrix + IccTestDataMatrix.Fix16_2D_Grad, // 36 bytes + IccTestDataMatrix.Fix16_1D_Grad, // 12 bytes + + // M + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0, // 12 bytes + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }); // Padding + + public static readonly object[][] LutAToBTagDataEntryTestData = + { + new object[] { LutAToB_Arr, LutAToB_Val }, + }; + + public static readonly IccLutBToATagDataEntry LutBToA_Val = new( + new[] + { + Curve_Val_0, + Curve_Val_1, + }, + null, + null, + null, + IccTestDataLut.CLUT_Val16, + new[] { Curve_Val_2, Curve_Val_1, Curve_Val_0 }); + + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, + new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // matrix: 0 + new byte[] { 0x00, 0x00, 0x00, 0x00 }, // m: 0 + new byte[] { 0x00, 0x00, 0x00, 0x3C }, // clut: 60 + new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 + + // B + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // CLUT + IccTestDataLut.CLUT_16, // 74 bytes + new byte[] { 0x00, 0x00 }, // Padding + + // A + CurveFull_2, // 18 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_1, // 14 bytes + new byte[] { 0x00, 0x00 }, // Padding + CurveFull_0); // 12 bytes + + public static readonly object[][] LutBToATagDataEntryTestData = + { + new object[] { LutBToA_Arr, LutBToA_Val }, + }; + + public static readonly IccMeasurementTagDataEntry Measurement_Val = new( + IccStandardObserver.Cie1931Observer, + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, + 1f, + IccStandardIlluminant.D50); + + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_1, + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UInt32_1); + + public static readonly object[][] MeasurementTagDataEntryTestData = + { + new object[] { Measurement_Arr, Measurement_Val }, + }; + + 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); + private static readonly IccLocalizedString LocalizedString_Rand2_esXL = CreateLocalizedString("es", "XL", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand2_xyXL = CreateLocalizedString("xy", "XL", IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand_en = CreateLocalizedString("en", null, IccTestDataPrimitives.Unicode_ValRand2); + private static readonly IccLocalizedString LocalizedString_Rand_Invariant = new(CultureInfo.InvariantCulture, IccTestDataPrimitives.Unicode_ValRand3); + + private static IccLocalizedString CreateLocalizedString(string language, string country, string text) + { + CultureInfo culture; + if (country == null) { - new object[] { Measurement_Arr, Measurement_Val }, - }; - - 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); - private static readonly IccLocalizedString LocalizedString_Rand2_esXL = CreateLocalizedString("es", "XL", IccTestDataPrimitives.Unicode_ValRand2); - private static readonly IccLocalizedString LocalizedString_Rand2_xyXL = CreateLocalizedString("xy", "XL", IccTestDataPrimitives.Unicode_ValRand2); - private static readonly IccLocalizedString LocalizedString_Rand_en = CreateLocalizedString("en", null, IccTestDataPrimitives.Unicode_ValRand2); - private static readonly IccLocalizedString LocalizedString_Rand_Invariant = new(CultureInfo.InvariantCulture, IccTestDataPrimitives.Unicode_ValRand3); - - private static IccLocalizedString CreateLocalizedString(string language, string country, string text) + try + { + culture = new CultureInfo(language); + } + catch (CultureNotFoundException) + { + culture = CultureInfo.InvariantCulture; + } + } + else { - CultureInfo culture; - if (country == null) + try { - try - { - culture = new CultureInfo(language); - } - catch (CultureNotFoundException) - { - culture = CultureInfo.InvariantCulture; - } + culture = new CultureInfo($"{language}-{country}"); } - else + catch (CultureNotFoundException) { - try - { - culture = new CultureInfo($"{language}-{country}"); - } - catch (CultureNotFoundException) - { - return CreateLocalizedString(language, null, text); - } + return CreateLocalizedString(language, null, text); } - - return new IccLocalizedString(culture, text); } - private static readonly IccLocalizedString[] LocalizedString_RandArr_enUS_deDE = new IccLocalizedString[] - { - LocalizedString_Rand_enUS, - LocalizedString_Rand_deDE, - }; + return new IccLocalizedString(culture, text); + } - private static readonly IccLocalizedString[] LocalizedString_RandArr_en_Invariant = new IccLocalizedString[] - { - LocalizedString_Rand_en, - LocalizedString_Rand_Invariant, - }; + private static readonly IccLocalizedString[] LocalizedString_RandArr_enUS_deDE = new IccLocalizedString[] + { + LocalizedString_Rand_enUS, + LocalizedString_Rand_deDE, + }; - private static readonly IccLocalizedString[] LocalizedString_SameArr_enUS_deDE_esXL_xyXL = new IccLocalizedString[] - { - LocalizedString_Rand_enUS, - LocalizedString_Rand2_deDE, - LocalizedString_Rand2_esXL, - LocalizedString_Rand2_xyXL, - }; - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new(LocalizedString_RandArr_enUS_deDE); - 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); - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new(LocalizedString_RandArr_en_Invariant); - 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); - - 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); - - public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val3 = new(LocalizedString_SameArr_enUS_deDE_esXL_xyXL); - 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); - - public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = - { - new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, - new object[] { MultiLocalizedUnicode_Arr2_Read, MultiLocalizedUnicode_Val2 }, - new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, - }; + private static readonly IccLocalizedString[] LocalizedString_RandArr_en_Invariant = new IccLocalizedString[] + { + LocalizedString_Rand_en, + LocalizedString_Rand_Invariant, + }; - public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Write = - { - new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, - new object[] { MultiLocalizedUnicode_Arr2_Write, MultiLocalizedUnicode_Val2 }, - new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, - }; + private static readonly IccLocalizedString[] LocalizedString_SameArr_enUS_deDE_esXL_xyXL = new IccLocalizedString[] + { + LocalizedString_Rand_enUS, + LocalizedString_Rand2_deDE, + LocalizedString_Rand2_esXL, + LocalizedString_Rand2_xyXL, + }; + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new(LocalizedString_RandArr_enUS_deDE); + 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); + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new(LocalizedString_RandArr_en_Invariant); + 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); + + 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); + + public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val3 = new(LocalizedString_SameArr_enUS_deDE_esXL_xyXL); + 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); + + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + new object[] { MultiLocalizedUnicode_Arr2_Read, MultiLocalizedUnicode_Val2 }, + new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, + }; - public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new( - new IccMultiProcessElement[] - { - 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 - IccTestDataMultiProcessElements.MPE_CLUT, - IccTestDataMultiProcessElements.MPE_CLUT); - - public static readonly object[][] MultiProcessElementsTagDataEntryTestData = - { - new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, - }; - - public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new( - 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( - new byte[] { 0x01, 0x02, 0x03, 0x04 }, - IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3, - ArrayHelper.Fill((byte)0x41, 31), - new byte[] { 0x00 }, - ArrayHelper.Fill((byte)0x34, 31), - new byte[] { 0x00 }, - IccTestDataNonPrimitives.NamedColor_Min, - IccTestDataNonPrimitives.NamedColor_Min); - - public static readonly object[][] NamedColor2TagDataEntryTestData = + public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Write = + { + new object[] { MultiLocalizedUnicode_Arr, MultiLocalizedUnicode_Val }, + new object[] { MultiLocalizedUnicode_Arr2_Write, MultiLocalizedUnicode_Val2 }, + new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, + }; + + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new( + new IccMultiProcessElement[] { - new object[] { NamedColor2_Arr, NamedColor2_Val }, - }; + IccTestDataMultiProcessElements.MPE_ValCLUT, + IccTestDataMultiProcessElements.MPE_ValCLUT, + }); - public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new(IccTestDataCurves.Parametric_ValVar1); - public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; + 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 + IccTestDataMultiProcessElements.MPE_CLUT, + IccTestDataMultiProcessElements.MPE_CLUT); + + public static readonly object[][] MultiProcessElementsTagDataEntryTestData = + { + new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, + }; + + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new( + 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( + new byte[] { 0x01, 0x02, 0x03, 0x04 }, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3, + ArrayHelper.Fill((byte)0x41, 31), + new byte[] { 0x00 }, + ArrayHelper.Fill((byte)0x34, 31), + new byte[] { 0x00 }, + IccTestDataNonPrimitives.NamedColor_Min, + IccTestDataNonPrimitives.NamedColor_Min); + + public static readonly object[][] NamedColor2TagDataEntryTestData = + { + new object[] { NamedColor2_Arr, NamedColor2_Val }, + }; + + public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new(IccTestDataCurves.Parametric_ValVar1); + public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; + + public static readonly object[][] ParametricCurveTagDataEntryTestData = + { + new object[] { ParametricCurve_Arr, ParametricCurve_Val }, + }; - public static readonly object[][] ParametricCurveTagDataEntryTestData = + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new( + new IccProfileDescription[] { - new object[] { ParametricCurve_Arr, ParametricCurve_Val }, - }; + IccTestDataNonPrimitives.ProfileDescription_ValRand1, + IccTestDataNonPrimitives.ProfileDescription_ValRand1 + }); - public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new( - new IccProfileDescription[] - { - IccTestDataNonPrimitives.ProfileDescription_ValRand1, - IccTestDataNonPrimitives.ProfileDescription_ValRand1 - }); + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ProfileDescription_Rand1, + IccTestDataNonPrimitives.ProfileDescription_Rand1); - public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_2, - IccTestDataNonPrimitives.ProfileDescription_Rand1, - IccTestDataNonPrimitives.ProfileDescription_Rand1); + public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = + { + new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, + }; - public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new( + new IccProfileSequenceIdentifier[] { - new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, - }; + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), + new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), + }); - public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new( - 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, - 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 }); - - public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = - { - new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, - }; + 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 }); + + public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = + { + new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, + }; - public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new( - new IccResponseCurve[] - { - IccTestDataCurves.Response_ValGrad, - IccTestDataCurves.Response_ValGrad, - }); - - 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 - - public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new( + new IccResponseCurve[] { - new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, - }; + IccTestDataCurves.Response_ValGrad, + IccTestDataCurves.Response_ValGrad, + }); - public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); - public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Fix16_1, - IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3); + 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 - public static readonly object[][] Fix16ArrayTagDataEntryTestData = - { - new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, - }; + public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = + { + new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, + }; - public static readonly IccSignatureTagDataEntry Signature_Val = new("ABCD"); - public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; + public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.Fix16_1, + IccTestDataPrimitives.Fix16_2, + IccTestDataPrimitives.Fix16_3); - public static readonly object[][] SignatureTagDataEntryTestData = - { - new object[] { Signature_Arr, Signature_Val }, - }; + public static readonly object[][] Fix16ArrayTagDataEntryTestData = + { + new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, + }; - public static readonly IccTextTagDataEntry Text_Val = new("ABCD"); - public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; + public static readonly IccSignatureTagDataEntry Signature_Val = new("ABCD"); + public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; - public static readonly object[][] TextTagDataEntryTestData = - { - new object[] { Text_Arr, Text_Val, 12u }, - }; + public static readonly object[][] SignatureTagDataEntryTestData = + { + new object[] { Signature_Arr, Signature_Val }, + }; - public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new(new float[] { 1, 2, 3 }); - public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UFix16_1, - IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3); + public static readonly IccTextTagDataEntry Text_Val = new("ABCD"); + public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; - public static readonly object[][] UFix16ArrayTagDataEntryTestData = - { - new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, - }; + public static readonly object[][] TextTagDataEntryTestData = + { + new object[] { Text_Arr, Text_Val, 12u }, + }; - public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new(new ushort[] { 1, 2, 3 }); - public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt16_1, - IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3); + public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new(new float[] { 1, 2, 3 }); + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UFix16_1, + IccTestDataPrimitives.UFix16_2, + IccTestDataPrimitives.UFix16_3); - public static readonly object[][] UInt16ArrayTagDataEntryTestData = - { - new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, - }; + public static readonly object[][] UFix16ArrayTagDataEntryTestData = + { + new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, + }; - public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new(new uint[] { 1, 2, 3 }); - public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3); + public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new(new ushort[] { 1, 2, 3 }); + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt16_1, + IccTestDataPrimitives.UInt16_2, + IccTestDataPrimitives.UInt16_3); - public static readonly object[][] UInt32ArrayTagDataEntryTestData = - { - new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, - }; + public static readonly object[][] UInt16ArrayTagDataEntryTestData = + { + new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, + }; - public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new(new ulong[] { 1, 2, 3 }); - public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt64_1, - IccTestDataPrimitives.UInt64_2, - IccTestDataPrimitives.UInt64_3); + public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new(new uint[] { 1, 2, 3 }); + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataPrimitives.UInt32_3); - public static readonly object[][] UInt64ArrayTagDataEntryTestData = - { - new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, - }; + public static readonly object[][] UInt32ArrayTagDataEntryTestData = + { + new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, + }; - public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new(new byte[] { 1, 2, 3 }); - public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; + public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new(new ulong[] { 1, 2, 3 }); + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt64_1, + IccTestDataPrimitives.UInt64_2, + IccTestDataPrimitives.UInt64_3); - public static readonly object[][] UInt8ArrayTagDataEntryTestData = - { - new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, - }; + public static readonly object[][] UInt64ArrayTagDataEntryTestData = + { + new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, + }; - public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new( - IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccStandardIlluminant.D50); + public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new(new byte[] { 1, 2, 3 }); + public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; - public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat( - IccTestDataNonPrimitives.XyzNumber_Var1, - IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataPrimitives.UInt32_1); + public static readonly object[][] UInt8ArrayTagDataEntryTestData = + { + new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, + }; - public static readonly object[][] ViewingConditionsTagDataEntryTestData = - { - new object[] { ViewingConditions_Arr, ViewingConditions_Val }, - }; + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new( + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccStandardIlluminant.D50); - public static readonly IccXyzTagDataEntry XYZ_Val = new(new Vector3[] - { - IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccTestDataNonPrimitives.XyzNumber_ValVar3, - }); + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataPrimitives.UInt32_1); - public static readonly byte[] XYZ_Arr = ArrayHelper.Concat( - IccTestDataNonPrimitives.XyzNumber_Var1, - IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataNonPrimitives.XyzNumber_Var3); + public static readonly object[][] ViewingConditionsTagDataEntryTestData = + { + new object[] { ViewingConditions_Arr, ViewingConditions_Val }, + }; - public static readonly object[][] XYZTagDataEntryTestData = - { - new object[] { XYZ_Arr, XYZ_Val, 44u }, - }; - - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new( - 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[] { 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 - ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 }); // Null terminator - - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); - 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)); - - public static readonly object[][] TextDescriptionTagDataEntryTestData = - { - new object[] { TextDescription_Arr1, TextDescription_Val1 }, - new object[] { TextDescription_Arr2, TextDescription_Val2 }, - }; - - public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new( - IccTestDataPrimitives.Ascii_ValRand4, - IccTestDataPrimitives.Ascii_ValRand1, - IccTestDataPrimitives.Ascii_ValRand2, - IccTestDataPrimitives.Ascii_ValRand3, - IccTestDataPrimitives.Ascii_ValRand4); - - public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.Ascii_Rand4, - new byte[] { 0 }, - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.Ascii_Rand1, - new byte[] { 0 }, - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.Ascii_Rand2, - new byte[] { 0 }, - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.Ascii_Rand3, - new byte[] { 0 }, - IccTestDataPrimitives.UInt32_6, - IccTestDataPrimitives.Ascii_Rand4, - new byte[] { 0 }); - - public static readonly object[][] CrdInfoTagDataEntryTestData = - { - new object[] { CrdInfo_Arr, CrdInfo_Val }, - }; + public static readonly IccXyzTagDataEntry XYZ_Val = new(new Vector3[] + { + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccTestDataNonPrimitives.XyzNumber_ValVar2, + IccTestDataNonPrimitives.XyzNumber_ValVar3, + }); - public static readonly IccScreeningTagDataEntry Screening_Val = new( - IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, - new IccScreeningChannel[] { IccTestDataNonPrimitives.ScreeningChannel_ValRand1, IccTestDataNonPrimitives.ScreeningChannel_ValRand2 }); + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat( + IccTestDataNonPrimitives.XyzNumber_Var1, + IccTestDataNonPrimitives.XyzNumber_Var2, + IccTestDataNonPrimitives.XyzNumber_Var3); - public static readonly byte[] Screening_Arr = ArrayHelper.Concat( - IccTestDataPrimitives.Int32_1, - IccTestDataPrimitives.UInt32_2, - IccTestDataNonPrimitives.ScreeningChannel_Rand1, - IccTestDataNonPrimitives.ScreeningChannel_Rand2); + public static readonly object[][] XYZTagDataEntryTestData = + { + new object[] { XYZ_Arr, XYZ_Val, 44u }, + }; + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new( + 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[] { 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 + ArrayHelper.Fill((byte)0x41, 66), + new byte[] { 0x00 }); // Null terminator + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); + 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)); + + public static readonly object[][] TextDescriptionTagDataEntryTestData = + { + new object[] { TextDescription_Arr1, TextDescription_Val1 }, + new object[] { TextDescription_Arr2, TextDescription_Val2 }, + }; + + public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new( + IccTestDataPrimitives.Ascii_ValRand4, + IccTestDataPrimitives.Ascii_ValRand1, + IccTestDataPrimitives.Ascii_ValRand2, + IccTestDataPrimitives.Ascii_ValRand3, + IccTestDataPrimitives.Ascii_ValRand4); + + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand1, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand2, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand3, + new byte[] { 0 }, + IccTestDataPrimitives.UInt32_6, + IccTestDataPrimitives.Ascii_Rand4, + new byte[] { 0 }); + + public static readonly object[][] CrdInfoTagDataEntryTestData = + { + new object[] { CrdInfo_Arr, CrdInfo_Val }, + }; - public static readonly object[][] ScreeningTagDataEntryTestData = - { - new object[] { Screening_Arr, Screening_Val }, - }; - - public static readonly IccUcrBgTagDataEntry UcrBg_Val = new( - new ushort[] { 3, 4, 6 }, - new ushort[] { 9, 7, 2, 5 }, - 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 }); - - public static readonly object[][] UcrBgTagDataEntryTestData = - { - new object[] { UcrBg_Arr, UcrBg_Val, 41 }, - }; + public static readonly IccScreeningTagDataEntry Screening_Val = new( + IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, + new IccScreeningChannel[] { IccTestDataNonPrimitives.ScreeningChannel_ValRand1, IccTestDataNonPrimitives.ScreeningChannel_ValRand2 }); - public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; - public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat( - TagDataEntryHeader_CurveArr, - Curve_Arr_2, - new byte[] { 0x00, 0x00 }); // padding + public static readonly byte[] Screening_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.Int32_1, + IccTestDataPrimitives.UInt32_2, + IccTestDataNonPrimitives.ScreeningChannel_Rand1, + IccTestDataNonPrimitives.ScreeningChannel_Rand2); - public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; - public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat( - TagDataEntryHeader_MultiLocalizedUnicodeArr, - MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 }); // padding + public static readonly object[][] ScreeningTagDataEntryTestData = + { + new object[] { Screening_Arr, Screening_Val }, + }; + + public static readonly IccUcrBgTagDataEntry UcrBg_Val = new( + new ushort[] { 3, 4, 6 }, + new ushort[] { 9, 7, 2, 5 }, + 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 }); + + public static readonly object[][] UcrBgTagDataEntryTestData = + { + new object[] { UcrBg_Arr, UcrBg_Val, 41 }, + }; - public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new( - IccProfileTag.Unknown, - 0, - (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2); + public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat( + TagDataEntryHeader_CurveArr, + Curve_Arr_2, + new byte[] { 0x00, 0x00 }); // padding - public static readonly IccTagTableEntry TagDataEntry_CurveTable = new(IccProfileTag.Unknown, 0, (uint)TagDataEntry_CurveArr.Length - 2); + public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat( + TagDataEntryHeader_MultiLocalizedUnicodeArr, + MultiLocalizedUnicode_Arr, + new byte[] { 0x00, 0x00 }); // padding - public static readonly object[][] TagDataEntryTestData = - { - new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, - new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, - }; - } + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new( + IccProfileTag.Unknown, + 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2); + + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new(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 }, + }; } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 8a14a6be2e..9d920d7189 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -1,167 +1,164 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// A test image file. +/// +public sealed class TestFile { /// - /// A test image file. + /// The test file cache. + /// + private static readonly ConcurrentDictionary Cache = new(); + + /// + /// The "Formats" directory, as lazy value + /// + // ReSharper disable once InconsistentNaming + private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); + + /// + /// The image (lazy initialized value) + /// + private volatile Image image; + + /// + /// Used to ensure image loading is threadsafe. + /// + private readonly object syncLock = new(); + + /// + /// The image bytes + /// + private byte[] bytes; + + /// + /// Initializes a new instance of the class. + /// + /// The file. + private TestFile(string file) => this.FullPath = file; + + /// + /// Gets the image bytes. + /// + public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); + + /// + /// Gets the full path to file. + /// + public string FullPath { get; } + + /// + /// Gets the file name. + /// + public string FileName => Path.GetFileName(this.FullPath); + + /// + /// Gets the file name without extension. + /// + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); + + /// + /// Gets the image with lazy initialization. /// - public sealed class TestFile + private Image Image { - /// - /// The test file cache. - /// - private static readonly ConcurrentDictionary Cache = new(); - - /// - /// The "Formats" directory, as lazy value - /// - // ReSharper disable once InconsistentNaming - private static readonly Lazy InputImagesDirectoryValue = new(() => TestEnvironment.InputImagesDirectoryFullPath); - - /// - /// The image (lazy initialized value) - /// - private volatile Image image; - - /// - /// Used to ensure image loading is threadsafe. - /// - private readonly object syncLock = new(); - - /// - /// The image bytes - /// - private byte[] bytes; - - /// - /// Initializes a new instance of the class. - /// - /// The file. - private TestFile(string file) => this.FullPath = file; - - /// - /// Gets the image bytes. - /// - public byte[] Bytes => this.bytes ??= File.ReadAllBytes(this.FullPath); - - /// - /// Gets the full path to file. - /// - public string FullPath { get; } - - /// - /// Gets the file name. - /// - public string FileName => Path.GetFileName(this.FullPath); - - /// - /// Gets the file name without extension. - /// - public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); - - /// - /// Gets the image with lazy initialization. - /// - private Image Image + get { - get + if (this.image is null) { - if (this.image is null) + lock (this.syncLock) { - lock (this.syncLock) - { - this.image ??= ImageSharp.Image.Load(this.Bytes); - } + this.image ??= ImageSharp.Image.Load(this.Bytes); } - - return this.image; } - } - /// - /// Gets the input image directory. - /// - private static string InputImagesDirectory => InputImagesDirectoryValue.Value; - - /// - /// Gets the full qualified path to the input test file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetInputFileFullPath(string file) - => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); - - /// - /// Creates a new test file or returns one from the cache. - /// - /// The file path. - /// - /// The . - /// - public static TestFile Create(string file) - => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(fileName))); - - /// - /// Gets the file name. - /// - /// The value. - /// - /// The . - /// - public string GetFileName(object value) - => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; - - /// - /// Gets the file name without extension. - /// - /// The value. - /// - /// The . - /// - public string GetFileNameWithoutExtension(object value) - => this.FileNameWithoutExtension + "-" + value; - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image() - => this.Image.Clone(); - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image(IImageDecoder decoder) - => this.CreateRgba32Image(decoder, new()); - - /// - /// Creates a new image. - /// - /// - /// The . - /// - public Image CreateRgba32Image(IImageDecoder decoder, DecoderOptions options) - { - options.Configuration = this.Image.GetConfiguration(); - using MemoryStream stream = new(this.Bytes); - return decoder.Decode(options, stream, default); + return this.image; } } + + /// + /// Gets the input image directory. + /// + private static string InputImagesDirectory => InputImagesDirectoryValue.Value; + + /// + /// Gets the full qualified path to the input test file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetInputFileFullPath(string file) + => Path.Combine(InputImagesDirectory, file).Replace('\\', Path.DirectorySeparatorChar); + + /// + /// Creates a new test file or returns one from the cache. + /// + /// The file path. + /// + /// The . + /// + public static TestFile Create(string file) + => Cache.GetOrAdd(file, (string fileName) => new TestFile(GetInputFileFullPath(fileName))); + + /// + /// Gets the file name. + /// + /// The value. + /// + /// The . + /// + public string GetFileName(object value) + => $"{this.FileNameWithoutExtension}-{value}{Path.GetExtension(this.FullPath)}"; + + /// + /// Gets the file name without extension. + /// + /// The value. + /// + /// The . + /// + public string GetFileNameWithoutExtension(object value) + => this.FileNameWithoutExtension + "-" + value; + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image() + => this.Image.Clone(); + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image(IImageDecoder decoder) + => this.CreateRgba32Image(decoder, new()); + + /// + /// Creates a new image. + /// + /// + /// The . + /// + public Image CreateRgba32Image(IImageDecoder decoder, DecoderOptions options) + { + options.Configuration = this.Image.GetConfiguration(); + using MemoryStream stream = new(this.Bytes); + return decoder.Decode(options, stream, default); + } } diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs index ca7810adde..8aefbe320e 100644 --- a/tests/ImageSharp.Tests/TestFileSystem.cs +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -1,57 +1,52 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +/// +/// A test image file. +/// +public class TestFileSystem : ImageSharp.IO.IFileSystem { - /// - /// A test image file. - /// - public class TestFileSystem : ImageSharp.IO.IFileSystem - { - private readonly Dictionary> fileSystem = new Dictionary>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> fileSystem = new Dictionary>(StringComparer.OrdinalIgnoreCase); - public void AddFile(string path, Func data) + public void AddFile(string path, Func data) + { + lock (this.fileSystem) { - lock (this.fileSystem) - { - this.fileSystem.Add(path, data); - } + this.fileSystem.Add(path, data); } + } - public Stream Create(string path) + public Stream Create(string path) + { + // if we have injected a fake file use it instead + lock (this.fileSystem) { - // if we have injected a fake file use it instead - lock (this.fileSystem) + if (this.fileSystem.ContainsKey(path)) { - if (this.fileSystem.ContainsKey(path)) - { - Stream stream = this.fileSystem[path](); - stream.Position = 0; - return stream; - } + Stream stream = this.fileSystem[path](); + stream.Position = 0; + return stream; } - - return File.Create(path); } - public Stream OpenRead(string path) + return File.Create(path); + } + + public Stream OpenRead(string path) + { + // if we have injected a fake file use it instead + lock (this.fileSystem) { - // if we have injected a fake file use it instead - lock (this.fileSystem) + if (this.fileSystem.ContainsKey(path)) { - if (this.fileSystem.ContainsKey(path)) - { - Stream stream = this.fileSystem[path](); - stream.Position = 0; - return stream; - } + Stream stream = this.fileSystem[path](); + stream.Position = 0; + return stream; } - - return File.OpenRead(path); } + + return File.OpenRead(path); } } diff --git a/tests/ImageSharp.Tests/TestFontUtilities.cs b/tests/ImageSharp.Tests/TestFontUtilities.cs index c03af763ad..01b1faa45a 100644 --- a/tests/ImageSharp.Tests/TestFontUtilities.cs +++ b/tests/ImageSharp.Tests/TestFontUtilities.cs @@ -1,87 +1,83 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// A test image file. +/// +public static class TestFontUtilities { /// - /// A test image file. + /// The formats directory. /// - public static class TestFontUtilities + private static readonly string FormatsDirectory = GetFontsDirectory(); + + /// + /// Gets the full qualified path to the file. + /// + /// + /// The file path. + /// + /// + /// The . + /// + public static string GetPath(string file) { - /// - /// The formats directory. - /// - private static readonly string FormatsDirectory = GetFontsDirectory(); + return Path.Combine(FormatsDirectory, file); + } - /// - /// Gets the full qualified path to the file. - /// - /// - /// The file path. - /// - /// - /// The . - /// - public static string GetPath(string file) + /// + /// Gets the correct path to the formats directory. + /// + /// + /// The . + /// + private static string GetFontsDirectory() + { + List directories = new List { - return Path.Combine(FormatsDirectory, file); - } + "TestFonts/", // Here for code coverage tests. + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" + }; - /// - /// Gets the correct path to the formats directory. - /// - /// - /// The . - /// - private static string GetFontsDirectory() - { - List directories = new List - { - "TestFonts/", // Here for code coverage tests. - "tests/ImageSharp.Tests/TestFonts/", // from travis/build script - "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 - "../../../../TestFonts/" - }; + directories = directories.SelectMany(x => new[] + { + Path.GetFullPath(x) + }).ToList(); - directories = directories.SelectMany(x => new[] - { - Path.GetFullPath(x) - }).ToList(); + AddFormatsDirectoryFromTestAssemblyPath(directories); - AddFormatsDirectoryFromTestAssemblyPath(directories); + string directory = directories.FirstOrDefault(Directory.Exists); - string directory = directories.FirstOrDefault(Directory.Exists); + if (directory != null) + { + return directory; + } - if (directory != null) - { - return directory; - } + throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); + } - throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]"); - } + /// + /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory + /// in certain scenarios like dotTrace test profiling. + /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. + /// + /// The directories list + private static void AddFormatsDirectoryFromTestAssemblyPath(List directories) + { + string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; + assemblyLocation = Path.GetDirectoryName(assemblyLocation); - /// - /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory - /// in certain scenarios like dotTrace test profiling. - /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location. - /// - /// The directories list - private static void AddFormatsDirectoryFromTestAssemblyPath(List directories) + if (assemblyLocation != null) { - string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location; - assemblyLocation = Path.GetDirectoryName(assemblyLocation); - - if (assemblyLocation != null) - { - string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); - dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); - directories.Add(dirFromAssemblyLocation); - } + string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/"); + dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation); + directories.Add(dirFromAssemblyLocation); } } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 9ec7d8f0be..127ccd32df 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -1,343 +1,335 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Numerics; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// A test image file. +/// +public class TestFormat : IConfigurationModule, IImageFormat { - /// - /// A test image file. - /// - public class TestFormat : IConfigurationModule, IImageFormat - { - private readonly Dictionary sampleImages = new(); + private readonly Dictionary sampleImages = new(); - // We should not change Configuration.Default in individual tests! - // Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead! - public static TestFormat GlobalTestFormat { get; } = new(); + // We should not change Configuration.Default in individual tests! + // Create new configuration instances with new Configuration(TestFormat.GlobalTestFormat) instead! + public static TestFormat GlobalTestFormat { get; } = new(); - public TestFormat() - { - this.Encoder = new TestEncoder(this); - this.Decoder = new TestDecoder(this); - } + public TestFormat() + { + this.Encoder = new TestEncoder(this); + this.Decoder = new TestDecoder(this); + } - public List DecodeCalls { get; } = new(); + public List DecodeCalls { get; } = new(); - public TestEncoder Encoder { get; } + public TestEncoder Encoder { get; } - public TestDecoder Decoder { get; } + public TestDecoder Decoder { get; } - private readonly byte[] header = Guid.NewGuid().ToByteArray(); + private readonly byte[] header = Guid.NewGuid().ToByteArray(); - public MemoryStream CreateStream(byte[] marker = null) + public MemoryStream CreateStream(byte[] marker = null) + { + var ms = new MemoryStream(); + byte[] data = this.header; + ms.Write(data, 0, data.Length); + if (marker != null) { - var ms = new MemoryStream(); - byte[] data = this.header; - ms.Write(data, 0, data.Length); - if (marker != null) - { - ms.Write(marker, 0, marker.Length); - } - - ms.Position = 0; - return ms; + ms.Write(marker, 0, marker.Length); } - public Stream CreateAsyncSemaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) - { - byte[] buffer = new byte[size]; - this.header.CopyTo(buffer, 0); - var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); - return seeakable ? semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); - } + ms.Position = 0; + return ms; + } - public void VerifySpecificDecodeCall(byte[] marker, Configuration config) - where TPixel : unmanaged, IPixel - { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); + public Stream CreateAsyncSemaphoreStream(SemaphoreSlim notifyWaitPositionReachedSemaphore, SemaphoreSlim continueSemaphore, bool seeakable, int size = 1024, int waitAfterPosition = 512) + { + byte[] buffer = new byte[size]; + this.header.CopyTo(buffer, 0); + var semaphoreStream = new SemaphoreReadMemoryStream(buffer, waitAfterPosition, notifyWaitPositionReachedSemaphore, continueSemaphore); + return seeakable ? semaphoreStream : new AsyncStreamWrapper(semaphoreStream, () => false); + } - Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); + public void VerifySpecificDecodeCall(byte[] marker, Configuration config) + where TPixel : unmanaged, IPixel + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - foreach (DecodeOperation d in discovered) - { - this.DecodeCalls.Remove(d); - } - } + Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); - public void VerifyAgnosticDecodeCall(byte[] marker, Configuration config) + foreach (DecodeOperation d in discovered) { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); + this.DecodeCalls.Remove(d); + } + } - Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); + public void VerifyAgnosticDecodeCall(byte[] marker, Configuration config) + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); - foreach (DecodeOperation d in discovered) - { - this.DecodeCalls.Remove(d); - } + Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); + + foreach (DecodeOperation d in discovered) + { + this.DecodeCalls.Remove(d); } + } - public Image Sample() - where TPixel : unmanaged, IPixel + public Image Sample() + where TPixel : unmanaged, IPixel + { + lock (this.sampleImages) { - lock (this.sampleImages) + if (!this.sampleImages.ContainsKey(typeof(TPixel))) { - if (!this.sampleImages.ContainsKey(typeof(TPixel))) - { - this.sampleImages.Add(typeof(TPixel), new Image(1, 1)); - } - - return (Image)this.sampleImages[typeof(TPixel)]; + this.sampleImages.Add(typeof(TPixel), new Image(1, 1)); } + + return (Image)this.sampleImages[typeof(TPixel)]; } + } - public Image SampleAgnostic() => this.Sample(); + public Image SampleAgnostic() => this.Sample(); - public string MimeType => "img/test"; + public string MimeType => "img/test"; - public string Extension => "test_ext"; + public string Extension => "test_ext"; - public IEnumerable SupportedExtensions => new[] { "test_ext" }; + public IEnumerable SupportedExtensions => new[] { "test_ext" }; - public int HeaderSize => this.header.Length; + public int HeaderSize => this.header.Length; - public string Name => this.Extension; + public string Name => this.Extension; - public string DefaultMimeType => this.MimeType; + public string DefaultMimeType => this.MimeType; - public IEnumerable MimeTypes => new[] { this.MimeType }; + public IEnumerable MimeTypes => new[] { this.MimeType }; - public IEnumerable FileExtensions => this.SupportedExtensions; + public IEnumerable FileExtensions => this.SupportedExtensions; - public bool IsSupportedFileFormat(ReadOnlySpan fileHeader) + public bool IsSupportedFileFormat(ReadOnlySpan fileHeader) + { + if (fileHeader.Length < this.header.Length) { - if (fileHeader.Length < this.header.Length) + return false; + } + + for (int i = 0; i < this.header.Length; i++) + { + if (fileHeader[i] != this.header[i]) { return false; } + } - for (int i = 0; i < this.header.Length; i++) - { - if (fileHeader[i] != this.header[i]) - { - return false; - } - } + return true; + } - return true; - } + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); + configuration.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); + configuration.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); + } - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); - configuration.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); - configuration.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); - } + public struct DecodeOperation + { + public byte[] Marker; + internal Configuration Config; - public struct DecodeOperation - { - 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) + { + return false; + } - public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) + if (testMarker.Length != this.Marker.Length) { - if (this.Config != config || this.PixelType != pixelType) - { - return false; - } + return false; + } - if (testMarker.Length != this.Marker.Length) + for (int i = 0; i < this.Marker.Length; i++) + { + if (testMarker[i] != this.Marker[i]) { return false; } - - for (int i = 0; i < this.Marker.Length; i++) - { - if (testMarker[i] != this.Marker[i]) - { - return false; - } - } - - return true; } + + return true; } + } - public class TestHeader : IImageFormatDetector - { - private readonly TestFormat testFormat; + public class TestHeader : IImageFormatDetector + { + private readonly TestFormat testFormat; - public int HeaderSize => this.testFormat.HeaderSize; + public int HeaderSize => this.testFormat.HeaderSize; - public IImageFormat DetectFormat(ReadOnlySpan header) + public IImageFormat DetectFormat(ReadOnlySpan header) + { + if (this.testFormat.IsSupportedFileFormat(header)) { - if (this.testFormat.IsSupportedFileFormat(header)) - { - return this.testFormat; - } - - return null; + return this.testFormat; } - public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; + return null; } - public class TestDecoder : IImageDecoderSpecialized - { - private readonly TestFormat testFormat; + public TestHeader(TestFormat testFormat) => this.testFormat = testFormat; + } - public TestDecoder(TestFormat testFormat) => this.testFormat = testFormat; + public class TestDecoder : IImageDecoderSpecialized + { + private readonly TestFormat testFormat; - public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; + public TestDecoder(TestFormat testFormat) => this.testFormat = testFormat; - public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public int HeaderSize => this.testFormat.HeaderSize; + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); + public int HeaderSize => this.testFormat.HeaderSize; - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Configuration configuration = options.GeneralOptions.Configuration; - var ms = new MemoryStream(); - stream.CopyTo(ms, configuration.StreamProcessingBufferSize); - byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); - this.testFormat.DecodeCalls.Add(new DecodeOperation - { - Marker = marker, - Config = configuration, - PixelType = typeof(TPixel) - }); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => ((IImageDecoderSpecialized)this).Decode(new() { GeneralOptions = options }, stream, cancellationToken); - // TODO record this happened so we can verify it. - return this.testFormat.Sample(); - } + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Configuration configuration = options.GeneralOptions.Configuration; + var ms = new MemoryStream(); + stream.CopyTo(ms, configuration.StreamProcessingBufferSize); + byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); + this.testFormat.DecodeCalls.Add(new DecodeOperation + { + Marker = marker, + Config = configuration, + PixelType = typeof(TPixel) + }); - public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + // TODO record this happened so we can verify it. + return this.testFormat.Sample(); } - public class TestDecoderOptions : ISpecializedDecoderOptions - { - public DecoderOptions GeneralOptions { get; set; } = new(); - } + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); + } - public class TestEncoder : IImageEncoder - { - private readonly TestFormat testFormat; + public class TestDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); + } - public TestEncoder(TestFormat testFormat) => this.testFormat = testFormat; + public class TestEncoder : IImageEncoder + { + private readonly TestFormat testFormat; - public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; + public TestEncoder(TestFormat testFormat) => this.testFormat = testFormat; - public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - // TODO record this happened so we can verify it. - } + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel => Task.CompletedTask; // TODO record this happened so we can verify it. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + // TODO record this happened so we can verify it. } - public struct TestPixelForAgnosticDecode : IPixel - { - public PixelOperations CreatePixelOperations() => new(); + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel => Task.CompletedTask; // TODO record this happened so we can verify it. + } - public void FromScaledVector4(Vector4 vector) - { - } + public struct TestPixelForAgnosticDecode : IPixel + { + public PixelOperations CreatePixelOperations() => new(); - public Vector4 ToScaledVector4() => default; + public void FromScaledVector4(Vector4 vector) + { + } - public void FromVector4(Vector4 vector) - { - } + public Vector4 ToScaledVector4() => default; - public Vector4 ToVector4() => default; + public void FromVector4(Vector4 vector) + { + } - public void FromArgb32(Argb32 source) - { - } + public Vector4 ToVector4() => default; - public void FromBgra5551(Bgra5551 source) - { - } + public void FromArgb32(Argb32 source) + { + } - public void FromBgr24(Bgr24 source) - { - } + public void FromBgra5551(Bgra5551 source) + { + } - public void FromBgra32(Bgra32 source) - { - } + public void FromBgr24(Bgr24 source) + { + } - public void FromAbgr32(Abgr32 source) - { - } + public void FromBgra32(Bgra32 source) + { + } - public void FromL8(L8 source) - { - } + public void FromAbgr32(Abgr32 source) + { + } - public void FromL16(L16 source) - { - } + public void FromL8(L8 source) + { + } - public void FromLa16(La16 source) - { - } + public void FromL16(L16 source) + { + } - public void FromLa32(La32 source) - { - } + public void FromLa16(La16 source) + { + } - public void FromRgb24(Rgb24 source) - { - } + public void FromLa32(La32 source) + { + } - public void FromRgba32(Rgba32 source) - { - } + public void FromRgb24(Rgb24 source) + { + } - public void ToRgba32(ref Rgba32 dest) - { - } + public void FromRgba32(Rgba32 source) + { + } - public void FromRgb48(Rgb48 source) - { - } + public void ToRgba32(ref Rgba32 dest) + { + } - public void FromRgba64(Rgba64 source) - { - } + public void FromRgb48(Rgb48 source) + { + } - public bool Equals(TestPixelForAgnosticDecode other) => false; + 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 4898d38b7a..676d460e56 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1,1003 +1,1001 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; // ReSharper disable InconsistentNaming // ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Class that contains all the relative test image paths in the TestImages/Formats directory. +/// Use with , . +/// +public static class TestImages { - /// - /// Class that contains all the relative test image paths in the TestImages/Formats directory. - /// Use with , . - /// - public static class TestImages + public static class Png { - public static class Png + public const string Transparency = "Png/transparency.png"; + public const string P1 = "Png/pl.png"; + public const string Pd = "Png/pd.png"; + public const string Blur = "Png/blur.png"; + public const string Indexed = "Png/indexed.png"; + public const string Splash = "Png/splash.png"; + public const string Cross = "Png/cross.png"; + public const string Powerpoint = "Png/pp.png"; + public const string SplashInterlaced = "Png/splash-interlaced.png"; + public const string Interlaced = "Png/interlaced.png"; + public const string Palette8Bpp = "Png/palette-8bpp.png"; + public const string Bpp1 = "Png/bpp1.png"; + public const string Gray4Bpp = "Png/gray_4bpp.png"; + public const string L16Bit = "Png/gray-16.png"; + public const string GrayA8Bit = "Png/gray-alpha-8.png"; + public const string GrayA8BitInterlaced = "Png/rollsroyce.png"; + public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; + public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; + public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; + public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; + public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; + public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; + public const string Rgb48Bpp = "Png/rgb-48bpp.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; + public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png"; + public const string Rgba64Bpp = "Png/rgb-16-alpha.png"; + public const string ColorsSaturationLightness = "Png/colors-saturation-lightness.png"; + public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; + public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; + public const string Bike = "Png/Bike.png"; + public const string BikeSmall = "Png/bike-small.png"; + public const string BikeGrayscale = "Png/BikeGrayscale.png"; + public const string SnakeGame = "Png/SnakeGame.png"; + public const string Icon = "Png/icon.png"; + public const string Kaboom = "Png/kaboom.png"; + public const string PDSrc = "Png/pd-source.png"; + public const string PDDest = "Png/pd-dest.png"; + public const string Gray1BitTrans = "Png/gray-1-trns.png"; + public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; + public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; + public const string L8BitTrans = "Png/gray-8-tRNS.png"; + 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"; + public const string TestPattern31x31 = "Png/testpattern31x31.png"; + public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; + public const string XmpColorPalette = "Png/xmp-colorpalette.png"; + + // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + public const string Filter0 = "Png/filter0.png"; + public const string SubFilter3BytesPerPixel = "Png/filter1.png"; + public const string SubFilter4BytesPerPixel = "Png/SubFilter4Bpp.png"; + public const string UpFilter = "Png/filter2.png"; + public const string AverageFilter3BytesPerPixel = "Png/filter3.png"; + public const string AverageFilter4BytesPerPixel = "Png/AverageFilter4Bpp.png"; + public const string PaethFilter3BytesPerPixel = "Png/filter4.png"; + public const string PaethFilter4BytesPerPixel = "Png/PaethFilter4Bpp.png"; + + // Paletted images also from http://www.schaik.com/pngsuite/pngsuite_fil_png.html + public const string PalettedTwoColor = "Png/basn3p01.png"; + public const string PalettedFourColor = "Png/basn3p02.png"; + public const string PalettedSixteenColor = "Png/basn3p04.png"; + public const string Paletted256Colors = "Png/basn3p08.png"; + + // Filter changing per scanline + public const string FilterVar = "Png/filterVar.png"; + + public const string VimImage1 = "Png/vim16x16_1.png"; + public const string VimImage2 = "Png/vim16x16_2.png"; + + public const string VersioningImage1 = "Png/versioning-1_1.png"; + public const string VersioningImage2 = "Png/versioning-1_2.png"; + + public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png"; + public const string Banner8Index = "Png/banner8-index.png"; + + public const string Ratio1x4 = "Png/ratio-1x4.png"; + public const string Ratio4x1 = "Png/ratio-4x1.png"; + + public const string Ducky = "Png/ducky.png"; + public const string Rainbow = "Png/rainbow.png"; + + public const string Bradley01 = "Png/Bradley01.png"; + public const string Bradley02 = "Png/Bradley02.png"; + + // Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014 + public const string Issue1014_1 = "Png/issues/Issue_1014_1.png"; + public const string Issue1014_2 = "Png/issues/Issue_1014_2.png"; + public const string Issue1014_3 = "Png/issues/Issue_1014_3.png"; + 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"; + + // Issue 1127: https://github.com/SixLabors/ImageSharp/issues/1127 + public const string Issue1127 = "Png/issues/Issue_1127.png"; + + // Issue 1177: https://github.com/SixLabors/ImageSharp/issues/1177 + public const string Issue1177_1 = "Png/issues/Issue_1177_1.png"; + public const string Issue1177_2 = "Png/issues/Issue_1177_2.png"; + + // Issue 935: https://github.com/SixLabors/ImageSharp/issues/935 + public const string Issue935 = "Png/issues/Issue_935.png"; + + // Issue 1765: https://github.com/SixLabors/ImageSharp/issues/1765 + public const string Issue1765_Net6DeflateStreamRead = "Png/issues/Issue_1765_Net6DeflateStreamRead.png"; + + // Discussion 1875: https://github.com/SixLabors/ImageSharp/discussions/1875 + public const string Issue1875 = "Png/raw-profile-type-exif.png"; + + // Issue 2217: https://github.com/SixLabors/ImageSharp/issues/2217 + public const string Issue2217 = "Png/issues/Issue_2217_AdaptiveThresholdProcessor.png"; + + // Issue 2209: https://github.com/SixLabors/ImageSharp/issues/2209 + public const string Issue2209IndexedWithTransparency = "Png/issues/Issue_2209.png"; + + public static class Bad { - public const string Transparency = "Png/transparency.png"; - public const string P1 = "Png/pl.png"; - public const string Pd = "Png/pd.png"; - public const string Blur = "Png/blur.png"; - public const string Indexed = "Png/indexed.png"; - public const string Splash = "Png/splash.png"; - public const string Cross = "Png/cross.png"; - public const string Powerpoint = "Png/pp.png"; - public const string SplashInterlaced = "Png/splash-interlaced.png"; - public const string Interlaced = "Png/interlaced.png"; - public const string Palette8Bpp = "Png/palette-8bpp.png"; - public const string Bpp1 = "Png/bpp1.png"; - public const string Gray4Bpp = "Png/gray_4bpp.png"; - public const string L16Bit = "Png/gray-16.png"; - public const string GrayA8Bit = "Png/gray-alpha-8.png"; - public const string GrayA8BitInterlaced = "Png/rollsroyce.png"; - public const string GrayAlpha1BitInterlaced = "Png/iftbbn0g01.png"; - public const string GrayAlpha2BitInterlaced = "Png/iftbbn0g02.png"; - public const string Gray4BitInterlaced = "Png/iftbbn0g04.png"; - public const string GrayAlpha16Bit = "Png/gray-alpha-16.png"; - public const string GrayTrns16BitInterlaced = "Png/gray-16-tRNS-interlaced.png"; - public const string Rgb24BppTrans = "Png/rgb-8-tRNS.png"; - public const string Rgb48Bpp = "Png/rgb-48bpp.png"; - public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; - public const string Rgb48BppTrans = "Png/rgb-16-tRNS.png"; - public const string Rgba64Bpp = "Png/rgb-16-alpha.png"; - public const string ColorsSaturationLightness = "Png/colors-saturation-lightness.png"; - public const string CalliphoraPartial = "Png/CalliphoraPartial.png"; - public const string CalliphoraPartialGrayscale = "Png/CalliphoraPartialGrayscale.png"; - public const string Bike = "Png/Bike.png"; - public const string BikeSmall = "Png/bike-small.png"; - public const string BikeGrayscale = "Png/BikeGrayscale.png"; - public const string SnakeGame = "Png/SnakeGame.png"; - public const string Icon = "Png/icon.png"; - public const string Kaboom = "Png/kaboom.png"; - public const string PDSrc = "Png/pd-source.png"; - public const string PDDest = "Png/pd-dest.png"; - public const string Gray1BitTrans = "Png/gray-1-trns.png"; - public const string Gray2BitTrans = "Png/gray-2-tRNS.png"; - public const string Gray4BitTrans = "Png/gray-4-tRNS.png"; - public const string L8BitTrans = "Png/gray-8-tRNS.png"; - 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"; - public const string TestPattern31x31 = "Png/testpattern31x31.png"; - public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; - public const string XmpColorPalette = "Png/xmp-colorpalette.png"; - - // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html - public const string Filter0 = "Png/filter0.png"; - public const string SubFilter3BytesPerPixel = "Png/filter1.png"; - public const string SubFilter4BytesPerPixel = "Png/SubFilter4Bpp.png"; - public const string UpFilter = "Png/filter2.png"; - public const string AverageFilter3BytesPerPixel = "Png/filter3.png"; - public const string AverageFilter4BytesPerPixel = "Png/AverageFilter4Bpp.png"; - public const string PaethFilter3BytesPerPixel = "Png/filter4.png"; - public const string PaethFilter4BytesPerPixel = "Png/PaethFilter4Bpp.png"; - - // Paletted images also from http://www.schaik.com/pngsuite/pngsuite_fil_png.html - public const string PalettedTwoColor = "Png/basn3p01.png"; - public const string PalettedFourColor = "Png/basn3p02.png"; - public const string PalettedSixteenColor = "Png/basn3p04.png"; - public const string Paletted256Colors = "Png/basn3p08.png"; - - // Filter changing per scanline - public const string FilterVar = "Png/filterVar.png"; - - public const string VimImage1 = "Png/vim16x16_1.png"; - public const string VimImage2 = "Png/vim16x16_2.png"; - - public const string VersioningImage1 = "Png/versioning-1_1.png"; - public const string VersioningImage2 = "Png/versioning-1_2.png"; - - public const string Banner7Adam7InterlaceMode = "Png/banner7-adam.png"; - public const string Banner8Index = "Png/banner8-index.png"; - - public const string Ratio1x4 = "Png/ratio-1x4.png"; - public const string Ratio4x1 = "Png/ratio-4x1.png"; - - public const string Ducky = "Png/ducky.png"; - public const string Rainbow = "Png/rainbow.png"; - - public const string Bradley01 = "Png/Bradley01.png"; - public const string Bradley02 = "Png/Bradley02.png"; - - // Issue 1014: https://github.com/SixLabors/ImageSharp/issues/1014 - public const string Issue1014_1 = "Png/issues/Issue_1014_1.png"; - public const string Issue1014_2 = "Png/issues/Issue_1014_2.png"; - public const string Issue1014_3 = "Png/issues/Issue_1014_3.png"; - 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"; - - // Issue 1127: https://github.com/SixLabors/ImageSharp/issues/1127 - public const string Issue1127 = "Png/issues/Issue_1127.png"; - - // Issue 1177: https://github.com/SixLabors/ImageSharp/issues/1177 - public const string Issue1177_1 = "Png/issues/Issue_1177_1.png"; - public const string Issue1177_2 = "Png/issues/Issue_1177_2.png"; - - // Issue 935: https://github.com/SixLabors/ImageSharp/issues/935 - public const string Issue935 = "Png/issues/Issue_935.png"; - - // Issue 1765: https://github.com/SixLabors/ImageSharp/issues/1765 - public const string Issue1765_Net6DeflateStreamRead = "Png/issues/Issue_1765_Net6DeflateStreamRead.png"; - - // Discussion 1875: https://github.com/SixLabors/ImageSharp/discussions/1875 - public const string Issue1875 = "Png/raw-profile-type-exif.png"; - - // Issue 2217: https://github.com/SixLabors/ImageSharp/issues/2217 - public const string Issue2217 = "Png/issues/Issue_2217_AdaptiveThresholdProcessor.png"; - - // Issue 2209: https://github.com/SixLabors/ImageSharp/issues/2209 - public const string Issue2209IndexedWithTransparency = "Png/issues/Issue_2209.png"; - - public static class Bad - { - public const string MissingDataChunk = "Png/xdtn0g01.png"; - public const string WrongCrcDataChunk = "Png/xcsn0g01.png"; - public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; - public const string MissingPaletteChunk1 = "Png/missing_plte.png"; - public const string MissingPaletteChunk2 = "Png/missing_plte_2.png"; - public const string InvalidGammaChunk = "Png/length_gama.png"; - - // Zlib errors. - public const string ZlibOverflow = "Png/zlib-overflow.png"; - public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; - public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png"; - - // Odd chunk lengths - public const string ChunkLength1 = "Png/chunklength1.png"; - public const string ChunkLength2 = "Png/chunklength2.png"; - - // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 - public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; - - // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 - public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; - - // Bad bit depth. - public const string BitDepthZero = "Png/xd0n2c08.png"; - public const string BitDepthThree = "Png/xd3n2c08.png"; - - // Invalid color type. - public const string ColorTypeOne = "Png/xc1n0g08.png"; - public const string ColorTypeNine = "Png/xc9n2c08.png"; - } + public const string MissingDataChunk = "Png/xdtn0g01.png"; + public const string WrongCrcDataChunk = "Png/xcsn0g01.png"; + public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; + public const string MissingPaletteChunk1 = "Png/missing_plte.png"; + public const string MissingPaletteChunk2 = "Png/missing_plte_2.png"; + public const string InvalidGammaChunk = "Png/length_gama.png"; + + // Zlib errors. + public const string ZlibOverflow = "Png/zlib-overflow.png"; + public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; + public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png"; + + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; + + // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 + public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; + + // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 + public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; + + // Bad bit depth. + public const string BitDepthZero = "Png/xd0n2c08.png"; + public const string BitDepthThree = "Png/xd3n2c08.png"; + + // Invalid color type. + public const string ColorTypeOne = "Png/xc1n0g08.png"; + public const string ColorTypeNine = "Png/xc9n2c08.png"; } + } - public static class Jpeg + public static class Jpeg + { + public static class Progressive { - public static class Progressive - { - public const string Fb = "Jpg/progressive/fb.jpg"; - public const string Progress = "Jpg/progressive/progress.jpg"; - public const string Festzug = "Jpg/progressive/Festzug.jpg"; - public const string Winter420_NonInterleaved = "Jpg/progressive/winter420_noninterleaved.jpg"; - - public static class Bad - { - public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; - public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; - } - - public static readonly string[] All = { Fb, Progress, Festzug }; - } + public const string Fb = "Jpg/progressive/fb.jpg"; + public const string Progress = "Jpg/progressive/progress.jpg"; + public const string Festzug = "Jpg/progressive/Festzug.jpg"; + public const string Winter420_NonInterleaved = "Jpg/progressive/winter420_noninterleaved.jpg"; - public static class Baseline - { - public static class Bad - { - public const string BadEOF = "Jpg/baseline/badeof.jpg"; - public const string BadRST = "Jpg/baseline/badrst.jpg"; - } - - public const string Cmyk = "Jpg/baseline/cmyk.jpg"; - public const string Exif = "Jpg/baseline/exif.jpg"; - public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; - public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; - public const string Calliphora_EncodedStrings = "Jpg/baseline/Calliphora_encoded_strings.jpg"; - public const string Ycck = "Jpg/baseline/ycck.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"; - public const string Lake = "Jpg/baseline/Lake.jpg"; - public const string Jpeg400 = "Jpg/baseline/jpeg400jfif.jpg"; - public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; - public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; - public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; - public const string JpegRgb = "Jpg/baseline/jpeg-rgb.jpg"; - public const string Jpeg410 = "Jpg/baseline/jpeg410.jpg"; - public const string Jpeg411 = "Jpg/baseline/jpeg411.jpg"; - public const string Jpeg422 = "Jpg/baseline/jpeg422.jpg"; - public const string Testorig420 = "Jpg/baseline/testorig.jpg"; - public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; - public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; - public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; - public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; - public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; - public const string Iptc = "Jpg/baseline/iptc.jpg"; - public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg"; - public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg"; - public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg"; - public const string Lossless = "Jpg/baseline/lossless.jpg"; - public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; - public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; - public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; - public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg"; - - // Jpeg's with arithmetic coding. - public const string ArithmeticCoding01 = "Jpg/baseline/Calliphora_arithmetic.jpg"; - public const string ArithmeticCoding02 = "Jpg/baseline/arithmetic_coding.jpg"; - public const string ArithmeticCodingProgressive01 = "Jpg/progressive/arithmetic_progressive.jpg"; - public const string ArithmeticCodingProgressive02 = "Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg"; - public const string ArithmeticCodingGray = "Jpg/baseline/Calliphora-arithmetic-grayscale.jpg"; - public const string ArithmeticCodingInterleaved = "Jpg/baseline/Calliphora-arithmetic-interleaved.jpg"; - public const string ArithmeticCodingWithRestart = "Jpg/baseline/Calliphora-arithmetic-restart.jpg"; - - public static readonly string[] All = - { - Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle420, GammaDalaiLamaGray, - Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, - Ratio1x1, Testorig12bit, YcckSubsample1222 - }; - } - - public static class Issues + public static class Bad { - public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; - public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; - public const string MissingFF00ProgressiveBedroom159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg"; - public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; - public const string BadZigZagProgressive385 = "Jpg/issues/Issue385-BadZigZag-Progressive.jpg"; - public const string MultiHuffmanBaseline394 = "Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg"; - public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; - public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg"; - public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg"; - public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg"; - public const string ExifDecodeOutOfRange694 = "Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg"; - public const string InvalidEOI695 = "Jpg/issues/Issue695-Invalid-EOI.jpg"; - public const string ExifResizeOutOfRange696 = "Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg"; - public const string InvalidAPP0721 = "Jpg/issues/Issue721-InvalidAPP0.jpg"; - public const string OrderedInterleavedProgressive723A = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg"; - public const string OrderedInterleavedProgressive723B = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg"; - public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg"; - public const string ExifGetString750Transform = "Jpg/issues/issue750-exif-tranform.jpg"; - public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg"; - public const string IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; - public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; - public const string IncorrectResize1006 = "Jpg/issues/issue1006-incorrect-resize.jpg"; - public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg"; - public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; - public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; - public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; - public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; - public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; - public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; - public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; - public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg"; - public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg"; - public const string Issue2133_DeduceColorSpace = "Jpg/issues/Issue2133.jpg"; - public const string Issue2136_ScanMarkerExtraneousBytes = "Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg"; - - public static class Fuzz - { - public const string NullReferenceException797 = "Jpg/issues/fuzz/Issue797-NullReferenceException.jpg"; - public const string AccessViolationException798 = "Jpg/issues/fuzz/Issue798-AccessViolationException.jpg"; - public const string DivideByZeroException821 = "Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg"; - public const string DivideByZeroException822 = "Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg"; - public const string NullReferenceException823 = "Jpg/issues/fuzz/Issue823-NullReferenceException.jpg"; - public const string IndexOutOfRangeException824A = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg"; - public const string IndexOutOfRangeException824B = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg"; - public const string IndexOutOfRangeException824C = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg"; - public const string IndexOutOfRangeException824D = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg"; - public const string IndexOutOfRangeException824E = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg"; - public const string IndexOutOfRangeException824F = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg"; - public const string IndexOutOfRangeException824G = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg"; - public const string IndexOutOfRangeException824H = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg"; - public const string ArgumentOutOfRangeException825A = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg"; - public const string ArgumentOutOfRangeException825B = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg"; - public const string ArgumentOutOfRangeException825C = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg"; - public const string ArgumentOutOfRangeException825D = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg"; - public const string ArgumentException826A = "Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg"; - public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; - public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg"; - public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; - public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; - public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; - public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; - public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; - public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg"; - } + public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; + public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; } - public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); - - public static class BenchmarkSuite - { - public const string Jpeg400_SmallMonochrome = Baseline.Jpeg400; - public const string Jpeg420Exif_MidSizeYCbCr = Baseline.Jpeg420Exif; - public const string Lake_Small444YCbCr = Baseline.Lake; - - // A few large images from the "issues" set are actually very useful for benchmarking: - public const string MissingFF00ProgressiveBedroom159_MidSize420YCbCr = Issues.MissingFF00ProgressiveBedroom159; - public const string BadRstProgressive518_Large444YCbCr = Issues.BadRstProgressive518; - public const string ExifGetString750Transform_Huge420YCbCr = Issues.ExifGetString750Transform; - } + public static readonly string[] All = { Fb, Progress, Festzug }; } - public static class Bmp + public static class Baseline { - // Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor. - // As such, the expected pixel output will be the reverse of the unaltered equivalent images. - public const string Car = "Bmp/Car.bmp"; - public const string F = "Bmp/F.bmp"; - public const string NegHeight = "Bmp/neg_height.bmp"; - public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp"; - public const string V5Header = "Bmp/BITMAPV5HEADER.bmp"; - public const string RLE24 = "Bmp/rgb24rle24.bmp"; - public const string RLE24Cut = "Bmp/rle24rlecut.bmp"; - public const string RLE24Delta = "Bmp/rle24rlecut.bmp"; - public const string RLE8 = "Bmp/RunLengthEncoded.bmp"; - public const string RLE8Cut = "Bmp/pal8rlecut.bmp"; - public const string RLE8Delta = "Bmp/pal8rletrns.bmp"; - public const string Rle8Delta320240 = "Bmp/rle8-delta-320x240.bmp"; - public const string Rle8Blank160120 = "Bmp/rle8-blank-160x120.bmp"; - public const string RLE8Inverted = "Bmp/RunLengthEncoded-inverted.bmp"; - public const string RLE4 = "Bmp/pal4rle.bmp"; - public const string RLE4Cut = "Bmp/pal4rlecut.bmp"; - public const string RLE4Delta = "Bmp/pal4rletrns.bmp"; - public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; - public const string Bit1 = "Bmp/pal1.bmp"; - public const string Bit2 = "Bmp/pal2.bmp"; - public const string Bit2Color = "Bmp/pal2color.bmp"; - public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; - public const string Bit4 = "Bmp/pal4.bmp"; - public const string Bit8 = "Bmp/test8.bmp"; - public const string Bit8Gs = "Bmp/pal8gs.bmp"; - public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; - public const string Bit16 = "Bmp/test16.bmp"; - public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; - public const string Bit32Rgb = "Bmp/rgb32.bmp"; - public const string Bit32Rgba = "Bmp/rgba32.bmp"; - public const string Rgb16 = "Bmp/rgb16.bmp"; - - // Note: This format can be called OS/2 BMPv1, or Windows BMPv2 - public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp"; - - public const string WinBmpv3 = "Bmp/rgb24.bmp"; - public const string WinBmpv4 = "Bmp/pal8v4.bmp"; - public const string WinBmpv5 = "Bmp/pal8v5.bmp"; - public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; - public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; - public const string Os2v2 = "Bmp/pal8os2v2.bmp"; - public const string Os2BitmapArray = "Bmp/ba-bm.bmp"; - public const string Os2BitmapArray9s = "Bmp/9S.BMP"; - public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.BMP"; - public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.BMP"; - public const string Os2BitmapArraySkater = "Bmp/SKATER.BMP"; - public const string Os2BitmapArraySpade = "Bmp/SPADE.BMP"; - public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.BMP"; - public const string Os2BitmapArrayWarpd = "Bmp/WARPD.BMP"; - public const string Os2BitmapArrayPines = "Bmp/PINES.BMP"; - public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; - public const string Pal8Offset = "Bmp/pal8offs.bmp"; - public const string OversizedPalette = "Bmp/pal8oversizepal.bmp"; - public const string Rgb24LargePalette = "Bmp/rgb24largepal.bmp"; - public const string InvalidPaletteSize = "Bmp/invalidPaletteSize.bmp"; - public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp"; - public const string Rgb24png = "Bmp/rgb24png.bmp"; - public const string Rgba32v4 = "Bmp/rgba32v4.bmp"; - public const string IccProfile = "Bmp/BMP_v5_with_ICC_2.bmp"; - - // Bitmap images with compression type BITFIELDS. - public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; - public const string Rgb32bf = "Bmp/rgb32bf.bmp"; - public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp"; - public const string Rgb16565 = "Bmp/rgb16-565.bmp"; - public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp"; - public const string Issue735 = "Bmp/issue735.bmp"; - public const string Rgba32bf56AdobeV3 = "Bmp/rgba32h56.bmp"; - public const string Rgb32h52AdobeV3 = "Bmp/rgb32h52.bmp"; - public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; - public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; - - public static readonly string[] BitFields = - { - Rgb32bfdef, - Rgb32bf, - Rgb16565, - Rgb16bfdef, - Rgb16565pal, - Issue735, - }; - - public static readonly string[] Miscellaneous = + public static class Bad { - Car, - F, - NegHeight - }; + public const string BadEOF = "Jpg/baseline/badeof.jpg"; + public const string BadRST = "Jpg/baseline/badrst.jpg"; + } - public static readonly string[] Benchmark = + public const string Cmyk = "Jpg/baseline/cmyk.jpg"; + public const string Exif = "Jpg/baseline/exif.jpg"; + public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; + public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; + public const string Calliphora_EncodedStrings = "Jpg/baseline/Calliphora_encoded_strings.jpg"; + public const string Ycck = "Jpg/baseline/ycck.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"; + public const string Lake = "Jpg/baseline/Lake.jpg"; + public const string Jpeg400 = "Jpg/baseline/jpeg400jfif.jpg"; + public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; + public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; + public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; + public const string JpegRgb = "Jpg/baseline/jpeg-rgb.jpg"; + public const string Jpeg410 = "Jpg/baseline/jpeg410.jpg"; + public const string Jpeg411 = "Jpg/baseline/jpeg411.jpg"; + public const string Jpeg422 = "Jpg/baseline/jpeg422.jpg"; + public const string Testorig420 = "Jpg/baseline/testorig.jpg"; + public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; + public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; + public const string LowContrast = "Jpg/baseline/AsianCarvingLowContrast.jpg"; + public const string Testorig12bit = "Jpg/baseline/testorig12.jpg"; + public const string YcckSubsample1222 = "Jpg/baseline/ycck-subsample-1222.jpg"; + public const string Iptc = "Jpg/baseline/iptc.jpg"; + public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg"; + public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg"; + public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg"; + public const string Lossless = "Jpg/baseline/lossless.jpg"; + public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; + public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; + public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; + public const string GrayscaleSampling2x2 = "Jpg/baseline/grayscale_sampling22.jpg"; + + // Jpeg's with arithmetic coding. + public const string ArithmeticCoding01 = "Jpg/baseline/Calliphora_arithmetic.jpg"; + public const string ArithmeticCoding02 = "Jpg/baseline/arithmetic_coding.jpg"; + public const string ArithmeticCodingProgressive01 = "Jpg/progressive/arithmetic_progressive.jpg"; + public const string ArithmeticCodingProgressive02 = "Jpg/progressive/Calliphora-arithmetic-progressive-interleaved.jpg"; + public const string ArithmeticCodingGray = "Jpg/baseline/Calliphora-arithmetic-grayscale.jpg"; + public const string ArithmeticCodingInterleaved = "Jpg/baseline/Calliphora-arithmetic-interleaved.jpg"; + public const string ArithmeticCodingWithRestart = "Jpg/baseline/Calliphora-arithmetic-restart.jpg"; + + public static readonly string[] All = { - Car, - F, - NegHeight, - CoreHeader, - V5Header, - RLE4, - RLE8, - RLE8Inverted, - Bit1, - Bit1Pal1, - Bit4, - Bit8, - Bit8Inverted, - Bit16, - Bit16Inverted, - Bit32Rgb + Cmyk, Ycck, Exif, Floorplan, + Calliphora, Turtle420, GammaDalaiLamaGray, + Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, + Ratio1x1, Testorig12bit, YcckSubsample1222 }; } - public static class Gif + public static class Issues { - 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"; - public const string Ratio4x1 = "Gif/base_4x1.gif"; - public const string Ratio1x4 = "Gif/base_1x4.gif"; - public const string LargeComment = "Gif/large_comment.gif"; - public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif"; - - // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite - public const string ZeroSize = "Gif/image-zero-size.gif"; - public const string ZeroHeight = "Gif/image-zero-height.gif"; - public const string ZeroWidth = "Gif/image-zero-width.gif"; - public const string MaxWidth = "Gif/max-width.gif"; - public const string MaxHeight = "Gif/max-height.gif"; - - public static class Issues + public const string CriticalEOF214 = "Jpg/issues/Issue214-CriticalEOF.jpg"; + public const string MissingFF00ProgressiveGirl159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Girl.jpg"; + public const string MissingFF00ProgressiveBedroom159 = "Jpg/issues/Issue159-MissingFF00-Progressive-Bedroom.jpg"; + public const string BadCoeffsProgressive178 = "Jpg/issues/Issue178-BadCoeffsProgressive-Lemon.jpg"; + public const string BadZigZagProgressive385 = "Jpg/issues/Issue385-BadZigZag-Progressive.jpg"; + public const string MultiHuffmanBaseline394 = "Jpg/issues/Issue394-MultiHuffmanBaseline-Speakers.jpg"; + public const string NoEoiProgressive517 = "Jpg/issues/Issue517-No-EOI-Progressive.jpg"; + public const string BadRstProgressive518 = "Jpg/issues/Issue518-Bad-RST-Progressive.jpg"; + public const string InvalidCast520 = "Jpg/issues/Issue520-InvalidCast.jpg"; + public const string DhtHasWrongLength624 = "Jpg/issues/Issue624-DhtHasWrongLength-Progressive-N.jpg"; + public const string ExifDecodeOutOfRange694 = "Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg"; + public const string InvalidEOI695 = "Jpg/issues/Issue695-Invalid-EOI.jpg"; + public const string ExifResizeOutOfRange696 = "Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg"; + public const string InvalidAPP0721 = "Jpg/issues/Issue721-InvalidAPP0.jpg"; + public const string OrderedInterleavedProgressive723A = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg"; + public const string OrderedInterleavedProgressive723B = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg"; + public const string OrderedInterleavedProgressive723C = "Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg"; + public const string ExifGetString750Transform = "Jpg/issues/issue750-exif-tranform.jpg"; + public const string ExifGetString750Load = "Jpg/issues/issue750-exif-load.jpg"; + public const string IncorrectQuality845 = "Jpg/issues/Issue845-Incorrect-Quality99.jpg"; + public const string IncorrectColorspace855 = "Jpg/issues/issue855-incorrect-colorspace.jpg"; + public const string IncorrectResize1006 = "Jpg/issues/issue1006-incorrect-resize.jpg"; + public const string ExifResize1049 = "Jpg/issues/issue1049-exif-resize.jpg"; + public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; + public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; + public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; + public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; + public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; + public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; + public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; + public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg"; + public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg"; + public const string Issue2133_DeduceColorSpace = "Jpg/issues/Issue2133.jpg"; + public const string Issue2136_ScanMarkerExtraneousBytes = "Jpg/issues/Issue2136-scan-segment-extraneous-bytes.jpg"; + + public static class Fuzz { - public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; - public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; - public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; - public const string DeferredClearCode = "Gif/issues/bugzilla-55918.gif"; - public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; - public const string Issue1530 = "Gif/issues/issue1530.gif"; - public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif"; - public const string Issue1962NoColorTable = "Gif/issues/issue1962_tiniest_gif_1st.gif"; - public const string Issue2012EmptyXmp = "Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif"; - public const string Issue2012BadMinCode = "Gif/issues/issue2012_drona1.gif"; + public const string NullReferenceException797 = "Jpg/issues/fuzz/Issue797-NullReferenceException.jpg"; + public const string AccessViolationException798 = "Jpg/issues/fuzz/Issue798-AccessViolationException.jpg"; + public const string DivideByZeroException821 = "Jpg/issues/fuzz/Issue821-DivideByZeroException.jpg"; + public const string DivideByZeroException822 = "Jpg/issues/fuzz/Issue822-DivideByZeroException.jpg"; + public const string NullReferenceException823 = "Jpg/issues/fuzz/Issue823-NullReferenceException.jpg"; + public const string IndexOutOfRangeException824A = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-A.jpg"; + public const string IndexOutOfRangeException824B = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-B.jpg"; + public const string IndexOutOfRangeException824C = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-C.jpg"; + public const string IndexOutOfRangeException824D = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-D.jpg"; + public const string IndexOutOfRangeException824E = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-E.jpg"; + public const string IndexOutOfRangeException824F = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-F.jpg"; + public const string IndexOutOfRangeException824G = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-G.jpg"; + public const string IndexOutOfRangeException824H = "Jpg/issues/fuzz/Issue824-IndexOutOfRangeException-H.jpg"; + public const string ArgumentOutOfRangeException825A = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-A.jpg"; + public const string ArgumentOutOfRangeException825B = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-B.jpg"; + public const string ArgumentOutOfRangeException825C = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-C.jpg"; + public const string ArgumentOutOfRangeException825D = "Jpg/issues/fuzz/Issue825-ArgumentOutOfRangeException-D.jpg"; + public const string ArgumentException826A = "Jpg/issues/fuzz/Issue826-ArgumentException-A.jpg"; + public const string ArgumentException826B = "Jpg/issues/fuzz/Issue826-ArgumentException-B.jpg"; + public const string ArgumentException826C = "Jpg/issues/fuzz/Issue826-ArgumentException-C.jpg"; + public const string AccessViolationException827 = "Jpg/issues/fuzz/Issue827-AccessViolationException.jpg"; + public const string ExecutionEngineException839 = "Jpg/issues/fuzz/Issue839-ExecutionEngineException.jpg"; + public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; + public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; + public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; + public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg"; } - - public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } - public static class Tga + public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); + + public static class BenchmarkSuite { - public const string Gray8BitTopLeft = "Tga/grayscale_UL.tga"; - public const string Gray8BitTopRight = "Tga/grayscale_UR.tga"; - public const string Gray8BitBottomLeft = "Tga/targa_8bit.tga"; - public const string Gray8BitBottomRight = "Tga/grayscale_LR.tga"; - - public const string Gray8BitRleTopLeft = "Tga/grayscale_rle_UL.tga"; - public const string Gray8BitRleTopRight = "Tga/grayscale_rle_UR.tga"; - public const string Gray8BitRleBottomLeft = "Tga/targa_8bit_rle.tga"; - public const string Gray8BitRleBottomRight = "Tga/grayscale_rle_LR.tga"; - - public const string Bit15 = "Tga/rgb15.tga"; - public const string Bit15Rle = "Tga/rgb15rle.tga"; - public const string Bit16BottomLeft = "Tga/targa_16bit.tga"; - public const string Bit16PalRle = "Tga/ccm8.tga"; - public const string Bit16RleBottomLeft = "Tga/targa_16bit_rle.tga"; - public const string Bit16PalBottomLeft = "Tga/targa_16bit_pal.tga"; - - public const string Gray16BitTopLeft = "Tga/grayscale_a_UL.tga"; - public const string Gray16BitBottomLeft = "Tga/grayscale_a_LL.tga"; - public const string Gray16BitBottomRight = "Tga/grayscale_a_LR.tga"; - public const string Gray16BitTopRight = "Tga/grayscale_a_UR.tga"; - - public const string Gray16BitRleTopLeft = "Tga/grayscale_a_rle_UL.tga"; - public const string Gray16BitRleBottomLeft = "Tga/grayscale_a_rle_LL.tga"; - public const string Gray16BitRleBottomRight = "Tga/grayscale_a_rle_LR.tga"; - public const string Gray16BitRleTopRight = "Tga/grayscale_a_rle_UR.tga"; - - public const string Bit24TopLeft = "Tga/rgb24_top_left.tga"; - public const string Bit24BottomLeft = "Tga/targa_24bit.tga"; - public const string Bit24BottomRight = "Tga/rgb_LR.tga"; - public const string Bit24TopRight = "Tga/rgb_UR.tga"; - - public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga"; - public const string Bit24RleBottomLeft = "Tga/targa_24bit_rle.tga"; - public const string Bit24RleTopRight = "Tga/rgb_rle_UR.tga"; - public const string Bit24RleBottomRight = "Tga/rgb_rle_LR.tga"; - - public const string Bit24PalTopLeft = "Tga/targa_24bit_pal_origin_topleft.tga"; - public const string Bit24PalTopRight = "Tga/indexed_UR.tga"; - public const string Bit24PalBottomLeft = "Tga/targa_24bit_pal.tga"; - public const string Bit24PalBottomRight = "Tga/indexed_LR.tga"; - - public const string Bit24PalRleTopLeft = "Tga/indexed_rle_UL.tga"; - public const string Bit24PalRleBottomLeft = "Tga/indexed_rle_LL.tga"; - public const string Bit24PalRleTopRight = "Tga/indexed_rle_UR.tga"; - public const string Bit24PalRleBottomRight = "Tga/indexed_rle_LR.tga"; - - public const string Bit32TopLeft = "Tga/rgb_a_UL.tga"; - public const string Bit32BottomLeft = "Tga/targa_32bit.tga"; - public const string Bit32TopRight = "Tga/rgb_a_UR.tga"; - public const string Bit32BottomRight = "Tga/rgb_a_LR.tga"; - - public const string Bit32PalTopLeft = "Tga/indexed_a_UL.tga"; - public const string Bit32PalBottomLeft = "Tga/indexed_a_LL.tga"; - public const string Bit32PalBottomRight = "Tga/indexed_a_LR.tga"; - public const string Bit32PalTopRight = "Tga/indexed_a_UR.tga"; - - public const string Bit32RleTopLeft = "Tga/rgb_a_rle_UL.tga"; - public const string Bit32RleTopRight = "Tga/rgb_a_rle_UR.tga"; - public const string Bit32RleBottomRight = "Tga/rgb_a_rle_LR.tga"; - public const string Bit32RleBottomLeft = "Tga/targa_32bit_rle.tga"; - - public const string Bit32PalRleTopLeft = "Tga/indexed_a_rle_UL.tga"; - public const string Bit32PalRleBottomLeft = "Tga/indexed_a_rle_LL.tga"; - public const string Bit32PalRleTopRight = "Tga/indexed_a_rle_UR.tga"; - public const string Bit32PalRleBottomRight = "Tga/indexed_a_rle_LR.tga"; - - public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga"; - public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; - public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; - public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; - - public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga"; - public const string WhiteStripesPattern = "Tga/whitestripes.png"; + public const string Jpeg400_SmallMonochrome = Baseline.Jpeg400; + public const string Jpeg420Exif_MidSizeYCbCr = Baseline.Jpeg420Exif; + public const string Lake_Small444YCbCr = Baseline.Lake; + + // A few large images from the "issues" set are actually very useful for benchmarking: + public const string MissingFF00ProgressiveBedroom159_MidSize420YCbCr = Issues.MissingFF00ProgressiveBedroom159; + public const string BadRstProgressive518_Large444YCbCr = Issues.BadRstProgressive518; + public const string ExifGetString750Transform_Huge420YCbCr = Issues.ExifGetString750Transform; } + } + + public static class Bmp + { + // Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor. + // As such, the expected pixel output will be the reverse of the unaltered equivalent images. + public const string Car = "Bmp/Car.bmp"; + public const string F = "Bmp/F.bmp"; + public const string NegHeight = "Bmp/neg_height.bmp"; + public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp"; + public const string V5Header = "Bmp/BITMAPV5HEADER.bmp"; + public const string RLE24 = "Bmp/rgb24rle24.bmp"; + public const string RLE24Cut = "Bmp/rle24rlecut.bmp"; + public const string RLE24Delta = "Bmp/rle24rlecut.bmp"; + public const string RLE8 = "Bmp/RunLengthEncoded.bmp"; + public const string RLE8Cut = "Bmp/pal8rlecut.bmp"; + public const string RLE8Delta = "Bmp/pal8rletrns.bmp"; + public const string Rle8Delta320240 = "Bmp/rle8-delta-320x240.bmp"; + public const string Rle8Blank160120 = "Bmp/rle8-blank-160x120.bmp"; + public const string RLE8Inverted = "Bmp/RunLengthEncoded-inverted.bmp"; + public const string RLE4 = "Bmp/pal4rle.bmp"; + public const string RLE4Cut = "Bmp/pal4rlecut.bmp"; + public const string RLE4Delta = "Bmp/pal4rletrns.bmp"; + public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp"; + public const string Bit1 = "Bmp/pal1.bmp"; + public const string Bit2 = "Bmp/pal2.bmp"; + public const string Bit2Color = "Bmp/pal2color.bmp"; + public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; + public const string Bit4 = "Bmp/pal4.bmp"; + public const string Bit8 = "Bmp/test8.bmp"; + public const string Bit8Gs = "Bmp/pal8gs.bmp"; + public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; + public const string Bit16 = "Bmp/test16.bmp"; + public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; + public const string Bit32Rgb = "Bmp/rgb32.bmp"; + public const string Bit32Rgba = "Bmp/rgba32.bmp"; + public const string Rgb16 = "Bmp/rgb16.bmp"; + + // Note: This format can be called OS/2 BMPv1, or Windows BMPv2 + public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp"; + + public const string WinBmpv3 = "Bmp/rgb24.bmp"; + public const string WinBmpv4 = "Bmp/pal8v4.bmp"; + public const string WinBmpv5 = "Bmp/pal8v5.bmp"; + public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; + public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; + public const string Os2v2 = "Bmp/pal8os2v2.bmp"; + public const string Os2BitmapArray = "Bmp/ba-bm.bmp"; + public const string Os2BitmapArray9s = "Bmp/9S.BMP"; + public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.BMP"; + public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.BMP"; + public const string Os2BitmapArraySkater = "Bmp/SKATER.BMP"; + public const string Os2BitmapArraySpade = "Bmp/SPADE.BMP"; + public const string Os2BitmapArraySunflower = "Bmp/SUNFLOW.BMP"; + public const string Os2BitmapArrayWarpd = "Bmp/WARPD.BMP"; + public const string Os2BitmapArrayPines = "Bmp/PINES.BMP"; + public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; + public const string Pal8Offset = "Bmp/pal8offs.bmp"; + public const string OversizedPalette = "Bmp/pal8oversizepal.bmp"; + public const string Rgb24LargePalette = "Bmp/rgb24largepal.bmp"; + public const string InvalidPaletteSize = "Bmp/invalidPaletteSize.bmp"; + public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp"; + public const string Rgb24png = "Bmp/rgb24png.bmp"; + public const string Rgba32v4 = "Bmp/rgba32v4.bmp"; + public const string IccProfile = "Bmp/BMP_v5_with_ICC_2.bmp"; + + // Bitmap images with compression type BITFIELDS. + public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp"; + public const string Rgb32bf = "Bmp/rgb32bf.bmp"; + public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp"; + public const string Rgb16565 = "Bmp/rgb16-565.bmp"; + public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp"; + public const string Issue735 = "Bmp/issue735.bmp"; + public const string Rgba32bf56AdobeV3 = "Bmp/rgba32h56.bmp"; + public const string Rgb32h52AdobeV3 = "Bmp/rgb32h52.bmp"; + public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; + public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; + + public static readonly string[] BitFields = + { + Rgb32bfdef, + Rgb32bf, + Rgb16565, + Rgb16bfdef, + Rgb16565pal, + Issue735, + }; + + public static readonly string[] Miscellaneous = + { + Car, + F, + NegHeight + }; - public static class Webp + public static readonly string[] Benchmark = { - // Reference image as png - public const string Peak = "Webp/peak.png"; + Car, + F, + NegHeight, + CoreHeader, + V5Header, + RLE4, + RLE8, + RLE8Inverted, + Bit1, + Bit1Pal1, + Bit4, + Bit8, + Bit8Inverted, + Bit16, + Bit16Inverted, + Bit32Rgb + }; + } - // Test pattern images for testing the encoder. - public const string TestPatternOpaque = "Webp/testpattern_opaque.png"; - public const string TestPatternOpaqueSmall = "Webp/testpattern_opaque_small.png"; - public const string RgbTestPattern100x100 = "Webp/rgb_pattern_100x100.png"; - public const string RgbTestPattern80x80 = "Webp/rgb_pattern_80x80.png"; - public const string RgbTestPattern63x63 = "Webp/rgb_pattern_63x63.png"; + public static class Gif + { + 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"; + public const string Ratio4x1 = "Gif/base_4x1.gif"; + public const string Ratio1x4 = "Gif/base_1x4.gif"; + public const string LargeComment = "Gif/large_comment.gif"; + public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif"; + + // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite + public const string ZeroSize = "Gif/image-zero-size.gif"; + public const string ZeroHeight = "Gif/image-zero-height.gif"; + public const string ZeroWidth = "Gif/image-zero-width.gif"; + public const string MaxWidth = "Gif/max-width.gif"; + public const string MaxHeight = "Gif/max-height.gif"; + + public static class Issues + { + public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; + public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; + public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; + public const string DeferredClearCode = "Gif/issues/bugzilla-55918.gif"; + public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; + public const string Issue1530 = "Gif/issues/issue1530.gif"; + public const string InvalidColorIndex = "Gif/issues/issue1668_invalidcolorindex.gif"; + public const string Issue1962NoColorTable = "Gif/issues/issue1962_tiniest_gif_1st.gif"; + public const string Issue2012EmptyXmp = "Gif/issues/issue2012_Stronghold-Crusader-Extreme-Cover.gif"; + public const string Issue2012BadMinCode = "Gif/issues/issue2012_drona1.gif"; + } - // Test image for encoding image with a palette. - public const string Flag = "Webp/flag_of_germany.png"; + public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; + } - // Test images for converting rgb data to yuv. - public const string Yuv = "Webp/yuv_test.png"; + public static class Tga + { + public const string Gray8BitTopLeft = "Tga/grayscale_UL.tga"; + public const string Gray8BitTopRight = "Tga/grayscale_UR.tga"; + public const string Gray8BitBottomLeft = "Tga/targa_8bit.tga"; + public const string Gray8BitBottomRight = "Tga/grayscale_LR.tga"; + + public const string Gray8BitRleTopLeft = "Tga/grayscale_rle_UL.tga"; + public const string Gray8BitRleTopRight = "Tga/grayscale_rle_UR.tga"; + public const string Gray8BitRleBottomLeft = "Tga/targa_8bit_rle.tga"; + public const string Gray8BitRleBottomRight = "Tga/grayscale_rle_LR.tga"; + + public const string Bit15 = "Tga/rgb15.tga"; + public const string Bit15Rle = "Tga/rgb15rle.tga"; + public const string Bit16BottomLeft = "Tga/targa_16bit.tga"; + public const string Bit16PalRle = "Tga/ccm8.tga"; + public const string Bit16RleBottomLeft = "Tga/targa_16bit_rle.tga"; + public const string Bit16PalBottomLeft = "Tga/targa_16bit_pal.tga"; + + public const string Gray16BitTopLeft = "Tga/grayscale_a_UL.tga"; + public const string Gray16BitBottomLeft = "Tga/grayscale_a_LL.tga"; + public const string Gray16BitBottomRight = "Tga/grayscale_a_LR.tga"; + public const string Gray16BitTopRight = "Tga/grayscale_a_UR.tga"; + + public const string Gray16BitRleTopLeft = "Tga/grayscale_a_rle_UL.tga"; + public const string Gray16BitRleBottomLeft = "Tga/grayscale_a_rle_LL.tga"; + public const string Gray16BitRleBottomRight = "Tga/grayscale_a_rle_LR.tga"; + public const string Gray16BitRleTopRight = "Tga/grayscale_a_rle_UR.tga"; + + public const string Bit24TopLeft = "Tga/rgb24_top_left.tga"; + public const string Bit24BottomLeft = "Tga/targa_24bit.tga"; + public const string Bit24BottomRight = "Tga/rgb_LR.tga"; + public const string Bit24TopRight = "Tga/rgb_UR.tga"; + + public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga"; + public const string Bit24RleBottomLeft = "Tga/targa_24bit_rle.tga"; + public const string Bit24RleTopRight = "Tga/rgb_rle_UR.tga"; + public const string Bit24RleBottomRight = "Tga/rgb_rle_LR.tga"; + + public const string Bit24PalTopLeft = "Tga/targa_24bit_pal_origin_topleft.tga"; + public const string Bit24PalTopRight = "Tga/indexed_UR.tga"; + public const string Bit24PalBottomLeft = "Tga/targa_24bit_pal.tga"; + public const string Bit24PalBottomRight = "Tga/indexed_LR.tga"; + + public const string Bit24PalRleTopLeft = "Tga/indexed_rle_UL.tga"; + public const string Bit24PalRleBottomLeft = "Tga/indexed_rle_LL.tga"; + public const string Bit24PalRleTopRight = "Tga/indexed_rle_UR.tga"; + public const string Bit24PalRleBottomRight = "Tga/indexed_rle_LR.tga"; + + public const string Bit32TopLeft = "Tga/rgb_a_UL.tga"; + public const string Bit32BottomLeft = "Tga/targa_32bit.tga"; + public const string Bit32TopRight = "Tga/rgb_a_UR.tga"; + public const string Bit32BottomRight = "Tga/rgb_a_LR.tga"; + + public const string Bit32PalTopLeft = "Tga/indexed_a_UL.tga"; + public const string Bit32PalBottomLeft = "Tga/indexed_a_LL.tga"; + public const string Bit32PalBottomRight = "Tga/indexed_a_LR.tga"; + public const string Bit32PalTopRight = "Tga/indexed_a_UR.tga"; + + public const string Bit32RleTopLeft = "Tga/rgb_a_rle_UL.tga"; + public const string Bit32RleTopRight = "Tga/rgb_a_rle_UR.tga"; + public const string Bit32RleBottomRight = "Tga/rgb_a_rle_LR.tga"; + public const string Bit32RleBottomLeft = "Tga/targa_32bit_rle.tga"; + + public const string Bit32PalRleTopLeft = "Tga/indexed_a_rle_UL.tga"; + public const string Bit32PalRleBottomLeft = "Tga/indexed_a_rle_LL.tga"; + public const string Bit32PalRleTopRight = "Tga/indexed_a_rle_UR.tga"; + public const string Bit32PalRleBottomRight = "Tga/indexed_a_rle_LR.tga"; + + public const string NoAlphaBits16Bit = "Tga/16bit_noalphabits.tga"; + public const string NoAlphaBits16BitRle = "Tga/16bit_rle_noalphabits.tga"; + public const string NoAlphaBits32Bit = "Tga/32bit_no_alphabits.tga"; + public const string NoAlphaBits32BitRle = "Tga/32bit_rle_no_alphabits.tga"; + + public const string Github_RLE_legacy = "Tga/Github_RLE_legacy.tga"; + public const string WhiteStripesPattern = "Tga/whitestripes.png"; + } - public static class Lossless - { - public const string Animated = "Webp/leo_animated_lossless.webp"; - public const string Earth = "Webp/earth_lossless.webp"; - public const string Alpha = "Webp/lossless_alpha_small.webp"; - public const string WithExif = "Webp/exif_lossless.webp"; - public const string WithIccp = "Webp/lossless_with_iccp.webp"; - public const string NoTransform1 = "Webp/lossless_vec_1_0.webp"; - public const string NoTransform2 = "Webp/lossless_vec_2_0.webp"; - public const string GreenTransform1 = "Webp/lossless1.webp"; - public const string GreenTransform2 = "Webp/lossless2.webp"; - public const string GreenTransform3 = "Webp/lossless3.webp"; - public const string GreenTransform4 = "Webp/lossless_vec_1_4.webp"; - public const string GreenTransform5 = "Webp/lossless_vec_2_4.webp"; - public const string CrossColorTransform1 = "Webp/lossless_vec_1_8.webp"; - public const string CrossColorTransform2 = "Webp/lossless_vec_2_8.webp"; - public const string PredictorTransform1 = "Webp/lossless_vec_1_2.webp"; - public const string PredictorTransform2 = "Webp/lossless_vec_2_2.webp"; - public const string ColorIndexTransform1 = "Webp/lossless4.webp"; - public const string ColorIndexTransform2 = "Webp/lossless_vec_1_1.webp"; - public const string ColorIndexTransform3 = "Webp/lossless_vec_1_5.webp"; - public const string ColorIndexTransform4 = "Webp/lossless_vec_2_1.webp"; - public const string ColorIndexTransform5 = "Webp/lossless_vec_2_5.webp"; - public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor - public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green - public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color - public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor - public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor - public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor - public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color - public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color - public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color - public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color - public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor - public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor - public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor - - // substract_green, predictor, cross_color - public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; - - // substract_green, predictor, cross_color - public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; - - // substract_green, predictor, cross_color - public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; - - // color_indexing, predictor, cross_color - public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; - - // substract_green, predictor, cross_color - public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; - - // Invalid / corrupted images - // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." - public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. - - public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. - - public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor - - public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. - } + public static class Webp + { + // Reference image as png + public const string Peak = "Webp/peak.png"; - public static class Lossy - { - public const string Earth = "Webp/earth_lossy.webp"; - public const string WithExif = "Webp/exif_lossy.webp"; - public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp"; - public const string WithIccp = "Webp/lossy_with_iccp.webp"; - public const string WithXmp = "Webp/xmp_lossy.webp"; - public const string BikeSmall = "Webp/bike_lossy_small.webp"; - public const string Animated = "Webp/leo_animated_lossy.webp"; - - // Lossy images without macroblock filtering. - public const string BikeWithExif = "Webp/bike_lossy_with_exif.webp"; - public const string NoFilter01 = "Webp/vp80-01-intra-1400.webp"; - public const string NoFilter02 = "Webp/vp80-00-comprehensive-010.webp"; - public const string NoFilter03 = "Webp/vp80-00-comprehensive-005.webp"; - public const string NoFilter04 = "Webp/vp80-01-intra-1417.webp"; - public const string NoFilter05 = "Webp/vp80-02-inter-1402.webp"; - public const string NoFilter06 = "Webp/test.webp"; - - // Lossy images with a simple filter. - public const string SimpleFilter01 = "Webp/segment01.webp"; - public const string SimpleFilter02 = "Webp/segment02.webp"; - public const string SimpleFilter03 = "Webp/vp80-00-comprehensive-003.webp"; - public const string SimpleFilter04 = "Webp/vp80-00-comprehensive-007.webp"; - public const string SimpleFilter05 = "Webp/test-nostrong.webp"; - - // Lossy images with a complex filter. - public const string IccpComplexFilter = WithIccp; - public const string VeryShort = "Webp/very_short.webp"; - public const string BikeComplexFilter = "Webp/bike_lossy_complex_filter.webp"; - public const string ComplexFilter01 = "Webp/vp80-02-inter-1418.webp"; - public const string ComplexFilter02 = "Webp/vp80-02-inter-1418.webp"; - public const string ComplexFilter03 = "Webp/vp80-00-comprehensive-002.webp"; - public const string ComplexFilter04 = "Webp/vp80-00-comprehensive-006.webp"; - public const string ComplexFilter05 = "Webp/vp80-00-comprehensive-009.webp"; - public const string ComplexFilter06 = "Webp/vp80-00-comprehensive-012.webp"; - public const string ComplexFilter07 = "Webp/vp80-00-comprehensive-015.webp"; - public const string ComplexFilter08 = "Webp/vp80-00-comprehensive-016.webp"; - public const string ComplexFilter09 = "Webp/vp80-00-comprehensive-017.webp"; - - // Lossy with partitions. - public const string Partitions01 = "Webp/vp80-04-partitions-1404.webp"; - public const string Partitions02 = "Webp/vp80-04-partitions-1405.webp"; - public const string Partitions03 = "Webp/vp80-04-partitions-1406.webp"; - - // Lossy with segmentation. - public const string SegmentationNoFilter01 = "Webp/vp80-03-segmentation-1401.webp"; - public const string SegmentationNoFilter02 = "Webp/vp80-03-segmentation-1403.webp"; - public const string SegmentationNoFilter03 = "Webp/vp80-03-segmentation-1407.webp"; - public const string SegmentationNoFilter04 = "Webp/vp80-03-segmentation-1408.webp"; - public const string SegmentationNoFilter05 = "Webp/vp80-03-segmentation-1409.webp"; - public const string SegmentationNoFilter06 = "Webp/vp80-03-segmentation-1410.webp"; - public const string SegmentationComplexFilter01 = "Webp/vp80-03-segmentation-1413.webp"; - public const string SegmentationComplexFilter02 = "Webp/vp80-03-segmentation-1425.webp"; - public const string SegmentationComplexFilter03 = "Webp/vp80-03-segmentation-1426.webp"; - public const string SegmentationComplexFilter04 = "Webp/vp80-03-segmentation-1427.webp"; - public const string SegmentationComplexFilter05 = "Webp/vp80-03-segmentation-1432.webp"; - - // Lossy with sharpness level. - public const string Sharpness01 = "Webp/vp80-05-sharpness-1428.webp"; - public const string Sharpness02 = "Webp/vp80-05-sharpness-1429.webp"; - public const string Sharpness03 = "Webp/vp80-05-sharpness-1430.webp"; - public const string Sharpness04 = "Webp/vp80-05-sharpness-1431.webp"; - public const string Sharpness05 = "Webp/vp80-05-sharpness-1433.webp"; - public const string Sharpness06 = "Webp/vp80-05-sharpness-1434.webp"; - - // Very small images (all with complex filter). - public const string Small01 = "Webp/small_13x1.webp"; - public const string Small02 = "Webp/small_1x1.webp"; - public const string Small03 = "Webp/small_1x13.webp"; - public const string Small04 = "Webp/small_31x13.webp"; - - // Lossy images with a alpha channel. - public const string Alpha1 = "Webp/lossy_alpha1.webp"; - public const string Alpha2 = "Webp/lossy_alpha2.webp"; - public const string Alpha3 = "Webp/alpha_color_cache.webp"; - public const string AlphaNoCompression = "Webp/alpha_no_compression.webp"; - public const string AlphaNoCompressionNoFilter = "Webp/alpha_filter_0_method_0.webp"; - public const string AlphaCompressedNoFilter = "Webp/alpha_filter_0_method_1.webp"; - public const string AlphaNoCompressionHorizontalFilter = "Webp/alpha_filter_1_method_0.webp"; - public const string AlphaCompressedHorizontalFilter = "Webp/alpha_filter_1_method_1.webp"; - public const string AlphaNoCompressionVerticalFilter = "Webp/alpha_filter_2_method_0.webp"; - public const string AlphaCompressedVerticalFilter = "Webp/alpha_filter_2_method_1.webp"; - public const string AlphaNoCompressionGradientFilter = "Webp/alpha_filter_3_method_0.webp"; - public const string AlphaCompressedGradientFilter = "Webp/alpha_filter_3_method_1.webp"; - public const string AlphaThinkingSmiley = "Webp/1602311202.webp"; - public const string AlphaSticker = "Webp/sticker.webp"; - - // Issues - public const string Issue1594 = "Webp/issues/Issue1594.webp"; - } - } + // Test pattern images for testing the encoder. + public const string TestPatternOpaque = "Webp/testpattern_opaque.png"; + public const string TestPatternOpaqueSmall = "Webp/testpattern_opaque_small.png"; + public const string RgbTestPattern100x100 = "Webp/rgb_pattern_100x100.png"; + public const string RgbTestPattern80x80 = "Webp/rgb_pattern_80x80.png"; + public const string RgbTestPattern63x63 = "Webp/rgb_pattern_63x63.png"; - public static class Tiff + // Test image for encoding image with a palette. + public const string Flag = "Webp/flag_of_germany.png"; + + // Test images for converting rgb data to yuv. + public const string Yuv = "Webp/yuv_test.png"; + + public static class Lossless { - public const string Benchmark_Path = "Tiff/Benchmarks/"; - public const string Benchmark_BwFax3 = "medium_bw_Fax3.tiff"; - public const string Benchmark_BwFax4 = "medium_bw_Fax4.tiff"; - public const string Benchmark_BwRle = "medium_bw_Rle.tiff"; - public const string Benchmark_GrayscaleUncompressed = "medium_grayscale_uncompressed.tiff"; - public const string Benchmark_PaletteUncompressed = "medium_palette_uncompressed.tiff"; - public const string Benchmark_RgbDeflate = "medium_rgb_deflate.tiff"; - public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; - public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; - public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; - - public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; - public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; - public const string Calliphora_GrayscaleLzw_Predictor = "Tiff/Calliphora_gray_lzw_predictor.tiff"; - public const string Calliphora_GrayscaleDeflate = "Tiff/Calliphora_gray_deflate.tiff"; - public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate_predictor.tiff"; - public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff"; - public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff"; - public const string Calliphora_RgbLzwPredictor = "Tiff/Calliphora_rgb_lzw_predictor.tiff"; - public const string Calliphora_RgbPaletteLzw = "Tiff/Calliphora_rgb_palette_lzw.tiff"; - public const string Calliphora_RgbPaletteLzw_Predictor = "Tiff/Calliphora_rgb_palette_lzw_predictor.tiff"; - public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff"; - public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff"; - public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tiff"; - public const string Fax3Uncompressed = "Tiff/ccitt_fax3_uncompressed.tiff"; - public const string Calliphora_Fax3Compressed_WithEolPadding = "Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff"; - public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; - public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; - public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; - public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff"; - public const string Fax4Compressed2 = "Tiff/CCITTGroup4.tiff"; - public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff"; - public const string WebpCompressed = "Tiff/webp_compressed.tiff"; - public const string Fax4CompressedMinIsBlack = "Tiff/CCITTGroup4_minisblack.tiff"; - public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; - public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; - public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; - public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; - public const string CcittFax3LowerOrderBitsFirst = "Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff"; - public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; - - // Test case for an issue, that the last bits in a row got ignored. - public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; - - public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; - public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; - public const string GrayscaleJpegCompressed = "Tiff/JpegCompressedGray.tiff"; - public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff"; - public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff"; - public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; - public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff"; - public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; - public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff"; - public const string RgbJpegCompressed2 = "Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff"; - public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff"; - public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff"; - public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff"; - public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff"; - public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; - public const string RgbLzwNoPredictorMultistripMotorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; - public const string RgbLzwNoPredictorSinglestripMotorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; - public const string RgbLzwMultistripPredictor = "Tiff/rgb_lzw_multistrip.tiff"; - public const string RgbPackbits = "Tiff/rgb_packbits.tiff"; - public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; - public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; - public const string RgbPalette = "Tiff/rgb_palette.tiff"; - public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; - public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; - public const string FlowerRgbFloat323232 = "Tiff/flower-rgb-float32_msb.tiff"; - public const string FlowerRgbFloat323232LittleEndian = "Tiff/flower-rgb-float32_lsb.tiff"; - public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff"; - public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff"; - public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff"; - public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.tiff"; - public const string FlowerRgb323232PredictorBigEndian = "Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff"; - public const string FlowerRgb323232PredictorLittleEndian = "Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff"; - public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff"; - public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff"; - public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff"; - public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff"; - public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; - public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; - public const string FlowerRgb161616PredictorBigEndian = "Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff"; - public const string FlowerRgb161616PredictorLittleEndian = "Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff"; - public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; - public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff"; - public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; - public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; - public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff"; - public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; - public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; - public const string FlowerRgb888Contiguous = "Tiff/flower-rgb-contig-08.tiff"; - public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff"; - public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff"; - public const string FlowerYCbCr888Contiguous = "Tiff/flower-ycbcr-contig-08_h1v1.tiff"; - public const string FlowerYCbCr888Planar = "Tiff/flower-ycbcr-planar-08_h1v1.tiff"; - public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff"; - public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff"; - public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff"; - public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff"; - public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff"; - public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff"; - public const string YCbCrJpegCompressed2 = "Tiff/ycbcr_jpegcompressed2.tiff"; - public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; - public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; - public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; - public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; - public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; - public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; - public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; - public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; - public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff"; - public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff"; - public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff"; - public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; - public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; - public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; - public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; - public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; - public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; - public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; - public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; - public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff"; - public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff"; - public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16_msb.tiff"; - public const string Flower24BitGray = "Tiff/flower-minisblack-24_msb.tiff"; - public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; - public const string Flower32BitGray = "Tiff/flower-minisblack-32_msb.tiff"; - public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; - public const string Flower32BitFloatGray = "Tiff/flower-minisblack-float32_msb.tiff"; - public const string Flower32BitFloatGrayLittleEndian = "Tiff/flower-minisblack-float32_lsb.tiff"; - public const string Flower32BitFloatGrayMinIsWhite = "Tiff/flower-miniswhite-float32_msb.tiff"; - public const string Flower32BitFloatGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-float32_lsb.tiff"; - public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32_msb.tiff"; - public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; - public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; - public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; - - // Images with alpha channel. - public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; - public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; - public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff"; - public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; - public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff"; - public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; - public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff"; - public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; - public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff"; - public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; - public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.tiff"; - public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff"; - public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; - public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; - public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; - public const string Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff"; - public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff"; - public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; - public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; - public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff"; - public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff"; - public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; - public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; - public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff"; - public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff"; - public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; - public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; - public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff"; - public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_lsb.tiff"; - public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff"; - public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff"; - public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; - public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; - public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; - public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff"; - public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff"; - public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; - public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; - public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff"; - public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff"; - public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff"; - public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff"; - public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; - - // Cie Lab color space. - public const string CieLab = "Tiff/CieLab.tiff"; - public const string CieLabPlanar = "Tiff/CieLabPlanar.tiff"; - public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff"; - - public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; - public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; - public const string Issues2123 = "Tiff/Issues/Issue2123.tiff"; - public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff"; - - public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; - public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; - - public const string RgbUncompressedTiled = "Tiff/rgb_uncompressed_tiled.tiff"; - public const string MultiframeDifferentSizeTiled = "Tiff/multipage_ withPreview_differentSize_tiled.tiff"; - - public const string MultiframeLzwPredictor = "Tiff/multipage_lzw.tiff"; - public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff"; - public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; - public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; - public const string MultiFrameMipMap = "Tiff/SKC1H3.tiff"; - - public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; - - public const string Fax4_Motorola = "Tiff/moy.tiff"; - - public const string SampleMetadata = "Tiff/metadata_sample.tiff"; - - // Iptc data as long[] instead of byte[] - public const string InvalidIptcData = "Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff"; - public const string IptcData = "Tiff/iptc.tiff"; - - public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; - - public static readonly string[] Metadata = { SampleMetadata }; + public const string Animated = "Webp/leo_animated_lossless.webp"; + public const string Earth = "Webp/earth_lossless.webp"; + public const string Alpha = "Webp/lossless_alpha_small.webp"; + public const string WithExif = "Webp/exif_lossless.webp"; + public const string WithIccp = "Webp/lossless_with_iccp.webp"; + public const string NoTransform1 = "Webp/lossless_vec_1_0.webp"; + public const string NoTransform2 = "Webp/lossless_vec_2_0.webp"; + public const string GreenTransform1 = "Webp/lossless1.webp"; + public const string GreenTransform2 = "Webp/lossless2.webp"; + public const string GreenTransform3 = "Webp/lossless3.webp"; + public const string GreenTransform4 = "Webp/lossless_vec_1_4.webp"; + public const string GreenTransform5 = "Webp/lossless_vec_2_4.webp"; + public const string CrossColorTransform1 = "Webp/lossless_vec_1_8.webp"; + public const string CrossColorTransform2 = "Webp/lossless_vec_2_8.webp"; + public const string PredictorTransform1 = "Webp/lossless_vec_1_2.webp"; + public const string PredictorTransform2 = "Webp/lossless_vec_2_2.webp"; + public const string ColorIndexTransform1 = "Webp/lossless4.webp"; + public const string ColorIndexTransform2 = "Webp/lossless_vec_1_1.webp"; + public const string ColorIndexTransform3 = "Webp/lossless_vec_1_5.webp"; + public const string ColorIndexTransform4 = "Webp/lossless_vec_2_1.webp"; + public const string ColorIndexTransform5 = "Webp/lossless_vec_2_5.webp"; + public const string TwoTransforms1 = "Webp/lossless_vec_1_10.webp"; // cross_color, predictor + public const string TwoTransforms2 = "Webp/lossless_vec_1_12.webp"; // cross_color, substract_green + public const string TwoTransforms3 = "Webp/lossless_vec_1_13.webp"; // color_indexing, cross_color + public const string TwoTransforms4 = "Webp/lossless_vec_1_3.webp"; // color_indexing, predictor + public const string TwoTransforms5 = "Webp/lossless_vec_1_6.webp"; // substract_green, predictor + public const string TwoTransforms6 = "Webp/lossless_vec_1_7.webp"; // color_indexing, predictor + public const string TwoTransforms7 = "Webp/lossless_vec_1_9.webp"; // color_indexing, cross_color + public const string TwoTransforms8 = "Webp/lossless_vec_2_10.webp"; // predictor, cross_color + public const string TwoTransforms9 = "Webp/lossless_vec_2_12.webp"; // substract_green, cross_color + public const string TwoTransforms10 = "Webp/lossless_vec_2_13.webp"; // color_indexing, cross_color + public const string TwoTransforms11 = "Webp/lossless_vec_2_3.webp"; // color_indexing, predictor + public const string TwoTransforms12 = "Webp/lossless_vec_2_6.webp"; // substract_green, predictor + public const string TwoTransforms13 = "Webp/lossless_vec_2_9.webp"; // color_indexing, predictor + + // substract_green, predictor, cross_color + public const string ThreeTransforms1 = "Webp/color_cache_bits_11.webp"; + + // color_indexing, predictor, cross_color + public const string ThreeTransforms2 = "Webp/lossless_vec_1_11.webp"; + + // substract_green, predictor, cross_color + public const string ThreeTransforms3 = "Webp/lossless_vec_1_14.webp"; + + // color_indexing, predictor, cross_color + public const string ThreeTransforms4 = "Webp/lossless_vec_1_15.webp"; + + // color_indexing, predictor, cross_color + public const string ThreeTransforms5 = "Webp/lossless_vec_2_11.webp"; + + // substract_green, predictor, cross_color + public const string ThreeTransforms6 = "Webp/lossless_vec_2_14.webp"; + + // color_indexing, predictor, cross_color + public const string ThreeTransforms7 = "Webp/lossless_vec_2_15.webp"; + + // substract_green, predictor, cross_color + public const string BikeThreeTransforms = "Webp/bike_lossless.webp"; + + // Invalid / corrupted images + // Below images have errors according to webpinfo. The error message webpinfo gives is "Truncated data detected when parsing RIFF payload." + public const string LossLessCorruptImage1 = "Webp/lossless_big_random_alpha.webp"; // substract_green, predictor, cross_color. + + public const string LossLessCorruptImage2 = "Webp/lossless_vec_2_7.webp"; // color_indexing, predictor. + + public const string LossLessCorruptImage3 = "Webp/lossless_color_transform.webp"; // cross_color, predictor + + public const string LossLessCorruptImage4 = "Webp/near_lossless_75.webp"; // predictor, cross_color. } - public static class BigTiff + public static class Lossy { - public const string Base = "Tiff/BigTiff/"; + public const string Earth = "Webp/earth_lossy.webp"; + public const string WithExif = "Webp/exif_lossy.webp"; + public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp"; + public const string WithIccp = "Webp/lossy_with_iccp.webp"; + public const string WithXmp = "Webp/xmp_lossy.webp"; + public const string BikeSmall = "Webp/bike_lossy_small.webp"; + public const string Animated = "Webp/leo_animated_lossy.webp"; + + // Lossy images without macroblock filtering. + public const string BikeWithExif = "Webp/bike_lossy_with_exif.webp"; + public const string NoFilter01 = "Webp/vp80-01-intra-1400.webp"; + public const string NoFilter02 = "Webp/vp80-00-comprehensive-010.webp"; + public const string NoFilter03 = "Webp/vp80-00-comprehensive-005.webp"; + public const string NoFilter04 = "Webp/vp80-01-intra-1417.webp"; + public const string NoFilter05 = "Webp/vp80-02-inter-1402.webp"; + public const string NoFilter06 = "Webp/test.webp"; + + // Lossy images with a simple filter. + public const string SimpleFilter01 = "Webp/segment01.webp"; + public const string SimpleFilter02 = "Webp/segment02.webp"; + public const string SimpleFilter03 = "Webp/vp80-00-comprehensive-003.webp"; + public const string SimpleFilter04 = "Webp/vp80-00-comprehensive-007.webp"; + public const string SimpleFilter05 = "Webp/test-nostrong.webp"; + + // Lossy images with a complex filter. + public const string IccpComplexFilter = WithIccp; + public const string VeryShort = "Webp/very_short.webp"; + public const string BikeComplexFilter = "Webp/bike_lossy_complex_filter.webp"; + public const string ComplexFilter01 = "Webp/vp80-02-inter-1418.webp"; + public const string ComplexFilter02 = "Webp/vp80-02-inter-1418.webp"; + public const string ComplexFilter03 = "Webp/vp80-00-comprehensive-002.webp"; + public const string ComplexFilter04 = "Webp/vp80-00-comprehensive-006.webp"; + public const string ComplexFilter05 = "Webp/vp80-00-comprehensive-009.webp"; + public const string ComplexFilter06 = "Webp/vp80-00-comprehensive-012.webp"; + public const string ComplexFilter07 = "Webp/vp80-00-comprehensive-015.webp"; + public const string ComplexFilter08 = "Webp/vp80-00-comprehensive-016.webp"; + public const string ComplexFilter09 = "Webp/vp80-00-comprehensive-017.webp"; + + // Lossy with partitions. + public const string Partitions01 = "Webp/vp80-04-partitions-1404.webp"; + public const string Partitions02 = "Webp/vp80-04-partitions-1405.webp"; + public const string Partitions03 = "Webp/vp80-04-partitions-1406.webp"; + + // Lossy with segmentation. + public const string SegmentationNoFilter01 = "Webp/vp80-03-segmentation-1401.webp"; + public const string SegmentationNoFilter02 = "Webp/vp80-03-segmentation-1403.webp"; + public const string SegmentationNoFilter03 = "Webp/vp80-03-segmentation-1407.webp"; + public const string SegmentationNoFilter04 = "Webp/vp80-03-segmentation-1408.webp"; + public const string SegmentationNoFilter05 = "Webp/vp80-03-segmentation-1409.webp"; + public const string SegmentationNoFilter06 = "Webp/vp80-03-segmentation-1410.webp"; + public const string SegmentationComplexFilter01 = "Webp/vp80-03-segmentation-1413.webp"; + public const string SegmentationComplexFilter02 = "Webp/vp80-03-segmentation-1425.webp"; + public const string SegmentationComplexFilter03 = "Webp/vp80-03-segmentation-1426.webp"; + public const string SegmentationComplexFilter04 = "Webp/vp80-03-segmentation-1427.webp"; + public const string SegmentationComplexFilter05 = "Webp/vp80-03-segmentation-1432.webp"; + + // Lossy with sharpness level. + public const string Sharpness01 = "Webp/vp80-05-sharpness-1428.webp"; + public const string Sharpness02 = "Webp/vp80-05-sharpness-1429.webp"; + public const string Sharpness03 = "Webp/vp80-05-sharpness-1430.webp"; + public const string Sharpness04 = "Webp/vp80-05-sharpness-1431.webp"; + public const string Sharpness05 = "Webp/vp80-05-sharpness-1433.webp"; + public const string Sharpness06 = "Webp/vp80-05-sharpness-1434.webp"; + + // Very small images (all with complex filter). + public const string Small01 = "Webp/small_13x1.webp"; + public const string Small02 = "Webp/small_1x1.webp"; + public const string Small03 = "Webp/small_1x13.webp"; + public const string Small04 = "Webp/small_31x13.webp"; + + // Lossy images with a alpha channel. + public const string Alpha1 = "Webp/lossy_alpha1.webp"; + public const string Alpha2 = "Webp/lossy_alpha2.webp"; + public const string Alpha3 = "Webp/alpha_color_cache.webp"; + public const string AlphaNoCompression = "Webp/alpha_no_compression.webp"; + public const string AlphaNoCompressionNoFilter = "Webp/alpha_filter_0_method_0.webp"; + public const string AlphaCompressedNoFilter = "Webp/alpha_filter_0_method_1.webp"; + public const string AlphaNoCompressionHorizontalFilter = "Webp/alpha_filter_1_method_0.webp"; + public const string AlphaCompressedHorizontalFilter = "Webp/alpha_filter_1_method_1.webp"; + public const string AlphaNoCompressionVerticalFilter = "Webp/alpha_filter_2_method_0.webp"; + public const string AlphaCompressedVerticalFilter = "Webp/alpha_filter_2_method_1.webp"; + public const string AlphaNoCompressionGradientFilter = "Webp/alpha_filter_3_method_0.webp"; + public const string AlphaCompressedGradientFilter = "Webp/alpha_filter_3_method_1.webp"; + public const string AlphaThinkingSmiley = "Webp/1602311202.webp"; + public const string AlphaSticker = "Webp/sticker.webp"; + + // Issues + public const string Issue1594 = "Webp/issues/Issue1594.webp"; + } + } + + public static class Tiff + { + public const string Benchmark_Path = "Tiff/Benchmarks/"; + public const string Benchmark_BwFax3 = "medium_bw_Fax3.tiff"; + public const string Benchmark_BwFax4 = "medium_bw_Fax4.tiff"; + public const string Benchmark_BwRle = "medium_bw_Rle.tiff"; + public const string Benchmark_GrayscaleUncompressed = "medium_grayscale_uncompressed.tiff"; + public const string Benchmark_PaletteUncompressed = "medium_palette_uncompressed.tiff"; + public const string Benchmark_RgbDeflate = "medium_rgb_deflate.tiff"; + public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; + public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; + public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; + + public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; + public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; + public const string Calliphora_GrayscaleLzw_Predictor = "Tiff/Calliphora_gray_lzw_predictor.tiff"; + public const string Calliphora_GrayscaleDeflate = "Tiff/Calliphora_gray_deflate.tiff"; + public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate_predictor.tiff"; + public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff"; + public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff"; + public const string Calliphora_RgbLzwPredictor = "Tiff/Calliphora_rgb_lzw_predictor.tiff"; + public const string Calliphora_RgbPaletteLzw = "Tiff/Calliphora_rgb_palette_lzw.tiff"; + public const string Calliphora_RgbPaletteLzw_Predictor = "Tiff/Calliphora_rgb_palette_lzw_predictor.tiff"; + public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff"; + public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff"; + public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tiff"; + public const string Fax3Uncompressed = "Tiff/ccitt_fax3_uncompressed.tiff"; + public const string Calliphora_Fax3Compressed_WithEolPadding = "Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff"; + public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; + public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; + public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; + public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff"; + public const string Fax4Compressed2 = "Tiff/CCITTGroup4.tiff"; + public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff"; + public const string WebpCompressed = "Tiff/webp_compressed.tiff"; + public const string Fax4CompressedMinIsBlack = "Tiff/CCITTGroup4_minisblack.tiff"; + public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; + public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; + public const string HuffmanRleAllTermCodes = "Tiff/huffman_rle_all_terminating_codes.tiff"; + public const string HuffmanRleAllMakeupCodes = "Tiff/huffman_rle_all_makeup_codes.tiff"; + public const string CcittFax3LowerOrderBitsFirst = "Tiff/basi3p02_fax3_lowerOrderBitsFirst.tiff"; + public const string HuffmanRleLowerOrderBitsFirst = "Tiff/basi3p02_huffman_rle_lowerOrderBitsFirst.tiff"; + + // Test case for an issue, that the last bits in a row got ignored. + public const string HuffmanRle_basi3p02 = "Tiff/basi3p02_huffman_rle.tiff"; + + public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff"; + public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff"; + public const string GrayscaleJpegCompressed = "Tiff/JpegCompressedGray.tiff"; + public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff"; + public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff"; + public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; + public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff"; + public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; + public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff"; + public const string RgbJpegCompressed2 = "Tiff/twain-rgb-jpeg-with-bogus-ycbcr-subsampling.tiff"; + public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff"; + public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff"; + public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff"; + public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff"; + public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; + public const string RgbLzwNoPredictorMultistripMotorola = "Tiff/rgb_lzw_noPredictor_multistrip_Motorola.tiff"; + public const string RgbLzwNoPredictorSinglestripMotorola = "Tiff/rgb_lzw_noPredictor_singlestrip_Motorola.tiff"; + public const string RgbLzwMultistripPredictor = "Tiff/rgb_lzw_multistrip.tiff"; + public const string RgbPackbits = "Tiff/rgb_packbits.tiff"; + public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff"; + public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff"; + public const string RgbPalette = "Tiff/rgb_palette.tiff"; + public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; + public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; + public const string FlowerRgbFloat323232 = "Tiff/flower-rgb-float32_msb.tiff"; + public const string FlowerRgbFloat323232LittleEndian = "Tiff/flower-rgb-float32_lsb.tiff"; + public const string FlowerRgb323232Contiguous = "Tiff/flower-rgb-contig-32.tiff"; + public const string FlowerRgb323232ContiguousLittleEndian = "Tiff/flower-rgb-contig-32_lsb.tiff"; + public const string FlowerRgb323232Planar = "Tiff/flower-rgb-planar-32.tiff"; + public const string FlowerRgb323232PlanarLittleEndian = "Tiff/flower-rgb-planar-32_lsb.tiff"; + public const string FlowerRgb323232PredictorBigEndian = "Tiff/flower-rgb-contig-32_msb_zip_predictor.tiff"; + public const string FlowerRgb323232PredictorLittleEndian = "Tiff/flower-rgb-contig-32_lsb_zip_predictor.tiff"; + public const string FlowerRgb242424Planar = "Tiff/flower-rgb-planar-24.tiff"; + public const string FlowerRgb242424PlanarLittleEndian = "Tiff/flower-rgb-planar-24_lsb.tiff"; + public const string FlowerRgb242424Contiguous = "Tiff/flower-rgb-contig-24.tiff"; + public const string FlowerRgb242424ContiguousLittleEndian = "Tiff/flower-rgb-contig-24_lsb.tiff"; + public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff"; + public const string FlowerRgb161616ContiguousLittleEndian = "Tiff/flower-rgb-contig-16_lsb.tiff"; + public const string FlowerRgb161616PredictorBigEndian = "Tiff/flower-rgb-contig-16_msb_zip_predictor.tiff"; + public const string FlowerRgb161616PredictorLittleEndian = "Tiff/flower-rgb-contig-16_lsb_zip_predictor.tiff"; + public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff"; + public const string FlowerRgb161616PlanarLittleEndian = "Tiff/flower-rgb-planar-16_lsb.tiff"; + public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; + public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; + public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff"; + public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; + public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; + public const string FlowerRgb888Contiguous = "Tiff/flower-rgb-contig-08.tiff"; + public const string FlowerRgb888Planar6Strips = "Tiff/flower-rgb-planar-08-6strips.tiff"; + public const string FlowerRgb888Planar15Strips = "Tiff/flower-rgb-planar-08-15strips.tiff"; + public const string FlowerYCbCr888Contiguous = "Tiff/flower-ycbcr-contig-08_h1v1.tiff"; + public const string FlowerYCbCr888Planar = "Tiff/flower-ycbcr-planar-08_h1v1.tiff"; + public const string FlowerYCbCr888Contiguoush2v1 = "Tiff/flower-ycbcr-contig-08_h2v1.tiff"; + public const string FlowerYCbCr888Contiguoush2v2 = "Tiff/flower-ycbcr-contig-08_h2v2.tiff"; + public const string FlowerYCbCr888Contiguoush4v4 = "Tiff/flower-ycbcr-contig-08_h4v4.tiff"; + public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff"; + public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff"; + public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff"; + public const string YCbCrJpegCompressed2 = "Tiff/ycbcr_jpegcompressed2.tiff"; + public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; + public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; + public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; + public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; + public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff"; + public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff"; + public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; + public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; + public const string FLowerRgb3Bit = "Tiff/flower-rgb-3bit.tiff"; + public const string FLowerRgb5Bit = "Tiff/flower-rgb-5bit.tiff"; + public const string FLowerRgb6Bit = "Tiff/flower-rgb-6bit.tiff"; + public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff"; + public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff"; + public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff"; + public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff"; + public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff"; + public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff"; + public const string Flower16BitGrayLittleEndian = "Tiff/flower-minisblack-16_lsb.tiff"; + public const string Flower16BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-16_lsb.tiff"; + public const string Flower16BitGrayPredictorBigEndian = "Tiff/flower-minisblack-16_msb_lzw_predictor.tiff"; + public const string Flower16BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-16_lsb_lzw_predictor.tiff"; + public const string Flower16BitGrayMinIsWhiteBigEndian = "Tiff/flower-miniswhite-16_msb.tiff"; + public const string Flower24BitGray = "Tiff/flower-minisblack-24_msb.tiff"; + public const string Flower24BitGrayLittleEndian = "Tiff/flower-minisblack-24_lsb.tiff"; + public const string Flower32BitGray = "Tiff/flower-minisblack-32_msb.tiff"; + public const string Flower32BitGrayLittleEndian = "Tiff/flower-minisblack-32_lsb.tiff"; + public const string Flower32BitFloatGray = "Tiff/flower-minisblack-float32_msb.tiff"; + public const string Flower32BitFloatGrayLittleEndian = "Tiff/flower-minisblack-float32_lsb.tiff"; + public const string Flower32BitFloatGrayMinIsWhite = "Tiff/flower-miniswhite-float32_msb.tiff"; + public const string Flower32BitFloatGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-float32_lsb.tiff"; + public const string Flower32BitGrayMinIsWhite = "Tiff/flower-miniswhite-32_msb.tiff"; + public const string Flower32BitGrayMinIsWhiteLittleEndian = "Tiff/flower-miniswhite-32_lsb.tiff"; + public const string Flower32BitGrayPredictorBigEndian = "Tiff/flower-minisblack-32_msb_deflate_predictor.tiff"; + public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; + + // Images with alpha channel. + public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; + public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; + public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff"; + public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; + public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff"; + public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; + public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff"; + public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; + public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff"; + public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; + public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.tiff"; + public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff"; + public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; + public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; + public const string Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff"; + public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff"; + public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; + public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff"; + public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff"; + public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; + public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff"; + public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff"; + public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; + public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff"; + public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_lsb.tiff"; + public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff"; + public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; + public const string Rgba16BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_lsb.tiff"; + public const string Rgba24BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha24bit_msb.tiff"; + public const string Rgba24BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha24bit_lsb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_msb.tiff"; + public const string Rgba24BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar24bit_lsb.tiff"; + public const string Rgba32BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha32bit_lsb.tiff"; + public const string Rgba32BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_msb.tiff"; + public const string Rgba32BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor32bit_lsb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_msb.tiff"; + public const string Rgba32BitPlanarUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlphaPlanar32bit_lsb.tiff"; + + // Cie Lab color space. + public const string CieLab = "Tiff/CieLab.tiff"; + public const string CieLabPlanar = "Tiff/CieLabPlanar.tiff"; + public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff"; + + public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; + public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; + public const string Issues2123 = "Tiff/Issues/Issue2123.tiff"; + public const string Issues2149 = "Tiff/Issues/Group4CompressionWithStrips.tiff"; + + public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; + public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; + + public const string RgbUncompressedTiled = "Tiff/rgb_uncompressed_tiled.tiff"; + public const string MultiframeDifferentSizeTiled = "Tiff/multipage_ withPreview_differentSize_tiled.tiff"; + + public const string MultiframeLzwPredictor = "Tiff/multipage_lzw.tiff"; + public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff"; + public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff"; + public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff"; + public const string MultiFrameMipMap = "Tiff/SKC1H3.tiff"; + + public const string LittleEndianByteOrder = "Tiff/little_endian.tiff"; + + public const string Fax4_Motorola = "Tiff/moy.tiff"; + + public const string SampleMetadata = "Tiff/metadata_sample.tiff"; + + // Iptc data as long[] instead of byte[] + public const string InvalidIptcData = "Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff"; + public const string IptcData = "Tiff/iptc.tiff"; + + public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; + + public static readonly string[] Metadata = { SampleMetadata }; + } - public const string BigTIFF = Base + "BigTIFF.tif"; - public const string BigTIFFLong = Base + "BigTIFFLong.tif"; - public const string BigTIFFLong8 = Base + "BigTIFFLong8.tif"; - public const string BigTIFFLong8Tiles = Base + "BigTIFFLong8Tiles.tif"; - public const string BigTIFFMotorola = Base + "BigTIFFMotorola.tif"; - public const string BigTIFFMotorolaLongStrips = Base + "BigTIFFMotorolaLongStrips.tif"; + public static class BigTiff + { + public const string Base = "Tiff/BigTiff/"; - public const string BigTIFFSubIFD4 = Base + "BigTIFFSubIFD4.tif"; - public const string BigTIFFSubIFD8 = Base + "BigTIFFSubIFD8.tif"; + public const string BigTIFF = Base + "BigTIFF.tif"; + public const string BigTIFFLong = Base + "BigTIFFLong.tif"; + public const string BigTIFFLong8 = Base + "BigTIFFLong8.tif"; + public const string BigTIFFLong8Tiles = Base + "BigTIFFLong8Tiles.tif"; + public const string BigTIFFMotorola = Base + "BigTIFFMotorola.tif"; + public const string BigTIFFMotorolaLongStrips = Base + "BigTIFFMotorolaLongStrips.tif"; - public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; - public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; - public const string MinIsBlack = Base + "BigTIFF_MinIsBlack.tif"; - public const string MinIsWhite = Base + "BigTIFF_MinIsWhite.tif"; + public const string BigTIFFSubIFD4 = Base + "BigTIFFSubIFD4.tif"; + public const string BigTIFFSubIFD8 = Base + "BigTIFFSubIFD8.tif"; - public const string Damaged_MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; - public const string Damaged_MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; - } + public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; + public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; + public const string MinIsBlack = Base + "BigTIFF_MinIsBlack.tif"; + public const string MinIsWhite = Base + "BigTIFF_MinIsWhite.tif"; - public static class Pbm - { - public const string BlackAndWhitePlain = "Pbm/blackandwhite_plain.pbm"; - public const string BlackAndWhiteBinary = "Pbm/blackandwhite_binary.pbm"; - public const string GrayscaleBinary = "Pbm/rings.pgm"; - public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; - public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; - public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; - public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; - public const string RgbBinary = "Pbm/00000_00000.ppm"; - public const string RgbPlain = "Pbm/rgb_plain.ppm"; - public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; - public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; - } + public const string Damaged_MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; + public const string Damaged_MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; + } + + public static class Pbm + { + public const string BlackAndWhitePlain = "Pbm/blackandwhite_plain.pbm"; + public const string BlackAndWhiteBinary = "Pbm/blackandwhite_binary.pbm"; + public const string GrayscaleBinary = "Pbm/rings.pgm"; + public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; + public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; + public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; + public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; + public const string RgbBinary = "Pbm/00000_00000.ppm"; + public const string RgbPlain = "Pbm/rgb_plain.ppm"; + public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; + public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 02431aaf52..6d9652d898 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -1,77 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Allows the approximate comparison of single precision floating point values. +/// +internal readonly struct ApproximateFloatComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer { + private readonly float epsilon; + /// - /// Allows the approximate comparison of single precision floating point values. + /// Initializes a new instance of the class. /// - internal readonly struct ApproximateFloatComparer : - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer - { - 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; + /// The comparison error difference epsilon to use. + public ApproximateFloatComparer(float epsilon = 1F) => this.epsilon = epsilon; - /// - public bool Equals(float x, float y) - { - float d = x - y; + /// + 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; + } - /// - public int GetHashCode(float obj) - => obj.GetHashCode(); + /// + public int GetHashCode(float obj) + => obj.GetHashCode(); - /// - public bool Equals(Vector2 x, Vector2 y) - => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + /// + public bool Equals(Vector2 x, Vector2 y) + => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); - /// - public int GetHashCode(Vector2 obj) - => obj.GetHashCode(); + /// + public int GetHashCode(Vector2 obj) + => obj.GetHashCode(); - /// - public bool Equals(IPixel x, IPixel y) - => this.Equals(x.ToScaledVector4(), y.ToScaledVector4()); + /// + public bool Equals(IPixel x, IPixel y) + => this.Equals(x.ToScaledVector4(), y.ToScaledVector4()); - public int GetHashCode(IPixel obj) - => obj.ToScaledVector4().GetHashCode(); + public int GetHashCode(IPixel obj) + => obj.ToScaledVector4().GetHashCode(); - /// - public bool Equals(Vector4 x, Vector4 y) - => this.Equals(x.X, y.X) - && this.Equals(x.Y, y.Y) - && this.Equals(x.Z, y.Z) - && this.Equals(x.W, y.W); + /// + public bool Equals(Vector4 x, Vector4 y) + => this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Z, y.Z) + && this.Equals(x.W, y.W); - /// - public int GetHashCode(Vector4 obj) - => obj.GetHashCode(); + /// + public int GetHashCode(Vector4 obj) + => obj.GetHashCode(); - /// - public bool Equals(ColorMatrix x, ColorMatrix y) - => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) - && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) - && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) - && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) - && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); + /// + public bool Equals(ColorMatrix x, ColorMatrix y) + => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) + && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) + && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) + && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) + && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); - /// - public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); - } + /// + public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs index 6dff82a001..8ff2abd90b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -1,58 +1,55 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Linq; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public static class ArrayHelper { - public static class ArrayHelper + /// + /// Concatenates multiple arrays of the same type into one. + /// + /// The array type + /// The arrays to concatenate. The order is kept + /// The concatenated array + public static T[] Concat(params T[][] arrays) { - /// - /// Concatenates multiple arrays of the same type into one. - /// - /// The array type - /// The arrays to concatenate. The order is kept - /// The concatenated array - public static T[] Concat(params T[][] arrays) + var result = new T[arrays.Sum(t => t.Length)]; + int offset = 0; + for (int i = 0; i < arrays.Length; i++) { - var result = new T[arrays.Sum(t => t.Length)]; - int offset = 0; - for (int i = 0; i < arrays.Length; i++) - { - arrays[i].CopyTo(result, offset); - offset += arrays[i].Length; - } - - return result; + arrays[i].CopyTo(result, offset); + offset += arrays[i].Length; } - /// - /// Creates an array filled with the given value. - /// - /// The array type - /// The value to fill the array with - /// The wanted length of the array - /// The created array filled with the given value - public static T[] Fill(T value, int length) - { - var result = new T[length]; - for (int i = 0; i < length; i++) - { - result[i] = value; - } - - return result; - } + return result; + } - /// - /// Creates a string from a character with a given length. - /// - /// The character to fill the string with - /// The wanted length of the string - /// The filled string - public static string Fill(char value, int length) + /// + /// Creates an array filled with the given value. + /// + /// The array type + /// The value to fill the array with + /// The wanted length of the array + /// The created array filled with the given value + public static T[] Fill(T value, int length) + { + var result = new T[length]; + for (int i = 0; i < length; i++) { - return string.Empty.PadRight(length, value); + result[i] = value; } + + return result; + } + + /// + /// Creates a string from a character with a given length. + /// + /// The character to fill the string with + /// The wanted length of the string + /// The filled string + public static string Fill(char value, int length) + { + return string.Empty.PadRight(length, value); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs index 61be7d0313..914f618ad0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/AsyncStreamWrapper.cs @@ -1,118 +1,112 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.TestUtilities +// https://github.com/dotnet/aspnetcore/blob/620c673705bb17b33cbc5ff32872d85a5fbf82b9/src/Hosting/TestHost/src/AsyncStreamWrapper.cs +internal class AsyncStreamWrapper : Stream { - // https://github.com/dotnet/aspnetcore/blob/620c673705bb17b33cbc5ff32872d85a5fbf82b9/src/Hosting/TestHost/src/AsyncStreamWrapper.cs - internal class AsyncStreamWrapper : Stream + private Stream inner; + private Func allowSynchronousIO; + + internal AsyncStreamWrapper(Stream inner, Func allowSynchronousIO) { - private Stream inner; - private Func allowSynchronousIO; + this.inner = inner; + this.allowSynchronousIO = allowSynchronousIO; + } - internal AsyncStreamWrapper(Stream inner, Func allowSynchronousIO) - { - this.inner = inner; - this.allowSynchronousIO = allowSynchronousIO; - } + public override bool CanRead => this.inner.CanRead; - public override bool CanRead => this.inner.CanRead; + public override bool CanSeek => false; - public override bool CanSeek => false; + public override bool CanWrite => this.inner.CanWrite; - public override bool CanWrite => this.inner.CanWrite; + public override long Length => this.inner.Length; - public override long Length => this.inner.Length; + public override long Position + { + get => throw new NotSupportedException("The stream is not seekable."); + set => throw new NotSupportedException("The stream is not seekable."); + } - public override long Position - { - get => throw new NotSupportedException("The stream is not seekable."); - set => throw new NotSupportedException("The stream is not seekable."); - } + public override void Flush() + { + // Not blocking Flush because things like StreamWriter.Dispose() always call it. + this.inner.Flush(); + } - public override void Flush() - { - // Not blocking Flush because things like StreamWriter.Dispose() always call it. - this.inner.Flush(); - } + public override Task FlushAsync(CancellationToken cancellationToken) + { + return this.inner.FlushAsync(cancellationToken); + } - public override Task FlushAsync(CancellationToken cancellationToken) + public override int Read(byte[] buffer, int offset, int count) + { + if (!this.allowSynchronousIO()) { - return this.inner.FlushAsync(cancellationToken); + throw new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true."); } - public override int Read(byte[] buffer, int offset, int count) - { - if (!this.allowSynchronousIO()) - { - throw new InvalidOperationException("Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true."); - } + return this.inner.Read(buffer, offset, count); + } - return this.inner.Read(buffer, offset, count); - } + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.inner.ReadAsync(buffer, offset, count, cancellationToken); + } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return this.inner.ReadAsync(buffer, offset, count, cancellationToken); - } + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.inner.BeginRead(buffer, offset, count, callback, state); + } - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return this.inner.BeginRead(buffer, offset, count, callback, state); - } + public override int EndRead(IAsyncResult asyncResult) + { + return this.inner.EndRead(asyncResult); + } - public override int EndRead(IAsyncResult asyncResult) - { - return this.inner.EndRead(asyncResult); - } + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("The stream is not seekable."); + } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException("The stream is not seekable."); - } + public override void SetLength(long value) + { + throw new NotSupportedException("The stream is not seekable."); + } - public override void SetLength(long value) + public override void Write(byte[] buffer, int offset, int count) + { + if (!this.allowSynchronousIO()) { - throw new NotSupportedException("The stream is not seekable."); + throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true."); } - public override void Write(byte[] buffer, int offset, int count) - { - if (!this.allowSynchronousIO()) - { - throw new InvalidOperationException("Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true."); - } - - this.inner.Write(buffer, offset, count); - } + this.inner.Write(buffer, offset, count); + } - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return this.inner.BeginWrite(buffer, offset, count, callback, state); - } + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return this.inner.BeginWrite(buffer, offset, count, callback, state); + } - public override void EndWrite(IAsyncResult asyncResult) - { - this.inner.EndWrite(asyncResult); - } + public override void EndWrite(IAsyncResult asyncResult) + { + this.inner.EndWrite(asyncResult); + } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return this.inner.WriteAsync(buffer, offset, count, cancellationToken); - } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return this.inner.WriteAsync(buffer, offset, count, cancellationToken); + } - public override void Close() - { - // Don't dispose the inner stream, we don't want to impact the client stream - } + public override void Close() + { + // Don't dispose the inner stream, we don't want to impact the client stream + } - protected override void Dispose(bool disposing) - { - // Don't dispose the inner stream, we don't want to impact the client stream - } + protected override void Dispose(bool disposing) + { + // Don't dispose the inner stream, we don't want to impact the client stream } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs index 2fa608f07e..284a94ad17 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,20 +1,19 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests -{ - using System; +namespace SixLabors.ImageSharp.Tests; - /// - /// The output produced by this test class should be grouped into the specified subfolder. - /// - public class GroupOutputAttribute : Attribute - { - public GroupOutputAttribute(string subfolder) - { - this.Subfolder = subfolder; - } +using System; - public string Subfolder { get; } +/// +/// The output produced by this test class should be grouped into the specified subfolder. +/// +public class GroupOutputAttribute : Attribute +{ + public GroupOutputAttribute(string subfolder) + { + this.Subfolder = subfolder; } + + public string Subfolder { get; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 50363b4110..592ebb4ebe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -1,204 +1,200 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Xunit.Sdk; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Base class for Theory Data attributes which pass an instance of to the test case. +/// +public abstract class ImageDataAttributeBase : DataAttribute { + protected readonly object[] AdditionalParameters; + + 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(); + } + /// - /// Base class for Theory Data attributes which pass an instance of to the test case. + /// Initializes a new instance of the class. /// - public abstract class ImageDataAttributeBase : DataAttribute + protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { - protected readonly object[] AdditionalParameters; + this.PixelTypes = pixelTypes; + this.AdditionalParameters = additionalParameters; + this.MemberName = memberName; + } - protected readonly PixelTypes PixelTypes; + /// + /// Gets the member name. + /// + public string MemberName { get; } - 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(); - } + /// + /// Gets or sets the member type. + /// + public Type MemberType { get; set; } - /// - /// Initializes a new instance of the class. - /// - protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) + /// Returns the data to be used to test the theory. + /// The method that is being tested + /// One or more sets of theory data. Each invocation of the test method + /// is represented by a single object array. + public override IEnumerable GetData(MethodInfo testMethod) + { + IEnumerable addedRows = Enumerable.Empty().ToArray(); + if (!string.IsNullOrWhiteSpace(this.MemberName)) { - this.PixelTypes = pixelTypes; - this.AdditionalParameters = additionalParameters; - this.MemberName = memberName; - } + Type type = this.MemberType ?? testMethod.DeclaringType; + Func accessor = this.GetPropertyAccessor(type, this.MemberName) ?? this.GetFieldAccessor(type, this.MemberName); - /// - /// Gets the member name. - /// - public string MemberName { get; } - - /// - /// Gets or sets the member type. - /// - public Type MemberType { get; set; } - - /// Returns the data to be used to test the theory. - /// The method that is being tested - /// One or more sets of theory data. Each invocation of the test method - /// is represented by a single object array. - public override IEnumerable GetData(MethodInfo testMethod) - { - IEnumerable addedRows = Enumerable.Empty().ToArray(); - if (!string.IsNullOrWhiteSpace(this.MemberName)) + if (accessor != null) { - Type type = this.MemberType ?? testMethod.DeclaringType; - Func accessor = this.GetPropertyAccessor(type, this.MemberName) ?? this.GetFieldAccessor(type, this.MemberName); - - if (accessor != null) + object obj = accessor(); + if (obj is IEnumerable memberItems) { - object obj = accessor(); - if (obj is IEnumerable memberItems) + addedRows = memberItems.Select(x => x as object[]); + if (addedRows.Any(x => x == null)) { - addedRows = memberItems.Select(x => x as object[]); - if (addedRows.Any(x => x == null)) - { - addedRows = memberItems.Select(x => new[] { x }); - } + addedRows = memberItems.Select(x => new[] { x }); } } } - - if (!addedRows.Any()) - { - addedRows = new[] { Array.Empty() }; - } - - bool firstIsProvider = this.FirstIsProvider(testMethod); - if (firstIsProvider) - { - return this.InnerGetData(testMethod, addedRows); - } - - return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); } - /// - /// 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) + if (!addedRows.Any()) { - TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); - return dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(TestImageProvider<>); + addedRows = new[] { Array.Empty() }; } - private IEnumerable InnerGetData(MethodInfo testMethod, IEnumerable memberData) + bool firstIsProvider = this.FirstIsProvider(testMethod); + if (firstIsProvider) { - foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes()) - { - Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); - - foreach (object[] originalFactoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) - { - foreach (object[] row in memberData) - { - object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; - Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); - actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; - actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; - - object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) - .Invoke(null, actualFactoryMethodArgs); - - 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); - yield return result; - } - } - } + return this.InnerGetData(testMethod, addedRows); } - /// - /// Generates the collection of method arguments from the given test as a generic enumerable. - /// - /// The test method - /// The test image provider factory type - /// The - protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); - return Enumerable.Repeat(args, 1); - } + return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); + } - /// - /// Generates the collection of method arguments from the given test. - /// - /// The test method - /// The test image provider factory type - /// The - protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - throw new InvalidOperationException("Semi-abstract method"); - } + /// + /// 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(); + return dataType.IsGenericType && dataType.GetGenericTypeDefinition() == typeof(TestImageProvider<>); + } - /// - /// Generates the method name from the given test method. - /// - /// The test method - /// The - protected abstract string GetFactoryMethodName(MethodInfo testMethod); - - /// - /// Gets the field accessor for the given type. - /// - /// The field accessor. - protected Func GetFieldAccessor(Type type, string memberName) + private IEnumerable InnerGetData(MethodInfo testMethod, IEnumerable memberData) + { + foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes()) { - FieldInfo fieldInfo = null; - for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); + + foreach (object[] originalFactoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) { - fieldInfo = reflectionType.GetRuntimeField(memberName); - if (fieldInfo != null) + foreach (object[] row in memberData) { - break; + object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; + Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; + actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; + + object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) + .Invoke(null, actualFactoryMethodArgs); + + 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); + yield return result; } } + } + } - if (fieldInfo is null || !fieldInfo.IsStatic) - { - return null; - } + /// + /// Generates the collection of method arguments from the given test as a generic enumerable. + /// + /// The test method + /// The test image provider factory type + /// The + protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); + return Enumerable.Repeat(args, 1); + } - return () => fieldInfo.GetValue(null); - } + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The + protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + throw new InvalidOperationException("Semi-abstract method"); + } + + /// + /// Generates the method name from the given test method. + /// + /// The test method + /// The + protected abstract string GetFactoryMethodName(MethodInfo testMethod); - /// - /// Gets the property accessor for the given type. - /// - /// The property accessor. - protected Func GetPropertyAccessor(Type type, string memberName) + /// + /// Gets the field accessor for the given type. + /// + /// The field accessor. + protected Func GetFieldAccessor(Type type, string memberName) + { + FieldInfo fieldInfo = null; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - PropertyInfo propInfo = null; - for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + fieldInfo = reflectionType.GetRuntimeField(memberName); + if (fieldInfo != null) { - propInfo = reflectionType.GetRuntimeProperty(memberName); - if (propInfo != null) - { - break; - } + break; } + } + + if (fieldInfo is null || !fieldInfo.IsStatic) + { + return null; + } - if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) + return () => fieldInfo.GetValue(null); + } + + /// + /// Gets the property accessor for the given type. + /// + /// The property accessor. + protected Func GetPropertyAccessor(Type type, string memberName) + { + PropertyInfo propInfo = null; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) + { + propInfo = reflectionType.GetRuntimeProperty(memberName); + if (propInfo != null) { - return null; + break; } + } - return () => propInfo.GetValue(null, null); + if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) + { + return null; } + + return () => propInfo.GetValue(null, null); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs index 5b61ce3327..d82d5cd910 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBasicTestPatternImagesAttribute.cs @@ -1,37 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase { - public class WithBasicTestPatternImagesAttribute : ImageDataAttributeBase + public WithBasicTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : this(null, width, height, pixelTypes, additionalParameters) + { + } + + public WithBasicTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) { - public WithBasicTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : this(null, width, height, pixelTypes, additionalParameters) - { - } - - public WithBasicTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Gets the width - /// - public int Width { get; } - - /// - /// Gets the height - /// - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "BasicTestPattern"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + this.Width = width; + this.Height = height; } + + /// + /// Gets the width + /// + public int Width { get; } + + /// + /// Gets the height + /// + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "BasicTestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs index a96bffc549..e9f57a7abe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs @@ -1,52 +1,50 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Triggers passing instances which produce a blank image of size width * height. +/// One instance will be passed for each the pixel format defined by the pixelTypes parameter +/// +public class WithBlankImagesAttribute : ImageDataAttributeBase { /// - /// Triggers passing instances which produce a blank image of size width * height. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// Triggers passing an that produces a blank image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Triggers passing an that produces a blank image of size width * height /// - public class WithBlankImagesAttribute : ImageDataAttributeBase + /// The member data + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) { - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Triggers passing an that produces a blank image of size width * height - /// - /// The member data - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithBlankImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - 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 }; + this.Width = width; + this.Height = height; } + + 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 }; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs index 6f6c3a82ad..cf472699a7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileAttribute.cs @@ -1,47 +1,45 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Triggers passing instances which read an image from the given file +/// One instance will be passed for each the pixel format defined by the pixelTypes parameter +/// +public class WithFileAttribute : ImageDataAttributeBase { + private readonly string fileName; + /// /// Triggers passing instances which read an image from the given file /// One instance will be passed for each the pixel format defined by the pixelTypes parameter /// - public class WithFileAttribute : ImageDataAttributeBase + /// The name of the file + /// The requested pixel types + /// Additional theory parameter values + public WithFileAttribute(string fileName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) { - private readonly string fileName; - - /// - /// Triggers passing instances which read an image from the given file - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the file - /// The requested pixel types - /// Additional theory parameter values - public WithFileAttribute(string fileName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) - { - this.fileName = fileName; - } + this.fileName = fileName; + } - /// - /// Triggers passing instances which read an image from the given file - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the file - /// The requested pixel types - /// Additional theory parameter values - public WithFileAttribute(string fileName, string dataMemberName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(dataMemberName, pixelTypes, additionalParameters) - { - this.fileName = fileName; - } + /// + /// Triggers passing instances which read an image from the given file + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the file + /// The requested pixel types + /// Additional theory parameter values + public WithFileAttribute(string fileName, string dataMemberName, PixelTypes pixelTypes, params object[] additionalParameters) + : base(dataMemberName, pixelTypes, additionalParameters) + { + this.fileName = fileName; + } - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.fileName }; + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.fileName }; - protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; - } + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs index bad8cd6676..57949e7b11 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs @@ -1,71 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName +/// instances will be passed for each the pixel format defined by the pixelTypes parameter +/// +public class WithFileCollectionAttribute : ImageDataAttributeBase { + private readonly string fileEnumeratorMemberName; + /// /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName /// instances will be passed for each the pixel format defined by the pixelTypes parameter /// - public class WithFileCollectionAttribute : ImageDataAttributeBase + /// The name of the static test class field/property enumerating the files + /// The requested pixel types + /// Additional theory parameter values + public WithFileCollectionAttribute( + string fileEnumeratorMemberName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(null, pixelTypes, additionalParameters) { - private readonly string fileEnumeratorMemberName; - - /// - /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName - /// instances will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the static test class field/property enumerating the files - /// The requested pixel types - /// Additional theory parameter values - public WithFileCollectionAttribute( - string fileEnumeratorMemberName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(null, pixelTypes, additionalParameters) - { - this.fileEnumeratorMemberName = fileEnumeratorMemberName; - } - - /// - /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName - /// instances will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The name of the static test class field/property enumerating the files - /// The member name for enumerating method parameters - /// The requested pixel types - /// Additional theory parameter values - public WithFileCollectionAttribute( - string fileEnumeratorMemberName, - string memberName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberName, pixelTypes, additionalParameters) - { - this.fileEnumeratorMemberName = fileEnumeratorMemberName; - } + this.fileEnumeratorMemberName = fileEnumeratorMemberName; + } - /// - /// Generates the collection of method arguments from the given test. - /// - /// The test method - /// The test image provider factory type - /// The - protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); - accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); + /// + /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName + /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The name of the static test class field/property enumerating the files + /// The member name for enumerating method parameters + /// The requested pixel types + /// Additional theory parameter values + public WithFileCollectionAttribute( + string fileEnumeratorMemberName, + string memberName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberName, pixelTypes, additionalParameters) + { + this.fileEnumeratorMemberName = fileEnumeratorMemberName; + } - var files = (IEnumerable)accessor(); - return files.Select(f => new object[] { f }); - } + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The + protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); + accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType, this.fileEnumeratorMemberName); - /// - protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; + var files = (IEnumerable)accessor(); + return files.Select(f => new object[] { f }); } + + /// + protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index bc373328bd..cd27eaf0ba 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -1,39 +1,36 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +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. +/// +public class WithMemberFactoryAttribute : ImageDataAttributeBase { + private readonly string memberMethodName; + /// /// 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. /// - public class WithMemberFactoryAttribute : ImageDataAttributeBase + /// 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) { - private readonly string memberMethodName; - - /// - /// 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 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) - { - this.memberMethodName = memberMethodName; - } - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - { - return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName }; - } + this.memberMethodName = memberMethodName; + } - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + { + 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 d45ce50ec6..a4149f7356 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -1,168 +1,166 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests -{ - using SixLabors.ImageSharp.PixelFormats; +namespace SixLabors.ImageSharp.Tests; + +using SixLabors.ImageSharp.PixelFormats; +/// +/// Triggers passing instances which produce an image of size width * height filled with the requested color. +/// One instance will be passed for each the pixel format defined by the pixelTypes parameter +/// +public class WithSolidFilledImagesAttribute : WithBlankImagesAttribute +{ /// /// Triggers passing instances which produce an image of size width * height filled with the requested color. /// One instance will be passed for each the pixel format defined by the pixelTypes parameter /// - public class WithSolidFilledImagesAttribute : WithBlankImagesAttribute + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(width, height, r, g, b, 255, pixelTypes, additionalParameters) { - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - byte r, - byte g, - byte b, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(width, height, r, g, b, 255, pixelTypes, additionalParameters) - { - } + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// /// Alpha - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - byte r, - byte g, - byte b, - byte a, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(null, width, height, r, g, b, a, pixelTypes, additionalParameters) - { - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// /// Alpha + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + byte r, + byte g, + byte b, + byte a, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(null, width, height, r, g, b, a, pixelTypes, additionalParameters) + { + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The member data to apply to theories - /// The width of the requested image - /// The height of the requested image - /// Red - /// Green - /// Blue - /// /// Alpha - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - string memberData, - int width, - int height, - byte r, - byte g, - byte b, - byte a, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberData, width, height, pixelTypes, additionalParameters) - { - this.R = r; - this.G = g; - this.B = b; - this.A = a; - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The member data to apply to theories + /// The width of the requested image + /// The height of the requested image + /// Red + /// Green + /// Blue + /// /// Alpha + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + string memberData, + int width, + int height, + byte r, + byte g, + byte b, + byte a, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberData, width, height, pixelTypes, additionalParameters) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The width of the requested image - /// The height of the requested image - /// The referenced color name (name of property in ). - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - int width, - int height, - string colorName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : this(null, width, height, colorName, pixelTypes, additionalParameters) - { - } + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The width of the requested image + /// The height of the requested image + /// The referenced color name (name of property in ). + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + int width, + int height, + string colorName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : this(null, width, height, colorName, pixelTypes, additionalParameters) + { + } - /// - /// Triggers passing instances which produce an image of size width * height filled with the requested color. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter - /// - /// The member data to apply to theories - /// The width of the requested image - /// The height of the requested image - /// The referenced color name (name of property in ). - /// The requested pixel types - /// Additional theory parameter values - public WithSolidFilledImagesAttribute( - string memberData, - int width, - int height, - string colorName, - PixelTypes pixelTypes, - params object[] additionalParameters) - : base(memberData, width, height, pixelTypes, additionalParameters) - { - Guard.NotNull(colorName, nameof(colorName)); + /// + /// Triggers passing instances which produce an image of size width * height filled with the requested color. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + /// The member data to apply to theories + /// The width of the requested image + /// The height of the requested image + /// The referenced color name (name of property in ). + /// The requested pixel types + /// Additional theory parameter values + public WithSolidFilledImagesAttribute( + string memberData, + int width, + int height, + string colorName, + PixelTypes pixelTypes, + params object[] additionalParameters) + : base(memberData, width, height, pixelTypes, additionalParameters) + { + Guard.NotNull(colorName, nameof(colorName)); - Rgba32 c = TestUtils.GetPixelOfNamedColor(colorName); - this.R = c.R; - this.G = c.G; - this.B = c.B; - this.A = c.A; - } + Rgba32 c = TestUtils.GetPixelOfNamedColor(colorName); + this.R = c.R; + this.G = c.G; + this.B = c.B; + this.A = c.A; + } - /// - /// Gets the red component. - /// - public byte R { get; } + /// + /// Gets the red component. + /// + public byte R { get; } - /// - /// Gets the green component. - /// - public byte G { get; } + /// + /// Gets the green component. + /// + public byte G { get; } - /// - /// Gets the blue component. - /// - public byte B { get; } + /// + /// Gets the blue component. + /// + public byte B { get; } - /// - /// Gets the alpha component. - /// - public byte A { get; } + /// + /// Gets the alpha component. + /// + public byte A { get; } - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) - => new object[] { this.Width, this.Height, this.R, this.G, this.B, this.A }; + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) + => new object[] { this.Width, this.Height, this.R, this.G, this.B, this.A }; - protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; - } + protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs index 46025e7cfe..4b016d5eae 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs @@ -1,56 +1,54 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Triggers passing instances which produce a blank image of size width * height. +/// One instance will be passed for each the pixel format defined by the pixelTypes parameter +/// +public class WithTestPatternImagesAttribute : ImageDataAttributeBase { /// - /// Triggers passing instances which produce a blank image of size width * height. - /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// Initializes a new instance of the class. /// - public class WithTestPatternImagesAttribute : ImageDataAttributeBase + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : this(null, width, height, pixelTypes, additionalParameters) { - /// - /// Initializes a new instance of the class. - /// - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : this(null, width, height, pixelTypes, additionalParameters) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The member data to apply to theories - /// The required width - /// The required height - /// The requested parameter - /// Additional theory parameter values - public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) - : base(memberData, pixelTypes, additionalParameters) - { - this.Width = width; - this.Height = height; - } - - /// - /// Gets the width - /// - public int Width { get; } - - /// - /// Gets the height - /// - public int Height { get; } - - protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; - - protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } + + /// + /// Initializes a new instance of the class. + /// + /// The member data to apply to theories + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(string memberData, int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(memberData, pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + /// + /// Gets the width + /// + public int Width { get; } + + /// + /// Gets the height + /// + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs index a1308ab46f..d161b80197 100644 --- a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -1,100 +1,95 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; -using System.IO; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +/// +/// RemoteExecutor can only execute static methods, which can only consume string arguments, +/// because data is being passed on command line interface. This utility allows serialization +/// of types to strings. +/// +internal class BasicSerializer : IXunitSerializationInfo { - /// - /// RemoteExecutor can only execute static methods, which can only consume string arguments, - /// because data is being passed on command line interface. This utility allows serialization - /// of types to strings. - /// - internal class BasicSerializer : IXunitSerializationInfo - { - private readonly Dictionary map = new Dictionary(); + private readonly Dictionary map = new Dictionary(); - public const char Separator = ':'; + public const char Separator = ':'; - private string DumpToString(Type type) + private string DumpToString(Type type) + { + using var ms = new MemoryStream(); + using var writer = new StreamWriter(ms); + writer.WriteLine(type.FullName); + foreach (KeyValuePair kv in this.map) { - using var ms = new MemoryStream(); - using var writer = new StreamWriter(ms); - writer.WriteLine(type.FullName); - foreach (KeyValuePair kv in this.map) - { - writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); - } - - writer.Flush(); - byte[] data = ms.ToArray(); - return System.Convert.ToBase64String(data); + writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); } - private Type LoadDump(string dump) - { - byte[] data = System.Convert.FromBase64String(dump); - - 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()) - { - string[] kv = s.Split(Separator); - this.map[kv[0]] = kv[1]; - } - - return type; - } + writer.Flush(); + byte[] data = ms.ToArray(); + return System.Convert.ToBase64String(data); + } - public static string Serialize(IXunitSerializable serializable) + private Type LoadDump(string dump) + { + byte[] data = System.Convert.FromBase64String(dump); + + 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()) { - var serializer = new BasicSerializer(); - serializable.Serialize(serializer); - return serializer.DumpToString(serializable.GetType()); + string[] kv = s.Split(Separator); + this.map[kv[0]] = kv[1]; } - public static T Deserialize(string dump) - where T : IXunitSerializable - { - var serializer = new BasicSerializer(); - Type type = serializer.LoadDump(dump); + return type; + } - var result = (T)Activator.CreateInstance(type); - result.Deserialize(serializer); - return result; - } + public static string Serialize(IXunitSerializable serializable) + { + var serializer = new BasicSerializer(); + serializable.Serialize(serializer); + return serializer.DumpToString(serializable.GetType()); + } - public void AddValue(string key, object value, Type type = null) - { - Guard.NotNull(key, nameof(key)); - if (value == null) - { - return; - } + public static T Deserialize(string dump) + where T : IXunitSerializable + { + var serializer = new BasicSerializer(); + Type type = serializer.LoadDump(dump); - type ??= value.GetType(); + var result = (T)Activator.CreateInstance(type); + result.Deserialize(serializer); + return result; + } - this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); + public void AddValue(string key, object value, Type type = null) + { + Guard.NotNull(key, nameof(key)); + if (value == null) + { + return; } - public object GetValue(string key, Type type) - { - Guard.NotNull(key, nameof(key)); + type ??= value.GetType(); + + this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); + } - if (!this.map.TryGetValue(key, out string str)) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } + public object GetValue(string key, Type type) + { + Guard.NotNull(key, nameof(key)); - return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); + if (!this.map.TryGetValue(key, out string str)) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; } - public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); + return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); } + + public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs index 804457aa0e..963c3c7835 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs @@ -1,25 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests.TestUtilities -{ - using System; +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +using System; - public static class ByteArrayUtility +public static class ByteArrayUtility +{ + public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian) { - public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian) + if (isLittleEndian != BitConverter.IsLittleEndian) + { + var reversedBytes = new byte[bytes.Length]; + Array.Copy(bytes, reversedBytes, bytes.Length); + Array.Reverse(reversedBytes); + return reversedBytes; + } + else { - if (isLittleEndian != BitConverter.IsLittleEndian) - { - var reversedBytes = new byte[bytes.Length]; - Array.Copy(bytes, reversedBytes, bytes.Length); - Array.Reverse(reversedBytes); - return reversedBytes; - } - else - { - return bytes; - } + return bytes; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs index 4412d0beb5..45e1425c75 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs @@ -1,39 +1,38 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +using System; +using System.Collections.Generic; + +public class ByteBuffer { - using System; - using System.Collections.Generic; + private readonly List bytes = new List(); + private readonly bool isLittleEndian; + + public ByteBuffer(bool isLittleEndian) + { + this.isLittleEndian = isLittleEndian; + } + + public void AddByte(byte value) + { + this.bytes.Add(value); + } + + public void AddUInt16(ushort value) + { + this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); + } + + public void AddUInt32(uint value) + { + this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); + } - public class ByteBuffer + public byte[] ToArray() { - private readonly List bytes = new List(); - private readonly bool isLittleEndian; - - public ByteBuffer(bool isLittleEndian) - { - this.isLittleEndian = isLittleEndian; - } - - public void AddByte(byte value) - { - this.bytes.Add(value); - } - - public void AddUInt16(ushort value) - { - this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); - } - - public void AddUInt32(uint value) - { - this.bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(this.isLittleEndian)); - } - - public byte[] ToArray() - { - return this.bytes.ToArray(); - } + return this.bytes.ToArray(); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index ffe18a919d..ad5aa4769f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -1,407 +1,404 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Diagnostics; using Microsoft.DotNet.RemoteExecutor; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +/// +/// Allows the testing against specific feature sets. +/// +public static class FeatureTestRunner { + private static readonly char[] SplitChars = new[] { ',', ' ' }; + /// - /// Allows the testing against specific feature sets. + /// Allows the deserialization of parameters passed to the feature test. + /// + /// + /// This is required because does not allow + /// marshalling of fields so we cannot pass a wrapped + /// allowing automatic deserialization. + /// + /// /// - public static class FeatureTestRunner - { - private static readonly char[] SplitChars = new[] { ',', ' ' }; + /// The type to deserialize to. + /// The string value to deserialize. + /// The value. + public static T DeserializeForXunit(string value) + where T : IXunitSerializable + => BasicSerializer.Deserialize(value); - /// - /// Allows the deserialization of parameters passed to the feature test. - /// - /// - /// This is required because does not allow - /// marshalling of fields so we cannot pass a wrapped - /// allowing automatic deserialization. - /// - /// - /// - /// The type to deserialize to. - /// The string value to deserialize. - /// The value. - public static T DeserializeForXunit(string value) - where T : IXunitSerializable - => BasicSerializer.Deserialize(value); + /// + /// Allows the deserialization of types implementing + /// passed to the feature test. + /// + /// The string value to deserialize. + /// The value. + public static T Deserialize(string value) + where T : IConvertible + => (T)Convert.ChangeType(value, typeof(T)); - /// - /// Allows the deserialization of types implementing - /// passed to the feature test. - /// - /// The string value to deserialize. - /// The value. - public static T Deserialize(string value) - where T : IConvertible - => (T)Convert.ChangeType(value, typeof(T)); + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics) + { + if (!RemoteExecutor.IsSupported) + { + return; + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) { - if (!RemoteExecutor.IsSupported) + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - return; - } + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + RemoteExecutor.Invoke( + action, + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - - RemoteExecutor.Invoke( - action, - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(); - } + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// - /// The test action to run. - /// The parameter passed will be a string representing the currently testing . - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics) + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// + /// The test action to run. + /// The parameter passed will be a string representing the currently testing . + /// The intrinsics features. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics) + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - intrinsic.Key.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(intrinsic.Key.ToString()); - } + RemoteExecutor.Invoke( + action, + intrinsic.Key.ToString(), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(intrinsic.Key.ToString()); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T serializable) - where T : IXunitSerializable + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + /// The value to pass as a parameter to the test action. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics, + T serializable) + where T : IXunitSerializable + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(serializable), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(serializable)); - } + RemoteExecutor.Invoke( + action, + BasicSerializer.Serialize(serializable), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(BasicSerializer.Serialize(serializable)); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T serializable) - where T : IXunitSerializable + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + /// The value to pass as a parameter to the test action. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics, + T serializable) + where T : IXunitSerializable + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(serializable), - intrinsic.Key.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(serializable), intrinsic.Key.ToString()); - } + RemoteExecutor.Invoke( + action, + BasicSerializer.Serialize(serializable), + intrinsic.Key.ToString(), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(BasicSerializer.Serialize(serializable), intrinsic.Key.ToString()); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The intrinsics features. - /// The value to pass as a parameter to the test action. - /// The second value to pass as a parameter to the test action. - public static void RunWithHwIntrinsicsFeature( - Action action, - HwIntrinsics intrinsics, - T arg1, - T2 arg2) - where T : IXunitSerializable - where T2 : IXunitSerializable + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The intrinsics features. + /// The value to pass as a parameter to the test action. + /// The second value to pass as a parameter to the test action. + public static void RunWithHwIntrinsicsFeature( + Action action, + HwIntrinsics intrinsics, + T arg1, + T2 arg2) + where T : IXunitSerializable + where T2 : IXunitSerializable + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - BasicSerializer.Serialize(arg1), - BasicSerializer.Serialize(arg2), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(BasicSerializer.Serialize(arg1), BasicSerializer.Serialize(arg2)); - } + RemoteExecutor.Invoke( + action, + BasicSerializer.Serialize(arg1), + BasicSerializer.Serialize(arg2), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(BasicSerializer.Serialize(arg1), BasicSerializer.Serialize(arg2)); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The value to pass as a parameter to the test action. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - T serializable, - HwIntrinsics intrinsics) - where T : IConvertible + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The value to pass as a parameter to the test action. + /// The intrinsics features. + public static void RunWithHwIntrinsicsFeature( + Action action, + T serializable, + HwIntrinsics intrinsics) + where T : IConvertible + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - serializable.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(serializable.ToString()); - } + RemoteExecutor.Invoke( + action, + serializable.ToString(), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(serializable.ToString()); } } + } - /// - /// Runs the given test within an environment - /// where the given features. - /// - /// The test action to run. - /// The value to pass as a parameter #0 to the test action. - /// The value to pass as a parameter #1 to the test action. - /// The intrinsics features. - public static void RunWithHwIntrinsicsFeature( - Action action, - T arg0, - T arg1, - HwIntrinsics intrinsics) - where T : IConvertible + /// + /// Runs the given test within an environment + /// where the given features. + /// + /// The test action to run. + /// The value to pass as a parameter #0 to the test action. + /// The value to pass as a parameter #1 to the test action. + /// The intrinsics features. + public static void RunWithHwIntrinsicsFeature( + Action action, + T arg0, + T arg1, + HwIntrinsics intrinsics) + where T : IConvertible + { + if (!RemoteExecutor.IsSupported) { - if (!RemoteExecutor.IsSupported) - { - return; - } + return; + } - foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + foreach (KeyValuePair intrinsic in intrinsics.ToFeatureKeyValueCollection()) + { + var processStartInfo = new ProcessStartInfo(); + if (intrinsic.Key != HwIntrinsics.AllowAll) { - var processStartInfo = new ProcessStartInfo(); - if (intrinsic.Key != HwIntrinsics.AllowAll) - { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; - RemoteExecutor.Invoke( - action, - arg0.ToString(), - arg1.ToString(), - new RemoteInvokeOptions - { - StartInfo = processStartInfo - }) - .Dispose(); - } - else - { - // Since we are running using the default architecture there is no - // point creating the overhead of running the action in a separate process. - action(arg0.ToString(), arg1.ToString()); - } + RemoteExecutor.Invoke( + action, + arg0.ToString(), + arg1.ToString(), + new RemoteInvokeOptions + { + StartInfo = processStartInfo + }) + .Dispose(); + } + else + { + // Since we are running using the default architecture there is no + // point creating the overhead of running the action in a separate process. + action(arg0.ToString(), arg1.ToString()); } } + } - internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics) + internal static Dictionary ToFeatureKeyValueCollection(this HwIntrinsics intrinsics) + { + // Loop through and translate the given values into COMPlus equivaluents + var features = new Dictionary(); + foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) { - // Loop through and translate the given values into COMPlus equivaluents - var features = new Dictionary(); - foreach (string intrinsic in intrinsics.ToString("G").Split(SplitChars, StringSplitOptions.RemoveEmptyEntries)) + var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic); + switch (intrinsic) { - var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic); - switch (intrinsic) - { - case nameof(HwIntrinsics.AllowAll): + case nameof(HwIntrinsics.AllowAll): - // Not a COMPlus value. We filter in calling method. - features.Add(key, nameof(HwIntrinsics.AllowAll)); - break; + // Not a COMPlus value. We filter in calling method. + features.Add(key, nameof(HwIntrinsics.AllowAll)); + break; - default: - features.Add(key, intrinsic.Replace("Disable", "Enable")); - break; - } + default: + features.Add(key, intrinsic.Replace("Disable", "Enable")); + break; } - - return features; } + + return features; } +} - /// - /// See - /// - /// ends up impacting all SIMD support(including System.Numerics) - /// but not things like , , and . - /// - /// - [Flags] +/// +/// See +/// +/// ends up impacting all SIMD support(including System.Numerics) +/// but not things like , , and . +/// +/// +[Flags] #pragma warning disable RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute). - public enum HwIntrinsics +public enum HwIntrinsics #pragma warning restore RCS1135 // Declare enum member with zero value (when enum has FlagsAttribute). - { - // Use flags so we can pass multiple values without using params. - // Don't base on 0 or use inverse for All as that doesn't translate to string values. - DisableHWIntrinsic = 1 << 0, - DisableSSE = 1 << 1, - DisableSSE2 = 1 << 2, - DisableAES = 1 << 3, - DisablePCLMULQDQ = 1 << 4, - DisableSSE3 = 1 << 5, - DisableSSSE3 = 1 << 6, - DisableSSE41 = 1 << 7, - DisableSSE42 = 1 << 8, - DisablePOPCNT = 1 << 9, - DisableAVX = 1 << 10, - DisableFMA = 1 << 11, - DisableAVX2 = 1 << 12, - DisableBMI1 = 1 << 13, - DisableBMI2 = 1 << 14, - DisableLZCNT = 1 << 15, - AllowAll = 1 << 16 - } +{ + // Use flags so we can pass multiple values without using params. + // Don't base on 0 or use inverse for All as that doesn't translate to string values. + DisableHWIntrinsic = 1 << 0, + DisableSSE = 1 << 1, + DisableSSE2 = 1 << 2, + DisableAES = 1 << 3, + DisablePCLMULQDQ = 1 << 4, + DisableSSE3 = 1 << 5, + DisableSSSE3 = 1 << 6, + DisableSSE41 = 1 << 7, + DisableSSE42 = 1 << 8, + DisablePOPCNT = 1 << 9, + DisableAVX = 1 << 10, + DisableFMA = 1 << 11, + DisableAVX2 = 1 << 12, + DisableBMI1 = 1 << 13, + DisableBMI2 = 1 << 14, + DisableLZCNT = 1 << 15, + AllowAll = 1 << 16 } diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs index 215ac30317..2a7b42f6b4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -1,21 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; +namespace SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.TestUtilities +public class GraphicsOptionsComparer : IEqualityComparer { - public class GraphicsOptionsComparer : IEqualityComparer + public bool Equals(GraphicsOptions x, GraphicsOptions y) { - public bool Equals(GraphicsOptions x, GraphicsOptions y) - { - return x.AlphaCompositionMode == y.AlphaCompositionMode - && x.Antialias == y.Antialias - && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth - && x.BlendPercentage == y.BlendPercentage - && x.ColorBlendingMode == y.ColorBlendingMode; - } - - public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); + return x.AlphaCompositionMode == y.AlphaCompositionMode + && x.Antialias == y.Antialias + && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth + && x.BlendPercentage == y.BlendPercentage + && x.ColorBlendingMode == y.ColorBlendingMode; } + + public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index 42202d18cd..87725aec93 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,60 +1,57 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public class ExactImageComparer : ImageComparer { - public class ExactImageComparer : ImageComparer - { - public static ExactImageComparer Instance { get; } = new ExactImageComparer(); + public static ExactImageComparer Instance { get; } = new ExactImageComparer(); - public override ImageSimilarityReport CompareImagesOrFrames( - ImageFrame expected, - ImageFrame actual) + public override ImageSimilarityReport CompareImagesOrFrames( + ImageFrame expected, + ImageFrame actual) + { + if (expected.Size() != actual.Size()) { - if (expected.Size() != actual.Size()) - { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); - } + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - int width = actual.Width; + 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]; + // 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]; - var differences = new List(); - Configuration configuration = expected.GetConfiguration(); - Buffer2D expectedBuffer = expected.PixelBuffer; - Buffer2D actualBuffer = actual.PixelBuffer; + var differences = new List(); + Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + for (int y = 0; y < actual.Height; y++) + { + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); + + PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); - PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); + for (int x = 0; x < width; x++) + { + Rgba64 aPixel = aBuffer[x]; + Rgba64 bPixel = bBuffer[x]; - for (int x = 0; x < width; x++) + if (aPixel != bPixel) { - Rgba64 aPixel = aBuffer[x]; - Rgba64 bPixel = bBuffer[x]; - - if (aPixel != bPixel) - { - var diff = new PixelDifference(new Point(x, y), aPixel, bPixel); - differences.Add(diff); - } + var diff = new PixelDifference(new Point(x, y), aPixel, bPixel); + differences.Add(diff); } } - - return new ImageSimilarityReport(expected, actual, differences); } + + return new ImageSimilarityReport(expected, actual, differences); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 8857471d03..d6cb24fd62 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -1,71 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException { - public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException + public ImageSimilarityReport[] Reports { get; } + + public ImageDifferenceIsOverThresholdException(IEnumerable reports) + : base("Image difference is over threshold!" + StringifyReports(reports)) { - public ImageSimilarityReport[] Reports { get; } + this.Reports = reports.ToArray(); + } - public ImageDifferenceIsOverThresholdException(IEnumerable reports) - : base("Image difference is over threshold!" + StringifyReports(reports)) - { - this.Reports = reports.ToArray(); - } + private static string StringifyReports(IEnumerable reports) + { + var sb = new StringBuilder(); - private static string StringifyReports(IEnumerable reports) - { - var sb = new StringBuilder(); + sb.Append(Environment.NewLine); - sb.Append(Environment.NewLine); + sb.AppendFormat("Test Environment OS : {0}", GetEnvironmentName()); + sb.Append(Environment.NewLine); - sb.AppendFormat("Test Environment OS : {0}", GetEnvironmentName()); - sb.Append(Environment.NewLine); + sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI); + 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); - sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); - sb.Append(Environment.NewLine); + sb.AppendFormat("Test Environment is Mono : {0}", TestEnvironment.IsMono); + sb.Append(Environment.NewLine); - sb.AppendFormat("Test Environment is Mono : {0}", TestEnvironment.IsMono); + int i = 0; + foreach (ImageSimilarityReport r in reports) + { + sb.Append("Report ImageFrame {i}: "); + sb.Append(r); sb.Append(Environment.NewLine); + i++; + } + + return sb.ToString(); + } - int i = 0; - foreach (ImageSimilarityReport r in reports) - { - sb.Append("Report ImageFrame {i}: "); - sb.Append(r); - sb.Append(Environment.NewLine); - i++; - } + private static string GetEnvironmentName() + { + if (TestEnvironment.IsMacOS) + { + return "MacOS"; + } - return sb.ToString(); + if (TestEnvironment.IsMacOS) + { + return "Linux"; } - private static string GetEnvironmentName() + if (TestEnvironment.IsWindows) { - if (TestEnvironment.IsMacOS) - { - return "MacOS"; - } - - if (TestEnvironment.IsMacOS) - { - return "Linux"; - } - - if (TestEnvironment.IsWindows) - { - return "Windows"; - } - - return "Unknown"; + return "Windows"; } + + return "Unknown"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs index 8879d6c175..5c6318971f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -1,19 +1,18 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public class ImageDimensionsMismatchException : ImagesSimilarityException { - public class ImageDimensionsMismatchException : ImagesSimilarityException + public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) + : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") { - public ImageDimensionsMismatchException(Size expectedSize, Size actualSize) - : base($"The image dimensions {actualSize} do not match the expected {expectedSize}!") - { - this.ExpectedSize = expectedSize; - this.ActualSize = actualSize; - } + this.ExpectedSize = expectedSize; + this.ActualSize = actualSize; + } - public Size ExpectedSize { get; } + public Size ExpectedSize { get; } - public Size ActualSize { 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 21588f6c9b..b5fb101e59 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,15 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison -{ - using System; +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using System; - public class ImagesSimilarityException : Exception +public class ImagesSimilarityException : Exception +{ + public ImagesSimilarityException(string message) + : base(message) { - public ImagesSimilarityException(string message) - : base(message) - { - } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index d2750c31c5..9a022a8734 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -1,141 +1,137 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public abstract class ImageComparer { - public abstract class ImageComparer - { - public static ImageComparer Exact { get; } = Tolerant(0, 0); - - /// - /// 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) => - new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); - - /// - /// 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 : unmanaged, IPixel - where TPixelB : unmanaged, IPixel; - } + public static ImageComparer Exact { get; } = Tolerant(0, 0); + + /// + /// 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) => + new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold); + + /// + /// 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 : unmanaged, IPixel + where TPixelB : unmanaged, IPixel; +} - public static class ImageComparerExtensions +public static class ImageComparerExtensions +{ + public static ImageSimilarityReport CompareImagesOrFrames( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); + + public static IEnumerable> CompareImages( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { - public static ImageSimilarityReport CompareImagesOrFrames( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); - - public static IEnumerable> CompareImages( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + var result = new List>(); + + if (expected.Frames.Count != actual.Frames.Count) { - var result = new List>(); + throw new Exception("Frame count does not match!"); + } - if (expected.Frames.Count != actual.Frames.Count) + for (int i = 0; i < expected.Frames.Count; i++) + { + ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]); + if (!report.IsEmpty) { - throw new Exception("Frame count does not match!"); + result.Add(report); } + } - for (int i = 0; i < expected.Frames.Count; i++) - { - ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]); - if (!report.IsEmpty) - { - result.Add(report); - } - } + return result; + } - return result; + public static void VerifySimilarity( + this ImageComparer comparer, + Image expected, + Image actual) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel + { + if (expected.Size() != actual.Size()) + { + throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); } - public static void VerifySimilarity( - this ImageComparer comparer, - Image expected, - Image actual) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + if (expected.Frames.Count != actual.Frames.Count) { - if (expected.Size() != actual.Size()) - { - throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); - } - - if (expected.Frames.Count != actual.Frames.Count) - { - throw new ImagesSimilarityException("Image frame count does not match!"); - } + throw new ImagesSimilarityException("Image frame count does not match!"); + } - IEnumerable reports = comparer.CompareImages(expected, actual); - if (reports.Any()) - { - throw new ImageDifferenceIsOverThresholdException(reports); - } + IEnumerable reports = comparer.CompareImages(expected, actual); + if (reports.Any()) + { + throw new ImageDifferenceIsOverThresholdException(reports); } + } - public static void VerifySimilarityIgnoreRegion( - this ImageComparer comparer, - Image expected, - Image actual, - Rectangle ignoredRegion) - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel + public static void VerifySimilarityIgnoreRegion( + this ImageComparer comparer, + Image expected, + Image actual, + Rectangle ignoredRegion) + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel + { + if (expected.Size() != actual.Size()) { - if (expected.Size() != actual.Size()) - { - throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); - } + throw new ImageDimensionsMismatchException(expected.Size(), actual.Size()); + } - if (expected.Frames.Count != actual.Frames.Count) - { - throw new ImagesSimilarityException("Image frame count does not match!"); - } + if (expected.Frames.Count != actual.Frames.Count) + { + throw new ImagesSimilarityException("Image frame count does not match!"); + } - IEnumerable> reports = comparer.CompareImages(expected, actual); - if (reports.Any()) + IEnumerable> reports = comparer.CompareImages(expected, actual); + if (reports.Any()) + { + var cleanedReports = new List>(reports.Count()); + foreach (ImageSimilarityReport r in reports) { - var cleanedReports = new List>(reports.Count()); - foreach (ImageSimilarityReport r in reports) + IEnumerable outsideChanges = r.Differences.Where( + x => + !(ignoredRegion.X <= x.Position.X + && x.Position.X <= ignoredRegion.Right + && ignoredRegion.Y <= x.Position.Y + && x.Position.Y <= ignoredRegion.Bottom)); + + if (outsideChanges.Any()) { - IEnumerable outsideChanges = r.Differences.Where( - x => - !(ignoredRegion.X <= x.Position.X - && x.Position.X <= ignoredRegion.Right - && ignoredRegion.Y <= x.Position.Y - && x.Position.Y <= ignoredRegion.Bottom)); - - if (outsideChanges.Any()) - { - cleanedReports.Add(new ImageSimilarityReport(r.ExpectedImage, r.ActualImage, outsideChanges, null)); - } + cleanedReports.Add(new ImageSimilarityReport(r.ExpectedImage, r.ActualImage, outsideChanges, null)); } + } - if (cleanedReports.Count > 0) - { - throw new ImageDifferenceIsOverThresholdException(cleanedReports); - } + if (cleanedReports.Count > 0) + { + throw new ImageDifferenceIsOverThresholdException(cleanedReports); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index 4ca96f8391..f5c70b0885 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -1,67 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using ImageMagick; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public static class ImageComparingUtils { - public static class ImageComparingUtils + public static void CompareWithReferenceDecoder( + TestImageProvider provider, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { - public static void CompareWithReferenceDecoder( - TestImageProvider provider, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } - - var testFile = TestFile.Create(path); - using Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); - if (useExactComparer) - { - ImageComparer.Exact.VerifySimilarity(magickImage, image); - } - else - { - ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); - } + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); } - public static Image DecodeWithMagick(FileInfo fileInfo) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + var testFile = TestFile.Create(path); + using Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); + if (useExactComparer) { - Configuration configuration = Configuration.Default.Clone(); - configuration.PreferContiguousImageBuffers = true; - using (var magickImage = new MagickImage(fileInfo)) - { - magickImage.AutoOrient(); - var result = new Image(configuration, magickImage.Width, magickImage.Height); + ImageComparer.Exact.VerifySimilarity(magickImage, image); + } + else + { + ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); + } + } - Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); + public static Image DecodeWithMagick(FileInfo fileInfo) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; + using (var magickImage = new MagickImage(fileInfo)) + { + magickImage.AutoOrient(); + var result = new Image(configuration, magickImage.Width, magickImage.Height); - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels.Span, - resultPixels.Length); - } + using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - return result; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels.Span, + resultPixels.Length); } + + return result; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 3beb6612d1..8f441cc573 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,110 +1,106 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public class ImageSimilarityReport { - public class ImageSimilarityReport + protected ImageSimilarityReport( + object expectedImage, + object actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) { - protected ImageSimilarityReport( - object expectedImage, - object actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) - { - this.ExpectedImage = expectedImage; - this.ActualImage = actualImage; - this.TotalNormalizedDifference = totalNormalizedDifference; - this.Differences = differences.ToArray(); - } + this.ExpectedImage = expectedImage; + this.ActualImage = actualImage; + this.TotalNormalizedDifference = totalNormalizedDifference; + this.Differences = differences.ToArray(); + } - public object ExpectedImage { get; } + public object ExpectedImage { get; } - public object ActualImage { get; } + public object ActualImage { get; } - // TODO: This should not be a nullable value! - public float? TotalNormalizedDifference { get; } + // TODO: This should not be a nullable value! + public float? TotalNormalizedDifference { get; } - public string DifferencePercentageString + public string DifferencePercentageString + { + get { - get + if (!this.TotalNormalizedDifference.HasValue) + { + return "?"; + } + else if (this.TotalNormalizedDifference == 0) + { + return "0%"; + } + else { - if (!this.TotalNormalizedDifference.HasValue) - { - return "?"; - } - else if (this.TotalNormalizedDifference == 0) - { - return "0%"; - } - else - { - return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; - } + return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; } } + } - public PixelDifference[] Differences { get; } + public PixelDifference[] Differences { get; } - public bool IsEmpty => this.Differences.Length == 0; + public bool IsEmpty => this.Differences.Length == 0; - public override string ToString() - { - return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); - } + public override string ToString() + { + return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference(); + } - private string PrintDifference() + private string PrintDifference() + { + var sb = new StringBuilder(); + if (this.TotalNormalizedDifference.HasValue) { - var sb = new StringBuilder(); - if (this.TotalNormalizedDifference.HasValue) - { - sb.AppendLine(); - sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); - } - - int max = Math.Min(5, this.Differences.Length); + sb.AppendLine(); + sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); + } - for (int i = 0; i < max; i++) - { - sb.Append(this.Differences[i]); - if (i < max - 1) - { - sb.AppendFormat(";{0}", Environment.NewLine); - } - } + int max = Math.Min(5, this.Differences.Length); - if (this.Differences.Length >= 5) + for (int i = 0; i < max; i++) + { + sb.Append(this.Differences[i]); + if (i < max - 1) { - sb.Append("..."); + sb.AppendFormat(";{0}", Environment.NewLine); } + } - return sb.ToString(); + if (this.Differences.Length >= 5) + { + sb.Append("..."); } + + return sb.ToString(); } +} - public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : unmanaged, IPixel - where TPixelB : unmanaged, IPixel +public class ImageSimilarityReport : ImageSimilarityReport + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel +{ + public ImageSimilarityReport( + ImageFrame expectedImage, + ImageFrame actualImage, + IEnumerable differences, + float? totalNormalizedDifference = null) + : base(expectedImage, actualImage, differences, totalNormalizedDifference) { - public ImageSimilarityReport( - ImageFrame expectedImage, - ImageFrame actualImage, - IEnumerable differences, - float? totalNormalizedDifference = null) - : base(expectedImage, actualImage, differences, totalNormalizedDifference) - { - } + } - public static ImageSimilarityReport Empty => - new ImageSimilarityReport(null, null, Enumerable.Empty(), 0f); + public static ImageSimilarityReport Empty => + new ImageSimilarityReport(null, null, Enumerable.Empty(), 0f); - public new ImageFrame ExpectedImage => (ImageFrame)base.ExpectedImage; + public new ImageFrame ExpectedImage => (ImageFrame)base.ExpectedImage; - public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; - } + public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs index aeac2e624e..8b3fdf6d1b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -3,45 +3,44 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public readonly struct PixelDifference { - public readonly struct PixelDifference + public PixelDifference( + Point position, + int redDifference, + int greenDifference, + int blueDifference, + int alphaDifference) { - public PixelDifference( - Point position, - int redDifference, - int greenDifference, - int blueDifference, - int alphaDifference) - { - this.Position = position; - this.RedDifference = redDifference; - this.GreenDifference = greenDifference; - this.BlueDifference = blueDifference; - this.AlphaDifference = alphaDifference; - } - - public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) - : this( - position, - actual.R - expected.R, - actual.G - expected.G, - actual.B - expected.B, - actual.A - expected.A) - { - } - - 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})]"; + this.Position = position; + this.RedDifference = redDifference; + this.GreenDifference = greenDifference; + this.BlueDifference = blueDifference; + this.AlphaDifference = alphaDifference; } + + public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) + : this( + position, + actual.R - expected.R, + actual.G - expected.G, + actual.B - expected.B, + actual.A - expected.A) + { + } + + 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})]"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index fb9aa64946..793d7af33e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,125 +1,122 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +public class TolerantImageComparer : ImageComparer { - public class TolerantImageComparer : ImageComparer + // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit + // 257 = (1 / 255) * 65535. + public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); + + /// + /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. + /// + /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. + public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) { - // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit - // 257 = (1 / 255) * 65535. - public const float DefaultImageThreshold = 257F / (100 * 100 * 65535); - - /// - /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. - /// - /// The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0) - { - Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); + Guard.MustBeGreaterThanOrEqualTo(imageThreshold, 0, nameof(imageThreshold)); - this.ImageThreshold = imageThreshold; - this.PerPixelManhattanThreshold = perPixelManhattanThreshold; - } + this.ImageThreshold = imageThreshold; + this.PerPixelManhattanThreshold = perPixelManhattanThreshold; + } - /// - /// - /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. - /// Examples of percentage differences on a single pixel: - /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel - /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel - /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel - /// - /// - /// The total differences is the sum of all pixel differences normalized by image dimensions! - /// The individual distances are calculated using the Manhattan function: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image - /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image - /// - /// - public float ImageThreshold { get; } - - /// - /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. - /// For an individual pixel pair the value is the Manhattan distance of pixels: - /// - /// https://en.wikipedia.org/wiki/Taxicab_geometry - /// - /// - public int PerPixelManhattanThreshold { get; } - - public override ImageSimilarityReport CompareImagesOrFrames(ImageFrame expected, ImageFrame actual) + /// + /// + /// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535. + /// Examples of percentage differences on a single pixel: + /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel + /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel + /// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel + /// + /// + /// The total differences is the sum of all pixel differences normalized by image dimensions! + /// The individual distances are calculated using the Manhattan function: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image + /// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image + /// + /// + public float ImageThreshold { get; } + + /// + /// Gets the threshold of the individual pixels before they accumulate towards the overall difference. + /// For an individual pixel pair the value is the Manhattan distance of pixels: + /// + /// https://en.wikipedia.org/wiki/Taxicab_geometry + /// + /// + public int PerPixelManhattanThreshold { get; } + + public override ImageSimilarityReport CompareImagesOrFrames(ImageFrame expected, ImageFrame actual) + { + if (expected.Size() != actual.Size()) { - if (expected.Size() != actual.Size()) - { - throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); - } + throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!"); + } - int width = actual.Width; + 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]; + // 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]; - float totalDifference = 0F; + float totalDifference = 0F; - var differences = new List(); - Configuration configuration = expected.GetConfiguration(); - Buffer2D expectedBuffer = expected.PixelBuffer; - Buffer2D actualBuffer = actual.PixelBuffer; + var differences = new List(); + Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; - for (int y = 0; y < actual.Height; y++) - { - Span aSpan = expectedBuffer.DangerousGetRowSpan(y); - Span bSpan = actualBuffer.DangerousGetRowSpan(y); + for (int y = 0; y < actual.Height; y++) + { + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); - PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); + PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); + PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); - for (int x = 0; x < width; x++) - { - int d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); + for (int x = 0; x < width; x++) + { + int d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]); - if (d > this.PerPixelManhattanThreshold) - { - var diff = new PixelDifference(new Point(x, y), aBuffer[x], bBuffer[x]); - differences.Add(diff); + if (d > this.PerPixelManhattanThreshold) + { + var diff = new PixelDifference(new Point(x, y), aBuffer[x], bBuffer[x]); + differences.Add(diff); - totalDifference += d; - } + totalDifference += d; } } + } - float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); - normalizedDifference /= 4F * 65535F; + float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height); + normalizedDifference /= 4F * 65535F; - if (normalizedDifference > this.ImageThreshold) - { - return new ImageSimilarityReport(expected, actual, differences, normalizedDifference); - } - else - { - return ImageSimilarityReport.Empty; - } + if (normalizedDifference > this.ImageThreshold) + { + return new ImageSimilarityReport(expected, actual, differences, normalizedDifference); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b) + else { - return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A); + return ImageSimilarityReport.Empty; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Diff(ushort a, ushort b) => Math.Abs(a - b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b) + { + return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Diff(ushort a, ushort b) => Math.Abs(a - b); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index c491099db1..1e3ad3a5d5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -1,90 +1,87 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -using SixLabors.ImageSharp.Advanced; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class TestImageProvider : IXunitSerializable { - public abstract partial class TestImageProvider : IXunitSerializable + public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) { - 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) { - throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); } - private class BasicTestPatternProvider : BlankProvider + // This parameterless constructor is needed for xUnit deserialization + public BasicTestPatternProvider() { - 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 override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}"); - public BasicTestPatternProvider(int width, int height) - : base(width, height) - { - } - - // This parameterless constructor is needed for xUnit deserialization - public BasicTestPatternProvider() + public override Image GetImage() + { + var result = new Image(this.Configuration, this.Width, this.Height); + result.ProcessPixelRows(accessor => { - } - - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"BasicTestPattern{this.Width}x{this.Height}"); + int midY = this.Height / 2; + int midX = this.Width / 2; - public override Image GetImage() - { - var result = new Image(this.Configuration, this.Width, this.Height); - result.ProcessPixelRows(accessor => + for (int y = 0; y < midY; y++) { - int midY = this.Height / 2; - int midX = this.Width / 2; + Span row = accessor.GetRowSpan(y); - for (int y = 0; y < midY; y++) - { - Span row = accessor.GetRowSpan(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 = accessor.GetRowSpan(y); - for (int y = midY; y < this.Height; y++) - { - Span row = accessor.GetRowSpan(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; + } - return result; - } + public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + int midY = this.Height / 2; + int midX = this.Width / 2; - public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + if (y < midY) { - int midY = this.Height / 2; - int midX = this.Width / 2; - - if (y < midY) - { - return x < midX ? TopLeftColor : TopRightColor; - } - else - { - return x < midX ? BottomLeftColor : BottomRightColor; - } + return x < midX ? TopLeftColor : TopRightColor; } - - private static TPixel GetBottomRightColor() + else { - TPixel bottomRightColor = default; - bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); - return bottomRightColor; + return x < midX ? BottomLeftColor : BottomRightColor; } } + + private static TPixel GetBottomRightColor() + { + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + return bottomRightColor; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index e78a733ccc..5a31707bf9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -5,49 +5,48 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + private class BlankProvider : TestImageProvider, IXunitSerializable { - private class BlankProvider : TestImageProvider, IXunitSerializable + public BlankProvider(int width, int height) + { + this.Width = width; + this.Height = height; + } + + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public BlankProvider() + { + this.Width = 100; + this.Height = 100; + } + + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"Blank{this.Width}x{this.Height}"); + + protected int Height { get; private set; } + + protected int Width { get; private set; } + + public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); + + public override void Deserialize(IXunitSerializationInfo info) + { + this.Width = info.GetValue("width"); + this.Height = info.GetValue("height"); + base.Deserialize(info); + } + + public override void Serialize(IXunitSerializationInfo info) { - public BlankProvider(int width, int height) - { - this.Width = width; - this.Height = height; - } - - /// - /// This parameterless constructor is needed for xUnit deserialization - /// - public BlankProvider() - { - this.Width = 100; - this.Height = 100; - } - - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"Blank{this.Width}x{this.Height}"); - - protected int Height { get; private set; } - - protected int Width { get; private set; } - - public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); - - public override void Deserialize(IXunitSerializationInfo info) - { - this.Width = info.GetValue("width"); - this.Height = info.GetValue("height"); - base.Deserialize(info); - } - - public override void Serialize(IXunitSerializationInfo info) - { - info.AddValue("width", this.Width); - info.AddValue("height", this.Height); - base.Serialize(info); - } + info.AddValue("width", this.Width); + info.AddValue("height", this.Height); + base.Serialize(info); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 733e3a31f5..a0f544b2fa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -1,299 +1,294 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; using System.Reflection; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + internal class FileProvider : TestImageProvider, IXunitSerializable { - internal class FileProvider : TestImageProvider, IXunitSerializable + // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider + // are shared between PixelTypes.Color & PixelTypes.Rgba32 + private class Key : IEquatable { - // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider - // are shared between PixelTypes.Color & PixelTypes.Rgba32 - private class Key : IEquatable + private readonly Tuple commonValues; + + private readonly Dictionary decoderParameters; + + public Key( + PixelTypes pixelType, + string filePath, + IImageDecoder customDecoder, + DecoderOptions options, + ISpecializedDecoderOptions specialized) + { + Type customType = customDecoder?.GetType(); + this.commonValues = new Tuple( + pixelType, + filePath, + customType); + this.decoderParameters = GetDecoderParameters(options, specialized); + } + + private static Dictionary GetDecoderParameters( + DecoderOptions options, + ISpecializedDecoderOptions specialized) { - private readonly Tuple commonValues; + Type type = options.GetType(); - private readonly Dictionary decoderParameters; + var data = new Dictionary(); - public Key( - PixelTypes pixelType, - string filePath, - IImageDecoder customDecoder, - DecoderOptions options, - ISpecializedDecoderOptions specialized) + while (type != null && type != typeof(object)) { - Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple( - pixelType, - filePath, - customType); - this.decoderParameters = GetDecoderParameters(options, specialized); + foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + string key = $"{type.FullName}.{p.Name}"; + data[key] = p.GetValue(options); + } + + type = type.GetTypeInfo().BaseType; } - private static Dictionary GetDecoderParameters( - DecoderOptions options, - ISpecializedDecoderOptions specialized) + GetSpecializedDecoderParameters(data, specialized); + + return data; + } + + private static void GetSpecializedDecoderParameters( + Dictionary data, + ISpecializedDecoderOptions options) + { + if (options is null) { - Type type = options.GetType(); + return; + } - var data = new Dictionary(); + Type type = options.GetType(); - while (type != null && type != typeof(object)) + while (type != null && type != typeof(object)) + { + foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + if (p.PropertyType == typeof(DecoderOptions)) { - string key = $"{type.FullName}.{p.Name}"; - data[key] = p.GetValue(options); + continue; } - type = type.GetTypeInfo().BaseType; + string key = $"{type.FullName}.{p.Name}"; + data[key] = p.GetValue(options); } - GetSpecializedDecoderParameters(data, specialized); - - return data; + type = type.GetTypeInfo().BaseType; } + } - private static void GetSpecializedDecoderParameters( - Dictionary data, - ISpecializedDecoderOptions options) + public bool Equals(Key other) + { + if (other is null) { - if (options is null) - { - return; - } - - Type type = options.GetType(); - - while (type != null && type != typeof(object)) - { - foreach (PropertyInfo p in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (p.PropertyType == typeof(DecoderOptions)) - { - continue; - } - - string key = $"{type.FullName}.{p.Name}"; - data[key] = p.GetValue(options); - } + return false; + } - type = type.GetTypeInfo().BaseType; - } + if (ReferenceEquals(this, other)) + { + return true; } - public bool Equals(Key other) + if (!this.commonValues.Equals(other.commonValues)) { - if (other is null) - { - return false; - } + return false; + } - if (ReferenceEquals(this, other)) - { - return true; - } + if (this.decoderParameters.Count != other.decoderParameters.Count) + { + return false; + } - if (!this.commonValues.Equals(other.commonValues)) + foreach (KeyValuePair kv in this.decoderParameters) + { + if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) { return false; } - if (this.decoderParameters.Count != other.decoderParameters.Count) + if (!Equals(kv.Value, otherVal)) { return false; } + } - foreach (KeyValuePair kv in this.decoderParameters) - { - if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) - { - return false; - } + return true; + } - if (!Equals(kv.Value, otherVal)) - { - return false; - } - } + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + if (ReferenceEquals(this, obj)) + { return true; } - public override bool Equals(object obj) + if (obj.GetType() != this.GetType()) { - if (obj is null) - { - return false; - } + return false; + } - if (ReferenceEquals(this, obj)) - { - return true; - } + return this.Equals((Key)obj); + } - if (obj.GetType() != this.GetType()) - { - return false; - } + public override int GetHashCode() => this.commonValues.GetHashCode(); - return this.Equals((Key)obj); - } + public static bool operator ==(Key left, Key right) => Equals(left, right); - public override int GetHashCode() => this.commonValues.GetHashCode(); + public static bool operator !=(Key left, Key right) => !Equals(left, right); + } - public static bool operator ==(Key left, Key right) => Equals(left, right); + private static readonly ConcurrentDictionary> Cache = new(); - public static bool operator !=(Key left, Key right) => !Equals(left, right); - } + // Needed for deserialization! + // ReSharper disable once UnusedMember.Local + public FileProvider() + { + } - private static readonly ConcurrentDictionary> Cache = new(); + public FileProvider(string filePath) => this.FilePath = filePath; - // Needed for deserialization! - // ReSharper disable once UnusedMember.Local - public FileProvider() - { - } + /// + /// Gets the file path relative to the "~/tests/images" folder + /// + public string FilePath { get; private set; } - public FileProvider(string filePath) => this.FilePath = filePath; + public override string SourceFileOrDescription => this.FilePath; - /// - /// Gets the file path relative to the "~/tests/images" folder - /// - public string FilePath { get; private set; } + public override Image GetImage() + { + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); + return this.GetImage(decoder); + } - public override string SourceFileOrDescription => this.FilePath; + public override Image GetImage(IImageDecoder decoder, DecoderOptions options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); - public override Image GetImage() + // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator + if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) { - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(this.FilePath); - return this.GetImage(decoder); + return this.DecodeImage(decoder, options); } - public override Image GetImage(IImageDecoder decoder, DecoderOptions options) + // do not cache so we can track allocation correctly when validating memory + if (MemoryAllocatorValidator.MonitoringAllocations) { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + return this.DecodeImage(decoder, options); + } - // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator - if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) - { - return this.DecodeImage(decoder, options); - } + var key = new Key(this.PixelType, this.FilePath, decoder, options, null); + Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); - // do not cache so we can track allocation correctly when validating memory - if (MemoryAllocatorValidator.MonitoringAllocations) - { - return this.DecodeImage(decoder, options); - } + return cachedImage.Clone(this.Configuration); + } - var key = new Key(this.PixelType, this.FilePath, decoder, options, null); - Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); + public override Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); - return cachedImage.Clone(this.Configuration); - } + options.Configuration = this.Configuration; - public override Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) - { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + // Used in small subset of decoder tests, no caching. + // TODO: Check Path here. Why combined? + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + using Stream stream = System.IO.File.OpenRead(path); + return Task.FromResult(decoder.Decode(options, stream, default)); + } - options.Configuration = this.Configuration; + public override Image GetImage(IImageDecoderSpecialized decoder, T options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); - // Used in small subset of decoder tests, no caching. - // TODO: Check Path here. Why combined? - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - using Stream stream = System.IO.File.OpenRead(path); - return Task.FromResult(decoder.Decode(options, stream, default)); + // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator + if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) + { + return this.DecodeImage(decoder, options); } - public override Image GetImage(IImageDecoderSpecialized decoder, T options) + // do not cache so we can track allocation correctly when validating memory + if (MemoryAllocatorValidator.MonitoringAllocations) { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); - - // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator - if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) - { - return this.DecodeImage(decoder, options); - } - - // do not cache so we can track allocation correctly when validating memory - if (MemoryAllocatorValidator.MonitoringAllocations) - { - return this.DecodeImage(decoder, options); - } - - var key = new Key(this.PixelType, this.FilePath, decoder, options.GeneralOptions, options); - Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); - - return cachedImage.Clone(this.Configuration); + return this.DecodeImage(decoder, options); } - public override Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) - { - Guard.NotNull(decoder, nameof(decoder)); - Guard.NotNull(options, nameof(options)); + var key = new Key(this.PixelType, this.FilePath, decoder, options.GeneralOptions, options); + Image cachedImage = Cache.GetOrAdd(key, _ => this.DecodeImage(decoder, options)); - options.GeneralOptions.Configuration = this.Configuration; + return cachedImage.Clone(this.Configuration); + } - // Used in small subset of decoder tests, no caching. - // TODO: Check Path here. Why combined? - string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); - using Stream stream = System.IO.File.OpenRead(path); - return Task.FromResult(decoder.Decode(options, stream, default)); - } + public override Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) + { + Guard.NotNull(decoder, nameof(decoder)); + Guard.NotNull(options, nameof(options)); - public override void Deserialize(IXunitSerializationInfo info) - { - this.FilePath = info.GetValue("path"); + options.GeneralOptions.Configuration = this.Configuration; - base.Deserialize(info); // must be called last - } + // Used in small subset of decoder tests, no caching. + // TODO: Check Path here. Why combined? + string path = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.FilePath); + using Stream stream = System.IO.File.OpenRead(path); + return Task.FromResult(decoder.Decode(options, stream, default)); + } - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - info.AddValue("path", this.FilePath); - } + public override void Deserialize(IXunitSerializationInfo info) + { + this.FilePath = info.GetValue("path"); - private Image DecodeImage(IImageDecoder decoder, DecoderOptions options) - { - options.Configuration = this.Configuration; + base.Deserialize(info); // must be called last + } - var testFile = TestFile.Create(this.FilePath); - using Stream stream = new MemoryStream(testFile.Bytes); - return decoder.Decode(options, stream, default); - } + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); + info.AddValue("path", this.FilePath); + } - private Image DecodeImage(IImageDecoderSpecialized decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - { - options.GeneralOptions.Configuration = this.Configuration; + private Image DecodeImage(IImageDecoder decoder, DecoderOptions options) + { + options.Configuration = this.Configuration; - var testFile = TestFile.Create(this.FilePath); - using Stream stream = new MemoryStream(testFile.Bytes); - return decoder.Decode(options, stream, default); - } + var testFile = TestFile.Create(this.FilePath); + using Stream stream = new MemoryStream(testFile.Bytes); + return decoder.Decode(options, stream, default); } - public static string GetFilePathOrNull(ITestImageProvider provider) + private Image DecodeImage(IImageDecoderSpecialized decoder, T options) + where T : class, ISpecializedDecoderOptions, new() { - var fileProvider = provider as FileProvider; - return fileProvider?.FilePath; + options.GeneralOptions.Configuration = this.Configuration; + + var testFile = TestFile.Create(this.FilePath); + using Stream stream = new MemoryStream(testFile.Bytes); + return decoder.Decode(options, stream, default); } } + + public static string GetFilePathOrNull(ITestImageProvider provider) + { + var fileProvider = provider as FileProvider; + return fileProvider?.FilePath; + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs index ab5b1638f3..4bf172aed2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs @@ -1,16 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public interface ITestImageProvider { - public interface ITestImageProvider - { - PixelTypes PixelType { get; } + PixelTypes PixelType { get; } - ImagingTestCaseUtility Utility { get; } + ImagingTestCaseUtility Utility { get; } - string SourceFileOrDescription { get; } + string SourceFileOrDescription { get; } - Configuration Configuration { get; set; } - } + Configuration Configuration { get; set; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs index e0c8975afc..390195274f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs @@ -1,69 +1,66 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Linq; using System.Reflection; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Provides instances for parametric unit tests. +/// +/// The pixel format of the image +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - /// - /// Provides instances for parametric unit tests. - /// - /// The pixel format of the image - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + private class MemberMethodProvider : TestImageProvider { - private class MemberMethodProvider : TestImageProvider - { - private string declaringTypeName; - private string methodName; - private Func> factoryFunc; + private string declaringTypeName; + private string methodName; + private Func> factoryFunc; - public MemberMethodProvider() - { - } + public MemberMethodProvider() + { + } - public MemberMethodProvider(string declaringTypeName, string methodName) - { - this.declaringTypeName = declaringTypeName; - this.methodName = methodName; - } + public MemberMethodProvider(string declaringTypeName, string methodName) + { + this.declaringTypeName = declaringTypeName; + this.methodName = methodName; + } - public override Image GetImage() - { - this.factoryFunc ??= this.GetFactory(); - return this.factoryFunc(); - } + public override Image GetImage() + { + this.factoryFunc ??= this.GetFactory(); + return this.factoryFunc(); + } - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); - info.AddValue(nameof(this.declaringTypeName), this.declaringTypeName); - info.AddValue(nameof(this.methodName), this.methodName); - } + info.AddValue(nameof(this.declaringTypeName), this.declaringTypeName); + info.AddValue(nameof(this.methodName), this.methodName); + } - public override void Deserialize(IXunitSerializationInfo info) - { - base.Deserialize(info); + public override void Deserialize(IXunitSerializationInfo info) + { + base.Deserialize(info); - this.methodName = info.GetValue(nameof(this.methodName)); - this.declaringTypeName = info.GetValue(nameof(this.declaringTypeName)); - } + this.methodName = info.GetValue(nameof(this.methodName)); + this.declaringTypeName = info.GetValue(nameof(this.declaringTypeName)); + } - private Func> GetFactory() - { - var declaringType = Type.GetType(this.declaringTypeName); - MethodInfo m = declaringType.GetMethod(this.methodName); - Type pixelType = typeof(TPixel); - Type imgType = typeof(Image<>).MakeGenericType(pixelType); - Type funcType = typeof(Func<>).MakeGenericType(imgType); - MethodInfo genericMethod = m.MakeGenericMethod(pixelType); - return (Func>)genericMethod.CreateDelegate(funcType); - } + private Func> GetFactory() + { + var declaringType = Type.GetType(this.declaringTypeName); + MethodInfo m = declaringType.GetMethod(this.methodName); + Type pixelType = typeof(TPixel); + Type imgType = typeof(Image<>).MakeGenericType(pixelType); + Type funcType = typeof(Func<>).MakeGenericType(imgType); + MethodInfo genericMethod = m.MakeGenericMethod(pixelType); + return (Func>)genericMethod.CreateDelegate(funcType); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 3a4f4cfee4..8c5101013f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -1,80 +1,78 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Provides instances for parametric unit tests. +/// +/// The pixel format of the image +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - /// - /// Provides instances for parametric unit tests. - /// - /// The pixel format of the image - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + private class SolidProvider : BlankProvider { - private class SolidProvider : BlankProvider - { - private byte a; + private byte a; - private byte b; + private byte b; - private byte g; + private byte g; - private byte r; + private byte r; - public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) - : base(width, height) - { - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } + public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) + : base(width, height) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// - public SolidProvider() - : base() - { - this.r = 0; - this.g = 0; - this.b = 0; - this.a = 0; - } + /// + /// This parameterless constructor is needed for xUnit deserialization + /// + public SolidProvider() + : base() + { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + } - public override string SourceFileOrDescription - => TestUtils.AsInvariantString($"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"); + public override string SourceFileOrDescription + => TestUtils.AsInvariantString($"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"); - public override Image GetImage() - { - Image image = base.GetImage(); - Color color = new Rgba32(this.r, this.g, this.b, this.a); + public override Image GetImage() + { + Image image = base.GetImage(); + Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); - return image; - } + image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); + return image; + } - public override void Serialize(IXunitSerializationInfo info) - { - info.AddValue("red", this.r); - info.AddValue("green", this.g); - info.AddValue("blue", this.b); - info.AddValue("alpha", this.a); - base.Serialize(info); - } + public override void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.r); + info.AddValue("green", this.g); + info.AddValue("blue", this.b); + info.AddValue("alpha", this.a); + base.Serialize(info); + } - public override void Deserialize(IXunitSerializationInfo info) - { - this.r = info.GetValue("red"); - this.g = info.GetValue("green"); - this.b = info.GetValue("blue"); - this.a = info.GetValue("alpha"); - base.Deserialize(info); - } + public override void Deserialize(IXunitSerializationInfo info) + { + this.r = info.GetValue("red"); + this.g = info.GetValue("green"); + this.b = info.GetValue("blue"); + this.a = info.GetValue("alpha"); + base.Deserialize(info); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index bb0e196499..8cd04f1cd1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -1,9 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Reflection; -using System.Threading.Tasks; using Castle.Core.Internal; using SixLabors.ImageSharp.Formats; @@ -12,170 +10,169 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Provides instances for parametric unit tests. - /// - /// The pixel format of the image. - public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable - where TPixel : unmanaged, IPixel - { - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); +namespace SixLabors.ImageSharp.Tests; - public virtual string SourceFileOrDescription => string.Empty; +/// +/// Provides instances for parametric unit tests. +/// +/// The pixel format of the image. +public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable + where TPixel : unmanaged, IPixel +{ + public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); - public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); + public virtual string SourceFileOrDescription => string.Empty; - /// - /// Gets the utility instance to provide information about the test image & manage input/output. - /// - public ImagingTestCaseUtility Utility { get; private set; } + public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); - public string TypeName { get; private set; } + /// + /// Gets the utility instance to provide information about the test image & manage input/output. + /// + public ImagingTestCaseUtility Utility { get; private set; } - public string MethodName { get; private set; } + public string TypeName { get; private set; } - public string OutputSubfolderName { get; private set; } + public string MethodName { get; private set; } - public static TestImageProvider BasicTestPattern( - int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + public string OutputSubfolderName { get; private set; } - public static TestImageProvider TestPattern( - int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + 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 Blank( + public static TestImageProvider TestPattern( int width, int height, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); - - public static TestImageProvider File( - string filePath, + => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Blank( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider File( + string filePath, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Lambda( + string declaringTypeName, + string methodName, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new FileProvider(filePath).Init(testMethod, pixelTypeOverride); + => new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Solid( + int width, + int height, + byte r, + byte g, + byte b, + byte a = 255, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); + + /// + /// Returns an instance to the test case with the necessary traits. + /// + /// A test image. + public abstract Image GetImage(); - public static TestImageProvider Lambda( - string declaringTypeName, - string methodName, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new MemberMethodProvider(declaringTypeName, methodName).Init(testMethod, pixelTypeOverride); + public Image GetImage(IImageDecoder decoder) + => this.GetImage(decoder, new()); - public static TestImageProvider Solid( - int width, - int height, - byte r, - byte g, - byte b, - byte a = 255, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new SolidProvider(width, height, r, g, b, a).Init(testMethod, pixelTypeOverride); + public Task> GetImageAsync(IImageDecoder decoder) + => this.GetImageAsync(decoder, new()); - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// A test image. - public abstract Image GetImage(); + public virtual Image GetImage(IImageDecoder decoder, DecoderOptions options) + => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); - public Image GetImage(IImageDecoder decoder) - => this.GetImage(decoder, new()); + public virtual Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) + => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); - public Task> GetImageAsync(IImageDecoder decoder) - => this.GetImageAsync(decoder, new()); + public virtual Image GetImage(IImageDecoderSpecialized decoder, T options) + where T : class, ISpecializedDecoderOptions, new() + => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); - public virtual Image GetImage(IImageDecoder decoder, DecoderOptions options) - => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + public virtual Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) + where T : class, ISpecializedDecoderOptions, new() + => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); - public virtual Task> GetImageAsync(IImageDecoder decoder, DecoderOptions options) - => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); + /// + /// Returns an instance to the test case with the necessary traits. + /// + /// A test image. + public Image GetImage(Action operationsToApply) + { + Image img = this.GetImage(); + img.Mutate(operationsToApply); + return img; + } - public virtual Image GetImage(IImageDecoderSpecialized decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - => throw new NotSupportedException($"Decoder specific GetImage() is not supported with {this.GetType().Name}!"); + public virtual void Deserialize(IXunitSerializationInfo info) + { + PixelTypes pixelType = info.GetValue("PixelType"); + string typeName = info.GetValue("TypeName"); + string methodName = info.GetValue("MethodName"); + string outputSubfolderName = info.GetValue("OutputSubfolderName"); - public virtual Task> GetImageAsync(IImageDecoderSpecialized decoder, T options) - where T : class, ISpecializedDecoderOptions, new() - => throw new NotSupportedException($"Decoder specific GetImageAsync() is not supported with {this.GetType().Name}!"); + this.Init(typeName, methodName, outputSubfolderName, pixelType); + } - /// - /// Returns an instance to the test case with the necessary traits. - /// - /// A test image. - public Image GetImage(Action operationsToApply) - { - Image img = this.GetImage(); - img.Mutate(operationsToApply); - return img; - } + public virtual void Serialize(IXunitSerializationInfo info) + { + info.AddValue("PixelType", this.PixelType); + info.AddValue("TypeName", this.TypeName); + info.AddValue("MethodName", this.MethodName); + info.AddValue("OutputSubfolderName", this.OutputSubfolderName); + } - public virtual void Deserialize(IXunitSerializationInfo info) + protected TestImageProvider Init( + string typeName, + string methodName, + string outputSubfolderName, + PixelTypes pixelTypeOverride) + { + if (pixelTypeOverride != PixelTypes.Undefined) { - PixelTypes pixelType = info.GetValue("PixelType"); - string typeName = info.GetValue("TypeName"); - string methodName = info.GetValue("MethodName"); - string outputSubfolderName = info.GetValue("OutputSubfolderName"); - - this.Init(typeName, methodName, outputSubfolderName, pixelType); + this.PixelType = pixelTypeOverride; } - public virtual void Serialize(IXunitSerializationInfo info) + this.TypeName = typeName; + this.MethodName = methodName; + this.OutputSubfolderName = outputSubfolderName; + + this.Utility = new ImagingTestCaseUtility { - info.AddValue("PixelType", this.PixelType); - info.AddValue("TypeName", this.TypeName); - info.AddValue("MethodName", this.MethodName); - info.AddValue("OutputSubfolderName", this.OutputSubfolderName); - } + SourceFileOrDescription = this.SourceFileOrDescription, + PixelTypeName = this.PixelType.ToString() + }; - protected TestImageProvider Init( - string typeName, - string methodName, - string outputSubfolderName, - PixelTypes pixelTypeOverride) + if (methodName != null) { - if (pixelTypeOverride != PixelTypes.Undefined) - { - this.PixelType = pixelTypeOverride; - } - - this.TypeName = typeName; - this.MethodName = methodName; - this.OutputSubfolderName = outputSubfolderName; - - this.Utility = new ImagingTestCaseUtility - { - SourceFileOrDescription = this.SourceFileOrDescription, - PixelTypeName = this.PixelType.ToString() - }; - - if (methodName != null) - { - this.Utility.Init(typeName, methodName, outputSubfolderName); - } - - return this; + this.Utility.Init(typeName, methodName, outputSubfolderName); } - protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) - { - string subfolder = - testMethod?.DeclaringType.GetAttribute()?.Subfolder ?? string.Empty; + return this; + } - return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); - } + protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) + { + string subfolder = + testMethod?.DeclaringType.GetAttribute()?.Subfolder ?? string.Empty; - public override string ToString() - => $"{this.SourceFileOrDescription}[{this.PixelType}]"; + return this.Init(testMethod?.DeclaringType.Name, testMethod?.Name, subfolder, pixelTypeOverride); } + + public override string ToString() + => $"{this.SourceFileOrDescription}[{this.PixelType}]"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 1169d69310..63307f7e21 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -1,220 +1,217 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { - public abstract partial class TestImageProvider : IXunitSerializable - where TPixel : unmanaged, IPixel + /// + /// A test image provider that produces test patterns. + /// + private class TestPatternProvider : BlankProvider { + private static readonly Dictionary> TestImages = new Dictionary>(); + + private static readonly TPixel[] BlackWhitePixels = + { + Color.Black.ToPixel(), + Color.White.ToPixel() + }; + + private static readonly TPixel[] PinkBluePixels = + { + Color.HotPink.ToPixel(), + Color.Blue.ToPixel() + }; + + public TestPatternProvider(int width, int height) + : base(width, height) + { + } + /// - /// A test image provider that produces test patterns. + /// This parameterless constructor is needed for xUnit deserialization /// - private class TestPatternProvider : BlankProvider + public TestPatternProvider() { - private static readonly Dictionary> TestImages = new Dictionary>(); + } - private static readonly TPixel[] BlackWhitePixels = - { - Color.Black.ToPixel(), - Color.White.ToPixel() - }; + public override string SourceFileOrDescription => TestUtils.AsInvariantString($"TestPattern{this.Width}x{this.Height}"); - private static readonly TPixel[] PinkBluePixels = + public override Image GetImage() + { + lock (TestImages) { - Color.HotPink.ToPixel(), - Color.Blue.ToPixel() - }; + if (!TestImages.ContainsKey(this.SourceFileOrDescription)) + { + var image = new Image(this.Width, this.Height); + DrawTestPattern(image); + TestImages.Add(this.SourceFileOrDescription, image); + } - public TestPatternProvider(int width, int height) - : base(width, height) - { + return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); } + } + + /// + /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. + /// + /// The image to draw on. + private static void DrawTestPattern(Image image) + { + // first lets split the image into 4 quadrants + Buffer2D pixels = image.GetRootFramePixelBuffer(); + BlackWhiteChecker(pixels); // top left + VerticalBars(pixels); // top right + TransparentGradients(pixels); // bottom left + Rainbow(pixels); // bottom right + } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// - public TestPatternProvider() + /// + /// Fills the top right quadrant with alternating solid vertical bars. + /// + private static void VerticalBars(Buffer2D pixels) + { + // topLeft + int left = pixels.Width / 2; + int right = pixels.Width; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 12; + if (stride < 1) { + stride = 1; } - public override string SourceFileOrDescription => TestUtils.AsInvariantString($"TestPattern{this.Width}x{this.Height}"); - - public override Image GetImage() + for (int y = top; y < bottom; y++) { - lock (TestImages) + int p = 0; + for (int x = left; x < right; x++) { - if (!TestImages.ContainsKey(this.SourceFileOrDescription)) + if (x % stride == 0) { - var image = new Image(this.Width, this.Height); - DrawTestPattern(image); - TestImages.Add(this.SourceFileOrDescription, image); + p++; + p = p % PinkBluePixels.Length; } - return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); + pixels[x, y] = PinkBluePixels[p]; } } + } - /// - /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. - /// - /// The image to draw on. - private static void DrawTestPattern(Image image) - { - // first lets split the image into 4 quadrants - Buffer2D pixels = image.GetRootFramePixelBuffer(); - BlackWhiteChecker(pixels); // top left - VerticalBars(pixels); // top right - TransparentGradients(pixels); // bottom left - Rainbow(pixels); // bottom right - } - - /// - /// Fills the top right quadrant with alternating solid vertical bars. - /// - private static void VerticalBars(Buffer2D pixels) + /// + /// fills the top left quadrant with a black and white checker board. + /// + private static void BlackWhiteChecker(Buffer2D pixels) + { + // topLeft + int left = 0; + int right = pixels.Width / 2; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 6; + + int p = 0; + for (int y = top; y < bottom; y++) { - // topLeft - int left = pixels.Width / 2; - int right = pixels.Width; - int top = 0; - int bottom = pixels.Height / 2; - int stride = pixels.Width / 12; - if (stride < 1) - { - stride = 1; - } - - for (int y = top; y < bottom; y++) + if (y % stride is 0) { - int p = 0; - for (int x = left; x < right; x++) - { - if (x % stride == 0) - { - p++; - p = p % PinkBluePixels.Length; - } - - pixels[x, y] = PinkBluePixels[p]; - } + p++; + p = p % BlackWhitePixels.Length; } - } - - /// - /// fills the top left quadrant with a black and white checker board. - /// - private static void BlackWhiteChecker(Buffer2D pixels) - { - // topLeft - int left = 0; - int right = pixels.Width / 2; - int top = 0; - int bottom = pixels.Height / 2; - int stride = pixels.Width / 6; - int p = 0; - for (int y = top; y < bottom; y++) + int pstart = p; + for (int x = left; x < right; x++) { - if (y % stride is 0) + if (x % stride is 0) { p++; p = p % BlackWhitePixels.Length; } - int pstart = p; - for (int x = left; x < right; x++) - { - if (x % stride is 0) - { - p++; - p = p % BlackWhitePixels.Length; - } - - pixels[x, y] = BlackWhitePixels[p]; - } - - p = pstart; + pixels[x, y] = BlackWhitePixels[p]; } + + p = pstart; } + } - /// - /// 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 - int left = 0; - int right = pixels.Width / 2; - int top = pixels.Height / 2; - int bottom = pixels.Height; - int height = (int)Math.Ceiling(pixels.Height / 6f); + /// + /// 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 + int left = 0; + int right = pixels.Width / 2; + int top = pixels.Height / 2; + int bottom = pixels.Height; + int height = (int)Math.Ceiling(pixels.Height / 6f); - 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 + 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 - var c = default(TPixel); + var c = default(TPixel); - for (int x = left; x < right; x++) - { - blue.W = red.W = green.W = (float)x / (float)right; + for (int x = left; x < right; x++) + { + blue.W = red.W = green.W = (float)x / (float)right; - c.FromVector4(red); - int topBand = top; - for (int y = topBand; y < top + height; y++) - { - pixels[x, y] = c; - } + c.FromVector4(red); + int topBand = top; + for (int y = topBand; y < top + height; y++) + { + 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(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++) - { - pixels[x, y] = c; - } + topBand = topBand + height; + c.FromVector4(blue); + for (int y = topBand; y < bottom; y++) + { + pixels[x, y] = c; } } + } - /// - /// 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; - int right = pixels.Width; - int top = pixels.Height / 2; - int bottom = pixels.Height; + /// + /// 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; + int right = pixels.Width; + int top = pixels.Height / 2; + int bottom = pixels.Height; - int pixelCount = left * top; - uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); - TPixel c = default; - var t = new Rgba32(0); + int pixelCount = left * top; + uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); + TPixel c = default; + var t = new Rgba32(0); - for (int x = left; x < right; x++) + for (int x = left; x < right; x++) + { + for (int y = top; y < bottom; y++) { - for (int y = top; y < bottom; y++) - { - t.PackedValue += stepsPerPixel; - var v = t.ToVector4(); + t.PackedValue += stepsPerPixel; + var v = t.ToVector4(); - // v.W = (x - left) / (float)left; - c.FromVector4(v); - pixels[x, y] = c; - } + // 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 cdf2caadf7..460ecac85a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,328 +1,323 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Utility class to provide information about the test image & the test case for the test code, +/// and help managing IO. +/// +public class ImagingTestCaseUtility { /// - /// Utility class to provide information about the test image & the test case for the test code, - /// and help managing IO. + /// Gets or sets the name of the TPixel in the owner /// - public class ImagingTestCaseUtility - { - /// - /// Gets or sets the name of the TPixel in the owner - /// - public string PixelTypeName { get; set; } = string.Empty; - - /// - /// 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; - - /// - /// 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; - - /// - /// Gets or sets the name of the test case (by default). - /// - public string TestName { get; set; } = string.Empty; - - private string GetTestOutputFileNameImpl( - string extension, - string details, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) - { - if (string.IsNullOrWhiteSpace(extension)) - { - extension = null; - } + public string PixelTypeName { get; set; } = string.Empty; - string fn = appendSourceFileOrDescription - ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : string.Empty; + /// + /// 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; - if (string.IsNullOrWhiteSpace(extension)) - { - extension = Path.GetExtension(this.SourceFileOrDescription); - } + /// + /// 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; - if (string.IsNullOrWhiteSpace(extension)) - { - extension = ".bmp"; - } + public string OutputSubfolderName { get; set; } = string.Empty; - extension = extension.ToLower(); + /// + /// Gets or sets the name of the test case (by default). + /// + public string TestName { get; set; } = string.Empty; - if (extension[0] != '.') - { - extension = '.' + extension; - } + private string GetTestOutputFileNameImpl( + string extension, + string details, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) + { + if (string.IsNullOrWhiteSpace(extension)) + { + extension = null; + } - if (fn != string.Empty) - { - fn = '_' + fn; - } + string fn = appendSourceFileOrDescription + ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) + : string.Empty; - string pixName = string.Empty; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = Path.GetExtension(this.SourceFileOrDescription); + } - if (appendPixelTypeToFileName) - { - pixName = this.PixelTypeName; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = ".bmp"; + } - if (pixName != string.Empty) - { - pixName = '_' + pixName; - } - } + extension = extension.ToLower(); - details = details ?? string.Empty; - if (details != string.Empty) - { - details = '_' + details; - } + if (extension[0] != '.') + { + extension = '.' + extension; + } - return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}{Path.DirectorySeparatorChar}{this.TestName}{pixName}{fn}{details}{extension}"); + if (fn != string.Empty) + { + fn = '_' + fn; } - /// - /// Gets the recommended file name for the output of the test - /// - /// The required extension - /// The settings modifying the output path - /// A boolean indicating whether to append the pixel type to output file name. - /// A boolean indicating whether to append to the test output file name. - /// The file test name - public string GetTestOutputFileName( - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + string pixName = string.Empty; + + if (appendPixelTypeToFileName) { - string detailsString = null; + pixName = this.PixelTypeName; - if (testOutputDetails is FormattableString fs) - { - detailsString = fs.AsInvariantString(); - } - else if (testOutputDetails is string s) - { - detailsString = s; - } - else if (testOutputDetails != null) + if (pixName != string.Empty) { - Type type = testOutputDetails.GetType(); - TypeInfo info = type.GetTypeInfo(); - if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) - { - detailsString = TestUtils.AsInvariantString($"{testOutputDetails}"); - } - else - { - IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); - - detailsString = string.Join( - "_", - properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); - } + pixName = '_' + pixName; } - - return this.GetTestOutputFileNameImpl( - extension, - detailsString, - appendPixelTypeToFileName, - appendSourceFileOrDescription); } - /// - /// 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. - /// 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. - /// The path to the saved image file. - public string SaveTestOutputFile( - Image image, - string extension = null, - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + details = details ?? string.Empty; + if (details != string.Empty) { - string path = this.GetTestOutputFileName( - extension, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); + details = '_' + details; + } - encoder ??= TestEnvironment.GetReferenceEncoder(path); + return TestUtils.AsInvariantString($"{this.GetTestOutputDir()}{Path.DirectorySeparatorChar}{this.TestName}{pixName}{fn}{details}{extension}"); + } - using (FileStream stream = File.OpenWrite(path)) - { - image.Save(stream, encoder); - } + /// + /// Gets the recommended file name for the output of the test + /// + /// The required extension + /// The settings modifying the output path + /// A boolean indicating whether to append the pixel type to output file name. + /// A boolean indicating whether to append to the test output file name. + /// The file test name + public string GetTestOutputFileName( + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string detailsString = null; - return path; + if (testOutputDetails is FormattableString fs) + { + detailsString = fs.AsInvariantString(); } - - public IEnumerable GetTestOutputFileNamesMultiFrame( - int frameCount, - string extension = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) + else if (testOutputDetails is string s) { - string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); - - if (!Directory.Exists(baseDir)) + detailsString = s; + } + else if (testOutputDetails != null) + { + Type type = testOutputDetails.GetType(); + TypeInfo info = type.GetTypeInfo(); + if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) { - Directory.CreateDirectory(baseDir); + detailsString = TestUtils.AsInvariantString($"{testOutputDetails}"); } - - for (int i = 0; i < frameCount; i++) + else { - string filePath = $"{baseDir}/{i:D2}.{extension}"; - yield return filePath; + IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); + + detailsString = string.Join( + "_", + properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) + .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); } } - public string[] SaveTestOutputFileMultiFrame( - Image image, - string extension = "png", - IImageEncoder encoder = null, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); - - string[] files = this.GetTestOutputFileNamesMultiFrame( - image.Frames.Count, - extension, - testOutputDetails, - appendPixelTypeToFileName).ToArray(); + return this.GetTestOutputFileNameImpl( + extension, + detailsString, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + } - for (int i = 0; i < image.Frames.Count; i++) - { - using (Image frameImage = image.Frames.CloneFrame(i)) - { - string filePath = files[i]; - using (FileStream stream = File.OpenWrite(filePath)) - { - frameImage.Save(stream, encoder); - } - } - } + /// + /// 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. + /// 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. + /// The path to the saved image file. + public string SaveTestOutputFile( + Image image, + string extension = null, + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string path = this.GetTestOutputFileName( + extension, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); - return files; - } + encoder ??= TestEnvironment.GetReferenceEncoder(path); - internal string GetReferenceOutputFileName( - string extension, - object testOutputDetails, - bool appendPixelTypeToFileName, - bool appendSourceFileOrDescription) + using (FileStream stream = File.OpenWrite(path)) { - return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); + image.Save(stream, encoder); } - public string[] GetReferenceOutputFileNamesMultiFrame( - int frameCount, - string extension, - object testOutputDetails, - bool appendPixelTypeToFileName = true) + return path; + } + + public IEnumerable GetTestOutputFileNamesMultiFrame( + int frameCount, + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + { + string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); + + if (!Directory.Exists(baseDir)) { - return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) - .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); + Directory.CreateDirectory(baseDir); } - internal void Init(string typeName, string methodName, string outputSubfolderName) + for (int i = 0; i < frameCount; i++) { - this.TestGroupName = typeName; - this.TestName = methodName; - this.OutputSubfolderName = outputSubfolderName; + string filePath = $"{baseDir}/{i:D2}.{extension}"; + yield return filePath; } + } - internal string GetTestOutputDir() - { - string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + public string[] SaveTestOutputFileMultiFrame( + Image image, + string extension = "png", + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); + + string[] files = this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName).ToArray(); - if (!string.IsNullOrEmpty(this.OutputSubfolderName)) + for (int i = 0; i < image.Frames.Count; i++) + { + using (Image frameImage = image.Frames.CloneFrame(i)) { - testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); + string filePath = files[i]; + using (FileStream stream = File.OpenWrite(filePath)) + { + frameImage.Save(stream, encoder); + } } - - return TestEnvironment.CreateOutputDirectory(testGroupName); } - public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); + return files; + } - public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) - where TPixel : unmanaged, IPixel + internal string GetReferenceOutputFileName( + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName, + bool appendSourceFileOrDescription) + { + return TestEnvironment.GetReferenceOutputFileName( + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); + } + + public string[] GetReferenceOutputFileNamesMultiFrame( + int frameCount, + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName = true) + { + return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) + .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); + } + + internal void Init(string typeName, string methodName, string outputSubfolderName) + { + this.TestGroupName = typeName; + this.TestName = methodName; + this.OutputSubfolderName = outputSubfolderName; + } + + internal string GetTestOutputDir() + { + string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); + + if (!string.IsNullOrEmpty(this.OutputSubfolderName)) { - TPixel pixel = img[x, y]; - Rgba64 rgbaPixel = default; - rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); - ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); + testGroupName = Path.Combine(this.OutputSubfolderName, testGroupName); + } - if (rgbaPixel.R + perChannelChange <= 255) - { - rgbaPixel.R += change; - } - else - { - rgbaPixel.R -= change; - } + return TestEnvironment.CreateOutputDirectory(testGroupName); + } - if (rgbaPixel.G + perChannelChange <= 255) - { - rgbaPixel.G += change; - } - else - { - rgbaPixel.G -= change; - } + public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) + where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); - if (rgbaPixel.B + perChannelChange <= 255) - { - rgbaPixel.B += perChannelChange; - } - else - { - rgbaPixel.B -= perChannelChange; - } + public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) + where TPixel : unmanaged, IPixel + { + TPixel pixel = img[x, y]; + Rgba64 rgbaPixel = default; + rgbaPixel.FromScaledVector4(pixel.ToScaledVector4()); + ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); - if (rgbaPixel.A + perChannelChange <= 255) - { - rgbaPixel.A += perChannelChange; - } - else - { - rgbaPixel.A -= perChannelChange; - } + if (rgbaPixel.R + perChannelChange <= 255) + { + rgbaPixel.R += change; + } + else + { + rgbaPixel.R -= change; + } - pixel.FromRgba64(rgbaPixel); - img[x, y] = pixel; + if (rgbaPixel.G + perChannelChange <= 255) + { + rgbaPixel.G += change; + } + else + { + rgbaPixel.G -= change; + } + + if (rgbaPixel.B + perChannelChange <= 255) + { + rgbaPixel.B += perChannelChange; } + else + { + rgbaPixel.B -= perChannelChange; + } + + if (rgbaPixel.A + perChannelChange <= 255) + { + rgbaPixel.A += perChannelChange; + } + else + { + rgbaPixel.A -= perChannelChange; + } + + pixel.FromRgba64(rgbaPixel); + img[x, y] = pixel; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 38a2bfbf16..de42ba0ccf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -1,82 +1,80 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Utility class to measure the execution of an operation. It can be used either by inheritance or by composition. +/// +public class MeasureFixture { /// - /// Utility class to measure the execution of an operation. It can be used either by inheritance or by composition. + /// Value indicating whether printing is enabled. /// - public class MeasureFixture - { - /// - /// 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. - /// - /// A value indicating how many times to run the action - /// The to execute - /// The name of the operation to print to the output - public void Measure(int times, Action action, [CallerMemberName] string operationName = null) + /// + /// Measures and prints the execution time of an , executed multiple times. + /// + /// A value indicating how many times to run the action + /// The to execute + /// 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} ..."); - } - - var sw = Stopwatch.StartNew(); + this.Output?.WriteLine($"{operationName} X {times} ..."); + } - for (int i = 0; i < times; i++) - { - action(); - } + var sw = Stopwatch.StartNew(); - sw.Stop(); - if (this.enablePrinting) - { - this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); - } + for (int i = 0; i < times; i++) + { + action(); } - /// - /// Initializes a new instance of - /// - /// A instance to print the results - public MeasureFixture(ITestOutputHelper output) + sw.Stop(); + if (this.enablePrinting) { - this.Output = output; + this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); } - - protected ITestOutputHelper Output { get; } } - public class MeasureGuard : IDisposable + /// + /// Initializes a new instance of + /// + /// A instance to print the results + public MeasureFixture(ITestOutputHelper output) { - private readonly string operation; + this.Output = output; + } - private readonly Stopwatch stopwatch = new Stopwatch(); + protected ITestOutputHelper Output { get; } +} - public MeasureGuard(ITestOutputHelper output, string operation) - { - this.operation = operation; - this.Output = output; - this.Output.WriteLine(operation + " ..."); - this.stopwatch.Start(); - } +public class MeasureGuard : IDisposable +{ + private readonly string operation; - private ITestOutputHelper Output { get; } + private readonly Stopwatch stopwatch = new Stopwatch(); - public void Dispose() - { - this.stopwatch.Stop(); - this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms"); - } + public MeasureGuard(ITestOutputHelper output, string operation) + { + this.operation = operation; + this.Output = output; + this.Output.WriteLine(operation + " ..."); + this.stopwatch.Start(); + } + + private ITestOutputHelper Output { get; } + + public void Dispose() + { + this.stopwatch.Stop(); + this.Output.WriteLine($"{this.operation} completed in {this.stopwatch.ElapsedMilliseconds}ms"); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs b/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs index 511b2cef5e..2d13de0745 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PausedStream.cs @@ -1,143 +1,137 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.TestUtilities +public class PausedStream : Stream { - public class PausedStream : Stream - { - private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0); - private readonly CancellationTokenSource cancelationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource cancelationTokenSource = new CancellationTokenSource(); - private readonly Stream innerStream; - private Action onWaitingCallback; + private readonly Stream innerStream; + private Action onWaitingCallback; - public void OnWaiting(Action onWaitingCallback) => this.onWaitingCallback = onWaitingCallback; + public void OnWaiting(Action onWaitingCallback) => this.onWaitingCallback = onWaitingCallback; - public void OnWaiting(Action onWaitingCallback) => this.OnWaiting(_ => onWaitingCallback()); + public void OnWaiting(Action onWaitingCallback) => this.OnWaiting(_ => onWaitingCallback()); - public void Release() - { - this.semaphore.Release(); - this.cancelationTokenSource.Cancel(); - } + public void Release() + { + this.semaphore.Release(); + this.cancelationTokenSource.Cancel(); + } - public void Next() => this.semaphore.Release(); + public void Next() => this.semaphore.Release(); - private void Wait() + private void Wait() + { + if (this.cancelationTokenSource.IsCancellationRequested) { - if (this.cancelationTokenSource.IsCancellationRequested) - { - return; - } - - this.onWaitingCallback?.Invoke(this.innerStream); - - try - { - this.semaphore.Wait(this.cancelationTokenSource.Token); - } - catch (OperationCanceledException) - { - // ignore this as its just used to unlock any waits in progress - } + return; } - private async Task Await(Func action) - { - await Task.Yield(); - this.Wait(); - await action(); - } + this.onWaitingCallback?.Invoke(this.innerStream); - private async Task Await(Func> action) + try { - await Task.Yield(); - this.Wait(); - return await action(); + this.semaphore.Wait(this.cancelationTokenSource.Token); } - - private T Await(Func action) + catch (OperationCanceledException) { - this.Wait(); - return action(); + // ignore this as its just used to unlock any waits in progress } + } - private void Await(Action action) - { - this.Wait(); - action(); - } + private async Task Await(Func action) + { + await Task.Yield(); + this.Wait(); + await action(); + } - public PausedStream(byte[] data) - : this(new MemoryStream(data)) - { - } + private async Task Await(Func> action) + { + await Task.Yield(); + this.Wait(); + return await action(); + } - public PausedStream(string filePath) - : this(File.OpenRead(filePath)) - { - } + private T Await(Func action) + { + this.Wait(); + return action(); + } - public PausedStream(Stream innerStream) => this.innerStream = innerStream; + private void Await(Action action) + { + this.Wait(); + action(); + } - public override bool CanTimeout => this.innerStream.CanTimeout; + public PausedStream(byte[] data) + : this(new MemoryStream(data)) + { + } - public override void Close() => this.Await(() => this.innerStream.Close()); + public PausedStream(string filePath) + : this(File.OpenRead(filePath)) + { + } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => this.Await(() => this.innerStream.CopyToAsync(destination, bufferSize, cancellationToken)); + public PausedStream(Stream innerStream) => this.innerStream = innerStream; - public override bool CanRead => this.innerStream.CanRead; + public override bool CanTimeout => this.innerStream.CanTimeout; - public override bool CanSeek => this.innerStream.CanSeek; + public override void Close() => this.Await(() => this.innerStream.Close()); - public override bool CanWrite => this.innerStream.CanWrite; + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => this.Await(() => this.innerStream.CopyToAsync(destination, bufferSize, cancellationToken)); - public override long Length => this.Await(() => this.innerStream.Length); + public override bool CanRead => this.innerStream.CanRead; - public override long Position { get => this.Await(() => this.innerStream.Position); set => this.Await(() => this.innerStream.Position = value); } + public override bool CanSeek => this.innerStream.CanSeek; - public override void Flush() => this.Await(() => this.innerStream.Flush()); + public override bool CanWrite => this.innerStream.CanWrite; - public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count)); + public override long Length => this.Await(() => this.innerStream.Length); - public override long Seek(long offset, SeekOrigin origin) => this.Await(() => this.innerStream.Seek(offset, origin)); + public override long Position { get => this.Await(() => this.innerStream.Position); set => this.Await(() => this.innerStream.Position = value); } - public override void SetLength(long value) => this.Await(() => this.innerStream.SetLength(value)); + public override void Flush() => this.Await(() => this.innerStream.Flush()); - public override void Write(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Write(buffer, offset, count)); + public override int Read(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Read(buffer, offset, count)); - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.ReadAsync(buffer, offset, count, cancellationToken)); + public override long Seek(long offset, SeekOrigin origin) => this.Await(() => this.innerStream.Seek(offset, origin)); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.WriteAsync(buffer, offset, count, cancellationToken)); + public override void SetLength(long value) => this.Await(() => this.innerStream.SetLength(value)); - public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value)); + public override void Write(byte[] buffer, int offset, int count) => this.Await(() => this.innerStream.Write(buffer, offset, count)); - public override int ReadByte() => this.Await(() => this.innerStream.ReadByte()); + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.ReadAsync(buffer, offset, count, cancellationToken)); - protected override void Dispose(bool disposing) => this.innerStream.Dispose(); + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => this.Await(() => this.innerStream.WriteAsync(buffer, offset, count, cancellationToken)); - public override void CopyTo(Stream destination, int bufferSize) => this.Await(() => this.innerStream.CopyTo(destination, bufferSize)); + public override void WriteByte(byte value) => this.Await(() => this.innerStream.WriteByte(value)); - public override int Read(Span buffer) - { - this.Wait(); - return this.innerStream.Read(buffer); - } + public override int ReadByte() => this.Await(() => this.innerStream.ReadByte()); - public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.ReadAsync(buffer, cancellationToken)); + protected override void Dispose(bool disposing) => this.innerStream.Dispose(); - public override void Write(ReadOnlySpan buffer) - { - this.Wait(); - this.innerStream.Write(buffer); - } + public override void CopyTo(Stream destination, int bufferSize) => this.Await(() => this.innerStream.CopyTo(destination, bufferSize)); - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.WriteAsync(buffer, cancellationToken)); + public override int Read(Span buffer) + { + this.Wait(); + return this.innerStream.Read(buffer); } + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.ReadAsync(buffer, cancellationToken)); + + public override void Write(ReadOnlySpan buffer) + { + this.Wait(); + this.innerStream.Write(buffer); + } + + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) => this.Await(() => this.innerStream.WriteAsync(buffer, cancellationToken)); } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index e0a6ec860c..077a668c03 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -1,78 +1,75 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Flags that are mapped to PackedPixel types. +/// They trigger the desired parametrization for . +/// +[Flags] +public enum PixelTypes { - /// - /// Flags that are mapped to PackedPixel types. - /// They trigger the desired parametrization for . - /// - [Flags] - public enum PixelTypes - { - Undefined = 0, + Undefined = 0, - A8 = 1 << 0, + A8 = 1 << 0, - Argb32 = 1 << 1, + Argb32 = 1 << 1, - Bgr565 = 1 << 2, + Bgr565 = 1 << 2, - Bgra4444 = 1 << 3, + Bgra4444 = 1 << 3, - Byte4 = 1 << 4, + Byte4 = 1 << 4, - HalfSingle = 1 << 5, + HalfSingle = 1 << 5, - HalfVector2 = 1 << 6, + HalfVector2 = 1 << 6, - HalfVector4 = 1 << 7, + HalfVector4 = 1 << 7, - NormalizedByte2 = 1 << 8, + NormalizedByte2 = 1 << 8, - NormalizedByte4 = 1 << 9, + NormalizedByte4 = 1 << 9, - NormalizedShort4 = 1 << 10, + NormalizedShort4 = 1 << 10, - Rg32 = 1 << 11, + Rg32 = 1 << 11, - Rgba1010102 = 1 << 12, + Rgba1010102 = 1 << 12, - Rgba32 = 1 << 13, + Rgba32 = 1 << 13, - Rgba64 = 1 << 14, + Rgba64 = 1 << 14, - RgbaVector = 1 << 15, + RgbaVector = 1 << 15, - Short2 = 1 << 16, + Short2 = 1 << 16, - Short4 = 1 << 17, + Short4 = 1 << 17, - Rgb24 = 1 << 18, + Rgb24 = 1 << 18, - Bgr24 = 1 << 19, + Bgr24 = 1 << 19, - Bgra32 = 1 << 20, + Bgra32 = 1 << 20, - Rgb48 = 1 << 21, + Rgb48 = 1 << 21, - Bgra5551 = 1 << 22, + Bgra5551 = 1 << 22, - L8 = 1 << 23, + L8 = 1 << 23, - L16 = 1 << 24, + L16 = 1 << 24, - La16 = 1 << 25, + La16 = 1 << 25, - La32 = 1 << 26, + La32 = 1 << 26, - Abgr32 = 1 << 27, + Abgr32 = 1 << 27, - // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper - // "All" is handled as a separate, individual case instead of using bitwise OR - All = 30 - } + // "All" is handled as a separate, individual case instead of using bitwise OR + All = 30 } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs index 91f69dded1..6ba73cd279 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs @@ -1,94 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +/// +/// A Png encoder that uses the ImageSharp core encoder but the default configuration. +/// This allows encoding under environments with restricted memory. +/// +public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder, IPngEncoderOptions { + /// + public PngBitDepth? BitDepth { get; set; } + + /// + public PngColorType? ColorType { get; set; } + + /// + public PngFilterMethod? FilterMethod { get; set; } + + /// + public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; + + /// + public int TextCompressionThreshold { get; set; } = 1024; + + /// + public float? Gamma { get; set; } + + /// + public IQuantizer Quantizer { get; set; } + + /// + public byte Threshold { get; set; } = byte.MaxValue; + + /// + public PngInterlaceMode? InterlaceMethod { get; set; } + + /// + public PngChunkFilter? ChunkFilter { get; set; } + + /// + public bool IgnoreMetadata { get; set; } + + /// + public PngTransparentColorMode TransparentColorMode { get; set; } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + Configuration configuration = Configuration.Default; + MemoryAllocator allocator = configuration.MemoryAllocator; + + using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); + encoder.Encode(image, stream); + } + /// - /// A Png encoder that uses the ImageSharp core encoder but the default configuration. - /// This allows encoding under environments with restricted memory. + /// Encodes the image to the specified stream from the . /// - public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder, IPngEncoderOptions + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to monitor for cancellation requests. + /// A representing the asynchronous operation. + public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel { - /// - public PngBitDepth? BitDepth { get; set; } - - /// - public PngColorType? ColorType { get; set; } - - /// - public PngFilterMethod? FilterMethod { get; set; } - - /// - public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression; - - /// - public int TextCompressionThreshold { get; set; } = 1024; - - /// - public float? Gamma { get; set; } - - /// - public IQuantizer Quantizer { get; set; } - - /// - public byte Threshold { get; set; } = byte.MaxValue; - - /// - public PngInterlaceMode? InterlaceMethod { get; set; } - - /// - public PngChunkFilter? ChunkFilter { get; set; } - - /// - public bool IgnoreMetadata { get; set; } - - /// - public PngTransparentColorMode TransparentColorMode { get; set; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel - { - Configuration configuration = Configuration.Default; - MemoryAllocator allocator = configuration.MemoryAllocator; - - using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); - encoder.Encode(image, stream); - } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The pixel format. - /// The to encode from. - /// The to encode the image data to. - /// The token to monitor for cancellation requests. - /// A representing the asynchronous operation. - public async Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - Configuration configuration = Configuration.Default; - MemoryAllocator allocator = configuration.MemoryAllocator; - - // The introduction of a local variable that refers to an object the implements - // IDisposable means you must use async/await, where the compiler generates the - // state machine and a continuation. - using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); - await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); - } + Configuration configuration = Configuration.Default; + MemoryAllocator allocator = configuration.MemoryAllocator; + + // The introduction of a local variable that refers to an object the implements + // IDisposable means you must use async/await, where the compiler generates the + // state machine and a continuation. + using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this)); + await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 1f1d7febe0..57d5a8af42 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; -using System.Threading; using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; @@ -13,98 +9,97 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +public class MagickReferenceDecoder : IImageDecoder { - public class MagickReferenceDecoder : IImageDecoder + private readonly bool validate; + + public MagickReferenceDecoder() + : this(true) { - private readonly bool validate; + } - public MagickReferenceDecoder() - : this(true) - { - } + public MagickReferenceDecoder(bool validate) => this.validate = validate; + + public static MagickReferenceDecoder Instance { get; } = new(); - public MagickReferenceDecoder(bool validate) => this.validate = validate; + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + Configuration configuration = options.Configuration; + var bmpReadDefines = new BmpReadDefines + { + IgnoreFileSize = !this.validate + }; - public static MagickReferenceDecoder Instance { get; } = new(); + var settings = new MagickReadSettings(); + settings.SetDefines(bmpReadDefines); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + using var magickImageCollection = new MagickImageCollection(stream, settings); + var framesList = new List>(); + foreach (IMagickImage magicFrame in magickImageCollection) { - Configuration configuration = options.Configuration; - var bmpReadDefines = new BmpReadDefines - { - IgnoreFileSize = !this.validate - }; + var frame = new ImageFrame(configuration, magicFrame.Width, magicFrame.Height); + framesList.Add(frame); - var settings = new MagickReadSettings(); - settings.SetDefines(bmpReadDefines); + MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; - using var magickImageCollection = new MagickImageCollection(stream, settings); - var framesList = new List>(); - foreach (IMagickImage magicFrame in magickImageCollection) + using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); + if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) { - var frame = new ImageFrame(configuration, magicFrame.Width, magicFrame.Height); - framesList.Add(frame); - - MemoryGroup framePixels = frame.PixelBuffer.FastMemoryGroup; - - using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); - if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - FromRgba32Bytes(configuration, data, framePixels); - } - else if (magicFrame.Depth is 16 or 14) - { - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - FromRgba64Bytes(configuration, bytes, framePixels); - } - else - { - throw new InvalidOperationException(); - } - } + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - return new Image(configuration, new ImageMetadata(), framesList); + FromRgba32Bytes(configuration, data, framePixels); + } + else if (magicFrame.Depth is 16 or 14) + { + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); + Span bytes = MemoryMarshal.Cast(data.AsSpan()); + FromRgba64Bytes(configuration, bytes, framePixels); + } + else + { + throw new InvalidOperationException(); + } } - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + return new Image(configuration, new ImageMetadata(), framesList); + } + + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); - private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); + foreach (Memory m in destinationGroup) { - Span sourcePixels = MemoryMarshal.Cast(rgbaBytes); - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba32( - configuration, - sourcePixels.Slice(0, destBuffer.Length), - destBuffer); - sourcePixels = sourcePixels.Slice(destBuffer.Length); - } + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32( + configuration, + sourcePixels.Slice(0, destBuffer.Length), + destBuffer); + sourcePixels = sourcePixels.Slice(destBuffer.Length); } + } - private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + foreach (Memory m in destinationGroup) { - foreach (Memory m in destinationGroup) - { - Span destBuffer = m.Span; - PixelOperations.Instance.FromRgba64Bytes( - configuration, - rgbaBytes, - destBuffer, - destBuffer.Length); - rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); - } + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 6091936d1f..e57da55895 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; @@ -9,166 +8,165 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +/// +/// Provides methods to convert to/from System.Drawing bitmaps. +/// +public static class SystemDrawingBridge { /// - /// Provides methods to convert to/from System.Drawing bitmaps. + /// Returns an image from the given System.Drawing bitmap. /// - public static class SystemDrawingBridge + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) + where TPixel : unmanaged, IPixel { - /// - /// Returns an image from the given System.Drawing bitmap. - /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel - { - int w = bmp.Width; - int h = bmp.Height; + int w = bmp.Width; + int h = bmp.Height; - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); - if (bmp.PixelFormat != PixelFormat.Format32bppArgb) - { - throw new ArgumentException( - $"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", - nameof(bmp)); - } + if (bmp.PixelFormat != PixelFormat.Format32bppArgb) + { + throw new ArgumentException( + $"{nameof(From32bppArgbSystemDrawingBitmap)} : pixel format should be {PixelFormat.Format32bppArgb}!", + nameof(bmp)); + } - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - var image = new Image(w, h); - try - { - byte* sourcePtrBase = (byte*)data.Scan0; + BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); + var image = new Image(w, h); + try + { + byte* sourcePtrBase = (byte*)data.Scan0; - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgra32); + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgra32); - Configuration configuration = image.GetConfiguration(); - image.ProcessPixelRows(accessor => + Configuration configuration = image.GetConfiguration(); + image.ProcessPixelRows(accessor => + { + using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); + fixed (Bgra32* destPtr = &workBuffer.GetReference()) { - using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); - fixed (Bgra32* destPtr = &workBuffer.GetReference()) + for (int y = 0; y < h; y++) { - for (int y = 0; y < h; y++) - { - Span row = accessor.GetRowSpan(y); - - byte* sourcePtr = sourcePtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgra32( - configuration, - workBuffer.GetSpan().Slice(0, w), - row); - } - } - }); - } - finally - { - bmp.UnlockBits(data); - } + Span row = accessor.GetRowSpan(y); - return image; - } + byte* sourcePtr = sourcePtrBase + (data.Stride * y); - /// - /// Returns an image from the given System.Drawing bitmap. - /// - /// The pixel format. - /// The input bitmap. - /// Thrown if the image pixel format is not of type - internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : unmanaged, IPixel + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + PixelOperations.Instance.FromBgra32( + configuration, + workBuffer.GetSpan().Slice(0, w), + row); + } + } + }); + } + finally { - int w = bmp.Width; - int h = bmp.Height; + bmp.UnlockBits(data); + } - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + return image; + } - if (bmp.PixelFormat != PixelFormat.Format24bppRgb) - { - throw new ArgumentException( - $"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", - nameof(bmp)); - } + /// + /// Returns an image from the given System.Drawing bitmap. + /// + /// The pixel format. + /// The input bitmap. + /// Thrown if the image pixel format is not of type + internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) + where TPixel : unmanaged, IPixel + { + int w = bmp.Width; + int h = bmp.Height; - BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); - var image = new Image(w, h); - try - { - byte* sourcePtrBase = (byte*)data.Scan0; + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); - long sourceRowByteCount = data.Stride; - long destRowByteCount = w * sizeof(Bgr24); + if (bmp.PixelFormat != PixelFormat.Format24bppRgb) + { + throw new ArgumentException( + $"{nameof(From24bppRgbSystemDrawingBitmap)}: pixel format should be {PixelFormat.Format24bppRgb}!", + nameof(bmp)); + } + + BitmapData data = bmp.LockBits(fullRect, ImageLockMode.ReadWrite, bmp.PixelFormat); + var image = new Image(w, h); + try + { + byte* sourcePtrBase = (byte*)data.Scan0; - Configuration configuration = image.GetConfiguration(); - Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + long sourceRowByteCount = data.Stride; + long destRowByteCount = w * sizeof(Bgr24); - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + Configuration configuration = image.GetConfiguration(); + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + + using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + { + fixed (Bgr24* destPtr = &workBuffer.GetReference()) { - fixed (Bgr24* destPtr = &workBuffer.GetReference()) + for (int y = 0; y < h; y++) { - for (int y = 0; y < h; y++) - { - Span row = imageBuffer.DangerousGetRowSpan(y); + Span row = imageBuffer.DangerousGetRowSpan(y); - byte* sourcePtr = sourcePtrBase + (data.Stride * y); + byte* sourcePtr = sourcePtrBase + (data.Stride * y); - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - PixelOperations.Instance.FromBgr24(configuration, workBuffer.GetSpan().Slice(0, w), row); - } + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + PixelOperations.Instance.FromBgr24(configuration, workBuffer.GetSpan().Slice(0, w), row); } } } - finally - { - bmp.UnlockBits(data); - } - - return image; } - - internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : unmanaged, IPixel + finally { - Configuration configuration = image.GetConfiguration(); - int w = image.Width; - int h = image.Height; + bmp.UnlockBits(data); + } - var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); - var fullRect = new System.Drawing.Rectangle(0, 0, w, h); - BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); - try + return image; + } + + internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) + where TPixel : unmanaged, IPixel + { + Configuration configuration = image.GetConfiguration(); + int w = image.Width; + int h = image.Height; + + var resultBitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb); + var fullRect = new System.Drawing.Rectangle(0, 0, w, h); + BitmapData data = resultBitmap.LockBits(fullRect, ImageLockMode.ReadWrite, resultBitmap.PixelFormat); + try + { + byte* destPtrBase = (byte*)data.Scan0; + long destRowByteCount = data.Stride; + long sourceRowByteCount = w * sizeof(Bgra32); + image.ProcessPixelRows(accessor => { - byte* destPtrBase = (byte*)data.Scan0; - long destRowByteCount = data.Stride; - long sourceRowByteCount = w * sizeof(Bgra32); - image.ProcessPixelRows(accessor => + using IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w); + fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { - using IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w); - fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) + for (int y = 0; y < h; y++) { - for (int y = 0; y < h; y++) - { - Span row = accessor.GetRowSpan(y); - PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); - byte* destPtr = destPtrBase + (data.Stride * y); - - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - } - } - }); - } - finally - { - resultBitmap.UnlockBits(data); - } + Span row = accessor.GetRowSpan(y); + PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); + byte* destPtr = destPtrBase + (data.Stride * y); - return resultBitmap; + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + } + } + }); } + finally + { + resultBitmap.UnlockBits(data); + } + + return resultBitmap; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index e14866f818..53d9410c5e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -1,54 +1,51 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SDBitmap = System.Drawing.Bitmap; using SDImage = System.Drawing.Image; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +public class SystemDrawingReferenceDecoder : IImageDecoder { - public class SystemDrawingReferenceDecoder : IImageDecoder + public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); + + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { - public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); + using var sourceBitmap = new SDBitmap(stream); + PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); + return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); + } - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using var sourceBitmap = new SDBitmap(stream); + if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) { - using var sourceBitmap = new SDBitmap(stream); - PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat)); - return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); } - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + using var convertedBitmap = new SDBitmap( + sourceBitmap.Width, + sourceBitmap.Height, + System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (var g = System.Drawing.Graphics.FromImage(convertedBitmap)) { - using var sourceBitmap = new SDBitmap(stream); - if (sourceBitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb) - { - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sourceBitmap); - } - - using var convertedBitmap = new SDBitmap( - sourceBitmap.Width, - sourceBitmap.Height, - System.Drawing.Imaging.PixelFormat.Format32bppArgb); - using (var g = System.Drawing.Graphics.FromImage(convertedBitmap)) - { - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; - - g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); - } - - return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + + g.DrawImage(sourceBitmap, 0, 0, sourceBitmap.Width, sourceBitmap.Height); } - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + return SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(convertedBitmap); } + + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 9d1a75bf77..af13d64ce2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -2,45 +2,41 @@ // Licensed under the Six Labors Split License. using System.Drawing.Imaging; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs +namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +public class SystemDrawingReferenceEncoder : IImageEncoder { - public class SystemDrawingReferenceEncoder : IImageEncoder - { - private readonly System.Drawing.Imaging.ImageFormat imageFormat; + private readonly System.Drawing.Imaging.ImageFormat imageFormat; - public SystemDrawingReferenceEncoder(ImageFormat imageFormat) - { - this.imageFormat = imageFormat; - } + public SystemDrawingReferenceEncoder(ImageFormat imageFormat) + { + this.imageFormat = imageFormat; + } - public static SystemDrawingReferenceEncoder Png { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Png); + public static SystemDrawingReferenceEncoder Png { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Png); - public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); + public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); - public void Encode(Image image, Stream stream) - where TPixel : unmanaged, IPixel + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) - { - sdBitmap.Save(stream, this.imageFormat); - } + sdBitmap.Save(stream, this.imageFormat); } + } - public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { - using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) - { - sdBitmap.Save(stream, this.imageFormat); - } - - return Task.CompletedTask; + sdBitmap.Save(stream, this.imageFormat); } + + return Task.CompletedTask; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs index db9affd019..c083ea9072 100644 --- a/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs +++ b/tests/ImageSharp.Tests/TestUtilities/SemaphoreReadMemoryStream.cs @@ -1,68 +1,62 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace SixLabors.ImageSharp.Tests.TestUtilities; -namespace SixLabors.ImageSharp.Tests.TestUtilities +internal class SemaphoreReadMemoryStream : MemoryStream { - internal class SemaphoreReadMemoryStream : MemoryStream + private readonly SemaphoreSlim continueSemaphore; + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore; + private int pauseDone; + private readonly long waitPosition; + + public SemaphoreReadMemoryStream( + byte[] buffer, + long waitPosition, + SemaphoreSlim notifyWaitPositionReachedSemaphore, + SemaphoreSlim continueSemaphore) + : base(buffer) { - private readonly SemaphoreSlim continueSemaphore; - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore; - private int pauseDone; - private readonly long waitPosition; + this.continueSemaphore = continueSemaphore; + this.notifyWaitPositionReachedSemaphore = notifyWaitPositionReachedSemaphore; + this.waitPosition = waitPosition; + } - public SemaphoreReadMemoryStream( - byte[] buffer, - long waitPosition, - SemaphoreSlim notifyWaitPositionReachedSemaphore, - SemaphoreSlim continueSemaphore) - : base(buffer) + public override int Read(byte[] buffer, int offset, int count) + { + int read = base.Read(buffer, offset, count); + if (this.Position > this.waitPosition && this.TryPause()) { - this.continueSemaphore = continueSemaphore; - this.notifyWaitPositionReachedSemaphore = notifyWaitPositionReachedSemaphore; - this.waitPosition = waitPosition; + this.notifyWaitPositionReachedSemaphore.Release(); + this.continueSemaphore.Wait(); } - public override int Read(byte[] buffer, int offset, int count) - { - int read = base.Read(buffer, offset, count); - if (this.Position > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - this.continueSemaphore.Wait(); - } - - return read; - } + return read; + } - private bool TryPause() => Interlocked.CompareExchange(ref this.pauseDone, 1, 0) == 0; + private bool TryPause() => Interlocked.CompareExchange(ref this.pauseDone, 1, 0) == 0; - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int read = await base.ReadAsync(buffer, offset, count, cancellationToken); + if (this.Position > this.waitPosition && this.TryPause()) { - int read = await base.ReadAsync(buffer, offset, count, cancellationToken); - if (this.Position > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - await this.continueSemaphore.WaitAsync(); - } - - return read; + this.notifyWaitPositionReachedSemaphore.Release(); + await this.continueSemaphore.WaitAsync(); } - public override int ReadByte() - { - if (this.Position + 1 > this.waitPosition && this.TryPause()) - { - this.notifyWaitPositionReachedSemaphore.Release(); - this.continueSemaphore.Wait(); - } + return read; + } - int result = base.ReadByte(); - return result; + public override int ReadByte() + { + if (this.Position + 1 > this.waitPosition && this.TryPause()) + { + this.notifyWaitPositionReachedSemaphore.Release(); + this.continueSemaphore.Wait(); } + + int result = base.ReadByte(); + return result; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs index d7e3f0a60a..732948b8e0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs +++ b/tests/ImageSharp.Tests/TestUtilities/SingleStreamFileSystem.cs @@ -1,19 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.IO; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +internal class SingleStreamFileSystem : IFileSystem { - internal class SingleStreamFileSystem : IFileSystem - { - private readonly Stream stream; + private readonly Stream stream; - public SingleStreamFileSystem(Stream stream) => this.stream = stream; + public SingleStreamFileSystem(Stream stream) => this.stream = stream; - Stream IFileSystem.Create(string path) => this.stream; + Stream IFileSystem.Create(string path) => this.stream; - Stream IFileSystem.OpenRead(string path) => this.stream; - } + Stream IFileSystem.OpenRead(string path) => this.stream; } diff --git a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs b/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs index ab4240fcc4..edae08ce16 100644 --- a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs +++ b/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs @@ -8,18 +8,17 @@ [assembly: Xunit.TestFramework(SixLaborsXunitTestFramework.Type, SixLaborsXunitTestFramework.Assembly)] -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +public class SixLaborsXunitTestFramework : XunitTestFramework { - public class SixLaborsXunitTestFramework : XunitTestFramework - { - public const string Type = "SixLabors.ImageSharp.Tests.TestUtilities.SixLaborsXunitTestFramework"; - public const string Assembly = "SixLabors.ImageSharp.Tests"; + public const string Type = "SixLabors.ImageSharp.Tests.TestUtilities.SixLaborsXunitTestFramework"; + public const string Assembly = "SixLabors.ImageSharp.Tests"; - public SixLaborsXunitTestFramework(IMessageSink messageSink) - : base(messageSink) - { - var message = new DiagnosticMessage(HostEnvironmentInfo.GetInformation()); - messageSink.OnMessage(message); - } + public SixLaborsXunitTestFramework(IMessageSink messageSink) + : base(messageSink) + { + var message = new DiagnosticMessage(HostEnvironmentInfo.GetInformation()); + messageSink.OnMessage(message); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs index e4b0cbd70c..31de3909e1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs @@ -1,109 +1,107 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Helper methods that allow the creation of random test data. +/// +internal static class TestDataGenerator { /// - /// Helper methods that allow the creation of random test data. + /// Creates an of the given length consisting of random values between the two ranges. /// - internal static class TestDataGenerator + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static float[] GenerateRandomFloatArray(this Random rnd, int length, float minVal, float maxVal) { - /// - /// Creates an of the given length consisting of random values between the two ranges. - /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static float[] GenerateRandomFloatArray(this Random rnd, int length, float minVal, float maxVal) - { - var values = new float[length]; + var values = new float[length]; - RandomFill(rnd, values, minVal, maxVal); + RandomFill(rnd, values, minVal, maxVal); - return values; - } + return values; + } - public static void RandomFill(this Random rnd, Span destination, float minVal, float maxVal) + public static void RandomFill(this Random rnd, Span destination, float minVal, float maxVal) + { + for (int i = 0; i < destination.Length; i++) { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = GetRandomFloat(rnd, minVal, maxVal); - } + destination[i] = GetRandomFloat(rnd, minVal, maxVal); } + } - /// - /// Creates an of the given length consisting of random values between the two ranges. - /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static Vector4[] GenerateRandomVectorArray(this Random rnd, int length, float minVal, float maxVal) - { - var values = new Vector4[length]; - - for (int i = 0; i < length; i++) - { - ref Vector4 v = ref values[i]; - v.X = GetRandomFloat(rnd, minVal, maxVal); - v.Y = GetRandomFloat(rnd, minVal, maxVal); - v.Z = GetRandomFloat(rnd, minVal, maxVal); - v.W = GetRandomFloat(rnd, minVal, maxVal); - } - - return values; - } + /// + /// Creates an of the given length consisting of random values between the two ranges. + /// + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static Vector4[] GenerateRandomVectorArray(this Random rnd, int length, float minVal, float maxVal) + { + var values = new Vector4[length]; - /// - /// Creates an of the given length consisting of rounded random values between the two ranges. - /// - /// The pseudo-random number generator. - /// The length. - /// The minimum value. - /// The maximum value. - /// The . - public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal) + for (int i = 0; i < length; i++) { - var values = new float[length]; + ref Vector4 v = ref values[i]; + v.X = GetRandomFloat(rnd, minVal, maxVal); + v.Y = GetRandomFloat(rnd, minVal, maxVal); + v.Z = GetRandomFloat(rnd, minVal, maxVal); + v.W = GetRandomFloat(rnd, minVal, maxVal); + } - for (int i = 0; i < length; i++) - { - values[i] = (float)Math.Round(rnd.GetRandomFloat(minVal, maxVal)); - } + return values; + } - return values; - } + /// + /// Creates an of the given length consisting of rounded random values between the two ranges. + /// + /// The pseudo-random number generator. + /// The length. + /// The minimum value. + /// The maximum value. + /// The . + public static float[] GenerateRandomRoundedFloatArray(this Random rnd, int length, float minVal, float maxVal) + { + var values = new float[length]; - /// - /// Creates an of the given length consisting of random values. - /// - /// The pseudo-random number generator. - /// The length. - /// The . - public static byte[] GenerateRandomByteArray(this Random rnd, int length) + for (int i = 0; i < length; i++) { - var values = new byte[length]; - rnd.NextBytes(values); - return values; + values[i] = (float)Math.Round(rnd.GetRandomFloat(minVal, maxVal)); } - public static short[] GenerateRandomInt16Array(this Random rnd, int length, short minVal, short maxVal) - { - var values = new short[length]; - for (int i = 0; i < values.Length; i++) - { - values[i] = (short)rnd.Next(minVal, maxVal); - } + return values; + } - return values; + /// + /// Creates an of the given length consisting of random values. + /// + /// The pseudo-random number generator. + /// The length. + /// The . + public static byte[] GenerateRandomByteArray(this Random rnd, int length) + { + var values = new byte[length]; + rnd.NextBytes(values); + return values; + } + + public static short[] GenerateRandomInt16Array(this Random rnd, int length, short minVal, short maxVal) + { + var values = new short[length]; + for (int i = 0; i < values.Length; i++) + { + values[i] = (short)rnd.Next(minVal, maxVal); } - private static float GetRandomFloat(this Random rnd, float minVal, float maxVal) => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; + return values; } + + private static float GetRandomFloat(this Random rnd, float minVal, float maxVal) => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 7e4797c5ab..89b43a0661 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -14,72 +12,71 @@ using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public static partial class TestEnvironment { - public static partial class TestEnvironment - { - private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); + private static readonly Lazy ConfigurationLazy = new(CreateDefaultConfiguration); - internal static Configuration Configuration => ConfigurationLazy.Value; + internal static Configuration Configuration => ConfigurationLazy.Value; - internal static IImageDecoder GetReferenceDecoder(string filePath) - { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.FindDecoder(format); - } + internal static IImageDecoder GetReferenceDecoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.FindDecoder(format); + } - internal static IImageEncoder GetReferenceEncoder(string filePath) - { - IImageFormat format = GetImageFormat(filePath); - return Configuration.ImageFormatsManager.FindEncoder(format); - } + internal static IImageEncoder GetReferenceEncoder(string filePath) + { + IImageFormat format = GetImageFormat(filePath); + return Configuration.ImageFormatsManager.FindEncoder(format); + } - internal static IImageFormat GetImageFormat(string filePath) - { - string extension = Path.GetExtension(filePath); + internal static IImageFormat GetImageFormat(string filePath) + { + string extension = Path.GetExtension(filePath); - return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); - } + return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); + } - private static void ConfigureCodecs( - this Configuration cfg, - IImageFormat imageFormat, - IImageDecoder decoder, - IImageEncoder encoder, - IImageFormatDetector detector) - { - cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); - cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); - cfg.ImageFormatsManager.AddImageFormatDetector(detector); - } + private static void ConfigureCodecs( + this Configuration cfg, + IImageFormat imageFormat, + IImageDecoder decoder, + IImageEncoder encoder, + IImageFormatDetector detector) + { + cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); + cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); + cfg.ImageFormatsManager.AddImageFormatDetector(detector); + } - private static Configuration CreateDefaultConfiguration() - { - var cfg = new Configuration( - new JpegConfigurationModule(), - new GifConfigurationModule(), - new PbmConfigurationModule(), - new TgaConfigurationModule(), - new WebpConfigurationModule(), - new TiffConfigurationModule()); + private static Configuration CreateDefaultConfiguration() + { + var cfg = new Configuration( + new JpegConfigurationModule(), + new GifConfigurationModule(), + new PbmConfigurationModule(), + new TgaConfigurationModule(), + new WebpConfigurationModule(), + new TiffConfigurationModule()); - IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); - IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); + IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); + IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); - // Magick codecs should work on all platforms - cfg.ConfigureCodecs( - PngFormat.Instance, - MagickReferenceDecoder.Instance, - pngEncoder, - new PngImageFormatDetector()); + // Magick codecs should work on all platforms + cfg.ConfigureCodecs( + PngFormat.Instance, + MagickReferenceDecoder.Instance, + pngEncoder, + new PngImageFormatDetector()); - cfg.ConfigureCodecs( - BmpFormat.Instance, - IsWindows ? SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance, - bmpEncoder, - new BmpImageFormatDetector()); + cfg.ConfigureCodecs( + BmpFormat.Instance, + IsWindows ? SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance, + bmpEncoder, + new BmpImageFormatDetector()); - return cfg; - } + return cfg; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 068ea2964d..113429ead7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -1,285 +1,281 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public static partial class TestEnvironment { - public static partial class TestEnvironment - { - private const string ImageSharpSolutionFileName = "ImageSharp.sln"; + private const string ImageSharpSolutionFileName = "ImageSharp.sln"; - private const string InputImagesRelativePath = @"tests\Images\Input"; + private const string InputImagesRelativePath = @"tests\Images\Input"; - private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; + private const string ActualOutputDirectoryRelativePath = @"tests\Images\ActualOutput"; - private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; + private const string ReferenceOutputDirectoryRelativePath = @"tests\Images\External\ReferenceOutput"; - private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; + private const string ToolsDirectoryRelativePath = @"tests\Images\External\tools"; - private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); + private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); - private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); - static TestEnvironment() => PrepareRemoteExecutor(); + static TestEnvironment() => PrepareRemoteExecutor(); - /// - /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. - /// - internal static Version NetCoreVersion => NetCoreVersionLazy.Value; + /// + /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. + /// + internal static Version NetCoreVersion => NetCoreVersionLazy.Value; - // ReSharper disable once InconsistentNaming + // ReSharper disable once InconsistentNaming - /// - /// Gets a value indicating whether test execution runs on CI. - /// + /// + /// Gets a value indicating whether test execution runs on CI. + /// #if ENV_CI - internal static bool RunsOnCI => true; + internal static bool RunsOnCI => true; #else - internal static bool RunsOnCI => false; + internal static bool RunsOnCI => false; #endif - /// - /// Gets a value indicating whether test execution is running with code coverage testing enabled. - /// + /// + /// Gets a value indicating whether test execution is running with code coverage testing enabled. + /// #if ENV_CODECOV - internal static bool RunsWithCodeCoverage => true; + internal static bool RunsWithCodeCoverage => true; #else - internal static bool RunsWithCodeCoverage => false; + internal static bool RunsWithCodeCoverage => false; #endif - internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; + internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; - private static readonly FileInfo TestAssemblyFile = - new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); + private static readonly FileInfo TestAssemblyFile = + new FileInfo(typeof(TestEnvironment).GetTypeInfo().Assembly.Location); - private static string GetSolutionDirectoryFullPathImpl() - { - DirectoryInfo directory = TestAssemblyFile.Directory; + private static string GetSolutionDirectoryFullPathImpl() + { + DirectoryInfo directory = TestAssemblyFile.Directory; - while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) + while (!directory.EnumerateFiles(ImageSharpSolutionFileName).Any()) + { + try { - try - { - directory = directory.Parent; - } - catch (Exception ex) - { - throw new DirectoryNotFoundException( - $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", - ex); - } - - if (directory == null) - { - throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); - } + directory = directory.Parent; + } + catch (Exception ex) + { + throw new DirectoryNotFoundException( + $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", + ex); } - return directory.FullName; + if (directory == null) + { + throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); + } } - private static string GetFullPath(string relativePath) => - Path.Combine(SolutionDirectoryFullPath, relativePath) - .Replace('\\', Path.DirectorySeparatorChar); + return directory.FullName; + } + + private static string GetFullPath(string relativePath) => + Path.Combine(SolutionDirectoryFullPath, relativePath) + .Replace('\\', Path.DirectorySeparatorChar); - /// - /// Gets the correct full path to the Input Images directory. - /// - internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); + /// + /// Gets the correct full path to the Input Images directory. + /// + internal static string InputImagesDirectoryFullPath => GetFullPath(InputImagesRelativePath); - /// - /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) - /// - internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); + /// + /// Gets the correct full path to the Actual Output directory. (To be written to by the test cases.) + /// + internal static string ActualOutputDirectoryFullPath => GetFullPath(ActualOutputDirectoryRelativePath); - /// - /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) - /// - internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); + /// + /// Gets the correct full path to the Expected Output directory. (To compare the test results to.) + /// + internal static string ReferenceOutputDirectoryFullPath => GetFullPath(ReferenceOutputDirectoryRelativePath); - internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); + internal static string ToolsDirectoryFullPath => GetFullPath(ToolsDirectoryRelativePath); - internal static string GetReferenceOutputFileName(string actualOutputFileName) => - actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); + internal static string GetReferenceOutputFileName(string actualOutputFileName) => + actualOutputFileName.Replace("ActualOutput", @"External\ReferenceOutput").Replace('\\', Path.DirectorySeparatorChar); - internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 + internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 - internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - internal static bool Is64BitProcess => IntPtr.Size == 8; + internal static bool Is64BitProcess => IntPtr.Size == 8; - internal static bool IsFramework => NetCoreVersion == null; + internal static bool IsFramework => NetCoreVersion == null; - /// - /// A dummy operation to enforce the execution of the static constructor. - /// - internal static void EnsureSharedInitializersDone() + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + + /// + /// Creates the image output directory. + /// + /// The path. + /// The path parts. + /// + /// The . + /// + internal static string CreateOutputDirectory(string path, params string[] pathParts) + { + path = Path.Combine(ActualOutputDirectoryFullPath, path); + + if (pathParts != null && pathParts.Length > 0) { + path = Path.Combine(path, Path.Combine(pathParts)); } - /// - /// Creates the image output directory. - /// - /// The path. - /// The path parts. - /// - /// The . - /// - internal static string CreateOutputDirectory(string path, params string[] pathParts) + if (!Directory.Exists(path)) { - path = Path.Combine(ActualOutputDirectoryFullPath, path); - - if (pathParts != null && pathParts.Length > 0) - { - path = Path.Combine(path, Path.Combine(pathParts)); - } + Directory.CreateDirectory(path); + } - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } + return path; + } - return path; + /// + /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, + /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe + /// with the help of CorFlags.exe found in Windows SDK. + /// + private static void PrepareRemoteExecutor() + { + if (!IsFramework || !Environment.Is64BitProcess) + { + return; } - /// - /// Creates Microsoft.DotNet.RemoteExecutor.exe.config for .NET framework, - /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe - /// with the help of CorFlags.exe found in Windows SDK. - /// - private static void PrepareRemoteExecutor() + string remoteExecutorConfigPath = + Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + + if (File.Exists(remoteExecutorConfigPath)) { - if (!IsFramework || !Environment.Is64BitProcess) - { - return; - } + // Already initialized + return; + } - string remoteExecutorConfigPath = - Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe.config"); + string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; + if (File.Exists(testProjectConfigPath)) + { + File.Copy(testProjectConfigPath, remoteExecutorConfigPath); + } - if (File.Exists(remoteExecutorConfigPath)) - { - // Already initialized - return; - } + if (Is64BitProcess) + { + return; + } - string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; - if (File.Exists(testProjectConfigPath)) - { - File.Copy(testProjectConfigPath, remoteExecutorConfigPath); - } + EnsureRemoteExecutorIs32Bit(); + } - if (Is64BitProcess) - { - return; - } + /// + /// Locate and run CorFlags.exe /32Bit+ + /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool + /// + private static void EnsureRemoteExecutorIs32Bit() + { + string windowsSdksDir = Path.Combine( + Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), + "Microsoft SDKs", + "Windows"); - EnsureRemoteExecutorIs32Bit(); - } + FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); - /// - /// Locate and run CorFlags.exe /32Bit+ - /// https://docs.microsoft.com/en-us/dotnet/framework/tools/corflags-exe-corflags-conversion-tool - /// - private static void EnsureRemoteExecutorIs32Bit() - { - string windowsSdksDir = Path.Combine( - Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), - "Microsoft SDKs", - "Windows"); + string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); - FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; - string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } - string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); - if (File.Exists(remoteExecutorTmpPath)) - { - // Already initialized - return; - } + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; - File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + var si = new ProcessStartInfo() + { + FileName = corFlagsFile.FullName, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using var proc = Process.Start(si); + proc.WaitForExit(); + string standardOutput = proc.StandardOutput.ReadToEnd(); + string standardError = proc.StandardError.ReadToEnd(); + + if (proc.ExitCode != 0) + { + throw new Exception( + $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); + } - string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); - var si = new ProcessStartInfo() - { - FileName = corFlagsFile.FullName, - Arguments = args, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - using var proc = Process.Start(si); - proc.WaitForExit(); - string standardOutput = proc.StandardOutput.ReadToEnd(); - string standardError = proc.StandardError.ReadToEnd(); - - if (proc.ExitCode != 0) + static FileInfo Find(DirectoryInfo root, string name) + { + FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + if (fi != null) { - throw new Exception( - $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); + return fi; } - File.Delete(remoteExecutorPath); - File.Copy(remoteExecutorTmpPath, remoteExecutorPath); - - static FileInfo Find(DirectoryInfo root, string name) + foreach (DirectoryInfo dir in root.EnumerateDirectories()) { - FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); + fi = Find(dir, name); if (fi != null) { return fi; } - - foreach (DirectoryInfo dir in root.EnumerateDirectories()) - { - fi = Find(dir, name); - if (fi != null) - { - return fi; - } - } - - return null; } + + return null; } + } - /// - /// Solution borrowed from: - /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 - /// - private static Version GetNetCoreVersion() + /// + /// Solution borrowed from: + /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 + /// + private static Version GetNetCoreVersion() + { + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + string runtimeFolderStr = assemblyPath[netCoreAppIndex + 1]; + int previewSuffix = runtimeFolderStr.IndexOf('-'); + if (previewSuffix > 0) { - string runtimeFolderStr = assemblyPath[netCoreAppIndex + 1]; - int previewSuffix = runtimeFolderStr.IndexOf('-'); - if (previewSuffix > 0) - { - runtimeFolderStr = runtimeFolderStr.Substring(0, previewSuffix); - } - - return Version.Parse(runtimeFolderStr); + runtimeFolderStr = runtimeFolderStr.Substring(0, previewSuffix); } - return null; + return Version.Parse(runtimeFolderStr); } + + return null; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 7d3a536c88..5032f8de58 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.IO; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; @@ -15,793 +12,790 @@ using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public static class TestImageExtensions { - public static class TestImageExtensions + /// + /// TODO: Consider adding this private processor to the library + /// + public static void MakeOpaque(this IImageProcessingContext ctx) => + ctx.ApplyProcessor(new MakeOpaqueProcessor()); + + public static void DebugSave( + this Image image, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true, + IImageEncoder encoder = null) + => image.DebugSave( + provider, + (object)testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription, + encoder); + + /// + /// Saves the image for debugging purpose. + /// + /// The 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 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, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true, + IImageEncoder encoder = null) { - /// - /// TODO: Consider adding this private processor to the library - /// - public static void MakeOpaque(this IImageProcessingContext ctx) => - ctx.ApplyProcessor(new MakeOpaqueProcessor()); - - public static void DebugSave( - this Image image, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageEncoder encoder = null) - => image.DebugSave( - provider, - (object)testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - encoder); - - /// - /// Saves the image for debugging purpose. - /// - /// The 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 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, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageEncoder encoder = null) + if (TestEnvironment.RunsWithCodeCoverage) { - if (TestEnvironment.RunsWithCodeCoverage) - { - return image; - } - - provider.Utility.SaveTestOutputFile( - image, - extension, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription, - encoder: encoder); return image; } - public static void DebugSave( - this Image image, - ITestImageProvider provider, - IImageEncoder encoder, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true) - => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); - - /// - /// Saves the image for debugging purpose. - /// - /// The image - /// The image provider - /// The image encoder - /// Details to be concatenated to the test output file, describing the parameters of the test. - /// A boolean indicating whether to append the pixel type to the output file name. - public static void DebugSave( - this Image image, - ITestImageProvider provider, - IImageEncoder encoder, - object testOutputDetails = null, - bool appendPixelTypeToFileName = true) - => provider.Utility.SaveTestOutputFile( - image, - encoder: encoder, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName); - - public static Image DebugSaveMultiFrame( - this Image image, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - if (TestEnvironment.RunsWithCodeCoverage) - { - return image; - } - - provider.Utility.SaveTestOutputFileMultiFrame( - image, - extension, - testOutputDetails: testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName); - - return image; - } + provider.Utility.SaveTestOutputFile( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription, + encoder: encoder); + return image; + } - public static Image CompareToReferenceOutput( - this Image image, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => image.CompareToReferenceOutput( - provider, - (object)testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// 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 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, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => CompareToReferenceOutput( - image, - ImageComparer.Tolerant(), - provider, - testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - public static Image CompareToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - => image.CompareToReferenceOutput( - comparer, - provider, - (object)testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName); - - /// - /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. - /// The output file should be named identically to the output produced by . - /// - /// 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, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageDecoder decoder = null) - where TPixel : unmanaged, IPixel + public static void DebugSave( + this Image image, + ITestImageProvider provider, + IImageEncoder encoder, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true) + => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); + + /// + /// Saves the image for debugging purpose. + /// + /// The image + /// The image provider + /// The image encoder + /// Details to be concatenated to the test output file, describing the parameters of the test. + /// A boolean indicating whether to append the pixel type to the output file name. + public static void DebugSave( + this Image image, + ITestImageProvider provider, + IImageEncoder encoder, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + => provider.Utility.SaveTestOutputFile( + image, + encoder: encoder, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + + public static Image DebugSaveMultiFrame( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.RunsWithCodeCoverage) { - using (Image referenceImage = GetReferenceOutputImage( - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription, - decoder)) - { - comparer.VerifySimilarity(referenceImage, image); - } - return image; } - public static Image CompareFirstFrameToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - FormattableString testOutputDetails, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => image.CompareFirstFrameToReferenceOutput( - comparer, - provider, - (object)testOutputDetails, - extension, - grayscale, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - public static Image CompareFirstFrameToReferenceOutput( - this Image image, - ImageComparer comparer, - ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - { - using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) - using (Image referenceImage = GetReferenceOutputImage( - provider, - testOutputDetails, - extension, - appendPixelTypeToFileName, - appendSourceFileOrDescription)) - { - firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); - firstFrameOnlyImage.Frames.RemoveFrame(0); + provider.Utility.SaveTestOutputFileMultiFrame( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); - comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); - } + return image; + } - return image; + public static Image CompareToReferenceOutput( + this Image image, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => image.CompareToReferenceOutput( + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + /// + /// 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 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, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => CompareToReferenceOutput( + image, + ImageComparer.Tolerant(), + provider, + testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + public static Image CompareToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + => image.CompareToReferenceOutput( + comparer, + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName); + + /// + /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. + /// The output file should be named identically to the output produced by . + /// + /// 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, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel + { + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription, + decoder)) + { + comparer.VerifySimilarity(referenceImage, image); } - public static Image CompareToReferenceOutputMultiFrame( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - object testOutputDetails = null, - string extension = "png", - bool grayscale = false, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel + return image; + } + + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + FormattableString testOutputDetails, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => image.CompareFirstFrameToReferenceOutput( + comparer, + provider, + (object)testOutputDetails, + extension, + grayscale, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ImageComparer comparer, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + { + using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName, + appendSourceFileOrDescription)) { - using (Image referenceImage = GetReferenceOutputImageMultiFrame( - provider, - image.Frames.Count, - testOutputDetails, - extension, - appendPixelTypeToFileName)) - { - comparer.VerifySimilarity(referenceImage, image); - } + firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); + firstFrameOnlyImage.Frames.RemoveFrame(0); - return image; + comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); } - 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, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); + return image; + } - if (!File.Exists(referenceOutputFile)) - { - throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); - } + public static Image CompareToReferenceOutputMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + using (Image referenceImage = GetReferenceOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName)) + { + comparer.VerifySimilarity(referenceImage, image); + } - decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); + return image; + } - using FileStream stream = File.OpenRead(referenceOutputFile); - return decoder.Decode(DecoderOptions.Default, stream, default); - } + 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, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); - public static Image GetReferenceOutputImageMultiFrame( - this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel + if (!File.Exists(referenceOutputFile)) { - string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( - frameCount, - extension, - testOutputDetails, - appendPixelTypeToFileName); - - var temporaryFrameImages = new List>(); + throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); - foreach (string path in frameFiles) - { - if (!File.Exists(path)) - { - throw new Exception("Reference output file missing: " + path); - } + using FileStream stream = File.OpenRead(referenceOutputFile); + return decoder.Decode(DecoderOptions.Default, stream, default); + } - using FileStream stream = File.OpenRead(path); - Image tempImage = decoder.Decode(DecoderOptions.Default, stream, default); - temporaryFrameImages.Add(tempImage); - } + 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, + extension, + testOutputDetails, + appendPixelTypeToFileName); - Image firstTemp = temporaryFrameImages[0]; + var temporaryFrameImages = new List>(); - var result = new Image(firstTemp.Width, firstTemp.Height); + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); - foreach (Image fi in temporaryFrameImages) + foreach (string path in frameFiles) + { + if (!File.Exists(path)) { - result.Frames.AddFrame(fi.Frames.RootFrame); - fi.Dispose(); + throw new Exception("Reference output file missing: " + path); } - // Remove the initial empty frame: - result.Frames.RemoveFrame(0); - return result; + using FileStream stream = File.OpenRead(path); + Image tempImage = decoder.Decode(DecoderOptions.Default, stream, default); + temporaryFrameImages.Add(tempImage); } - public static IEnumerable GetReferenceOutputSimilarityReports( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel - { - using Image referenceImage = provider.GetReferenceOutputImage( - testOutputDetails, - extension, - appendPixelTypeToFileName); - return comparer.CompareImages(referenceImage, image); - } + Image firstTemp = temporaryFrameImages[0]; - public static Image ComparePixelBufferTo( - this Image image, - Span expectedPixels) - where TPixel : unmanaged, IPixel - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualPixels)); - CompareBuffers(expectedPixels, actualPixels.Span); + var result = new Image(firstTemp.Width, firstTemp.Height); - return image; + foreach (Image fi in temporaryFrameImages) + { + result.Frames.AddFrame(fi.Frames.RootFrame); + fi.Dispose(); } - public static Image ComparePixelBufferTo( - this Image image, - Memory expectedPixels) - where TPixel : unmanaged, IPixel => - ComparePixelBufferTo(image, expectedPixels.Span); - - public static void CompareBuffers(Span expected, Span actual) - where T : struct, IEquatable - { - Assert.True(expected.Length == actual.Length, "Buffer sizes are not equal!"); + // Remove the initial empty frame: + result.Frames.RemoveFrame(0); + return result; + } - for (int i = 0; i < expected.Length; i++) - { - T x = expected[i]; - T a = actual[i]; + public static IEnumerable GetReferenceOutputSimilarityReports( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + using Image referenceImage = provider.GetReferenceOutputImage( + testOutputDetails, + extension, + appendPixelTypeToFileName); + return comparer.CompareImages(referenceImage, image); + } - Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); - } - } + public static Image ComparePixelBufferTo( + this Image image, + Span expectedPixels) + where TPixel : unmanaged, IPixel + { + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualPixels)); + CompareBuffers(expectedPixels, actualPixels.Span); - public static void CompareBuffers(Buffer2D expected, Buffer2D actual) - where T : struct, IEquatable - { - Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + return image; + } - for (int y = 0; y < expected.Height; y++) - { - Span expectedRow = expected.DangerousGetRowSpan(y); - Span actualRow = actual.DangerousGetRowSpan(y); - for (int x = 0; x < expectedRow.Length; x++) - { - T expectedVal = expectedRow[x]; - T actualVal = actualRow[x]; + public static Image ComparePixelBufferTo( + this Image image, + Memory expectedPixels) + where TPixel : unmanaged, IPixel => + ComparePixelBufferTo(image, expectedPixels.Span); - Assert.True( - expectedVal.Equals(actualVal), - $"Buffers differ at position ({x},{y})! Expected: {expectedVal} | Actual: {actualVal}"); - } - } - } + public static void CompareBuffers(Span expected, Span actual) + where T : struct, IEquatable + { + Assert.True(expected.Length == actual.Length, "Buffer sizes are not equal!"); - /// - /// 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 : unmanaged, IPixel + for (int i = 0; i < expected.Length; i++) { - foreach (ImageFrame imageFrame in image.Frames) - { - imageFrame.ComparePixelBufferTo(expectedPixel); - } + T x = expected[i]; + T a = actual[i]; - return image; + Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); } + } - /// - /// 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 : unmanaged, IPixel + public static void CompareBuffers(Buffer2D expected, Buffer2D actual) + where T : struct, IEquatable + { + Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + + for (int y = 0; y < expected.Height; y++) { - foreach (ImageFrame imageFrame in image.Frames) + Span expectedRow = expected.DangerousGetRowSpan(y); + Span actualRow = actual.DangerousGetRowSpan(y); + for (int x = 0; x < expectedRow.Length; x++) { - imageFrame.ComparePixelBufferTo(expectedPixelColor.ToPixel()); - } + T expectedVal = expectedRow[x]; + T actualVal = actualRow[x]; - return image; + Assert.True( + expectedVal.Equals(actualVal), + $"Buffers differ at position ({x},{y})! Expected: {expectedVal} | Actual: {actualVal}"); + } } + } - /// - /// 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 : unmanaged, IPixel + /// + /// 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 : unmanaged, IPixel + { + foreach (ImageFrame imageFrame in image.Frames) { - Assert.True(imageFrame.DangerousTryGetSinglePixelMemory(out Memory actualPixelMem)); - Span actualPixels = actualPixelMem.Span; + imageFrame.ComparePixelBufferTo(expectedPixel); + } - for (int i = 0; i < actualPixels.Length; i++) - { - Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); - } + return image; + } - return imageFrame; + /// + /// 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 : unmanaged, IPixel + { + foreach (ImageFrame imageFrame in image.Frames) + { + imageFrame.ComparePixelBufferTo(expectedPixelColor.ToPixel()); } - public static ImageFrame ComparePixelBufferTo( - this ImageFrame image, - Span expectedPixels) - where TPixel : unmanaged, IPixel - { - Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualMem)); - Span actual = actualMem.Span; - Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); + return image; + } - for (int i = 0; i < expectedPixels.Length; i++) - { - Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); - } + /// + /// 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 : unmanaged, IPixel + { + Assert.True(imageFrame.DangerousTryGetSinglePixelMemory(out Memory actualPixelMem)); + Span actualPixels = actualPixelMem.Span; - return image; + for (int i = 0; i < actualPixels.Length; i++) + { + Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!"); } - public static Image CompareToOriginal( - this Image image, - ITestImageProvider provider, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - => CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); - - public static Image CompareToOriginal( - this Image image, - ITestImageProvider provider, - ImageComparer comparer, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } + return imageFrame; + } - var testFile = TestFile.Create(path); + public static ImageFrame ComparePixelBufferTo( + this ImageFrame image, + Span expectedPixels) + where TPixel : unmanaged, IPixel + { + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualMem)); + Span actual = actualMem.Span; + Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); + for (int i = 0; i < expectedPixels.Length; i++) + { + Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!"); + } - using var stream = new MemoryStream(testFile.Bytes); - using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) - { - comparer.VerifySimilarity(original, image); - } + return image; + } - return image; - } + public static Image CompareToOriginal( + this Image image, + ITestImageProvider provider, + IImageDecoder referenceDecoder = null) + where TPixel : unmanaged, IPixel + => CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); - public static Image CompareToOriginalMultiFrame( + public static Image CompareToOriginal( this Image image, ITestImageProvider provider, ImageComparer comparer, IImageDecoder referenceDecoder = null) where TPixel : unmanaged, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } - - var testFile = TestFile.Create(path); + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); + var testFile = TestFile.Create(path); - using var stream = new MemoryStream(testFile.Bytes); - using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) - { - comparer.VerifySimilarity(original, image); - } + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); - return image; + using var stream = new MemoryStream(testFile.Bytes); + using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) + { + comparer.VerifySimilarity(original, image); } - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - ImageComparer comparer, - Action> operation, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel + return image; + } + + public static Image CompareToOriginalMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + IImageDecoder referenceDecoder = null) + where TPixel : unmanaged, IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) { - using Image image = provider.GetImage(); - operation(image); - - image.DebugSave( - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); } - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - Action> operation, - FormattableString testOutputDetails, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - ImageComparer.Tolerant(), - operation, - testOutputDetails, - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - ImageComparer comparer, - Action> operation, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation( - comparer, - operation, - $"", - appendPixelTypeToFileName, - appendSourceFileOrDescription); - - /// - /// Utility method for doing the following in one step: - /// 1. Executing an operation (taken as a delegate) - /// 2. Executing DebugSave() - /// 3. Executing CompareToReferenceOutput() - /// - internal static void VerifyOperation( - this TestImageProvider provider, - Action> operation, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel - => provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); - - /// - /// Loads the expected image with a reference decoder + compares it to . - /// Also performs a debug save using . - /// - /// The path to the encoded output file. - internal static string VerifyEncoder( - this Image image, - ITestImageProvider provider, - string extension, - object testOutputDetails, - IImageEncoder encoder, - ImageComparer customComparer = null, - bool appendPixelTypeToFileName = true, - string referenceImageExtension = null, - IImageDecoder referenceDecoder = null) - where TPixel : unmanaged, IPixel + var testFile = TestFile.Create(path); + + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(path); + + using var stream = new MemoryStream(testFile.Bytes); + using (Image original = referenceDecoder.Decode(DecoderOptions.Default, stream, default)) { - string actualOutputFile = provider.Utility.SaveTestOutputFile( - image, - extension, - encoder, - testOutputDetails, - appendPixelTypeToFileName); + comparer.VerifySimilarity(original, image); + } + + return image; + } - referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CompareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + ImageComparer comparer, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + operation(image); + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + + image.CompareToReferenceOutput( + comparer, + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); + } - using FileStream stream = File.OpenRead(actualOutputFile); - using Image encodedImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CompareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + FormattableString testOutputDetails, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + ImageComparer.Tolerant(), + operation, + testOutputDetails, + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CompareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + ImageComparer comparer, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation( + comparer, + operation, + $"", + appendPixelTypeToFileName, + appendSourceFileOrDescription); + + /// + /// Utility method for doing the following in one step: + /// 1. Executing an operation (taken as a delegate) + /// 2. Executing DebugSave() + /// 3. Executing CompareToReferenceOutput() + /// + internal static void VerifyOperation( + this TestImageProvider provider, + Action> operation, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + => provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); + + /// + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + /// The path to the encoded output file. + internal static string VerifyEncoder( + this Image image, + ITestImageProvider provider, + string extension, + object testOutputDetails, + IImageEncoder encoder, + ImageComparer customComparer = null, + bool appendPixelTypeToFileName = true, + string referenceImageExtension = null, + IImageDecoder referenceDecoder = null) + where TPixel : unmanaged, IPixel + { + string actualOutputFile = provider.Utility.SaveTestOutputFile( + image, + extension, + encoder, + testOutputDetails, + appendPixelTypeToFileName); - ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(encodedImage, image); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - return actualOutputFile; - } + using FileStream stream = File.OpenRead(actualOutputFile); + using Image encodedImage = referenceDecoder.Decode(DecoderOptions.Default, stream, default); - internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( - this TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - var allocator = new TestMemoryAllocator(); - provider.Configuration.MemoryAllocator = allocator; - return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); - } + ImageComparer comparer = customComparer ?? ImageComparer.Exact; + comparer.VerifySimilarity(encodedImage, image); - internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) - { - var image = new Image(buffer.Width, buffer.Height); + return actualOutputFile; + } - Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory pixelMem)); - Span pixels = pixelMem.Span; - Span bufferSpan = buffer.DangerousGetSingleSpan(); + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var allocator = new TestMemoryAllocator(); + provider.Configuration.MemoryAllocator = allocator; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); + } - for (int i = 0; i < bufferSpan.Length; i++) - { - float value = bufferSpan[i] * scale; - var v = new Vector4(value, value, value, 1f); - pixels[i].FromVector4(v); - } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) + { + var image = new Image(buffer.Width, buffer.Height); - return image; - } + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory pixelMem)); + Span pixels = pixelMem.Span; + Span bufferSpan = buffer.DangerousGetSingleSpan(); - private class MakeOpaqueProcessor : IImageProcessor + for (int i = 0; i < bufferSpan.Length; i++) { - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : unmanaged, IPixel - => new MakeOpaqueProcessor(configuration, source, sourceRectangle); + float value = bufferSpan[i] * scale; + var v = new Vector4(value, value, value, 1f); + pixels[i].FromVector4(v); } - private class MakeOpaqueProcessor : ImageProcessor + return image; + } + + private class MakeOpaqueProcessor : IImageProcessor + { + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixel : unmanaged, IPixel + => new MakeOpaqueProcessor(configuration, source, sourceRectangle); + } + + private class MakeOpaqueProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + public MakeOpaqueProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) { - 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; + protected override void OnFrameApply(ImageFrame source) + { + Rectangle sourceRectangle = this.SourceRectangle; + Configuration configuration = this.Configuration; - var operation = new RowOperation(configuration, sourceRectangle, source.PixelBuffer); + var operation = new RowOperation(configuration, sourceRectangle, source.PixelBuffer); - ParallelRowIterator.IterateRowIntervals( - configuration, - sourceRectangle, - in operation); - } + ParallelRowIterator.IterateRowIntervals( + configuration, + sourceRectangle, + in operation); + } - private readonly struct RowOperation : IRowIntervalOperation - { - private readonly Configuration configuration; - private readonly Rectangle bounds; - private readonly Buffer2D source; + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly Buffer2D source; - public RowOperation(Configuration configuration, Rectangle bounds, Buffer2D source) - { - this.configuration = configuration; - this.bounds = bounds; - this.source = source; - } + public RowOperation(Configuration configuration, Rectangle bounds, Buffer2D source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } - public void Invoke(in RowInterval rows, Span span) + public void Invoke(in RowInterval rows, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) + Span rowSpan = this.source.DangerousGetRowSpan(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 rowSpan = this.source.DangerousGetRowSpan(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++) - { - ref Vector4 v = ref span[i]; - v.W = 1F; - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, 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 TestMemoryAllocator allocator; - private readonly int pixelSizeInBytes; +internal class AllocatorBufferCapacityConfigurator +{ + private readonly TestMemoryAllocator allocator; + private readonly int pixelSizeInBytes; - public AllocatorBufferCapacityConfigurator(TestMemoryAllocator allocator, int pixelSizeInBytes) - { - this.allocator = allocator; - this.pixelSizeInBytes = pixelSizeInBytes; - } + public AllocatorBufferCapacityConfigurator(TestMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } - public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; - public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + 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 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); - } + /// + /// 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 62865993b6..fe94cffc43 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,181 +1,178 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Tests.Memory +namespace SixLabors.ImageSharp.Tests.Memory; + +internal class TestMemoryAllocator : MemoryAllocator { - internal class TestMemoryAllocator : MemoryAllocator + private List allocationLog; + private List returnLog; + + public TestMemoryAllocator(byte dirtyValue = 42) { - private List allocationLog; - private List returnLog; + this.DirtyValue = dirtyValue; + } - public TestMemoryAllocator(byte dirtyValue = 42) - { - this.DirtyValue = dirtyValue; - } + /// + /// Gets the value to initialize the result buffer with, with non-clean options () + /// + public byte DirtyValue { get; } - /// - /// Gets the value to initialize the result buffer with, with non-clean options () - /// - public byte DirtyValue { get; } + public int BufferCapacityInBytes { get; set; } = int.MaxValue; + + public IReadOnlyList AllocationLog => this.allocationLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - public int BufferCapacityInBytes { get; set; } = int.MaxValue; + public IReadOnlyList ReturnLog => this.returnLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - public IReadOnlyList AllocationLog => this.allocationLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; - public IReadOnlyList ReturnLog => this.returnLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); + public void EnableNonThreadSafeLogging() + { + this.allocationLog = new List(); + this.returnLog = new List(); + } - 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, this); + } - public void EnableNonThreadSafeLogging() - { - this.allocationLog = new List(); - this.returnLog = new List(); - } + private T[] AllocateArray(int length, AllocationOptions options) + where T : struct + { + var array = new T[length + 42]; + this.allocationLog?.Add(AllocationRequest.Create(options, length, array)); - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + if (options == AllocationOptions.None) { - T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length, this); + Span data = MemoryMarshal.Cast(array.AsSpan()); + data.Fill(this.DirtyValue); } - private T[] AllocateArray(int length, AllocationOptions options) - where T : struct + 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, int hashCodeOfBuffer) { - var array = new T[length + 42]; - this.allocationLog?.Add(AllocationRequest.Create(options, length, array)); + this.ElementType = elementType; + this.AllocationOptions = allocationOptions; + this.Length = length; + this.LengthInBytes = lengthInBytes; + this.HashCodeOfBuffer = hashCodeOfBuffer; - if (options == AllocationOptions.None) + if (elementType == typeof(Vector4)) { - Span data = MemoryMarshal.Cast(array.AsSpan()); - data.Fill(this.DirtyValue); } - - return array; } - private void Return(BasicArrayBuffer buffer) - where T : struct + public static AllocationRequest Create(AllocationOptions allocationOptions, int length, T[] buffer) { - this.returnLog?.Add(new ReturnRequest(buffer.Array.GetHashCode())); + Type type = typeof(T); + int elementSize = Marshal.SizeOf(type); + return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); } - public struct AllocationRequest - { - 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, T[] buffer) - { - Type type = typeof(T); - int elementSize = Marshal.SizeOf(type); - return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); - } + public Type ElementType { get; } - public Type ElementType { get; } + public AllocationOptions AllocationOptions { get; } - public AllocationOptions AllocationOptions { get; } + public int Length { get; } - public int Length { get; } + public int LengthInBytes { get; } - public int LengthInBytes { get; } + public int HashCodeOfBuffer { get; } + } - public int HashCodeOfBuffer { get; } + public struct ReturnRequest + { + public ReturnRequest(int hashCodeOfBuffer) + { + this.HashCodeOfBuffer = hashCodeOfBuffer; } - public struct ReturnRequest + public int HashCodeOfBuffer { get; } + } + + /// + /// Wraps an array as an instance. + /// + private class BasicArrayBuffer : MemoryManager + where T : struct + { + private readonly TestMemoryAllocator allocator; + private GCHandle pinHandle; + + public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) { - public ReturnRequest(int hashCodeOfBuffer) - { - this.HashCodeOfBuffer = hashCodeOfBuffer; - } + this.allocator = allocator; + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); + this.Array = array; + this.Length = length; + } - public int HashCodeOfBuffer { get; } + public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) + : this(array, array.Length, allocator) + { } /// - /// Wraps an array as an instance. + /// Gets the array. /// - private class BasicArrayBuffer : MemoryManager - where T : struct - { - private readonly TestMemoryAllocator allocator; - private GCHandle pinHandle; - - public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) - { - this.allocator = allocator; - DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); - this.Array = array; - this.Length = length; - } - - public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) - : this(array, array.Length, allocator) - { - } - - /// - /// Gets the array. - /// - public T[] Array { get; } + public T[] Array { get; } - /// - /// Gets the length. - /// - public int Length { get; } + /// + /// Gets the length. + /// + public int Length { get; } - /// - public override Span GetSpan() => this.Array.AsSpan(0, this.Length); + /// + public override Span GetSpan() => this.Array.AsSpan(0, this.Length); - public override unsafe MemoryHandle Pin(int elementIndex = 0) + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + if (!this.pinHandle.IsAllocated) { - if (!this.pinHandle.IsAllocated) - { - this.pinHandle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); - } - - void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); - return new MemoryHandle(ptr, pinnable: this); + this.pinHandle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); } - public override void Unpin() - { - this.pinHandle.Free(); - } + void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); + return new MemoryHandle(ptr, pinnable: this); + } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.allocator.Return(this); - } - } + public override void Unpin() + { + this.pinHandle.Free(); } - private class ManagedByteBuffer : BasicArrayBuffer, IMemoryOwner + /// + protected override void Dispose(bool disposing) { - public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) - : base(array, allocator) + if (disposing) { + this.allocator.Return(this); } } } + + private class ManagedByteBuffer : BasicArrayBuffer, IMemoryOwner + { + public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) + : base(array, allocator) + { + } + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs index 4514f6d408..50e086d579 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs @@ -1,49 +1,47 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class TestMemoryManager : MemoryManager + where T : struct { - public class TestMemoryManager : MemoryManager - where T : struct + public TestMemoryManager(T[] pixelArray) + { + this.PixelArray = pixelArray; + } + + public T[] PixelArray { get; private set; } + + public bool IsDisposed { get; private set; } + + public override Span GetSpan() + { + return this.PixelArray; + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + throw new NotImplementedException(); + } + + public override void Unpin() + { + throw new NotImplementedException(); + } + + public static TestMemoryManager CreateAsCopyOf(Span copyThisBuffer) + { + var pixelArray = new T[copyThisBuffer.Length]; + copyThisBuffer.CopyTo(pixelArray); + return new TestMemoryManager(pixelArray); + } + + protected override void Dispose(bool disposing) { - public TestMemoryManager(T[] pixelArray) - { - this.PixelArray = pixelArray; - } - - public T[] PixelArray { get; private set; } - - public bool IsDisposed { get; private set; } - - public override Span GetSpan() - { - return this.PixelArray; - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - throw new NotImplementedException(); - } - - public override void Unpin() - { - throw new NotImplementedException(); - } - - public static TestMemoryManager CreateAsCopyOf(Span copyThisBuffer) - { - var pixelArray = new T[copyThisBuffer.Length]; - copyThisBuffer.CopyTo(pixelArray); - return new TestMemoryManager(pixelArray); - } - - protected override void Dispose(bool disposing) - { - this.IsDisposed = true; - this.PixelArray = null; - } + this.IsDisposed = true; + this.PixelArray = null; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index e32b0b2393..fb879c7697 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -1,66 +1,64 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +public class TestPixel : IXunitSerializable + where TPixel : unmanaged, IPixel { - public class TestPixel : IXunitSerializable - where TPixel : unmanaged, IPixel + public TestPixel() { - public TestPixel() - { - } - - public TestPixel(float red, float green, float blue, float alpha) - { - Guard.MustBeBetweenOrEqualTo(red, 0F, 1F, nameof(red)); - Guard.MustBeBetweenOrEqualTo(green, 0F, 1F, nameof(green)); - Guard.MustBeBetweenOrEqualTo(blue, 0F, 1F, nameof(blue)); - Guard.MustBeBetweenOrEqualTo(alpha, 0F, 1F, nameof(alpha)); + } - this.Red = red; - this.Green = green; - this.Blue = blue; - this.Alpha = alpha; - } + public TestPixel(float red, float green, float blue, float alpha) + { + Guard.MustBeBetweenOrEqualTo(red, 0F, 1F, nameof(red)); + Guard.MustBeBetweenOrEqualTo(green, 0F, 1F, nameof(green)); + Guard.MustBeBetweenOrEqualTo(blue, 0F, 1F, nameof(blue)); + Guard.MustBeBetweenOrEqualTo(alpha, 0F, 1F, nameof(alpha)); - public float Red { get; set; } + this.Red = red; + this.Green = green; + this.Blue = blue; + this.Alpha = alpha; + } - public float Green { get; set; } + public float Red { get; set; } - public float Blue { get; set; } + public float Green { get; set; } - public float Alpha { get; set; } + public float Blue { get; set; } - public TPixel AsPixel() - { - var pix = default(TPixel); - pix.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); - return pix; - } + public float Alpha { get; set; } - internal Span AsSpan() => new(new[] { this.AsPixel() }); + public TPixel AsPixel() + { + var pix = default(TPixel); + pix.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + return pix; + } - public void Deserialize(IXunitSerializationInfo info) - { - this.Red = info.GetValue("red"); - this.Green = info.GetValue("green"); - this.Blue = info.GetValue("blue"); - this.Alpha = info.GetValue("alpha"); - } + internal Span AsSpan() => new(new[] { this.AsPixel() }); - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("red", this.Red); - info.AddValue("green", this.Green); - info.AddValue("blue", this.Blue); - info.AddValue("alpha", this.Alpha); - } + public void Deserialize(IXunitSerializationInfo info) + { + this.Red = info.GetValue("red"); + this.Green = info.GetValue("green"); + this.Blue = info.GetValue("blue"); + this.Alpha = info.GetValue("alpha"); + } - public override string ToString() => $"{typeof(TPixel).Name}{this.AsPixel()}"; + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.Red); + info.AddValue("green", this.Green); + info.AddValue("blue", this.Blue); + info.AddValue("alpha", this.Alpha); } + + public override string ToString() => $"{typeof(TPixel).Name}{this.AsPixel()}"; } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestType.cs b/tests/ImageSharp.Tests/TestUtilities/TestType.cs index 7291ec6a79..5b4f6a6024 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestType.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestType.cs @@ -3,25 +3,24 @@ using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +public class TestType : IXunitSerializable { - public class TestType : IXunitSerializable + public TestType() { - public TestType() - { - } + } - public void Deserialize(IXunitSerializationInfo info) - { - } + public void Deserialize(IXunitSerializationInfo info) + { + } - public void Serialize(IXunitSerializationInfo info) - { - } + public void Serialize(IXunitSerializationInfo info) + { + } - public override string ToString() - { - return $"Type<{typeof(T).Name}>"; - } + public override string ToString() + { + return $"Type<{typeof(T).Name}>"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index a980bc49f0..0b792b7fb0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; @@ -14,404 +11,402 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Various utility and extension methods. +/// +public static class TestUtils { - /// - /// Various utility and extension methods. - /// - public static class TestUtils - { - private static readonly Dictionary ClrTypes2PixelTypes = new Dictionary(); + private static readonly Dictionary ClrTypes2PixelTypes = new Dictionary(); - private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; + private static readonly Assembly ImageSharpAssembly = typeof(Rgba32).GetTypeInfo().Assembly; - private static readonly Dictionary PixelTypes2ClrTypes = new Dictionary(); + private static readonly Dictionary PixelTypes2ClrTypes = new Dictionary(); - private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() - .Except(new[] { PixelTypes.Undefined, PixelTypes.All }) - .ToArray(); + private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() + .Except(new[] { PixelTypes.Undefined, PixelTypes.All }) + .ToArray(); - static TestUtils() + static TestUtils() + { + // Add Rgba32 Our default. + Type defaultPixelFormatType = typeof(Rgba32); + PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; + ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; + + // Add PixelFormat types + string nameSpace = typeof(A8).FullName; + nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(A8).Name.Length - 1); + foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) { - // Add Rgba32 Our default. - Type defaultPixelFormatType = typeof(Rgba32); - PixelTypes2ClrTypes[PixelTypes.Rgba32] = defaultPixelFormatType; - ClrTypes2PixelTypes[defaultPixelFormatType] = PixelTypes.Rgba32; - - // Add PixelFormat types - string nameSpace = typeof(A8).FullName; - nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(A8).Name.Length - 1); - foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) - { - string typeName = $"{nameSpace}.{pt}"; - Type t = ImageSharpAssembly.GetType(typeName); - PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); - ClrTypes2PixelTypes[t] = pt; - } + string typeName = $"{nameSpace}.{pt}"; + Type t = ImageSharpAssembly.GetType(typeName); + PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); + ClrTypes2PixelTypes[t] = pt; } + } - public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; - public static byte[] GetRandomBytes(int length, int seed = 42) - { - var rnd = new Random(42); - byte[] bytes = new byte[length]; - rnd.NextBytes(bytes); - return bytes; - } + public static byte[] GetRandomBytes(int length, int seed = 42) + { + var rnd = new Random(42); + byte[] bytes = new byte[length]; + rnd.NextBytes(bytes); + return bytes; + } - internal static byte[] FillImageWithRandomBytes(Image image) + internal static byte[] FillImageWithRandomBytes(Image image) + { + byte[] expected = TestUtils.GetRandomBytes(image.Width * image.Height * 2); + image.ProcessPixelRows(accessor => { - byte[] expected = TestUtils.GetRandomBytes(image.Width * image.Height * 2); - image.ProcessPixelRows(accessor => + int cnt = 0; + for (int y = 0; y < accessor.Height; y++) { - int cnt = 0; - for (int y = 0; y < accessor.Height; y++) + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new La16(expected[cnt++], expected[cnt++]); - } + row[x] = new La16(expected[cnt++], expected[cnt++]); } - }); - return expected; - } + } + }); + return expected; + } - public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TPixel : unmanaged, IPixel + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) + where TPixel : unmanaged, IPixel + { + if (a.Width != b.Width || a.Height != b.Height) { - if (a.Width != b.Width || a.Height != b.Height) - { - return false; - } + return false; + } - var rgb1 = default(Rgb24); - var rgb2 = default(Rgb24); + var rgb1 = default(Rgb24); + var rgb2 = default(Rgb24); - Buffer2D pixA = a.GetRootFramePixelBuffer(); - Buffer2D pixB = b.GetRootFramePixelBuffer(); - for (int y = 0; y < a.Height; y++) + Buffer2D pixA = a.GetRootFramePixelBuffer(); + Buffer2D pixB = b.GetRootFramePixelBuffer(); + for (int y = 0; y < a.Height; y++) + { + for (int x = 0; x < a.Width; x++) { - for (int x = 0; x < a.Width; x++) - { - TPixel ca = pixA[x, y]; - TPixel cb = pixB[x, y]; + TPixel ca = pixA[x, y]; + TPixel cb = pixB[x, y]; - if (compareAlpha) + if (compareAlpha) + { + if (!ca.Equals(cb)) { - if (!ca.Equals(cb)) - { - return false; - } + return false; } - else + } + else + { + Rgba32 rgba = default; + ca.ToRgba32(ref rgba); + rgb1 = rgba.Rgb; + cb.ToRgba32(ref rgba); + rgb2 = rgba.Rgb; + + if (!rgb1.Equals(rgb2)) { - Rgba32 rgba = default; - ca.ToRgba32(ref rgba); - rgb1 = rgba.Rgb; - cb.ToRgba32(ref rgba); - rgb2 = rgba.Rgb; - - if (!rgb1.Equals(rgb2)) - { - return false; - } + return false; } } } - - return true; } - public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); + return true; + } + + public static string ToCsv(this IEnumerable items, string separator = ",") => string.Join(separator, items.Select(o => string.Format(CultureInfo.InvariantCulture, "{0}", o))); - public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; + public static Type GetClrType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; - /// - /// Returns the enumerations for the given type. - /// - /// The pixel type. - public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; + /// + /// 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) + public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) + { + if (pixelTypes == PixelTypes.Undefined) { - if (pixelTypes == PixelTypes.Undefined) - { - return Enumerable.Empty>(); - } - else if (pixelTypes == PixelTypes.All) - { - // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly - return PixelTypes2ClrTypes; - } + return Enumerable.Empty>(); + } + else if (pixelTypes == PixelTypes.All) + { + // TODO: Need to return unknown types here without forcing CLR to load all types in ImageSharp assembly + return PixelTypes2ClrTypes; + } - var result = new Dictionary(); - foreach (PixelTypes pt in AllConcretePixelTypes) + var result = new Dictionary(); + foreach (PixelTypes pt in AllConcretePixelTypes) + { + if (pixelTypes.HasAll(pt)) { - if (pixelTypes.HasAll(pt)) - { - result[pt] = pt.GetClrType(); - } + result[pt] = pt.GetClrType(); } - - return result; } - internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => - (pixelTypes & flagsToCheck) == flagsToCheck; + return result; + } - /// - /// Enumerate all available -s - /// - /// The pixel types - internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck) => + (pixelTypes & flagsToCheck) == flagsToCheck; - internal static Color GetColorByName(string colorName) - { - var f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; - return (Color)f.GetValue(null); - } + /// + /// Enumerate all available -s + /// + /// The pixel types + internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes)); + + internal static Color GetColorByName(string colorName) + { + var f = (FieldInfo)typeof(Color).GetMember(colorName)[0]; + return (Color)f.GetValue(null); + } + + internal static TPixel GetPixelOfNamedColor(string colorName) + 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 = new TestMemoryAllocator(); + provider.Configuration.MemoryAllocator = allocator; + allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); + + using Image actual = provider.GetImage(); + actual.Mutate(process); + comparer.VerifySimilarity(expected, actual); + } - internal static TPixel GetPixelOfNamedColor(string colorName) - 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 + /// + /// Utility for testing image processor extension methods: + /// 1. Run a processor defined by 'process' + /// 2. Run 'DebugSave()' to save the output locally + /// 3. Run 'CompareToReferenceOutput()' to compare the results to the expected output + /// + /// The + /// 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, + object testOutputDetails = null, + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + { + if (comparer == null) { - comparer ??= ImageComparer.Exact; - using Image expected = provider.GetImage(); - int width = expected.Width; - expected.Mutate(process); - - var allocator = new TestMemoryAllocator(); - provider.Configuration.MemoryAllocator = allocator; - allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); - - using Image actual = provider.GetImage(); - actual.Mutate(process); - comparer.VerifySimilarity(expected, actual); + comparer = ImageComparer.TolerantPercentage(0.001f); } - /// - /// Utility for testing image processor extension methods: - /// 1. Run a processor defined by 'process' - /// 2. Run 'DebugSave()' to save the output locally - /// 3. Run 'CompareToReferenceOutput()' to compare the results to the expected output - /// - /// The - /// 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, - object testOutputDetails = null, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel + using (Image image = provider.GetImage()) { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); - } + image.Mutate(process); - using (Image image = provider.GetImage()) - { - image.Mutate(process); + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); - image.DebugSave( + // TODO: Investigate the cause of pixel inaccuracies under Linux + if (TestEnvironment.IsWindows) + { + image.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - - // TODO: Investigate the cause of pixel inaccuracies under Linux - if (TestEnvironment.IsWindows) - { - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - } } } + } - internal static void RunValidatingProcessorTest( - this TestImageProvider provider, - Func processAndGetTestOutputDetails, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel + internal static void RunValidatingProcessorTest( + this TestImageProvider provider, + Func processAndGetTestOutputDetails, + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + { + if (comparer == null) { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); - } + comparer = ImageComparer.TolerantPercentage(0.001f); + } - using (Image image = provider.GetImage()) - { - FormattableString testOutputDetails = $""; - image.Mutate(ctx => testOutputDetails = processAndGetTestOutputDetails(ctx)); + using (Image image = provider.GetImage()) + { + FormattableString testOutputDetails = $""; + image.Mutate(ctx => testOutputDetails = processAndGetTestOutputDetails(ctx)); + + image.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); - image.DebugSave( + // TODO: Investigate the cause of pixel inaccuracies under Linux + if (TestEnvironment.IsWindows) + { + image.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - - // TODO: Investigate the cause of pixel inaccuracies under Linux - if (TestEnvironment.IsWindows) - { - image.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - } } } + } - public static void RunValidatingProcessorTestOnWrappedMemoryImage( - this TestImageProvider provider, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null, - string useReferenceOutputFrom = null, - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true) - where TPixel : unmanaged, IPixel + public static void RunValidatingProcessorTestOnWrappedMemoryImage( + this TestImageProvider provider, + Action process, + object testOutputDetails = null, + ImageComparer comparer = null, + string useReferenceOutputFrom = null, + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true) + where TPixel : unmanaged, IPixel + { + if (comparer == null) { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); - } + comparer = ImageComparer.TolerantPercentage(0.001f); + } - using (Image image0 = provider.GetImage()) + using (Image image0 = provider.GetImage()) + { + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); + + using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { - Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); + image1.Mutate(process); + image1.DebugSave( + provider, + testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName, + appendSourceFileOrDescription: appendSourceFileOrDescription); - using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) + // TODO: Investigate the cause of pixel inaccuracies under Linux + if (TestEnvironment.IsWindows) { - image1.Mutate(process); - image1.DebugSave( + string testNameBackup = provider.Utility.TestName; + + if (useReferenceOutputFrom != null) + { + provider.Utility.TestName = useReferenceOutputFrom; + } + + image1.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - // TODO: Investigate the cause of pixel inaccuracies under Linux - if (TestEnvironment.IsWindows) - { - string testNameBackup = provider.Utility.TestName; - - if (useReferenceOutputFrom != null) - { - provider.Utility.TestName = useReferenceOutputFrom; - } - - image1.CompareToReferenceOutput( - comparer, - provider, - testOutputDetails, - appendPixelTypeToFileName: appendPixelTypeToFileName, - appendSourceFileOrDescription: appendSourceFileOrDescription); - - provider.Utility.TestName = testNameBackup; - } + provider.Utility.TestName = testNameBackup; } } } + } - /// - /// Same as 'RunValidatingProcessorTest{TPixel}' but with an additional parameter passed to 'process' - /// - internal static void RunRectangleConstrainedValidatingProcessorTest( - this TestImageProvider provider, - Action process, - object testOutputDetails = null, - ImageComparer comparer = null, - bool appendPixelTypeToFileName = true) - where TPixel : unmanaged, IPixel + /// + /// Same as 'RunValidatingProcessorTest{TPixel}' but with an additional parameter passed to 'process' + /// + internal static void RunRectangleConstrainedValidatingProcessorTest( + this TestImageProvider provider, + Action process, + object testOutputDetails = null, + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel + { + if (comparer == null) { - if (comparer == null) - { - comparer = ImageComparer.TolerantPercentage(0.001f); - } - - using (Image image = provider.GetImage()) - { - 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, appendPixelTypeToFileName: appendPixelTypeToFileName); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); - } + comparer = ImageComparer.TolerantPercentage(0.001f); } - /// - /// Same as 'RunValidatingProcessorTest{TPixel}' but without the 'CompareToReferenceOutput()' step. - /// - internal static void RunProcessorTest( - this TestImageProvider provider, - Action process, - object testOutputDetails = null) - where TPixel : unmanaged, IPixel + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.Mutate(process); - image.DebugSave(provider, testOutputDetails); - } + 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, appendPixelTypeToFileName: appendPixelTypeToFileName); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); } + } - public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); - - public static IResampler GetResampler(string name) + /// + /// Same as 'RunValidatingProcessorTest{TPixel}' but without the 'CompareToReferenceOutput()' step. + /// + internal static void RunProcessorTest( + this TestImageProvider provider, + Action process, + object testOutputDetails = null) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + image.Mutate(process); + image.DebugSave(provider, testOutputDetails); + } + } - if (property is null) - { - throw new Exception($"No resampler named '{name}"); - } + public static string AsInvariantString(this FormattableString formattable) => FormattableString.Invariant(formattable); - return (IResampler)property.GetValue(null); - } + public static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); - public static IDither GetDither(string name) + if (property is null) { - PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + throw new Exception($"No resampler named '{name}"); + } - if (property is null) - { - throw new Exception($"No dither named '{name}"); - } + return (IResampler)property.GetValue(null); + } - return (IDither)property.GetValue(null); - } + public static IDither GetDither(string name) + { + PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); - public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) + if (property is null) { - return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) - .Select(p => p.Name) - .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) - .ToArray(); + 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) + .Select(p => p.Name) + .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) + .ToArray(); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 46c2cbeabb..350333965a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -4,59 +4,58 @@ using System.Numerics; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +public class TestVector4 : IXunitSerializable { - public class TestVector4 : IXunitSerializable + public TestVector4() + { + } + + public TestVector4(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = x; + this.W = w; + } + + 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) + { + return d?.AsVector() ?? default(Vector4); + } + + public Vector4 AsVector() + { + return new Vector4(this.X, this.Y, this.Z, this.W); + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.X = info.GetValue("x"); + this.Y = info.GetValue("y"); + this.Z = info.GetValue("z"); + this.W = info.GetValue("w"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("x", this.X); + info.AddValue("y", this.Y); + info.AddValue("z", this.Z); + info.AddValue("w", this.W); + } + + public override string ToString() { - public TestVector4() - { - } - - public TestVector4(float x, float y, float z, float w) - { - this.X = x; - this.Y = y; - this.Z = x; - this.W = w; - } - - 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) - { - return d?.AsVector() ?? default(Vector4); - } - - public Vector4 AsVector() - { - return new Vector4(this.X, this.Y, this.Z, this.W); - } - - public void Deserialize(IXunitSerializationInfo info) - { - this.X = info.GetValue("x"); - this.Y = info.GetValue("y"); - this.Z = info.GetValue("z"); - this.W = info.GetValue("w"); - } - - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue("x", this.X); - info.AddValue("y", this.Y); - info.AddValue("z", this.Z); - info.AddValue("w", this.W); - } - - public override string ToString() - { - return $"{this.AsVector().ToString()}"; - } + return $"{this.AsVector().ToString()}"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs index 0705e28f43..52447b6c2c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -2,66 +2,64 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class BasicSerializerTests { - public class BasicSerializerTests + internal class BaseObj : IXunitSerializable { - internal class BaseObj : IXunitSerializable - { - public double Length { get; set; } + public double Length { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public int Lives { get; set; } + public int Lives { get; set; } - public virtual void Deserialize(IXunitSerializationInfo info) - { - 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(this.Length)); - this.Name = info.GetValue(nameof(this.Name)); - this.Lives = info.GetValue(nameof(this.Lives)); - } + public virtual void Deserialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(this.Length), this.Length); + info.AddValue(nameof(this.Name), this.Name); + info.AddValue(nameof(this.Lives), this.Lives); } - internal class DerivedObj : BaseObj + public virtual void Serialize(IXunitSerializationInfo info) { - public double Strength { get; set; } + this.Length = info.GetValue(nameof(this.Length)); + this.Name = info.GetValue(nameof(this.Name)); + this.Lives = info.GetValue(nameof(this.Lives)); + } + } - public override void Deserialize(IXunitSerializationInfo info) - { - this.Strength = info.GetValue(nameof(this.Strength)); - base.Deserialize(info); - } + internal class DerivedObj : BaseObj + { + public double Strength { get; set; } - public override void Serialize(IXunitSerializationInfo info) - { - base.Serialize(info); - info.AddValue(nameof(this.Strength), this.Strength); - } + public override void Deserialize(IXunitSerializationInfo info) + { + this.Strength = info.GetValue(nameof(this.Strength)); + base.Deserialize(info); } - [Fact] - public void SerializeDeserialize_ShouldPreserveValues() + public override void Serialize(IXunitSerializationInfo info) { - var obj = new DerivedObj() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; + base.Serialize(info); + info.AddValue(nameof(this.Strength), this.Strength); + } + } - string str = BasicSerializer.Serialize(obj); - BaseObj mirrorBase = BasicSerializer.Deserialize(str); + [Fact] + public void SerializeDeserialize_ShouldPreserveValues() + { + var obj = new DerivedObj() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; - DerivedObj mirror = Assert.IsType(mirrorBase); - Assert.Equal(obj.Length, mirror.Length); - Assert.Equal(obj.Name, mirror.Name); - Assert.Equal(obj.Lives, mirror.Lives); - Assert.Equal(obj.Strength, mirror.Strength); - } + string str = BasicSerializer.Serialize(obj); + BaseObj mirrorBase = BasicSerializer.Deserialize(str); + + DerivedObj mirror = Assert.IsType(mirrorBase); + Assert.Equal(obj.Length, mirror.Length); + Assert.Equal(obj.Name, mirror.Name); + Assert.Equal(obj.Lives, mirror.Lives); + Assert.Equal(obj.Strength, mirror.Strength); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs index fc8ca6c452..6ce07e7665 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs @@ -1,271 +1,266 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; using System.Runtime.Intrinsics.X86; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; + +public class FeatureTestRunnerTests { - public class FeatureTestRunnerTests + public static TheoryData Intrinsics => + new() + { + { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } }, + { HwIntrinsics.DisableHWIntrinsic, new string[] { "EnableHWIntrinsic" } }, + { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } } + }; + + [Theory] + [MemberData(nameof(Intrinsics))] + public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues) { - public static TheoryData Intrinsics => - new() - { - { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } }, - { HwIntrinsics.DisableHWIntrinsic, new string[] { "EnableHWIntrinsic" } }, - { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } } - }; + Dictionary features = expectedIntrinsics.ToFeatureKeyValueCollection(); + HwIntrinsics[] keys = features.Keys.ToArray(); - [Theory] - [MemberData(nameof(Intrinsics))] - public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues) + HwIntrinsics actualIntrinsics = keys[0]; + for (int i = 1; i < keys.Length; i++) { - Dictionary features = expectedIntrinsics.ToFeatureKeyValueCollection(); - HwIntrinsics[] keys = features.Keys.ToArray(); + actualIntrinsics |= keys[i]; + } - HwIntrinsics actualIntrinsics = keys[0]; - for (int i = 1; i < keys.Length; i++) - { - actualIntrinsics |= keys[i]; - } + Assert.Equal(expectedIntrinsics, actualIntrinsics); - Assert.Equal(expectedIntrinsics, actualIntrinsics); + IEnumerable actualValues = features.Select(x => x.Value); + Assert.Equal(expectedValues, actualValues); + } - IEnumerable actualValues = features.Select(x => x.Value); - Assert.Equal(expectedValues, actualValues); + [Fact] + public void AllowsAllHwIntrinsicFeatures() + { + if (!Vector.IsHardwareAccelerated) + { + return; } - [Fact] - public void AllowsAllHwIntrinsicFeatures() - { - if (!Vector.IsHardwareAccelerated) - { - return; - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + () => Assert.True(Vector.IsHardwareAccelerated), + HwIntrinsics.AllowAll); + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - () => Assert.True(Vector.IsHardwareAccelerated), - HwIntrinsics.AllowAll); + [Fact] + public void CanLimitHwIntrinsicBaseFeatures() + { + static void AssertDisabled() + { + Assert.False(Sse.IsSupported); + Assert.False(Sse2.IsSupported); + Assert.False(Aes.IsSupported); + Assert.False(Pclmulqdq.IsSupported); + Assert.False(Sse3.IsSupported); + Assert.False(Ssse3.IsSupported); + Assert.False(Sse41.IsSupported); + Assert.False(Sse42.IsSupported); + Assert.False(Popcnt.IsSupported); + Assert.False(Avx.IsSupported); + Assert.False(Fma.IsSupported); + Assert.False(Avx2.IsSupported); + Assert.False(Bmi1.IsSupported); + Assert.False(Bmi2.IsSupported); + Assert.False(Lzcnt.IsSupported); } - [Fact] - public void CanLimitHwIntrinsicBaseFeatures() + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertDisabled, + HwIntrinsics.DisableHWIntrinsic); + } + + [Fact] + public void CanLimitHwIntrinsicFeaturesWithIntrinsicsParam() + { + static void AssertHwIntrinsicsFeatureDisabled(string intrinsic) { - static void AssertDisabled() + Assert.NotNull(intrinsic); + + switch (Enum.Parse(intrinsic)) { - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); + case HwIntrinsics.DisableHWIntrinsic: + Assert.False(Sse.IsSupported); + Assert.False(Sse2.IsSupported); + Assert.False(Aes.IsSupported); + Assert.False(Pclmulqdq.IsSupported); + Assert.False(Sse3.IsSupported); + Assert.False(Ssse3.IsSupported); + Assert.False(Sse41.IsSupported); + Assert.False(Sse42.IsSupported); + Assert.False(Popcnt.IsSupported); + Assert.False(Avx.IsSupported); + Assert.False(Fma.IsSupported); + Assert.False(Avx2.IsSupported); + Assert.False(Bmi1.IsSupported); + Assert.False(Bmi2.IsSupported); + Assert.False(Lzcnt.IsSupported); + break; + case HwIntrinsics.DisableSSE: + Assert.False(Sse.IsSupported); + break; + case HwIntrinsics.DisableSSE2: + Assert.False(Sse2.IsSupported); + break; + case HwIntrinsics.DisableAES: + Assert.False(Aes.IsSupported); + break; + case HwIntrinsics.DisablePCLMULQDQ: + Assert.False(Pclmulqdq.IsSupported); + break; + case HwIntrinsics.DisableSSE3: + Assert.False(Sse3.IsSupported); + break; + case HwIntrinsics.DisableSSSE3: + Assert.False(Ssse3.IsSupported); + break; + case HwIntrinsics.DisableSSE41: + Assert.False(Sse41.IsSupported); + break; + case HwIntrinsics.DisableSSE42: + Assert.False(Sse42.IsSupported); + break; + case HwIntrinsics.DisablePOPCNT: + Assert.False(Popcnt.IsSupported); + break; + case HwIntrinsics.DisableAVX: + Assert.False(Avx.IsSupported); + break; + case HwIntrinsics.DisableFMA: + Assert.False(Fma.IsSupported); + break; + case HwIntrinsics.DisableAVX2: + Assert.False(Avx2.IsSupported); + break; + case HwIntrinsics.DisableBMI1: + Assert.False(Bmi1.IsSupported); + break; + case HwIntrinsics.DisableBMI2: + Assert.False(Bmi2.IsSupported); + break; + case HwIntrinsics.DisableLZCNT: + Assert.False(Lzcnt.IsSupported); + break; } + } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertDisabled, - HwIntrinsics.DisableHWIntrinsic); + foreach (HwIntrinsics intrinsic in Enum.GetValues()) + { + FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic); } + } - [Fact] - public void CanLimitHwIntrinsicFeaturesWithIntrinsicsParam() + [Fact] + public void CanLimitHwIntrinsicFeaturesWithSerializableParam() + { + static void AssertHwIntrinsicsFeatureDisabled(string serializable) { - static void AssertHwIntrinsicsFeatureDisabled(string intrinsic) - { - Assert.NotNull(intrinsic); + Assert.NotNull(serializable); + Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); + Assert.False(Sse.IsSupported); + } - switch (Enum.Parse(intrinsic)) - { - case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); - break; - case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); - break; - case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); - break; - case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); - break; - case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - } - } + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHwIntrinsicsFeatureDisabled, + HwIntrinsics.DisableSSE, + new FakeSerializable()); + } + + [Fact] + public void CanLimitHwIntrinsicFeaturesWithSerializableAndIntrinsicsParams() + { + static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic) + { + Assert.NotNull(serializable); + Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - foreach (HwIntrinsics intrinsic in Enum.GetValues()) + switch (Enum.Parse(intrinsic)) { - FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic); + case HwIntrinsics.DisableHWIntrinsic: + Assert.False(Sse.IsSupported); + Assert.False(Sse2.IsSupported); + Assert.False(Aes.IsSupported); + Assert.False(Pclmulqdq.IsSupported); + Assert.False(Sse3.IsSupported); + Assert.False(Ssse3.IsSupported); + Assert.False(Sse41.IsSupported); + Assert.False(Sse42.IsSupported); + Assert.False(Popcnt.IsSupported); + Assert.False(Avx.IsSupported); + Assert.False(Fma.IsSupported); + Assert.False(Avx2.IsSupported); + Assert.False(Bmi1.IsSupported); + Assert.False(Bmi2.IsSupported); + Assert.False(Lzcnt.IsSupported); + break; + case HwIntrinsics.DisableSSE: + Assert.False(Sse.IsSupported); + break; + case HwIntrinsics.DisableSSE2: + Assert.False(Sse2.IsSupported); + break; + case HwIntrinsics.DisableAES: + Assert.False(Aes.IsSupported); + break; + case HwIntrinsics.DisablePCLMULQDQ: + Assert.False(Pclmulqdq.IsSupported); + break; + case HwIntrinsics.DisableSSE3: + Assert.False(Sse3.IsSupported); + break; + case HwIntrinsics.DisableSSSE3: + Assert.False(Ssse3.IsSupported); + break; + case HwIntrinsics.DisableSSE41: + Assert.False(Sse41.IsSupported); + break; + case HwIntrinsics.DisableSSE42: + Assert.False(Sse42.IsSupported); + break; + case HwIntrinsics.DisablePOPCNT: + Assert.False(Popcnt.IsSupported); + break; + case HwIntrinsics.DisableAVX: + Assert.False(Avx.IsSupported); + break; + case HwIntrinsics.DisableFMA: + Assert.False(Fma.IsSupported); + break; + case HwIntrinsics.DisableAVX2: + Assert.False(Avx2.IsSupported); + break; + case HwIntrinsics.DisableBMI1: + Assert.False(Bmi1.IsSupported); + break; + case HwIntrinsics.DisableBMI2: + Assert.False(Bmi2.IsSupported); + break; + case HwIntrinsics.DisableLZCNT: + Assert.False(Lzcnt.IsSupported); + break; } } - [Fact] - public void CanLimitHwIntrinsicFeaturesWithSerializableParam() + foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics))) { - static void AssertHwIntrinsicsFeatureDisabled(string serializable) - { - Assert.NotNull(serializable); - Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - Assert.False(Sse.IsSupported); - } - - FeatureTestRunner.RunWithHwIntrinsicsFeature( - AssertHwIntrinsicsFeatureDisabled, - HwIntrinsics.DisableSSE, - new FakeSerializable()); + FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable()); } + } - [Fact] - public void CanLimitHwIntrinsicFeaturesWithSerializableAndIntrinsicsParams() + public class FakeSerializable : IXunitSerializable + { + public void Deserialize(IXunitSerializationInfo info) { - static void AssertHwIntrinsicsFeatureDisabled(string serializable, string intrinsic) - { - Assert.NotNull(serializable); - Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - - switch (Enum.Parse(intrinsic)) - { - case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); - break; - case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); - break; - case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); - break; - case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); - break; - case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - } - } - - foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics))) - { - FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable()); - } } - public class FakeSerializable : IXunitSerializable + public void Serialize(IXunitSerializationInfo info) { - public void Deserialize(IXunitSerializationInfo info) - { - } - - public void Serialize(IXunitSerializationInfo info) - { - } } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs index 76e7cbcd31..e8994a344f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs @@ -1,31 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.PixelFormats; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +[GroupOutput("Foo")] +public class GroupOutputTests { - [GroupOutput("Foo")] - public class GroupOutputTests + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.Equal("Foo", provider.Utility.OutputSubfolderName); - } + Assert.Equal("Foo", provider.Utility.OutputSubfolderName); + } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; - Assert.Contains(expected, provider.Utility.GetTestOutputDir()); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; + Assert.Contains(expected, provider.Utility.GetTestOutputDir()); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index ab5b2604aa..4c1a740e2b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -1,178 +1,174 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Linq; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class ImageComparerTests { - public class ImageComparerTests + public ImageComparerTests(ITestOutputHelper output) { - public ImageComparerTests(ITestOutputHelper output) - { - this.Output = output; - } + this.Output = output; + } - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001f, 1)] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0)] - public void TolerantImageComparer_ApprovesPerfectSimilarity( - TestImageProvider provider, - float imageThreshold, - int pixelThreshold) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001f, 1)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0, 0)] + public void TolerantImageComparer_ApprovesPerfectSimilarity( + TestImageProvider provider, + float imageThreshold, + int pixelThreshold) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - var comparer = ImageComparer.Tolerant(imageThreshold, pixelThreshold); - comparer.VerifySimilarity(image, clone); - } + var comparer = ImageComparer.Tolerant(imageThreshold, pixelThreshold); + comparer.VerifySimilarity(image, clone); } } + } - [Theory] - [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] - public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] + public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); - var comparer = ImageComparer.Tolerant(); - comparer.VerifySimilarity(image, clone); - } + var comparer = ImageComparer.Tolerant(); + comparer.VerifySimilarity(image, clone); } } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - byte perChannelChange = 20; - ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); + byte perChannelChange = 20; + ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); - var comparer = ImageComparer.Tolerant(); + var comparer = ImageComparer.Tolerant(); - ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( - () => comparer.VerifySimilarity(image, clone)); + ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny( + () => comparer.VerifySimilarity(image, clone)); - PixelDifference diff = ex.Reports.Single().Differences.Single(); - Assert.Equal(new Point(3, 1), diff.Position); - } + PixelDifference diff = ex.Reports.Single().Differences.Single(); + Assert.Equal(new Point(3, 1), diff.Position); } } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] - public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] + public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1); - - var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3); - comparer.VerifySimilarity(image, clone); - } + ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1); + + var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3); + comparer.VerifySimilarity(image, clone); } } + } - [Theory] - [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 : unmanaged, IPixel + [Theory] + [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 : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) { - using (Image clone = image.Clone(ctx => ctx.Resize(w, h))) - { - ImageDimensionsMismatchException ex = Assert.ThrowsAny( - () => - { - ImageComparer comparer = Mock.Of(); - comparer.VerifySimilarity(image, clone); - }); - this.Output.WriteLine(ex.Message); - } + ImageDimensionsMismatchException ex = Assert.ThrowsAny( + () => + { + ImageComparer comparer = Mock.Of(); + comparer.VerifySimilarity(image, clone); + }); + this.Output.WriteLine(ex.Message); } } + } - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - ImagingTestCaseUtility.ModifyPixel(clone.Frames[0], 42, 43, 1); + ImagingTestCaseUtility.ModifyPixel(clone.Frames[0], 42, 43, 1); - IEnumerable reports = ImageComparer.Exact.CompareImages(image, clone); + IEnumerable reports = ImageComparer.Exact.CompareImages(image, clone); - PixelDifference difference = reports.Single().Differences.Single(); - Assert.Equal(new Point(42, 43), difference.Position); - } + PixelDifference difference = reports.Single().Differences.Single(); + Assert.Equal(new Point(42, 43), difference.Position); } } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - ExactImageComparer.Instance.CompareImages(image, clone); - } + ExactImageComparer.Instance.CompareImages(image, clone); } } + } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - ImagingTestCaseUtility.ModifyPixel(clone, 42, 24, 1); - ImagingTestCaseUtility.ModifyPixel(clone, 7, 93, 1); - - IEnumerable reports = ExactImageComparer.Instance.CompareImages(image, clone); - - this.Output.WriteLine(reports.Single().ToString()); - PixelDifference[] differences = reports.Single().Differences; - Assert.Equal(2, differences.Length); - Assert.Contains(differences, d => d.Position == new Point(42, 24)); - Assert.Contains(differences, d => d.Position == new Point(7, 93)); - } + ImagingTestCaseUtility.ModifyPixel(clone, 42, 24, 1); + ImagingTestCaseUtility.ModifyPixel(clone, 7, 93, 1); + + IEnumerable reports = ExactImageComparer.Instance.CompareImages(image, clone); + + this.Output.WriteLine(reports.Single().ToString()); + PixelDifference[] differences = reports.Single().Differences; + Assert.Equal(2, differences.Length); + Assert.Contains(differences, d => d.Position == new Point(42, 24)); + Assert.Contains(differences, d => d.Position == new Point(7, 93)); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index f97d1341f3..3d4dd4a65b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,88 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; + +public class MagickReferenceCodecTests { - public class MagickReferenceCodecTests - { - public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; + public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public const PixelTypes PixelTypesToTest32 = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; + public const PixelTypes PixelTypesToTest32 = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - public const PixelTypes PixelTypesToTest64 = - PixelTypes.Rgba32 | PixelTypes.Rgb24 | PixelTypes.Rgba64 | PixelTypes.Rgb48; + public const PixelTypes PixelTypesToTest64 = + PixelTypes.Rgba32 | PixelTypes.Rgb24 | PixelTypes.Rgba64 | PixelTypes.Rgb48; - public const PixelTypes PixelTypesToTest48 = - PixelTypes.Rgba32 | PixelTypes.Rgba64 | PixelTypes.Rgb48; + public const PixelTypes PixelTypesToTest48 = + PixelTypes.Rgba32 | PixelTypes.Rgba64 | PixelTypes.Rgb48; - [Theory] - [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 : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(testImage); + [Theory] + [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 : unmanaged, IPixel + { + string path = TestFile.GetInputFileFullPath(testImage); - var magickDecoder = new MagickReferenceDecoder(); - var sdDecoder = new SystemDrawingReferenceDecoder(); + var magickDecoder = new MagickReferenceDecoder(); + var sdDecoder = new SystemDrawingReferenceDecoder(); - ImageComparer comparer = ImageComparer.Exact; + ImageComparer comparer = ImageComparer.Exact; - using FileStream mStream = File.OpenRead(path); - using FileStream sdStream = File.OpenRead(path); + using FileStream mStream = File.OpenRead(path); + using FileStream sdStream = File.OpenRead(path); - using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); - using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); + using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); + using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - mImage.DebugSave(dummyProvider); + mImage.DebugSave(dummyProvider); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); - } + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); } + } - [Theory] - [WithBlankImages(1, 1, PixelTypesToTest64, TestImages.Png.Rgba64Bpp)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)] - [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)] - [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 : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(testImage); + [Theory] + [WithBlankImages(1, 1, PixelTypesToTest64, TestImages.Png.Rgba64Bpp)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48Bpp)] + [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppInterlaced)] + [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 : unmanaged, IPixel + { + string path = TestFile.GetInputFileFullPath(testImage); - var magickDecoder = new MagickReferenceDecoder(); - var sdDecoder = new SystemDrawingReferenceDecoder(); + var magickDecoder = new MagickReferenceDecoder(); + var sdDecoder = new SystemDrawingReferenceDecoder(); - // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space) - var comparer = ImageComparer.TolerantPercentage(1, 1020); - using FileStream mStream = File.OpenRead(path); - using FileStream sdStream = File.OpenRead(path); - using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); - using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); - ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); + // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space) + var comparer = ImageComparer.TolerantPercentage(1, 1020); + using FileStream mStream = File.OpenRead(path); + using FileStream sdStream = File.OpenRead(path); + using Image mImage = magickDecoder.Decode(DecoderOptions.Default, mStream, default); + using Image sdImage = sdDecoder.Decode(DecoderOptions.Default, sdStream, default); + ImageSimilarityReport report = comparer.CompareImagesOrFrames(mImage, sdImage); - mImage.DebugSave(dummyProvider); + mImage.DebugSave(dummyProvider); - if (TestEnvironment.IsWindows) - { - Assert.True(report.IsEmpty); - } + if (TestEnvironment.IsWindows) + { + Assert.True(report.IsEmpty); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index 3391db58bb..3a3fcefdbe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -1,96 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; + +public class ReferenceDecoderBenchmarks { - public class ReferenceDecoderBenchmarks - { - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public const string SkipBenchmarks = + public const string SkipBenchmarks = #if true - "Benchmark, enable manually!"; + "Benchmark, enable manually!"; #else - null; + null; #endif - public const int DefaultExecutionCount = 50; + public const int DefaultExecutionCount = 50; - public static readonly string[] PngBenchmarkFiles = - { - TestImages.Png.CalliphoraPartial, - TestImages.Png.Kaboom, - TestImages.Png.Bike, - TestImages.Png.Splash, - TestImages.Png.SplashInterlaced - }; - - public static readonly string[] BmpBenchmarkFiles = - { - TestImages.Bmp.NegHeight, - TestImages.Bmp.Car, - TestImages.Bmp.V5Header - }; - - public ReferenceDecoderBenchmarks(ITestOutputHelper output) + public static readonly string[] PngBenchmarkFiles = { - this.Output = output; - } + TestImages.Png.CalliphoraPartial, + TestImages.Png.Kaboom, + TestImages.Png.Bike, + TestImages.Png.Splash, + TestImages.Png.SplashInterlaced + }; - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkMagickPngDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel + public static readonly string[] BmpBenchmarkFiles = { - this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png"); - } + TestImages.Bmp.NegHeight, + TestImages.Bmp.Car, + TestImages.Bmp.V5Header + }; - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png"); - } + public ReferenceDecoderBenchmarks(ITestOutputHelper output) + { + this.Output = output; + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkMagickBmpDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkMagickPngDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png"); + } - [Theory(Skip = SkipBenchmarks)] - [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] - public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); - } + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png"); + } - private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) - { - var measure = new MeasureFixture(this.Output); - measure.Measure( - times, - () => + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkMagickBmpDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp"); + } + + [Theory(Skip = SkipBenchmarks)] + [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] + public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); + } + + private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) + { + var measure = new MeasureFixture(this.Output); + measure.Measure( + times, + () => + { + foreach (string testFile in testFiles) { - foreach (string testFile in testFiles) - { - Image image = TestFile.Create(testFile).CreateRgba32Image(decoder); - image.Dispose(); - } - }, - info); - } + Image image = TestFile.Create(testFile).CreateRgba32Image(decoder); + image.Dispose(); + } + }, + info); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs index 1070da9b48..e080bda9b6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SemaphoreReadMemoryStreamTests.cs @@ -1,91 +1,85 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Xunit; +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests +public class SemaphoreReadMemoryStreamTests { - public class SemaphoreReadMemoryStreamTests - { - private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); - private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); - private readonly byte[] buffer = new byte[128]; + private readonly SemaphoreSlim continueSemaphore = new SemaphoreSlim(0); + private readonly SemaphoreSlim notifyWaitPositionReachedSemaphore = new SemaphoreSlim(0); + private readonly byte[] buffer = new byte[128]; - [Fact] - public void Read_BeforeWaitLimit_ShouldFinish() - { - using Stream stream = this.CreateTestStream(); - int read = stream.Read(this.buffer); - Assert.Equal(this.buffer.Length, read); - } + [Fact] + public void Read_BeforeWaitLimit_ShouldFinish() + { + using Stream stream = this.CreateTestStream(); + int read = stream.Read(this.buffer); + Assert.Equal(this.buffer.Length, read); + } - [Fact] - public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() - { - using Stream stream = this.CreateTestStream(); - int read = await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - Assert.Equal(this.buffer.Length, read); - } + [Fact] + public async Task ReadAsync_BeforeWaitLimit_ShouldFinish() + { + using Stream stream = this.CreateTestStream(); + int read = await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + Assert.Equal(this.buffer.Length, read); + } - [Fact] - public async Task Read_AfterWaitLimit_ShouldPause() - { - using Stream stream = this.CreateTestStream(); - stream.Read(this.buffer); - Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); + [Fact] + public async Task Read_AfterWaitLimit_ShouldPause() + { + using Stream stream = this.CreateTestStream(); + stream.Read(this.buffer); + Assert.Equal(0, this.notifyWaitPositionReachedSemaphore.CurrentCount); - Task readTask = Task.Factory.StartNew( - () => - { - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - stream.Read(this.buffer); - }, - TaskCreationOptions.LongRunning); + Task readTask = Task.Factory.StartNew( + () => + { + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + stream.Read(this.buffer); + }, + TaskCreationOptions.LongRunning); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - await this.notifyWaitPositionReachedSemaphore.WaitAsync(); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - this.continueSemaphore.Release(); - await readTask; - } + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.continueSemaphore.Release(); + await readTask; + } - [Fact] - public async Task ReadAsync_AfterWaitLimit_ShouldPause() - { - using Stream stream = this.CreateTestStream(); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + [Fact] + public async Task ReadAsync_AfterWaitLimit_ShouldPause() + { + using Stream stream = this.CreateTestStream(); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - Task readTask = Task.Factory.StartNew( - async () => - { - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - await stream.ReadAsync(this.buffer, 0, this.buffer.Length); - }, - TaskCreationOptions.LongRunning); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - await this.notifyWaitPositionReachedSemaphore.WaitAsync(); - await Task.Delay(5); - Assert.False(readTask.IsCompleted); - this.continueSemaphore.Release(); - await readTask; - } + Task readTask = Task.Factory.StartNew( + async () => + { + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + await stream.ReadAsync(this.buffer, 0, this.buffer.Length); + }, + TaskCreationOptions.LongRunning); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + await this.notifyWaitPositionReachedSemaphore.WaitAsync(); + await Task.Delay(5); + Assert.False(readTask.IsCompleted); + this.continueSemaphore.Release(); + await readTask; + } - private Stream CreateTestStream(int size = 1024, int waitAfterPosition = 256) - { - byte[] buffer = new byte[size]; - return new SemaphoreReadMemoryStream(buffer, waitAfterPosition, this.notifyWaitPositionReachedSemaphore, this.continueSemaphore); - } + private Stream CreateTestStream(int size = 1024, int waitAfterPosition = 256) + { + byte[] buffer = new byte[size]; + return new SemaphoreReadMemoryStream(buffer, waitAfterPosition, this.notifyWaitPositionReachedSemaphore, this.continueSemaphore); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index e7e215d708..91df7d1496 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -1,112 +1,108 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests +namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; + +public class SystemDrawingReferenceCodecTests { - public class SystemDrawingReferenceCodecTests - { - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public SystemDrawingReferenceCodecTests(ITestOutputHelper output) => this.Output = output; + public SystemDrawingReferenceCodecTests(ITestOutputHelper output) => this.Output = output; - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); - string fileName = provider.Utility.GetTestOutputFileName("png"); - sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); - } + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + using System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image); + string fileName = provider.Utility.GetTestOutputFileName("png"); + sdBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); + } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) + where TPixel : unmanaged, IPixel + { + string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using var sdBitmap = new System.Drawing.Bitmap(path); - using Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); - image.DebugSave(dummyProvider); - } + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image image = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); + image.DebugSave(dummyProvider); + } - private static string SavePng(TestImageProvider provider, PngColorType pngColorType) - where TPixel : unmanaged, IPixel + private static string SavePng(TestImageProvider provider, PngColorType pngColorType) + where TPixel : unmanaged, IPixel + { + using Image sourceImage = provider.GetImage(); + if (pngColorType != PngColorType.RgbWithAlpha) { - using Image sourceImage = provider.GetImage(); - if (pngColorType != PngColorType.RgbWithAlpha) - { - sourceImage.Mutate(c => c.MakeOpaque()); - } - - var encoder = new PngEncoder { ColorType = pngColorType }; - return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); + sourceImage.Mutate(c => c.MakeOpaque()); } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) - where TPixel : unmanaged, IPixel + var encoder = new PngEncoder { ColorType = pngColorType }; + return provider.Utility.SaveTestOutputFile(sourceImage, "png", encoder); + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (TestEnvironment.IsLinux) { - if (TestEnvironment.IsLinux) - { - return; - } - - string path = SavePng(provider, PngColorType.RgbWithAlpha); - - using var sdBitmap = new System.Drawing.Bitmap(path); - using Image original = provider.GetImage(); - using Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); + return; } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] - public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - string path = SavePng(provider, PngColorType.Rgb); + string path = SavePng(provider, PngColorType.RgbWithAlpha); - using Image original = provider.GetImage(); - using var sdBitmap = new System.Drawing.Bitmap(path); - using Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap); - ImageComparer comparer = ImageComparer.Exact; - comparer.VerifySimilarity(original, resaved); - } + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image original = provider.GetImage(); + using Image resaved = SystemDrawingBridge.From32bppArgbSystemDrawingBitmap(sdBitmap); + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); + } - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] - public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) - where TPixel : unmanaged, IPixel - { - string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); - using FileStream stream = File.OpenRead(path); - using Image image = SystemDrawingReferenceDecoder.Instance.Decode(DecoderOptions.Default, stream, default); - image.DebugSave(dummyProvider); - } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] + public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + string path = SavePng(provider, PngColorType.Rgb); - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void SaveWithReferenceEncoder(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); - } + using Image original = provider.GetImage(); + using var sdBitmap = new System.Drawing.Bitmap(path); + using Image resaved = SystemDrawingBridge.From24bppRgbSystemDrawingBitmap(sdBitmap); + ImageComparer comparer = ImageComparer.Exact; + comparer.VerifySimilarity(original, resaved); + } + + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) + where TPixel : unmanaged, IPixel + { + string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); + using FileStream stream = File.OpenRead(path); + using Image image = SystemDrawingReferenceDecoder.Instance.Decode(DecoderOptions.Default, stream, default); + image.DebugSave(dummyProvider); + } + + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void SaveWithReferenceEncoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + provider.Utility.SaveTestOutputFile(image, "png", SystemDrawingReferenceEncoder.Png); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index ce7a3ebc4b..36c078dc0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.IO; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; @@ -11,128 +9,126 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class TestEnvironmentTests { - public class TestEnvironmentTests - { - public TestEnvironmentTests(ITestOutputHelper output) - => this.Output = output; + public TestEnvironmentTests(ITestOutputHelper output) + => this.Output = output; - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - private void CheckPath(string path) - { - this.Output.WriteLine(path); - Assert.True(Directory.Exists(path)); - } + private void CheckPath(string path) + { + this.Output.WriteLine(path); + Assert.True(Directory.Exists(path)); + } - [Fact] - public void SolutionDirectoryFullPath() - => this.CheckPath(TestEnvironment.SolutionDirectoryFullPath); + [Fact] + public void SolutionDirectoryFullPath() + => this.CheckPath(TestEnvironment.SolutionDirectoryFullPath); - [Fact] - public void InputImagesDirectoryFullPath() - => this.CheckPath(TestEnvironment.InputImagesDirectoryFullPath); + [Fact] + public void InputImagesDirectoryFullPath() + => this.CheckPath(TestEnvironment.InputImagesDirectoryFullPath); - [Fact] - public void ExpectedOutputDirectoryFullPath() - => this.CheckPath(TestEnvironment.ReferenceOutputDirectoryFullPath); + [Fact] + public void ExpectedOutputDirectoryFullPath() + => this.CheckPath(TestEnvironment.ReferenceOutputDirectoryFullPath); - [Fact] - public void GetReferenceOutputFileName() - { - string actual = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, @"foo\bar\lol.jpeg"); - string expected = TestEnvironment.GetReferenceOutputFileName(actual); + [Fact] + public void GetReferenceOutputFileName() + { + string actual = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, @"foo\bar\lol.jpeg"); + string expected = TestEnvironment.GetReferenceOutputFileName(actual); - this.Output.WriteLine(expected); - Assert.Contains(TestEnvironment.ReferenceOutputDirectoryFullPath, expected); - } + this.Output.WriteLine(expected); + Assert.Contains(TestEnvironment.ReferenceOutputDirectoryFullPath, expected); + } - [Theory] - [InlineData("lol/foo.png", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] - [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] - [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebpEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) + [Theory] + [InlineData("lol/foo.png", typeof(SystemDrawingReferenceEncoder))] + [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceEncoder))] + [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] + [InlineData("lol/Baz.gif", typeof(GifEncoder))] + [InlineData("lol/foobar.webp", typeof(WebpEncoder))] + public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) + { + if (!TestEnvironment.IsWindows) { - if (!TestEnvironment.IsWindows) - { - return; - } - - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); - Assert.IsType(expectedEncoderType, encoder); + return; } - [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] - [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] - [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebpDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) - { - if (!TestEnvironment.IsWindows) - { - return; - } + IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); + Assert.IsType(expectedEncoderType, encoder); + } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); - Assert.IsType(expectedDecoderType, decoder); + [Theory] + [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] + [InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))] + [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] + [InlineData("lol/Baz.gif", typeof(GifDecoder))] + [InlineData("lol/foobar.webp", typeof(WebpDecoder))] + public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) + { + if (!TestEnvironment.IsWindows) + { + return; } - [Theory] - [InlineData("lol/foo.png", typeof(ImageSharpPngEncoderWithDefaultConfiguration))] - [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] - [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] - [InlineData("lol/Baz.gif", typeof(GifEncoder))] - [InlineData("lol/foobar.webp", typeof(WebpEncoder))] - public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) - { - if (!TestEnvironment.IsLinux) - { - return; - } + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); + Assert.IsType(expectedDecoderType, decoder); + } - IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); - Assert.IsType(expectedEncoderType, encoder); + [Theory] + [InlineData("lol/foo.png", typeof(ImageSharpPngEncoderWithDefaultConfiguration))] + [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] + [InlineData("lol/Baz.JPG", typeof(JpegEncoder))] + [InlineData("lol/Baz.gif", typeof(GifEncoder))] + [InlineData("lol/foobar.webp", typeof(WebpEncoder))] + public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) + { + if (!TestEnvironment.IsLinux) + { + return; } - [Theory] - [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] - [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] - [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] - [InlineData("lol/Baz.gif", typeof(GifDecoder))] - [InlineData("lol/foobar.webp", typeof(WebpDecoder))] - public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) - { - if (!TestEnvironment.IsLinux) - { - return; - } + IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); + Assert.IsType(expectedEncoderType, encoder); + } - IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); - Assert.IsType(expectedDecoderType, decoder); + [Theory] + [InlineData("lol/foo.png", typeof(MagickReferenceDecoder))] + [InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))] + [InlineData("lol/Baz.JPG", typeof(JpegDecoder))] + [InlineData("lol/Baz.gif", typeof(GifDecoder))] + [InlineData("lol/foobar.webp", typeof(WebpDecoder))] + public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) + { + if (!TestEnvironment.IsLinux) + { + return; } - // RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework: - // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 - public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); + Assert.IsType(expectedDecoderType, decoder); + } - [ConditionalFact(nameof(IsNot32BitNetFramework))] - public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() - { - static void FailingCode() - { - Assert.False(true); - } + // RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework: + // https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30 + public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess; - Assert.ThrowsAny(() => RemoteExecutor.Invoke(FailingCode).Dispose()); + [ConditionalFact(nameof(IsNot32BitNetFramework))] + public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest() + { + static void FailingCode() + { + Assert.False(true); } + + Assert.ThrowsAny(() => RemoteExecutor.Invoke(FailingCode).Dispose()); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index d6c5067a41..998bd3ea22 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -1,108 +1,104 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; +namespace SixLabors.ImageSharp.Tests; -namespace SixLabors.ImageSharp.Tests +public class TestImageExtensionsTests { - public class TestImageExtensionsTests + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( + TestImageProvider provider) + where TPixel : unmanaged, IPixel { - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( - TestImageProvider provider) - where TPixel : unmanaged, IPixel + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.CompareToReferenceOutput(provider); - } + image.CompareToReferenceOutput(provider); } + } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( - TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); - } + Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); } + } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_DoNotAppendPixelType( - TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_DoNotAppendPixelType( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - image.DebugSave(provider, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); - } + image.DebugSave(provider, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, appendPixelTypeToFileName: false); } + } - [Theory] - [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] - public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] + public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); - } + Assert.ThrowsAny(() => image.CompareToReferenceOutput(provider)); } + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenSimilar(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenSimilar(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + using (Image clone = image.Clone()) { - using (Image clone = image.Clone()) - { - clone.CompareToOriginal(provider, ImageComparer.Exact); - } + clone.CompareToOriginal(provider, ImageComparer.Exact); } } + } - [Theory] - [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) - { - ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); + ImagingTestCaseUtility.ModifyPixel(image, 3, 1, 1); - Assert.ThrowsAny(() => - { - image.CompareToOriginal(provider, ImageComparer.Exact); - }); - } + Assert.ThrowsAny(() => + { + image.CompareToOriginal(provider, ImageComparer.Exact); + }); } + } - [Theory] - [WithBlankImages(10, 10, PixelTypes.Rgba32)] - public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithBlankImages(10, 10, PixelTypes.Rgba32)] + public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) { - using (Image image = provider.GetImage()) + Assert.ThrowsAny(() => { - Assert.ThrowsAny(() => - { - image.CompareToOriginal(provider, Mock.Of()); - }); - } + image.CompareToOriginal(provider, Mock.Of()); + }); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index f37e89c415..5889907f09 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -1,459 +1,453 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Collections.Concurrent; -using System.IO; -using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - -using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class TestImageProviderTests { - public class TestImageProviderTests + public static readonly TheoryData BasicData = new() { - public static readonly TheoryData BasicData = new() - { - TestImageProvider.Blank(10, 20), - TestImageProvider.Blank(10, 20), - }; + TestImageProvider.Blank(10, 20), + TestImageProvider.Blank(10, 20), + }; - public static readonly TheoryData FileData = new() - { - TestImageProvider.File(TestImages.Bmp.Car), - TestImageProvider.File(TestImages.Bmp.F) - }; + public static readonly TheoryData FileData = new() + { + TestImageProvider.File(TestImages.Bmp.Car), + TestImageProvider.File(TestImages.Bmp.F) + }; - public static string[] AllBmpFiles = { TestImages.Bmp.F, TestImages.Bmp.Bit8 }; + public static string[] AllBmpFiles = { TestImages.Bmp.F, TestImages.Bmp.Bit8 }; - public TestImageProviderTests(ITestOutputHelper output) => this.Output = output; + public TestImageProviderTests(ITestOutputHelper output) => this.Output = output; - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - /// - /// 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 : unmanaged, IPixel => - new(3, 3); + /// + /// 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 : unmanaged, IPixel => + new(3, 3); - [Theory] - [MemberData(nameof(BasicData))] - public void Blank_MemberData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); + [Theory] + [MemberData(nameof(BasicData))] + public void Blank_MemberData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); - } + Assert.True(img.Width * img.Height > 0); + } - [Theory] - [MemberData(nameof(FileData))] - public void File_MemberData(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); - this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); + [Theory] + [MemberData(nameof(FileData))] + public void File_MemberData(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); + this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); - Image img = provider.GetImage(); + Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); - } + Assert.True(img.Width * img.Height > 0); + } - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( - TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) { - if (!TestEnvironment.Is64BitProcess) - { - // We don't cache with the 32 bit build. - return; - } + // We don't cache with the 32 bit build. + return; + } - Assert.NotNull(provider.Utility.SourceFileOrDescription); + Assert.NotNull(provider.Utility.SourceFileOrDescription); - TestDecoder.DoTestThreadSafe( - () => - { - string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); + TestDecoder.DoTestThreadSafe( + () => + { + string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); - var decoder = new TestDecoder(); - decoder.InitCaller(testName); + var decoder = new TestDecoder(); + decoder.InitCaller(testName); - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - provider.GetImage(decoder); - Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); - }); - } + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + }); + } - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + const string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); - TestDecoderWithParameters.DoTestThreadSafe( - () => + TestDecoderWithParameters decoder1 = new(); + TestDecoderWithParametersOptions options1 = new() { - const string testName = nameof(this - .GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); + Param1 = "Lol", + Param2 = 42 + }; - TestDecoderWithParameters decoder1 = new(); - TestDecoderWithParametersOptions options1 = new() - { - Param1 = "Lol", - Param2 = 42 - }; + decoder1.InitCaller(testName); - decoder1.InitCaller(testName); + TestDecoderWithParameters decoder2 = new(); + TestDecoderWithParametersOptions options2 = new() + { + Param1 = "LoL", + Param2 = 42 + }; - TestDecoderWithParameters decoder2 = new(); - TestDecoderWithParametersOptions options2 = new() - { - Param1 = "LoL", - Param2 = 42 - }; + decoder2.InitCaller(testName); - decoder2.InitCaller(testName); + provider.GetImage(decoder1, options1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - provider.GetImage(decoder1, options1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + provider.GetImage(decoder2, options2); + Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); + }); + } - provider.GetImage(decoder2, options2); - Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); - }); + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( + TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + // We don't cache with the 32 bit build. + return; } - [Theory] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( - TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - if (!TestEnvironment.Is64BitProcess) - { - // We don't cache with the 32 bit build. - return; - } + Assert.NotNull(provider.Utility.SourceFileOrDescription); - Assert.NotNull(provider.Utility.SourceFileOrDescription); + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + const string testName = nameof(this + .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); - TestDecoderWithParameters.DoTestThreadSafe( - () => + TestDecoderWithParameters decoder1 = new(); + TestDecoderWithParametersOptions options1 = new() { - const string testName = nameof(this - .GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); + Param1 = "Lol", + Param2 = 666 + }; - TestDecoderWithParameters decoder1 = new(); - TestDecoderWithParametersOptions options1 = new() - { - Param1 = "Lol", - Param2 = 666 - }; + decoder1.InitCaller(testName); - decoder1.InitCaller(testName); + TestDecoderWithParameters decoder2 = new(); + TestDecoderWithParametersOptions options2 = new() + { + Param1 = "Lol", + Param2 = 666 + }; - TestDecoderWithParameters decoder2 = new(); - TestDecoderWithParametersOptions options2 = new() - { - Param1 = "Lol", - Param2 = 666 - }; + decoder2.InitCaller(testName); - decoder2.InitCaller(testName); + provider.GetImage(decoder1, options1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - provider.GetImage(decoder1, options1); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + provider.GetImage(decoder2, options2); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + }); + } - provider.GetImage(decoder2, options2); - Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); - }); - } + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) + where TPixel : unmanaged, IPixel => + Assert.Empty(provider.Utility.OutputSubfolderName); + + [Theory] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] + [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] + [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] + public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) + where TPixel : unmanaged, IPixel => + Assert.Equal(expected, provider.PixelType); + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) - where TPixel : unmanaged, IPixel => - Assert.Empty(provider.Utility.OutputSubfolderName); - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] - [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] - [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] - public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TPixel : unmanaged, IPixel => - Assert.Equal(expected, provider.PixelType); - - [Theory] - [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] - public void SaveTestOutputFileMultiFrame(TestImageProvider provider) - where TPixel : unmanaged, IPixel + Assert.True(files.Length > 2); + foreach (string path in files) { - using Image image = provider.GetImage(); - string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); - - Assert.True(files.Length > 2); - foreach (string path in files) - { - this.Output.WriteLine(path); - Assert.True(File.Exists(path)); - } + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); } + } - [Theory] - [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] - [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] - public void Use_WithBasicTestPatternImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - img.DebugSave(provider); - } + [Theory] + [WithBasicTestPatternImages(50, 100, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] + public void Use_WithBasicTestPatternImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image img = provider.GetImage(); + img.DebugSave(provider); + } - [Theory] - [WithBlankImages(42, 666, PixelTypes.All, "hello")] - public void Use_WithBlankImagesAttribute_WithAllPixelTypes( - TestImageProvider provider, - string message) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); + [Theory] + [WithBlankImages(42, 666, PixelTypes.All, "hello")] + public void Use_WithBlankImagesAttribute_WithAllPixelTypes( + TestImageProvider provider, + string message) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); - } + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } - [Theory] - [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] - public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); + [Theory] + [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] + public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); - Assert.Equal(42, img.Width); - Assert.Equal(666, img.Height); - Assert.Equal("hello", message); - } + Assert.Equal(42, img.Width); + Assert.Equal(666, img.Height); + Assert.Equal("hello", message); + } - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] - [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] - public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); - using Image img = provider.GetImage(); - Assert.True(img.Width * img.Height > 0); + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] + [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] + public void Use_WithFileAttribute(TestImageProvider provider, int yo) + where TPixel : unmanaged, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + using Image img = provider.GetImage(); + Assert.True(img.Width * img.Height > 0); - Assert.Equal(123, yo); + Assert.Equal(123, yo); - string fn = provider.Utility.GetTestOutputFileName("jpg"); - this.Output.WriteLine(fn); - } + string fn = provider.Utility.GetTestOutputFileName("jpg"); + this.Output.WriteLine(fn); + } - [Theory] - [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] - public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => EnsureCustomConfigurationIsApplied(provider); + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] + public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => EnsureCustomConfigurationIsApplied(provider); - [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void Use_WithFileCollection(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Assert.NotNull(provider.Utility.SourceFileOrDescription); - using Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png"); - } + [Theory] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void Use_WithFileCollection(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + using Image image = provider.GetImage(); + provider.Utility.SaveTestOutputFile(image, "png"); + } - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] - public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Theory] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] + public void Use_WithMemberFactoryAttribute(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); + Assert.Equal(3, img.Width); + if (provider.PixelType == PixelTypes.Rgba32) { - Image img = provider.GetImage(); - Assert.Equal(3, img.Width); - if (provider.PixelType == PixelTypes.Rgba32) - { - Assert.IsType>(img); - } + Assert.IsType>(img); } + } - [Theory] - [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - Image img = provider.GetImage(); - Assert.Equal(10, img.Width); - Assert.Equal(20, img.Height); + [Theory] + [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] + public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Image img = provider.GetImage(); + Assert.Equal(10, img.Width); + Assert.Equal(20, img.Height); - Buffer2D pixels = img.GetRootFramePixelBuffer(); - Rgba32 rgba = default; - for (int y = 0; y < pixels.Height; y++) + Buffer2D pixels = img.GetRootFramePixelBuffer(); + Rgba32 rgba = default; + for (int y = 0; y < pixels.Height; y++) + { + for (int x = 0; x < pixels.Width; x++) { - for (int x = 0; x < pixels.Width; x++) - { - pixels[x, y].ToRgba32(ref rgba); + pixels[x, y].ToRgba32(ref rgba); - Assert.Equal(255, rgba.R); - Assert.Equal(100, rgba.G); - Assert.Equal(50, rgba.B); - Assert.Equal(200, rgba.A); - } + Assert.Equal(255, rgba.R); + Assert.Equal(100, rgba.G); + Assert.Equal(50, rgba.B); + Assert.Equal(200, rgba.A); } } + } - [Theory] - [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] - public void Use_WithTestPatternImages(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using Image img = provider.GetImage(); - img.DebugSave(provider); - } + [Theory] + [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image img = provider.GetImage(); + img.DebugSave(provider); + } - [Theory] - [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] - public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) - where TPixel : unmanaged, IPixel - => EnsureCustomConfigurationIsApplied(provider); + [Theory] + [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] + public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => EnsureCustomConfigurationIsApplied(provider); - private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) - where TPixel : unmanaged, IPixel + private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (provider.GetImage()) { - using (provider.GetImage()) - { - var customConfiguration = Configuration.CreateDefaultInstance(); - provider.Configuration = customConfiguration; + var customConfiguration = Configuration.CreateDefaultInstance(); + provider.Configuration = customConfiguration; - using Image image2 = provider.GetImage(); - using Image image3 = provider.GetImage(); - Assert.Same(customConfiguration, image2.GetConfiguration()); - Assert.Same(customConfiguration, image3.GetConfiguration()); - } + using Image image2 = provider.GetImage(); + using Image image3 = provider.GetImage(); + Assert.Same(customConfiguration, image2.GetConfiguration()); + Assert.Same(customConfiguration, image3.GetConfiguration()); } + } - private class TestDecoder : IImageDecoderSpecialized - { - // Couldn't make xUnit happy without this hackery: - private static readonly ConcurrentDictionary InvocationCounts = new(); + private class TestDecoder : IImageDecoderSpecialized + { + // Couldn't make xUnit happy without this hackery: + private static readonly ConcurrentDictionary InvocationCounts = new(); - private static readonly object Monitor = new(); + private static readonly object Monitor = new(); - private string callerName; + private string callerName; - public static void DoTestThreadSafe(Action action) + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) { - lock (Monitor) - { - action(); - } + action(); } + } - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCounts[this.callerName]++; - return new Image(42, 42); - } + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + InvocationCounts[this.callerName]++; + return new Image(42, 42); + } - public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + public Image Decode(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); - 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; - } + internal void InitCaller(string name) + { + this.callerName = name; + InvocationCounts[name] = 0; } + } - private class TestDecoderWithParameters : IImageDecoderSpecialized - { - private static readonly ConcurrentDictionary InvocationCounts = new(); + private class TestDecoderWithParameters : IImageDecoderSpecialized + { + private static readonly ConcurrentDictionary InvocationCounts = new(); - private static readonly object Monitor = new(); + private static readonly object Monitor = new(); - private string callerName; + private string callerName; - public static void DoTestThreadSafe(Action action) + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) { - lock (Monitor) - { - action(); - } + action(); } + } - public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); - - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); + public IImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - InvocationCounts[this.callerName]++; - return new Image(42, 42); - } + public Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode((TestDecoderWithParametersOptions)new() { GeneralOptions = options }, stream, cancellationToken); - public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) - => this.Decode(options, stream, cancellationToken); + public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + InvocationCounts[this.callerName]++; + return new Image(42, 42); + } - internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; + public Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); - internal void InitCaller(string name) - { - this.callerName = name; - InvocationCounts[name] = 0; - } - } + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; - private class TestDecoderOptions : ISpecializedDecoderOptions + internal void InitCaller(string name) { - public DecoderOptions GeneralOptions { get; set; } = new(); + this.callerName = name; + InvocationCounts[name] = 0; } + } - private class TestDecoderWithParametersOptions : ISpecializedDecoderOptions - { - public string Param1 { get; set; } + private class TestDecoderOptions : ISpecializedDecoderOptions + { + public DecoderOptions GeneralOptions { get; set; } = new(); + } + + private class TestDecoderWithParametersOptions : ISpecializedDecoderOptions + { + public string Param1 { get; set; } - public int Param2 { get; set; } + public int Param2 { get; set; } - public DecoderOptions GeneralOptions { get; set; } = new(); - } + public DecoderOptions GeneralOptions { get; set; } = new(); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 44183acdd8..33ee68068f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -1,137 +1,131 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; - -using Xunit; using Xunit.Abstractions; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +public class TestUtilityExtensionsTests { - public class TestUtilityExtensionsTests + public TestUtilityExtensionsTests(ITestOutputHelper output) { - public TestUtilityExtensionsTests(ITestOutputHelper output) - { - this.Output = output; - } + this.Output = output; + } - private ITestOutputHelper Output { get; } + private ITestOutputHelper Output { get; } - public static Image CreateTestImage() - where TPixel : unmanaged, IPixel - { - var image = new Image(10, 10); + public static Image CreateTestImage() + where TPixel : unmanaged, IPixel + { + var image = new Image(10, 10); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int i = 0; i < 10; i++) + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int i = 0; i < 10; i++) + { + for (int j = 0; j < 10; j++) { - for (int j = 0; j < 10; j++) - { - var v = new Vector4(i, j, 0, 1); - v /= 10; + var v = new Vector4(i, j, 0, 1); + v /= 10; - var color = default(TPixel); - color.FromVector4(v); + var color = default(TPixel); + color.FromVector4(v); - pixels[i, j] = color; - } + pixels[i, j] = color; } - - return image; } - [Theory] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] - [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] - public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TPixel : unmanaged, IPixel - { - Image a = provider.GetImage(); - Image b = provider.GetImage(x => x.OilPaint(3, 2)); + return image; + } - Assert.False(a.IsEquivalentTo(b, compareAlpha)); - } + [Theory] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] + public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) + where TPixel : unmanaged, IPixel + { + Image a = provider.GetImage(); + Image b = provider.GetImage(x => x.OilPaint(3, 2)); - [Theory] - [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 : unmanaged, IPixel - { - Image a = provider.GetImage(); - Image b = provider.GetImage(); + Assert.False(a.IsEquivalentTo(b, compareAlpha)); + } - Assert.True(a.IsEquivalentTo(b, compareAlpha)); - } + [Theory] + [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 : unmanaged, IPixel + { + Image a = provider.GetImage(); + Image b = provider.GetImage(); - [Theory] - [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] - [InlineData(PixelTypes.Argb32, typeof(Argb32))] - [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] - public void ToType(PixelTypes pt, Type expectedType) - { - Assert.Equal(pt.GetClrType(), expectedType); - } + Assert.True(a.IsEquivalentTo(b, compareAlpha)); + } - [Theory] - [InlineData(typeof(Rgba32), PixelTypes.Rgba32)] - [InlineData(typeof(Argb32), PixelTypes.Argb32)] - public void GetPixelType(Type clrType, PixelTypes expectedPixelType) - { - Assert.Equal(expectedPixelType, clrType.GetPixelType()); - } + [Theory] + [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] + [InlineData(PixelTypes.Argb32, typeof(Argb32))] + [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] + public void ToType(PixelTypes pt, Type expectedType) + { + Assert.Equal(pt.GetClrType(), expectedType); + } - private static void AssertContainsPixelType( - PixelTypes pt, - IEnumerable> pixelTypesExp) - { - Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - } + [Theory] + [InlineData(typeof(Rgba32), PixelTypes.Rgba32)] + [InlineData(typeof(Argb32), PixelTypes.Argb32)] + public void GetPixelType(Type clrType, PixelTypes expectedPixelType) + { + Assert.Equal(expectedPixelType, clrType.GetPixelType()); + } - [Fact] - public void ExpandAllTypes_1() - { - PixelTypes pixelTypes = PixelTypes.A8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; + private static void AssertContainsPixelType( + PixelTypes pt, + IEnumerable> pixelTypesExp) + { + Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); + } - IEnumerable> expanded = pixelTypes.ExpandAllTypes(); + [Fact] + public void ExpandAllTypes_1() + { + PixelTypes pixelTypes = PixelTypes.A8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; - Assert.Equal(4, expanded.Count()); + IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - AssertContainsPixelType(PixelTypes.A8, expanded); - AssertContainsPixelType(PixelTypes.Bgr565, expanded); - AssertContainsPixelType(PixelTypes.HalfVector2, expanded); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - } + Assert.Equal(4, expanded.Count()); - [Fact] - public void ExpandAllTypes_2() - { - PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Abgr32 | PixelTypes.RgbaVector; + AssertContainsPixelType(PixelTypes.A8, expanded); + AssertContainsPixelType(PixelTypes.Bgr565, expanded); + AssertContainsPixelType(PixelTypes.HalfVector2, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + } - IEnumerable> expanded = pixelTypes.ExpandAllTypes(); + [Fact] + public void ExpandAllTypes_2() + { + PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Abgr32 | PixelTypes.RgbaVector; - Assert.Equal(4, expanded.Count()); + IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - AssertContainsPixelType(PixelTypes.Bgra32, expanded); - AssertContainsPixelType(PixelTypes.Abgr32, expanded); - AssertContainsPixelType(PixelTypes.RgbaVector, expanded); - } + Assert.Equal(4, expanded.Count()); - [Fact] - public void ToTypes_All() - { - KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + AssertContainsPixelType(PixelTypes.Bgra32, expanded); + AssertContainsPixelType(PixelTypes.Abgr32, expanded); + AssertContainsPixelType(PixelTypes.RgbaVector, expanded); + } - Assert.True(expanded.Length >= TestUtils.GetAllPixelTypes().Length - 2); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); - } + [Fact] + public void ToTypes_All() + { + KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + + Assert.True(expanded.Length >= TestUtils.GetAllPixelTypes().Length - 2); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); } } diff --git a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs index db9ebdc9f5..eca6529009 100644 --- a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs +++ b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs @@ -1,33 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Diagnostics; using System.Reflection; using Xunit.Sdk; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute - { - private readonly int expected = 0; + private readonly int expected = 0; - public ValidateDisposedMemoryAllocationsAttribute() - : this(0) - { - } + public ValidateDisposedMemoryAllocationsAttribute() + : this(0) + { + } - public ValidateDisposedMemoryAllocationsAttribute(int expected) - => this.expected = expected; + public ValidateDisposedMemoryAllocationsAttribute(int expected) + => this.expected = expected; - public override void Before(MethodInfo methodUnderTest) - => MemoryAllocatorValidator.MonitorAllocations(); + public override void Before(MethodInfo methodUnderTest) + => MemoryAllocatorValidator.MonitorAllocations(); - public override void After(MethodInfo methodUnderTest) - { - MemoryAllocatorValidator.ValidateAllocations(this.expected); - MemoryAllocatorValidator.StopMonitoringAllocations(); - } + public override void After(MethodInfo methodUnderTest) + { + MemoryAllocatorValidator.ValidateAllocations(this.expected); + MemoryAllocatorValidator.StopMonitoringAllocations(); } } diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs index 79715e5f04..ace55fadae 100644 --- a/tests/ImageSharp.Tests/VectorAssert.cs +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -1,95 +1,91 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Collections.Generic; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit; // ReSharper disable MemberHidesStaticFromOuterClass -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests; + +/// +/// Class to perform simple image comparisons. +/// +public static class VectorAssert { - /// - /// Class to perform simple image comparisons. - /// - public static class VectorAssert + public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) + where TPixel : unmanaged, IPixel + { + Equal(expected.ToVector4(), actual.ToVector4(), precision); + } + + public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer { - public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) - where TPixel : unmanaged, IPixel + private readonly int precision; + + public PrecisionEqualityComparer(int precision) + { + this.precision = precision; + } + + public bool Equals(Vector2 x, Vector2 y) + { + return this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y); + } + + public bool Equals(Vector3 x, Vector3 y) + { + 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 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) { - Equal(expected.ToVector4(), actual.ToVector4(), precision); + return Math.Round(x, this.precision) == Math.Round(y, this.precision); } - public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) + public int GetHashCode(Vector4 obj) { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + return obj.GetHashCode(); } - public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) + public int GetHashCode(Vector3 obj) { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + return obj.GetHashCode(); } - public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) + public int GetHashCode(Vector2 obj) { - Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + return obj.GetHashCode(); } - private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer + public int GetHashCode(float obj) { - private readonly int precision; - - public PrecisionEqualityComparer(int precision) - { - this.precision = precision; - } - - public bool Equals(Vector2 x, Vector2 y) - { - return this.Equals(x.X, y.X) && - this.Equals(x.Y, y.Y); - } - - public bool Equals(Vector3 x, Vector3 y) - { - 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 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) - { - return Math.Round(x, this.precision) == Math.Round(y, this.precision); - } - - public int GetHashCode(Vector4 obj) - { - return obj.GetHashCode(); - } - - public int GetHashCode(Vector3 obj) - { - return obj.GetHashCode(); - } - - public int GetHashCode(Vector2 obj) - { - return obj.GetHashCode(); - } - - public int GetHashCode(float obj) - { - return obj.GetHashCode(); - } + return obj.GetHashCode(); } } }